岑文初

构客网首页  博客  论坛

 
  SOA我有话说
  用户信息
 
帐号:  新手必读
密码: 保存密码
 
  分类列表
全部类别(51 篇)
我的文章(34 篇)
SOA SCA(16 篇)
  按月归档
2007年-05月(5 篇)
2007年-11月(22 篇)
2008年-09月(21 篇)
2008年-10月(3 篇)
  SOA2007 - SOA实践
我们何时迈向SOA
——SOA在中国的整体发展现状究竟如何?
我们如何迈向SOA
——中国企业如何迈出实施SOA的第一步?
我们应采用何种技术
——SOA国际标准SCA/SDO的具体内涵?
我们还需要何种技能
——SOA将如何改变系统架构设计以及项目管理过程?

显示第 1-10 条记录,共 50 条记录 首页 前页 后页 尾页  到第 页,共 5

对于GC回收优化转贴文章的一点补充

发布时间:2008年11月20日 作者:岑文初

阅读次数:28次 类别:我的文章 永久链接 Trackback 
  记得在我前一阵子的blog中写了关于jdk1.5的pool的内存溢出问题,这次乘着新的memcache客户端的使用,做了一次全面的压力测试。

jdk采用1.5的压力测试结果压了一个周末回来就无法响应了,看了看它的GC输出:

全都是[Full GC [Tenured: 786431K->786431K(786432K), 3.4802480 secs] 1022399K->1022399K(1022400K), [Perm : 36711K->36711K(98304K)], 3.4808510 secs]

 

替换jdk1.5为jdk1.6昨天中午开始做压力测试到今天中午我取了一下日志,看见GC输出如下:

[GC [PSYoungGen: 256608K->3608K(257792K)] 859002K->606698K(1044224K), 0.0562040 secs] [Times: user=0.17 sys=0.01, real=0.05 secs]

 

这就很明显的看出了差别了,前者已经把家底全部都用完了,出于out of memory的状态了。后者还是普通的GC回收,回收效果很好,同时看了看jboss启动的时候回收后占用的YoungGen就1300K左右,因此跑了一天的压力测试应该说还算是正常。

 

Full GC也就是在年轻代无法满足内存分配要求的时候才去做,这时候其实对于中老两代来说应该已经可能被占用完毕了,Full GC发生频率也越来越高,服务器响应速度也越来越慢。

因此对于持久的压力测试来说,打印出GC可以很好的分析出应用本身的内存使用状况,避免长期运行中少量内存泄露最终导致的应用不可用。

 

顺便说一下memcache 客户端修改后的测试结果:

  测试方案:

                   开始50个并发,每个并发每次请求完毕后休息0.1秒,10分钟后增长50个并发,按此规律增长到500并发。

                   旧版本SIP是在JDK1.5环境下完成的压力测试,

                   压力机和以前一样,是10.2.226.40DELL19508CPU8G内存。

                   压力机模拟发出对一个需要签名的API不断的调用请求。

 

         测试结论:

                   老版本的SIP性能和以前一样,并发500的时候,TPS接近480CPU 68%

                   新版本的SIP性能比老版本好很多,并发500的时候,TPS接近770,且系统压力很小,CPU利用率平均只有16%


“开放”的一些感想

发布时间:2008年11月05日 作者:岑文初

阅读次数:67次 类别:我的文章 永久链接 Trackback 
从去年到今年,开放这个词也在互联网上炒得火热,自己一年多的工作也让自己对开放这个词有了自己的一些理解和认识。
 
开放的平台
         去年到今年自己的工作也随着公司的战略改变不断的发生着变化。最早公司定位致力于为中小企业提供商务管理软件,让中小企业能够通过使用在线软件轻松搞定电子商务贸易管理。随后公司又致力于提供开放的在线软件运营平台,为众多ISV和中小企业建立一个软件交易平台,中小企业可以随需定制管理软件。到今年年初,提出了服务集成平台,ISV的应用开发不再是封闭的开发模式,可以基于ISP提供的服务定制出更加丰富的应用。其实这种转变也是对平台的开放的思想不断成熟的一个过程。
         独自实现在线管理软件和传统软件其实没有太大的差别,唯一的差别就是把应用由客户的机器拉到了软件提供商的服务器上,对于维护,更新和商业模式可能有部分的变化,但是根本上来说软件的封闭性还是和传统软件一样。互联网软件的最大特点就是个性化需求强烈以及需求变更周期短,要适应行业客户的需求,仅仅靠一个公司的几杆枪几号人的创意远远不够。Web2.0的热潮其实能够给开发人员最大的启示就是参与才是力量的源泉,其实软件开发也是一样,如果能够集合互联网上众多ISV的思想和创意,那么满足用户需求并不是一件难事,同时及时响应用户需求也不再是火烧屁股的事情。同时,看看互联网应用开发的今天,国外Open API前几年就已经兴起,Amazon,Google,Yahoo,FaceBook,MySpace等等,将自己的数据,存储,计算通过API的方式提供给第三方,让第三方开发者能够通过使用这些服务有机会实践自己的创新和创意,互联网应用的开发也有了新的开放式开发模式。服务集成平台其实就是为ISV提供了创建应用的一个资源平台,ISV可以通过服务集成平台获取到各个ISP(例如淘宝)的API,在其基础上开发出在线应用,然后直接挂接到应用运营平台为终端用户提供应用服务。这很类似于传统行业的产业链,服务集成平台就好比原料交易市场,应用运营平台就好比商品交易市场。回过头来看,阿里系的各个子公司,其实都是在以这种思路做事,从加入公司到现在,给我印象最深刻的一句话就是:“凡事不要先想着如何赚到别人的钱,让别人先赚到钱,别人自然很乐意的和你分享”,这种双赢的思想在开放中能够得到最好的实践。
 
