redis常见问题解决方案和使用规范

公众号:yunops

一、Redis 说明:

二、部署与集群

这里并不就部署进行详细展开,简单就可选的集群模式及其特点说明如下:

  1. Twemproxy
    • 无法平滑的扩容或者缩容,甚至修改配置都需要重启服务;
    • 很难运维,甚至没有 Dashboard;
  2. Redis Cluster(官方方案):
    • 无中心化设计;
    • 程序难以编写,代码量多的吓人,最佳实践很少;
    • 整个系统高度耦合,升级困难;
    • 不支持 database
  3. codis
    • 部分原生命令不支持
  4. redis-sentinel 模式
    • 不是严格意义上的集群,只是高可用方案的一种;
    • 需要 cli 客户端适配,不是透明连接的;

三、redis 数据淘汰策略

Redis 一般用来缓存热点数据,为了更好的利用内存,Redis 设计了相应的内存淘汰机制(也叫做缓存淘汰机制),分为两类(被动删除和主动删除),说明如下:

四、redis 常见问题与解决方案

  1. 缓存一致性:缓存和数据库数据保持一致):
    • 解决方式:旁路缓存(Cache Aside Pattern)
      • 读请求:先读 cache,再读 db
      • 变更操作:先操作数据库,再 淘汰 缓存
  2. 缓存击穿(影响轻微):高流量下 大量请求读取一个失效的 Key -> Redis Miss -> 穿透到 DB
    • 解决方式:采用分布式锁,只有拿到锁的第一个线程去请求数据库,然后插入缓存
  3. 缓存穿透(影响一般):访问一个不存在的 Key(恶意攻击)-> Redis Miss -> 穿透到 DB
    • 解决方式:
      • 给相应的 Key 设置一个 Null 值,放在缓存中
      • BloomFilter 预先判断
  4. 缓存雪崩(影响严重):大量 Key 同时失效或者 Redis 宕机 -> Redis Miss -> 压力打到 DB
    • 解决方式:
      • 给失效时间加上相对的随机数
      • 保证 Redis 的高可用
  5. 缓存污染(影响一般):有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,继续留存在缓存中白白占用缓存空间。
    • 解决方式:
      • 在明确知道数据被再次访问的情况下,volatile-ttl 可以有效避免缓存污染
  6. hot key(影响严重):突然有大量请求去访问 redis 上的某个特定 key,过于集中的流量达到物理网卡上限,从而导致这台 redis 的服务器宕机;
    • 提前发现 hot key:
      • 凭借业务经验,进行预估哪些是热 key
      • 在操作 redis 之前,加入一行代码进行数据统计
      • 用 redis 自带命令:monitor 或者 redis-cli + hotkeys 参数(redis 4.0.3 以上)
    • 解决方式:
      • 利用二级缓存:比如利用 ehcache,或者一个 HashMap 都可以。在你发现热 key 以后,把热 key 加载到系统的 JVM 中。;针对这种热 key 请求,会直接从 jvm 中取,而不会走到 redis 层。
      • 备份热 key:将 HotKey 与一个随机数组合生成一个新 key,分散到多个 redis 实例中;
  7. redis 大 key(影响严重)可能导致如下问题 - 解决方式:禁止使用大 key
    • 集群中各节点内存空间不均衡
    • 超时阻塞:由于 Redis 单线程的特性,操作 bigkey 比较耗时,也就意味着阻塞 Redis 的可能性增大;
    • 网络阻塞:每次获取 bigkey 产生的网络流量较大,假设一个 bigkey 为 1MB,每次访问量为 1000,那么每秒产生 1000MB 的流量,对于普通的千兆网卡(按照字节算是 128MB/s)的服务器简直是灭顶之灾;
  8. Redis 阻塞(影响严重)- 解决方式:提前做好规划,及时定位处理;
    • 可能导致 Redis 阻塞的原因(根据实际情况单独处理):
      1. 慢查询
      2. bigkey 大对象
      3. swap
      4. fork 子进程
      5. AOF 刷盘阻塞
      6. Redis 输入、输出缓冲区导致的阻塞
      7. 网络问题

五、redis 使用规范

  1. 冷热数据区分:只存储热数据 (如 QPS 超过 5k) 的数据加载到 Redis,低频数据放到 Mysql 或者 ES 中
  2. 业务数据分离:根据数据业务关联性分多个 Redis 实例使用,可避免业务相互影响、避免单实例膨胀,并降低故障影响面;
  3. 使用连接池且避免频繁创建销毁:连接池可以降低操作连接的资源开销,请确保使用了正确的 Redis 客户端连接池配置,避免在使用了连接池的情况下仍然存在连接频繁创建和销毁的情况
  4. 消息大小限制(避免大 key 和 hot key):Redis 是单线程服务,消息过大会阻塞并拖慢其他操作、也可能导致持久化到磁盘时的 I/O 问题,保持消息体在 1kb 以下(最大不过 10kb,可以使用 snappy、msgpack 等压缩)
  5. 缓存 Key 设置失效时间:作为缓存使用的 Key,必须要设置失效时间(注意失效时间的单位:有的是秒,有的是毫秒)
  6. 缓存不能有中间态:缓存应该仅作缓存用,去掉后业务逻辑不应发生改变,万不可切入到业务里,徒增增加业务风险和维护成本;
  7. 严禁使用 Keys:属于 O(N)操作,会阻塞其他正常命令,且效率极低;建议 rename 此命令,从根源禁用;
  8. 严禁使用 Flush:flush 命令会清空所有数据,属于高危操作,应该 rename 此命令,从根源禁用;
  9. 严禁作为消息队列使用:Redis 当作消息队列使用,会有容量、网络、效率、功能方面的多种问题;
  10. 严禁不设置范围的批量操作:避免执行批量操作函数,如不设范围的 zset 操作、对大数据量 Key 使用 HGETALL、Redis Cluster 集群的 mget 操作、sunion, sinter, sdiff 等一些聚合操作;
  11. 禁止事务操作:redis 本身已经很快了,如无大的必要,建议捕获异常进行回滚,不要使用事务函数
  12. 禁止 lua 脚本扩展:和 SQL 的存储过程类似,redis 中使用 lua 会引入性能和一些难以维护的问题;
  13. 禁止长时间 monitor:monitor 函数可以快速看到当前 redis 正在执行的数据流,但是长时间阻塞在 monitor 命令上,会严重影响 redis 的性能(尽量避免高峰时间 monitor)
  14. 禁止分 database 使用(禁用 select 函数):容易误操作且 cluster 集群模式也不支持多个 database;
  15. 建议不使用高级数据结构:使用基本的 5 种:string(字符串)、list(列表)、hash(字典)、set(集合) 和 zset(有序集合)
  16. 建议扩展方式首选客户端 hash 而不是扩容集群规格(比如上 M/S 或者 Cluster):集群越大,在状态同步和持久化方面的性能越差,优先使用客户端 hash 进行集群拆分,将数据落到不同的 redis 实例中;

参考资料