一,Redis 供应的数据驱动库
在进行 Redis 开发的时候,为了适应全面的使用需要,所以任何的主流编程语言 Redis 官方都会提供数据驱动支持的,就类似 Java 开发之中关系型数据库的数据驱动库的提供(JDBC)。只不过JDBC 是由 Java官方定制标准,而后不同的关系型数据库去实施,而对Redis 来讲,不用的语言会提供各自的驱动包。
官网地址:Connect with Redis client API libraries

可以看到 Redis 官网对于 Java 提供了有两种数据驱动包,一个旧的是 Jedis,一个是新的是 Lettuce 数据驱动包。 它们的主要区别在于设计理念 和底层架构:Jedis 是轻量、直连的"老兵",而 Lettuce 则是基于Netty的现代化、高性能"新贵"。
| 对比维度 | Jedis | Lettuce |
|---|---|---|
| 线程安全 | 非线程安全。多线程下需使用连接池,否则会出错。 | 线程安全。单个连接可被多线程共享,减少连接数。 |
| 编程模型 | 同步阻塞。API与Redis命令一一对应,简单直观。 | 同步、异步、反应式。基于Netty的非阻塞I/O,适合高并发。 |
| 连接管理 | 每个命令可能需要新建或从池中获取连接,开销较大。 | 可复用单一长连接处理多数命令,连接管理更高效。 |
| 集群/哨兵支持 | 支持,但需手动处理节点重定向等逻辑。 | 原生支持。自动处理节点发现、重定向和故障转移。 |
| 性能表现 | 同步阻塞模型,高并发下可能成为瓶颈。 | 异步非阻塞,吞吐量高、响应时间稳定。 |
| 资源消耗 | 相对轻量。 | 基于Netty,功能复杂,内存和CPU占用可能稍高。 |
| Spring Boot | Spring Boot 1.x 默认使用。 | Spring Boot 2.x 及之后版本默认使用。 |
二,Lettuce 数据驱动包
2.1 RedisClient
Lettuce 是一个新的数据驱动包,springboot2.x 系列之后也是默认提供这个数据驱动包来连接 Redis 服务器。小编这边使用这个来开始连接。
java
package com.toast.redis.lettuce;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisCredentialsProvider;
import io.lettuce.core.RedisURI;
import io.lettuce.core.StaticCredentialsProvider;
public class LettuceRedisTest {
// redis的服务器地址
private static final String REDIS_HOST = "toast-redis-server";
// 连接端口
private static final int REDIS_PORT = 6379;
// 用户名,默认
private static final String REDIS_USERNAME = "default";
// 连接密码
private static final String REDIS_PASSWORD = "ToastR#16";
// 默认的数据库连接索引
private static final int REDIS_INDEX = 0;
public static void main(String[] args) {
// 1. 构建 Redis 连接 URI
RedisURI uri = RedisURI.create(REDIS_HOST, REDIS_PORT);
// 认证信息
RedisCredentialsProvider provider = new StaticCredentialsProvider(REDIS_USERNAME, REDIS_PASSWORD.toCharArray());
uri.setCredentialsProvider(provider);
uri.setDatabase(REDIS_INDEX);
RedisClient client = RedisClient.create(uri);
System.out.println("【Redis客户端实例】client = " + client);
client.close();
}
}
输出内容:
ini
【Redis客户端实例】client = io.lettuce.core.RedisClient@7671cb68 # 表示连接成功
代码并不难,就是简单的连接操作,现在我们整体拆分一下 RedisClient 代码的设计模块。