开放的框架
         一个公司技术是需要积累的,如果纯粹让每一个开发人员根据自己的能力去合作开发企业的产品和平台,对于企业,对于产品都是不利的。Java吸引人就在于它的开源世界,每一个开发人员可以去获得自己想要的,或者去贡献给他人自己创造的。现在很多公司应聘的过程就是一个开源知识问答,其实是否用过能说明什么问题呢,关键是没用过如何去学习和了解并且快速上手,如果能力再强一点,那就知道如何定制和扩展,我想这样的才可以叫做企业需要的人才。
         从公司成立那时起,内部就有一个应用开发框架,作用就是为了快速开发应用,尽最大可能降低开发者对于开发技术的学习,集中精力致力于业务开发。(当然看到这里估计98%的开发人员都会皱起眉头)。我也为此贡献了自己2个Q的工作时间,当时我主要负责后台重构,需要建立起一个服务框架,开始参考了OSGI(因为它的模块化和动态载入机制),发现并不是很合适,然后接触了SCA框架(可扩展,模块化,SOA的支持),最后决定在开源项目Tuscany0.91版本的基础上再次开发和封装,实现了内部的ASF(应用服务框架),ASF作为我们开发框架的后台基础框架被广泛使用在了我们的多条产品线以及基础平台上,但是ASF的质疑就一直没有停过,性能,学习成本,调试困难度等等。虽然自己竭力去写了厚厚的一套文档,一组单元测试工具,一系列的问题查找工具,作了多次的压力测试,学习普及,但是还是得不到一些架构师的支持。
         其实,自己在后面也做过一些思考,其实对于ASF来说,它的可扩展性没有什么好怀疑,他不像其他开源项目,我可以封装Hessian组件,REST组件等等,随需载入,开发者只需要配置一下标签,即可使用,因此这样的框架下,不会随着技术的发展和自己的封闭而腐烂。但是,有一点就导致推广产生了那么多问题,那就是参与。我记忆很深的就是我们的首席架构师在今年招开会议评估ASF的问题时地邮件中说的:“ASF不是岑文初一个人的ASF,也不是平台一部的ASF,而是大家的ASF”。其实那时候我已经不再专职负责ASF,当今年因为一个项目进度由于开发受到影响时再次提出ASF的质疑地时候,我自己真的觉得比较沮丧,很多架构师和开发者从来就没有看过文档,没有用过调试工具,没有看过Q&A,一出问题就觉得无所适从,要找人解决框架问题,我曾经说是否Spring用的时候出现问题,第一想法就是去找Spring的开发者,还是先会看看文档,调试一下。我想这应该是两方面的原因,但如果能够让每个人都参与进来,那么就不会是今天一人独挡的局面。
         因此未来自己的工作中,不论是内部的基础组件还是基础平台都会多邀请一些参与者,毕竟自己的肩膀有限,蚂蚁就算在大力也需要有伙伴的支持。
 
开放的心态
         这点其实是做人的基本要素,有一个宽阔的胸怀才会有更多的机会,才会成长的更快。但是自己这点的却做得很不够。开发人员都有一个相同的特点就是热衷于技术钻研,今天搞一个东西比你快一点,明天做一个东西比他功能多一点,总是在技术方面去寻找满足。其实老大一直和我们也在说,现在公司内部的架构师并不是一个“全专”,也不一定是一个写代码高手,但是在某一个领域会有深入的研究,同时接触其他领域也能够胜任。没有什么技术人员是绝对的高手,其实随着工作重心的不断变化,所接触的领域也会不断发生变化,因此不可能有所谓的“全才”。
         有时候自己也会用技术的眼光去看待人或者事,其实这样只会让自己看不到自己的不足,也忽略了别人的优点,更重要的就是失去了一次进步的机会。其实经常给自己换换思路会对自己有很大的帮助,就好比最近忙于写了一阵子代码,那么就给自己一个机会去看看一些关于搜索领域的知识。开发了一个阶段的服务集成平台,去了解一下所有的国外网站Open API的风格,结构,流程。用惯了Java后,去学习学习Php,Ruby等等。这样换换脑子对自己来说会有新的收获。
         开放的心态理解容易,但是要让他不仅仅写在MSN的title中,而写在心里却需要不断地督促和付出。不过知道自己有问题好过觉得自己没有问题。
 
         写了那么些,其实思路比较乱,我想从随笔里面也看得出来,但是还是想记录一下自己的一些思考,起码以后回过头来可以看到自己成长的过程。

关于Memcached Cache是集中式还是分布式的一点补充

发布时间:2008年10月06日 作者:岑文初

