从零理解 Redisson:Java 分布式工具箱的入门与实战

从零理解 Redisson:Java 分布式工具箱的入门与实战

一、Redisson 是什么

1.1 一句话定义

Redisson 是一个基于 Redis 的 Java 客户端框架,它把 Redis 的底层命令封装成了 Java 开发者熟悉的数据结构和工具(锁、队列、Map、信号量等),让你像使用本地 Java 对象一样使用分布式功能。

1.2 类比理解

你熟悉的 Redisson 提供的 区别
java.util.HashMap RMap(分布式Map) 数据存在 Redis,多个服务实例共享
java.util.concurrent.locks.ReentrantLock RLock(分布式锁) 锁存在 Redis,跨JVM生效
java.util.concurrent.BlockingQueue RBlockingQueue(分布式队列) 队列存在 Redis,多实例消费
java.util.concurrent.Semaphore RSemaphore(分布式信号量) 信号量存在 Redis,跨实例限流

简单说:Redisson = Redis + Java 并发工具包的分布式版本。

1.3 Redisson vs Jedis vs Lettuce

框架 定位 特点
Jedis Redis 底层客户端 直接发送 Redis 命令,简单轻量
Lettuce Redis 底层客户端 基于 Netty,支持异步,Spring Boot 默认
Redisson Redis 高级客户端 封装了分布式锁、集合、队列等高级功能
java 复制代码
// Jedis:直接操作 Redis 命令
jedis.set("key", "value");
jedis.setnx("lock-key", "holder");

// Lettuce(通过 Spring RedisTemplate):也是操作命令
redisTemplate.opsForValue().set("key", "value");
redisTemplate.opsForValue().setIfAbsent("lock-key", "holder");

// Redisson:面向对象的方式,屏蔽底层命令
RLock lock = redissonClient.getLock("lock-key");
lock.lock(); // 内部自动处理 SETNX、过期时间、看门狗等

二、为什么需要 Redisson

2.1 自己实现分布式锁的痛点

如果用 Jedis/Lettuce 自己实现分布式锁,需要处理:

java 复制代码
// 自己实现:代码量大,容易出错
public boolean tryLock(String key, String holderId, long timeout) {
    // 1. 加锁(SETNX + 过期时间,必须原子操作)
    Boolean success = redisTemplate.opsForValue()
        .setIfAbsent(key, holderId, timeout, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(success);
}

public void unlock(String key, String holderId) {
    // 2. 释放锁(必须验证持有者,必须用 Lua 脚本保证原子性)
    String script =
        "if redis.call('get',KEYS[1]) == ARGV[1] then "
        + "return redis.call('del',KEYS[1]) else return 0 end";
    redisTemplate.execute(
        new DefaultRedisScript<>(script, Long.class),
        Collections.singletonList(key), holderId);
}

// 3. 还需要自己实现:
//    - 看门狗续期
//    - 可重入支持
//    - 公平锁
//    - 红锁(RedLock)
//    - 异步加锁
//    - 锁的监听和通知

2.2 Redisson 帮你做了什么

java 复制代码
// Redisson:一行代码搞定,内部自动处理所有细节
RLock lock = redissonClient.getLock("my-lock");
lock.lock();
try {
    // 业务逻辑
} finally {
    lock.unlock();
}

Redisson 内部自动处理了:

  • 加锁的原子性(Lua 脚本)
  • 释放锁的持有者验证
  • 看门狗自动续期
  • 可重入计数
  • 等待队列和公平性
  • 集群/哨兵模式的兼容

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

三、快速上手

3.1 引入依赖

xml 复制代码
<!-- Maven -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.27.0</version>
</dependency>

3.2 配置连接

yaml 复制代码
# application.yml
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: your-password

或者使用 Redisson 自己的配置:

yaml 复制代码
# redisson.yml
singleServerConfig:
  address: "redis://127.0.0.1:6379"
  password: "your-password"
  connectionMinimumIdleSize: 5
  connectionPoolSize: 10

3.3 注入使用

java 复制代码
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private RedissonClient redissonClient;

    public void processOrder(Integer orderId) {
        // 获取锁对象(此时还没有加锁)
        RLock lock = redissonClient.getLock("order-" + orderId);

        try {
            // 加锁:等待10秒,锁自动30秒过期
            boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (!acquired) {
                throw new RuntimeException("获取锁失败");
            }

            // 执行业务
            doProcess(orderId);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

四、Redisson 核心功能详解

4.1 分布式锁(RLock)

这是 Redisson 最常用的功能。

java 复制代码
RLock lock = redissonClient.getLock("my-lock");

// 方式一:阻塞加锁(一直等到获取成功)
lock.lock();

// 方式二:尝试加锁(等待指定时间)
boolean success = lock.tryLock(10, TimeUnit.SECONDS);

// 方式三:尝试加锁 + 指定锁过期时间
boolean success = lock.tryLock(10, 30, TimeUnit.SECONDS);
// 参数1:最多等10秒
// 参数2:锁30秒后自动释放(不启用看门狗)

// 方式四:尝试加锁 + 看门狗自动续期
boolean success = lock.tryLock(10, TimeUnit.SECONDS);
// 不指定第二个参数 → 启用看门狗,锁不会自动过期

// 释放锁
lock.unlock();

4.2 公平锁(RFairLock)

普通锁是"谁抢到算谁的",公平锁是"先来先得"。

java 复制代码
// 公平锁:按请求顺序获取锁
RLock fairLock = redissonClient.getFairLock("fair-lock");
fairLock.lock();
try {
    // 业务逻辑
} finally {
    fairLock.unlock();
}

4.3 读写锁(RReadWriteLock)

允许多个线程同时读,但写的时候独占。

java 复制代码
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rw-lock");

// 读锁:多个线程可以同时持有
RLock readLock = rwLock.readLock();
readLock.lock();
try {
    // 读取数据(多个线程可以同时读)
    return dataRepository.findById(id);
} finally {
    readLock.unlock();
}

// 写锁:独占,其他读和写都要等待
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
    // 修改数据(独占)
    dataRepository.save(data);
} finally {
    writeLock.unlock();
}

4.4 分布式Map(RMap)

java 复制代码
// 像使用 HashMap 一样,但数据存在 Redis
RMap<String, UserInfo> userCache = redissonClient.getMap("user-cache");

// 存入
userCache.put("user-123", userInfo);

// 读取
UserInfo info = userCache.get("user-123");

// 设置过期时间
userCache.put("user-123", userInfo, 30, TimeUnit.MINUTES);

4.5 分布式队列(RBlockingQueue)

java 复制代码
// 生产者
RBlockingQueue<String> queue =
    redissonClient.getBlockingQueue("task-queue");
queue.offer("task-001");

// 消费者(阻塞等待)
String task = queue.take(); // 如果队列为空,会阻塞等待
processTask(task);

4.6 限流器(RRateLimiter)

java 复制代码
// 创建限流器:每秒最多10个请求
RRateLimiter limiter = redissonClient.getRateLimiter("api-limiter");
limiter.trySetRate(
    RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);

// 使用
if (limiter.tryAcquire()) {
    // 允许通过
    processRequest();
} else {
    // 被限流
    throw new RuntimeException("请求过于频繁");
}

五、RLock 的内部原理

5.1 加锁过程

Redisson 加锁时执行的 Lua 脚本(简化版):

lua 复制代码
-- 如果锁不存在
if redis.call('exists', KEYS[1]) == 0 then
    -- 创建锁,设置持有者和重入计数
    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])

