写在前面
简介
- memcache是一款开源软件,由LiveJournal的Brad Fitzpatrick开发,以BSD license授权发布。
- 是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个巨大的hash表,将数据(包括图像、视频、文件以及数据库检索的结果等)调用到内存中,然后从内存中读取,从而大大提高读取速度。
特性
- 非持久性存储
- 定位于分布式存储,不适用于单机系统
- 不支持List、Array等复杂的数据
Memcache和Memcached
- 这款开源软件的项目名叫Memcache,Memcached (Memcached-Daemon的简称) 是软件的主程序名。
- 在php中,有两个为memcache开发的扩展(客户端):Memcache扩展和memcached扩展。后者比前者要新,功能也比较多。推荐安装后者。
部署环境
安装memcache服务端
sudo apt-get install memcached #for Ubuntu yum install memcached #for CentOS
安装php扩展(客户端)
这里以安装memcached扩展为例,从官网下载最新版本,解压并cd到目录
wget http://pecl.php.net/get/memcached-2.2.0.tgz tar -zxvf memcached-2.2.0.tgz cd memcached-2.2.0
编译安装
phpize #生成configure配置文件。若找不到命令请用绝对路径 /usr/local/php/bin/phpize ./configure --with-php-config=/usr/local/php/bin/php-config #配置 make #编译 make install #安装。需要root权限
安装完后会提示安装的目录:
Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20131226/
编辑php.ini文件
#找到extension_dir,如果没有设置扩展的路径,则把上面memcached.so所在的路径粘贴进去 extension_dir = "/usr/local/php/lib/php/extensions/no-debug-non-zts-20131226/" #找到;extension=php_XXX.dll的地方,在最下面添加上 extension=memcached.so
最后,重启web服务器和php-fpm
看看phpinfo()是否将memcached添加成功。我这里为了做测试编译了两个扩展,实际编译后者就可以。

