image-20220831174219222

# 一、管道(Pipeline)

管道技术(Pipeline)本质上就是一种批处理技术。由于客户端与服务端通过socket进行连接,当短时间内大量命令产生的时候,如果采用普通的传输方式进行请求,命令间需要同步调用、请求的往返需要时间,并且客户端和服务端需要多次进行IO的的用户态到内核台的切换,会造成大量的网络资源浪费,而管道技术就是将所有命令都整合起来一次性发给服务端,再一次性响应给客户端,从而大大提升redis的响应速度。管道虽然提升了响应速度,但是仍然需要注意管道传输的数据大小,redis的输入缓冲区默认的最大是1G,超过会被断开连接。

image-20220809001446974

pipeline和原生批量命令(mget、mset)的区别

  • 原生命令具有原子性(跟setnx原子性命令类似),pipeline不具备原子性(中间命令出错仍然会继续执行)。
  • 原生命令是一个命令对应多个键值对,而pipeline是多条命令。
  • 原生命令是服务端实现,而pipeline是服务端和客户端共同完成。(pipeline需要在客户端缓冲区中组装多条指令)

pipeline和事务的区别

  • *Redis的事物 (opens new window)

  • 事物关注的是ACID,而pipeline关注的是RTT的减少

  • Redis的事物通过Multi、Exec命令控制,先Multi开启事物,此过程的命令会被存储在队列中,然后在通过exec提交事物

    • 原子性:不保证,执行过程有命令出错,仍然会继续执行,但是如果是在执行前检测到命令有误,则会回滚事物
    • 隔离性:保证,Redis的事物有CAS(check and set)机制,通过watch机制监控相关key数据是否被修改,修改则关闭事物
    • 持久性:不保证,没来得及被持久化的数据,仍然会丢失

pipeline的优缺点

  • 优点:批量操作,减少传输IO及系统切换开销
  • 缺点:
    • 不能保证原子性,部分指令可能执行失败,并且其他指令会继续执行。
    • 只能作用在一个节点上,暂时不支持集群。

适用场景

  • pipeline适用于指令间没有依赖关系的情况,并且对可靠性要求不高的场景。因为其不能保证所有命令都正常执行。
  • 例如用户群发短信,这时候就可以用pipeline,因为群发消息实时性要求不高,并且即使部分失败,后续有补偿机制即可。

pipeline用法

  1. redistemplate
List<Long> List = redisTemplate.executePipelined(new RedisCallback<Long>() {
  @Nullable
  @Override
  public Long doInRedis(RedisConnection connection) throws DataAccessException {
    connection.openPipeline();
    for (int i = 0; i < 1000000; i++) {
      String key = "123" + i;
      connection.zCount(key.getBytes(), 0,Integer.MAX_VALUE);
    }
    return null;
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
  1. Jedis
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 获取 Pipeline 对象
Pipeline pipe = jedis.pipelined();
// 设置多个 Redis 命令
for (int i = 0; i < 100; i++) {
  pipe.set("key" + i, "val" + i);
}
// 执行命令并返回结果
List<Object> res = pipe.syncAndReturnAll();
for (Object obj : res) {
  // 打印结果
  System.out.println(obj);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 二、持久化(AOF/RDB)

由于Redis在内存中做数据操作,为了保证服务器重启时数据不丢失,Redis提供了两种持久化机制(Redis的持久化过程都是通过fork新的线程异步执行),分别为AOF(appendonly.aof)和RDB(dump.rdb),两者的区别如下:

  • 文件格式不同:AOF存储的是键值操作的命令,以文件追加的方式添加到AOF文件中,所以文件会比较大。而RDB存储的是某时刻的内存快照,将当前数据以二进制文件存储并写入磁盘,相对文件比较小。
    • AOF文件恢复速度比较慢,但是可读性好,容易修复,而且增量的方式更加轻量。
    • RDB文件则恢复速度快,但是每次都需要持久化整块内存快照,所以在数据量较大而且CPU性能不佳时会导致服务停顿。
  • 持久化时间不同:AOF默认每秒将命令追加到AOF文件中,即使服务重启也只会丢失一秒内的操作,而RDB由于是对内存快照的复制,不能像AOF那样进行增量增加,所以通常是隔一段时间进行持久化存储,这就导致RDB模式下容易丢失某段时间内的数据。
  • 恢复程度不同:如果不小心调用了flushall命令,由于AOF是文件追加方式,可以通过删除flushall命令还原,而RDB是基于内存快照,会导致所有数据被清空。

AOF和RDB除了自动触发外,也可以通过手动触发:

  • save:同步持久化,阻塞主线程
  • bgsave:异步持久化,通过fork命令创建子线程,而fork也会消耗一定的内存

image.png

AOF重写机制

由于AOF以文件追加方式添加到AOF文档,为了解决文档过大的问题,Redis提供了文件重写功能,当达到一定的文件大小(默认为60M),则重写当前文件,并把当前数据的最少操作命令保存到新文件上,从而减少文件大小。

AOF和RDB混合持久化

由于AOF和RDB各有弊端,AOF文件过大时会影响启动速度,RDB会有一段时间的数据丢失,为了同时能用到AOF和RDB的优点,Redis4.0后新增了混合持久化的方式,在混合持久化的模式下,AOF在重写时会把Redis的持久化数据,以RDB的格式进行存储,并且将接下来的持久化以AOF格式追加到文件末尾。开头为RDB文件,使得Redis能够快速启动,同时又结合了AOF的优点,避免了大量数据丢失的风险。

image.png

image.png

# 三、事务

Redis的事务底层通过MULTI(开启事务) 、 EXEC (执行)、 DISCARD(回滚) 和 WATCH(监听)几个命令实现。事务衡量指标是ACID,Redis中的事务保证如下:

  • A原子性:编译时保证原子性,如果编译时发现中间有命令语法错误,则回滚;运行时不保证,运行中有命令出错则跳过继续执行。Redis只保证所有的命令,要么全部执行,要么全部不执行。而不是完全成功。(Redis官方认为的原子性)

  • C一致性:如果按照官方对A的理解,那么一致性也是保证的。

  • I隔离性:Redis事务是严格遵守隔离性的,因为redis是单线程模式,可以保证命令执行过程中不会被其他客户端命令打断。

  • D持久性:Redis是基于内存进行数据操作的,虽然有持久化机制,但是无法保证所有数据不丢失。

CAS操作实现乐观锁

案例分析:

用户1先读key,后操作,再写入

用户2先读key,后操作,再写入

这会出现并发问题,导致数据被覆盖,Redis提供了watch命令实现CAS操作,通过watch监控某个键值对,然后开启事务进行修改,如果在提交事务前发现键值对被修改了,则回滚事务。

WATCH mykey
MULTI
val = GET mykey
val = val + 1
SET mykey $val
EXEC
1
2
3
4
5
6

img

Redis事务执行步骤

  • 开启:以MULTI开始一个事务
  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
  • 执行:由EXEC命令触发事务

当一个客户端切换到事务状态之后, 服务器会根据这个客户端发来的不同命令执行不同的操作:

  • 如果客户端发送的命令为 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令的其中一个, 那么服务器立即执行这个命令。
  • 与此相反, 如果客户端发送的命令是 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令以外的其他命令, 那么服务器并不立即执行这个命令, 而是将这个命令放入一个事务队列里面, 然后向客户端返回 QUEUED 回复。

img

# 四、参考

Last Updated: 2023-2-6 10:35:39 ├F10: PM┤