一、背景介绍
最近也是无意中翻阅了一本书《深入分布式缓存》在第四章节也是有讲到本地缓存工具Ehcache与Guava Cache 使用的一些场景与介绍。
而我之前在工作中也遇到了需要本地缓存的场景,一直有个问题困惑到我,就是缓存一致性的问题,和缓存同步可靠性问题,所以也想通过书中找答案;虽然没找到合适的答案但至少学习了一些实现思路。
二、为什么要用 ?
一般追求极致的业务场景、也就是说Redis已经不能满足业务需求了。比如要求毫秒级处理业务,并且请求峰值又高!而Redis有些时候他内部数据同步;备份刷磁盘的时候会影响一些性能;可能是秒级的。但在几万或者10几万QPS的时候也就是有上万的请求受影响。
三、本地缓存组件怎么选?
本地缓存有很多种方式 分别:比如JDK自带的 HashMap、线程安全 ConcurrentHashMap、比如google封装的框架:Guava Cache 、还是支持集群的 Ehcache 各有各的特色应用场景。
接下来我们一一介绍:
A) 如 ConcurrentHashMap 也是可以做本地缓存的容器、只不过维护起来不太方便,只支持常规的CRUD。如果要实现时间配置;过期自动清理就不方便了;过期清理,还得自己去封装组件定时去清理,就是个手动挡。
不清理的情况下、如果服务器没重启的情况下很容易导致长期堆积无效数据、很容易造成资源浪费,最好的方式给值设置【过期时间】一定时间后实现自动清理。
其实像这些常规的功能redis就实现的很好、只不过我们是想要怎么在本地内存也能够一样的维护缓存数据。
B)Guava是Google提供的一套Java工具包,而GuavaCache作为Guava的Cache部分而提供了一套非常完善的本地缓存机制。在Guava之前,JDK的ConcurrentHashMap因为能友好的支持并发而被经常用作本地缓存,但它毕竟还是个Map,不具备缓存的一些特性,比如缓存过期,缓存数据的加载/刷新等。
个人觉得 Guava Cache 就是Concurrent-HashMap的加强补充版本,在只能够CRUD的基础上增加了,缓存失效规则、刷新规则设计等基本元素,同时使用起来也非常简洁简单。
最常用的是基于过期时间删除:
expireAfterAccess(long, TimeUnit):某个key最后一次访问后,再隔多长时间后删除。
expireAfterWrite(long, TimeUnit):某个key被创建后,再隔多长时间后删除。
在单机的情况下不管是使用Guava 还是Concurren-HashMap都没有问题,在分布式的情况下,每个节点都是独立的、协同每个节点数据一致就成了问题了。
C)Ehcache是一个用Java实现的使用简单、高速、线程安全的缓存管理类库,其提供了用内存、磁盘文件存储,以及分布式存储等多种灵活的管理方案。同时Ehcache作为开放源代码项目,采用限制比较宽松的Apache License V2.0作为授权方式,被广泛地用于Hibernate、 Spring、Cocoon等其他开源系统。Ehcache从Hibernate发展而来,逐渐涵盖了全部功能,是目前发展势头很好的一个项目。Ehcache具有快速、简单、低消耗、依赖性小、扩展性强、支持对象或序列化缓存、支持缓存或元素的失效、提供LRU/LFU/FIFO缓存策略、支持内存缓存及磁盘缓存、采用分布式缓存机制等特点。
个人觉得Ehcache算半个Redis了,功能比较强大比如能够在服务器重启缓存到磁盘;但代码植入性比较强 ,需要做很多配置。
最主要他有集群方案;通过JMS消息订阅的方式通知到每个节点;来更新数据;同时也支持第三方组件集成来实现比如ActiveMQ Kafka RabbitMQ都可以。还可以搭建集中式集群,单实例在内存中可以缓存20GB以上的数据。
个人还是觉得Ehcache有点复杂;还是倾向使用Guava Cache 来实现本地缓存,只不过他的内存一致性方案没有成熟,不过还是可以通过 第三方组件来实践消息订阅其实就能够满足。
比如刚刚所说的ActiveMQ Kafka RabbitMQ 或者zookeeper 的订阅通知 也可以;只不过为了这一个场景来维护一个消息系统感觉没必要;如果项目中有用到其中一个组件 可以随带使用他们的特性来实现。
如果都没有用到的情况下还是可以考虑使用zookeeper来实现订阅通知,相对更轻量级 维护相对也简单。
我们来看看演变图:
单机的情况下如下:
没有加通知的情况如下:
上图可以看得触发【订单服务1】收到了路由请求,更新了数据,而其他的没收到通知、所以缓存还是旧数据。
通过第三方组件辅助后情况如下:
【业务模块】触发ZK事件、ZK 路径 发生改变通知到监控自己的客户端、ZK客户端收到通知后;触发更新数据操作,逻辑还是比较简洁简单。
其他的消息系统实现应该差不多,都是节点去订阅;更新就通知,ZK订阅通知传送的数据不能太大;毕竟不是存储基础的系统。如果需要大量的存储还得考虑别的场景。
4、总结
数据漂移问题:比如说在节点很多的情况下;由于网络问题很难保证每个节点同步,比如节点1 已经接受到请求并且更新了、节点2还没收更新请求;那么发送到节点2的请求处理的还是旧数据处理,就会造成部分结果不是最新的。
监控问题:如果节点多的情况下每个节点是否通知到;这个需要监控到;某个节点同步一旦出现问题,很难定位到问题;造成业务损失。
这里给个监控实现思路:
1、一个订单更新:生成一个【更新事件】记录。
2、每个节点收到通知更新完数据;立即上报自己的状态。
3、 启一个线程去循环查看【更新事件】记录状态是否被打满!
4、 比如5分钟内打满(监控耗时可以根据实际业务场景来设定);把事件状态改成【已完成】事件。
6、如果超过5分钟 并且没有被打满;立刻报警 发送短信、或者邮件通知开发运维去处理(包含节点信息方便定位问题)。
分布式的情况下使用本地缓存 业务场景要能够接受一定的数据同步延时。也就是接受部分数据不一致的情况,这是个前提。