5.2 Redis 中锁的数据结构

复制代码
Key: "my-lock"
Type: Hash
Value:
    "holder-id-thread-1" → 2  (重入计数=2,表示加了2次锁)
TTL: 30000ms

5.3 释放锁过程

lua 复制代码
-- 检查锁是否是自己持有的
if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then
    return nil  -- 不是自己的锁,不操作
end

-- 重入计数-1
local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)

if counter > 0 then
    -- 还有重入,只重置过期时间
    redis.call('pexpire', KEYS[1], ARGV[2])
    return 0
else
    -- 计数归零,真正释放锁
    redis.call('del', KEYS[1])
    -- 通知等待的线程
    redis.call('publish', KEYS[2], ARGV[3])
    return 1
end

六、Redisson 与 Spring Boot 集成

6.1 自动配置(最简方式)

引入 redisson-spring-boot-starter 后,Redisson 会自动读取 spring.redis 配置创建 RedissonClient

java 复制代码
@Service
public class MyService {

    @Resource
    private RedissonClient redissonClient; // 自动注入

    public void doSomething() {
        RLock lock = redissonClient.getLock("key");
        // ...
    }
}

6.2 手动配置(需要自定义参数时)

java 复制代码
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://127.0.0.1:6379")
            .setPassword("password")
            .setConnectionMinimumIdleSize(5)
            .setConnectionPoolSize(20)
            .setTimeout(3000)
            .setRetryAttempts(3);
        return Redisson.create(config);
    }
}

6.3 集群模式配置

java 复制代码
@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useClusterServers()
        .addNodeAddress(
            "redis://node1:6379",
            "redis://node2:6379",
            "redis://node3:6379")
        .setPassword("password");
    return Redisson.create(config);
}

七、实战示例:商品库存扣减

7.1 完整代码

java 复制代码
/**
 * 库存服务实现.
 */
