Redis从入门到精通(二十一)Redis最佳实践(二)mset、pipeline、慢查询优化、内存划分

前言

Redis最佳实现系列文章:

Redis从入门到精通(二十)Redis最佳实践(一)优雅的Key结构、拒绝BigKey

7.2 批处理优化

7.2.1 命令执行流程

客户端与Redis服务器交互时,单个命令的执行流程如下:

N条命令的执行流程:

Redis处理指令是很快的,主要花费的时候在于网络传输,于是很容易就想到可以将多条指令批量的传输给Redis:

7.2.2 mset

Redis提供了msethmset这样的命令,可以实现批量插入数据。 例如利用mset一次性批量插入1000条数据:

@Test
public void testMset() {
    long b = System.currentTimeMillis();
    String[] arr = new String[2000];
    for (int i = 0; i < 2000; i++) {
        arr[i] = "test:mset:key_" + i;
        arr[i+1] = "value_" + i;
        i++;
    }
    jedis.mset(arr);
    long e = System.currentTimeMillis();
    System.out.println("time: " + (e - b));
}

控制台打印执行时间:

time: 7

此时Redis中的数据:

7.2.3 Pipeline

mset虽然可以批处理,但是却只能操作部分数据类型,因此如果有对复杂数据类型的批处理需要,建议使用Pipeline,例如:

@Test
public void testPipeline() {
    long b = System.currentTimeMillis();
    // 创建管道
    Pipeline pipeline = jedis.pipelined();
    for (int i = 1; i <= 1000; i++) {
        // 放入命令到管道
        pipeline.set("test:pipeline:key_" + i, "value_" + i);
    }
    pipeline.sync();
    long e = System.currentTimeMillis();
    System.out.println("time: " + (e - b));
}

控制台打印执行时间:

time: 119

此时Redis中的数据:

7.2.4 集群下的批处理

7.2.4.1 问题与解决方案

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

这样的要求其实很难实现,因为在批处理时可能一次要插入很多条数据,这些数据很有可能落在不相同的节点上,这就会导致报错了。

解决这个问题,有4种方案:

7.2.4.2 基于Spring的串行化执行
@Test
public void testMset() {
    // MSET写数据
    Map<String, String> map = new HashMap<>(3);
    map.put("name", "Rose");
    map.put("age", "21");
    map.put("sex", "Female");
    stringRedisTemplate.opsForValue().multiSet(map);
    // MGET取数据
    List<String> strings = stringRedisTemplate.opsForValue().multiGet(Arrays.asList("name", "age", "sex"));
    strings.forEach(System.out::println);
}

运行单元测试结果如下:

Rose
21
Female

此时Redis种的数据:

7.3 服务器端优化

7.3.1 持久化配置

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

  • 用来做缓存的Redis实例尽量不要开启持久化功能

  • 建议关闭RDB持久化功能,使用AOF持久化

  • 利用脚本定期在slave节点做RDB,实现数据备份

  • 设置合理的rewrite阈值,避免频繁的bgrewrite

  • 配置no-appendfsync-on-rewrite = yes禁止在rewrite期间做AOF,避免因AOF引起的阻塞

  • 部署有关建议:

    • Redis实例的物理机要预留足够内存,应对fork和rewrite
    • 单个Redis实例内存上限不要太大,例如4G或8G
    • 不要与CPU密集型应用部署在一起
    • 不要与高硬盘负载应用一起部署。例如:数据库、消息队列。

7.3.2 慢查询优化

7.3.2.1 什么是慢查询

并不是说很慢的查询才是慢查询,而是在Redis执行时,耗时超过某个阈值的命令,称为慢查询。

慢查询的危害:由于Redis是单线程的,所以当客户端发出指令后,它们都会进入到Rdis底层的queue来执行,如果此时有一些慢查询的数据,就会导致大量请求阻塞,从而引起报错。

  • 慢查询的阈值可以通过 slowlog-log-slower-than 配置指定,单位是微秒。默认是10000,建议1000。
127.0.0.1:6379> CONFIG GET slowlog-log-slower-than
1) "slowlog-log-slower-than"
2) "10000"
  • 慢查询会被放入慢查询日志中,日志的长度有上限,可以通过 slowlog-max-len 配置指定,本质是一个队列。默认是128,建议1000。
127.0.0.1:6379> CONFIG GET slowlog-max-len
1) "slowlog-max-len"
2) "128"
  • 要修改这两个配置可以使用 config set 命令:
127.0.0.1:6379> CONFIG set slowlog-log-slower-than 1000
OK
127.0.0.1:6379> CONFIG set slowlog-max-len 1000
OK
7.3.2.2 如何查看慢查询

查看慢查询日志列表可以使用以下名:

  • slowlog len:查询慢查询日志长度
  • slowlog get [n]:读取n条慢查询日志
  • slowlog reset:清空慢查询列表

7.3.3 命令及安全配置

Redis默认情况下,会绑定在0.0.0.0:6379,这样将会使Redis服务暴露到公网上,如果Redis没有开启身份认证,则可以导致任意用户在可以访问目标服务器的情况下未授权访问Redis以及读取Redis的数据

攻击者在未授权访问Redis的情况下,还可以利用Redis的相关方法,在Redis服务器上写入公钥,进而可以使用对应私钥直接登录目标服务器。

更详细的漏洞描述和重现方式见:https://cloud.tencent.com/developer/article/1039000