| AutoCloseable | 通过实现自动关闭接口,实现Socket 自动关闭,节省IO资源 |
|---|---|
| AbstractRedisClient | 抽象层的RedisClient, 里面提供着线程池,连接服务器方法标准化,后面的具体实现如下:AbstractRedisClient ├── RedisClient (单机) ├── RedisClusterClient (集群) └── RedisSentinelClient(哨兵) |
| RedisURI | 核心配置, 本质就是连接信息载体(host、port、db、ssl 等),还支持认证信息 |
| RedisCredentialsProvider | 动态提供认证信息,适用于:密码轮换,动态 Token,云服务认证(IAM / ACL);最简单的实现new StaticCredentialsProvider("user", "password");固定用户名密码 不支持动态刷新 |
完整关系总结
js
AutoCloseable
↑
AbstractRedisClient ←(资源管理核心)
↑
RedisClient ←(真正用的客户端)
RedisClient 使用 RedisURI 建立连接
RedisURI 内部可以配置:
↓
RedisCredentialsProvider(认证策略接口)
↑
StaticCredentialsProvider(固定认证实现)
2.2 StatefulConnection
当RedisClient 被创建好之后,我们就可以通过 RedisClient 发送命令输送到 Redis 服务器中。由于 Redis 有着丰富的数据结构。所以命令的API也是根据不同的数据结构类型分类的。并且还提供同步命令发送,异步命令发送,以及响应式命令发送三种方式。
现在我们介绍一下 Redis 的服务连接通道业务模块,在java的Redis数据驱动包中,对于连接通道的设计,提供超时时间,字符集,以及自动关闭通道功能,提供同步连接,异步连接,响应式连接,以及最重要的就是发送命令的功能。
java
public class LettuceRedisTest {
// redis的服务器地址
private static final String REDIS_HOST = "toast-redis-server";
// 连接端口
private static final int REDIS_PORT = 6379;
// 用户名,默认
private static final String REDIS_USERNAME = "default";
// 连接密码
private static final String REDIS_PASSWORD = "ToastR#16";
// 默认的数据库连接索引
private static final int REDIS_INDEX = 0;
public static void main(String[] args) {
// 1. 构建 Redis 连接 URI
RedisURI uri = RedisURI.create(REDIS_HOST, REDIS_PORT);
// 认证信息
RedisCredentialsProvider provider = new StaticCredentialsProvider(REDIS_USERNAME, REDIS_PASSWORD.toCharArray());
uri.setCredentialsProvider(provider);
uri.setDatabase(REDIS_INDEX);
RedisClient client = RedisClient.create(uri);
System.out.println("【Redis客户端实例】client = " + client);
// 建立连接 Redis 服务通道
StatefulRedisConnection<String, String> connect = client.connect();
client.close();
}
}
查看 StatefulRedisConnection 接口的继承详情
java
package io.lettuce.core.api;
public interface StatefulRedisConnection<K, V>
extends StatefulConnection<K, V> {/** 忽略代码体*/}
查看 StatefulConnection 接口的继承
java
package io.lettuce.core.api;
public interface StatefulConnection<K, V>
extends java.lang.AutoCloseable, io.lettuce.core.api.AsyncCloseable {/** 忽略代码体*/}
// AsyncCloseable 接口如下
package io.lettuce.core.api;
import java.util.concurrent.CompletableFuture;
/**
* @since 6.2
* @author Mark Paluch
*/
public interface AsyncCloseable extends io.lettuce.core.internal.AsyncCloseable {
CompletableFuture<Void> closeAsync();
}
可以从继承结构得知,Redis 的连接通道是具备同步自动关闭,异步自动关闭。现在我们在接口的详情方法。StatefulConnection 的接口本质分成 7 大能力
C3
# 1. 连接状态监听(Listener)
# 2. 超时控制(Timeout)
# 3. 命令调度(Dispatch)
# 4. 生命周期管理(Close)
# 5. 客户端配置(Options / Resources)
# 6. IO行为控制(Flush / AutoFlush)
# 7. 编码策略(Codec)
java
package io.lettuce.core.api;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisConnectionStateListener;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.protocol.RedisCommand;
import io.lettuce.core.resource.ClientResources;
/**
* @since 4.0
*/
public interface StatefulConnection<K, V> extends AutoCloseable, AsyncCloseable {
/** 1. 连接状态监听(Listener) */
void addListener(RedisConnectionStateListener listener); // 追加连接监听器
void removeListener(RedisConnectionStateListener listener); // 移除连接监听器
/** 2. 超时控制(Timeout) */
void setTimeout(Duration timeout); // 设置超时时间
Duration getTimeout(); // 获取超时时间
/** 3. 命令调度(Dispatch) */
<T> RedisCommand<K, V, T> dispatch(RedisCommand<K, V, T> command); // 命令调度-命令发送
//命令调度-批量命令发送,支持同步和异步
Collection<RedisCommand<K, V, ?>> dispatch(Collection<? extends RedisCommand<K, V, ?>> commands);
/** 4. 生命周期管理(Close) */
void close(); // 同步关闭
boolean isOpen(); // 是否连接中
CompletableFuture<Void> closeAsync(); // 异步关闭
/** 5. 客户端配置(Options / Resources) */
ClientOptions getOptions(); // RedisClient 配置项
ClientResources getResources(); // 线程池资源
/** 6. IO行为控制(Flush / AutoFlush) */
void setAutoFlushCommands(boolean autoFlush); // 是否自动发送命令
void flushCommands(); // 刷新命令队列
/** 7. 编码策略(Codec) */
RedisCodec<K, V> getCodec();
}
1️⃣ 连接监听(观察者模式)
java
void addListener(RedisConnectionStateListener listener);
void removeListener(RedisConnectionStateListener listener);
✅ 干什么?: 监听连接状态变化:建立连接,断开连接,重连成功
🧠 设计本质: 👉 观察者模式(Observer)
🔥 使用场景(真实工程) ✔ 做监控, ✔ 自动恢复策略, ✔ 熔断 / 降级
java
connection.addListener(new RedisConnectionStateListener() {
public void onRedisDisconnected(...) {
// 告警 / 降级
}
});
2️⃣ 超时控制
java
void setTimeout(Duration timeout);
Duration getTimeout();
✅ 干什么?: 控制命令执行最大等待时间
🧠 影响范围: sync(阻塞等待),async(Future timeout),reactive(Mono timeout)
🔥 本质: 👉 防止"卡死"
3️⃣ 命令调度(最核心🔥)
java
<T> RedisCommand<K, V, T> dispatch(RedisCommand<K, V, T> command);
Collection<RedisCommand<K, V, ?>> dispatch(Collection<? extends RedisCommand<K, V, ?>> commands);
✅ 干什么?: 👉 发送 Redis 命令的底层入口
🧠 重点理解:
java
// 你平时写
// connection.sync().get("key");
// 其实底层就是:
// 构造 RedisCommand → dispatch()
🔥 单条 vs 批量:
单条:dispatch(command)
批量(Pipeline):dispatch(commands)
🚀 设计价值: 👉 把"命令执行"抽象成统一入口, ✔ 支持 pipeline, ✔ 支持批量发送, ✔ 支持 async/reactive
👉 本质一句话:
java
dispatch = Lettuce 的"命令总入口"
4️⃣ 生命周期管理(资源控制)
java
void close();
CompletableFuture<Void> closeAsync();
boolean isOpen();
✅ 干什么?:
| 方法 | 作用 |
|---|---|
| close() | 同步关闭 |
| closeAsync() | 异步关闭 |
| isOpen() | 是否连接中 |
🧠 设计亮点: 👉 同时支持同步 & 异步关闭
🔥 为什么要 async close?: 因为底层是 Netty:
java
关闭连接 = IO 操作(非阻塞)
5️⃣ 客户端配置访问
java
ClientOptions getOptions();
ClientResources getResources();
✅ 干什么? :获取底层配置;
java
# ✔ ClientOptions
# ● 超时策略
# ● 重连策略
# ● 心跳配置
# ✔ ClientResources
# ● EventLoop 线程池
# ● Timer
# ● 线程模型
🧠 设计目的: 👉 把底层能力"暴露但不耦合"
6️⃣ IO 行为控制(高级🔥)
java
void setAutoFlushCommands(boolean autoFlush);
void flushCommands();
✅ 干什么?: 控制命令是否立即发送
🔥 默认行为:autoFlush = true 👉 每个命令:
java
write + flush(立刻发)
🚀 关闭自动 flush:
java
connection.setAutoFlushCommands(false); // 👉 此时:命令只进队列,不发
🔥 手动 flush:
java
connection.flushCommands(); // 👉 一次性发送(🔥性能优化关键)
🧠 本质: 👉 Pipeline(批处理), 💥 性能意义
减少网络往返(RTT)
7️⃣ Codec(数据解耦)
java
RedisCodec<K, V> getCodec();
✅ 干什么?: 获取当前连接的编码策略
🧠 本质: 👉 数据序列化策略
🔥 意义:支持 JSON,支持二进制,支持自定义对象
👉 一句话总结这个接口
ini
StatefulConnection =
连接生命周期 + 命令调度 + IO控制 + 编码策略 + 监听机制
👉 从架构角度看
它是:
👉 "命令执行层"和"网络通信层"的桥梁
👉 从设计模式看
| 模块 | 模式 |
|---|---|
| Listener | 观察者模式 |
| Codec | 策略模式 |
| Dispatch | 命令模式 |
| Flush | 批处理模式 |
| AsyncClose | 异步设计 |
2.3 StatefulRedisConnection
这个接口其实是在刚才那个 StatefulConnection 的基础上,往"Redis语义层"再包了一层 。
如果说前者是"通用连接抽象",那这个就是:👉 面向 Redis 使用者的"最终连接形态"。
java
package io.lettuce.core.api;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.push.PushListener;
import io.lettuce.core.api.reactive.RedisReactiveCommands;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.protocol.ConnectionWatchdog;
public interface StatefulRedisConnection<K, V> extends StatefulConnection<K, V> {
boolean isMulti();
RedisCommands<K, V> sync();
RedisAsyncCommands<K, V> async();
RedisReactiveCommands<K, V> reactive();
void addListener(PushListener listener);
void removeListener(PushListener listener);
}
✅ 它多做了什么?
在父接口基础上增加了:
shell
# 1. Redis API 门面(sync / async / reactive)
# 2. 事务状态感知(isMulti)
# 3. Push 消息处理(Redis 6 新特性)
StatefulRedisConnection = StatefulConnection(底层能力)+ Redis操作语义(命令API + 事务 + 推送)
1️⃣ isMulti() ------ 事务状态感知
arduino
boolean isMulti();
✅ 干什么?: 判断当前连接是否处于 MULTI / EXEC 事务中
****📌 示例
java
RedisCommands<String, String> cmd = connection.sync();
cmd.multi(); // 开启事务
System.out.println(connection.isMulti()); // true
cmd.exec(); // 提交事务
System.out.println(connection.isMulti()); // false
🧠 设计意义
arduino
// 👉 "连接是有状态的",这里从接口名称也可以看出来,Stateful Connection 有状态的连接
// Redis 事务是 基于连接的状态机:NORMAL → MULTI → EXEC/DISCARD → NORMAL
2️⃣ sync() ------ 同步命令 API
csharp
RedisCommands<K, V> sync();
✅ 干什么?: 返回同步(阻塞)Redis API
🧠 本质: 👉 对 async 的"阻塞封装"
🔥 设计亮点:不创建新连接!! 👉 只是一个:Facade(门面)
📌 示例:
java
RedisCommands<String, String> sync = connection.sync();
sync.set("k", "v");
String v = sync.get("k");
3️⃣ async() ------ 异步 API(核心🔥)
java
RedisAsyncCommands<K, V> async();
✅ 干什么?: 返回 Future / CompletionStage 风格 API
🧠 本质(非常重要): 👉 Lettuce 的"真实执行层"
📌 示例:
java
RedisAsyncCommands<String, String> async = connection.async();
async.set("k", "v");
async.get("k").thenAccept(v -> {
System.out.println(v);
});
所有 API:
js
sync / reactive
↓
async
↓
dispatch()
↓
Netty
4️⃣ reactive() ------ 响应式 API
csharp
RedisReactiveCommands<K, V> reactive();
✅ 干什么? :返回 Reactive Streams API(Mono / Flux)
📌 示例:
java
RedisReactiveCommands<String, String> reactive = connection.reactive();
reactive.get("k")
.subscribe(v -> System.out.println(v));
🧠 本质: 👉 async 的"响应式适配器"
🔥 设计意义: 支持 WebFlux,支持流式处理,支持 backpressure
5️⃣ PushListener ------ Redis 6 推送机制🔥
java
void addListener(PushListener listener);
void removeListener(PushListener listener);
✅ 干什么?: 处理 Redis 主动推送的消息
📌 支持场景:
java
# Redis 6+:
# ● Pub/Sub(发布订阅)
# ● Keyspace notifications
# ● Client tracking(缓存失效通知)
📌 示例(概念)
java
connection.addListener(message -> {
System.out.println("收到 push 消息: " + message);
});
🧠 本质
👉 Redis 从"请求响应模型" → "事件驱动模型"
🔥 和普通命令的区别
| 类型 | 特点 |
|---|---|
| GET/SET | 请求 → 响应 |
| PUSH | Redis 主动推 |
🧠 设计模式
👉 事件监听 / 发布订阅模型
2.4 commoands 命令接口设计
RedisClient,以及StatefulRedisConnection 连接讲解完之后,我们现在讲解 Redis 的命令设计,讲解完这个之后,就可以完全理解整个的设计流程了。从client, connection,commands 一条完整的设计路线。
1️⃣ 代码案例
在学习之前我们直接上代码案例
java
public class LettuceRedisTest {
// redis的服务器地址
private static final String REDIS_HOST = "toast-redis-server";
// 连接端口
private static final int REDIS_PORT = 6379;
// 用户名,默认
private static final String REDIS_USERNAME = "default";
// 连接密码
private static final String REDIS_PASSWORD = "ToastR#16";
// 默认的数据库连接索引
private static final int REDIS_INDEX = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1️⃣ 创建 RedisURI(连接配置)
RedisURI uri = RedisURI.create(REDIS_HOST, REDIS_PORT);
// 认证信息
RedisCredentialsProvider provider = new StaticCredentialsProvider(REDIS_USERNAME, REDIS_PASSWORD.toCharArray());
uri.setCredentialsProvider(provider);
uri.setDatabase(REDIS_INDEX);
// 2️⃣ 创建 Client(资源管理)
RedisClient client = RedisClient.create(uri);
System.out.println("【Redis客户端实例】client = " + client);
// 3️⃣ 使用 EventBus 监听连接状态(推荐做法)
// 订阅 Lettuce 的全局事件总线
client.getResources().eventBus().get().subscribe(event -> {
// 判断事件类型
if (event instanceof io.lettuce.core.event.connection.ConnectedEvent) {
System.out.println("✅ [EventBus] 连接已建立: " + event);
}
else if (event instanceof io.lettuce.core.event.connection.DisconnectedEvent) {
System.out.println("❌ [EventBus] 连接已断开: " + event);
}
else if (event instanceof io.lettuce.core.event.connection.ConnectionActivatedEvent) {
System.out.println("🟢 [EventBus] 连接已激活(可读写): " + event);
}
else if (event instanceof io.lettuce.core.event.connection.ConnectionDeactivatedEvent) {
System.out.println("🔴 [EventBus] 连接已停用: " + event);
}
});
// 4️⃣ 创建连接(线程安全,可复用)
StatefulRedisConnection<String, String> connection = client.connect(StringCodec.UTF8);
// =============================
// 🧠 一:同步 API(阻塞)
// =============================
System.out.println("================同步 API(阻塞)==============");
RedisCommands<String, String> sync = connection.sync();
sync.set("sync-name", "sync-lettuce");
String value = sync.get("sync-name");
System.out.println("sync result: " + value);
// =============================
// ⚡ 二:异步 API(Future)
// =============================
System.out.println("================异步 API(Future)==============");
RedisAsyncCommands<String, String> async = connection.async();
RedisFuture<String> set = async.set("async-name", "async-lettuce");
String setResult = set.get();
System.out.println("async set result: " + setResult);
CompletableFuture<String> future =
async.get("async-name").toCompletableFuture();
System.out.println("async get result: " + future.get());
// =============================
// 🌊 三:Reactive API(响应式)
// =============================
System.out.println("================Reactive API(响应式)==============");
RedisReactiveCommands<String, String> reactive = connection.reactive();
// 1. 将 set 和 get 串联起来 (then 表示等 set 完成后,再执行 get)
Mono<String> mono = reactive.set("reactive-name", "reactive-lettuce")
.then(reactive.get("reactive-name"));
// 2. 订阅并触发执行 (核心!没有 subscribe 就不会执行)
mono.doOnSuccess(v -> {
System.out.println("✅ reactive result: " + v);
}).subscribe();
// 3. 【关键】因为 subscribe 是异步的,主线程会立刻往下走。
// 如果不让主线程等一会儿,下面的 client.close() 会立刻关闭连接,导致上面的回调来不及执行!
try {
System.out.println("主线程等待异步结果...");
Thread.sleep(2000); // 休眠 2 秒,给 Lettuce 足够的时间完成网络 IO 和回调
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4. 关闭连接
System.out.println("准备关闭客户端...");
client.close();
}
}
输出结果
java
【Redis客户端实例】client = io.lettuce.core.RedisClient@2e222612
✅ [EventBus] 连接已建立: ConnectedEvent [/192.168.77.156:51524 -> toast-redis-server/47.93.0.181:6379]
🟢 [EventBus] 连接已激活(可读写): ConnectionActivatedEvent [/192.168.77.156:51524 -> toast-redis-server/47.93.0.181:6379]
================同步 API(阻塞)==============
sync result: sync-lettuce
================异步 API(Future)==============
async set result: OK
async get result: async-lettuce
================Reactive API(响应式)==============
主线程等待异步结果...
✅ reactive result: reactive-lettuce
准备关闭客户端...
❌ [EventBus] 连接已断开: DisconnectedEvent [/192.168.77.156:51524 -> toast-redis-server/47.93.0.181:6379]
🔴 [EventBus] 连接已停用: ConnectionDeactivatedEvent [/192.168.77.156:51524 -> toast-redis-server/47.93.0.181:6379]
🧩 这三个 API 的关系(必须记住)
dart
sync
↓
reactive → async → dispatch → Netty
👉 核心一句话:async 是核心,其他都是包装
同步是使用RedisCommands 命令接口,异步是使用RedisAsyncCommands 命令接口, 响应式使用的命令接口是:RedisReactiveCommands。直接查看接口。

