Redis缓存设计与性能优化最佳实践

开发规范与性能优化
一、键值设计
1. key名设计
(1)【建议】: 可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id

(2)【建议】:简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:

(3)【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符
2. value设计
(1)【强制】:拒绝bigkey(防止网卡流量、慢查询)
在Redis中,一个字符串最大512MB,一个二级数据结构(例如hash、list、set、zset)可以存
储大约40亿个(2^32-1)个元素,但实际中如果下面两种情况,我就会认为它是bigkey。

  1. 字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey。
  2. 非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。
    一般来说,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
    反例:一个包含200万个元素的list。 非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞)
    bigkey的危害:
    1.导致redis阻塞
    2.网络拥塞
    bigkey也就意味着每次获取要产生的网络流量较大,假设一个bigkey为1MB,客户端每秒访问
    量为1000,那么每秒产生1000MB的流量,对于普通的千兆网卡(按照字节算是128MB/s)的服务
    器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey
    可能会对其他实例也造成影响,其后果不堪设想。
  3. 过期删除
    有个bigkey,它安分守己(只执行简单的命令,例如hget、lpop、zscore等),但它设置了过
    期时间,当它过期后,会被删除,如果没有使用Redis 4.0的过期异步删除( lazyfree-lazy- expire yes ),就会存在阻塞Redis的可能性。
    bigkey的产生:
    一般来说,bigkey的产生都是由于程序设计不当,或者对于数据规模预料不清楚造成的,来看几
    个例子:
    (1) 社交类:粉丝列表,如果某些明星或者大v不精心设计下,必是bigkey。
    (2) 统计类:例如按天存储某项功能或者网站的用户集合,除非没几个人用,否则必是bigkey。
    (3) 缓存类:将数据从数据库load出来序列化放到Redis里,这个方式非常常用,但有两个地方需
    要注意,第一,是不是有必要把所有字段都缓存;第二,有没有相关关联的数据,有的同学为了
    图方便把相关数据都存一个key下,产生bigkey。
    如何优化bigkey

  4. big list: list1、list2、...listN
    big hash:可以讲数据分段存储,比如一个大的key,假设存了1百万的用户数据,可以拆分成
    200个key,每个key下面存放5000个用户数据
  5. 如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要
    hmget,而不是hgetall),删除也是一样,尽量使用优雅的方式来处理。
    (2)【推荐】:选择适合的数据类型。
    例如:实体类型(要合理控制和使用数据结构,但也要注意节省内存和性能之间的平衡)
    反例:

    正例:

    3.【推荐】:控制key的生命周期,redis不是垃圾桶。
    建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期)。
    二、命令使用
    1.【推荐】 O(N)命令关注N的数量
    例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有
    遍历的需求可以使用hscan、sscan、zscan代替。
    2.【推荐】:禁用命令
    禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的
    方式渐进式处理。
    3.【推荐】合理使用select
    redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还
    是单线程处理,会有干扰。
    4.【推荐】使用批量操作提高效率

    但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
    注意两者不同:

    5.【建议】Redis事务功能较弱,不建议过多使用,可以用lua替代
    三、客户端使用
    1.【推荐】
    避免多个应用使用一个Redis实例
    正例:不相干的业务拆分,公共数据做服务化。
    2.【推荐】
    使用带有连接池的数据库,可以有效控制连接,同时提高效率,标准使用方式:


    连接池参数含义:

    优化建议:
    1) maxTotal :最大连接数,早期的版本叫maxActive
    实际上这个是一个很难回答的问题,考虑的因素比较多:
    业务希望Redis并发量
    客户端执行命令时间
    Redis资源:例如 nodes(例如应用个数) * maxTotal 是不能超过redis的最大连接数
    maxclients。
    资源开销:例如虽然希望控制 空闲连接 (连接池此刻可马上使用的连接),但是不希望因
    为连接池的频繁释放创建连接造成不必靠开销。
    以一个例子说明 ,假设: 一次命令时间(borrow|return resource + Jedis执行命令(含网络) )的平均耗时约为1ms,一个连接的QPS大约是1000业务期望的QPS是50000那么理论上需要的资源池大小是50000 / 1000 = 50个。但事实上这是个理论值,还要考虑到要比理论值预留一些资源,通常来讲maxTotal可以比理论值大一些。
    但这个值不是越大越好,一方面连接太多占用客户端和服务端资源,另一方面对于Redis这种高
    QPS的服务器,一个大命令的阻塞即使设置再大资源池仍然会无济于事。
    2) maxIdle和minIdle
    maxIdle实际上才是业务需要的最大连接数,maxTotal是为了 给出余量 ,所以maxIdle不要设置
    过小,否则会有new Jedis(新连接)开销。
    连接池的最佳性能是maxTotal = maxIdle ,这样就避免连接池伸缩带来的性能干扰。但是如果
    并发量不大或者maxTotal设置过高,会导致不必要的连接资源浪费。一般推荐maxIdle可以设置
    为按上面的业务期望QPS计算出来的理论连接数,maxTotal可以再放大一倍。
    minIdle(最小空闲连接数),与其说是最小空闲连接数,不如说是" 至少需要保持的空闲连接
    ",在使用连接的过程中,如果连接数超过了minIdle,那么继续建立连接,如果超过了
    maxIdle,当超过的连接执行完业务后会慢慢被移出连接池释放掉。
    如果系统启动完马上就会有很多的请求过来,那么可以给redis连接池做 预热 ,比如快速的创建一
    些redis连接,执行简单命令,类似ping(),快速的将连接池里的空闲连接提升到minIdle的数量。
    连接池预热 示例代码:


    总之,要根据实际系统的QPS和调用redis客户端的规模整体评估每个节点所使用的连接池大小。
    3.【建议】
    高并发下建议客户端添加熔断功能(例如sentinel、hystrix)
    4.【推荐】
    设置合理的密码,如有必要可以使用SSL加密访问
    5.【建议】
    Redis对于过期键有三种清除策略:
  6. 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期
    key
  7. 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一
    已过期 的key
  8. 当前已用内存超过maxmemory限定时,触发 主动清理策略
    主动清理策略 在Redis 4.0 之前一共实现了 6 种内存淘汰策略,在 4.0 之后,又增加了 2 种策
    略,总共8种:
    a) 针对设置了过期时间的key做处理:
  9. volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删
    除,越早过期的越先被删除。
  10. volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
  11. volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。
  12. volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。
    b) 针对所有的key做处理:
  13. allkeys-random:从所有键值对中随机选择并删除数据。
  14. allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。
  15. allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
    c) 不处理:
  16. noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error)
    OOM command not allowed when used memory",此时Redis只响应读操作。
    LRU 算法 Least Recently Used ,最近最少使用)
    淘汰很久没被访问过的数据,以 最近一次访问时间 作为参考。
    LFU 算法 Least Frequently Used ,最不经常使用)
    淘汰最近一段时间被访问次数最少的数据,以 次数 作为参考。 当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下
    降,缓存污染情况比较严重。这时使用LFU可能更好点。
    根据自身业务类型,配置好maxmemory-policy(默认是noeviction),推荐使用volatile-lru。如
    果不设置最大内存,当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交
    换 (swap),会让 Redis 的性能急剧下降。
    当Redis运行在主从模式时,只有主结点才会执行过期删除策略,然后把删除操作"del key"同
    步到从结点删除数据。
    四、系统内核参数优化
    vm.swapiness
    swap对于操作系统来说比较重要,当物理内存不足时,可以将一部分内存页进行swap到硬盘
    上,以解燃眉之急。但世界上没有免费午餐,swap空间由硬盘提供,对于需要高并发、高吞吐的
    应用来说,磁盘IO通常会成为系统瓶颈。在Linux中,并不是要等到所有物理内存都使用完才会
    使用到swap,系统参数swppiness会决定操作系统使用swap的倾向程度。swappiness的取值范
    围是0~100,swappiness的值越大,说明操作系统可能使用swap的概率越高,swappiness值越
    低,表示操作系统更加倾向于使用物理内存。swappiness的取值越大,说明操作系统可能使用
    swap的概率越高,越低则越倾向于使用物理内存。
    如果linux内核版本<3.5,那么swapiness设置为0,这样系统宁愿swap也不会oom killer(杀掉
    进程)如果linux内核版本>=3.5,那么swapiness设置为1,这样系统宁愿swap也不会oom killer
    一般需要保证redis不会被kill掉:

    PS:OOM killer 机制是指Linux操作系统发现可用内存不足时,强制杀死一些用户进程(非内核
    进程),来保证系统有足够的可用内存进行分配。
    vm.overcommit_memory(默认0)
    0:表示内核将检查是否有足够的可用物理内存(实际不一定用满)供应用进程使用;如果有足够的
    可用物理内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程
    1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何
    如果是0的话,可能导致类似fork等操作执行失败,申请不到足够的内存空间
    Redis建议把这个值设置为1,就是为了让fork操作能够在低内存下也执行成功。

    合理设置文件句柄数
    操作系统进程试图打开一个文件(或者叫句柄),但是现在进程打开的句柄数已经达到了上限,继
    续打开会报错:"
    Too many open files"

    慢查询日志:slowlog
相关推荐
qq_529835351 小时前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
月光水岸New3 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6753 小时前
数据库基础1
数据库
我爱松子鱼3 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo4 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser5 小时前
【SQL】多表查询案例
数据库·sql
Galeoto5 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
希忘auto5 小时前
详解Redis在Centos上的安装
redis·centos
人间打气筒(Ada)5 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231115 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql