缓存技术(转)
EhCache
1.简介
EhCache 是一个纯Java的进程内缓存框架。能直接缓存Java的对象。
ehcache是一个用Java实现的使用简单,高速,实现线程安全的缓存管理类库,ehcache提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案。
Ehcache的类层次模型主要为三层,最上层的是CacheManager,他是操作Ehcache的入口。我们可以通过CacheManager.getInstance()获得一个单个的CacheManager,或者通过CacheManager的构造函数创建一个新的CacheManager。每个CacheManager都管理着多个Cache。而每个Cache都以一种类Hash的方式,关联着多个Elemenat。而Element则是我们用于存放要缓存内容的地方。
ehcache的刷新策略是当缓存在放入的时候记录一个放入时间,它是用Lazy Evict的方式,在取的时候同设置的TTL(Time To Live )比较
ehcache缓存的3种清空策略:
1 FIFO,先进先出
2 LFU,最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
3 LRU,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
可以为CacheManager添加事件监听,当对CacheManager增删Cache时,事件处理器将会得到通知。要配置事件处理,需要通过ehcache的配置文件来完成。
2.分布式策略
——在分布式情况下,一个节点的缓存发生变动,需要通过广播通知其他节点。Ehcache有两种通知策略:copy和invalidate。一个是没有就拷贝过来,比较自然;另外一个是如果没有,那就反过来连自己也取消,这样就必须重新到数据库中查询,网络负载低。
——发现机制。也就是发现其他节点的机制,有两种,一种是利用RFC1112进行广播,好处是可以动态增减节点,另外一种是维护一个静态列表(IP)。
——传递机制。(Delivery Mechanism) 支持JMS, RMI, TCP, UDP, 广播,JXTA, JGroups。 缺省是RMI。
——复制的缺陷和Ehcache的解决办法
☆ 碎嘴的通讯协议(Chatty Protocal)
因为是一个网状拓扑,一个节点的每个变化需要通知其他N-1个其他节点,造成网络较大的负载。可以通过批量异步操作来降低负载。加快对客户端的响应。
☆ 冗余的通知
发起通知的不应该收到并处理通知。(因为是广播,所以会收到)可以通过为每个Cache创建一个GUID来标明身份,这样如果发起的Cache GUID与自身相同,则不用处理。
☆ 潜在的数据不一致
这是一个比较大的问题。只要是分布式存储,就一定会遇到这个问题。造成这个问题的原因有很多,比如对同一Cache的同一数据进行并行更新等;解决的办法是通过:同步传递(锁)。客户端请求先处于挂起状态,在同步传递并在所有缓存中数据都同步之后再返回。
☆ 使缓存数据失效以实现更新(Update via Invalidate)
默认的方式是一个缓存的数据被更新之后,就将新数据复制给其他所有缓存。另外的方式是,如果缓存数据被更新,则清除所有节点缓存中对应的数据,重新到数据库中查询。并重新加载到所有的节点缓存中。(Liferay就是采用了这种方式)另外的一种保证数据一致的方式就是:设置TTL值(time to live), 到期之后重新从数据库中更新;
Memcached
Memcached大致的原理也和ehcache 相同,将数据采用键值的形式存放在内存中,使用时可以将查询的md5作为键,查询的结果作为值。相对ehcache而言,memcached是一个工具,ehcache是一个框架,memcached更加底层更加灵活,当然你也要写相应的代码去使用它。
1、请求由client端进行处理,client端维护着一个memcached服务器列表,根据用户的请求将响应指向不同的memcached服务器;(也就是说,每个缓冲值,在所有服务器中只保持着一份copy,不像ehcache每个服务器中都有),但是并不提供冗余;当某个服务器S停止运行或崩溃了,所有存放在S上的键/值对都将丢失。它是一种不互相通信的分布式缓存。
2、memcached对CPU的要求不高,但对内存要求较高,因此可以与webapp server安装在一起,互补(web app server是CPU要求高,内存要求低)
3、可以动态地,甚至无限地增加memcached服务器的数量
如果是多个应用在数据层整合,Ehcache,是一个进程内的缓存方案,受Spring管理,每个Web App的缓存相互独立(抛开ClassLoader Share),基本上不可能实现多应用缓存共享。即使使用消息中间件进行监听,也不是一个完美的解决方案。
因为是Client/Server结构的,通过采用二级hash算法进行缓存服务器分配、缓存数据的读写;整个缓存体系中,一个key对应的value只保存在惟一的一个服务器上;而且,memcached server是守护进程(daemon )方式运行的,因此无论对于什么应用,只要用相同的memcached client进行配置,就能共享缓存。
选择服务器的过程可以看作一致性Hash算法的过程。
Memcached有两个核心组件组成:服务端(ms)和客户端(mc),在一个memcached的查询中,mc先通过计算key的hash值来确定kv对所处在的ms位置。当ms确定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。因为这之间没有交互以及多播协议,所以memcached交互带给网络的影响是最小化的。
有没有一种和NOSQL很像的感觉(键-值),对没错,NOSQL技术,虽然现在归于数据库的一种,但其本质也是缓存技术和数据库技术的一种融合产物。
1.Memcached特征
memcached作为高速运行的分布式缓存服务器,具有以下的特点。
- 协议简单 :memcached的服务器客户端通信并不使用复杂的XML等格式, 而使用简单的基于文本行的协议。
- 基于libevent的事件处理 :libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。
- 内置内存存储方式:为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。 由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。 另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。 memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
- memcached不互相通信的分布式:memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。 各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢? 这完全取决于客户端的实现。
2.Memcached内存存储
最近的memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存。 在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free来进行的。 但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下, 会导致操作系统比memcached进程本身还慢。Slab Allocator就是为解决该问题而诞生的。
Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块, 以完全解决内存碎片问题。
Slab Allocation的原理相当简单。 将分配的内存分割成各种尺寸的块(chunk), 并把尺寸相同的块分成组(chunk的集合)(图1)。
memcached根据收到的数据的大小,选择最适合数据大小的slab(图2)。 memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk, 然后将数据缓存于其中。
Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。
这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了
3.删除机制
Lazy Expiration
memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。 这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。
LRU:从缓存中有效删除数据的原理
memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况, 此时就要使用名为 Least Recently Used(LRU)机制来分配空间。 顾名思义,这是删除“最近最少使用”的记录的机制。 因此,当memcached的内存空间不足时(无法从slab class获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。 从缓存的实用角度来看,该模型十分理想。
4.分布式算法
下面假设memcached服务器有node1~node3三台, 应用程序要保存键名为“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的数据。
图1 分布式简介:准备
首先向memcached中添加“tokyo”。将“tokyo”传给客户端程序库后, 客户端实现的算法就会根据“键”来决定保存数据的memcached服务器。 服务器选定后,即命令它保存“tokyo”及其值。
图2 分布式简介:添加时
同样,“kanagawa”“chiba”“saitama”“gunma”都是先选择服务器再保存。
接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。 函数库通过与数据保存时相同的算法,根据“键”选择服务器。 使用的算法相同,就能选中与保存时相同的服务器,然后发送get命令。 只要数据没有因为某些原因被删除,就能获得保存的值。
图3 分布式简介:获取时
这样,将不同的键保存到不同的服务器上,就实现了memcached的分布式。 memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障无法连接,也不会影响其他的缓存,系统依然能继续运行。
根据余数计算分散
Cache::Memcached的分布式方法简单来说,就是“根据服务器台数的余数进行分散”。 求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。
下面将Cache::Memcached简化成以下的Perl脚本来进行说明。
1 use strict; 2 use warnings; 3 use String::CRC32; 4 5 my @nodes = ('node1','node2','node3'); 6 my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma'); 7 8 foreach my $key (@keys) { 9 my $crc = crc32($key); # CRC値 10 my $mod = $crc % ( $#nodes + 1 ); 11 my $server = $nodes[ $mod ]; # 根据余数选择服务器 12 printf "%s => %s\n", $key, $server; 13 }
余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。 那就是当添加或移除服务器时,缓存重组的代价相当巨大。 添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器, 从而影响缓存的命中率。
Consistent Hashing
首先求出memcached服务器(节点)的哈希值, 并将其配置到0~2SUP(32)的圆(continuum)上。 然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。 如果超过2SUP(32)仍然找不到服务器,就会保存到第一台memcached服务器上。
图4 Consistent Hashing:基本原理
从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响。
图5 Consistent Hashing:添加服务器
因此,Consistent Hashing最大限度地抑制了键的重新分布。 而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。 使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。 因此,使用虚拟节点的思想,为每个物理节点(服务器) 在continuum上分配100~200个点。这样就能抑制分布不均匀, 最大限度地减小服务器增减时的缓存重新分布。
参考:http://daihaixiang.blog.163.com/blog/static/3830134201161891838346/
http://tech.idv2.com/2008/08/17/memcached-pdf/