同步的 Redis 命令接口继承了一堆接口,而这些接口都是按照类型分类而实现的。有字符串数据类型,还有哈希(Hash)数据类型,还有ACL命令等等。从基本命令,认证授权ACL,集群,数据类型,搜索等等,包含了Redis所有的命令操作。只不过实现的方式是以同步的方式实现。

异步命令接口,和响应式命令接口也都是继承了一堆的命令接口。实现的方式分别是异步和响应式的方式实现的。

三,Spring data Redis
3.1 RedisTemplate 简介
只要是 Java 开发,就离不开 Spring 生态圈,对于 Spring 生态中有一个 Spring Data 模块,该模块支持着各类的数据驱动。同样也支持对 Redis 的数据驱动。也就是 Spring Data Redis,其中支持 Redis 官网的两种数据驱动包。一种是 Lettuce ,另一种是 Jedis。默认使用的是 Lecttuce 数据驱动包;
在 Spring Data Redis 里面为了简化命令操作的管理问题,所以提供了 RedisTemplate,通过模板类实现各类数据的简化处理,但是需要提醒的是,如果是一些 Redis 的新命令,有可能 RedisTemplate 是不支持的,也可以依靠 execute() 方法进行命令的执行。
观察 RedisTemplate 类
java
package org.springframework.data.redis.core;
@NullUnmarked
public class RedisTemplate<K, V>
extends RedisAccessor
implements RedisOperations<K, V>, BeanClassLoaderAware {/** 忽略其他代码 */}
RedisAccessor 是 Spring Data Redis 中的一个基础抽象类。
作用: 它内部持有一个 RedisConnectionFactory(Redis 连接工厂)的引用,并提供了 getConnection() 等获取底层 Redis 连接的基础方法。
意义: RedisTemplate(同步)和 ReactiveRedisTemplate(响应式)都需要与 Redis 建立连接。将连接管理的公共逻辑抽取到 RedisAccessor 父类中,避免了代码重复,符合单一职责原则。RedisTemplate 只需要专注于实现 RedisOperations 定义的业务逻辑即可。
java
package org.springframework.data.redis.core;
@NullUnmarked
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
private boolean enableTransactionSupport = false;
private boolean exposeConnection = false;
private boolean initialized = false;
private boolean enableDefaultSerializer = true;
private @Nullable ClassLoader classLoader;
/** 支持自定义序列化,有key, string,hash, 等等*/
private @Nullable RedisSerializer<?> defaultSerializer;
private @Nullable RedisSerializer keySerializer = null;
private @Nullable RedisSerializer valueSerializer = null;
private @Nullable RedisSerializer hashKeySerializer = null;
private @Nullable RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
private @Nullable ScriptExecutor<K> scriptExecutor;
private final BoundOperationsProxyFactory boundOperations = new BoundOperationsProxyFactory();
// 对 Redis 各数据类型做一层简化包装
private final ValueOperations<K, V> valueOps = new DefaultValueOperations(this);
private final ListOperations<K, V> listOps = new DefaultListOperations(this);
private final SetOperations<K, V> setOps = new DefaultSetOperations(this);
private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations(this, ObjectHashMapper.getSharedInstance());
private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations(this);
private final GeoOperations<K, V> geoOps = new DefaultGeoOperations(this);
private final HashOperations<K, ?, ?> hashOps = new DefaultHashOperations(this);
private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations(this);
private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations(this);
/** 忽略其他代码 */
}
以下是 RedisTemplate 中各类 Operations 接口与 Redis 数据类型、命令及业务场景的对应关系表:
| Operations 接口 | 对应 Redis 数据类型 | 常用 Redis 命令 |
|---|---|---|
ValueOperations |
String (字符串) | SET, GET, INCR, DECR, SETEX, APPEND |
ListOperations |
List (列表) | LPUSH, RPUSH, LPOP, RPOP, LRANGE, LLEN |
SetOperations |
Set (集合) | SADD, SREM, SMEMBERS, SINTER(交), SUNION(并), SDIFF(差) |
HashOperations |
Hash (哈希) | HSET, HGET, HMSET, HMGET, HGETALL, HDEL, HINCRBY |
ZSetOperations |
ZSet (有序集合) | ZADD, ZREM, ZRANGE, ZRANGEBYSCORE, ZSCORE, ZINCRBY |
GeoOperations |
GEO (地理位置) | GEOADD, GEODIST(距离), GEOPOS(坐标), GEOSEARCH(范围搜索) |
StreamOperations |
Stream (消息流) | XADD(追加), XREAD(读取), XRANGE, XGROUP(消费者组), XACK |
HyperLogLogOperations |
HyperLogLog (基数统计) | PFADD(添加), PFCOUNT(统计), PFMERGE(合并) |
ClusterOperations |
非数据类型 (集群管理) | CLUSTER INFO, CLUSTER NODES, CLUSTER KEYSLOT |
💡 核心设计总结:
Spring Data Redis 通过这种门面模式(Facade) 的设计,将 Redis 原生的、略显杂乱的命令,按照数据结构 进行了完美的分类封装。 开发者在编写代码时,只需调用 redisTemplate.opsForXxx(),即可享受类型安全(泛型支持)和底层客户端无关(Lettuce/Jedis 无缝切换) 的便利。
从代码上就可以看到 RedisTemplate 对数据类型的命令做了一层包装,并且支持自定义序列化操作。

