Redisson完全指南:分布式系统的瑞士军刀

一、为什么需要 Redisson

1.1 一个让人头疼的场景

想象一下:你的电商系统部署了 3 个实例,用户下单扣库存的请求被负载均衡分发到不同实例上:

┌──────────┐     ┌──────────┐     ┌──────────┐
│  实例 A   │     │  实例 B   │     │  实例 C   │
│  读库存=1 │     │  读库存=1 │     │          │
│  扣减→0  │     │  扣减→0  │     │          │
└─────┬────┘     └─────┬────┘     └──────────┘
      │                │
      └────────┬───────┘
               ↓
        ┌──────────────┐
        │   MySQL 数据库  │
        │ 库存被扣成了 -1 │  ← 超卖!
        └──────────────┘

两个实例同时读到库存为 1,各自扣减,最终库存变成 -1。这就是经典的分布式并发问题

本地锁(synchronizedReentrantLock)只能保护单个 JVM 内的互斥,跨进程就失效了。你需要的是一把「全局锁」——分布式锁

1.2 自己用 Redis 实现分布式锁有多难?

很多人觉得用 Redis 的 SET key value NX EX 30 就够了。但生产环境下,你需要处理的问题远不止这些:

问题 说明
死锁 客户端获取锁后崩溃,锁永远不会释放
误删 客户端 A 的锁过期了,客户端 B 获取锁后,A 又把 B 的锁删了
续期 业务执行时间不确定,锁的过期时间设长了浪费、短了不安全
重入 同一线程嵌套调用,自己把自己锁住了
重试 获取锁失败要不要重试?怎么重试?等多久?
网络异常 Redis 连接断了怎么办?命令执行超时怎么办?
// ❌ 一个"能用"但有隐患的手写实现
String nodeId = UUID.randomUUID().toString();
jedis.set("lock:key", nodeId, "NX", "EX", 30);
// ... 业务执行 ...
// 释放锁时还要用 Lua 脚本保证原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList("lock:key"), Collections.singletonList(nodeId));

这还只是最基础的场景,公平锁、读写锁、联锁的实现复杂度更是指数级增长。

1.3 Redisson:开箱即用的分布式工具箱

Redisson 是一个基于 Redis 的 Java 驻内存数据网格(In-Memory Data Grid)客户端。简单说,它帮你把上面那些头疼的问题全部封装好了

// ✅ 同样的场景,用 Redisson 只需 3 行
RLock lock = redissonClient.getLock("lock:key");
lock.lock();       // 自动重试 + 自动续期 + 防死锁
try {
    deductStock();  // 专注业务逻辑
} finally {
    lock.unlock();  // 只删自己的锁
}

核心价值

  1. 简化开发:分布式锁、信号量、集合等复杂逻辑一行代码搞定
  2. 生产级可靠:自动处理续期、重试、网络异常等边界情况
  3. 功能丰富:提供锁、限流器、集合、队列等 20+ 分布式对象
  4. 集群友好:无缝支持 Redis 单节点、哨兵、集群模式

1.4 Redisson vs 其他方案

方案 优点 缺点 适用场景
Redis SET NX 简单、无依赖 需要自己处理续期、重入、误删等问题 非常简单的临时场景
Redisson 功能全面、生产级可靠 依赖 Redis、学习成本略高 大多数分布式场景
ZooKeeper/Curator 强一致性、支持临时节点 性能较低、运维复杂 强一致性要求高的场景
etcd 强一致性、Go 生态友好 Java 客户端不如 Redisson 成熟 云原生/K8s 生态
数据库乐观锁 无额外依赖 性能差、不适合高并发 低并发简单场景

选型建议:如果你的系统已经使用了 Redis(大多数 Java 项目都是),Redisson 是分布式协调的最优选。只有在对一致性有极端要求(如金融核心交易)时才考虑 ZooKeeper。


二、快速上手

2.1 添加依赖

Maven

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.45.0</version>
</dependency>

Gradle

implementation 'org.redisson:redisson-spring-boot-starter:3.45.0'

