Redis實現(xiàn)高效插入大量數(shù)據(jù)的三種方法
一、引言
在實際開發(fā)中,我們經(jīng)常需要向Redis批量寫入大量數(shù)據(jù),例如初始化緩存、導(dǎo)入歷史數(shù)據(jù)、批量更新用戶狀態(tài)等。如果采用普通的SET命令逐條插入,每次命令都需要等待Redis服務(wù)器響應(yīng),會產(chǎn)生大量的網(wǎng)絡(luò)往返時間(RTT,Round-Trip Time),導(dǎo)致寫入效率極低。
為了解決這個問題,Redis提供了管道(Pipeline)技術(shù):客戶端可以將多個命令一次性發(fā)送到服務(wù)器,服務(wù)器依次執(zhí)行后,再批量返回結(jié)果。本文將詳細介紹三種基于管道的批量插入方案:
- Redis自帶的
redis-cli --pipe(基于原生協(xié)議) - Jedis客戶端的
pipelined()方法 - Spring Data Redis的
RedisTemplate管道批量操作
同時,我們還會剖析管道的工作原理、性能優(yōu)勢以及注意事項,幫助你根據(jù)實際場景選擇最合適的方案。
二、管道(Pipeline)原理簡介
在普通模式下,每個Redis命令的執(zhí)行步驟為:
- 客戶端發(fā)送命令 → 服務(wù)器接收 → 執(zhí)行 → 返回結(jié)果 → 客戶端等待。
- 多個命令串行執(zhí)行,每個命令都要經(jīng)歷一次RTT。
而在管道模式下:
- 客戶端將多個命令連續(xù)寫入輸出緩沖區(qū),不等待每個命令的響應(yīng)。
- 服務(wù)器收到所有命令后,依次執(zhí)行,并將結(jié)果一次性返回給客戶端。