漏洞出现的核心的原因有以下几点:

  • Redis未设置密码
  • 利用了Redis的config set命令动态修改Redis配置
  • 使用了root账号权限启动Redis

为了避免这样的漏洞,可以参照以下建议:

  • Redis一定要设置密码
  • 禁止使用下面命令:keys、flushall、flushdb、config set等命令,可以利用rename-command禁用。
  • bind:限制网卡,禁止外网网卡访问
  • 开启防火墙
  • 不要使用root账户启动Redis
  • 尽量不使用默认的端口

7.3.4 内存划分和内存配置

当Redis内存不足时,可能导致Key频繁被删除、响应时间变长、QPS不稳定等问题。Redis的内存占用一般包括三个方面:

  • 1)数据内存:Redis最主要的部分,存储Redis的键值信息,主要问题是BigKey问题、内存碎片问题。

    • 碎片问题:Redis底层分配有自己的分配策略。例如当前Key只需要10个字节,此时分配8字节肯定不够,那么底层就会分配16个字节,多出来的6个字节就不能被使用,也就时产生了碎片。
  • 2)进程内存:Redis主进程本身运⾏需要占⽤的内存,如代码、常量池等等;这部分内存⼤约⼏兆,在⼤多数⽣产环境中与Redis数据占⽤的内存相⽐可以忽略。

  • 3)缓冲区内存:一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用BigKey,可能导致内存溢出。

Redis提供了一些命令,用于查询Redis目前的内存分配状态:

  • info memory:查看内存分配状态

  • memory usage key:查看某个key的内存
127.0.0.1:6379> memory usage test:pipeline:key_195
(integer) 80

内存缓冲区常见的有三种:

  • 复制缓冲区:主从复制的repl_backlog_buf,如果太小可能导致频繁的全量复制,影响性能。可以通过repl-backlog-size配置来设置,默认1mb。
  • AOF缓冲区:AOF执行rewrite之前的缓冲区。无法设置容量上限。
  • 客户端缓冲区:分为输入缓冲区和输出缓冲区,输入缓冲区最大1G且不能设置,输出缓冲区可以设置。

一般复制缓冲区和AOF缓冲区不会有问题,可能有问题的是客户端缓冲区。

客户端缓冲区指的就是客户端向Redis发送命令时,用来缓存命令的一个输入端缓冲区,以及Redis向客户端返回数据的输出缓存区。输入缓冲区最大1G且不能设置,一般不会有问题,如果超过了这个空间,Redis会直接断开,因为此时此刻就代表着Redis处理不过来了。

因此输入端缓冲区并不需要担心,要担心的时输出端缓冲区。 客户端缓冲区的配置如下:

如上图所示,client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>配置的几个参数含义如下:

  • <class>:客户端类型,包括normal(普通客户端)、replica(主从复制客户端)、pubsub(PubSub客户端)
  • <hard limit>:缓冲区上限,超过hard limit后断开客户端
  • <soft limit> <soft seconds>:持续时间限制,当内存大小达到soft limit,并持续soft seconds秒后,断开客户端

7.3.5 集群or主从?

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

7.3.5.1 集群完整性问题

在Redis的默认配置中,如果发现任意一个插槽不可用,则整个集群都会停止对外服务。

在实际开发中,最重要的是可用性,即使有slot不能使用,Redis集群也应该可以对外提供服务。为此,需要把如下配置修改成no:

# 默认为yes
cluster-require-full-coverage no
7.3.5.2 集群带宽问题

集群节点之间会不断的互相Ping来确定集群中其它节点的状态。每次Ping携带的信息至少包括:插槽信息、集群状态信息等。

集群中节点越多,集群状态信息数据量也越大,10个节点的相关信息可能达到1kb,此时每次集群互通需要的带宽会非常高,这样会导致集群中大量的带宽都会被Ping信息所占用。

解决方案主要有:

  • 避免大集群,集群节点数不要太多,最好少于1000,如果业务庞大,则建立多个集群。
  • 避免在单个物理机中运行太多Redis实例。
  • 配置合适的cluster-node-timeout值。
7.3.5.3 其他问题
  • 数据倾斜问题
  • 客户端性能问题
  • 命令的集群兼容性问题
  • lua和事务问题
7.3.5.4 结论

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

本节完。本节所涉及的代码和资源可从git仓库下载:https://gitee.com/weidag/redis_learning.git

更多内容请查阅分类专栏:Redis从入门到精通

感兴趣的读者还可以查阅我的另外几个专栏:

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-21 18:00:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-21 18:00:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-21 18:00:03       87 阅读
  4. Python语言-面向对象

    2024-04-21 18:00:03       96 阅读

热门阅读

  1. Linux 日志常用命令

    2024-04-21 18:00:03       161 阅读
  2. 微信小程序实现腾讯地图

    2024-04-21 18:00:03       36 阅读
  3. 物联网智能互联创新开发平台

    2024-04-21 18:00:03       40 阅读
  4. uniapp中使用axios在真机运行报错(一)

    2024-04-21 18:00:03       28 阅读
  5. 微服务OR单体架构

    2024-04-21 18:00:03       30 阅读
  6. 微服务面试题

    2024-04-21 18:00:03       37 阅读
  7. 深入解析NPM:常用命令详解与实战示例

    2024-04-21 18:00:03       33 阅读