角色介绍
sskaje:提起疑问者,疑似国人
narfbg:CI作者之一
sskaje:
在CI3和CI4中,用来实现session的redis驱动与memcached驱动关于session key锁的设计是不对的.
估计是sskaje一针见血的表达导致了作者的第一次回复会带有不满情绪..
CI4中,代码老套,虽然更加兼容但是仍然有bug
CI4中关于session锁的循环中的伪代码如下:
if redis::ttl(lock_key) > 0 then sleep 1s continue loop redis::set(lock_key, ttl)
这里sskaje拿出了CI4中的代码,估计是当时CI4中代码还没更新,作者也提到在后来的版本中升级了这段代码.但是作者的方法就是接下来的这段代码,直接kill掉了其他的并发请求.
没错,两个并发请求会同时通过ttl(有效时间)>0的检查,并且同时上锁.并发的请求对session执行写操作可能会导致数据污染,然而
1.并发的session读操作
2.一个请求执行session写操作,多个并发请求执行session读操作
都能正常执行
在CI3的某个提交中,引进了如下逻辑(伪代码):
if redis::ttl(lock_key) > 0 then sleep 1s continue loop if redis::ttl(lock_key) == key-not-exists then redis::setNx(lock_key, ttl) else redis::set(lock_key, ttl)
然而并没有什么卵用反而错的更离谱了
与CI4中的情况一样,都是并发请求可以同时越过ttl>0并且匹配到ttl===-2的条件,因为此时还不存在key.请求1在执行redis::setNx后返回成功,请求2因为已经存在了key而失败了.因此请求2将无法获取到session数据
redis::setNx与redis::set不同点在于只对不存在的key生效,若key已存在则会返回false,然后sskaje给出了简单的解决方案:
redis:
protected function _get_lock($session_id) { // PHP 7 reuses the SessionHandler object on regeneration, // so we need to check here if the lock key is for the // correct session ID. if ($this->_lock_key === $this->_key_prefix.$session_id.':lock') { log_message('error', 'SESSION Redis _get_lock'); return $this->_redis->setTimeout($this->_lock_key, 300); } // 30 attempts to obtain a lock, in case another request already has it $lock_key = $this->_key_prefix.$session_id.':lock'; $attempt = 0

