Redis 管道技术

管道(Pipeline)可以批量发送命令,减少网络往返次数,大幅提升性能。

什么是管道?

传统方式

每条命令都需要一次网络往返:

客户端                  Redis服务器
  |                         |
  |----- SET key1 -------->|
  |<----- OK --------------|
  |                         |
  |----- SET key2 -------->|
  |<----- OK --------------|
  |                         |
  |----- SET key3 -------->|
  |<----- OK --------------|

3条命令 = 6次网络传输(3次请求 + 3次响应)

管道方式

多条命令一次性发送:

客户端                  Redis服务器
  |                         |
  |----- SET key1 -------->|
  |----- SET key2 -------->|
  |----- SET key3 -------->|
  |                         |
  |<----- OK --------------|
  |<----- OK --------------|
  |<----- OK --------------|

3条命令 = 2次网络传输(1次批量请求 + 1次批量响应)

💡 性能提升: 管道可以减少网络往返,大幅提升性能,尤其是网络延迟较高时。

管道原理

网络延迟影响

假设网络往返延迟为1ms:

传统方式:
- 100条命令 = 100次往返 = 100ms网络延迟
- 加上Redis处理时间(假设0.1ms/命令)= 10ms
- 总时间 = 100ms + 10ms = 110ms

管道方式:
- 100条命令 = 1次往返 = 1ms网络延迟
- 加上Redis处理时间 = 10ms
- 总时间 = 1ms + 10ms = 11ms

性能提升:110ms → 11ms,提升10倍!

管道特点

⚠️ 注意: 管道不是事务,不保证原子性。如需原子性,使用MULTI/EXEC。

使用管道

redis-cli管道模式

BASH
# 使用redis-cli的管道模式
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3" | redis-cli

# 或从文件读取
cat commands.txt | redis-cli

Python使用管道

PYTHON
import redis

r = redis.Redis(host='localhost', port=6379)

# 创建管道
pipe = r.pipeline()

# 添加命令到管道
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.set('key3', 'value3')

# 执行管道
results = pipe.execute()
print(results)  # [True, True, True]

Java使用管道(Jedis)

JAVA
Jedis jedis = new Jedis("localhost", 6379);

Pipeline pipeline = jedis.pipelined();

pipeline.set("key1", "value1");
pipeline.set("key2", "value2");
pipeline.set("key3", "value3");

List<Object> results = pipeline.syncAndReturnAll();

Node.js使用管道(ioredis)

JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();

const pipeline = redis.pipeline();

pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.set('key3', 'value3');

const results = await pipeline.exec();

管道与事务

管道 + 事务

管道可以与事务结合使用:

PYTHON
import redis

r = redis.Redis()

# 创建管道,开启事务
pipe = r.pipeline(transaction=True)

pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.incr('counter')

# 执行事务
results = pipe.execute()

管道 vs 事务

对比项 管道 事务
原子性 ❌ 无 ✅ 有
隔离性 ❌ 无 ✅ 有
性能提升 ✅ 显著 ✅ 显著
批量发送 ✅ 是 ✅ 是
适用场景 批量操作、提升性能 需要原子性的批量操作
💡 选择: 不需要原子性时用管道,需要原子性时用事务或管道+事务。

管道应用场景

场景1:批量设置

PYTHON
import redis

r = redis.Redis()
pipe = r.pipeline()

# 批量设置1000个键
for i in range(1000):
    pipe.set(f'key:{i}', f'value:{i}')

results = pipe.execute()
print(f'设置完成:{len(results)}个键')

场景2:批量获取

PYTHON
import redis

r = redis.Redis()
pipe = r.pipeline()

# 批量获取1000个键
keys = [f'key:{i}' for i in range(1000)]
for key in keys:
    pipe.get(key)

values = pipe.execute()
print(f'获取完成:{len(values)}个值')

场景3:批量删除

PYTHON
import redis

r = redis.Redis()
pipe = r.pipeline()

# 批量删除
keys = ['key1', 'key2', 'key3', 'key4', 'key5']
for key in keys:
    pipe.delete(key)

results = pipe.execute()
print(f'删除完成:{sum(results)}个键')

场景4:数据导入

PYTHON
import redis
import json

r = redis.Redis()
pipe = r.pipeline()