@Service
public class StockServiceImpl implements StockService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private StockRepository stockRepository;

    /**
     * 扣减库存(使用分布式锁防止超卖).
     *
     * @param skuId 商品SKU ID
     * @param quantity 扣减数量
     * @return true=扣减成功,false=库存不足
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean deductStock(Integer skuId, Integer quantity) {
        // 按SKU维度加锁
        String lockKey = "stock-deduct-" + skuId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 等待5秒,不指定leaseTime(启用看门狗)
            boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
            if (!acquired) {
                log.warn("获取库存锁超时, skuId:{}", skuId);
                return false;
            }

            // 查询当前库存
            Stock stock = stockRepository.findBySkuId(skuId);
            if (stock == null || stock.getQuantity() < quantity) {
                log.info("库存不足, skuId:{}, 当前:{}, 需要:{}",
                    skuId,
                    stock == null ? 0 : stock.getQuantity(),
                    quantity);
                return false;
            }

            // 扣减库存
            stock.setQuantity(stock.getQuantity() - quantity);
            stockRepository.save(stock);

            log.info("库存扣减成功, skuId:{}, 扣减:{}, 剩余:{}",
                skuId, quantity, stock.getQuantity());
            return true;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("获取库存锁被中断, skuId:{}", skuId);
            return false;
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

7.2 关键代码解读

java 复制代码
// 1. getLock("key") --- 获取锁对象(还没加锁)
RLock lock = redissonClient.getLock(lockKey);

// 2. tryLock(waitTime) --- 尝试加锁
//    - 如果锁空闲:立即获取,返回 true
//    - 如果锁被占用:等待最多5秒
//    - 5秒内锁释放了:获取成功,返回 true
//    - 5秒后仍被占用:放弃,返回 false
boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);

// 3. isHeldByCurrentThread() --- 检查当前线程是否持有锁
//    防止释放别人的锁
if (lock.isHeldByCurrentThread()) {
    lock.unlock();
}

八、Redisson 功能全景图

复制代码
RedissonClient
    │
    ├── 分布式锁
    │   ├── RLock(可重入锁)
    │   ├── RFairLock(公平锁)
    │   ├── RReadWriteLock(读写锁)
    │   ├── RSemaphore(信号量)
    │   └── RCountDownLatch(倒计时门闩)
    │
    ├── 分布式集合
    │   ├── RMap(分布式Map)
    │   ├── RSet(分布式Set)
    │   ├── RList(分布式List)
    │   ├── RSortedSet(有序集合)
    │   └── RQueue / RBlockingQueue(队列)
    │
    ├── 分布式服务
    │   ├── RRemoteService(远程调用)
    │   ├── RScheduledExecutorService(定时任务)
    │   └── RExecutorService(分布式执行器)
    │
    └── 其他工具
        ├── RRateLimiter(限流器)
        ├── RAtomicLong(原子计数器)
        ├── RTopic(发布订阅)
        └── RBloomFilter(布隆过滤器)

九、常见问题

Q1:Redisson 和 Spring Cache 能一起用吗?

可以。Redisson 提供了 Spring Cache 的实现:

java 复制代码
@Cacheable(cacheNames = "userCache", key = "#userId")
public UserInfo getUserById(Integer userId) {
    return userRepository.findById(userId).orElse(null);
}

Q2:Redisson 支持 Redis 集群吗?

支持。Redisson 支持单机、哨兵、集群、主从等所有 Redis 部署模式。

Q3:Redisson 的性能如何?

  • 加锁/释放锁:通常 1-3 毫秒(取决于网络延迟)
  • 比自己用 RedisTemplate 实现稍慢(多了一些内部逻辑)
  • 但比 ZooKeeper 实现快很多

Q4:项目中已经有 RedisTemplate 了,还需要 Redisson 吗?

两者可以共存。RedisTemplate 用于简单的缓存读写,Redisson 用于分布式锁、限流等高级功能。


十、总结

问题 答案
Redisson 是什么? 基于 Redis 的 Java 分布式工具框架
和 Jedis/Lettuce 的区别? Jedis/Lettuce 是底层客户端,Redisson 是高级封装
最常用的功能? 分布式锁(RLock)
为什么不自己实现分布式锁? 自己实现需要处理原子性、续期、可重入、持有者验证等细节
如何引入? redisson-spring-boot-starter 依赖即可
性能影响? 加锁/释放锁约 1-3ms,对大多数业务可忽略
相关推荐
暗冰ཏོ11 小时前
《Vue + React + Java + PHP 项目部署到服务器完整指南》
java·服务器·vue.js·react.js·项目部署
XMYX-011 小时前
40 - Go HTTP 客户端:从 http.Get 到高性能连接池
开发语言·http·golang
_Aaron___11 小时前
Spring AI 2.0 之后,MCP Server 该按远程企业服务来设计
java·人工智能·spring
NE_STOP11 小时前
Docker--Docker简介及系统架构
java
Daydream.V11 小时前
C++ 入门全攻略:从基础语法到核心特性
java·开发语言·c++
我是一颗柠檬11 小时前
【JDK8新特性】接口默认方法与静态方法Day8
java·开发语言·后端·intellij-idea
juniperhan11 小时前
Flink 系列第25篇:Flink SQL 集成 Hive 实践:流批一体下的实时数仓利器
大数据·数据仓库·hive·分布式·sql·flink
lulu121654407811 小时前
【开发者指南】Gemini 3.5开发入门:从API调用到Agent构建
java·开发语言·人工智能·python·ai编程
盲敲代码的阿豪11 小时前
Python 爬虫入门基础教程:从入门到实践
开发语言·爬虫·python
SimonKing11 小时前
从单机到高并发:手搓唯一编号的生成方案
java·后端·程序员