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

  • Post author:
  • Post category:其他




一、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秒

在这里插入图片描述



版权声明:本文为weixin_43989347原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。