Skip to content

Commit 09b9780

Browse files
authored
feat(session): a unified Redis session client that supports switching between underlying clients (#611)
## AgentScope-Java Version 1.0.8-SNAPSHOT ## Description releted to #610 ### Background The current JedisSeesion uses JedisPool, which only supports standalone scenarios. In production environments, users mostly use more complex deployment methods, such as sentinels, clusters, and master-slave setups. Furthermore, in a Java environment, users may use different Redis client implementations, such as `Jedis`, `Lettuce`, and `Redisson`. If each is used independently, scaling up with different Redis client implementations would result in `a lot of duplicate code`. Moreover, naming conventions like JedisSeesion are unclear, as they are all essentially types of Redis clients. ### New Feature - **Unified the RedisSession client**: using the adapter pattern to implement different underlying Redis clients, and consolidated the duplicate code that was originally scattered across various client implementations into the RedisSession. - **Various Redis deployment modes**: Jedis, Lettuce, Redisson adopt more modern APIs and support various deployment modes such as standalone, sentinel, cluster, and master-slave. - **README**: Add a README introduction for the `agentscope-extensions-session-redis` module The newly added codes all have complete javadoc documentation and the `mvn test` has passed. ### Modify - Added lettuce dependency - Add the "deprecated" tag to the old versions of JedisSession and RedissonSession, and update the corresponding javadoc. ### New Usage #### Jedis Usage Examples - Jedis Standalone (using RedisClient): ```java // Create Jedis RedisClient (new API) RedisClient redisClient = RedisClient.create("redis://localhost:6379"); // Build RedisSession Session session = RedisSession.builder() .jedisClient(redisClient) .build(); ``` - Jedis Cluster (using RedisClusterClient) ```java // Create Jedis RedisClusterClient Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("localhost", 7000)); nodes.add(new HostAndPort("localhost", 7001)); nodes.add(new HostAndPort("localhost", 7002)); RedisClusterClient redisClusterClient = RedisClusterClient.create(nodes); // Build RedisSession Session session = RedisSession.builder() .jedisClient(redisClusterClient) .build(); ``` - Jedis Sentinel (using RedisSentinelClient) ```java // Create Jedis RedisSentinelClient Set<String> sentinelNodes = new HashSet<>(); sentinelNodes.add("localhost:26379"); sentinelNodes.add("localhost:26380"); RedisSentinelClient redisSentinelClient = RedisSentinelClient.create("mymaster", sentinelNodes); // Build RedisSession Session session = RedisSession.builder() .jedisClient(redisSentinelClient) .build(); ``` #### Lettuce Usage Examples - Lettuce Standalone ```java // Create Lettuce RedisClient RedisClient redisClient = RedisClient.create("redis://localhost:6379"); // Build RedisSession Session session = RedisSession.builder() .lettuceClient(redisClient) .build(); ``` - Lettuce Cluster ```java // Create Lettuce RedisClient for cluster RedisURI clusterUri = RedisURI.create("redis://localhost:7000"); RedisClient redisClient = RedisClient.create(clusterUri); // Build RedisSession Session session = RedisSession.builder() .lettuceClient(redisClient) .build(); ``` - Lettuce Sentinel ```java // Create Lettuce RedisClient for sentinel RedisURI sentinelUri = RedisURI.builder() .withSentinelMasterId("mymaster") .withSentinel("localhost", 26379) .withSentinel("localhost", 26380) .build(); RedisClient redisClient = RedisClient.create(sentinelUri); // Build RedisSession Session session = RedisSession.builder() .lettuceClient(redisClient) .build(); ``` #### Redisson Usage Example ```java // Create RedissonClient (configure as needed for your deployment mode) Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); // or for cluster: config.useClusterServers().addNodeAddress("redis://localhost:7000"); // or for sentinel: config.useSentinelServers().setMasterName("mymaster").addSentinelAddress("redis://localhost:26379"); RedissonClient redissonClient = Redisson.create(config); // Build RedisSession Session session = RedisSession.builder() .redissonClient(redissonClient) .build(); ``` ## Checklist Please check the following items before code is ready to be reviewed. - [x] Code has been formatted with `mvn spotless:apply` - [x] All tests are passing (`mvn test`) - [x] Javadoc comments are complete and follow project conventions - [x] Related documentation has been updated (e.g. links, examples, etc.) - [x] Code is ready for review
1 parent a163540 commit 09b9780

14 files changed

Lines changed: 2473 additions & 135 deletions

File tree

agentscope-dependencies-bom/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
<a2a-server-common.version>0.3.3.Final</a2a-server-common.version>
100100
<a2a-transport-jsonrpc.version>0.3.3.Final</a2a-transport-jsonrpc.version>
101101
<jedis.version>7.2.1</jedis.version>
102+
<lettuce.version>6.4.2.RELEASE</lettuce.version>
102103
<xxl-job.version>3.3.2</xxl-job.version>
103104
<quartz.version>2.5.2</quartz.version>
104105
<spring.version>7.0.5</spring.version>
@@ -378,6 +379,13 @@
378379
<version>${jedis.version}</version>
379380
</dependency>
380381

382+
<!-- Lettuce client for Redis-based session implementation -->
383+
<dependency>
384+
<groupId>io.lettuce</groupId>
385+
<artifactId>lettuce-core</artifactId>
386+
<version>${lettuce.version}</version>
387+
</dependency>
388+
381389
<!-- XXL-JOB Core -->
382390
<dependency>
383391
<groupId>com.xuxueli</groupId>
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# Redis Session Extension
2+
3+
Redis-based session implementation for AgentScope supporting multiple Redis clients.
4+
5+
## Overview
6+
7+
This extension provides a Redis-based session storage implementation for AgentScope, supporting multiple Redis client libraries and deployment modes.
8+
9+
## Supported Redis Clients
10+
11+
- **Jedis** - Standalone, Cluster, Sentinel
12+
- **Lettuce** - Standalone, Cluster, Sentinel
13+
- **Redisson** - Standalone, Cluster, Sentinel, Master/Slave
14+
15+
## Maven Dependency
16+
17+
```xml
18+
<dependency>
19+
<groupId>io.agentscope</groupId>
20+
<artifactId>agentscope-extensions-session-redis</artifactId>
21+
<version>${agentscope.version}</version>
22+
</dependency>
23+
```
24+
25+
## Usage Examples
26+
27+
### Jedis
28+
29+
#### Standalone
30+
31+
```java
32+
import redis.clients.jedis.RedisClient;
33+
import io.agentscope.core.session.redis.RedisSession;
34+
35+
// Create Jedis RedisClient
36+
RedisClient redisClient = RedisClient.create("redis://localhost:6379");
37+
38+
// Build RedisSession
39+
Session session = RedisSession.builder()
40+
.jedisClient(redisClient)
41+
.build();
42+
```
43+
44+
#### Cluster
45+
46+
```java
47+
import redis.clients.jedis.RedisClusterClient;
48+
import redis.clients.jedis.HostAndPort;
49+
import io.agentscope.core.session.redis.RedisSession;
50+
51+
// Create Jedis RedisClusterClient
52+
Set<HostAndPort> nodes = new HashSet<>();
53+
nodes.add(new HostAndPort("localhost", 7000));
54+
nodes.add(new HostAndPort("localhost", 7001));
55+
nodes.add(new HostAndPort("localhost", 7002));
56+
RedisClusterClient clusterClient = RedisClusterClient.create(nodes);
57+
58+
// Build RedisSession
59+
Session session = RedisSession.builder()
60+
.jedisClient(clusterClient)
61+
.build();
62+
```
63+
64+
#### Sentinel
65+
66+
```java
67+
import redis.clients.jedis.RedisSentinelClient;
68+
import io.agentscope.core.session.redis.RedisSession;
69+
70+
// Create Jedis RedisSentinelClient
71+
Set<String> sentinelNodes = new HashSet<>();
72+
sentinelNodes.add("localhost:26379");
73+
sentinelNodes.add("localhost:26380");
74+
RedisSentinelClient sentinelClient = RedisSentinelClient.create("mymaster", sentinelNodes);
75+
76+
// Build RedisSession
77+
Session session = RedisSession.builder()
78+
.jedisClient(sentinelClient)
79+
.build();
80+
```
81+
82+
### Lettuce
83+
84+
#### Standalone
85+
86+
```java
87+
import io.lettuce.core.RedisClient;
88+
import io.agentscope.core.session.redis.RedisSession;
89+
90+
// Create Lettuce RedisClient
91+
RedisClient redisClient = RedisClient.create("redis://localhost:6379");
92+
93+
// Build RedisSession
94+
Session session = RedisSession.builder()
95+
.lettuceClient(redisClient)
96+
.build();
97+
```
98+
99+
#### Cluster
100+
101+
```java
102+
import io.lettuce.core.RedisClient;
103+
import io.lettuce.core.RedisURI;
104+
import io.agentscope.core.session.redis.RedisSession;
105+
106+
// Create Lettuce RedisClusterClient for cluster
107+
RedisClusterClient clusterClient = RedisClusterClient.create("redis://localhost:7000");
108+
109+
// Build RedisSession
110+
Session session = RedisSession.builder()
111+
.lettuceClusterClient(clusterClient)
112+
.build();
113+
```
114+
115+
#### Sentinel
116+
117+
```java
118+
import io.lettuce.core.RedisClient;
119+
import io.lettuce.core.RedisURI;
120+
import io.agentscope.core.session.redis.RedisSession;
121+
122+
// Create Lettuce RedisClient for sentinel
123+
RedisURI sentinelUri = RedisURI.builder()
124+
.withSentinelMasterId("mymaster")
125+
.withSentinel("localhost", 26379)
126+
.withSentinel("localhost", 26380)
127+
.build();
128+
RedisClient redisClient = RedisClient.create(sentinelUri);
129+
130+
// Build RedisSession
131+
Session session = RedisSession.builder()
132+
.lettuceClient(redisClient)
133+
.build();
134+
```
135+
136+
### Redisson
137+
138+
```java
139+
import org.redisson.api.RedissonClient;
140+
import org.redisson.config.Config;
141+
import io.agentscope.core.session.redis.RedisSession;
142+
143+
// Create RedissonClient
144+
Config config = new Config();
145+
config.useSingleServer().setAddress("redis://localhost:6379");
146+
// cluster: config.useClusterServers().addNodeAddress("redis://localhost:7000");
147+
// sentinel: config.useSentinelServers().setMasterName("mymaster").addSentinelAddress("redis://localhost:26379");
148+
149+
RedissonClient redissonClient = Redisson.create(config);
150+
151+
// Build RedisSession
152+
Session session = RedisSession.builder()
153+
.redissonClient(redissonClient)
154+
.build();
155+
```
156+
157+
### Custom Key Prefix
158+
159+
```java
160+
import redis.clients.jedis.RedisClient;
161+
import io.agentscope.core.session.redis.RedisSession;
162+
163+
// Create Redis client
164+
RedisClient redisClient = RedisClient.create("redis://localhost:6379");
165+
166+
// Build RedisSession with custom key prefix
167+
Session session = RedisSession.builder()
168+
.jedisClient(redisClient)
169+
.keyPrefix("myapp:session:")
170+
.build();
171+
```
172+
173+
## Key Structure
174+
175+
The session state is stored in Redis with following key structure:
176+
177+
- Single state: `{prefix}{sessionId}:{stateKey}` - Redis String containing JSON
178+
- List state: `{prefix}{sessionId}:{stateKey}:list` - Redis List containing JSON items
179+
- List hash: `{prefix}{sessionId}:{stateKey}:list:_hash` - Hash for change detection
180+
- Session marker: `{prefix}{sessionId}:_keys` - Redis Set tracking all state keys
181+
182+
## Core Functionality
183+
184+
### Save Single
185+
186+
```java
187+
SessionKey sessionKey = SimpleSessionKey.of("session1");
188+
TestState state = new TestState("value", 42);
189+
190+
session.save(sessionKey, "testModule", state);
191+
```
192+
193+
### Get Single
194+
195+
```java
196+
SessionKey sessionKey = SimpleSessionKey.of("session1");
197+
Optional<TestState> state = session.get(sessionKey, "testModule", TestState.class);
198+
```
199+
200+
### Save List
201+
202+
```java
203+
SessionKey sessionKey = SimpleSessionKey.of("session1");
204+
List<TestState> states = List.of(
205+
new TestState("value1", 1),
206+
new TestState("value2", 2)
207+
);
208+
209+
session.save(sessionKey, "testList", states);
210+
```
211+
212+
### Get List
213+
214+
```java
215+
SessionKey sessionKey = SimpleSessionKey.of("session1");
216+
List<TestState> states = session.getList(sessionKey, "testList", TestState.class);
217+
```
218+
219+
### Check Session Existence
220+
221+
```java
222+
SessionKey sessionKey = SimpleSessionKey.of("session1");
223+
boolean exists = session.exists(sessionKey);
224+
```
225+
226+
### Delete Session
227+
228+
```java
229+
SessionKey sessionKey = SimpleSessionKey.of("session1");
230+
session.delete(sessionKey);
231+
```
232+
233+
### List All Sessions
234+
235+
```java
236+
Set<SessionKey> sessionKeys = session.listSessionKeys();
237+
```
238+
239+
### Clear All Sessions
240+
241+
```java
242+
Mono<Integer> deletedCount = session.clearAllSessions();
243+
long count = deletedCount.block();
244+
```

0 commit comments

Comments
 (0)