版本选择:建议使用 3.x 最新稳定版。3.45+ 基于 Netty 4.1.x,支持 Java 8+,与 Spring Boot 2.x/3.x 均兼容。如果使用 Spring Boot 3.x,需确认 Redisson 版本 ≥ 3.25.0。

2.2 配置方式

方式一:YAML 配置文件(推荐)

创建 src/main/resources/redisson.yml

# 单节点模式
singleServerConfig:
  address: "redis://localhost:6379"
  password: null
  database: 0
  connectionPoolSize: 24
  connectionMinimumIdleSize: 8
  idleConnectionTimeout: 10000
  connectTimeout: 5000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500

# 线程配置
threads: 16
nettyThreads: 32
# 传输模式(默认 NIO,Linux 下可改为 EPOLL 获得更佳性能)
# transportMode: "EPOLL"

application.yml 中引用:

spring:
  redis:
    redisson:
      file: classpath:redisson.yml

方式二:Spring Boot 配置(最简洁)

直接在 application.yml 中配置:

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password:
      database: 0

# Redisson 自动复用上面的 Redis 配置

方式三:编程式配置(最灵活)

@Configuration
public class RedissonConfig {

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://localhost:6379")
                .setPassword(null)
                .setDatabase(0)
                .setConnectionPoolSize(24)
                .setConnectionMinimumIdleSize(8)
                .setConnectTimeout(5000)
                .setTimeout(3000)
                .setRetryAttempts(3)
                .setRetryInterval(1500);

        config.setThreads(16)
                .setNettyThreads(32);

        return Redisson.create(config);
    }
}

2.3 哨兵模式配置

# redisson.yml
sentinelServersConfig:
  masterName: "mymaster"
  sentinelAddresses:
    - "redis://sentinel1:26379"
    - "redis://sentinel2:26379"
    - "redis://sentinel3:26379"
  password: "your-password"
  database: 0

2.4 集群模式配置

# redisson.yml
clusterServersConfig:
  nodeAddresses:
    - "redis://node1:6379"
    - "redis://node2:6379"
    - "redis://node3:6379"
    - "redis://node4:6379"
    - "redis://node5:6379"
    - "redis://node6:6379"
  password: "your-password"
  scanInterval: 2000    # 集群状态扫描间隔(毫秒)

2.5 Hello World

@Autowired
private RedissonClient redissonClient;

public void hello() {
    // 分布式锁
    RLock lock = redissonClient.getLock("myLock");
    lock.lock();
    try {
        System.out.println("Hello, Redisson!");
    } finally {
        lock.unlock();
    }
}

三、分布式锁(核心重点)

分布式锁是 Redisson 最重要的功能,也是大多数团队引入 Redisson 的首要原因。本章将从原理到实践,全面解析。

3.1 为什么分布式锁不像本地锁那么简单

本地锁(如 synchronized)依赖 JVM 进程内的共享内存,跨进程就失效了。分布式锁必须依赖外部存储来协调多个进程。

Redis 天然适合做分布式锁的底层存储:

  • 单线程执行:Redis 命令是原子性的,不存在并发竞争
  • 高性能:内存操作,延迟极低(亚毫秒级)
  • 支持过期EX 参数天然支持锁的自动释放

但 Redis 做分布式锁有一个根本性的矛盾:Redis 的设计目标是高性能,而不是强一致性。这意味着你需要理解它的局限性,才能用好它。

3.2 可重入锁(最常用,掌握这一个就够了)

可重入锁是 Redisson 最核心的锁实现,占据 90% 以上的使用场景。

基本使用

RLock lock = redissonClient.getLock("order:lock:123");

// 方式一:阻塞式获取(自动续期)
lock.lock();
try {
    processOrder();
} finally {
    lock.unlock();
}

// 方式二:带超时的非阻塞获取
boolean locked = lock.tryLock(3, 30, TimeUnit.SECONDS);
if (locked) {
    try {
        processOrder();
    } finally {
        lock.unlock();
    }
}

底层原理:Lua 脚本保证原子性

Redisson 的加锁操作通过 Lua 脚本 在 Redis 端原子执行,确保「检查 + 设置」不会被打断:

-- 加锁 Lua 脚本(简化版)
-- KEYS[1] = 锁名称
-- ARGV[1] = 锁过期时间
-- ARGV[2] = 客户端标识(线程ID)

if (redis.call('exists', KEYS[1]) == 0) then
    -- 锁不存在,获取锁,设置重入计数为 1
    redis.call('hset', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    -- 锁存在且是自己的,重入计数 +1(可重入)
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;

-- 锁被别人持有,返回剩余过期时间
return redis.call('pttl', KEYS[1]);

关键设计

  • 使用 Hash 结构存储锁,field 是客户端线程标识,value 是重入次数
  • Lua 脚本保证了「判断是否存在 → 加锁/重入 → 设置过期」的原子性
  • 通过客户端标识(clientId:threadId)保证只有锁的持有者才能释放锁
Redis 中的数据结构:
  Key: "order:lock:123"
  Value (Hash):
    "a1b2c3d4:101" → "1"     ← 客户端ID:线程ID → 重入次数

3.3 看门狗机制(自动续期)

看门狗是 Redisson 最巧妙的设计之一,解决了「锁的过期时间设多少合适」这个老大难问题。

问题场景

时间线:
0s          10s              20s
↓            ↓                ↓
[获取锁,30s] → [业务还在执行...] → [锁过期!其他线程获取锁] ← 并发问题!

如果业务执行时间超过锁的过期时间,锁会被提前释放,导致并发安全问题。

看门狗的工作机制

当你不指定 leaseTime 时(如使用无参 lock.lock()),Redisson 会启动一个定时任务,自动为锁续期:

时间线:
0s         10s          20s          30s          40s
↓           ↓            ↓            ↓            ↓
[获取锁]   [续期到30s]  [续期到30s]  [续期到30s]  [unlock,停止续期]

看门狗定时任务:每 lockWatchdogTimeout/3(默认10秒)检查一次
  → 如果当前线程仍持有锁,就重置过期时间为 lockWatchdogTimeout(默认30秒)
  → 如果线程已结束或调用 unlock(),停止续期

核心参数

参数 默认值 说明
lockWatchdogTimeout 30 秒 锁的续期目标时间(也是默认过期时间)
续期间隔 10 秒 lockWatchdogTimeout / 3

⚠️ 关键提醒:看门狗仅在未显式指定 leaseTime 时生效。一旦你指定了 leaseTime(如 lock.lock(30, TimeUnit.SECONDS)lock.tryLock(3, 30, TimeUnit.SECONDS)),看门狗将不会启动,锁会在指定时间后自动释放。

// ✅ 看门狗生效:自动续期
lock.lock();
lock.tryLock(-1, -1, TimeUnit.SECONDS);

// ❌ 看门狗不生效:固定过期时间
lock.lock(30, TimeUnit.SECONDS);
lock.tryLock(3, 30, TimeUnit.SECONDS);

看门狗的风险

  • 程序崩溃时:看门狗线程也会随之终止,锁会在 lockWatchdogTimeout(默认30秒)后自动过期释放。这个时间通常是可接受的。
  • 进程假死时:如果 JVM 发生 Full GC 或 CPU 飙升导致进程假死但未崩溃,看门狗可能仍在续期。这种情况需要配合监控来发现和处理。

3.4 锁参数完全指南

参数说明

参数 含义 推荐值 说明
waitTime 获取锁的最大等待时长 0~10秒 0 = 不等待,-1 = 永久等待
leaseTime 锁的自动释放时长 10~60秒 -1 = 看门狗自动续期

参数组合推荐

场景 waitTime leaseTime 看门狗 理由
防重复提交 0秒 30秒 快速失败,不阻塞用户
库存扣减 3秒 10秒 快速操作,短锁即可
用户交互操作 5秒 30秒 允许短暂等待
批量数据导入 10秒 -1 执行时间不可预估
定时任务 -1 -1 后台任务,必须执行成功
文件生成 30秒 60秒 耗时但时间可预估

常见错误配置

// ❌ 错误1:waitTime 过长,用户等1分钟
lock.tryLock(60, 30, TimeUnit.SECONDS);
// 建议:waitTime 设为 3-5 秒

// ❌ 错误2:leaseTime 过短,业务还没做完锁就释放了
lock.tryLock(3, 3, TimeUnit.SECONDS);
// 建议:leaseTime = 业务最大执行时间 × 1.5

// ❌ 错误3:以为指定了 leaseTime 还有看门狗
lock.lock(30, TimeUnit.SECONDS);
// 事实:看门狗不会启动,30秒后锁一定释放

// ❌ 错误4:直接 unlock 不检查
lock.unlock();  // 可能抛出 IllegalMonitorStateException
// 建议:
if (lock.isHeldByCurrentThread()) {
    lock.unlock();
}

3.5 锁类型全景

除了可重入锁,Redisson 还提供了多种特殊用途的锁:

公平锁(Fair Lock)

按请求顺序获取锁,先到先得(FIFO),避免「饥饿」现象:

RLock fairLock = redissonClient.getFairLock("fairLock");
fairLock.lock();
try {
    // 按请求顺序执行
} finally {
    fairLock.unlock();
}

适用场景:抢红包、任务队列等需要严格顺序的场景。性能略低于普通锁。

读写锁(ReadWrite Lock)

读锁共享,写锁排他,读写互斥:

RReadWriteLock rwLock = redissonClient.getReadWriteLock("data:lock");

// 读操作(多线程可同时持有读锁)
rwLock.readLock().lock();
try {
    return readData();
} finally {
    rwLock.readLock().unlock();
}

// 写操作(独占)
rwLock.writeLock().lock();
try {
    updateData();
} finally {
    rwLock.writeLock().unlock();
}

适用场景:读多写少的缓存更新、配置中心。

联锁(MultiLock)

同时锁定多个资源,全部获取成功才算成功,避免部分加锁导致的死锁:

RLock lock1 = redissonClient.getLock("resource1");
RLock lock2 = redissonClient.getLock("resource2");

RLock multiLock = redissonClient.getMultiLock(lock1, lock2);
multiLock.lock();
try {
    // 同时操作两个资源
    transfer(fromAccount, toAccount);
} finally {
    multiLock.unlock();
}

适用场景:跨账户转账(同时锁 A 和 B)、跨业务操作。

为什么联锁能避免死锁?

传统方式:
  线程1: lock(A) → 等待 lock(B)
  线程2: lock(B) → 等待 lock(A)   ← 死锁!

联锁方式:
  线程1: lock(A, B) → 同时获取,要么全成功要么全失败
  线程2: lock(A, B) → 等线程1释放后再尝试

红锁(RedLock)

在多个独立的 Redis 节点上同时加锁,大多数节点成功即算成功:

RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RLock lock3 = client3.getLock("lock");

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
try {
    // 在多个 Redis 节点上加锁
} finally {
    redLock.unlock();
}

⚠️ 争议:RedLock 算法由 Redis 作者 Antirez 提出,但分布式系统专家 Martin Kleppmann 在 How to do distributed locking 一文中指出了其在时钟跳跃和网络分区下的潜在问题。建议:大多数业务场景用单节点可重入锁就够了,仅在极端高可用需求时才考虑 RedLock。

锁类型对比

锁类型 性能 公平性 复杂度 推荐指数 典型场景
可重入锁 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 90% 的场景
公平锁 ⭐⭐ ⭐⭐⭐ 严格顺序要求
读写锁 ⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐ 读多写少
联锁 ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ 多资源操作
红锁 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐ 极端高可用

3.6 锁的高级用法

RLock lock = redissonClient.getLock("myLock");

// 状态检查
lock.isLocked();                // 锁是否被任何线程持有
lock.isHeldByCurrentThread();   // 当前线程是否持有
lock.getHoldCount();            // 重入次数

// 异步获取锁
RFuture<Boolean> future = lock.tryLockAsync(3, 30, TimeUnit.SECONDS);
future.whenComplete((locked, ex) -> {
    if (locked) {
        try { doWork(); } finally { lock.unlock(); }
    }
});

// 可中断的锁获取(响应线程中断信号)
lock.lockInterruptibly();

// 强制释放锁(⚠️ 仅用于异常恢复,严禁正常业务使用)
lock.forceUnlock();

3.7 实战案例:电商扣库存

以下是一个完整的扣库存方案,包含锁获取、幂等校验、库存扣减和异常处理:

@Service
@RequiredArgsConstructor
public class StockService {

    private final RedissonClient redissonClient;
    private final StockMapper stockMapper;
    private final OrderMapper orderMapper;

    /**
     * 扣减库存(防止超卖 + 防止重复扣减)
     */
    @Transactional
    public boolean deductStock(String orderId, String productId, int quantity) {
        String lockKey = "lock:stock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // waitTime=5秒等待,leaseTime=10秒自动释放(数据库操作很快)
            boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            if (!locked) {
                log.warn("获取库存锁失败: productId={}", productId);
                throw new BusinessException("系统繁忙,请稍后再试");
            }

            try {
                // 1. 幂等校验:检查订单是否已扣减
                if (orderMapper.isDeducted(orderId)) {
                    throw new BusinessException("请勿重复提交");
                }

                // 2. 查询当前库存
                int stock = stockMapper.getStock(productId);
                if (stock < quantity) {
                    throw new BusinessException("库存不足");
                }

                // 3. 扣减库存
                stockMapper.deduct(productId, quantity);

                // 4. 记录扣减状态
                orderMapper.markDeducted(orderId);

                return true;
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("操作被中断");
        }
    }
}

四、分布式限流

4.1 信号量(RSemaphore)

信号量控制同时访问某资源的并发数量,就像停车场的车位管理:

RSemaphore semaphore = redissonClient.getSemaphore("export:semaphore");

// 初始化(设置总车位)
semaphore.trySetPermits(20);

// 进入停车场
semaphore.acquire();              // 阻塞等待
boolean ok = semaphore.tryAcquire();  // 非阻塞

// 离开停车场
semaphore.release();

// 查看空余车位数
int available = semaphore.availablePermits();

实际场景:Excel 导出限流

@PostConstruct
public void init() {
    redissonClient.getSemaphore("export:semaphore").trySetPermits(20);
}

public void exportExcel(HttpServletResponse response) {
    RSemaphore semaphore = redissonClient.getSemaphore("export:semaphore");
    try {
        if (!semaphore.tryAcquire(1, 3, TimeUnit.SECONDS)) {
            throw new BusinessException("导出任务繁忙,请稍后再试");
        }
        try {
            doExport(response);
        } finally {
            semaphore.release();
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

许可证数量设置公式

许可证数量 = 服务实例数 × 单实例最大并发 × 安全系数(0.8)

4.2 过期许可信号量(PermitExpirableSemaphore)

每个许可证有独立的过期时间,防止因程序崩溃导致许可证永久占用:

RPermitExpirableSemaphore semaphore = redissonClient
    .getPermitExpirableSemaphore("task:semaphore");

// 获取许可证,8秒后自动释放
String permitId = semaphore.acquire(8, TimeUnit.SECONDS);
try {
    doTask();
} finally {
    semaphore.release(permitId);
}

4.3 限流器(RRateLimiter)

基于令牌桶算法的限流器,支持按时间维度精确控制速率:

RRateLimiter rateLimiter = redissonClient.getRateLimiter("api:limiter");

// 设置速率:每秒最多100个请求
rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS);

// 尝试获取1个许可
boolean allowed = rateLimiter.tryAcquire(1);
if (!allowed) {
    throw new BusinessException("请求过于频繁,请稍后再试");
}

RateType 说明

  • RateType.OVERALL:全局限流(所有客户端共享配额)
  • RateType.PER_CLIENT:单客户端限流(每个 RedissonClient 独立配额)

信号量 vs 限流器对比

维度 RSemaphore RRateLimiter
限流维度 并发数 时间速率(每秒/每分/每小时)
原理 许可证计数 令牌桶算法
典型场景 控制「同时在执行」的数量 控制「单位时间内」的请求数
示例 最多20个同时导出 每秒最多100次API调用

五、分布式数据结构

Redisson 提供了与 Java 集合 API 几乎一致的分布式数据结构,数据存储在 Redis 中,可跨进程共享。

5.1 RMap(分布式 Map)

RMap<String, User> userMap = redissonClient.getMap("user:map");

// 与 ConcurrentHashMap 几乎一样的 API
userMap.put("user:1", new User("1", "张三"));
User user = userMap.get("user:1");
userMap.putIfAbsent("user:2", newUser);   // 不存在才写入
userMap.fastPut("user:3", newUser);       // 快速写入(不返回旧值)
userMap.expire(1, TimeUnit.HOURS);        // 设置过期时间

本地缓存优化RLocalCachedMap 在 JVM 内存中维护一份缓存副本,减少网络开销:

RLocalCachedMap<String, User> cachedMap = redissonClient
    .getLocalCachedMap("user:cache", LocalCachedMapOptions.defaults()
        .cacheSize(1000)                    // 本地缓存最大条目数
        .timeToLive(10, TimeUnit.MINUTES)   // 生存时间
        .evictionPolicy(EvictionPolicy.LRU) // 淘汰策略
    );

cachedMap.put("user:1", user);
User u = cachedMap.get("user:1");  // 优先从本地获取,未命中再查 Redis

5.2 RSet(分布式 Set)

RSet<String> set = redissonClient.getSet("tags:set");
set.add("java");
set.contains("java");

// 集合运算
RSet<String> set1 = redissonClient.getSet("set1");
set1.readIntersection("set2");  // 交集(共同好友)
set1.readUnion("set2");         // 并集
set1.readDiff("set2");          // 差集

5.3 RList(分布式 List)

RList<String> list = redissonClient.getList("messages:list");
list.add("message1");
list.add(0, "message2");       // 插入到指定位置
String msg = list.get(0);
list.range(0, 2);              // 获取前3个元素
list.trim(0, 4);               // 只保留前5个

5.4 RScoredSortedSet(分布式有序集合)

RScoredSortedSet<String> sortedSet = redissonClient.getScoredSortedSet("ranking");

sortedSet.add(100.5, "user1");
sortedSet.add(95.3, "user2");

Collection<String> top10 = sortedSet.valueRange(0, 9);  // 前10名
int rank = sortedSet.rank("user1");                      // 排名
Double score = sortedSet.getScore("user1");              // 分数
sortedSet.addScore("user1", 5.0);                        // 加分

适用场景:排行榜、优先级队列、延迟队列。

5.5 布隆过滤器(RBloomFilter)

高效判断「某个元素一定不存在或可能存在」,用极小的内存开销过滤大量数据:

RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("user:bloom");

// 初始化:预计100万个元素,误判率0.01%
bloomFilter.tryInit(1_000_000, 0.0001);

bloomFilter.add("user:1");
bloomFilter.contains("user:1");  // true(可能存在)
bloomFilter.contains("user:999"); // false(一定不存在)

典型场景:防止缓存穿透

请求 → 布隆过滤器判断 → 不存在 → 直接返回(不查数据库)
                       → 可能存在 → 查缓存 → 命中 → 返回
                                          → 未命中 → 查数据库 → 写入缓存 → 返回

六、分布式协调与通信

6.1 发布订阅(RTopic)

RTopic topic = redissonClient.getTopic("order:events");

// 发布消息
topic.publish(new OrderEvent("ORDER_CREATED", "123"));

// 订阅消息
int listenerId = topic.addListener(OrderEvent.class, (channel, event) -> {
    System.out.println("收到消息: " + event);
});

// 取消订阅
topic.removeListener(listenerId);

可靠消息订阅RReliableTopic):消息持久化到 Redis,确保订阅者离线期间不丢消息。

6.2 闭锁(CountDownLatch)

分布式版本的 java.util.concurrent.CountDownLatch

RCountDownLatch latch = redissonClient.getCountDownLatch("task:latch");
latch.trySetCount(5);   // 设置计数器

// 各个工作节点完成后
latch.countDown();

// 主节点等待所有工作完成
latch.await();

6.3 分布式队列

// 基本队列(FIFO)
RQueue<String> queue = redissonClient.getQueue("task:queue");
queue.offer("task1");
String task = queue.poll();

// 阻塞队列
RBlockingQueue<String> bq = redissonClient.getBlockingQueue("task:queue");
bq.offer("task1");
String task = bq.take();  // 阻塞等待

// 延迟队列
RDelayedQueue<String> dq = redissonClient.getDelayedQueue(bq);
dq.offer("delayedTask", 10, TimeUnit.SECONDS);  // 10秒后可被消费

适用场景:异步任务处理、削峰填谷、延迟任务。


七、生产实践

7.1 序列化选型

序列化方式 优点 缺点 适用场景
FstCodec(默认) 高性能、体积小 不可读、跨语言不支持 内部系统
JsonJacksonCodec 可读性好、跨语言 性能一般、体积大 需要人工查看或跨语言
KryoCodec 性能最佳 跨版本兼容性差 超高性能场景
MarshallingCodec JBoss 系列 依赖多 JBoss 生态
Config config = new Config();
config.setCodec(new JsonJacksonCodec());  // 全局设置序列化方式

与 Spring Data Redis 共存:两者默认使用不同序列化,建议各管各的 Key,互不交叉读写。如需共享数据,统一使用 JsonJacksonCodec

7.2 降级方案

Redis 故障时分布式功能失效,需要设计降级策略:

// 降级方案:分布式锁 → 本地锁
private final ConcurrentHashMap<String, ReentrantLock> localLocks = new ConcurrentHashMap<>();

public boolean tryLockWithFallback(String lockKey, long waitTime, long leaseTime) {
    try {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
    } catch (Exception e) {
        log.warn("Redis不可用,降级到本地锁: {}", lockKey);
        ReentrantLock localLock = localLocks.computeIfAbsent(lockKey, k -> new ReentrantLock());
        try {
            return localLock.tryLock(waitTime, TimeUnit.SECONDS);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

⚠️ 注意:本地锁只能保证单 JVM 内的互斥,降级方案仅作为兜底策略。

7.3 Key 命名规范

类型前缀:业务模块:业务操作:资源标识
类型 示例 说明
分布式锁 lock:order:create:123 订单创建锁
信号量 semaphore:export:excel 导出限流
限流器 limiter:api:query API限流
分布式Map cache:user:info 用户缓存
布隆过滤器 bloom:user:ids 用户ID去重

原则:冒号分层、小写字母、见名知意、不超过100字节。

7.4 监控与告警

监控项 说明 建议告警阈值
锁获取失败次数 反映并发竞争程度 >10次/分钟
锁持有时间 反映业务执行效率 >配置值的80%
信号量可用率 反映资源利用率 可用 <20%
Redis 连接数 反映连接池压力 >最大连接数的80%
Redis 响应时间 反映网络和负载 >100ms

7.5 连接池优化

并发量 max-active min-idle max-idle
低(<100) 8-16 2-4 4-8
中(100-500) 16-32 4-8 8-16
高(>500) 32-64 8-16 16-32

八、总结

8.1 组件选择速查表

我需要… 用这个 一句话说明
防止并发 RLock 可重入锁,90%场景首选
限流(并发数) RSemaphore 控制同时在执行的数量
限流(速率) RRateLimiter 控制每秒/每分钟的请求数
读多写少 RReadWriteLock 读锁共享,写锁排他
同时锁多个资源 MultiLock 全部获取才算成功
缓存 RMap / RLocalCachedMap 后者带本地缓存优化
去重 RBloomFilter 超省内存,有误判率
排行榜 RScoredSortedSet 自动按分数排序
异步任务 RBlockingQueue / RDelayedQueue 后者支持延迟执行
消息通知 RTopic 发布订阅模式

8.2 最佳实践清单

开发

测试

生产

posted @ 2026-04-22 15:12  flycloudy  阅读(57)  评论(0)    收藏  举报