阅读次数:243次 类别:我的文章 永久链接 Trackback 
   Memcached是一种集中式Cache,支持分布式横向扩展。这里需要有点说明,很多开发者觉得Memcached是一种分布式Cache,但是其实Memcached服务端本身是单实例的,只是在客户端实现过程中可以根据存储的主键作分区存储,而这个区就是Memcached服务端的一个或者多个实例,如果将客户端也囊括到Memcached中,那么可以部分概念上说是集中式的。其实回顾一下集中式的构架,无非两种情况:1.节点均衡的网状(JBoss Tree Cache),利用JGroup的多播通信机制来同步数据。2.Master-Slaves模式(分布式文件系统),由Master来管理Slave,如何选择Slave,如何迁移数据,都是由Master来完成,但是Master本身也存在单点问题。 贴了这个帖子以后,有同学说我是不是写错了,Memcached Cache应该是分布式的Cache,怎么变成集中式了。

    这里把我另外一部分的内容贴出来。

    Memcached是一种集中式Cache,支持分布式横向扩展。这里需要有点说明,很多开发者觉得Memcached是一种分布式Cache,但是其实Memcached服务端本身是单实例的,只是在客户端实现过程中可以根据存储的主键作分区存储,而这个区就是Memcached服务端的一个或者多个实例,如果将客户端也囊括到Memcached中,那么可以部分概念上说是集中式的。其实回顾一下集中式的构架,无非两种情况:1.节点均衡的网状(JBoss Tree Cache),利用JGroup的多播通信机制来同步数据。2.Master-Slaves模式(分布式文件系统),由Master来管理Slave,如何选择Slave,如何迁移数据,都是由Master来完成,但是Master本身也存在单点问题。

总结几个它的特点来理解一下它的优点和限制。

         Memory:内存存储,不言而喻,速度快,对于内存的要求高,不指出的话所缓存的内容非持久化。对于CPU要求很低,所以常常采用将Memcached服务端和一些CPU高消耗Memory低消耗应用部属在一起。(作为我们AEP正好有这样的环境,我们的接口服务器有多台,接口服务器对于CPU要求很高(由于WS-Security),但是对于Memory要求很低,因此可以用作Memcached的服务端部属机器)

         集中式Cache:避开了分布式Cache的传播问题,但是需要非单点保证其可靠性,这个就是后面集成中所作的cluster的工作,可以将多个Memcached作为一个虚拟的cluster,同时对于cluster的读写和普通的memcached的读写性能没有差别。

         分布式扩展:Memcached的很突出一个优点,就是采用了可分布式扩展的模式。可以将部属在一台机器上的多个Memcached服务端或者部署在多个机器上的Memcached服务端组成一个虚拟的服务端,对于调用者来说完全屏蔽和透明。提高的单机器的内存利用率,也提供了scale out的方式。

         Socket通信:传输内容的大小以及序列化的问题需要注意,虽然Memcached通常会被放置到内网作为Cache,Socket传输速率应该比较高(当前支持Tcp和udp两种模式,同时根据客户端的不同可以选择使用nio的同步或者异步调用方式),但是序列化成本和带宽成本还是需要注意。这里也提一下序列化,对于对象序列化的性能往往让大家头痛,但是如果对于同一类的Class对象序列化传输,第一次序列化时间比较长,后续就会优化,其实也就是说序列化最大的消耗不是对象序列化,而是类的序列化。如果穿过去的只是字符串,那么是最好的,省去了序列化的操作,因此在Memcached中保存的往往是较小的内容。

         特殊的内存分配机制:首先要说明的是Memcached支持最大的存储对象为1M。它的内存分配比较特殊,但是这样的分配方式其实也是对于性能考虑的,简单的分配机制可以更容易回收再分配,节省对于CPU的使用。这里用一个酒窖比喻来说明这种内存分配机制,首先在Memcached起来的时候可以通过参数设置使用的总共的Memory,这个就是建造一个酒窖,然后在有酒进入的时候,首先申请(通常是1M)的空间,用来建酒架,酒架根据这个酒瓶的大小分割酒架为多个小格子安放酒瓶,将同样大小范围内的酒瓶都放置在一类酒架上面。例如20cm半径的酒瓶放置在可以容纳20-25cm的酒架A上,30cm半径的酒瓶就放置在容纳25-30cm的酒架B上。回收机制也很简单,首先新酒入库,看看酒架是否有可以回收的地方,如果有直接使用,如果没有申请新的地方,如果申请不到,采用配置的过期策略。这个特点来看,如果要放的内容大小十分离散,同时大小比例相差梯度很明显,那么可能对于使用空间来说不好,可能在酒架A上就放了一瓶酒,但占用掉了一个酒架的位置。

         Cache机制简单:有时候很多开源的项目做的面面俱到,但是最后也就是因为过于注重一些非必要性的功能而拖累了性能,这里要提到的就是Memcached的简单性。首先它没有什么同步,消息分发,两阶段提交等等,它就是一个很简单的Cache,把东西放进去,然后可以取出来,如果发现所提供的Key没有命中,那么就很直白的告诉你,你这个key没有任何对应的东西在缓存里,去数据库或者其他地方取,当你在外部数据源取到的时候,可以直接将内容置入到Cache中,这样下次就可以命中了。这里会提到怎么去同步这些数据,两种方式,一种就是在你修改了以后立刻更新Cache内容,这样就会即时生效。另一种是说容许有失效时间,到了失效时间,自然就会将内容删除,此时再去去的时候就会命中不了,然后再次将内容置入Cache,用来更新内容。后者用在一些时时性要求不高,写入不频繁的情况。

         客户端的重要性:Memcached是用C写的一个服务端,客户端没有规定,反正是Socket传输,只要语言支持Socket通信,通过Command的简单协议就可以通信,但是客户端设计的合理十分重要,同时也给使用者提供了很大的空间去扩展和设计客户端来满足各种场景的需要,包括容错,权重,效率,特殊的功能性需求,嵌入框架等等。

         几个应用点:小对象的缓存(用户的token,权限信息,资源信息)。小的静态资源缓存。Sql结果的缓存(这部分用的好,性能提高相当大,同时由于Memcached自身提供scale out,那么对于db scale out的老大难问题无疑是一剂好药)。ESB消息缓存。


