# smart-redisson-spring-boot-starter
**Repository Path**: the-best-riven/smart-redisson-spring-boot-starter
## Basic Information
- **Project Name**: smart-redisson-spring-boot-starter
- **Description**: redisson的springboot starter实现,并基于spring-messaging对redisson的队列和延迟队列进行了实现。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 43
- **Forks**: 16
- **Created**: 2019-09-27
- **Last Updated**: 2025-06-18
## Categories & Tags
**Categories**: spring-boot-ext
**Tags**: None
## README
# smart-redisson-spring-boot-starter
#### 介绍
redisson的springboot starter简单实现,并基于spring-messaging对redisson的队列和延迟队列进行了封装,简洁方便的使用redisson的延迟队列\
提供了发送队列消息的操作模板、基于注解的消费者实现
#### 背景
日常开发中经常遇到延迟要求非常精准的场景,常规的通常没有延迟消息功能或者延迟不精准,\
如rabbitmq,虽然可以通过死信队列处理模拟延迟队列,但是死信队列是阻塞的,\
rocketmq,虽然有提供延迟消息的场景且消息不阻塞,但经过测试发现延迟时间不是非常精准,有时候误差在秒级。\
经调研发现redisson基于redis实现了延迟队列,非则塞且误差在毫秒级,基于此场景,对其使用spring相关组件进行封装,以便在spring应用中方便的使用
#### 运行环境及框架
| 版本 | 环境要求 | 版本更新要点 |
|-------|----------------------------|------------------------------------------|
| 1.x.x | java 1.8
springboot2.0 | 略 |
| 3.4.0 | java17+
springboot3.4 | 适配spring3
支持多种redis架构
移除fastjson |
| 3.4.1 | java17+
springboot3.4 | 增加分布式锁功能 | |
#### 安装教程
##### 1. 从1.1.4-beta.1开始已发布maven中央仓库,直接引用即可
```xml
io.gitee.the-best-riven
smart-redisson-spring-boot-starter
3.4.1
```
##### 2. 1.1.4-beta.1之前的版本未发布maven中央仓库,需要手动引入相关依赖包
```xml
com.riven
smart-redisson-spring-boot-starter
1.0.0.RELEASE
system
***
org.springframework.boot
spring-boot-starter
2.0.6.RELEASE
org.springframework.boot
spring-boot-autoconfigure
2.0.6.RELEASE
org.springframework.boot
spring-boot-configuration-processor
2.0.6.RELEASE
org.springframework
spring-messaging
5.0.10.RELEASE
org.redisson
redisson
3.11.1
com.alibaba
fastjson
1.2.60
```
##### 3. 有私服的可以将jar包放到私服,直接普通的引用即可
```xml
com.riven
smart-redisson-spring-boot-starter
1.1.3-beta.1
```
##### 4. 特别说明
1.1.4-beta.1之前的groupId为com.riven,因maven中央仓库的审核要求,\
1.1.4-beta.1发布到maven中央仓库后groupId改为io.gitee.the-best-riven
#### 使用说明
##### 1. 引入starter之后,配置文件新增
```yaml
spring:
smart-redisson:
#server-type: standalone
#server-address: localhost:6379
server-type: cluster
server-address: localhost:7000,localhost:7001,localhost:7002
password: 123456
```
不加配置默认Redis服务为单实例standalone、服务连接地址为localhost:6379。\
配置结束之后即可使用通过spring容器使用RedissonClient了。
##### 2. 如果需要使用队列,在spring容器注入一个RedissonQueue实例即可,如:
```java
@Bean
public RedissonQueue redissonQueue() {
return new RedissonQueue("riven", true, null, messageConverter());
}
```
创建队列的时候可以指定队列名称、是否延迟队列、隔离策略、消息转换器。\
隔离策略主要是应用于如下场景:\
在服务集群模式中,假设A、B、C三台机器,A机器生产的消息只希望自己消费,这就叫做隔离,\
可通过指定隔离策略进行集群的隔离,源码提供了DefaultIsolationStrategy,可根据需要使用。\
消息转换器主要是对消息进行转换以及一些附加处理如增加消息头等
##### 3. 向队列发送消息
```java
@Autowired
private RedissonTemplate redissonTemplate;
public void test() {
CarLbsDto carLbsDto = new CarLbsDto();
carLbsDto.setCid(1);
carLbsDto.setBusinessType("0");
carLbsDto.setCity("北京市");
carLbsDto.setCityId(265);
carLbsDto.setName("fsfds");
carLbsDto.setCarNum("156156");
redissonTemplate.sendWithDelay("riven", carLbsDto, 5000);
}
```
##### 4. 消费消息
在bean方法上增加@RedissonListener注解即可,还可额外定义消息转换器以实体类形参作为方法入参(默认是string类型)
```java
@Configuration
public class RedissonTestApplication {
@Bean("myMessageConverter")
public MessageConverter messageConverter() {
return new MessageConverter() {
@Override
public QueueMessage> toMessage(Object object, Map headers) throws MessageConversionException {
//do something you want, eg:
headers.put("my_header", "my_header_value");
return QueueMessageBuilder.withPayload(object).headers(headers).build();
}
@Override
public Object fromMessage(RedissonMessage redissonMessage) throws MessageConversionException {
byte[] payload = redissonMessage.getPayload();
String payloadStr = new String(payload);
return JSONObject.parseObject(payloadStr, CarLbsDto.class);
}
};
}
@RedissonListener(queues = "riven", messageConverter = "myMessageConverter")
public void handler(@Header(value = RedissonHeaders.MESSAGE_ID, required = false) String messageId,
@Header(RedissonHeaders.DELIVERY_QUEUE_NAME) String queue,
@Header(RedissonHeaders.SEND_TIMESTAMP) long sendTimestamp,
@Header(RedissonHeaders.EXPECTED_DELAY_MILLIS) long expectedDelayMillis,
@Header(value = "my_header", required = false, defaultValue = "test") String myHeader,
@Payload CarLbsDto carLbsDto) {
System.out.println(messageId);
System.out.println(queue);
System.out.println(myHeader);
long actualDelay = System.currentTimeMillis() - (sendTimestamp + expectedDelayMillis);
System.out.println("receive " + carLbsDto + ", delayed " + actualDelay + " millis");
}
}
```
其中注解可以配置消费的队列、异常处理器、隔离策略、消息转换器
其中隔离策略通常与生产者保持一致
异常处理器可自定义消费发生异常之后如何处理,源码提供了RequeueRedissonListenerErrorHandler,可根据需要使用
消息转换器是把RedissonMessage转换成需要的对象,当然不转换消费方法直接使用RedissonMessage作为参数也是可以的
##### 分布式锁
启动类增加@EnableDistributedLock注解,以开启分布式锁功能
```java
@SpringBootApplication
@EnableDistributedLock(application = "my-application")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
/**
* 使用RedissonLock实现锁功能,不配置则默认使用SetNxRedissonLockProvider基于setnx命令实现锁
*/
@Bean
public LockProvider myLockProvider(RedissonClient redissonClient) {
return new RedissonLockProvider(redissonClient);
}
/**
* 使用@LockSubject注解获取锁主体,@LockSubject支持配置spel表达式,优先级最高
*/
@DistributedLock
@PostMapping("/testa")
public void testa(@RequestParam("name") @LockSubject String name,
@RequestBody @LockSubject("id") User user) {
testService.get();
}
/**
* 使用@DistributedLock注解并指定subjectProvider获取锁主体,优先级低于@LockSubject
*/
@DistributedLock(subjectProvider = LoginUserProvider.class)
@PostMapping("/testb")
public void testb(@RequestBody User user) {
testService.get();
}
public static class LoginUserProvider implements LockSubjectProvider {
@Override
public Integer getSubject() {
return LoginUserHolder.getLoginUserId();
}
}
/**
* 向spring容器注入默认的锁主体提供者,@LockSubject和subjectProvider都不存在时才会使用,优先级最低
* 若此时没有默认类获取锁主体提供者,则不执行锁操作
*/
@Bean
public LockSubjectProvider defaultLockSubjectProvider() {
//@TODO 获取锁主体
return () -> LoginUserHolder.getLoginUserId();
}
}
```
#### 设计说明/初衷
每个队列均可在定义时指定MessageConverter,如果不指定,则会使用RedissonTemplate中默认的全局MessageConverter。\
每个消费者也可指定MessageConverter,如果不指定,则会尝试在spring容器中中寻找MessageConverter的bean实例,\
还未找到,则使用默认的MessageConverter,默认的MessageConverter进行的操作是将从redis拿到的消息的消息体转换成字符串。\
至于为什么是字符串呢?这还跟Redisson的序列化和反序列化有关。设计的初衷是,为了避免耦合、提升可扩展性,\
序列化和反序列化使用的是fastjson(详见FastJsonCodec编码解码器,3.4版本已改为jackson,对应RedissonMessageCodec),并且消息的内容不会包含任何项目或者类相关的信息。\
试想一下,A项目发送消息com.a.TestClass对象到队列,如果是采用java的序列化,那么B项目消费消息的时候也必须严格按照\
com.a.TestClass进行定义类,才能正确的接收到消息的内容,否则反序列化出错。同样json序列化带上类信息时,\
也会存在同样的问题,这不是我们想要的。所以消息的存储完全是与项目、类信息等无关的,仅仅是一个json格式的数据,\
所以消费者读到的数据实际上是一个json格式的字符串,在使用的时候我们要注意到,消费者接收到的消息都是基于json转换而来的,\
如果我们不自定义MessageConverter转换器,那么我们拿到的数据消息体就是一个json字符串,消息头就是一个json对象。
#### 常用类
##### 1.常用的核心注解
```java
RedissonListener
DistributedLock
LockSubject
```
##### 2.常用的核心类
```java
RedissonClient
RedissonQueue
RedissonTemplate
RedissonMessage
```
##### 3.常用的核心接口
```java
IsolationStrategy
MessageConverter
RedissonConfigCustomizer
```
#### 性能测试
单线程写入速度1100/s左右,因为写入速度瓶颈明显不是在redis服务器上,所以测试客户端多线程写入,写入速度1.2w+/s。\
消费速度1300/s左右。\
以上测试数据为个人PC机测试结果,非专业测试机测试结果,仅供参考,有需要可自行搭建环境测试。
#### 码云特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)