Redisson 分布式集合详解:像用本地集合一样操作跨服务共享数据

Redisson 分布式集合详解:像用本地集合一样操作跨服务共享数据

一、为什么需要分布式集合

1.1 本地集合的局限

在单机应用中,你可以用 HashMapArrayListHashSet 存储数据,同一个 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)
适合存大量数据吗? 适合热数据缓存,不适合替代数据库
相关推荐
phltxy14 小时前
RabbitMQ高级特性-消息确认与持久性博客
分布式·rabbitmq·ruby
2603_9547083114 小时前
协调控制柜在微电网中的核心地位:数据枢纽、控制核心、安全屏障
分布式·安全·架构·能源·需求分析
淡漠的蓝精灵15 小时前
Pulsar 入门:云原生分布式消息流平台
分布式·其他·云原生
ai生成式引擎优化技术16 小时前
DLOS Kernel v1.0:面向分布式AI任务执行与Agent调度的统一运行时内核
人工智能·分布式
ai生成式引擎优化技术17 小时前
DLOS v0.7:面向分布式多智能体AI操作系统的自进化内核
人工智能·分布式
未若君雅裁17 小时前
RabbitMQ 消息可靠性:生产者确认、持久化、消费者ACK与幂等消费
分布式·微服务·rabbitmq
数据库小学妹17 小时前
分布式数据库架构演进:从集中式到分布式,三大路线一次讲清楚
数据库·分布式·数据库架构
juniperhan17 小时前
Flink 系列第25篇:Flink SQL 集成 Hive 实践:流批一体下的实时数仓利器
大数据·数据仓库·hive·分布式·sql·flink
ai生成式引擎优化技术17 小时前
DLOS Kernel v1.0 = 分布式AI任务执行 + Agent调度 + Memory系统 + Event驱动 的统一运行时内核
人工智能·分布式