一、场景问题

1.数据丢失场景

持久化数据丢失:

  • RDB采用定时备份,有可能丢失间隔区间内的数据
  • AOF使用追加,也有可能丢失1s的数据

主从切换:主从切换是异步的,必定会导致数据丢失

脑裂问题:出现多个master,网络恢复时丢失一些master上的数据

2.跟数据库数据一致性问题

缓存一致性问题:mysql数据变更,如何保证redis的数据一致性呢?

常见的答案是延时双删,即先删redis,再该数据库,然后间隔一定时间再删redis

防止修改数据期间从redis读到不一致数据

但是实际上,这个间隔时间如何确定?工作中也基本上没有人用延时双删,因为redis本来就不是一个保证强一致性的数据库,它的优点在于快

所以最常见的方式,就是直接把redis的数据删除即可,下次查询数据库时再放进去就行了

常见数据一致性解决方案:

  • 加锁:牺牲性能,不建议使用
  • 延时双删:间隔时间不好确定,不建议使用
  • 最终一致性方案
    • 给缓存设置过期时间,允许一部分时间内的数据不一致
    • canal监听,在数据变更后同步修改redis数据

3.缓存穿透、击穿、雪崩

缓存穿透

定义:查询的key,redis里面没有,db里面也没有

解决方案:

  • 封禁ip:如果某ip大量请求不存在的key,直接把这个ip干掉
  • 布隆过滤器:不存在的一定无法经过布隆过滤器
缓存击穿

定义:某热点key突然失效,全部请求都打到了db

解决方案:加互斥锁,不影响正常访问,但是过期后会加锁,防止大量请求到db

缓存雪崩

定义:大量热点key同时失效

解决方案:

  • 保证redis高可用:防止出现redis挂了导致的缓存雪崩
  • 设置不同的过期时间
  • 加互斥锁:很少这么干

但是实际上,现在mysql也都是集群部署,崩不了

4.慢查询、阻塞和bigkey

慢查询

所谓慢查询,就是很慢的查询-_-#

如何找到?

在默认配置中,超过10ms的查询就是慢查询(不包含网络时间)

会保存在慢查询日志当中,也可以通过监控工具进行监控,报警

如何解决?

拆分数据

阻塞

通常是指Redis服务器无法立即响应客户端请求的情况

可以通过业务日志,监控命令来判断阻塞原因

  • 外部原因:网络阻塞,cpu被占用
  • 内部原因:
    • 指令查询耗时
    • 数据结构设置不合理,导致查询过久
    • fork子线程在进行aof刷盘
bigKey

指一个key对应的value过大

可以采用拆分,压缩,分片等方式解决

  • 拆分:比如hash类型,可以将里面的键值对多拆分拆分,变成小一点的结构
  • 压缩:采用一定的压缩算法,将数据规模变小,提高存储效率
  • 分片:想办法把数据映射到不同哈希槽上,对key拆分,别都放到一起

二、实战

1.客户端选择

Jedis

  • 简单,使用较多
  • 非线程安全
  • 支持多种模式

Lettuce

  • 线程安全
  • 支持同步、异步
  • 支持读写分离
  • 支持多种模式

Redission

  • 支持分布式锁
  • 支持分布式容器等高级功能

2.实战之授权

存储分布式session

3.实战之用户状态保存

使用bitmap数据结构,记录用户的一些状态,比如是否点过新人引导,是否领过优惠券,使用0/1表示即可,状态不可逆,以用户为粒度进行读写

同时,mysql里面也要有一个bigint的结构与其对应,保证最终一致性

4.实战之流量限制

限流基本概念:

  • 对访问频率的限制
  • 对连接数的限制
  • 对客户端的限制
  • 对速率的限制

限流常用算法:

  • 时间窗算法:设定一段时间内的请求次数上限来进行限流的
  • 漏桶算法:桶大小固定,出桶速度固定,可能会溢出
  • 令牌桶算法:能在短时间内处理大量请求
    • 桶里最多x个令牌,因此同时最多能支持x个取令牌操作
    • 有一个线程检测桶里令牌是否低于x,是的话就定时产生令牌放桶里
    • 拿走令牌的线程也可以在任务处理完毕后,在桶里令牌不到x时放回去

在分布式系统中,可以使用令牌桶算法,使用redis搭配搭配网关gateWay,来进行分布式系统限流