缓存系统MemCached的Java客户端优化历程

发布时间:2008年09月28日 作者:岑文初

阅读次数:250次 类别:我的文章 永久链接 Trackback 
Memcached是一种集中式Cache,支持分布式横向扩展。这里需要解释说明一下,很多开发者觉得Memcached是一种分布式缓存系统,但是其实Memcached服务端本身是单实例的,只是在客户端实现过程中可以根据存储的主键做分区存储,而这个区就是Memcached服务端的一个或者多个实例,如果将客户端也囊括到Memcached中,那么可以部分概念上说是集中式的。其实回顾一下集中式的构架,无非两种情况:一是节点均衡的网状(JBoss Tree Cache),利用JGroup的多播通信机制来同步数据;二是Master-Slaves模式(分布式文件系统),由Master来管理Slave,比如如何选择Slave,如何迁移数据等都是由Master来完成,但是Master本身也存在单点问题。下面再总结几个它的特点来理解一下其优点和限制

Memcached 是什么?

Memcached是一种集中式Cache,支持分布式横向扩展。这里需要解释说明一下,很多开发者觉得Memcached是一种分布式缓存系统,但是其实Memcached服务端本身是单实例的,只是在客户端实现过程中可以根据存储的主键做分区存储,而这个区就是Memcached服务端的一个或者多个实例,如果将客户端也囊括到Memcached中,那么可以部分概念上说是集中式的。其实回顾一下集中式的构架,无非两种情况:一是节点均衡的网状(JBoss Tree Cache),利用JGroup的多播通信机制来同步数据;二是Master-Slaves模式(分布式文件系统),由Master来管理Slave,比如如何选择Slave,如何迁移数据等都是由Master来完成,但是Master本身也存在单点问题。下面再总结几个它的特点来理解一下其优点和限制。

内存存储:不言而喻,速度快,但对于内存的要求高。这种情况对CPU要求很低,所以常常采用将Memcached服务端和一些CPU高消耗内存、低消耗应用部署在一起。(我们的某个产品正好有这样的环境,我们的接口服务器有多台,它们对CPU要求很高——原因在于WS-Security的使用,但是对于内存要求很低,因此可以用作Memcached的服务端部署机器)。

集中式缓存(Cache):避开了分布式缓存的传播问题,但是需要非单点来保证其可靠性,这个就是后面集成中所作的集群(Cluster)工作,可以将多个Memcached作为一个虚拟的集群,同时对于集群的读写和普通的Memcached的读写性能没有差别。

分布式扩展:Memcached很突出的一个优点就是采用了可分布式扩展的模式。可以将部署在一台机器上的多个Memcached服务端或者部署在多个机器上的Memcached服务端组成一个虚拟的服务端,对于调用者来说则是完全屏蔽和透明的。这样做既提高了单机的内存利用率,也提供了向上扩容(Scale Out)的方式。

Socket通信:这儿需要注意传输内容的大小和序列化的问题,虽然Memcached通常会被放置到内网作为缓存,Socket传输速率应该比较高(当前支持TCP和UDP两种模式,同时根据客户端的不同可以选择使用NIO的同步或者异步调用方式),但是序列化成本和带宽成本还是需要注意。这里也提一下序列化,对于对象序列化的性能往往让大家头痛,但是如果对于同一类的Class对象序列化传输,第一次序列化时间比较长,后续就会优化,也就是说序列化最大的消耗不是对象序列化,而是类的序列化。如果穿过去的只是字符串,这种情况是最理想的,省去了序列化的操作,因此在Memcached中保存的往往是较小的内容。

特殊的内存分配机制:首先要说明的是Memcached支持最大的存储对象为1M。它的内存分配比较特殊,但是这样的分配方式其实也是基于性能考虑的,简单的分配机制可以更容易回收再分配,节省对CPU的使用。这里用一个酒窖做比来说明这种内存分配机制,首先在Memcached启动的时候可以通过参数来设置使用的所有内存——酒窖,然后在有酒进入的时候,首先申请(通常是1M)的空间,用来建酒架,而酒架根据这个酒瓶的大小将自己分割为多个小格子来安放酒瓶,并将同样大小范围内的酒瓶都放置在一类酒架上面。例如20厘米半径的酒瓶放置在可以容纳20-25厘米的酒架A上,30厘米半径的酒瓶就放置在容纳25-30厘米的酒架B上。回收机制也很简单,首先新酒入库,看看酒架是否有可以回收的地方,如果有就直接使用,如果没有则申请新的地方,如果申请不到,就采用配置的过期策略。从这个特点来看,如果要放的内容大小十分离散,同时大小比例相差梯度很明显的话,那么可能对于空间使用来说效果不好,因为很可能在酒架A上就放了一瓶酒,但却占用掉了一个酒架的位置。