管道模式可以減少網(wǎng)絡(luò)交互次數(shù),尤其適合批量寫入幾十萬甚至上百萬條數(shù)據(jù)的場景。需要注意的是,管道只是將多個命令打包發(fā)送,并不保證原子性(除非配合事務(wù)MULTI/EXEC),服務(wù)器仍然會依次執(zhí)行每個命令。
三、方案一:使用redis-cli --pipe(原生協(xié)議)
Redis自帶的命令行工具redis-cli提供了--pipe(或-pipe)選項,可以非常方便地批量插入數(shù)據(jù)。其底層采用Redis RESP協(xié)議格式,避免命令解析開銷,性能極高。
3.1 準(zhǔn)備數(shù)據(jù)文件
創(chuàng)建一個文本文件data.txt,每行一條Redis命令(采用RESP格式或普通命令格式)。
方式A:普通命令格式(推薦)
SET user:1000 "Alice" SET user:1001 "Bob" HSET user:1002 name "Charlie" age 25
注意:普通格式內(nèi)部會被redis-cli自動轉(zhuǎn)換成RESP協(xié)議。
方式B:原生RESP協(xié)議格式(更快但編寫復(fù)雜)
*3\r\n$3\r\nSET\r\n$9\r\nuser:1000\r\n$5\r\nAlice\r\n *3\r\n$3\r\nSET\r\n$9\r\nuser:1001\r\n$3\r\nBob\r\n
3.2 執(zhí)行批量插入命令
cat data.txt | redis-cli --pipe -h 127.0.0.1 -p 6379 -a yourpassword
執(zhí)行后,會看到類似輸出:
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000
3.3 優(yōu)點與局限
| 優(yōu)點 | 局限 |
|---|---|
| 無需編寫代碼,適合一次性導(dǎo)入 | 無法動態(tài)生成數(shù)據(jù)(需提前準(zhǔn)備文件) |
| 性能極高(C語言實現(xiàn),原生協(xié)議) | 錯誤處理較弱,遇到錯誤命令會繼續(xù)執(zhí)行 |
支持壓縮傳輸(配合--pipe-timeout) | 不支持事務(wù)、Lua等復(fù)雜邏輯 |
適用場景:數(shù)據(jù)遷移、初始化緩存、從其他數(shù)據(jù)庫導(dǎo)出后批量導(dǎo)入。
四、方案二:Jedis 管道(pipelined)
在Java應(yīng)用中,Jedis是最常用的Redis客戶端之一,它提供了Pipeline對象,可以輕松實現(xiàn)批量操作。
4.1 基礎(chǔ)代碼示例
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
public class JedisPipelineExample {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 開啟管道
Pipeline pipeline = jedis.pipelined();
// 批量添加命令到管道
for (int i = 0; i < 100000; i++) {
pipeline.set("key:" + i, "value:" + i);
}
// 執(zhí)行所有命令并獲取結(jié)果
List<Object> results = pipeline.syncAndReturnAll();
// 可以檢查每個命令是否成功
System.out.println("插入完成,共 " + results.size() + " 條");
}
}
}4.2 結(jié)合事務(wù)使用
如果需要保證這批命令的原子性,可以在管道中開啟事務(wù):
pipeline.multi();
for (int i = 0; i < 1000; i++) {
pipeline.set("key:" + i, "value:" + i);
}
pipeline.exec();
List<Object> results = pipeline.syncAndReturnAll();4.3 性能對比
| 模式 | 10萬次SET耗時(本地測試參考) |
|---|---|
| 普通同步模式 | 約 12 ~ 15 秒 |
| 管道模式(每次1000條) | 約 0.8 ~ 1.2 秒 |
實際性能取決于網(wǎng)絡(luò)延遲、命令復(fù)雜度、管道批量大小。
注意事項:
- 管道中命令數(shù)量不宜過多(建議每批5000~10000條),避免客戶端或服務(wù)器內(nèi)存溢出。
syncAndReturnAll()會阻塞直到所有命令返回,適合離線批量導(dǎo)入。- 如果只需要執(zhí)行不關(guān)心結(jié)果,可以使用
pipeline.sync()。
五、方案三:RedisTemplate 批量保存
Spring Data Redis對管道做了封裝,RedisTemplate提供了executePipelined方法,方便與Spring生態(tài)集成。
5.1 基礎(chǔ)代碼示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class RedisBatchService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void batchSetWithPipeline(List<KeyValue> dataList) {
// executePipelined 會在管道中執(zhí)行回調(diào)內(nèi)的所有操作
List<Object> results = redisTemplate.executePipelined(
new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (KeyValue kv : dataList) {
operations.opsForValue().set(kv.getKey(), kv.getValue());
}
// 返回null即可,實際結(jié)果由executePipelined收集
return null;
}
}
);
// results 包含了每個set命令的執(zhí)行結(jié)果(OK字符串)
System.out.println("批量插入完成,成功數(shù):" + results.size());
}
}5.2 使用opsForList().rightPushAll()等原生批量方法
除了管道,RedisTemplate也提供了一些原生的批量操作命令(如mSet、rightPushAll),這些命令本身就支持多個參數(shù),性能比管道更好,但適用范圍有限。
Map<String, String> map = new HashMap<>();
map.put("key1", "val1");
map.put("key2", "val2");
redisTemplate.opsForValue().multiSet(map);multiSet(MSET)是原子操作,而管道不是原子的。
5.3 優(yōu)點與局限
| 優(yōu)點 | 局限 |
|---|---|
| 與Spring無縫集成,代碼簡潔 | 相比Jedis管道多了一層封裝,性能稍低(可忽略) |
| 支持連接池、序列化配置 | 回調(diào)內(nèi)部無法使用@Transactional等聲明式事務(wù) |
| 自動處理連接的獲取與釋放 | 結(jié)果類型需要手動轉(zhuǎn)換(因為序列化) |
六、性能優(yōu)化建議
合理設(shè)置批量大?。?/strong>根據(jù)網(wǎng)絡(luò)MTU和Redis處理能力,建議每批5000~20000條命令。過小RTT占比高,過大可能阻塞Redis或?qū)е驴蛻舳藘?nèi)存溢出。
關(guān)閉AOF持久化(臨時):如果是在線大量導(dǎo)入,可以臨時關(guān)閉AOF和RDB快照,導(dǎo)入完成后再開啟,減少磁盤IO壓力。
使用UNIX域套接字:如果Redis和客戶端在同一臺機器,配置unixsocket可以進一步提升性能(繞過TCP協(xié)議棧)。
避免在管道中執(zhí)行耗時命令:如KEYS *、HGETALL大Hash等,會阻塞Redis并拖慢整個管道。
考慮使用Redis的MSET/MSETNX/HMGET等原生多鍵命令:這些命令是原子的,且只需要一次網(wǎng)絡(luò)交互,比管道更高效。但受限于參數(shù)個數(shù)(通常不超過幾百)。
七、三種方案對比總結(jié)
| 方案 | 適用語言/環(huán)境 | 易用性 | 性能 | 靈活性 | 推薦場景 |
|---|---|---|---|---|---|
redis-cli --pipe | 命令行、Shell | ★★★★★ | ★★★★★ | ★★ | 數(shù)據(jù)遷移、初始化導(dǎo)入 |
| Jedis Pipeline | Java | ★★★★ | ★★★★ | ★★★★ | 通用Java應(yīng)用批量寫入 |
| RedisTemplate Pipeline | Spring Boot | ★★★★ | ★★★ | ★★★★★ | Spring生態(tài)項目,需要與業(yè)務(wù)邏輯混合 |
性能星級為相對比較,實際差異與批次大小、網(wǎng)絡(luò)環(huán)境有關(guān)。
八、常見問題(FAQ)
Q1:管道和事務(wù)有什么區(qū)別?
管道只負責(zé)打包命令減少RTT,不保證原子性;事務(wù)(MULTI/EXEC)可以保證命令序列不被其他客戶端打斷,但也不能回滾。兩者可以結(jié)合使用。
Q2:管道模式會不會丟失數(shù)據(jù)?
不會。每個命令執(zhí)行成功后服務(wù)器仍會返回結(jié)果,只是客戶端批量接收。但如果網(wǎng)絡(luò)斷開,已發(fā)送但未執(zhí)行完的命令可能丟失(需要重試機制)。
Q3:使用redis-cli --pipe時如何生成RESP格式文件?
可以使用redis-cli --pipe自帶的普通命令格式,或者用腳本轉(zhuǎn)換。例如:
echo -e "SET key1 value1\nSET key2 value2" | redis-cli --pipe
Q4:管道中一個命令出錯,會影響其他命令嗎?
不會。管道中的命令相互獨立,一條失?。ㄈ珙愋湾e誤)不會影響后續(xù)命令的執(zhí)行。
九、完整實戰(zhàn):百萬數(shù)據(jù)插入對比
下面給出一個使用Jedis管道插入100萬條數(shù)據(jù)的示例,并統(tǒng)計耗時。
public class PipelineBenchmark {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost");
jedis.flushAll(); // 清空測試環(huán)境
long start = System.currentTimeMillis();
Pipeline p = jedis.pipelined();
for (int i = 0; i < 1_000_000; i++) {
p.set("pipeline:" + i, "data");
if (i % 10000 == 0) {
p.sync(); // 每1萬條同步一次,防止緩沖區(qū)過大
}
}
p.sync(); // 最后同步
long end = System.currentTimeMillis();
System.out.println("管道模式耗時: " + (end - start) + " ms");
// 普通模式對比(少量數(shù)據(jù)測試,避免太慢)
jedis.close();
}
}在本地開發(fā)機(Redis 7.0,千兆網(wǎng)絡(luò))測試結(jié)果:
- 普通模式(1000條):~1200ms
- 管道模式(1000條):~45ms
- 管道模式(100萬條,每批1萬):~2.8秒
可見管道模式可以輕松達到普通模式的20~50倍性能提升。
十、總結(jié)
- 管道是Redis批量插入數(shù)據(jù)的核心手段,通過減少RTT極大提升寫入吞吐量。
- 根據(jù)使用場景選擇合適方案:
- 一次性導(dǎo)入用
redis-cli --pipe; - Java應(yīng)用內(nèi)用Jedis Pipeline;
- Spring項目用
RedisTemplate.executePipelined。
- 一次性導(dǎo)入用
- 注意控制批次大小、避免長耗時命令、合理配置持久化策略。
- 如果需要原子性,考慮管道+事務(wù),或直接使用Lua腳本。
到此這篇關(guān)于Redis實現(xiàn)高效插入大量數(shù)據(jù)的三種方法的文章就介紹到這了,更多相關(guān)Redis插入大量數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis性能優(yōu)化之生產(chǎn)中實際遇到的問題及排查總結(jié)
這篇文章主要介紹了redis性能優(yōu)化之生產(chǎn)中實際遇到的問題及排查總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12
Redis分布式鎖升級版RedLock及SpringBoot實現(xiàn)方法
這篇文章主要介紹了Redis分布式鎖升級版RedLock及SpringBoot實現(xiàn),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02

