分布式缓存下缓存优化设计方案(一)

相关知识:缓存cache、分布式缓存memcache、C# expressions、IL Emit

推荐几篇不错的文章(和C#关系比较密切,不过个人认为其思想还是值得学习的):

[1]memcache的原理介绍

[2]Expression Tree上手指南

[3]关于动态调用函数的实现

[4]Emit 学习之旅

切入正题,先感慨下不得不说大学学好操作系统原理还是很重要的,很多思想其实在抠腚的时候都值得借鉴。首先关于Cache,貌似记操作系统中描述缓存是这么形容的高速的缓冲存储器,存储内存中经常使用的数据以避免平凡的内存数据交换。在应用层面Cache的作用也是非常强大的,客户端的缓存可以避免平凡的从服务器获取数据浪费流量,服务器端的缓存则可以缓解在高访问量情况下对数据库以及服务器本身造成的压力。MemCache是使用较多的一种分布式缓存工具,其实本质上和localcache一样,只是将缓存主机与应用服务器分离,应用服务器作为memcache的客户端,通过网络传输访问memcache主机读写缓存(一般都采用TCP协议)。分布式缓存的好处,我个人总结主要有一下几点:1.减轻应用服务器的存储负荷;2.多应用服务器情况下,只保留一份缓存(存在于缓存主机上),保证了数据的一致性,也给缓存的管理带来了便利(关于memcache的介绍可以参看[1],memcache是个开源工程github上也能到其源码)。

再切入正题,Hierarchy Cache(分级缓存,擦,这是我自己起的名字,不要误会不是什么高深的玩意)。上面说道Memcache是分布式存储的一种解决方案,那么在缓存读写的过程中必当需要进行网络传输(在高访问量以及海量数据的情况下,通过网络传输以及hash获取cache的效率要明显高于数据库查询批量查询的效率,这也是缓存存在的重要意义),虽然采用TCP传输协议,但是如果缓存的内容较多且访问频繁,那IO的开销增大缓存的效率必然下降,应对这种情况经过思考老大和我分别提出了两种解决方案 ,一种是设置短时间localcache(针对短时间频率访问情况),一种是差异化hierarchy cache(针对缓存数据量较大情况);

首先介绍第一种解决方案,短时间localcache,为频繁访问的缓存保留本地备份(localcache),在一段时间内的访问可以本地缓存命中而不用频繁的访问缓存主机,理论上:memcache访问次数(采用本地缓存)=memcache访问次数(不采用本地i缓存)/本地缓存时间,而且由于本地缓存时间较短所以依然能够保持缓存的一致性。

第二种解决方案,即我所说的差异化hierarchy cache,主要针对了了缓存数据量较大情况。这里我借鉴了操作系统中TLB设计的思路,不将全量数据缓存,而是人为的设置两级缓存,分别缓存全量数据中的更常访问的部分。有人可能奇怪了既然缓存本来就是存储经常访问的数据,那何来更常访问一说?这里需要说明一点,我们做分级缓存的数据存的确是存在这种特性的,比如说我们有一个有1w条数据的榜单,客户端使用分页来展示这些榜单,一般用户在查看这些榜单的时候往往只需要查看前几十条、几百条,如果服务器对全量的数据做了cache那每次请求势必要对1w条数据进行io的操作(至于为什么不几十条、几百条作为一个缓存结果缓存,是因为这样会导致缓存的碎片话,给清理缓存造成严重的影响)。再回到我说的差异化分级缓存,首先介绍下差异化,这个差异化是指不同数据定制不同的cache数据量大小。再者是分级,目前我只采用了两个层级,并且是从数据量初始位置开始缓存(这个可以根据自己产品的特性定制)。举个例子吧,当前我有1w条数据,如果不使用缓存,客户端每次分页请求数据都需要全量的查找数据库排序并且遍历找到对应数据;如果使用普通缓存全量1w条数据,则客户端每次请求,需要将1w条数据从memcache主机发往应用服务器再遍历对应数据;如果使用分级缓存(假设分两级,LevelOne代表200条数据,LevelTwo代表500条数据),那么在客户端访问时,大部分请求都可以通过LevelOne、LevelTwo两个层级的缓存命中,而对于超过这两个层级的数据请求再去查询数据库(因为这种访问请求较少所以性能损耗是可以容忍的),这样网络传输和数据库查询性能损耗都大大降低,而且并没有造成缓存的碎片话(是不是和TLB很像…)。

关于如何实现这种方案,最近参考了反射、Expressions Tree、IL Emit的实现机理,最终选择了使用IL来织入代码进行实现,采用的开发模式则是伟大的面向运气的开发模式(开个玩笑,结构化编程切忌不要一步到位,要慢慢来)。