缓存机制简单:有时候很多开源项目做的面面俱到,但到最后因为过于注重一些非必要的功能而拖累了性能,这里提到的就是Memcached的简单性。首先它没有什么同步,消息分发,两阶段提交等等,它就是一个很简单的缓存,把东西放进去,然后可以取出来,如果发现所提供的Key没有命中,那么就很直白地告诉你,你这个Key没有任何对应的东西在缓存里,去数据库或者其他地方取;当你在外部数据源取到的时候,可以直接将内容置入到缓存中,这样下次就可以命中了。这里介绍一下同步这些数据的两种方式:一种是在你修改了以后立刻更新缓存内容,这样就会即时生效;另一种是说容许有失效时间,到了失效时间,自然就会将内容删除,此时再去取的时候就不会命中,然后再次将内容置入缓存,用来更新内容。后者用在一些实时性要求不高,写入不频繁的情况。

客户端的重要性:Memcached是用C写的一个服务端,客户端没有规定,反正是Socket传输,只要语言支持Socket通信,通过Command的简单协议就可以通信。但是客户端设计的合理十分重要,同时也给使用者提供了很大的空间去扩展和设计客户端来满足各种场景的需要,包括容错、权重、效率、特殊的功能性需求和嵌入框架等等。

几个应用点:小对象的缓存(用户的Token、权限信息、资源信息);小的静态资源缓存;SQL结果的缓存(这部分如果用的好,性能提高会相当大,同时由于Memcached自身提供向上扩容,那么对于数据库向上扩容的老大难问题无疑是一剂好药);ESB消息缓存。

优化MemCached系统Java客户端的原因

MemCached在大型网站被应用得越来越广泛,不同语言的客户端也都在官方网站上有提供,但是Java开发者的选择并不多。由于现在的MemCached服务端是用C写的,因此我这个C不太熟悉的人也就没有办法去优化它。当然对于它的内存分配机制等细节还是有所了解,因此在使用的时候也会十分注意,这些文章在网络上有很多。这里我重点介绍一下对于MemCache系统的Java客户端优化的两个阶段。

第一阶段:封装Whalin

第一阶段主要是在官方推荐的Java客户端之一whalin开源实现基础上做再次封装。

  1. 缓存服务接口化:定义了IMemCache接口,在应用部分仅仅只是使用接口,为将来替换缓存服务实现提供基础。
  2. 使用配置代替代码初始化客户端:通过配置客户端和SocketIO Pool属性,直接交由CacheManager来维护Cache Client Pool的生命周期,便于单元测试。
  3. KeySet的实现:对于MemCached来说本身是不提供KeySet的方法的,在接口封装初期,同事向我提出这个需求的时候,我个人觉得也是没有必要提供,因为缓存轮询是比较低效的,同时这类场景,往往可以去数据源获取KeySet,而不是从MemCached去获取。但是SIP的一个场景的出现,让我不得不去实现了KeySet。
    SIP在作服务访问频率控制的时候需要记录在控制间隔期内的访问次数和流量,此时由于是集群,因此数据必须放在集中式的存储或者缓存中,数据库肯定撑不住这样大数据量的更新频率,因此考虑使用Memcached的很出彩的操作——全局计数器(storeCounter,getCounter,inc,dec),但是在检查计数器的时候如何去获取当前所有的计数器?我曾考虑使用DB或者文件,但是效率有问题,同时如果放在一个字段中的话,还会存在并发问题。因此不得不实现了KeySet,在使用KeySet的时候有一个参数,类型是Boolean,这个字段的存在是因为在Memcached中数据的删除并不是直接删除,而是标注一下,这样会导致实现keySet的时候取出可能已经删除的数据。如果对于数据严谨性要求低,速度要求高,那么不需要再去验证Key是否真的有效,而如果要求Key必须正确存在,就需要再多一次的轮询查找。
  4. 集群的实现:Memcached作为集中式缓存,存在着集中式的致命问题:单点问题。虽然Memcached支持多Instance分布在多台机器上,但仅仅只是解决了数据全部丢失的问题,当其中一台机器出错以后,还是会导致部分数据的丢失,一个篮子掉在地上还是会把部分的鸡蛋打破。因此就需要实现一个备份机制,能够保证Memcached在部分失效以后,数据还能够依然使用,当然大家很多时候都用缓存不命中就去数据源获取的策略。然而在SIP的场景中,如果部分信息找不到就去数据库查找,很容易将SIP弄垮,因此SIP对于Memcached中的数据认为是可信的,做集群也是必要的。
  5. LocalCache结合Memcached使用,提高数据获取效率:在第一次压力测试过程中,发现和原先预料的一样,Memcached并不是完全无损失的,Memcached是通过Socket数据交互来进行通信的,因此机器的带宽,网络IO,Socket连接数都是制约Memcached发挥其作用的障碍。Memcache的一个突出优点就是Timeout的设置,也就是可以对放进去的数据设置有效期,从而在一定的容忍时间内对那些不敏感的数据就可以不去更新,以提高效率。根据这个思想,其实在集群中的每一个Memcached客户端也可以使用本地的缓存,来存储获取过的数据,设置一定的失效时间,来减少对于Memcached的访问次数,提高整体性能。

