Memcache工作原理总结
<!--[if !supportLists]-->1. <!--[endif]-->分片原理
咱们废话话不多说了,直接看Memcache的原理。首先memcache解决的最大的一个问题就是内存多次读取的内存碎片问题。内存碎片分为内存内部碎片和内存外部碎片。一般是指在外部碎片中出现了不连续的细小内存片段,不能够被进程利用。因为不连续,不能组合成大而的连续空间,导致这部分空间很可惜的浪费了。内存碎片是因为在分配一个内存块后,使之空闲,但不将空闲内存归还给最大内存块而产生的。
那么memcache启动进程的时候就按照预先设定好的大小(默认是64mb)相内存开辟出一段连续的内存空间,之后再将这段内存空间分成不同的片段。
相信下面的图大家都见过了
Memcache单进程最大可开的内存是2GB,如果想缓存更多的数据,建议还是开辟更多的memcache进程(不同端口)或者使用分布式memcache进行缓存,将数据缓存到不同的物理机或者虚拟机上。
Memcache进程启动,在内存开辟了连续的区域。咱们用上面的图形来举例,这段连续的区域就好像上面的slab1+slab2+slab3+……+slab(n).分配区域相同的构成了slab(分片组)。Slab下面可不直接就是存储区域片(就是图中的chunks)了。而是page,如果一个新的缓存数据要被存放,memcached首先选择一个合适的slab,然后查看该slab是否还有空闲的chunk,如果有则直接存放进去;如果没有则要进行申请。slab申请内存时以page为单位,所以在放入第一个数据,无论大小为多少,都会有1M大小的page被分配给该slab。申请到page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk的数组,在从这个chunk数组中选择一个用于存储数据。(http://blog.csdn.net/21aspnet/article/details/7022827)。在Page中才是一个个小存储单元——chunks,一个page默认1mb,那么可以放多少个88字节单位的chunks呢?1024*1024/88约等于11915个。如果放入记录是一个100字节的数据,那么在88字节的chunks和112字节的chunks中如何调配呢。答案当然是紧着大的用,不可能将请求过来的数据再做个分解、分离存储、合并读取吧。这样也就带来了一个小问题,还是有空间浪费掉了。112-100=12字节,这12字节就浪费了。
在缓存的清除方面,memcache是不释放已分配内存。当已分配的内存所在的记录失效后,这段以往的内存空间,memcache自然会重复利用起来。至于过期的方式,也是采取get到此段内存数据的时候采取查询时间戳,看是否已经超时失效。基本不会有其他线程干预数据的生命周期。至于清空的策略等同于ehcache的默认策略——最近很少使用清空策略——也就是英文常用的LRU——Least Recently Used。
而memcache鉴定内存不足是在什么情况下呢:无法从slab里面获取新的存储单元了。这个对内存十分贪婪的东东。基本服务器都得是2~4GB以上方能吃得消(非时效性,或者说时效性较低的数据)。
Memcache借助了操作系统的libevent工具做高效的读写。libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥高性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。Memcache号称可以接受任意数量的连接请求。事实真的是这样吗?
<!--[if !supportLists]-->1. <!--[endif]-->存储过程分析
假设我们现在往memcache中存储一个缓存记录,首先在使用memcache客户端程序的时候要制定一个初始化的服务机器路由表,比如Java的客户端程序
cachedClient = new MemCachedClient(); //获取连接池实例 SockIOPool pool = SockIOPool.getInstance(); //设置缓存服务器地址,可以设置多个实现分布式缓存 pool.setServers(new String[]{"127.0.0.1:11211","192.176.17.90:11211"});
那么在做存储的时候memcache客户端程序会hash出一个码,之后再根据路由表去将请求转发给memcache服务端,也就是说memcache的客户端程序相当于做了一个类似负载均衡的功能。下面这个图也是大家以前看过的
而memcache在server上面的进程仅仅负责监听服务和接受请求、存储数据的作用。分发不归他管。所以这么看的话,散列到每台memcache服务机器,让每台机器分布存储得均匀是客户端代码实现的一个难点。这个时侯Hash散列算法就显得格外重要了吧。
<!--[if !supportLists]-->1. <!--[endif]-->读取过程分析
理解了memcache的存储就不难理解memcache的读取缓存的过程了。在读取的时候也是根据key算出一个hash,之后在算出指定的路由物理机位置,再将请求分发到服务机上。
memcache分布式读写的存储方式有利有弊。如果node2宕机了,那么node2的缓存数据就没了,那么还得先从数据库load出来数据,重新根据路由表(此时只有node1和node3),重新请求到一个缓存物理机上,在写到重定向的缓存机器中。灾难恢复已经实现得较为完备。弊端就是维护这么一个高可用缓存,成本有点儿大了。为了存储更多的数据,这样做是否利大于弊,还是得看具体的应用场景再定。
<!--[if !supportLists]-->1. <!--[endif]-->与ehcache的争论
Ehcache的争论之前就说过了,总是在性能上来说这两个的性能如何。还有大家在网上常见的一个列表进行了比较。笔者觉得,这两个最大的差异是原理的差异决定了应用场景的差异。比如做单点应用缓存的时候,就可以使用ehcache直接向本地内存进行缓存的读写。而做集群缓存的时候一般是借由一个集中式管理server来做缓存,既然是集中式server就少不了网络传输了,这个时侯memcache较为适合。不是说ehcache不能做集群式的缓存,而是做了集群的缓存的代价(RMI、JMS、JGroups)、网络资源的占用确实比memcache高一些。至于内存的读写操作效率,这个不太好说。Ehcache用java的随机读写类操作二进制的buffer。Memcache底层是基于libevent程序库的C服务。这个相信效率都差不多。关键的消耗还是在网络IO资源上。