有道无术,术尚可求,有术无道,止于术。
本系列Redis 版本 7.2.5
源码地址:https://gitee.com/pearl-organization/study-redis-demo
文章目录
- 1. 往返时间
- 2. 管道技术
- 3. 代码演示
- 4. 其他批处理
- 4.1 原生批处理命令
- 4.2 事务
- 4.3 脚本
1. 往返时间
官方文档
Redis
是一种基于 CS
模型以及请求/响应协议的TCP
服务。通常情况下一个请求会遵循以下步骤:
- 客户端向服务端发送一个请求,并监听
Socket
返回,通常是以阻塞模式,等待服务端响应。 - 服务端处理命令,并将结果返回给客户端。
例如,一个客户端发送四次命令的流程如下所示:
客户端和服务端之间通过网络进行连接通信,无论网络延迟如何,数据包从客户端到服务端的传输,以及从服务端回到客户端的响应,都需要一定的时间,这段时间被称为往返时间(RTT
)。
当客户端需要连续执行许多请求时,例如向同一列表添加多个元素或者将数据库填充多个键,往返时间如何影响性能就很容易理解了。例如,如果往返时间为250
毫秒(非常慢的连接),即使服务端能够每秒处理10
万个请求,客户端每秒也只能发送四个请求。
2. 管道技术
对于上述问题,Redis
提供了管道(Plpeline
)技术,允许客户端将多个命令一次性发送给服务器,而不需要等待每个命令的回复。这样可以减少每条命令的网络延迟时间,特别适合需要发送大量命令的场景。
执行流程如下所示:
管道技术不仅减少了 RTT
,并且极大地提升了在 Redis
服务器上每秒可以执行的操作数量。
使用注意事项:
- 使用管道时,命令只是会依次以行,不保证原子性,如果其中某个命令令发生异常,将会继续执行
- 组装的命令个数不能太多,不然数据量太大时,客户端阻塞的时间可能过久,同时服务端此时也被迫回复一个队列答复,占用很多内存
- 需要发送大量的命令时,需要按照合理数量分批次的处理,例如一次发送
10K
的命令,读取回复后,后再发送另一个10k
的命令。
3. 代码演示
下面代码中,使用 Jedis
插入 10000
个 Key
:
public static void main(String[] args) {
// 创建连接池
JedisPool pool = new JedisPool("localhost", 6379,"default","123456");
// 获取客户端
try (Jedis jedis = pool.getResource()) {
// 1. 不使用管道, 插入 10000 个 Key
long startTime = System.currentTimeMillis();
for (int i = 10000; i > 0; i--) {
jedis.set("key" + i, String.valueOf(i));
}
System.out.println("不使用管道执行时间:" + ( System.currentTimeMillis()-startTime)+"毫秒");
// 2. 使用管道
startTime = System.currentTimeMillis();
Pipeline pipelined = jedis.pipelined();
for (int i = 10000; i > 0; i--) {
pipelined .set("key" + i, String.valueOf(i));
}
System.out.println("使用管道执行时间:" + ( System.currentTimeMillis()-startTime)+"毫秒");
}
}
可以看到,在使用了管道后,执行时间缩短了近 30
倍:
不使用管道执行时间:793毫秒
使用管道执行时间:26毫秒
对于查询命令,当然也可以使用管道,不过结果值是批量返回的:
Pipeline pipelined = jedis.pipelined();
for (int i = 10000; i > 0; i--) {
//pipelined .set("key" + i, String.valueOf(i));
pipelined.get("key" + i);
}
// 获取执行结果
List<Object> results = pipelined.syncAndReturnAll();
4. 其他批处理
除了管道, Redis
还提供了其他批处理方式,可以根据具体需求选择合适的方式来操作 Redis
。
4.1 原生批处理命令
Redis
提供了两个常用的原生命令来处理多个键的操作:
MGET
: 同时获取多个键的值MSET
: 同时设置多个键的值
MGET
和 MSET
命令都是原子操作,即要么全部执行成功,要么全部执行失败。适合于需要一次性获取或设置多个键值对的场景。
示例:
MGET key1 key2 key3
MSET key1 value1 key2 value2 key3 value3
4.2 事务
通过事务机制,可以将多个命令打包在一起执行,事务中的所有命令要么全部执行,要么全部不执行。
示例:
MULTI
SET key1 value1
GET key2
INCR key3
EXEC
4.3 脚本
通过 Redis
脚本,可以将多个命令打包成一个原子操作,确保在执行期间不会被其他命令打断。
示例:
local result1 = redis.call('SET', KEYS[1], ARGV[1])
local result2 = redis.call('GET', KEYS[2])
local result3 = redis.call('INCR', KEYS[3])
return {result1, result2, result3}