因此,在每一个客户端中都内置了一个有超时机制的本地缓存(采用Lazy Timeout机制),在获取数据的时候,首先在本地查询数据是否存在,如果不存在则再向Memcache发起请求,获得数据以后,将其缓存在本地,并设置有效时间。方法定义如下:

/**

* 降低memcache的交互频繁造成的性能损失,因此采用本地cache结合memcache的方式

* @param key

* @param 本地缓存失效时间单位秒

* @return

**/

public Object get(String key,int localTTL);

第二阶段:优化

第一阶段的封装基本上已经可以满足现有的需求,也被自己的项目和其他产品线所使用,但是不经意的一句话,让我开始了第二阶段的优化。有同事告诉我说Memcached客户端的SocketIO代码里面有太多的Synchronized(同步),多多少少会影响性能。虽然过去看过这部分代码,但是当时只是关注里面的Hash算法。根据同事所说的回去一看,果然有不少的同步,可能是作者当时写客户端的时候JDK版本较老的缘故造成的,现在Concurrent包被广泛应用,因此优化并不是一件很难的事情。但是由于原有Whalin没有提供扩展的接口,因此不得不将Whalin除了SockIO,其余全部纳入到封装过的客户端的设想,然后改造SockIO部分。

结果也就有了这个放在Google上的开源客户端:http://code.google.com/p/memcache-client-forjava/

  1. 优化Synchronized:在原有代码中SockIO的资源池被分成三个池(普通Map实现),——Free(闲)、Busy(忙)和Dead(死锁),然后根据SockIO使用情况来维护这三个资源池。优化方式为首先简化资源池,只有一个资源池,设置一个状态池,在变更资源状态的过程时仅仅变更资源池中的内容。然后用ConcurrentMap来替代Map,同时使用putIfAbsent方法来简化Synchronized,具体的代码可参见Google上该软件的源文件。
  2. 原以为这次优化后,效率应该会有很大的提高,但是在初次压力测试后发现,并没有明显的提高,看来有其他地方的耗时远远大于连接池资源维护,因此用JProfiler作了性能分析,发现了最大的一个瓶颈:Read数据部分。原有设计中读取数据是按照单字节读取,然后逐步分析,为的仅仅就是遇到协议中的分割符可以识别。但是循环Read单字节和批量分页Read性能相差很大,因此我内置了读入缓存页(可设置大小),然后再按照协议的需求去读取和分析数据,结果显示效率得到了很大的提高。具体的数据参见最后部分的压力测试结果。

上面两部分的工作不论是否提升了性能,但是对于客户端本身来说都是有意义的,当然提升性能给应用带来的吸引力更大。这部分细节内容可以参看代码实现部分,对于调用者来说完全没有任何功能影响,仅仅只是性能。

压力测试比较

在这个压力测试之前,其实已经做过很多次压力测试了,测试中的数据本身并没有衡量Memcached的意义,因为测试是使用我自己的机器,其中性能、带宽、内存和网络IO都不是服务器级别的,这里仅仅是将使用原有的第三方客户端和改造后的客户端作一个比较。场景就是模拟多用户多线程在同一时间发起缓存操作,然后记录下操作的结果。

Client版本在测试中有两个:2.0和2.2。2.0是封装调用Whalin Memcached Client 2.0.1版本的客户端实现。2.2是使用了新SockIO的无第三方依赖的客户端实现。checkAlive指的是在使用连接资源以前是否需要验证连接资源有效(发送一次请求并接受响应),因此启用该设置对于性能来说会有不少的影响,不过建议还是使用这个检查。

单个缓存服务端实例的各种配置和操作下比较:

缓存配置 用户 操作 客户端 版本 总耗时(ms) 单线程耗时(ms) 提高处理能力百分比
checkAlive 100 1000 put simple obj
1000 get simple obj
2.0
2.2
13242565
7772767
132425
77727
+41.3%
No checkAlive 100 1000 put simple obj
1000 put simple obj
2.0
2.2
7200285
4667239
72002
46672
+35.2%
checkAlive 100 1000 put simple obj
2000 get simple obj
2.0
2.2
20385457
11494383
203854
114943
+43.6%
No checkAlive 100 1000 put simple obj
2000 get simple obj
2.0
2.2
11259185
7256594
112591
72565
+35.6%
checkAlive 100 1000 put complex obj
1000 get complex obj
2.0
2.2
15004906
9501571
150049
95015
+36.7%
No checkAlive 100 1000 put complex obj
1000 put complex obj
2.0
2.2
9022578
6775981
90225
67759
+24.9%

从上面的压力测试可以看出这么几点,首先优化SockIO提升了不少性能,其次SockIO优化的是get的性能,对于put没有太大的作用。原以为获取数据越大性能效果提升越明显,但结果并不是这样。

单个缓存实例和双缓存实例的测试比较:

缓存配置 用户 操作 客户端 版本 总耗时(ms) 单线程耗时(ms) 提高处理能力百分比
One Cache instance
checkAlive
100 1000 put simple obj
1000 get simple obj
2.0
2.2
13242565
7772767
132425
77727
+41.3%
Two Cache instance
checkAlive
100 1000 put simple obj
1000 put simple obj
2.0
2.2
13596841
7696684
135968
76966
+43.4%

