redis已经成为十分常用且好用的中间件,但使用仍需要注意一些问题
Redis问题
- 它的内存增长快——内存
- Redis操作延迟变大——性能
- 故障发生频率增多——高可靠
- 运维需要注意什么——日常运维
- 部署Redis的环境参数——资源规划
- 监控Redis关注什么指标——监控与安全
方案
1 内存
- Redis性能高是因为Redis是部署在内存上的数据库,所以访问Redis中数据速度很快
- 相对于磁盘来说,内存是比较珍贵的,当业务增大时Redis数据会越来越多
- 因此需要内存优化策略,防止因业务增长导致Redis占用的内存开始膨胀
- 控制key长度:保证 key 在简单、清晰的前提下,尽可能把 key 定义得短一些,节省过长的key内存内存 user:book:123优化成u:bk:123
- 避免存储大key:String 10KB以下,List/Hash/Set/Zset 元素数量控制在1w以下
- 选择业务合适的数据类型:String、Set尽量int类型(整数编码存储),Hash、ZSet存储元素数量控制在转换阈值以下,以压缩列表(ziplist)存储,节约内存,多数据才会转化成哈希表和跳表
- 把Redis当作缓存使用:尽可能设置过期时间,只保留经常访问的热数据,内存利用率比较高
- 实例设置maxmemory+淘汰策略
- 控制内存上限,提前预估业务数据量,设置maxmemory控制上限,避免膨胀
- volatile-lru / allkeys-lru:优先保留最近访问过的数据
- volatile-lfu / allkeys-lfu:优先保留访问次数最频繁的数据(4.0+版本支持)
- volatile-ttl :优先淘汰即将过期的数据
- volatile-random / allkeys-random:随机淘汰数据
- 控制内存上限,提前预估业务数据量,设置maxmemory控制上限,避免膨胀
- 数据压缩后写入Redis:进一步优化,采用snappy、gzip等压缩算法,但需要权衡,客户端去解压缩会使得CPU资源消耗增多
2 高性能
- 单机Redis可以达到10w的QPS,如果发生延迟情况,那就不对了,需要尽可能避免操作延迟
- 避免存储大key
- 由于 Redis 处理请求是单线程的,在写入一个 bigkey 时,更多时间将消耗在「内存分配」上,同样在删除一个bigkey时,释放内存这个耗时更多,它们都会使得操作延迟
- 同时有网络数据传输耗时增加的可能,导致后续请求排队
- 若确实有需求读取,应当拆分成多个小key存储
- 开始lazy-free 机制
- 4.0+支持,机制在删除一个bigkey时,主线程只删除bigkey,释放内存的耗时操作会放导后台去执行,最大程度避免对主线程的影响
- 无法避免bigkey的情况开启
- 不使用复杂度过高的命令
- 如 SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE 等聚合类命令,建议放在客户端执行。
- Redis是单线程模型处理请求,执行复杂度过高的命令会让其他请求等待,发生排队延迟,同时也会消耗更多的CPU资源
- 执行O(N)命令,要关注N的大小
- 一次查询过多数据,传输过程耗时会很长,操作延迟增大
- 在查询数据时,你要遵循以下原则:
- 先查询数据元素的数量(LLEN/HLEN/SCARD/ZCARD)
- 元素数量较少,可一次性查询全量数据
- 元素数量非常多,分批查询数据(LRANGE/HASCAN/SSCAN/ZSCAN)
- 对于容器类型(List/Hash/Set/ZSet),在元素数量未知的情况下,一定不要无脑执行 LRANGE key 0 -1 / HGETALL / SMEMBERS / ZRANGE key 0 -1。
- 关注DEL时间复杂度
- O(1)一般,但是不一定
- string类型是O(1)
- List/Hash/Zset/Set O(n),即删除一个key,元素个数越多del越慢
- 删除大量元素时,需要依次回收每个元素的内存,越多越慢
- 建议:分批删除
- List:多次LPOP或RPOP,直到元素删除完
- Hash/Set/ZSet:先执行HSCAN/SSCAN/SCAN查询元素,再执行 HDEL/SREM/ZREM 依次删除每个元素
- 批量命令代替单个命令
- 可以显著减少客户端、服务端的来回网络 IO 次数
- String / Hash 使用 MGET/MSET 替代 GET/SET,HMGET/HMSET 替代 HGET/HSET
- 其它数据类型使用 Pipeline,打包一次性发送多个命令到服务端执行
- 避免集中过期key
- 清理按照采用定时 + 懒惰的方式在主线程执行
- 存在大量key集中过期,在清理过期key时会有阻塞主线程的风险
- 设置过期时间时,再增加一个随机时间,打散它们,降低集中过期的风险
- 使用长连接操作Redis,合理配置连接池
- 每次建立TCP连接都需要经过三次握手、四次挥手,这个过程也相当耗时
- 连接池访问Redis,设置合理的参数,长时间不操作Redis时,需及时释放资源
- 建议只用db0
- 有16个db(默认)
-
在一个连接上操作多个db数据,每次都需要先执行SELECT,会给Redis带来额外的压力
-
多个db目的是按不同业务线存储数据,但其实可以拆分多个实例存储,拆分多个实例不但不会相互影响,还能提高Redis访问性能
-
Cluster只支持db0,后期想迁移到Cluster,成本高
-
- 读写分离+分片集群
- 如果业务读请求量很大,那么可以采用部署多个从库的方式,实现读写分离,让 Redis 的从库分担读压力,进而提升性能。
- 如果你的业务写请求量很大,单个 Redis 实例已无法支撑这么大的写流量,那么此时你需要使用分片集群,分担写压力。
- 不开启AOF或者AOF配置为每秒刷盘
-
如果对于丢失数据不敏感的业务,建议不开启 AOF,避免 AOF 写磁盘拖慢 Redis 的性能。
-
如果确实需要开启 AOF,建议配置为 appendfsync everysec,把数据持久化的刷盘操作,放到后台线程中去执行,尽量降低 Redis 写磁盘对性能的影响
- 使用物理机部署Redis
-
持久化是创建子进程的方式进行,虚拟环境执行fork的耗时会大的多
- 关闭操作系统内存大页机制
- Linux 操作系统提供了内存大页机制,其特点在于,每次应用程序向操作系统申请内存时,申请单位由之前的 4KB 变为了 2MB。
- 我们想想持久化:当 Redis 在做数据持久化时,会先 fork 一个子进程,此时主进程和子进程共享相同的内存地址空间。
- 当主进程需要修改现有数据时,会采用写时复制(Copy On Write)的方式进行操作,在这个过程中,需要重新申请内存。
- 如果申请内存单位变为了 2MB,那么势必会增加内存申请的耗时,如果此时主进程有大量写操作,需要修改原有的数据,那么在此期间,操作延迟就会变大。
- 避免存储大key
3 可靠性
- 关键在于持续性,从三大维度包装
- 资源隔离、多副本、故障恢复
-
按业务线部署实例
- 按不同业务线部署Redis实例,一个实例崩了不会影响其他 2.部署主从集群
- 多副本实例
- 主库宕机,从库仍然可以使用,避免了数据丢失风险,降低了服务不可用的时间
- 主从库要分布到不同机器,且不要交叉部署
- 主库会承担所有的读写流量,优先保证主库的稳定性,从库机器异常也不能影响到主库
- 定时备份只在从库机器执行,消耗从库机器的资源,避免对主库的影响
-
合理配置主从复制参数
- 参数不合理
- 赋值中断
- 从库发起全量复制导致主库性能收到影响
- 设置合理的 repl-backlog 参数:过小的 repl-backlog 在写流量比较大的场景下,主从复制中断会引发全量复制数据的风险
- 设置合理的 slave client-output-buffer-limit:当从库复制发生问题时,过小的 buffer 会导致从库缓冲区溢出,从而导致复制中断
- 参数不合理
-
部署哨兵模式,实现故障自动切换
- 只部署了主从节点,但故障发生时是无法自动切换的,所以,你还需要部署哨兵集群,实现故障的
自动切换。 - 多个哨兵节点需要分布在
不同机器上,实例为奇数个,防止哨兵选举失败,影响切换时间。
- 只部署了主从节点,但故障发生时是无法自动切换的,所以,你还需要部署哨兵集群,实现故障的
4 安全
- Redis被注入可执行脚本的问题,root权限问题
- 不要把 Redis 部署在公网可访问的服务器上
- 部署时不使用默认端口 6379
- 以普通用户启动 Redis 进程,禁止 root 用户启动
- 限制 Redis 配置文件的目录访问权限
- 推荐开启密码认证
- 禁用/重命名危险命令(KEYS/FLUSHALL/FLUSHDB/CONFIG/EVAL)
运维
-
禁止使用 KEYS/FLUSHALL/FLUSHDB 命令
- 这些命令会长时间阻塞Redis主线程
- 建议:SCAN替换成KEYS,在4.0+上可以使用FLUSHALL/FLUSHDB ASYNC,清空数据的操作放在后台线程执行
-
扫描线上实例时,设置休眠时间
- 对实例做大key的统计分析或者SCAN扫描线上实例,都建议在扫描时设置休眠时间,防止扫描过程中实例中的OPS(每秒操作次数)过高对Redis产生性能抖动
-
慎用MONITOR命令
- 排查问题会通过这个命令查看正在执行的命令
- 但是如果OPS高,会导致Redis输出缓冲区的内存持续增长,内存资源严重消耗,可能会导致内存超过maxmemory而引发内存淘汰
-
从库必须设置为slave-read-only
- 从库要避免写入数据,导致主从数据不一致
- 4.0以下,从库若不是read-only,写入有过期时间的数据,不会做定时清理和释放内存
-
合理分配timeout和tcp-keepalive参数
- 网络问题导致意外的连接中断,maxclients参数又很小,导致客户端无法于服务端建立新的连接(服务端认为超过了maxclients),原因是没建立一个连接,都会为客户端分配一个client fd,网络发生问题时,服务端并不会立即释放这个client fd
- 内部有个定时任务,会定时检测所有client的空闲时间是否超过配置的timeout值
- 若没有开启tcp-keepalive,直到timeout时间后才会清理释放client fd
- 没有清理之前,如果还有大量新连接进来,会导致Redis内部持有的client fd超过了maxclients,新连接会被拒绝
- 优化
- 不要配置过高的timeout:让服务器端尽快把无效的client fd清理
- Redis开启tcp-keepalive:服务端会定时给客户端发送TCP心跳包,检测连接连通性,当网络异常时,可以尽快清理僵尸client fd
-
调整maxmemory时,注意主从库的调整顺序
- 5.0以下,从库内存超过maxmemory,会触发数据淘汰
- 某些场景下从库可能更早达到maxmemory(MONITOR导致输出缓冲区占用大量内存),从库开始淘汰数据,主从库就会产生不一致
- 主从库修改顺序
- 调大maxmemory:先修改从库,再修改主库
- 调小maxmemory:先修改主库,再修改从库
- 5.0 增加了一个配置replica-ignore-maxmemory,默认从库超过maxmemory不会淘汰数据,就解决了这个问题
- 5.0以下,从库内存超过maxmemory,会触发数据淘汰
额外说明
-
合理资源规划和完善监控预警非常重要
- 首先保证CPU、内存、磁盘资源、带宽都要足够
- 规划好容量,主库机器预留一半的内存空间,防止主从机器网络故障,引发大面积全量同步,导致主库机器内存不足的问题
- 单个实例内存建议10G以下,大实例在主从全量同步、RDB备份时有阻塞风险
-
监控
- 资源不足要及时抱紧
- 设置合理的slowlog阈值,slowlog过多时要报警
- 监控组件才气Redis INFO信息时要用长连接,避免频繁的短连接
- 做好实例运行时监控,重点关注expired_keys、evicted_keys、latest_fork_usec指标,这些指标短时突增可能有阻塞风险
总结
- 知识点很多,但都很重要
- 加油写文章吧