redis学习-缓存设计中要提前考虑的事情

[toc]

[toc]

本系列文章整理摘抄自

缓存设计前需要权衡成本和收益

收益

  • 加速读写
  • 降低后端负载

成本

  • 数据不一致性
    • 缓存层和存储层数据势必会有不一致的时间,需要考虑如何避免对业务造成的影响。
  • 代码维护成本
    • redis 和 mysql 两方代码都要考虑。

缓存如何更新?

基于内存的缓存不可能将所有的数据都做缓存,一般针对的都是热点数据。热点数据具有时效性,过了一定时间将成为非热点数据

所以,缓存数据通常具有生命时长,到达指定的时间后,将被更新或者删除。

熟悉缓存的常用更新策略,才能针对业务场景做出合理的选择。

更新策略1: LRU/LFU/FIFO 算法

通常缓存量超过了预设的最大值时,将会采取以上策略,具体由 maxmemory-policy 参数指定。

一致性最差。

更新策略2: 超时剔除

通过给缓存数据设置过期时间,让其在过期时间后自动删除,例如Redis提供的expire命令。

一致性取决于时间窗口。

更新策略3: 主动更新

当堆数据一致性要求高时,在更新存储层数据后,需要立即更新缓存中的数据。

一致性最好。

最佳实践方案

低一致性业务

配置最大内存 + 淘汰策略

高一致性业务

超时剔除 + 主动更新

缓存的粒度如何选?

对于缓存数据库的内容时,要考虑缓存全部字段,或者部分字段。

  • 全部字段:
    • 全部缓存占用空间过大
    • 并不是所有字段都用的到
  • 部分字段:
    • 未来可能用到新字段

缓存预热

在系统刚上线,直接将数据加载进缓存系统,提前准备。

缓存穿透问题(针对无效查询)

概念描述

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不命中。

产生的原因

  • 问题1:业务代码或者数据出现问题
  • 问题2:恶意攻击,爬虫造成的大量空命中

造成的影响

如果出现大量的缓存穿透,会对后端的数据库产生大流量的冲击,严重可使得数据库服务宕机。

通用解决方案

问题如何发现?

统计总调用数、 缓存层命中数、 存储层命中数,如果发现大量存储层空命中, 可能就是出现了缓存穿透问题。

问题如何预防?

问题1的解决. 缓存空对象 + 设置短过期时间

实时性高。

存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。

问题2的解决. 布隆过滤器拦截(缓存层之前对存在的key做保存)

实时性差。

访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来, 做第一层拦截

缓存雪崩

概念描述

发生大规模的缓存失效的情况,或者缓存层宕机,大量流量冲入存储层。

产生的原因

缓存服务由于各种原因失效了。

造成的影响

数据库服务宕机。

通用解决方案

提前的规划: 保证缓存服务的高可用。

主从 + 哨兵, 集群。
Redis Sentinel和RedisCluster都实现了高可用

出现雪崩时的处理:隔离组件做限流和降级处理

  • ehcache本地缓存 + Hystrix限流并降级,避免MySQL被搞死。
  • 加锁排队(并发量不高的情况下)

Hystrix限流并降级的流程

海量的用户请求出现:

  • 首先通过限流组件Hystrix限流(只有预定的请求进入存储层)
  • 对于未通过的流量,直接导到 预定的降级处理方案,比如友好的提示等待。

3. 提前排演测试

缓存击(针对失效数据)-热点数据高并发访问时,失效来不及重建

概念描述

当热点数据并发访问量非常大,由于之前设置了过期时间,失效后难以短时间重建。

比如: 热点的娱乐新闻。

产生的原因

缓存服务由于各种原因失效了。

造成的影响

数据库服务宕机。

通用解决方案

1. 限制重建的线程数 - 互斥锁

并不是所有的线程都需要去重建,第一个遇到的线程重建,其他线程等待即可。

互斥锁的实现1:通过setnx 和 expire命令实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
String get(String key) {
// 从Redis中获取数据
String value = redis.get(key);
// 如果value为空, 则开始重构缓存
if (value == null) {
// 只允许一个线程重构缓存, 使用nx, 并设置过期时间ex
String mutexKey = "mutext:key:" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
// 从数据源获取数据
value = db.get(key);
// 回写Redis, 并设置过期时间
redis.setex(key, timeout, value);
// 删除key_mutex
redis.delete(mutexKey);
}/
/ 其他线程休息50毫秒后重试
else {
Thread.sleep(50);
get(key);
}
}return value;
}
互斥锁的实现2:通过watch和Redis的事务命令实现

2. 永不过期

但是会出现数据不一致的情况。

3. 错峰失效

不要让热点数据集中失效,而是一批一批,分时间段的失效

4. 失效后sleep(rand()) 这样不会所有请求都去立刻查db

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值