关注公众号 架构师之路很久,沈剑分享的很多干货,收益良多。结合工作需要整理一下缓存应用中需要注意的问题。
缓存无处不在,跨进程的缓存:浏览器本地缓存–>cdn–>webServer(Nginx)–>appServer(tomcat,jetty,WAS)–>缓存服务器–>DB。
进程内缓存,一级缓存,二级缓存。
缓存分类
本地缓存
- 特点
优点:store in jvm;本地访问,快速访问,省去网络开销;各节点独立存储,适合小量数据,只读数据。
缺点:存储量有限,一份数据复制多份,每个jvm一份,浪费内存,多节点间数据更新同步复杂,影响服务无状态化,开发复杂度提高。 - 常见实现方式
HashMap
ConcurrentHashMap,
Ehcache- 使用场景
少量高频只读数据 - 缓存更新方式
应用启动时,初始化缓存,缓存预热。
读缓存,过期则 单线程从DB获取数据,存入本地缓存。
源数据更新,直接删除缓存。 缓存只提供读服务,不做缓存的更新修改。
多节点部署时,源数据更新,发消息到MQ,各个服务节点消费消息,删除本地缓存。
- 使用场景
缓存服务-分布式缓存服务
- Redis
- MemCache
- Apache Ignite
缓存常见误用
现象: 缓存当数据管道使用,多个应用进程读写同一个缓存服务
- 使用不当原因
- 应用间耦合严重,缓存偏向于数据存储层,存储直接暴漏给外部系统,架构分层上不合理。
- 服务器间Rpc通信,暴漏接口,针对接口建立协议,而不是针对实现。
- 数据权限归属不清
- 系统读写速度不同,互相影响,资源使用存在争抢问题。 - 解决方案
系统间通过服务接口约定交互协议。通过MQ解耦,缓存只是接口实现层面的事情。
现象:缓存雪崩
- 原因:缓存服务整体挂掉,导致流量压垮源数据库,导致源数据库启动不了。
- 解决办法:
容量规划、缓存水平切分,缓存局部挂掉,不影响整体。
缓存失效策略
限流方案
现象:调用方缓存数据
- 原因:开发人员原因
- 解决办法:
服务方缓存,作为服务的实现方式,对调用方隐藏。服务调用方只通过接口访问数据,客户端与缓存解耦合。
现象:缓存穿透
-
原因:访问大量不存在的数据,导致回源流量过大,给DB带来很大压力。
-
解决办法 :
缓存空对象。如果缓存未命中,而数据库中也没有这个对象,则可以缓存一个空对象到缓存。如果使用Redis,这种key需设置一个较短的时间,以防内存浪费。
缓存预测,预测key是否存在,如果缓存的量不大可以使用hash来判断,如果量大可以使用布隆过滤器来做判断. -
缓存更新策略
单线程独立写数据到缓存服务
缓存、源系统读写一致性问题
- WriteThrough
- ReadThrough
- 什么是“Cache Aside Pattern”?
- 读过程
先读cache,再读db
如果,cache hit,则直接返回数据
如果,cache miss,则访问db,并将数据set回缓存 - 写过程
第一步要操作数据库,第二步操作缓存
缓存,采用delete淘汰,而不是set更新
写过程–** 争议的地方**
考虑DB,缓存服务都是集群的情况:
1、先写DB,后同步缓存
正常情况:
db主–>db从同步完成–>缓存读新值,缓存存放最新的值。
异常情况:
db主写–>缓存服务读db从(旧值)–>db从同步新值。缓存存放旧值直到过期。
解决办法:
业务需求不要求实时一致,可忽略不处理。
技术上解决写时的不一致: - 第一种办法:
1、写数据时记录key值到分布式cache.预估数据同步时间段,设置key缓存有效期和同步时间一致。
2、读数据时,先从分布式cache查询key是否存在,若存在,则数据正在同步中,读节点为脏数据,从主节点读取,不存在,数据没有修改,从节点读取。
当然这种方案,依赖cache的高可用,只是缩小数据不一致的时间段,同时, - 另一种办法
1、先写DB,db节点数据同步的同时,异步发消息到MQ
2、缓存服务消费MQ内容,淘汰数据,从主DB同步最新数据。
此种办法,依赖MQ的高可用,消息有序。
总之,不管那种方案,基础服务,消息中间件的稳定性、可用性都是必须要有保障的,越基础的越重要。写服务数据主从同步期间,会出现短暂不一致,业务上是否可接受缓存数据的暂时不准确是首先要考虑的。