redis提供了三种客户端用于java开发,分别是jedis,lettuce和Redisson

jedis客户端

Jedis是Redis官方推荐的Java客户端,提供了简洁易用的API与Redis服务端进行交互,支持连接池、事务、管道等所有Redis功能。

基础数据操作

字符串:set/get/incr/decr/append 等

哈希:hset/hget/hgetAll/hincrBy 等

列表:lpush/rpush/lpop/rpop/lrange 等

集合:sadd/smembers/sinter/sunion 等

有序集合:zadd/zrange/zrevrange/zscore 等

事务操作

JRedis 事务通过 MULTI、EXEC、DISCARD 和 WATCH 等命令实现,可以将多个命令打包按顺序执行,保证原子性(要么全部执行,要么全部不执行),同时可以通过WATCH指令实现乐观锁功能。

事务执行流程

1、multi() - 开始事务,返回 Transaction 对象
2、调用 Transaction 对象的方法将命令加入队列
3、exec() - 执行事务,返回包含所有命令结果的 List
4、如果执行失败,可以调用 discard() 取消事务

try {
Transaction t = jedis.multi();
t.set(“k1”, “v1”);
t.incr(“k1”); // 这个命令会在执行时出错
t.set(“k2”, “v2”);
t.exec(); // 虽然 incr 会失败,但其他命令仍会执行
} catch (Exception e) {
t.discard(); // 出现异常时放弃事务
}

管道

Jedis 的管道(Pipeline)功能是一种高性能的批量操作机制,可以显著提升与 Redis 服务器的交互效率。

管道功能

1、一次性发送多个命令到服务器

2、不需要等待每个命令的响应

3、最后一次性读取所有响应

4、减少网络往返时间(RTT),大幅提升性能

执行流程

1、pipelined() - 创建管道对象

2、调用 Pipeline 对象的方法将命令加入队列

3、sync() - 执行所有命令但不返回结果

4、或 syncAndReturnAll() - 执行并返回所有命令的结果

5、或 close() - 执行所有命令并关闭管道

Jedis jedis = new Jedis(“localhost”);

// 创建管道
Pipeline pipeline = jedis.pipelined();

// 批量发送命令(不立即执行)
pipeline.set(“key1”, “value1”);
pipeline.set(“key2”, “value2”);
pipeline.get(“key1”);
pipeline.get(“key2”);

// 执行并获取所有响应
List results = pipeline.syncAndReturnAll();

// 处理结果
for (Object result : results) {
System.out.println(result);
}

连接池

Jedis 实例本身是非线程安全的。如果多个线程共享同一个 Jedis 实例,可能导致命令交替执行(如命令 A 的请求和命令 B 的响应混淆),从而引发线程安全问题。

通过使用 Jedis 连接池(如 JedisPool)可以解决线程安全问题,每个线程从池中获取独立的 Jedis 实例,避免多线程竞争。同时也能提升性能,复用连接减少创建/销毁开销,同时通过参数(如 maxTotal、maxIdle)控制资源使用。

工作流程

在这里插入图片描述

相关配置

参数 默认值 说明
maxTotal 8 最大连接数
maxIdle 8 最大空闲连接
minIdle 0 最小空闲连接
maxWaitMillis -1(无限等待) 获取连接超时时间
testOnBorrow false 借出时测试连接
testOnReturn false 归还时测试连接
testWhileIdle false 空闲时测试连接
timeBetweenEvictionRuns -1 空闲检测间隔

同步执行

Jedis 的同步执行是指客户端发送命令后阻塞等待Redis服务器返回结果的模式,这是Jedis最基础也是最常用的执行方式。

执行步骤

1、客户端发送命令到Redis服务器。

2、线程阻塞等待响应。

3、收到响应后继续执行后续代码。

管道方式同步执行

虽然管道主要用于批量操作,但同步模式下仍然需要等待所有命令执行完毕:

Jedis jedis = new Jedis(“localhost”);

// 创建管道
Pipeline p = jedis.pipelined();

// 批量加入命令
for (int i = 0; i < 1000; i++) {
p.set(“pkey:” + i, “value” + i);
p.expire(“pkey:” + i, 3600);
}

// 同步执行并获取所有结果
List results = p.syncAndReturnAll();
System.out.println(“共执行 " + results.size() / 2 + " 个SET+EXPIRE操作”);

jedis.close();

连接池方式同步执行

