• 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"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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));
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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。没有办法到一个槽内。
    
    • 1
    • 2
    • 3

    四种处理方式:

    在这里插入图片描述

    并行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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、服务端优化

    1、持久化配置

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

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

    在这里插入图片描述

  • 相关阅读:
    JavaSSM笔记(二)SpringMvc基础
    【实用工具】Centos 安装ARL灯塔
    记一次难忘的大厂面试经历
    数据库小记
    LeetCode二叉树系列——235.二叉搜索树的最近公共祖先
    24点游戏题库算法分析
    Kubernetes 1.25 集群搭建
    CAD重复圆绘制机械图形
    直播带货平台有哪些
    vue项目类微信聊天页面,输入法弹出,ios的标题会整体上移问题
  • 原文地址:https://blog.csdn.net/weixin_43989347/article/details/125429229