3.2 配置 RedisTemplate 类
接下来配置一下 RedisTemplate 相关配置类。配置文件信息:
yml
toast.data:
redis:
host: redis-server
port: 6379
username: default
password: ToastR#16
database: 0
pool:
max-total: 200 # 最大可用连接
min-idle: 30 # 空闲时连接最大维持的连接数
test-on-borrow: true # 连接测试后再返回给调用处
配置类
java
package com.toast.redis.lettuce.config;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @author toast
* @time 2026/7/9
* @remark
*/
@Configuration
@PropertySource("classpath:redis-config.yml")
public class SpringDataRedisConfig {
@Bean // Redis 连接配置
public RedisStandaloneConfiguration redisStandaloneConfiguration(
@Value("${toast.redis.host}") String host,
@Value("${toast.redis.port}") int port,
@Value("${toast.redis.username}") String username,
@Value("${toast.redis.password}") String password,
@Value("${toast.redis.database}") int database
) {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(host);
config.setPort(port);
config.setPassword(username);
config.setPassword(RedisPassword.of(password));
config.setDatabase(database);
return config;
}
@Bean // 配置连接池
public GenericObjectPoolConfig genericObjectPoolConfig(
@Value("${toast.redis.pool.max-total}") int maxTotal,
@Value("${toast.redis.pool.min-idle}") int minIdle,
@Value("${toast.redis.pool.test-on-borrow}") boolean testOnBorrow) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMinIdle(minIdle);
poolConfig.setTestOnBorrow(testOnBorrow);
return poolConfig;
}
@Bean // 使用 Lettuce 客户端组件
public LettuceClientConfiguration lettuceClientConfiguration(@Autowired GenericObjectPoolConfig poolConfig) {
return LettucePoolingClientConfiguration.builder()
.poolConfig(poolConfig)
.build(); // Lettuce 连接池定义
}
@Bean // 定义Lettuce 连接工厂
public LettuceConnectionFactory lettuceConnectionFactory(
@Autowired RedisStandaloneConfiguration redisStandaloneConfiguration,
@Autowired LettuceClientConfiguration lettuceClientConfiguration
) {
return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
}
@Bean
public RedisTemplate<String, Object> testRedisTemplate(
@Autowired LettuceConnectionFactory connectionFactory
) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 追加 RedisTemplate 之中的序列化
// string数据类型 key 采用字符串序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
// string数据类型 value 采用 JDK序列化
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
// hash 数据类型 key 采用字符串序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// hash 数据类型 value 采用JSON序列化
redisTemplate.setHashValueSerializer(new JacksonJsonRedisSerializer<Object>(Object.class));
return redisTemplate;
}
}
3.3 RedisTemplate 基本使用
基础数据操作
java
@Service
public class RedisBasicService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void basicSetAndGet() {
String key = "user:1001";
User user = new User("Toast", 25); // 假设这是一个自定义对象
// 【设置数据】并指定过期时间为 10 分钟
redisTemplate.opsForValue().set(key, user, 10, TimeUnit.MINUTES);
// 【获取数据】
Object cachedUser = redisTemplate.opsForValue().get(key);
System.out.println("获取到的对象: " + cachedUser);
// 如果存的是普通字符串
redisTemplate.opsForValue().set("msg:hello", "Hello Redis");
String msg = (String) redisTemplate.opsForValue().get("msg:hello");
}
}
Key 本身 的元数据或生命周期命令
Redis 除了对数据类型有命令操作。这些操作不针对具体的数据结构(String/Hash等),而是针对 Key 本身 的元数据或生命周期。这个时候并不会使用到 getOpsXxx() 数据类型系列方法
java
public void keyLevelOperations() {
String key = "user:1001";
// 【设置过期时间】
redisTemplate.expire(key, 30, TimeUnit.SECONDS);
// 【获取剩余过期时间】(返回秒数)
Long ttl = redisTemplate.getExpire(key);
System.out.println("剩余过期时间: " + ttl + " 秒");
// 【判断 Key 是否存在】
Boolean hasKey = redisTemplate.hasKey(key);
// 【获取 Key 的数据类型】(返回 STRING, LIST, HASH 等)
DataType type = redisTemplate.type(key);
// 【重命名 Key】
redisTemplate.rename(key, "user:1001_new");
// 【删除 Key】
redisTemplate.delete("user:1001_new");
}
执行数据类型以外的底层/Server 命令 (核心需求)
执行 Spring 已封装的 Server 命令 (如 PING, INFO)
java
public void executeServerCommands() {
// 【案例 A:执行 PING 命令测试连通性】
String pingResult = redisTemplate.execute((RedisCallback<String>) connection -> {
// connection 是 Spring 封装的 RedisConnection 接口,跨客户端兼容
return connection.ping();
});
System.out.println("PING 结果: " + pingResult); // 输出 PONG
// 【案例 B:执行 INFO 命令获取服务器运行信息】
Properties serverInfo = redisTemplate.execute((RedisCallback<Properties>) connection -> {
// 调用 serverCommands() 获取服务器级别的命令集
return connection.serverCommands().info();
});
System.out.println("Redis 版本: " + serverInfo.getProperty("redis_version"));
// 【案例 C:清空当前数据库 (危险操作,慎用)】
redisTemplate.execute((RedisCallback<String>) connection -> {
connection.serverCommands().flushDb();
return "OK";
});
}
当需要多个命令原子性执行时,Lua 是最佳选择。
java
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
public void executeLuaScript() {
// Lua 脚本:如果 key 不存在则设置,并返回 1;存在则返回 0 (类似 SETNX)
String luaScript =
"if redis.call('exists', KEYS[1]) == 0 then " +
" redis.call('set', KEYS[1], ARGV[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScript);
redisScript.setResultType(Long.class);
// 执行脚本:传入 key 列表和参数列表
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList("lua_lock_key"), // KEYS[1]
"my_lock_value" // ARGV[1]
);
System.out.println("Lua 脚本执行结果: " + result);
}
四,RedisTemplate 的 Pipeline 命令流水线
在 Redis 的高性能优化中,Pipeline(命令流水线) 是最核心、最常用的手段之一。
简单来说,传统的 Redis 操作是 "一问一答" 的阻塞模式,而 Pipeline 则是 "批量打包,集中处理" 的流水线模式。
下面我为你详细拆解 Pipeline 的原理、在 RedisTemplate 中的使用方法以及避坑指南。
4.1 为什么需要 Pipeline?(核心原理)
传统模式的痛点:RTT(往返时间)延迟
假设你要向 Redis 写入 1000 个 Key。 在普通模式下,客户端和 Redis 服务端需要进行 1000 次网络交互:
客户端发 SET k1 v1 ➔ 等待网络传输 ➔ 服务端执行 ➔ 返回 OK ➔ 等待网络传输 ➔ 客户端发 SET k2 v2 ➔ ...
假设每次网络往返时间(RTT)是 1 毫秒,1000 次操作光在网络传输上就要耗费 1000 毫秒(1秒) ,而 Redis 服务端执行这些命令可能只需要几毫秒。网络延迟成为了最大的瓶颈。
Pipeline 的魔法:批量打包
Pipeline 允许客户端将多个命令打包成一个批次 发送给服务端,服务端收到后在内存中依次执行,最后将所有的结果打包成一个批次返回给客户端。
客户端发 [SET k1 v1, SET k2 v2, ... SET k1000 v1000] ➔ 等待网络传输 ➔ 服务端连续执行 ➔ 返回 [OK, OK, ... OK]
1000 次操作,只需要 1 次网络往返! 耗时从 1000ms 骤降到 1ms 左右,性能提升可达 数十倍甚至上百倍。
4.2 在 RedisTemplate 中使用 Pipeline
在 Spring Data Redis 中,使用 Pipeline 需要调用 executePipelined 方法,并传入一个 RedisCallback。
案例 1:批量写入(MSET 的升级版)
当你需要批量写入大量数据,且 Key 的命名规则不统一,无法使用简单的 MSET 时,Pipeline 是最佳选择。
java
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RedisPipelineService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisPipelineService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void batchSetUsers() {
// 模拟 1000 个用户数据
List<User> users = generateMockUsers(1000);
// 使用 executePipelined 开启流水线
List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
// 1. 获取序列化器(将 Java 对象转为字节或 JSON)
// 注意:如果你配置了 GenericJackson2JsonRedisSerializer,这里会自动使用
org.springframework.data.redis.serializer.RedisSerializer<String> stringSerializer =
(org.springframework.data.redis.serializer.RedisSerializer<String>) redisTemplate.getKeySerializer();
org.springframework.data.redis.serializer.RedisSerializer<Object> valueSerializer =
redisTemplate.getValueSerializer();
// 2. 在循环中发送命令。这些命令不会立即执行,而是被放入 Pipeline 缓冲区
for (User user : users) {
byte[] key = stringSerializer.serialize("user:" + user.getId());
byte[] value = valueSerializer.serialize(user);
// 底层调用 Lettuce/Jedis 的 set 命令,直接放入流水线
connection.set(key, value);
}
// 3. 【关键】doInRedis 方法必须返回 null!
// 因为 Pipeline 的结果是由 executePipelined 统一返回的,这里返回任何值都会被忽略。
return null;
}
});
// results 列表包含了上面所有命令的执行结果(例如 1000 个 "OK" 或 true)
System.out.println("Pipeline 执行完毕,返回结果数量: " + results.size());
}
}
案例 2:批量读取(解决 MGET 只能处理 String 的问题)
如果你存的是对象(使用了 JSON 序列化),MGET 取出来的是 JSON 字符串,还需要手动反序列化。而使用 Pipeline 配合 getValueSerializer(),Spring 会自动帮你反序列化回 Java 对象。
java
public List<Object> batchGetUsers(List<String> userIds) {
return redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
org.springframework.data.redis.serializer.RedisSerializer<String> stringSerializer =
(org.springframework.data.redis.serializer.RedisSerializer<String>) redisTemplate.getKeySerializer();
for (String userId : userIds) {
byte[] key = stringSerializer.serialize("user:" + userId);
// 将 GET 命令放入流水线
connection.get(key);
}
return null;
}
});
// 返回的 List<Object> 中,每个元素都已经自动反序列化成了 User 对象(如果你配置了 JSON 序列化器)
}
4.3 ⚠️ Pipeline 的避坑指南(非常重要)
在使用 Pipeline 时,有几个极易踩坑的地方,结合你使用的 Lettuce 客户端和 Spring Boot 3.4 环境,请务必注意:
1. RedisCallback 的返回值必须是 null
在 doInRedis 方法内部,你发送的所有命令都会被拦截并放入 Pipeline 缓冲区。如果你在方法最后 return "hello",这个 "hello" 会被直接丢弃。真正的结果全在 executePipelined 返回的 List<Object> 里。
2. 结果顺序严格对应
executePipelined 返回的 List<Object> 中,元素的顺序与你在 doInRedis 中发送命令的顺序严格一致 。 例如:你依次发了 SET k1, GET k2, SET k3,返回的 List 就是 [SET的结果, GET的结果, SET的结果]。
3. 序列化器的类型转换问题
如果你使用了 GenericJackson2JsonRedisSerializer,Pipeline 返回的 List<Object> 中,读取出来的对象通常已经是反序列化好的 Java 对象了。 但如果你使用的是默认的 JdkSerializationRedisSerializer,返回的可能是 byte[],你需要手动反序列化,或者在配置中统一修改序列化器。
4. 不要一次性打包太多命令(内存炸弹)
虽然 Pipeline 很快,但千万不要一次性把几万个命令打包发过去。
- 客户端内存暴涨:Lettuce 需要在内存中构建巨大的命令缓冲区。
- 服务端阻塞:Redis 是单线程执行命令的,如果一次性收到几万个命令,会长时间阻塞其他客户端的请求,导致整个 Redis 集群响应变慢。
- 建议 :通常建议一次 Pipeline 打包 500 ~ 2000 条命令。如果数据量更大,请在代码中进行分批处理(例如每 1000 条执行一次 Pipeline)。
5. Pipeline 与 事务(Multi/Exec)的区别
- Pipeline 只是为了减少网络 RTT,它不保证原子性。如果其中一条命令执行失败(比如类型错误),其他命令依然会执行。
- 事务(
redisTemplate.execute()结合connection.multi()/connection.exec()) 保证原子性,要么全成功,要么全失败。 - 注意 :在 Spring Data Redis 中,Pipeline 和 事务不能混用。你不能在 Pipeline 里面开启事务,也不能在事务里面使用 Pipeline。
4.4 总结对比
| 特性 | 普通循环操作 | Pipeline (流水线) | Transaction (事务) |
|---|---|---|---|
| 网络交互次数 | N 次 (极慢) | 1 次 (极快) | 2 次 (MULTI + EXEC) |
| 原子性 | 无 | 无 | 有 (要么全成功要么全失败) |
| 适用场景 | 极少量的简单操作 | 批量导入/导出、批量更新 | 需要保证多个操作同时成功的场景 (如扣减库存+生成订单) |
| 底层实现 | 逐条发送 | 客户端缓冲区打包发送 | 服务端标记事务块 |