大名鼎鼎的分布式缓存系统memcached,在开源社区中可谓是无人不知无人不晓,memcached支持分布式的横向扩展,但memcached的服 务端却是单实例,并无"分布式"的功能,所谓的分布式只是客户端在存储的主键做分布的存储;还有memcached组件缓存对象,如果组件无进行序列化必 定无法正确取得数据;如何使用memcached的java组件来监控memcached的运行状态;以上等等的问题是我在日常的工作中碰到并解决的,拿 出来跟大家做个分享^_^
对象的序列化
首先memcached是独立的服务器组件,独立于应用系统,从客户端保存和读取对象到memcached是必须通过网络传输,因为网络传输都是二进制的数据,所以所有的对象都必须经过序列化,否则无法存储到memcahced的服务器端.
正如我们以往在集群中应用的序列化一样,memcached的序列化的性能也是往往让大家头疼,如果我们对我们的domain类进行对象的序列化,第一次 序列化时间会比较长,但后续会优化,也就是说序列化最大的消耗不是对象的序列化,而是类的序列化,如果存储的只是一个String对象,这种情况是最理想 的,省去了序列化的操作.实际上String对象本身已经实现了序列化接口,无法我们再次去进行序列化操作.
memcached的原子加法
记录一下上次犯得一个错误
static int count = 0 ;
count ++ ;
MemCachedClient mcc = new MemCachedClient();
mcc.add( " test.html " , count);
%>
这段代码的作用是将test.html的用户访问次数保存到memcached中,粗劣一看好像并无错误,但在高并发时的出来的访问数据一定是小于实际的 访问数量,当然这里并不是memcached对象锁的问题,主要还是程序中线程的同步问题,但是如果使用java的synchronized或lock那 么在性能上肯定是无法忍受的,memcached客户端组件带有原子性的加法和减法
MemCachedClient mcc = new MemCachedClient();
System.out.println(mcc.addOrIncr( " test.html " , 1 ));
%>
long addORIncr(String key,long inc)为计数器值增加inc,如果计数器不存在,则保存inc为计数器的值,必须注意的是服务器端不会对超过2的32次方的行为进行检查
分布式的mencached
memcached虽然是属于分布式的缓存服务器,但实际上memcached服务端之间并无分布式的功能,不会互相通信共享数据,如何进行分布式,这完全是取决于客户端的实现
假设我们现在有三台memcached服务器分别为node1,node2,node3,应用程序要保存键名分别 为"test1","test2","test3",客户端实现的算法就是根据键名来决定保存数据的memcached服务器,我们将"test1"保存 到node1,"test2"保存到node2,"test3"保存到node3,并且在读取缓存数据也是通过一样的算法从各台服务器上读取相应的 key,这样通过一个最简单的算法将不同的键保存到不同的服务器上,实现了memcached的分布式.
但是这种算法很难确保每台服务器得到较为平均的数据量,我们需要改变一下客户端的算法,简单来说,就是根据服务器的台数的余数进行分散
" test1 " .hashCode() % 3
%>
根据key的java.lang.String.hashCode()取得散列值,再将值模服务器的台数得到余数值,我们再根据这个余数值来判定这个 key要存入哪一台服务器,当key的数量越来越大,对key的散列取模也会趋向平均,基本可以保证几台memcached服务器所存储的缓存量趋向平均
似乎很完美,余数计算的方法很简单,数据的分散性也很优秀,但也有其缺点,就是当需要添加或移除服务器时,缓存的重组代价是相当巨大的,添加或移除服务器时,余数就会发生变化,这样就无法取到与原来缓存时相同的服务器.
网上介绍的Consistent Hashing算法基本上可以解决这个问题,这里做个简单的说明,首先是求出memcached服务器节点的哈希值,并将其配置到0-2的32次方的圆 上,然后用同样的方法求出存储数据的键的哈希值,并映射到圆上.然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上.如果超过2的 32次方仍然找不到服务器,就会保存到第一台memcached服务器上
从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响
几种连接客户端的对比
目前java的memcached主要有Java-Memcached-Client,Xmemached,Spymemcached三种,这三个客户端的性能测试可以看
http://xmemcached.googlecode.com/svn/trunk/benchmark/benchmark.html
请求的资源为64Bytes,在低并发Java-Memcached-Client是占有一定的优势,但在并发数超过100以后,Java- Memcached-Client是呈现直线下跌,并发数达到300已经无法承受,Spymemcached和Xmemached表现相对稳定,特别是 Xmemached无论在低并发或高并发都保持优秀的性能表现
并发数固定为100时,在小文件的请求Java-Memcached-Client还是占有优势,当随着请求的size越来越大,三者趋向于同一点
如果你对memcached访问的负载不高,那么Java-Memcached-Client是一个不错的选择,如果你对memcached访问的负载要 求较高,推荐使用Xmemached,如果需要异步的批量处理,可以选择Spymemcached,如果你什么都不知道,那么建议使用 Xmemached,因为无论在何种情况,它都可以表现出较好的性能,虽然不是最好
监控memcached
推荐使用nagios或cactis进行监控,nagios没有配置过,cactis是需要下载一个脚本插件
这里推荐一个从网上淘来的php,只要把它放到你的机器中,当然你的机器要支持php环境,将此php放入你的网页访问网络就可以访问
下载
http://www.blogjava.net/Files/dongbule/cacti/memcache.rar
修改php以下几个选项
define ( ' ADMIN_PASSWORD ' , ' password ' ); // Admin Password
$MEMCACHE_SERVERS [] = ' 192.168.1.100:11211 ' ; // add more as an array
# $MEMCACHE_SERVERS[] = 'mymemcache-server2:11211'; // add more as an array
监控的平台
理解memcached的删除机制
memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期, 这种技术被称为lazy(惰性)expiration.因此,memcached不会在过期监视上耗费CPU时间
memcached会优先使用已超时的记录的空间,并使用LRU算法来分配空间,因此当memcached的内存空间不足,就从最近违背使用的记录中搜索,并将空间分配给新的记录
不过在某些情况下LRU机制会造成某些麻烦,如你并不想要淘汰已被缓存过的记录,可以在memcached启动时添加 -M 参数来禁止LRU,但这样在memcached的内存用尽时,memcached会返回错误,是否使用LRU,在于你的需求