结果显示,单个客户端对应多个服务端实例性能提升略高于单客户端对应单服务端实例。

缓存集群的测试比较:

缓存配置 用户 操作 客户端 版本 总耗时(ms) 单线程耗时(ms) 提高处理能力百分比
No Cluster
checkAlive
100 1000 put simple obj
1000 get simple obj
2.0
2.2
13242565
7772767
132425
77727
+41.3%
Cluster
checkAlive
100 1000 put simple obj
1000 put simple obj
2.0
2.2
25044268
8404606
250442
84046
+66.5%

这部分和SocketIO优化无关。2.0采用的是向集群中所有客户端更新成功以后才返回的策略,2.2采用了异步更新,并且是分布式客户端节点获取的方式来分散压力,因此提升效率很多。

开源代码下载

其实封装后的客户端一直在内部使用,现在作了二次优化以后,觉得应该开源出来,一是可以完善自己的客户端代码,二是也可以和更多的开发者交流使用心得。目前我已经在Google Code上传了应用的代码、范例和说明等,有兴趣的朋友可以下载下来测试一下,与现在用的Java Memcached客户端在易用性和性能方面是否有所提高,也期待更多对于这部分开源内容的反馈,能够将它做的更好。


MemCached Cache Java Client封装优化历程

发布时间:2008年09月26日 作者:岑文初

阅读次数:155次 类别:我的文章 永久链接 Trackback 
  MemCached Cache在大型网站被应用得越来越广泛,不同语言的客户端也都在官方网站上有提供,但是Java的选择并不多。由于现在的MemCached Cache服务端是用C写的,因此我这个C不太熟悉的人也就没有办法去优化它,当然对于它的内存分配机制等细节还是有所了解,因此在使用的时候也会十分注意,这些文章Google一把应该也有很多了。这里就说说对于MemCache Java客户端的优化的两个阶段。

 

  MemCached Cache在大型网站被应用得越来越广泛,不同语言的客户端也都在官方网站上有提供,但是Java的选择并不多。由于现在的MemCached Cache服务端是用C写的,因此我这个C不太熟悉的人也就没有办法去优化它,当然对于它的内存分配机制等细节还是有所了解,因此在使用的时候也会十分注意,这些文章Google一把应该也有很多了。这里就说说对于MemCache Java客户端的优化的两个阶段。

 

First Stage

       我也和其他使用Memcached Cache的同学一样,看了官方网站的内容,然后去下载了whalin memcached Client,后来Stat的时候遇到问题,就给作者发了邮件说明了情况,作者让我下载 2.0.1 版本,这个版本也是比较不错的一个版本,后续的封装也是基于这个版本之上。

         第一阶段主要是在whalin的客户端作了再次封装。

1.       Cache服务接口化。

定义了IMemCache接口,在应用部分仅仅只是使用接口,为将来替换Cache服务实现提供基础。

2.       使用配置代替代码初始化客户端。

通过配置客户端和SocketIO Pool属性,直接交管由CacheManager来维护Cache Client Pool的生命周期,方便实用以及单元测试。

3.       KeySet的实现。

对于MemCached来说本身是不提供KeySet的方法的,在接口封装初期,同事向我提出这个需求的时候,我个人觉得也是没有必要提供,因为Cache轮询是比较低效的,同时这类场景,往往可以去数据源获取KeySet,而不是从MemCached去获取。但是SIP的一个场景的出现,让我不得不去实现了KeySet

SIP在作服务访问频率控制的时候需要记录在控制间隔期内的访问次数和流量,此时由于是集群,因此数据必须放在集中式的存储或者缓存中,数据库肯定是撑不住这样大数据量的更新频率的,因此考虑使用Memcached的很出彩的操作,全局计数器(storeCounter,getCounter,inc,dec),但是在检查计数器的时候如何去获取当前所有的计数器,曾考虑使用DB或者文件,但是效率还是问题,同时如果放在一个字段中并发还是有问题。因此不得不实现了KeySet,在使用KeySet的时候有一个参数,类型是Boolean,这个字段的存在是因为,在Memcached中数据的删除并不是直接删除,而是标注一下,这样会导致实现keySet的时候取出可能已经删除的数据,如果对于数据严谨性要求低,速度要求高,那么不需要再去验证key是否真的有效,如果要求key必须正确存在,就需要再多一次的轮询查找。

4.       Cluster的实现。

Memcached作为集中式Cache,就存在着集中式的致命问题:单点问题,Memcached支持多Instance分布在多台机器上,仅仅只是解决了数据全部丢失的问题,但是当其中一台机器出错以后,还是会导致部分数据的丢失,一个篮子掉在地上还是会把部分的鸡蛋打破。

因此就需要实现一个备份机制,能够保证Memcached在部分失效以后,数据还能够依然使用,当然大家很多时候都用Cache不命中就去数据源获取的策略,但是在SIP的场景中,如果部分信息找不到就去数据库查找,那么要把SIP弄垮真的是很容易,因此SIP对于Memcached中的数据认为是可信的,因此做Cluster也是必要的。

 

 

(1)              应用传入需要操作的key,通过CacheManager获取配置在Cluster中的客户端。