简单使用
这里只介绍php中memcached类的使用。更多请参考手册
<?php
$md = new memcached;
//向服务器池添加服务器,也可以使用addServers方法一次添加多个
//public bool Memcached::addServer ( string $host , int $port [, int $weight = 0 ] )
$md->addServer('127.0.0.1',11211);
//$expiration默认为0,永不超时。但即使为0,当内存不够用的时候,memcache也会将不常使用的数据删掉
//expiration不能超过60×60×24×30(30天时间的秒数);如果大于这个值,服务端会将其作为一个真实的Unix时间戳来处理而不是 自当前时间的偏移
//public bool Memcached::add ( string $key , mixed $value [, int $expiration ] )
$md->add('addItem','this is content one by add');
$md->add('addItem','this is content two by add');//false.若使用add方法添加已经存在的key,无法添加成功
//public mixed Memcached::get ( string $key [, callback $cache_cb [, float &$cas_token ]] )
echo $md->get('addItem');//after add:this is content one by add
//public bool Memcached::delete ( string $key [, int $time = 0 ] )
$md->delete('addItem');
echo $md->get('addItem');//空
//使用set方法重复添加同样的key是合法的,且后面的数据会覆盖前面的数据
//public bool Memcached::set ( string $key , mixed $value [, int $expiration ] )
$md->set('tip','hello');//tip='hello'
$md->set('tip','world');//tip='world'
//若使用追加方法,必须将OPT_COMPRESSION(压缩)设置为false
$md->setOption(Memcached::OPT_COMPRESSION,false);
$md->set('tip','hello');//tip='hello'
//向指定元素后面追加一个字符串
//public bool Memcached::append ( string $key , string $value )
$md->append('tip',' world');//tip='hello world'
$arr = [
'name' => 'foam',
'age' => '22',
];
//同时存储多个数据
//public bool Memcached::setMulti ( array $items [, int $expiration ] )
$md->setMulti($arr);
echo $md->get('name');//foam
echo $md->get('age');//22
$md->set('count','-99');//count=-99
//自增
//public int Memcached::increment ( string $key [, int $offset = 1 ] )
$md->increment('count');//false.对于负数和非整数是无法自增的
$md->set('count',0);
$md->increment('count');//count=1
$md->increment('count',100);//count=101
//自减
//public int Memcached::decrement ( string $key [, int $offset = 1 ] )
$md->decrement('count');//count=100
$md->decrement('count',50);//count=50
$md->decrement('count',60);//count=0.最小只能减到0
//清空所有数据
//public bool Memcached::flush ([ int $delay = 0 ] )
$md->flush();
$md->get('tip');//false
$md->get('count');//false
其他
Memcache的分布式缓存
Memcache尽管说是分布式缓存服务器,但其分布式却是由客户端实现的。
保存数据时,客户端会根据数据的键使用特定的算法(不同的客户端,算法不同)选择要保存的服务器,将其存入其中。获取数据时也是使用相同的算法在所在的服务器获取。这些客户端算法一般在Hash的基础上进行改良,保证其数据的分散性。
具体策略就不展开说明了。
Memcache与锁
在高并发的业务场景中,都会使用锁的概念。如:
场景一:一批cache同时过期,许多客户端同时并发请求cache。此时业务逻辑发现cache过期,会高并发下从DB同时请求数据并重新设置给memcache。整个过程都是高并发的,就如决堤,洪水汹涌袭来一般。对系统的负载能力将是巨大的挑战。
场景二:脏数据场景,有两个客户A和B。A发送投票请求,服务器在12时00分00秒10毫秒取出当前票数100,在12时00分00秒15毫秒给当前票数自增1并保存。B发送投票请求,服务器在12时00分00秒12毫秒取出当前票数100,在12时00分00秒13毫秒给当前票数自增1并保存。总票数原本应该为102的实际上却是101。即使数据库上已经使用了锁的策略,但由于磁盘IO速度慢,仍然会导致读写脏数据的错误。如果锁的动作发生在内存上就不一样了。
我们可以用memcache实现简单的锁,重点在于memcache的add方法,如果cache上有相同的键了,add方法会返回false。下面是用php实现的简单锁。
class MemcacheLock
{
private $_host = '127.0.0.1';
private $_port = 11212;
private $_md;
const TIMEOUT = 10;
public function __construct()
{
$this->_md = new memcached;
$this->_md->addServer($this->_host,$this->_port);
}
//为防止产生死锁,需要设置过期时间,时间长度根据业务决定
public function lock($key,$timeOut=self::TIMEOUT)
{
return $this->_md->add($key,'lock',$timeOut);
}
public function unLock($key)
{
$this->_md->delete($key);
}
}
Memcache和Memcached扩展互不兼容
NOTIC:这里指的是Memcache的两个php扩展
php.net上有人做了测试,两个扩展间由于调用的客户端库不同,存储的方式有一定的差异。
测试脚本:
$memcache = new Memcache;
$memcacheD = new Memcached;
$memcache->addServer('127.0.0.1',11211);
$memcacheD->addServer('127.0.0.1',11211);
$checks = array(
123,
4542.32,
'a string',
true,
array(123, 'string'),
(object)array('key1' => 'value1'),
);
foreach ($checks as $i => $value) {
print "<br/><br/>Checking WRITE with Memcache<br/>";
$key = 'cachetest' . $i;
$memcache->set($key, $value);
usleep(100);
$val = $memcache->get($key);
$valD = $memcacheD->get($key);
if ($val !== $valD) {
print "Not compatible!<br/>";
var_dump(compact('val', 'valD'));
}
print "<br/><br/>Checking WRITE with MemcacheD";
$key = 'cachetest' . $i;
$memcacheD->set($key, $value);
usleep(100);
$val = $memcache->get($key);
$valD = $memcacheD->get($key);
if ($val !== $valD) {
print "Not compatible!<br/>";
var_dump(compact('val', 'valD'));
}
}
结果是只有处理string的方式一样,其他数据结构都采用了不同的处理方式。因此两个扩展间是不能相互切换的。
Last:还有许多值得说的地方,日后若有memcache的相关内容可能会补充进来。以上内容若存在错误的地方,请留言或发邮件指正错误,谢谢。