Redis的最佳实践?看完不心动,算我输!!

一、Redis键值设计

1、优雅的key结构

Redis的key虽然可以自定义,但是最好遵循几个最佳实战约定:

  • 遵循基本格式:[业务名称]:[数据名]:[id] login:user:10
  • 不包含特殊字符
  • value长度不超过44字节 (4版本之前是39)

优点:

  1. 阅读性强
  2. 避免key冲突
  3. 方便管理。比如:删除该业务下所有的key
  4. 更节省内存

1.为什么value长度不尽量不超过44字节

key是String类型,底层编码包括:int、embstr、raw三种

  • int:全部是数值的情况下,采用int编码。将字符串当成数值存储。
  • embstr:小于44字节使用,是连续的内存空间,内存占用更小
  • raw:大于44字节使用,是通过指针,指向不同的内存空间。 由于内存不连续,所以访问的时候性能收到影响。
# 测试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"

2、拒绝BigKey

1.什么是BigKey

虽然一个key,最大能存放512,但是5MB的就是很大的key了

BigKey通常以key的大小和key中成员变量来综合判定,例如:

  • key本身的数据量过大:一个String类型的key,它的值是5MB
  • key中的成员变量过多:一个ZSET类型的key,他的成员变量10,000个
  • key中的成员数据量过大:一个Hash类型的key,它的成员变量虽然只有1000个,但是这些成员变量的value总大小为100MB

推荐值:

  • 对于String类型的单个key,建议value小于10KB
  • 对于集合类型的key,建议元素数量小于1000

2.BigKey的危害

  • 网络阻塞
    对BigKey执行读请求时,少量的QPS就可能导致带宽使用率被占满,导致redis实例,乃至所在物理机变慢

例如一个key的大小5MB,并发20次请求,那么就是100MB的带宽。如果服务器只有100MB的带宽,那么就是全部占用了。

  • 数据倾斜
    BigKey所在的Redis实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡

  • Redis阻塞
    对元素较多的hash、list、zset等做运算会比较耗时,从而主线程阻塞

  • CPU压力
    对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其他应用

3.如何发现BigKey

  • 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的网络数据,超出预警值时主动告警

4.如何删除BigKey

BigKey由于内存占用较多,即使删除这样的key也需要耗时很长时间,导致Redis主线程阻塞,引发一系列问题。

  • Redis3.0及以下版本
    如果是集合类型,则用(扫描的方法)遍历Bigkey的元素,先逐个删除子元素,最后删除BigKey

  • Redis 4.0以后
    Redis在4.0后提供了异步删除的命令:unlik

3、恰当的数据类型

string类型存储了一个123,会占用48个字节。有很多源信息。

1.存储User对象

可以使用Hash类型。
在这里插入图片描述

  • hash的entry数量小于500时,底层使用ziplist,空间占用小,可以灵活访问对象的任意字段。
  • hash的entry数量超过500时,会使用哈希表,内存占用比较多。一百万数据,占用内存大小是62.23M
    在这里插入图片描述

方法一:导致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执行命令耗时。

  • 测试案例一:每次执行一次set命令,十万条数据,大概需要44秒。

1、N条命令批处理执行

N次命令的响应时间=1次往返的网络传输耗时+N次Redis执行命令耗时。

  • 测试案例二:批处理MSET命令,十万条数据,大概需要182毫秒
    优点:速度最快,具有原子性
    缺点:数据类型受限制

不要在一次批处理中传输太多命令,否则单次命令占用带宽过多,会导致网络阻塞。

2、Pipeline

MSET虽然可以批处理,但是却只能操作部分数据类型(String和Hash类型),因此如果有对复杂数据类型的批处理需要,建议使用Pipeline功能:
优点:数据类型不收限制
缺点:速度略微低于M操作,并不是原子操作。

  • 测试案例三:批处理Pipeline,十万条数据,大概需要250毫秒
@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));
        }
    }

3、集群下的批处理

如MSET或Pipeline这样的批处理需要再一次请求中携带多条命令,而此时如果Redis是一个集群,那批处理命令的多个key必须落在一个插槽中,否则会导致执行失败。

mset name jack age12 sex male
(error)CROSSLOT Keys in request don't hash to the same solt
# 翻译:在这次请求中是:跨域多次槽的key,没有办法去做hash。没有办法到一个槽内。

四种处理方式:

在这里插入图片描述

并行solt(推荐使用)

计算每个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);
    }

三、服务端优化

1、持久化配置

Redis的持久化虽然可以保证数据安全,但也会带来很多额外的开销,因此持久化请遵循下列建议:

  • 用来做缓存的Redis实例尽量不要开启持久化功能。
  • 建议关闭RDB持久化功能,使用AOF持久化。
  • 利用脚本定期在slave从节点做RDB,实现数据备份。
  • 设置合理的rewrite阈值,避免频繁的bgrewrite
超过上一次rewrite的大小的百分百。并且超过64兆
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

  • 配置no-appendfsync-on-rewrite=yes,进制在rewrit期间做aof,避免因aof引起的阻塞

部署有关建议:

  • Redis实例的物理机要预留足够内存,应对fork和rewrite
  • 单个Redis实例内存上限不要太大,例如4G或者8G。可以加快fork速度、减少主从同步、数据迁移的压力。
  • Redis实例不要和CPU密集型应用部署在一起。例如ES
  • 不要与高硬盘负载应用一起部署。例如数据库、消息队列。

2、慢查询

慢查询:在redis执行时耗时超过某个阈值的命令。称为慢查询

慢查询的阈值可以通过配置指定:

  1. 默认阈值是10毫秒,redis执行命令是微妙级别,建议是1毫秒。
    slowlog-log-slower-than 10000
  2. 慢查询会放入慢查询日志中,日志的长度有上限,可以通过配置指定:建议长度是1000
    slowlog-max-len 128

3、命令及安全配置

  1. Redis一定要设置密码
  2. 进制线上使用下面的命令:keys、flushall、flushdb、config set 等命令。可以利用rename-command禁用或者另起名字。
  3. bind:限制网卡,进制外网网卡访问
  4. 开启防火墙
  5. 不要使用Root账户启用Redis
  6. 尽量不是有默认的端口

四、集群最佳实践

集群虽然具备高可用特性,能实现自动故障恢复,但是如果使用不当,也会存在一些问题。

  1. 集群完整性问题
  2. 集群带宽问题
  3. 数据倾斜问题。hash_tag
  4. 客户端性能问题
  5. 命令的集群兼容性问题
  6. 集群下不支持lua和事务问题。

所以要不要使用集群?

单体的Redis(主从Redis)已经达到万级别的QPS,并且也具备很强的高可用特性。如果主从能满足业务需求的情况下,尽量不搭建Redis集群。

1、集群完整性问题

集群要不要全部覆盖。也就说插槽数量一个都不能少。当有插槽不能使用时,整个redis集群都不可用。默认是开启的。
cluster-require-full-coverage yes
为了保证高可用性,建议将此配置改为 false

2、集群带宽问题

集群节点之间会不断的互相ping来确定集群中其他节点的状态。每次ping携带的信息至少包括:

  • 插槽信息
  • 集群状态信息

集群中节点越多,集群状态信息数据量也越大,10各节点的相关信息可能达到1kb,此时每次集群互通需要的带宽会非常高。

解决途径:

  1. 避免大集群,集群节点数不要太多,最好少于1000,如果业务庞大,则建立多个集群
  2. 避免在单个物理机中运行多个redis实例
  3. 配置合适的cluster-nodetimeout值。 集群节点客观下线的超时时间。15秒

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值