Redis缓存中间件的使用,极大的提升了应用程序数据查询的性能和效率。在使用Redis的过程中存在其他的问题——Redis穿透、雪崩。
Redis穿透
redis穿透:指总是查询一条redis缓存中不存在的记录,会直接查询数据库略过redis,redis起不到作用。
解决方案:
1)缓存空对象
缓存空对象会有两个问题: 第一, 空值做了缓存, 意味着缓存层中存了更多的键, 需要更多的内存空间(如果是攻击, 问题更严重) , 比较有效的方法是针对这类数据设置一个较短的过期时间, 让其自动剔除。 第二, 缓存层和存储层的数据会有一段时间窗口的不一致, 可能会对业务有一定影响。例如过期时间设置为5分钟, 如果此时存储层添加了这个数据, 那此段时间就会出现缓存层和存储层数据的不一致, 此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
代码如下:
String get (String key){
// 从缓存中获取数据
String cacheValue = cache.get(key);
// 缓存为空
if (StringUtils.isBlank(cacheValue)) {
// 从存储中获取
String storageValue = storage.get(key);
cache.set(key, storageValue);
// 如果存储数据为空, 需要设置一个过期时间(300秒)
if (storageValue == null) {
cache.expire(key, 60 * 5);
}
return storageValue;
} else {
// 缓存非空
return cacheValue;
}
}
2)布隆过滤器拦截
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190820200234970.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NoaWNodXd1,size_16,color_FFFFFF,t_70)
如图,在访问缓存层和存储层之前, 将存在的key用布隆过滤器提前保存起来, 做第一层拦截。 例如: 一个推荐系统有4亿个用户id, 每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中, 但是最新的用户由于没有历史行为, 就会发生缓存穿透的行为, 为此可以将所有推荐数据的用户做成布隆过滤器。 如果布隆过滤器认为该用户id不存在, 那么就不会访问存储层, 在一定程度保护了存储层。
这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景, 代码维护较为复杂, 但是缓存空间占用少。
3)方案对比
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190820200650490.png)
Redis雪崩
redis雪崩:redis缓存中所有缓存数据失效,导致所有的查询都去查询数据库。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190820201449448.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NoaWNodXd1,size_16,color_FFFFFF,t_70)
解决方案:
1)保证缓存层服务高可用性。 缓存层设计成高可用的, 即使个别节点、 个别机器、 甚至是机房宕掉, 依然可以提供服务, 例如前面介绍过的Redis Sentinel和Redis Cluster都实现高可用。
2) 依赖隔离组件为后端限流并降级。 无论是缓存层还是存储层都会有出错的概率, 可以将它们视同为资源。 作为并发量较大的系统, 假如有一个资源不可用, 可能会造成线程全部阻塞( hang) 在这个资源上, 造成整个系统不可用。 降级机制在高并发系统中是非常普遍的: 比如推荐服务中, 如果个性化推荐服务不可用, 可以降级补充热点数据, 不至于造成前端页面是开天窗。 在实际项目中, 我们需要对重要的资源( 例如Redis、 MySQL、HBase、 外部接口) 都进行隔离, 让每种资源都单独运行在自己的线程池中, 即使个别资源出现了问题, 对其他服务没有影响。 但是线程池如何管理, 比如如何关闭资源池、 开启资源池、 资源池阀值管理, 这些做起来还是相当复杂的。
热点key重建优化
1)互斥锁
如果构建缓存过程出现问题或者时间较长, 可能会存在死锁和线程池阻塞的风险, 但是这种方法能够较好地降低后端存储负载, 并在一致性上做得比较好
2)设置缓存永不过期
这种方案由于没有设置真正的过期时间, 实际上已经不存在热点key产生的一系列危害, 但是会存在数据不一致的情况, 同时代码复杂度会增大。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)