黄波的博客开发、学习、生活、技术分享
黄波的博客开发、学习、生活、技术分享

高级篇(二)Redis 最佳实践

警告
本文最后更新于 2022-08-04,文中内容可能已过时。

1. Redis键值设计

1.1 优雅的key结构

key 最佳实践约定:

  • 遵循基本格式:[业务名称]:[数据名]:[id]
  • 长度不超过44字节
  • 不包含特殊字符

key是string类型,底层编码包含int、embstr和raw三种。embstr在小于44字节使用,采用连续内存空间,内存占用更小。当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片。

1.2 拒绝BigKey

推荐值:

  • 单个key的value小于10KB
  • 对于集合类型的key,建议元素数量小于1000

1.2.1 BigKey的危害

  • 网络阻塞
    • 对 BigKey 执行读请求时,少量的 QPS 就可能导致带宽使用率被占满,导致 Redis 实例,乃至所在物理机变慢
  • 数据倾斜
    • BigKey 所在的 Redis 实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡
  • Redis 阻塞
    • 对元素较多的 hash、list、zset 等做运算会耗时较长,使主线程被阻塞
  • CPU 压力
    • 对 BigKey 的数据序列化和反序列化会导致 CPU 的使用率飙升,影响 Redis 实例和本机其它应用

1.2.2 如何发现BigKey

  • redis-cli --bigkeys
  • SCAN 命令
  • 第三方工具
  • 网络监控

1.2.3 如何删除BigKey

BigKey 内存占用较多,即便时删除这样的 key 也需要耗费很长时间,导致 Redis 主线程阻塞,引发一系列问题。

使用unlink 命令异步删除

1.3 恰当的数据类型

  1. 例1:比如存储一个User对象,我们有三种存储方式:
  • json 字符串:实现简单;但数据耦合
  • 字段打散:可以访问任意字段;但占用空间大,没法统一控制
  • hash(推荐):底层 ziplist,占用空间小,可以访问任意字段;但代码相对复杂
  1. 例2:假如有hash类型的key,其中有100万对 field 和 value ,field 是自增 id,这个 key 存在什么问题?如何优化?

存在的问题:

  • hash 的 entry 数量超过 500 时,会使用哈希表而不是 ZipList,内存占用较多
  • 可以通过 hash-max-ziplist-entries 配置 entry 上限。但是如果 entry 过多就会导致 BigKey 问题

解决方案:

  • 拆分为小的 hash,将 id / 100 作为 key, 将 id % 100 作为 field,这样每 100 个元素为一个 Hash

2. 批处理优化

2.1 pipeline

redis 处理指令是很快的,主要花费的时候在于网络传输。于是乎很容易想到将多条指令批量的传输给 redis

方案:

  • msethmset 批量插入命令
  • pipeline:redis-py 可指定是否 transaction

2.2 集群下的批处理

集群下的批处理解决方案

redis-py:

  • cluster.mset({'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}) 不能用,必须同一个 slot 才行。因此要先用 cluster.keyslot(<key>)分组。
  • cluster.pipeline().set('k1', 'v1').set('k2', 'v2').set('k3', 'v3').execute() 可以用。

3. 服务端优化

3.1 持久化配置

  • 缓存的 Redis 实例不要做持久化
  • 建议关闭 RDB,使用 AOF,RDB 安全性不好,太频繁了性能消耗大
  • 利用脚本定期在 slave 做 RDB,实现数据备份
  • 设置合理的 rewrite 阈值,避免频繁的 bgrewrite
  • 配置 no-appendfsync-on-rewrite yes,禁止在 rewrite 期间做 AOF, 避免 AOF 引起阻塞(牺牲数据安全性
  • 部署相关:
    • 要预留足够内存应对 fork 和 rewrite
    • 单个实例内存上限不要太大(可单机多实例),以加快 fork 速度、减少主从同步、数据迁移压力
    • 不要和 CPU 密集型应用部署在一起
    • 不要和高硬盘负载应用部署在一起

3.2 慢查询优化

  1. 配置慢查询日志
  • slowlog-log-slower-than:慢查询阈值,单位是微秒。默认是10000,建议1000
  • slowlog-max-len:慢查询日志(本质是一个队列)的长度。默认是128,建议1000
  1. 查看慢查询
  • slowlog len:查询慢查询日志长度
  • slowlog get [n]:读取n条慢查询日志
  • slowlog reset:清空慢查询列表

3.3 命令及安全配置

Redis 未授权访问配合 SSH key 文件利用分析漏洞

为了避免这样的漏洞,这里给出一些建议:

  • Redis 一定要设置密码
  • 禁止线上使用下面命令:keys、flushall、flushdb、config set 等命令。可以利用 rename-command 配置禁用。
  • bind 配置:限制网卡,禁止外网网卡访问
  • 开启防火墙
  • 不要使用 Root 账户启动 Redis
  • 尽量不用默认的端口

3.4 内存配置

当 Redis 内存不足时,可能导致 Key 频繁被删除、响应时间变长、QPS 不稳定等问题。当内存使用率达到 90% 以上时就需要我们警惕,并快速定位到内存占用的原因。

内存占用说明
数据内存是Redis最主要的部分,存储Redis的键值信息。主要问题是BigKey问题、内存碎片问题
进程内存Redis主进程本身运⾏肯定需要占⽤内存,如代码、常量池等等;这部分内存⼤约⼏兆,在⼤多数⽣产环境中与Redis数据占⽤的内存相⽐可以忽略。
缓冲区内存一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用BigKey,可能导致内存溢出。

查看内存占用命令:

  • info memory: 查看内存分配的情况
  • memory …:查看 key 的内存占用情况

内存缓冲区常见的有三种:

  • 复制缓冲区:主从复制的 repl_backlog_buf,如果太小可能导致频繁的全量复制,影响性能。通过 replbacklog-size 来设置,默认1mb
  • AOF 缓冲区:AOF 刷盘之前的缓存区域,AOF 执行 rewrite 的缓冲区。无法设置容量上限
  • 客户端缓冲区:分为输入缓冲区和输出缓冲区,输入缓冲区最大 1G 且不能设置。输出缓冲区client-output-buffer-limit 配置)可以设置

处理大量的 big value,那么会导致我们的输出结果过多,如果输出缓存区过大,内存占满,会导致 redis 直接断开,而默认配置的情况下, 其实他是没有大小的,这就比较坑了,内存可能一下子被占满,会直接导致咱们的 redis 断开,所以解决方案有两个

  1. 设置一个大小
  2. 增加我们带宽的大小,避免我们出现大量数据从而直接超过了redis的承受能力

3.5 集群还是主从

集群虽然具备高可用特性,能实现自动故障恢复,但是如果使用不当,也会存在一些问题:

  • 集群完整性问题:cluster-require-full-coverage 配置是否所有 slot 都能用,集群才提供服务
  • 集群带宽问题:集群节点之间会不断的互相 Ping 来确定集群中其它节点的状态,节点太多时,会占用很多带宽
  • 数据倾斜问题
  • 客户端性能问题
  • 命令的集群兼容性问题
  • lua 和事务问题:lua 和事务都是要保证原子性问题,如果你的 key 不在一个节点,那么是无法保证lua的执行和事务的特性的,所以在集群模式是没有办法执行 lua 和事务的

单体 Redis(主从 Redis)已经能达到万级别的 QPS,并且也具备很强的高可用特性。如果主从能满足业务需求的情况下,所以如果不是在万不得已的情况下,尽量不搭建 Redis 集群

0%