向连接池借一个线程,利用借来的线程,顺序的发送指令。

// 创建连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(30);

// 初始化连接池
JedisPool jedisPool = new JedisPool(poolConfig, “localhost”);

try (Jedis jedis = jedisPool.getResource()) {
// 同步操作
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
jedis.set(“pool🔑” + i, “val” + i);
}
long duration = System.currentTimeMillis() - start;
System.out.println("同步设置1000个key耗时: " + duration + “ms”);
}

jedisPool.close();


异步执行

Redis 的异步执行模式可以显著提升应用程序的吞吐量,特别是在高并发场景下,Jedis 客户端本身不直接提供异步API,需要一些处理。

异步执行步骤

在这里插入图片描述

线程池+CompletableFuture 模式实现

使用java的异步执行工具类CompletableFuture 来实现redis的异步执行。

public CompletableFuture getAsync(JedisPool pool, String key) {
return CompletableFuture.supplyAsync(() -> {
try (Jedis jedis = pool.getResource()) {
return jedis.get(key);
}
}, redisExecutor);
}

// 链式调用示例
getAsync(jedisPool, “user:1001”)
.thenApply(userJson -> objectMapper.readValue(userJson, User.class))
.thenAccept(user -> System.out.println("获取用户: " + user.getName()))
.exceptionally(ex -> {
System.err.println("查询失败: " + ex.getMessage());
return null;
});

lettuce

Lettuce 是一个高性能、可扩展的 Redis Java 客户端,基于 Netty 实现,支持 异步、同步、响应式 编程模型。

流程访问

1、命令提交按序进入命令队列

2、netty循环按序读取命令队列的命令。

3、netty发送命令到服务器中。

4、redis响应返回,netty回调相关线程。

从访问流程可以得出,lettuce默认操作方式是异步操作,且指令是按序执行,所以线程是安全的。

在这里插入图片描述

异步

Lettuce 的异步操作基于 RedisAsyncCommands 接口,所有操作都返回 RedisFuture 对象。

基本异步调用

RedisAsyncCommands<String, String> asyncCommands = client.connect().async();

RedisFuture future = asyncCommands.get(“user:1001”);

// 阻塞获取结果(不推荐,失去异步优势)
String value = future.get();

// 非阻塞处理
future.thenAccept(result -> {
System.out.println("获取的值: " + result);
});

组合多个异步操作

RedisFuture future1 = asyncCommands.get(“user:1001:name”);
RedisFuture future2 = asyncCommands.get(“user:1001:email”);

CompletableFuture.allOf(future1, future2.toCompletableFuture())
.thenRun(() -> {
try {
System.out.println("Name: " + future1.get());
System.out.println("Email: " + future2.get());
} catch (Exception e) {
e.printStackTrace();
}
});

异常处理

asyncCommands.get(“invalid-key”)
.thenAccept(System.out::println)
.exceptionally(throwable -> {
System.err.println("发生错误: " + throwable.getMessage());
return null;
});

同步

Lettuce客户端默认采用异步非阻塞式操作,通过回调函数处理响应,这是其高性能设计的基础。同时,Lettuce也提供了同步操作API,其实现原理是在异步操作基础上通过Future.get()方法进行线程阻塞等待(即使用阻塞命令),底层本质还是异步操作。

创建连接

