Redisson 分布式集合详解:像用本地集合一样操作跨服务共享数据
一、为什么需要分布式集合
1.1 本地集合的局限
在单机应用中,你可以用 HashMap、ArrayList、HashSet 存储数据,同一个 JVM 内的所有线程都能访问。
但在微服务架构中,你的应用部署了多个实例(如3台服务器)。每台服务器有自己独立的内存,本地集合无法跨实例共享。
服务器A 的 HashMap: {"user-1": "张三", "user-2": "李四"}
服务器B 的 HashMap: {} ← 看不到服务器A的数据!
服务器C 的 HashMap: {} ← 也看不到!
1.2 分布式集合的作用
Redisson 的分布式集合把数据存在 Redis 中,所有服务实例都能读写同一份数据:
Redis: {"user-1": "张三", "user-2": "李四"}
│
├── 服务器A 通过 RMap 读写 → 能看到所有数据
├── 服务器B 通过 RMap 读写 → 能看到所有数据
└── 服务器C 通过 RMap 读写 → 能看到所有数据
1.3 和直接用 RedisTemplate 的区别
java
// RedisTemplate:需要手动序列化、指定 Redis 命令
redisTemplate.opsForHash().put("user-cache", "user-1", jsonString);
String json = (String) redisTemplate.opsForHash()
.get("user-cache", "user-1");
UserInfo user = JSON.parseObject(json, UserInfo.class);
// Redisson RMap:像本地 HashMap 一样使用,自动序列化
RMap<String, UserInfo> userCache = redissonClient.getMap("user-cache");
userCache.put("user-1", userInfo); // 自动序列化
UserInfo user = userCache.get("user-1"); // 自动反序列化
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
二、RMap:分布式 Map
2.1 基本用法
java
@Service
public class UserCacheServiceImpl implements UserCacheService {
@Resource
private RedissonClient redissonClient;
/**
* 获取用户缓存Map.
*/
private RMap<Integer, UserInfo> getUserCacheMap() {
return redissonClient.getMap("user-cache");
}
/**
* 缓存用户信息.
*/
public void cacheUser(Integer userId, UserInfo userInfo) {
RMap<Integer, UserInfo> cache = getUserCacheMap();
cache.put(userId, userInfo);
}
/**
* 获取用户信息.
*/
public UserInfo getUser(Integer userId) {
RMap<Integer, UserInfo> cache = getUserCacheMap();
return cache.get(userId);
}
/**
* 删除用户缓存.
*/
public void removeUser(Integer userId) {
RMap<Integer, UserInfo> cache = getUserCacheMap();
cache.remove(userId);
}
/**
* 获取所有缓存的用户ID.
*/
public Set<Integer> listAllCachedUserIds() {
RMap<Integer, UserInfo> cache = getUserCacheMap();
return cache.keySet();
}
}
2.2 带过期时间的 Map(RMapCache)
普通 RMap 的数据不会过期。如果需要每个 entry 有独立的过期时间,使用 RMapCache:
java
/**
* 验证码缓存服务.
* 每个验证码5分钟后自动过期.
*/
@Service
public class VerifyCodeServiceImpl implements VerifyCodeService {
@Resource
private RedissonClient redissonClient;
/**
* 存储验证码(5分钟过期).
*/
public void saveCode(String phone, String code) {
RMapCache<String, String> codeCache =
redissonClient.getMapCache("verify-code-cache");
// 每个 entry 独立过期:5分钟后自动删除
codeCache.put(phone, code, 5, TimeUnit.MINUTES);
}
/**
* 验证验证码.
*/
public boolean verifyCode(String phone, String inputCode) {
RMapCache<String, String> codeCache =
redissonClient.getMapCache("verify-code-cache");
String savedCode = codeCache.get(phone);
if (savedCode == null) {
return false; // 已过期或不存在
}
return savedCode.equals(inputCode);
}
}
2.3 RMap 常用方法对照
| Java HashMap | Redisson RMap | 说明 |
|---|---|---|
put(key, value) |
put(key, value) |
存入键值对 |
get(key) |
get(key) |
根据 key 获取 value |
remove(key) |
remove(key) |
删除键值对 |
containsKey(key) |
containsKey(key) |
判断 key 是否存在 |
size() |
size() |
获取元素数量 |
keySet() |
keySet() |
获取所有 key |
values() |
values() |
获取所有 value |
putIfAbsent(key, value) |
putIfAbsent(key, value) |
不存在时才存入 |
| --- | put(key, value, ttl, unit) |
存入并设置过期时间(RMapCache) |
三、RSet:分布式 Set
3.1 基本用法
java
/**
* 用户签到服务.
* 使用 Set 记录每天签到的用户(天然去重).
*/
@Service
public class CheckInServiceImpl implements CheckInService {
@Resource
private RedissonClient redissonClient;
/**
* 用户签到.
*/
public boolean checkIn(Integer userId) {
String today = LocalDate.now().toString(); // 如 "2026-05-25"
RSet<Integer> checkedUsers =
redissonClient.getSet("check-in-" + today);
// add 返回 true 表示新增成功(之前没签到过)
// 返回 false 表示已存在(今天已经签到过了)
return checkedUsers.add(userId);
}
/**
* 查询用户今天是否已签到.
*/
public boolean hasCheckedIn(Integer userId) {
String today = LocalDate.now().toString();
RSet<Integer> checkedUsers =
redissonClient.getSet("check-in-" + today);
return checkedUsers.contains(userId);
}
/**
* 获取今天签到人数.
*/
public int getTodayCheckInCount() {
String today = LocalDate.now().toString();
RSet<Integer> checkedUsers =
redissonClient.getSet("check-in-" + today);
return checkedUsers.size();
}
}
3.2 Set 的集合运算
java
// 昨天签到的用户
RSet<Integer> yesterday = redissonClient.getSet("check-in-2026-05-24");
// 今天签到的用户
RSet<Integer> today = redissonClient.getSet("check-in-2026-05-25");
// 交集:连续两天都签到的用户
Set<Integer> continuousUsers =
yesterday.readIntersection("check-in-2026-05-25");
// 差集:昨天签到但今天没签到的用户(流失用户)
Set<Integer> lostUsers =
yesterday.readDiff("check-in-2026-05-25");
// 并集:两天内签到过的所有用户
Set<Integer> allUsers =
yesterday.readUnion("check-in-2026-05-25");
四、RList:分布式 List
4.1 基本用法
java
/**
* 消息记录服务.
* 使用 List 存储用户的最近消息(有序,可重复).
*/
@Service
public class MessageServiceImpl implements MessageService {
@Resource
private RedissonClient redissonClient;
/**
* 添加消息到用户的消息列表.
*/
public void addMessage(Integer userId, String message) {
RList<String> messages =
redissonClient.getList("messages-" + userId);
messages.add(message);
}
/**
* 获取用户最近的N条消息.
*/
public List<String> getRecentMessages(Integer userId, int count) {
RList<String> messages =
redissonClient.getList("messages-" + userId);
int size = messages.size();
if (size == 0) {
return Collections.emptyList();
}
// 取最后 count 条
int fromIndex = Math.max(0, size - count);
return messages.subList(fromIndex, size);
}
/**
* 获取消息总数.
*/
public int getMessageCount(Integer userId) {
RList<String> messages =
redissonClient.getList("messages-" + userId);
return messages.size();
}
}
4.2 RList vs RQueue 的选择
| 场景 | 选择 | 原因 |
|---|---|---|
| 需要按索引访问 | RList | 支持 get(index)、subList |
| 先进先出消费 | RQueue | 专为队列设计,poll() 取出并删除 |
| 需要阻塞等待 | RBlockingQueue | 队列为空时阻塞等待新元素 |
五、RQueue / RBlockingQueue:分布式队列
5.1 普通队列
java
/**
* 任务队列服务.
*/
@Service
public class TaskQueueServiceImpl implements TaskQueueService {
@Resource
private RedissonClient redissonClient;
/**
* 提交任务到队列.
*/
public void submitTask(TaskDto task) {
RQueue<TaskDto> queue = redissonClient.getQueue("task-queue");
queue.offer(task); // 放入队列尾部
}
/**
* 从队列取出一个任务(非阻塞,队列为空返回null).
*/
public TaskDto pollTask() {
RQueue<TaskDto> queue = redissonClient.getQueue("task-queue");
return queue.poll(); // 取出并删除队首元素
}
/**
* 查看队首任务(不删除).
*/
public TaskDto peekTask() {
RQueue<TaskDto> queue = redissonClient.getQueue("task-queue");
return queue.peek();
}
}
5.2 阻塞队列(生产者-消费者模式)
java
/**
* 订单处理消费者.
* 使用阻塞队列实现生产者-消费者模式.
*/
@Service
public class OrderConsumerServiceImpl implements OrderConsumerService {
@Resource
private RedissonClient redissonClient;
/**
* 生产者:提交订单到队列.
*/
public void submitOrder(OrderDto order) {
RBlockingQueue<OrderDto> queue =
redissonClient.getBlockingQueue("order-queue");
queue.offer(order);
}
/**
* 消费者:阻塞等待并处理订单.
* 通常在单独的线程或定时任务中运行.
*/
public void consumeOrders() {
RBlockingQueue<OrderDto> queue =
redissonClient.getBlockingQueue("order-queue");
while (!Thread.currentThread().isInterrupted()) {
try {
// 阻塞等待:队列为空时会一直等,直到有新元素
OrderDto order = queue.take();
processOrder(order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private void processOrder(OrderDto order) {
log.info("处理订单: {}", order.getOrderCode());
// 业务逻辑...
}
}
5.3 延迟队列(RDelayedQueue)
java
/**
* 延迟任务服务.
* 例如:订单30分钟未支付自动取消.
*/
@Service
public class DelayTaskServiceImpl implements DelayTaskService {
@Resource
private RedissonClient redissonClient;
/**
* 添加延迟任务.
* @param task 任务内容
* @param delay 延迟时间
* @param unit 时间单位
*/
public void addDelayTask(TaskDto task, long delay, TimeUnit unit) {
RBlockingQueue<TaskDto> blockingQueue =
redissonClient.getBlockingQueue("delay-task-queue");
RDelayedQueue<TaskDto> delayedQueue =
redissonClient.getDelayedQueue(blockingQueue);
// 30分钟后任务才会出现在 blockingQueue 中
delayedQueue.offer(task, delay, unit);
}
/**
* 消费延迟任务.
*/
public void consumeDelayTasks() {
RBlockingQueue<TaskDto> queue =
redissonClient.getBlockingQueue("delay-task-queue");
while (!Thread.currentThread().isInterrupted()) {
try {
// 只有到期的任务才会被取出
TaskDto task = queue.take();
handleExpiredTask(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private void handleExpiredTask(TaskDto task) {
log.info("处理到期任务: {}", task.getTaskId());
// 例如:取消未支付订单
}
}
六、RScoredSortedSet:分布式有序集合
6.1 基本用法
java
/**
* 排行榜服务.
* 使用有序集合实现分数排行.
*/
@Service
public class LeaderboardServiceImpl implements LeaderboardService {
@Resource
private RedissonClient redissonClient;
/**
* 更新用户分数.
*/
public void updateScore(Integer userId, double score) {
RScoredSortedSet<Integer> leaderboard =
redissonClient.getScoredSortedSet("game-leaderboard");
leaderboard.addScore(userId, score);
}
/**
* 获取用户排名(从0开始).
*/
public Integer getUserRank(Integer userId) {
RScoredSortedSet<Integer> leaderboard =
redissonClient.getScoredSortedSet("game-leaderboard");
// 按分数从高到低排名
return leaderboard.revRank(userId);
}
/**
* 获取前N名.
*/
public Collection<Integer> getTopUsers(int count) {
RScoredSortedSet<Integer> leaderboard =
redissonClient.getScoredSortedSet("game-leaderboard");
// 按分数从高到低取前 count 名
return leaderboard.valueRangeReversed(0, count - 1);
}
/**
* 获取用户分数.
*/
public Double getUserScore(Integer userId) {
RScoredSortedSet<Integer> leaderboard =
redissonClient.getScoredSortedSet("game-leaderboard");
return leaderboard.getScore(userId);
}
}
七、完整实战示例:在线用户管理
7.1 场景描述
一个即时通讯系统,需要:
- 记录当前在线的用户(去重)
- 记录用户的在线状态信息(Map)
- 用户上线/下线时更新状态
7.2 完整代码
java
/**
* 在线用户管理服务.
*/
@Service
public class OnlineUserServiceImpl implements OnlineUserService {
@Resource
private RedissonClient redissonClient;
private static final String ONLINE_USERS_KEY = "online-users";
private static final String USER_STATUS_KEY = "user-status";
/**
* 用户上线.
*/
public void userOnline(Integer userId, String deviceType) {
// 1. 添加到在线用户集合
RSet<Integer> onlineUsers =
redissonClient.getSet(ONLINE_USERS_KEY);
onlineUsers.add(userId);
// 2. 记录用户状态信息
RMapCache<Integer, UserStatusDto> statusMap =
redissonClient.getMapCache(USER_STATUS_KEY);
UserStatusDto status = new UserStatusDto();
status.setUserId(userId);
status.setDeviceType(deviceType);
status.setOnlineTime(new Date());
// 状态信息2小时后过期(心跳续期)
statusMap.put(userId, status, 2, TimeUnit.HOURS);
log.info("用户上线, userId:{}, device:{}", userId, deviceType);
}
/**
* 用户下线.
*/
public void userOffline(Integer userId) {
// 1. 从在线集合移除
RSet<Integer> onlineUsers =
redissonClient.getSet(ONLINE_USERS_KEY);
onlineUsers.remove(userId);
// 2. 删除状态信息
RMapCache<Integer, UserStatusDto> statusMap =
redissonClient.getMapCache(USER_STATUS_KEY);
statusMap.remove(userId);
log.info("用户下线, userId:{}", userId);
}
/**
* 心跳续期(客户端定期调用).
*/
public void heartbeat(Integer userId) {
RMapCache<Integer, UserStatusDto> statusMap =
redissonClient.getMapCache(USER_STATUS_KEY);
UserStatusDto status = statusMap.get(userId);
if (status != null) {
// 续期2小时
statusMap.put(userId, status, 2, TimeUnit.HOURS);
}
}
/**
* 判断用户是否在线.
*/
public boolean isOnline(Integer userId) {
RSet<Integer> onlineUsers =
redissonClient.getSet(ONLINE_USERS_KEY);
return onlineUsers.contains(userId);
}
/**
* 获取在线用户数.
*/
public int getOnlineCount() {
RSet<Integer> onlineUsers =
redissonClient.getSet(ONLINE_USERS_KEY);
return onlineUsers.size();
}
/**
* 获取用户状态信息.
*/
public UserStatusDto getUserStatus(Integer userId) {
RMapCache<Integer, UserStatusDto> statusMap =
redissonClient.getMapCache(USER_STATUS_KEY);
return statusMap.get(userId);
}
/**
* 批量判断用户是否在线.
*/
public Map<Integer, Boolean> batchCheckOnline(List<Integer> userIds) {
RSet<Integer> onlineUsers =
redissonClient.getSet(ONLINE_USERS_KEY);
Map<Integer, Boolean> result = new HashMap<>();
for (Integer userId : userIds) {
result.put(userId, onlineUsers.contains(userId));
}
return result;
}
}
八、性能注意事项
8.1 大集合的遍历
java
// 危险:如果 Map 有100万条数据,一次性加载到内存
RMap<String, String> bigMap = redissonClient.getMap("big-map");
Set<String> allKeys = bigMap.keySet(); // 可能OOM!
// 安全:使用迭代器,分批加载
RMap<String, String> bigMap = redissonClient.getMap("big-map");
for (String key : bigMap.keySet(10)) { // 每次加载10条
String value = bigMap.get(key);
// 处理...
}
8.2 批量操作
java
// 低效:逐个操作,每次一次网络往返
for (int i = 0; i < 1000; i++) {
map.put("key-" + i, "value-" + i); // 1000次网络请求
}
// 高效:批量操作,一次网络往返
Map<String, String> batch = new HashMap<>();
for (int i = 0; i < 1000; i++) {
batch.put("key-" + i, "value-" + i);
}
map.putAll(batch); // 1次网络请求
8.3 本地缓存加速(RLocalCachedMap)
如果数据读多写少,可以使用带本地缓存的 Map,减少 Redis 访问:
java
// 带本地缓存的 Map:读取时优先从本地内存获取
LocalCachedMapOptions<String, String> options =
LocalCachedMapOptions.<String, String>defaults()
.evictionPolicy(EvictionPolicy.LRU)
.cacheSize(1000)
.timeToLive(5, TimeUnit.MINUTES);
RLocalCachedMap<String, String> cachedMap =
redissonClient.getLocalCachedMap("config-cache", options);
// 第一次读:从 Redis 获取,同时缓存到本地
String value = cachedMap.get("key"); // 访问 Redis
// 第二次读:直接从本地内存获取,不访问 Redis
String value2 = cachedMap.get("key"); // 本地内存,零延迟
九、集合类型选择指南
| 需求 | 选择 | 原因 |
|---|---|---|
| 键值对存储/缓存 | RMap / RMapCache | 类似 HashMap,支持过期 |
| 去重集合 | RSet | 天然去重,支持集合运算 |
| 有序列表 | RList | 按插入顺序,支持索引访问 |
| 分数排行 | RScoredSortedSet | 按分数排序,支持排名查询 |
| 先进先出队列 | RQueue | 生产者放入,消费者取出 |
| 阻塞消费队列 | RBlockingQueue | 队列为空时阻塞等待 |
| 延迟任务 | RDelayedQueue | 指定延迟后才可消费 |
| 读多写少的缓存 | RLocalCachedMap | 本地缓存加速,减少网络IO |
十、总结
| 问题 | 答案 |
|---|---|
| 分布式集合解决什么问题? | 多个服务实例共享同一份数据 |
| 和 RedisTemplate 的区别? | 面向对象API,自动序列化,使用更简单 |
| 数据存在哪里? | Redis 中,所有实例通过网络访问 |
| 性能如何? | 每次操作有网络延迟(1-3ms),批量操作可优化 |
| 数据会丢失吗? | 取决于 Redis 的持久化配置(AOF/RDB) |
| 适合存大量数据吗? | 适合热数据缓存,不适合替代数据库 |