在Redis中,良好的键值设计可以达成事半功倍的效果,而不好的键值设计可能会带来Redis服务停滞,网络阻塞,CPU使用率飙升等一系列问题,今天就教大家如何设计一个良好的key-value
1 优雅的key结构Redis的Key虽然可以自定义,但最好遵循下面的几个最佳实践约定:
遵循基本格式:[业务名称]:[数据名]:[id],例如我们的登录业务,需要保存用户信息,其key可以设计成如下格式这种设计的好处不仅在于可读性强,还在于可以避免key的冲突问题,而且方便管理
Key的长度不超过44字节
无论是哪种数据类型, key都是string类型,string类型的底层编码包含int、embstr和raw三种。如果key中全是数字,那么就会直接以int类型去存储,而int占用的空间也是最小的,当然出于业务需求,我们不可能将key设计为一个全数字的,而如果不是纯数字,底层存储的就是SDS内容,如果小于44字节,就会使用embstr类型,embstr在内存中是一段连续的存储空间,内存占用相对raw来说较小,而当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片
需要注意的是,如果你的redis版本低于4.0,那么界限是39字节而非44字节
Key中不包含一些特殊字符
2 拒绝BigKey 2.1 判断BigKeyBigKey顾名思义就是一个很大的Key,这里的大并不是指Key本身很大,而是指包括这个Key的Value在内的一整个键值对很大
BigKey通常以Key-Value的大小或者Key中成员的数量来综合判定,例如:
Key的Value过大:例如一个String类型的Key,它的Value为5MB Key中的成员数过多:例如一个ZSET类型的Key,它的成员数量为10000个 Key中成员的Value过大:例如一个Hash类型的Key,它的成员数量虽然只有1000个,但这些成员的Value总大小为100 MB那么如何判断元素的大小呢?redis中为我们提供了相应的命令,语法如下:
memory usage 键名这条命令会返回一条数据占用内存的总大小,这个大小不仅包括Key和Value的大小,还包括数据存储时的一些元信息,因此可能你的Key与Value只占用了几十个字节,但最终的返回结果是几百个字节
但是我们一般不推荐使用memory指令,因为这个指令对CPU的占用率是很高的,实际开发中我们一般只需要衡量Value的大小或者Key中的成员数即可
例如如果我们使用的数据类型是String,就可以使用以下命令,返回的结果是Value的长度
strlen 键名如果我们使用的数据类型是List,就可以使用以下命令,返回的结果是List中成员的个数
llen 键名一般我们推荐,单个key的value小于10KB,集合类型的key元素数量小于1000
2.2 BigKey的危害网络阻塞
当我们对一个BigKey发起读请求时,只需少量的QPS就可能导致带宽使用率被占满,导致Redis实例乃至所在物理机变慢,例如一个bigkey占用5M内存,只需要QPS达到20,那么1秒钟就会占100M的带宽
数据倾斜
集群环境下,由于所有插槽一开始都是均衡分配的,因此BigKey所在的Redis实例内存使用率会远超其他实例,从而无法使数据分片的内存资源达到均衡,最后不得不手动重新分配插槽,增加运维人员的负担
Redis阻塞
对元素较多的hash、list、zset等做运算会耗时较久,而且由于Redis是单线程的,在运算过程中会导致服务阻塞,无法接收其他用户请求
CPU压力
对BigKey的数据进行序列化或反序列化都会导致CPU的使用率飙升,影响Redis实例和本机其它应用
2.3 如何发现BigKey既然我们知道了什么叫BigKey以及BigKey的危害,那么如何去快速发现Redis中所有的BigKey呢?这里为大家提供以下几种方案:
1)利用Redis本身提供的命令
利用以下命令,可以遍历分析所有key,并返回Key的整体统计信息与每种数据类型中Top1的BigKey
redis-cli -a 密码 --bigkeys演示如下(这里我的redis没有设置密码,如果你的redis设置了密码,则需要使用 -a 密码 进行连接)
2)自己手动编写程序进行扫描
我们可以通过自己编写程序,将Redis中所有的数据查询出来并一一统计长度来找出BigKey,这里不建议使用keys *来查询所有数据,因为keys * 是一次将所有的数据全部查找出来,如果数据量很大,key *一次可能要几十秒甚至几分钟,在如此长的时间内,Redis的主线程会因为执行该命令而被阻塞。
这里建议使用redis提供的scan命令,语法如下:
scan 起始位置 count 数量scan扫描有点类似于分页查询,而被分页的对象是redis中所有的数据,scan命令调用一次只会从指定的起始位置开始返回指定数量的数据,以及此次扫描结束时光标所在的位置,下一次扫描时就需要从这个光标开始继续往下扫描
这里提供一个已经编写好的查找BigKey的测试类,大家可以参考一下
import com.heima.jedis.util.JedisConnectionFactory;import org.junit.jupit