Author:文初
Email: wenchu.cenwc@alibaba-inc.com
Blog: http://blog.csdn.net/cenwenchu79/
<!----><o:p> </o:p>
MemCached Cache在大型网站被应用得越来越广泛,不同语言的客户端也都在官方网站上有提供,但是Java的选择并不多。由于现在的MemCached Cache服务端是用C写的,因此我这个C不太熟悉的人也就没有办法去优化它,当然对于它的内存分配机制等细节还是有所了解,因此在使用的时候也会十分注意,这些文章Google一把应该也有很多了。这里就说说对于MemCache Java客户端的优化的两个阶段。
<o:p> </o:p>
First Stage<o:p></o:p>
我也和其他使用Memcached Cache的同学一样,看了官方网站的内容,然后去下载了whalin memcached Client,后来Stat的时候遇到问题,就给作者发了邮件说明了情况,作者让我下载 2.0.1 版本,这个版本也是比较不错的一个版本,后续的封装也是基于这个版本之上。<o:p></o:p>
第一阶段主要是在whalin的客户端作了再次封装。<o:p></o:p>
1. Cache服务接口化。<o:p></o:p>
定义了IMemCache接口,在应用部分仅仅只是使用接口,为将来替换Cache服务实现提供基础。<o:p></o:p>
2. 使用配置代替代码初始化客户端。<o:p></o:p>
通过配置客户端和SocketIO Pool属性,直接交管由CacheManager来维护Cache Client Pool的生命周期,方便实用以及单元测试。<o:p></o:p>
3. KeySet的实现。<o:p></o:p>
对于MemCached来说本身是不提供KeySet的方法的,在接口封装初期,同事向我提出这个需求的时候,我个人觉得也是没有必要提供,因为Cache轮询是比较低效的,同时这类场景,往往可以去数据源获取KeySet,而不是从MemCached去获取。但是SIP的一个场景的出现,让我不得不去实现了KeySet。<o:p></o:p>
SIP在作服务访问频率控制的时候需要记录在控制间隔期内的访问次数和流量,此时由于是集群,因此数据必须放在集中式的存储或者缓存中,数据库肯定是撑不住这样大数据量的更新频率的,因此考虑使用Memcached的很出彩的操作,全局计数器(storeCounter,getCounter,inc,dec),但是在检查计数器的时候如何去获取当前所有的计数器,曾考虑使用DB或者文件,但是效率还是问题,同时如果放在一个字段中并发还是有问题。因此不得不实现了KeySet,在使用KeySet的时候有一个参数,类型是Boolean,这个字段的存在是因为,在Memcached中数据的删除并不是直接删除,而是标注一下,这样会导致实现keySet的时候取出可能已经删除的数据,如果对于数据严谨性要求低,速度要求高,那么不需要再去验证key是否真的有效,如果要求key必须正确存在,就需要再多一次的轮询查找。<o:p></o:p>
4. Cluster的实现。<o:p></o:p>
Memcached作为集中式Cache,就存在着集中式的致命问题:单点问题,Memcached支持多Instance分布在多台机器上,仅仅只是解决了数据全部丢失的问题,但是当其中一台机器出错以后,还是会导致部分数据的丢失,一个篮子掉在地上还是会把部分的鸡蛋打破。<o:p></o:p>
因此就需要实现一个备份机制,能够保证Memcached在部分失效以后,数据还能够依然使用,当然大家很多时候都用Cache不命中就去数据源获取的策略,但是在SIP的场景中,如果部分信息找不到就去数据库查找,那么要把SIP弄垮真的是很容易,因此SIP对于Memcached中的数据认为是可信的,因此做Cluster也是必要的。
<o:p></o:p>
<!----><v:shapetype o:spt="75" coordsize="21600,21600" filled="f" stroked="f" id="_x0000_t75" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:extrusionok="f" o:connecttype="rect" gradientshapeok="t"></v:path><o:lock v:ext="edit" aspectratio="t"></o:lock></v:shapetype><v:shape o:spid="_x0000_i1025" id="图片_x0020_2" type="#_x0000_t75" style="VISIBILITY: visible; WIDTH: 342.75pt; HEIGHT: 329.25pt"><v:imagedata src="file:///C:\DOCUME~1\WENCHU~1.CEN\LOCALS~1\Temp\msohtmlclip1\01\clip_image001.emz" o:title=""></v:imagedata></v:shape><o:p></o:p>
(1) 应用传入需要操作的key,通过CacheManager获取配置在Cluster中的客户端。<o:p></o:p>
(2) 当获得Cache Client以后,执行Cache操作。<o:p></o:p>
(3) A.如果是读取操作,当不能命中时去集群其他Cache客户端获取数据,如果获取到数据,尝试写入到本次获得的Cache客户端,并返回结果。(达到数据恢复的作用)<o:p></o:p>
B.如果是更新操作,在本次获取得Cache客户端执行更新操作以后,立即返回,将更新集群其他机器命令提交给客户端的异步更新线程对列去异步执行。(由于如果是根据key来获取Cache,那么异步执行不会影响到此主键的查询操作)<o:p></o:p>
存在的问题:如果是设置了Timeout的数据,那么在丢失以后被复制的过程中就会变成永久有效的内容。<o:p></o:p>
5. LocalCache结合Memcached使用,提高数据获取效率。<o:p></o:p>
在第一次压力测试过程中,发现和原先预料的一样,Memcached并不是完全无损失的,Memcached是通过Socket数据交互来进行通信的,因此机器的带宽,网络IO,Socket连接数都是制约Memcached发挥其作用的障碍。Memcache的一个突出优点就是Timeout的设置,也就是放入进去的数据可以设置有效期,自动会失效,这样对于一些不敏感的数据就可以在一定的容忍时间内不去更新,提高效率。根据这个思想,其实在集群中的每一个Memcached客户端也可以使用本地的Cache,来缓存获取过的数据,设置一定的失效时间,来减少对于Memcached的访问次数,提高整体性能。<o:p></o:p>
因此,在每一个客户端中内置了一个有超时机制的本地缓存(采用lazy timeout机制),在获取数据的时候,首先在本地查询数据是否存在,如果不存在则再向Memcache发起请求,获得数据以后,将其缓存在本地,并设置有效时间。方法定义如下:<o:p></o:p>
/**<o:p></o:p>
* 降低memcache的交互频繁造成的性能损失,因此采用本地cache结合memcache的方式<o:p></o:p>
* @param key<o:p></o:p>
* @param 本地缓存失效时间单位秒<o:p></o:p>
* @return<o:p></o:p>
*/<o:p></o:p>
public Object get(String key,int localTTL);<o:p></o:p>
<o:p> </o:p>
<o:p> </o:p>
Second Stage<o:p></o:p>
第一阶段的封装基本上已经可以满足现有的需求,也被自己的项目和其他产品线使用,但是不经意的一句话,让我开始了第二阶段的优化。单位里面有个同学说Memcache客户端里面在SocketIO代码里面有太多的synchronized,多多少少会影响性能。虽然过去看过这部分代码,但是当时只是关注里面的Hash算法,那天回去后一看,果然有不少的synchronized,可能是与客户端当时写的时候Jdk版本较早的缘故造成的,现在Concurrent包被广泛应用,因此优化并不是一件很难的事情。但是由于原有whalin没有提供扩展的接口,因此不得不将whalin除了SockIO部分全部纳入到封装过的客户端中,然后改造SockIO部分。<o:p></o:p>
因此也有了这个放在Google上的<o:p></o:p>
open source: http://code.google.com/p/memcache-client-forjava/<o:p></o:p>
<o:p> </o:p>
一. 优化synchronized部分。在原有代码中SockIO的资源池分成三个池(普通Map实现),Free,Busy,Dead,然后根据SockIO使用情况来维护这三个资源池。<o:p></o:p>
优化方式,首先简化资源池,只有一个资源池,设置一个状态池,在变更资源状态的过程时仅仅变更资源池中的内容。再次,用ConcurrentMap来替代Map,同时使用putIfAbsent方法来简化Synchronized,具体的代码可以看open source的代码部分。<o:p></o:p>
二. 原以为这优化后,效率应该会有很大的提高,但是在初次压力测试后发现,并没有明显的提高,看来有其他地方的耗时远远大于连接池资源维护,因此用JProfiler作了性能分析,发现了最大的一个瓶颈:read数据部分,原有设计中读取数据是按照单字节读取,然后逐步分析,为的仅仅就是遇到协议中的分割符可以识别,但是循环read单字节和批量分页read性能相差很大,因此内置了读入缓存页(可设置大小),然后再按照协议的需求去读取和分析数据,效率得到了很大的提高。具体的看最后部分的压力测试结果。<o:p></o:p>
<o:p> </o:p>
上面两部分的工作不论是否提升了性能,但是对于客户端本身来说都是有意义的,当然提升性能给应用带来的吸引力更大。这部分细节内容可以参看代码实现部分,对于调用者来说完全没有任何功能影响,仅仅只是性能。<o:p></o:p>
<o:p> </o:p>
<o:p> </o:p>
压力测试<o:p></o:p>
在这个压力测试之前,其实已经做过很多次压力测试了,测试中的数据本身并没有衡量Memcached的意义,因为测试是使用我自己的机器,性能,带宽,内存,网络IO都不是服务器级别的,这里仅仅是将使用原有的第三方客户端和改造后的客户端作一个比较。场景就是模拟多用户多线程在同一时间发起Cache操作,然后记录下操作的结果。<o:p></o:p>
<o:p> </o:p>
Client版本在测试中有两个:2.0和2.2。2.0是封装调用whalin memcached Client 2.0.1版本的客户端实现。2.2是使用了新SockIO的无第三方依赖的客户端实现。<o:p></o:p>
checkAlive指的是在使用连接资源以前是否需要验证连接资源有效(发送一次请求并接受响应),因此打开对于性能来说会有不少的影响,不过建议还是使用这个检查。<o:p></o:p>
<o:p> </o:p>
One Cache Server instance各种配置和操作下比较: <o:p></o:p>
Cache配置<o:p></o:p> |
User<o:p></o:p> |
操作<o:p></o:p> |
Client 版本<o:p></o:p> |
总耗时(ms)<o:p></o:p> |
|
分享到:
相关推荐
在实际使用中,通常会开发 Java 客户端库来封装 Memcached 的操作,提供更方便的 API。封装过程可能包括: 1. **连接池管理**:为了提高性能,客户端可以使用连接池管理多个到 Memcached 服务器的连接,避免频繁...
本主题将深入探讨如何基于Java客户端对Memcached进行封装,以便更高效地在Java应用中使用它。 首先,我们需要理解Java中的Memcached客户端库,如spymemcached或xmemcached。这两个库都提供了与Memcached服务器通信...
1. **安装库**:首先,需要安装适用于.NET的Memcached客户端库,如Enyim.Caching或Memcached.ClientLibrary等。 2. **创建缓存策略**:定义一个缓存策略类,该类将负责处理Memcached的增删查改操作。这通常包括添加...
- `alisoft-xplatform-asf-cache-2.5.1`:阿里巴巴软件团队封装的客户端,依赖于 `commons-logging`, `hessian`, `log4j`, `stax-api`, 和 `wstx-asl` 等 jar 包。 **六、Java 示例代码** 以下是一个使用 `java_...
- alisoft-xplatform-asf-cache-2.5.1:这是由阿里软件架构师岑文初封装的客户端库,注释是中文的,适合中文用户理解和使用。 具体使用Java客户端进行Memcached操作的步骤如下: - 配置客户端依赖的jar包。 - 创建...
官方提供的Memcached Client for Java基于Java BIO实现,SpyMemcached和XMemcached则是基于Java NIO的实现,其中XMemcached在并发性能上表现更优,SpyMemcached也有不错的性能。在实际应用中,可以根据项目需求选择...
在实际项目中,你会看到类似`DBConnection`的类用于封装连接,`SQLExecutor`处理SQL语句,`CacheClient`进行缓存操作等。 总结来说,"数据库操作API封装"意味着将数据库操作的细节隐藏在库或类中,为开发者提供简洁...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
google-api-translate-java(Java 语言对Google翻译引擎的封装类库) 语音识别程序 SpeechLion.tar SpeechLion 是一个语音识别程序,主要用来处理桌面命令,基于 Sphinx-4 语音识别引擎开发。用户可以通过该软件来...
_cache = new MemcachedClient(); } // 实现ICacheStrategy接口的方法 public void Add(string objId, object o, int second) { _cache.Store(StoreMode.Set, objId, o, TimeSpan.FromSeconds(second)); } ...