Redis 从入门到入土①:概述及问题

Redis 是速度非常快的 非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射

1. 与 Memcached 的不同

  1. 数据类型:
    1. Redis 支持五种不同的数据类型,可以更灵活地解决问题
    2. Memcached 仅支持字符串类型
  2. 数据持久化:
    1. Redis 支持两种持久化策略:RDB 快照和 AOF 日志
    2. Memcached 不支持持久化
  3. 分布式:
    1. Redis Cluster 实现了分布式的支持
    2. Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点
  4. 内存管理机制:
    1. 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中
    2. Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了

2. 遇到的问题

缓存世界中的三大问题及解决方案

2.1. 缓存和数据库双写一致性问题

  • ** 只能降低 ** 不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存
  • 不能先写缓存再写数据库,万一数据库事务回滚会产生数据不一致
  • 采取 正确更新策略,先更新数据库,再删缓存
  • 因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用 消息队列

2.2. 缓存雪崩问题

概念: 即缓存 同一时间大面积 失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常
思路:

  1. 不同时间
  2. 双缓存

解决:

  1. 给缓存的失效时间,加上一个 随机值,避免集体失效
    1. 零点补课避免出现大量过期,不宜使用随机
    2. 与时点性无关,可以使用随机
  2. 使用 互斥锁,但是该方案吞吐量明显下降了
  3. 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作
    1. 从缓存 A 读数据库,有则直接返回
    2. A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程。更新线程同时更新缓存 A 和缓存 B

2.3. 击穿
缓存过期了

  1. 利用 互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试

2.3. 缓存穿透问题

概念: 黑客故意去 ** 请求 ** 缓存中 ** 不存在 ** 的数据,导致所有的请求都怼到数据库上,从而数据库连接异常
直接原因:

  1. 频繁请求
  2. 值不存在直接请求数据库

思路:

  1. 对请求做拦截(锁、拦截器)
  2. 不直接访问数据库

解决:

  1. 利用 互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
  2. 采用 异步更新策略,无论 key 是否取到值,都直接返回
    1. value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存
    2. 需要做缓存预热(项目启动前,先加载缓存)操作
  3. 提供一个能迅速判断请求是否有效的 拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回

2.4. 并发竞争 key 问题

概念: 同时有多个子系统去 set 一个 key

  1. 如果对这个 key 操作,不要求顺序
    1. 分布式锁
  2. 如果对这个 key 操作,要求顺序
    1. 写入数据库的时候,需要保存一个 时间戳

image.png
采用队列模式将并发访问变为串行访问

3. 单线程快速的原因

  1. 纯内存操作
  2. 单线程操作,避免了频繁的上下文切换
  3. 采用了非阻塞 I/O 多路复用机制

image.png