Redis 数据驱动库的供应

一,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)
原子性 (要么全成功要么全失败)
适用场景 极少量的简单操作 批量导入/导出、批量更新 需要保证多个操作同时成功的场景 (如扣减库存+生成订单)
底层实现 逐条发送 客户端缓冲区打包发送 服务端标记事务块
相关推荐
刘小八19 小时前
Redis 缓存一致性:一文讲透延时双删原理、并发时序
java·数据库·redis·缓存
数行拙笔20 小时前
Redis---zset类型
数据库·redis·缓存
宠友信息1 天前
消息序号如何保证即时通讯源码聊天记录稳定加载
java·spring boot·redis·python·mysql·uni-app
hold?fish:palm1 天前
RDB全量快照备份
c++·redis·后端
爬也要爬着前进2 天前
redis哨兵模式搭建
数据库·redis·bootstrap
万亿少女的梦1682 天前
基于微信小程序、Spring Boot与Vue3的智慧养老管理系统设计与实现
spring boot·redis·微信小程序·mybatis·vue3
蚰蜒螟2 天前
从 Jedis 到内核:深入剖析 Redis BRPOP 的阻塞与唤醒机制
数据库·redis·缓存
糖果店的幽灵2 天前
【langgraph 从入门到精通graphApi 篇】节点与边 —— 图的骨架
java·数据库·人工智能·redis·langgraph
后端观测站2 天前
第八篇:Redis 为什么不会每次修改都写磁盘?
数据库·redis·缓存