写在前面
简介
- 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的相关内容可能会补充进来。以上内容若存在错误的地方,请留言或发邮件指正错误,谢谢。