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

文章目录

    • 前言
    • [7.2 批处理优化](#7.2 批处理优化)
      • [7.2.1 命令执行流程](#7.2.1 命令执行流程)
      • [7.2.2 mset](#7.2.2 mset)
      • [7.2.3 Pipeline](#7.2.3 Pipeline)
      • [7.2.4 集群下的批处理](#7.2.4 集群下的批处理)
        • [7.2.4.1 问题与解决方案](#7.2.4.1 问题与解决方案)
        • [7.2.4.2 基于Spring的串行化执行](#7.2.4.2 基于Spring的串行化执行)
    • [7.3 服务器端优化](#7.3 服务器端优化)
      • [7.3.1 持久化配置](#7.3.1 持久化配置)
      • [7.3.2 慢查询优化](#7.3.2 慢查询优化)
        • [7.3.2.1 什么是慢查询](#7.3.2.1 什么是慢查询)
        • [7.3.2.2 如何查看慢查询](#7.3.2.2 如何查看慢查询)
      • [7.3.3 命令及安全配置](#7.3.3 命令及安全配置)
      • [7.3.4 内存划分和内存配置](#7.3.4 内存划分和内存配置)
      • [7.3.5 集群or主从?](#7.3.5 集群or主从?)
        • [7.3.5.1 集群完整性问题](#7.3.5.1 集群完整性问题)
        • [7.3.5.2 集群带宽问题](#7.3.5.2 集群带宽问题)
        • [7.3.5.3 其他问题](#7.3.5.3 其他问题)
        • [7.3.5.4 结论](#7.3.5.4 结论)

前言

Redis最佳实现系列文章:

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

7.2 批处理优化

7.2.1 命令执行流程

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

N条命令的执行流程:

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

7.2.2 mset

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

java 复制代码
@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,例如:

java 复制代码
@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的串行化执行
java 复制代码
@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。
sh 复制代码
127.0.0.1:6379> CONFIG GET slowlog-log-slower-than
1) "slowlog-log-slower-than"
2) "10000"
  • 慢查询会被放入慢查询日志中,日志的长度有上限,可以通过 slowlog-max-len 配置指定,本质是一个队列。默认是128,建议1000。
sh 复制代码
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的内存
sh 复制代码
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:

sh 复制代码
# 默认为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从入门到精通

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

相关推荐
君不见,青丝成雪3 分钟前
Hadoop技术栈(四)HIVE常用函数汇总
大数据·数据库·数据仓库·hive·sql
码熔burning8 分钟前
JVM 面试精选 20 题(续)
jvm·面试·职场和发展
刘一说9 分钟前
CentOS 系统 Java 开发测试环境搭建手册
java·linux·运维·服务器·centos
Victor35614 分钟前
Redis(14)Redis的列表(List)类型有哪些常用命令?
后端
Victor35614 分钟前
Redis(15)Redis的集合(Set)类型有哪些常用命令?
后端
卷福同学15 分钟前
来上海三个月,我在马路边上遇到了阿里前同事...
java·后端
bingbingyihao2 小时前
多数据源 Demo
java·springboot
不羁。。5 小时前
【撸靶笔记】第七关:GET - Dump into outfile - String
数据库·笔记·oracle
yangchanghua1117 小时前
pgsql 如何查询今天范围内的数据(当天0点0分0秒 - 当天23点59分59秒....)
数据库·pgsql
larance7 小时前
SQLAlchemy 的异步操作来批量保存对象列表
数据库·python