# 从JSON文件导入数据
with open('data.json', 'r') as f:
    data = json.load(f)
    
    for item in data:
        key = f"user:{item['id']}"
        value = json.dumps(item)
        pipe.set(key, value)

pipe.execute()
print('导入完成')

场景5:计数器批量更新

PYTHON
import redis

r = redis.Redis()
pipe = r.pipeline()

# 批量更新计数器
counters = {
    'article:1:views': 10,
    'article:2:views': 20,
    'article:3:views': 30,
}

for key, increment in counters.items():
    pipe.incrby(key, increment)

pipe.execute()
print('计数器更新完成')

管道性能对比

测试代码

PYTHON
import redis
import time

r = redis.Redis()

# 测试数据
n = 10000

# 方式1:普通方式
start = time.time()
for i in range(n):
    r.set(f'key:{i}', f'value:{i}')
end = time.time()
print(f'普通方式:{end - start:.2f}秒')

# 方式2:管道方式
start = time.time()
pipe = r.pipeline()
for i in range(n):
    pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
end = time.time()
print(f'管道方式:{end - start:.2f}秒')

测试结果示例

普通方式:5.23秒
管道方式:0.51秒

性能提升:约10倍

管道最佳实践

1. 合理设置批量大小

PYTHON
# ❌ 一次性发送太多命令
pipe = r.pipeline()
for i in range(100000):
    pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()  # 可能占用大量内存

# ✅ 分批执行
batch_size = 1000
for batch in range(0, 100000, batch_size):
    pipe = r.pipeline()
    for i in range(batch, batch + batch_size):
        pipe.set(f'key:{i}', f'value:{i}')
    pipe.execute()

2. 错误处理

PYTHON
import redis

r = redis.Redis()
pipe = r.pipeline()

pipe.set('key1', 'value1')
pipe.incr('key1')  # 这会失败(key1不是数字)
pipe.set('key2', 'value2')

try:
    results = pipe.execute()
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            print(f'命令{i}失败:{result}')
except Exception as e:
    print(f'管道执行失败:{e}')

3. 使用事务保证原子性

PYTHON
import redis

r = redis.Redis()

# 需要原子性时,使用事务
pipe = r.pipeline(transaction=True)

pipe.set('account:a', '100')
pipe.set('account:b', '50')

try:
    results = pipe.execute()
    print('事务执行成功')
except Exception as e:
    print(f'事务执行失败:{e}')

4. 监控管道性能

PYTHON
import redis
import time

r = redis.Redis()

start = time.time()
pipe = r.pipeline()

for i in range(10000):
    pipe.set(f'key:{i}', f'value:{i}')

results = pipe.execute()
end = time.time()

print(f'执行时间:{end - start:.2f}秒')
print(f'QPS:{10000 / (end - start):.0f}')

管道限制

1. 内存占用

PYTHON
# 管道会缓存所有命令和结果
# 批量太大时占用大量内存

# 解决方案:分批执行

2. 非原子性

PYTHON
# 管道中的命令可能被其他客户端打断
# 如需原子性,使用事务

3. 无法使用中间结果

PYTHON
# 管道中无法使用前一条命令的结果

# ❌ 错误示例
pipe = r.pipeline()
pipe.incr('counter')
# 无法获取incr的结果用于后续命令
pipe.set('result', ???)  # 无法使用incr结果
pipe.execute()

# ✅ 解决方案:使用Lua脚本

❓ 常见问题

Q 管道和事务的区别?
A 管道减少网络往返,不保证原子性。事务保证原子性,也减少网络往返。
Q 管道能提升多少性能?
A 取决于网络延迟。网络延迟越高,提升越明显。通常提升5-10倍。
Q 管道批量大小如何设置?
A 建议每批100-1000条命令。太大占用内存,太小性能提升不明显。
Q 管道中的命令失败会怎样?
A 某条命令失败不影响其他命令执行。需要检查返回结果。
Q 何时使用管道?
A 批量操作、数据导入导出、需要提升性能的场景。

📖 小节

📝 作业

  1. 基本管道: 使用管道批量设置100个键,对比普通方式的性能
  2. 批量操作: 使用管道实现批量获取、批量删除
  3. 性能对比: 测试不同批量大小(10、100、1000)的性能差异
  4. 管道+事务: 使用管道+事务实现原子性的批量操作

下一课

下一课我们将学习 Python 使用 Redis,学习Python操作Redis。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