(2)              当获得Cache Client以后,执行Cache操作。

(3)              A.如果是读取操作,当不能命中时去集群其他Cache客户端获取数据,如果获取到数据,尝试写入到本次获得的Cache客户端,并返回结果。(达到数据恢复的作用)

B.如果是更新操作,在本次获取得Cache客户端执行更新操作以后,立即返回,将更新集群其他机器命令提交给客户端的异步更新线程对列去异步执行。(由于如果是根据key来获取Cache,那么异步执行不会影响到此主键的查询操作)

存在的问题:如果是设置了Timeout的数据,那么在丢失以后被复制的过程中就会变成永久有效的内容。

5.       LocalCache结合Memcached使用,提高数据获取效率。

在第一次压力测试过程中,发现和原先预料的一样,Memcached并不是完全无损失的,Memcached是通过Socket数据交互来进行通信的,因此机器的带宽,网络IOSocket连接数都是制约Memcached发挥其作用的障碍。Memcache的一个突出优点就是Timeout的设置,也就是放入进去的数据可以设置有效期,自动会失效,这样对于一些不敏感的数据就可以在一定的容忍时间内不去更新,提高效率。根据这个思想,其实在集群中的每一个Memcached客户端也可以使用本地的Cache,来缓存获取过的数据,设置一定的失效时间,来减少对于Memcached的访问次数,提高整体性能。

         因此,在每一个客户端中内置了一个有超时机制的本地缓存(采用lazy timeout机制),在获取数据的时候,首先在本地查询数据是否存在,如果不存在则再向Memcache发起请求,获得数据以后,将其缓存在本地,并设置有效时间。方法定义如下:

/**

            * 降低memcache的交互频繁造成的性能损失,因此采用本地cache结合memcache的方式

            * @param key

            * @param 本地缓存失效时间单位秒

            * @return

     */

    public Object get(String key,int localTTL);

 

 

Second Stage

         第一阶段的封装基本上已经可以满足现有的需求,也被自己的项目和其他产品线使用,但是不经意的一句话,让我开始了第二阶段的优化。单位里面有个同学说Memcache客户端里面在SocketIO代码里面有太多的synchronized,多多少少会影响性能。虽然过去看过这部分代码,但是当时只是关注里面的Hash算法,那天回去后一看,果然有不少的synchronized,可能是与客户端当时写的时候Jdk版本较早的缘故造成的,现在Concurrent包被广泛应用,因此优化并不是一件很难的事情。但是由于原有whalin没有提供扩展的接口,因此不得不将whalin除了SockIO部分全部纳入到封装过的客户端中,然后改造SockIO部分。

因此也有了这个放在Google上的

open source: http://code.google.com/p/memcache-client-forjava/

 

一.              优化synchronized部分。在原有代码中SockIO的资源池分成三个池(普通Map实现),FreeBusyDead,然后根据SockIO使用情况来维护这三个资源池。

优化方式,首先简化资源池,只有一个资源池,设置一个状态池,在变更资源状态的过程时仅仅变更资源池中的内容。再次,用ConcurrentMap来替代Map,同时使用putIfAbsent方法来简化Synchronized,具体的代码可以看open source的代码部分。

二.              原以为这优化后,效率应该会有很大的提高,但是在初次压力测试后发现,并没有明显的提高,看来有其他地方的耗时远远大于连接池资源维护,因此用JProfiler作了性能分析,发现了最大的一个瓶颈:read数据部分,原有设计中读取数据是按照单字节读取,然后逐步分析,为的仅仅就是遇到协议中的分割符可以识别,但是循环read单字节和批量分页read性能相差很大,因此内置了读入缓存页(可设置大小),然后再按照协议的需求去读取和分析数据,效率得到了很大的提高。具体的看最后部分的压力测试结果。

 

上面两部分的工作不论是否提升了性能,但是对于客户端本身来说都是有意义的,当然提升性能给应用带来的吸引力更大。这部分细节内容可以参看代码实现部分,对于调用者来说完全没有任何功能影响,仅仅只是性能。

 

 

压力测试

         在这个压力测试之前,其实已经做过很多次压力测试了,测试中的数据本身并没有衡量Memcached的意义,因为测试是使用我自己的机器,性能,带宽,内存,网络IO都不是服务器级别的,这里仅仅是将使用原有的第三方客户端和改造后的客户端作一个比较。场景就是模拟多用户多线程在同一时间发起Cache操作,然后记录下操作的结果。

 

Client版本在测试中有两个:2.02.22.0是封装调用whalin memcached Client 2.0.1版本的客户端实现。2.2是使用了新SockIO的无第三方依赖的客户端实现。

         checkAlive指的是在使用连接资源以前是否需要验证连接资源有效(发送一次请求并接受响应),因此打开对于性能来说会有不少的影响,不过建议还是使用这个检查。

 

One Cache Server instance各种配置和操作下比较:

Cache配置

User

操作

Client 版本

总耗时(ms)

单线程耗时(ms)

提高处理能力百分比

checkAlive

100

1000 put simple obj

1000 get simple obj

2.0

13242565

132425

+41.3%

2.2

7772767

77727

No checkAlive

100

1000 put simple obj

1000 get simple obj

2.0

7200285

72002

+35.2%

2.2

4667239

46672

checkAlive