// 创建连接
RedisClient client = RedisClient.create(“redis://localhost”);
StatefulRedisConnection<String, String> connection = client.connect();

// 获取同步命令接口
RedisCommands<String, String> syncCommands = connection.sync();

同步操作

// 字符串操作
syncCommands.set(“key”, “value”);
String value = syncCommands.get(“key”);

// 哈希操作
syncCommands.hset(“user:1000”, “name”, “Alice”);
String name = syncCommands.hget(“user:1000”, “name”);

// 列表操作
syncCommands.lpush(“tasks”, “task1”, “task2”);
List tasks = syncCommands.lrange(“tasks”, 0, -1);

管道

lettuce也可以使用管道实现批量命令的传输,提高命令

管道实现同步执行

管道的同步执行和异步差不多,区别在于获取结果的时候,是阻塞获取。

RedisCommands<String, String> syncCommands = client.connect().sync();

// 开启管道
syncCommands.setAutoFlushCommands(false);

// 批量添加命令
syncCommands.set(“key1”, “value1”);
syncCommands.get(“key1”);
syncCommands.set(“key2”, “value2”);

// 一次性发送所有命令并获取结果
syncCommands.flushCommands();

List results = syncCommands.get(“key1”);

管道实现异步执行

RedisAsyncCommands<String, String> asyncCommands = client.connect().async();

// 创建批处理对象
AsyncCommandBatch batch = new AsyncCommandBatch();

// 添加多个命令
batch.add(asyncCommands.set(“pipe-key1”, “value1”));
batch.add(asyncCommands.set(“pipe-key2”, “value2”));
batch.add(asyncCommands.get(“pipe-key1”));

// 执行批处理并获取结果
batch.complete().thenAccept(results -> {
// results 是按命令添加顺序返回的结果列表
for (Object result : results) {
System.out.println("结果: " + result);
}
});

连接池

Lettuce客户端默认采用单连接模式,该模式通过多生产者单消费者(MPSC)队列实现线程安全,能够高效处理大多数高并发场景。然而,在执行阻塞式Redis命令(如BLPOP、BRPOP或长时间运行的Lua脚本)时,单连接模式会因命令队列的串行执行特性导致后续请求的等待时间累积性增加。此时,使用连接池模式(多连接)可通过物理连接隔离阻塞操作,从而显著提升系统整体吞吐量。

单连接模式

1、只有一个命令队列MPSC处理命令。

2、保证现场安全,单个EventLoop线程从队列取出命令处理

在这里插入图片描述

连接池模式

1、连接池有用多个命令队列

2、每个连接池绑定一个netty的eventloop线程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Redisson

Redisson 是一个基于 Redis 的、线程安全的 Java 客户端,专为简化分布式系统开发而设计。它通过提供丰富的分布式数据结构(如锁、集合、队列等)和同步器,高效解决了分布式环境下的数据一致性和线程安全问题。其底层基于高性能的 Netty 框架实现非阻塞 I/O 通信,并内置连接池管理,显著提升了通信效率和系统吞吐量,是构建高可用、高性能分布式应用的理想工具。

架构图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一致性

Redisson 是一个基于 Java 的 Redis 客户端,专为分布式系统设计。其在保障数据一致性方面采用了多层次策略:在单个节点上依赖 Redis 的原子命令和 Lua 脚本;在主从节点间依托于 Redis 的异步复制机制实现最终一致性;并在跨多个主节点的集群环境下,通过 RedLock 等分布式算法来应对网络分区和节点故障,从而在不同层面确保数据的安全与一致。

Lua 脚本 - 保证单节点原子性

这是 Redisson 最重要、最常用的机制,用于保证在单个 Redis 节点上多个指令的原子性。

原理:将多个 Redis 命令编写在一个 Lua 脚本中。Redis 服务器会单线程、原子性地执行整个 Lua 脚本。在执行期间,不会被任何其他命令打断,脚本内的所有命令要么全部成功,要么全部失败。

如何实现“同时成功”:脚本执行后返回一个结果。如果成功,代表所有命令都成功了;如果失败(比如语法错误、参数错误),则所有命令都不生效。

示例:

if redis.call(“get”, KEYS[1]) == ARGV[1] then
return redis.call(“del”, KEYS[1])
else
return 0
end

Redis 事务 - 保证单节点批量执行的原子性

对于需要批量执行一系列命令,且不依赖于中间结果的场景,可以使用事务。

原理:使用 MULTI 开启一个事务,后续的命令被放入队列。使用 EXEC 原子性地执行队列中的所有命令。在 EXEC 命令执行前,所有命令都不会实际运行。

与Lua的区别:

事务中的命令可能会个别失败(例如对字符串执行 HINCRBY),但其他命令会继续执行。Redis事务不支持回滚。

Lua脚本是全部成功或全部失败(如果遇到错误会终止)。

如何实现“同时成功”:EXEC 命令返回一个数组,包含队列中每个命令的执行结果。客户端可以检查这个数组来判断每个命令是否成功。

redlock锁保证集群操作的一致性

RedLock 是一个基于多数派投票、旨在容忍部分基础设施故障的分布式锁算法。它通过让客户端与多个独立的Redis主节点交互,并在大多数节点上成功获取锁来定义“成功”,从而避免了单点或主从复制架构下的锁失效问题。它是一种用操作复杂性和延迟来换取更高可靠性的设计选择。

redlock算法流程图

在这里插入图片描述

redlock应用场景举例

假设公司有一栋楼,这栋楼一次只能由一个人进入,里面有多个重要的房间(节点1到节点4),每个房间里都有一个保险箱。

RedLock 锁:就像是进入这栋大楼的通用门禁卡。

您的业务操作:就像是去操作每个房间里的保险箱。

操作:

获取门禁卡(加锁):
去向4个保安(节点1-4)申请门禁卡。

保安1、2、3同意了,给了您卡。

保安4今天请假了,没联系上(节点4失败)。

结果:因为超过半数的保安(3/4)同意了,您成功获得了门禁卡。您现在有权进入大楼。

执行任务(业务操作):
拿着门禁卡进入大楼。

先去了房间1,成功地用密码打开了保险箱,完成了操作。

然后您走到房间4门口,发现房间4的门坏了,根本打不开!(节点4无法写入)。

结果:您的保险箱操作失败了。

总结

从上述机制可以看出,RedLock 的本质是一个分布式互斥(Mutual Exclusion)原语。它的核心职责是保证在分布式环境下,同一时间只有一个客户端能获得对共享资源集群的操作权限,从而进入临界区(Critical Section)。

至于在临界区内对集群中各个节点的具体操作(如数据写入、更新)是否都能成功,以及操作失败后如何保证数据一致性(如回滚或补偿),则完全由应用层(开发人员)逻辑负责。RedLock 为此提供了一个安全的执行环境,但并不关心和保障其内部业务操作的结果。

主从节点一致性

Redis 主从复制默认采用异步复制机制,其设计目标是优先保证主节点的高性能和低延迟,为此牺牲了强一致性,转而追求最终一致性。

1、当客户端向主节点(Master)写入数据(例如执行 SET、HSET 命令)并成功后:

2、主节点立即响应:主节点会立刻将成功结果返回给客户端,此时客户端认为写入已完成。

3、异步传播:随后,主节点会异步地将引发数据变化的命令(或是在混合持久化模式下的部分数据)记录到其复制缓冲区(Replication Buffer) 中。

4、从节点拉取日志:各个从节点(Slave)会通过定时任务持续地从主节点的复制缓冲区拉取这些命令日志。

5、从节点重放命令:从节点在本地重放(Replay)这些命令,从而使自身状态最终与主节点保持一致。

因此,在主节点响应客户端的那一刻,数据可能尚未到达任何从节点。这意味着从节点在一定时间窗口内会包含旧数据,但最终(只要网络连接稳定)会变得与主节点一致。

同步器

Redisson 同步器的核心思想是:将 Java 并发包(java.util.concurrent,简称 JUC)中那些强大的线程同步工具(如信号量、闭锁等),通过 Redis 实现其分布式版本,让它们能在跨 JVM、跨服务器的分布式环境中工作。

RCountDownLatch (分布式闭锁)

允许一个或多个线程等待其他线程完成操作。

RCountDownLatch latch = redisson.getCountDownLatch(“myLatch”);
latch.trySetCount(3); // 需要完成3个任务

// 在分布式节点A上
latch.countDown();

// 在分布式节点B上
latch.countDown();

// 在协调者节点C上
latch.await(); // 会阻塞,直到第三个 countDown() 被调用
System.out.println(“All tasks completed!”);

RSemaphore (分布式信号量)

用于控制同时访问特定资源的线程(或分布式客户端)数量,如限制同时发送短信的客户端、同时生成报表的任务等。

RSemaphore semaphore = redisson.getSemaphore(“mySemaphore”);
semaphore.trySetPermits(10); // 设置总许可数为10

// 在任何客户端中
semaphore.acquire(); // 获取一个许可,总量变为9
try {
// 执行受保护的资源访问操作,例如调用一个只能接受10个并发请求的API
} finally {
semaphore.release(); // 操作完成,释放许可
}

RPermitExpirableSemaphore (可过期许可信号量)

这是 RSemaphore 的增强版。每个获取到的许可都会关联一个租赁时间(lease time) 和一个唯一的许可ID(permit ID)。如果一个客户端获取了许可但之后崩溃了,没有释放许可,会导致许可永久丢失(死锁)。可过期信号量通过租赁机制,即使客户端崩溃,许可也会在超时后自动释放,从而避免死锁。

acquire(leaseTime, timeUnit):获取一个许可,并指定租赁时间。

tryAcquire(waitTime, leaseTime, timeUnit):尝试获取。

release(permitId):通过特定的许可ID来释放许可。

RReadWriteLock (分布式读写锁)

实现读写分离锁。允许多个读锁同时持有,但写锁是独占的,读-写互斥,写-写互斥,读-读不互斥。

RReadWriteLock rwLock = redisson.getReadWriteLock(“myRWLock”);
RLock readLock = rwLock.readLock();
RLock writeLock = rwLock.writeLock();

// 读操作
readLock.lock();
try {
// … 多个客户端可以同时进入此代码块 …
} finally {
readLock.unlock();
}

// 写操作
writeLock.lock();
try {
// … 同一时间只有一个客户端可以进入 …
} finally {
writeLock.unlock();
}


连接池

**借出连接:**当需要执行命令时,业务线程会从连接池“借用”一个物理TCP连接。如果池中有空闲连接则直接取出;如果没有且未达上限,则创建新连接;如果已达上限,则等待。

**命令执行:**借到的连接被用于发送Redis命令。这个连接在Netty中对应一个Channel。

**响应处理:**Netty的I/O线程异步地处理网络读写、编解码。响应返回后,结果会传递给业务线程。

**归还连接:**命令执行完毕后,物理连接并不会关闭,而是被“归还”到连接池中,供其他业务线程下次使用。

254bf259c35a4acfef76a1183f468398

异步操作

异步操作是 Redisson 的底层基础和核心,底层全异步,所有命令都会立即返回一个 RFuture 对象,不会阻塞调用线程,可以通过回调函数的方式处理异步执行的结果。

异步执行:

// 异步获取一个字符串对象
RBucket bucket = redisson.getBucket(“myKey”);
// 调用getAsync(),立即返回一个RFuture,而不是字符串本身
RFuture future = bucket.getAsync();

// 异步设置值
RFuture setFuture = bucket.setAsync(“myValue”);

// 异步获取锁
RLock lock = redisson.getLock(“myLock”);
RFuture lockFuture = lock.lockAsync();

异步结果处理:

//使用监听器的方式,在redis返回的时候回调函数,实现真正的异步处理。

future.onComplete((result, exception) -> {
if (exception != null) {
// 处理失败异常
exception.printStackTrace();
} else {
// 处理成功结果
System.out.println("Async result: " + result);
}
});

同步操作

Redisson底层虽然是异步执行,但是在某些业务场景中,我们仍需要同步操作,Reddisson提供的同步操作是基于异步封装,具体的实现是调用阻塞方法获得数据。

// 同步获取值 - 线程会在此阻塞,直到从Redis拿到结果
String value = bucket.get();

// 同步设置值 - 线程阻塞,直到收到Redis的’OK’响应
bucket.set(“myValue”);

// 同步加锁 - 线程阻塞,直到成功获取到锁或超时
lock.lock();
try {
// 在锁内执行操作
} finally {
lock.unlock();
}

总结

特性 Jedis Lettuce Redisson
定位 轻量级、阻塞式、直连型客户端 高性能、非阻塞、响应式客户端 分布式、基于对象的、中间件式客户端
网络模型 BIO (阻塞 I/O),连接基于连接池 NIO (非阻塞 I/O),基于 Netty 的事件驱动 NIO (非阻塞 I/O),基于 Netty 的事件驱动
抽象层次 低层级,对 Redis 命令的简单映射 中层级,提供友好的同步/异步/响应式 API 高层级,提供分布式对象和服务
线程安全 连接不安全,需靠连接池实现 连接安全,单个连接可共享于多个线程 连接安全,所有操作都是线程安全的
主要API 基本与 Redis 命令一一对应 同步 (sync)、异步 (async)、响应式 (reactive) 分布式对象 (RList, RMap)、锁 (RLock)、服务
集群支持 支持,但需要手动管理 优秀,自动管理连接和路由 优秀,自动管理连接和路由,并提供高级集群工具
事务 支持 MULTI/EXEC 支持 MULTI/EXEC,并提供函数式 API 支持,并提供分布式事务的抽象
发布订阅 支持,但需要独占连接 优秀,基于事件监听模型,连接可复用 优秀,提供强大的 Topic 和 Pattern Topic 支持
适用场景 简单应用、命令行式操作、学习成本低 高并发、低延迟应用(如微服务)、Spring 生态、响应式编程 分布式系统、需要分布式锁、队列、集合等高级功能
Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