Redis的key虽然可以自定义,但是最好遵循几个最佳实战约定:
login:user:10
优点:
key是String类型,底层编码包括:int、embstr、raw三种:
# 测试value,是纯数字。底层编码是int
127.0.0.1:6379> set name 123
OK
127.0.0.1:6379> object encoding name
"int"
# 测试44个字节,底层编码是embstr
127.0.0.1:6379> set name 12345678912345678912345678912345678912345678
OK
127.0.0.1:6379> object encoding name
"embstr"
# 测试45个字节,底层编码是raw
127.0.0.1:6379> set name 123456789123456789123456789123456789123456789
OK
127.0.0.1:6379> object encoding name
"raw"
虽然一个key,最大能存放512,但是5MB的就是很大的key了
BigKey通常以key的大小和key中成员变量来综合判定,例如:
推荐值:
例如一个key的大小5MB,并发20次请求,那么就是100MB的带宽。如果服务器只有100MB的带宽,那么就是全部占用了。
数据倾斜
BigKey所在的Redis实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡
Redis阻塞
对元素较多的hash、list、zset等做运算会比较耗时,从而主线程阻塞
CPU压力
对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其他应用
redis-cli bigkeys
利用Redis-cli提供的–bigkeys参数,可以遍历分析所有key,并返回Key的整体统计信息与每个数据的TOP1的bigkey。
缺点:
只能看到第一名,有可能第一名并不是bigkey。也有可能第一第二第三都是bigkey。
scan扫描
自己编程,利用scan扫描Redis中的所有key,利用strlen和hlen等命令判断key的长度。(不建议使用memory usage,因为是主线程操作)
1.并不是主线程去操作
2.并且用的是迭代器逐步扫描
3.每次扫描一小部分
第三方工具
利用第三方工具,如Redis-Rdb-Tools分析RDB快照文件,全面分析内存使用情况
网络监控
自定义工具,监控进出Redis的网络数据,超出预警值时主动告警
BigKey由于内存占用较多,即使删除这样的key也需要耗时很长时间,导致Redis主线程阻塞,引发一系列问题。
Redis3.0及以下版本
如果是集合类型,则用(扫描的方法)遍历Bigkey的元素,先逐个删除子元素,最后删除BigKey
Redis 4.0以后
Redis在4.0后提供了异步删除的命令:unlik
string类型存储了一个123,会占用48个字节。有很多源信息。
可以使用Hash类型。
方法一:导致BigKey问题
可以通过hash-max-ziplist-entries配置entry上限。但是如果entry过多就会导致BigKey问题。`
方法二:
拆分为小的hash,将id/100作为key,将id%100作为field,这样每100个元素为一个hash
例如:有100万条数据,不拆分是1个哈希,拆分成1万个哈希。将100万数据放到1万个哈希里面,每个哈希只有100个数据。
内存占用:
单个命令执行的流程:
N次命令的响应时间=N次往返的网络传输耗时+N次Redis执行命令耗时。
N次命令的响应时间=1次往返的网络传输耗时+N次Redis执行命令耗时。
不要在一次批处理中传输太多命令,否则单次命令占用带宽过多,会导致网络阻塞。
MSET虽然可以批处理,但是却只能操作部分数据类型(String和Hash类型),因此如果有对复杂数据类型的批处理需要,建议使用Pipeline功能:
优点:数据类型不收限制
缺点:速度略微低于M操作,并不是原子操作。
@Resource
private Jedis jedis;
@Test
void testPipeline() {
// 创建管道
Pipeline pipelined = jedis.pipelined();
// 获取当前时间
long l = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
//放入命令到管道
pipelined.set("test_key:"+i,"value_"+i);
if (i % 1000 == 0 ){
//每放入一千条命令,批量执行一次
pipelined.sync();
}
long l1 = System.currentTimeMillis();
System.out.println("time====>"+(l1-l));
}
}
如MSET或Pipeline这样的批处理需要再一次请求中携带多条命令,而此时如果Redis是一个集群,那批处理命令的多个key必须落在一个插槽中,否则会导致执行失败。
mset name jack age12 sex male
(error)CROSSLOT Keys in request don't hash to the same solt
# 翻译:在这次请求中是:跨域多次槽的key,没有办法去做hash。没有办法到一个槽内。
计算每个key的插槽,将存在相同插槽的key,存放在同一组中。从而分出多个组。 在通过多线程的方式同时执行各组的命令。
@Test
void testMSetInCluster(){
HashMap<String, String> map = new HashMap<>(3);
map.put("name","zhangsan");
map.put("age","21");
map.put("sex","male");
stringRedisTemplate.opsForValue().multiSet(map);
}
Redis的持久化虽然可以保证数据安全,但也会带来很多额外的开销,因此持久化请遵循下列建议:
超过上一次rewrite的大小的百分百。并且超过64兆
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
慢查询:在redis执行时耗时超过某个阈值的命令。称为慢查询
慢查询的阈值可以通过配置指定:
slowlog-log-slower-than 10000
slowlog-max-len 128
集群虽然具备高可用特性,能实现自动故障恢复,但是如果使用不当,也会存在一些问题。
单体的Redis(主从Redis)已经达到万级别的QPS,并且也具备很强的高可用特性。如果主从能满足业务需求的情况下,尽量不搭建Redis集群。
集群要不要全部覆盖。也就说插槽数量一个都不能少。当有插槽不能使用时,整个redis集群都不可用。默认是开启的。
cluster-require-full-coverage yes
为了保证高可用性,建议将此配置改为 false
集群节点之间会不断的互相ping来确定集群中其他节点的状态。每次ping携带的信息至少包括:
集群中节点越多,集群状态信息数据量也越大,10各节点的相关信息可能达到1kb,此时每次集群互通需要的带宽会非常高。
解决途径: