文章归档友情连接照片地图

Memcached 踩坑小记

分类:PHP编程  作者:rming  时间:2015-05-16

场景

        公司的开发环境情况比较混乱,有人在使用开发机上(局域网共享)开发,有人在本机vagrant,而代码版本管理上也存在同样的情况,大多数项目在使用svn,仅有几个在使用 git,而在 git 项目的开发工作流中,缺少 pull-request 的 review ,有人直接在 master 分支上开发,有人单开分支开发后合并到 master,可以说,情况之混乱简直罕见。

        正是因为开发环境的不同导致了这次的问题。

发现问题

  • 我发现在 vagrant 中的 memcached 设置的缓存在过期时间后仍然还在,不能过期销毁!?
  • 把 vagrant 中的 memcached server 设置为开发机,发现缓存的销毁时间总会提前几十秒!?

为什么?

        查看代码,代码是 PHP Yii 框架的,Yii框架的 CMemCache 类调用 PHP 的 memcached 扩展,最终操作 memcached。

Yii 框架代码:

//CMemCache +118
return $this->_cache=$this->useMemcached ? new Memcached : new Memcache;
//CMemCache +171
protected function setValue($key,$value,$expire)
{
if($expire>0)
$expire+=time();
else
$expire=0;
return $this->useMemcached ? $this->_cache->set($key,$value,$expire) : $this->_cache->set($key,$value,0,$expire);
}

        从代码中可以看出来,PHP memcache 扩展 和 memcached 扩展在设置有效期的时候参数顺序是有差别的,但是问题不在这,因为框架代码为我们填了这个坑。

PHP Memcached 超时时间 文档:

一些存储命令在发送时会包含一个失效值(与一个元素或一个客户端操作请求相关)到服务端。所有这类用法,实际发送的值可以是一个Unix时间戳(自1970年1月1日起至失效时间的整型秒数),或者是一个从现在算起的以秒为单位的数字。对于后一种情况,这个秒数不能超过60×60×24×30(30天时间的秒数);如果失效的值大于这个值, 服务端会将其作为一个真实的Unix时间戳来处理而不是自当前时间的偏移。
如果失效值被设置为0(默认),此元素永不过期(但是它可能由于服务端为了给其他新的元素分配空间而被删除)。

从文档中得知,PHP 扩展:

public bool Memcached::set ( string $key , mixed $value [, int $expiration ] )

中的 $expiration 既可以是 offset 的秒数 ,也可以是 过期时间时间戳,最终 Memcached 服务中存储的时 过期时间戳。

但是在 Yii 框架代码中,只实现了 offset 秒数的过期时间设置方法。

查看 Memcached 服务中的数据

\> telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
\> stats
STAT pid 13018
STAT uptime 4671
STAT time 1431720918
STAT version 1.4.13
STAT libevent 2.0.16-stable
\> stats items
STAT items:5:number 1
STAT items:5:age 4123
STAT items:5:evicted 0
STAT items:5:evicted_nonzero 0
STAT items:5:evicted_time 0
STAT items:5:outofmemory 0
STAT items:5:tailrepairs 0
STAT items:5:reclaimed 0
STAT items:5:expired_unfetched 0
STAT items:5:evicted_unfetched 0
END
\> stats cachedump 5 0
ITEM wechat_access_tokenwxc764b33902bf79d2 [107 b; 1431745507 s]
END

        查看发现 Memcached 的时间戳慢了8个小时,显然是本地 locale 或者时区设置出了问题,而 缓存的过期时间设置是正确地。

查阅资料后发现:

If you use memcached with the Identity Service—for example, using thememcache token driver or the auth_token middleware—ensure that the system time of memcached hosts is set to UTC. Memcached uses the host's system time in determining whether a key has expired, whereas the Identity Service sets key expiry in UTC. The timezone used by the Identity Service and memcached must match if key expiry is to behave as expected.

原来是这样

        过期时间在 Memcached 中存储是以 过期时间戳 存储的,Memcached 的过期时间计算是基于系统时间的,当 PHP 时间基准与 Memcached 时间基准 存在不同时,将导致 过期时间 时间戳 设置出问题。

解决

问题可能出现在以下场景:

  • PHP 代码执行 与 Memcached 服务不在同一服务器,且两个服务器系统时间有差异
  • PHP 的 timezone 是单独设置的 与系统时间有差异
  • 因为本地 locale 或者 timezone 设置问题,导致 Memcached 取到的系统时间有问题

正确设置 PHP 时区,Memcached 服务系统时间,时区,locale,保证两者基准时间已知,确保启动 Memcached 服务配置启动没有报错警告。

我始终觉得同一开发环境(本地 Vagrant 或者 开发机 Docker),可以有效避免这些杂七杂八的问题。

参考



提交评论