本文从如下7个维度,带你全面理解Redis的最佳实践和优化:
1、如何节省内存
1.1、控制Key的长度
- 在开发业务时,要提前预估Redis中写入key的数量,如果key数量达到了百万级别,那过长的key也会占用过多的内存空间
- 在保证key简单、清晰的前提下,key要尽可能地简短,这对内存的优化非常直接和高效
1.2、避免存储bigkey
- 如果大量存储bigkey(value过大或过多),会占用大量Redis内存;此外,客户端读写bigkey也会产生性能问题
- 避免Redis存储bigkey,建议如下:
1)String类型:控制在10KB以下
2)List/Hash/Set/ZSet:元素数量控制在1万以下
1.3、选择合适的数据类型
- Redis提供了丰富的数据类型,这些数据类型底层对应着多种数据结构来实现(可进一步节约内存资源):如,String、Set在存储int数据时,会采用整数编码来存储;Hash、ZSet在存储元素较少时,会采用压缩列表(ZipList)存储,在元素较多时,会转为哈希表(HashTable)和跳表(SkipList)存储 ==》 Hash:HashTable;ZSet:HashTable + SkipTable
- 利用如上数据存储的特性,建议如下:
1)String、Set:尽量存储int类型数据
2)List/Hash/Set/Zset:存储的元素数量尽可能地控制在转换阈值之下(元素数量尽量控制在1万以下),以便节省内存
1.4、把Redis当做缓存而非数据库
- 要把Redis当作缓存而非数据库,即Redis只存储经常访问的热点数据,以此保证较高的内存利用率
- 写入到Redis中的数据,尽可能设置过期时间
- 业务应用在Redis中无法查询到数据时,才从后端数据库查询并加载到Redis
1.5、设置内存上限和淘汰策略
- Redis中key都设置了过期时间,但如果业务应用写入数据量很大,并设置的过期时间比较久,则短期内Redis内存依旧会快速增长。如果控制Redis内存的上限,就会导致使用过多的内存资源
- 对于此种场景,你需要提前预估业务数据量,并为Redis实例设置maxmemory来控制实例的内存上限,如此来避免Redis占用内存持增长
- 此外,还需要结合业务特点设置数据的淘汰策略(默认情况下,推荐使用allkeys-lru)
1)基于LFU算法的淘汰策略(对是否设置了过期时间TTL进行再分类)
allkeys-lfu:对全体key,基于LFU算法进行淘汰(默认推荐)
volatile-lfu: 对设置了TTL的key,基于LFU算法进行淘汰
2)基于LFU算法的淘汰策略(对是否设置了过期时间TTL进行再分类)
allkeys-lru:对全体key,基于LRU算法进行淘汰
volatile-lru:对设置了TTL的key,基于LRU算法进行淘汰
3)随机淘汰(对是否设置了过期时间TTL进行再分类)
allkeys-random:对全体key ,随机进行淘汰
volatile-random:对设置了TTL的key ,随机进行淘汰
4)其他
volatile-ttl:对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰
noeviction:不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略
1.6、数据压缩后写入Redis
- 如果想进一步优化Redis内存,还可以在业务应用中先将数据压缩,再写入到Redis(采用snappy、gzip等压缩算法),但是,客户端在读取压缩存储的数据时要解压数据,这会消耗一定的CPU资源,这需要根据实际情况来权衡
2、如何发挥Redis的高性能
- 在使用Redis时,往往需要我们关注于如何持续发挥Redis的高性能,从而避免操作延迟的情况发生,结合实战经验给出的建议如下:
2.1、避免存储bigkey
- 由于Redis处理请求是单线程的,当写入一个bigkey时,会在内存分配上耗费较多时间,导致操作延迟会增加,同理,删除bigkey会耗时地释放内存
- 在读取bigkey时,会在
网络数据传输
上花费更多时间,后续执行的请求会发生排队,导致Redis性能下降 - 如果确实有存储bigkey的需求,可以将bigkey拆分为多个小key存储
2.2、开启lazy-free机制
- 如果无法避免存储bigkey的情况,建议开启Redis的lazy-free机制。当开启lazy-free后,删除bigkey时释放内存的耗时操作将会交由后台线程去执行,如此以避免对主线程的影响
2.3、批量命令代替单个命令
- 当需要一次性操作多个key时,推荐使用批量命令来处理,相较于多次单个操作的优势在于,可减少客户端和服务端之间的网络IO次数
1)String或Hash结构使用MGET/MSET代替GET/SET命令,HMGET/HMSET代替HGET/HSET命令
2)其他数据类型使用Pipeline,一次性打包发送多个命令到服务端执行
2.4、注意DEL操作的时间复杂度
2.5、避免key集中过期
- Redis通过定时+懒惰的方式来清理过期key,该过程在主线程中执行
- 如果业务中存在大量key过期的情况,Redis在清理过期key的过程中,存在主线程阻塞的风险,可以通过给过期时间增加一个随机值,将key的过期时间分散开来,从而避免大量key集中过期对主线程的影响
2.6、使用长连接操作Redis,合理配置连接池
- 业务应用要使用长连接操作Redis,而非短连接
- 当使用短连接操作Redis时,每次都需要经过TCP三次握手,四次挥手,会徒增操作耗时
- 客户端应该使用连接池的方式访问Redis,并设置合理的参数,长时间不操作Redis时,应及时释放连接资源
2.7、只使用db0
- Redis尽管提供了16个db,但推荐只使用db0,理由如下:
1)在一个连接上操作多个db数据时,每次都需要执行SELECT,会给Redis带来额外的压力
2)Redis Cluster只支持db0,如果想要在集群中迁移数据,迁移成本会很高
2.8、使用读写分离 & 分片集群
- 如果
业务
读请求量很大,则可以采用部署多个从库的方式,实现读写分离
,通过Redis的从库来分担读请求的压力,进而提升性能
- 如果
业务
写请求量很大,单个Redis实例已经无法支撑写流量
,则可以使用分片集群来减轻写请求的压力
2.9、不开启AOF或AOF配置为每秒刷盘
- 如果是丢失数据不敏感的业务,不建议开启AOF,避免AOF写磁盘影响Redis的性能;如果确实需要开启AOF,则建议配置为appendfsync everysec,把数据持久化的刷盘操作交给后台线程执行,尽量降低Redis写磁盘对性能的影响
2.10、使用物理机部署Redis
- Redis通过创建子进程来完成数据的持久化操作,而创建子进程 需要使用系统调用fork函数【fork函数:用于从一个已经存在的进程内创建一个新进程,这会极大地浪费时间和系统资源,Linux内核需要采取写时拷贝技术(Copy On Write)来提高效率】
- 虚拟机环境执行fork操作的耗时要比物理机慢得多,所以,Redis应尽可能地部署在物理机上
3、如何保证Redis的可靠性
下面从资源隔离、多副本、故障恢复三个维度,带你分析保障Redis可靠性的最佳实践
3.1、按照业务线部署实例
- 提高可靠性的首要步骤就是资源隔离,即按照不同的业务线部署Redis实例,当其中的某个实例发生故障时,不会影响到其他实例,其实施成本,但收效巨大
3.2、部署主从集群
- 如果只是使用单机Redis,就会存在机器宕机服务不可用的风险;而如果部署了【多副本】实例(主从集群),当主库宕机后,依旧有从库可以使用,避免了数据丢失的风险,也降低了服务不可用的时间
- 在部署主从集群时,主从库需要分布在不同机器上,避免交叉部署,原因在于,Redis的主库往往会承担所有的读写流量,开发者一定要优先保证主库的稳定性,即便从库机器异常,也要保证不会对主库产生影响
- 当我们对Redis进行日常维护时,如数据定时备份等操作,可以只在从库上进行,只会消耗从库机器的资源,可避免对主库的影响
3.3、合理配置主从复制参数
在部署主从集群时,如果参数配置不合理,有可能发生的主从复制问题以及建议如下:
- 1)主从复制中断
- 2)从库发起全量复制,主库性能受到影响
两点建议如下: - 1)设置合理的repl-backlog参数,过小的repl-backlog在写流量比较大的场景下,主从复制中断会引发全量复制数据的风险
- 2)设置合理的slave client-output-buffer-limit,当从库复制发生问题时,过小的buffer会让从库缓冲区溢出, 从而导致复制中断
3.4、部署哨兵集群,实现故障自动切换
- 只部署了主从节点,在故障发生时是无法自动切换的,所以,还需要部署哨兵集群,实现故障的自动切换
- 多个哨兵节点需要分布在不同机器上,实例为奇数个,防止哨兵选举失败,影响切换时间
4、Redis日常运维总结
在平时运维Redis时,需要注意如下几点:
4.1、禁用KEYS/ FLUSHALL/ FLUSHDB命令
- 执行KEYS/ FLUSHALL/ FLUSHDB命令,会长时间阻塞Redis主线程,应禁用或
- SCAN代替KEYS
- 4.0+版本使用FLUSHALL/ FLUSHDB ASYNC,清空数据的操作交由后台线程执行
4.2、扫描线上实例时,设置休眠时间
- 使用SCAN扫描线上实例,和对实例的bigkey做统计分析,建议在扫描时要设置休眠时间,防止在扫描过程中,实例OPS过高对Redis性能产生抖动(OPS:operation per second ,每秒操作数,即每秒对Redis的持久化操作)
4.3、慎用MONITOR命令
- 在排查Redis问题时,有时会使用MONITOR查看Redis正在执行的命令,但如果Redis OPS较高,在执行MONITOR会导致Redis输出缓冲区的内存持续增长,会严重消耗Redis的内存资源,导致实例内存超过maxmemory,引发数据淘汰,所以,慎用MONITOR命令
4.4、从库必须设置为slave-read-only
- 必须要避免从库写入数据,从而导致数据不一致的问题,即从库必须设置为slave-read-only状态(只读状态)
- 如果 从库不是read-only状态,且使用4.0以下版本的Redis,就会出现问题:从库写入了有过期时间的数据,不会做定时清理和释放内存 ==》这会造成从库的内存泄露
4.5、合理配置timeout和tcp-keepalive参数
- 如果由于网络原因,导致大量客户端连接与Redis意外中断,而Redis配置的maxclients比较小,则有可能导致客户端无法与服务端建立新的连接(服务端会认为超过maxclients),其原因在于客户端与服务端每建立一个连接,Redis都会给客户端分配一个client fd,当客户端和服务端网络发生问题时,服务端并不会立即释放该client fd
- Redis内部有定时任务,会定时检测所有client的空闲时间是否超过配置的timeout值,如果Redis没有开启tcp-keepalive,服务端会等到配置的timeout后,才会清理释放该client fd,而在没有清理前,如果还有大量新连接进来,就可能导致Redis服务端内部持有的client fd超过了maxclients,这时新连接就会被拒绝
- 优化建议如下:
1)不要配置过高的timeout:让服务端尽快将无效的client fd清理掉
2)Redis开启tcp-keepalive:服务端会定时给客户端发送TCP心跳包,检测连接的连通性,当网络异常时,可以尽快清理掉僵尸client fd
4.6、调整maxmemory时,注意主从库的调整顺序
- Redis5.0以下版本存在如下问题:从库内存如果超过了maxmemory,也会触发数据淘汰
- 在某些场景下,从库是可能优先主库达到maxmemory的(如在从库执行MONITOR命令,输出缓冲区占用大量内存),则此时从库开始淘汰数据,主从库就会产生不一致
- 在调整maxmemory时,一定要注意主从库的修改顺序:
1)调大maxmemory:先修改从库,再修改主库
2)调小maxmemory:先修改主库,再修改从库
直到Redis5.0,Redis新增了参数配置replica-ignore-maxmemory,默认从库超过maxmemory不会淘汰
5、Redis安全如何保证
- 针对Redis可能存在的安全问题,给出的建议如下:
1)部署Redis时不使用默认端口6379
2)以普通用户启动Redis进程,禁止root用户启动
3)限制Redis配置文件的目录的访问权限
4)推荐开启密码认证
5)禁用高危命令(KEYS/FLUSHALL/ CONFIG / EVAL)
如上,基本上即可保证Redis的安全风险在可控范围内
6、Redis问题预防
- 预防Redis问题,需要做好合理的资源规划和完善的监控预警两个方面
6.1、资源规划
在部署Redis时,如果可以提前做好资源规划,可以避免很多因为资源不足产生的问题,建议如下:
- 保证机器有足够的CPU、内存、带宽、磁盘资源
- 提前做好容量规划,主库机器预留一半的内存资源,防止主从机器产生网络故障,引发大面积全量同步,导致主库机器内存不足
- 单个实例内存建议控制在10G以下,大实例在主从全量同步、RDB备份时有阻塞风险
6.2、监控预警
- 监控预警是提高稳定性的重要环节,完善的监控预警,可以提前暴露问题,便于我们快速反应,最小化问题,建议如下:
- 做好机器CPU、内存、带宽、磁盘监控,资源不足时及时报警,任意资源不足都会影响Redis性能
- 设置合理的slowlog阈值,并对其进行监控,slowlog过多及时报警
- 监控组件采集Redis INFO信息时,采用长连接,避免频繁的短连接
- 做好实例运行时监控,重点关注expired_keys、evicted_keys、latest_fork_usec指标,这些指标短时间内突增可能会有阻塞风险
7、总结