缓存设计上主要目标:
高可用,高性能,易用性,各业务模块缓存使用的隔离性
主要分为服务端设计与客户端使用
服务端设计架构:
负载均衡+twemproxy+redis集群是一种解决方案
由代理层twemproxy进行分片
客户端设计,保证易用性:
标准Spring Data Redis
Spring Data Redis是对Jedis的一个封装,提供@cacheable缓存注解及RedisTemplate操作api
开发人员无需再管理jedis连接的生命周期
当然缺点是多了层封装,且spring Data Redis在往cache中存数据时用的始终是 multi命令,这样在代理层分片的架构下需要覆写源码方法,去除multi。
监控部分主要分两部份,一部分是cache平台监控与业务无关,还有一个是与业务相关的监控。
cache平台监控主要是监控内存使用率与命中率。
业务相关的监控主要是发现开发人员是否正确定义缓存失效时间等,有可能开发人员使用了错误的缓存名称导致难以查找的bug
监控业务缓存块还有一个功能是提供清除某个缓存块的数据内容,这样当程序有bug导致缓存无法更新时也可以临时清除缓存
各业务模块缓存使用的隔离性设计,主要是出于服务化之后的场景。
比如将系统按业务垂直切分,服务化后有:商品模块,订单模块等服务模块
这时商品模块与订单模块的开发人员是不同的,如果不保证cache的隔离性,就有可能两个模块定义了相同的cache名称,但他们定义的cache失效是不同的。
这样就产生了一个难以发现的问题。解决这个问题可以引入一个group组概念。比如商品模块的group为itemCenter,这样当商品模块定义了cache区块名称为:
searchProduct时,底层spring Data Redis会创建一个cache名称为:itemCenter_searchProduct的区块。
引入group需要做两件事情,一个是在spring Data Redis创建区块cache时,将名称加上group前缀,这步是通过覆写RedisCacheManager的getCache(
Sting name)来实现。另一个是在生成具体存储内容的key时加上group前缀。
另外我们有时希望在更新了某个商品后,希望同时更新主站服务的缓存块时,可以通过@CacheEvict来将主站的缓存块清除。
因为在商品服务中所有缓存的操作会带有group,因此需要有一个入口能够让用户指定cache全称而忽略group。
这边我们内部解析cache名称的代码中约定如果以 「*」开头则认为忽略组名采用「*」后面的cache全称。这样就能够实现在商品服务中清除主站服务缓存。
spring data redis在存储一个key,value时会做两件事情,一件是将key,value存入string结构中。另外再维护当前区块cache拥有的key元素,即将当前key加入到当前
区域cache中,这个结构键为 「区块名称~keys」。
业务上我们希望能够查看某个cache区块中元素个数,这样能够比较监控到代码是否存在问题,或者有没有预热到,防止性能问题。
查找某个cache区块中元素个数可以通过查找上述spring data redis维护的区块cache中元素来做。
还有一个功能,我们希望列出我们系统业务使用的所有区块cache。这个功能可以通过redis的keys命令来查找,但一方面这个keys问题性能较差,且代理层分片不支持。
因此我们这边维护了一个hash,key为:NETEASE_Cache_Areas,field为cache块名称,value为超时时间。