Redisson 简单使用
文章目录
1.依赖
github 地址:https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter
文档地址:https://redisson.org/
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.1</version>
</dependency>
2. Redisson 特性
- Redisson 主要针对 redis 在分布式系统中的应用,为了解决分布式锁等问题,进而封装成的Rids 操作;
- 功能比较简单, 不支持排序,事务,管道,分区等redis特性,可以认为是jedis的补充,不能替换jedis;
- 程序接口调用方式采用异步执行和异步流执行两种方式 ;
- 提供多种分布式对象,如:Object Bucket,Bitset,AtomicLong,Bloom Filter 和 HyperLogLog 等 ;
- 提供丰富的分布式集合,如:Map,Multimap,Set,SortedSet,List,Deque,Queue 等 ;
- 分布式锁和同步器的实现,可重入锁(Reentrant Lock),公平锁(Fair Lock),联锁(MultiLock),红锁(Red Lock),信号量(Semaphonre),可过期性信号锁(PermitExpirableSemaphore)等;
- 提供先进的分布式服务,如分布式远程服务(Remote Service),分布式实时对象(Live Object)服务,分布式执行服务(Executor Service),分布式调度任务服务(Schedule Service)和分布式映射归纳服务(MapReduce);
2. Redisson 、 RDelayedQueue 延迟队列
延迟队列主要是用于处理延时任务,与定时任务存在的区别如下所示:、
- 定时任务有明确的触发时间,触发周期,而延时任务没有确定的时间,是随着业务去订立延时任务;
- 定时任务一般执行的是批处理操作是多个任务,而延时任务一般是单个任务;
使用常景:
- 生成订单30分钟未支付,则自动取消;
- 生成订单60秒后,给用户发短信;
- 邮件延时发送;
PS: 也可以采用 RocketMQ 实现延时任务;
RDelayedQueue 使用代码示例
@Autowired
private RedissonClient redissonClient;
private final Lock lock = new ReentrantLock();
/**
* 任务回调监听
*
* @param <T>
*/
public interface TaskEventListener<T> {
/**
* 执行方法
*
* @param t
*/
void invoke(T t);
}
/**
* 添加队列
*
* @param t 传输的数据
* @param zClass 队列名称
* @param delay 延迟时间
* @param timeUnit 时间单位
* @param <T> 泛型
*/
public <T> void addQueue(T t, String queueName, long delay, TimeUnit timeUnit) {
RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(queueName);
// 创建延时队列
RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
// 添加延时队列数据
delayedQueue.offer(t, delay, timeUnit);
// 使用完队列后消除,如果是长期使用的队列,建议不要删除
// delayedQueue.destroy();
}
/**
* 获取队列
*
* @param queueName 队列名称
* @param taskEventListener 任务回调监听 具体业务处理逻辑
* @param <T> 泛型
* @return
*/
public <T> void getQueue(String queueName, TaskEventListener<T> taskEventListener) {
//由 hutool 定时线程定时拉取队列
CronUtil.schedule("*/1 * * * * *", (Task) () -> {
lock.lock();
try {
RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(queueName);
if (blockingFairQueue.size() == 0) {
return;
} else {
//放弃线程占用
Thread.sleep(0);
}
log.debug("获取队列数据,{}", blockingFairQueue.size());
while (blockingFairQueue.size() > 0) {
// 获取延迟队列
T t = blockingFairQueue.take();
// 执行回调接口的方法
taskEventListener.invoke(t);
}
} catch (InterruptedException e) {
log.error("获取队列失败{},", e.getMessage());
} finally {
lock.unlock();
}
});
// 支持秒级别定时任务
CronUtil.setMatchSecond(true);
CronUtil.start();
}
——————————————————————————————————————————————————————————————————————————————————
触发获取队列定时任务
// 实现 ApplicationRunner 项目启动后加载
@Slf4j
@Order(10000)
@Component
public class MailSendHandler implements ApplicationRunner {
@Autowired
private RedisDelayedQueue redisDelayedQueue;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("启动定时延迟队列,邮件发送监听");
//监听延迟队列
// 当获取到延迟队列后,实际处理逻辑
RedisDelayedQueue.TaskEventListener<MailSendDTO> taskEventListener = mailSendDTO -> {
业务处理逻辑代码 }
// 触发获取队列的定时任务 调用前面的 getQueue 方法
redisDelayedQueue.getQueue(“MailSendDTO”, taskEventListener);
}
}
3. RTopic 发布/订阅主题的使用
RTopic 的使用是具有实时性,只要发送主题就会消费;
-
客户端订阅某个或某些频道的时候, 这个客户端与被订阅频道之间就建立起了一种订阅关系。Redis 将所有频道的订阅关系都保存在服务器状态的
pubsub_channels
字典里面, 这个字典的键是某个被订阅的频道, 而键的值则是一个链表, 链表里面记录了所有订阅这个频道的客户端:- 通过 Topic 订阅主题 ------- topic.addListener():
@Autowired
private RedissonClient redissonClient;
// 添加主题
//添加topic监听
/**
* * @param 主题name
* * @param 消息解码器
*/
RTopic topic = redissonClient.getTopic(RedisMqConstant.MAIL_KEY, new SerializationCodec());
// 订阅此主题, 当主题发布的时候进行调用 回调函数
/**
* * @param 消息类型
* * @param 回调函数 此处为 Lambda 表达式
* 回调函数第一个参数 为主题频道,第二个参数为 主题消息(消息内容)
*/
topic.addListener(String.class, (charSequence, id) -> {
log.debug("onMessage:" + charSequence + "; Thread: " + Thread.currentThread().getName());
// 业务处理逻辑
mailBodyService.save(id);
});
RTopic topic = redissonClient.getTopic(RedisMqConstant.MAIL_KEY, new SerializationCodec());
// 发布主题
topic.publish(id);
- 通过 RedisMessageListenerContainer 订阅主题:
/**
* @author R 类名称可以自定义
*/
@Configuration
public class RedisMessageListener {
/**
* 创建连接工厂
* @param connectionFactory 可以通过 redisTemplate.getConnectionFactory() 进行获取
* @param listenerAdapter 回调函数
* @param listenerAdapter2 回调函数
* @return 创建RedisMessageListenerContainer ,并订阅主题
*/
@Bean
public RedisMessageListenerContainer container(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter,
MessageListenerAdapter listenerAdapter2){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// param1 主题回调函数
// param2 主题对象
container.addMessageListener(listenerAdapter,new PatternTopic("syncEmail"));
container.addMessageListener(listenerAdapter2,new PatternTopic("pushEmail"));
// 或者这种添加回调函数和订阅主题也是可以
// Collection<PatternTopic> list = CollUtil.create(PatternTopic.class);
// list.add(new PatternTopic("syncEmail"));
//list.add(new PatternTopic("pushEmail"));
// container.addMessageListener(listenerAdapter,list);
return container;
}
/**
* 绑定消息监听者和接收监听的方法
* @param receiver
* @return 包装回调函数到 MessageListenerAdapter
* 当有主题发布时,执行 ReceiverRedisMessage 中的 receiveMessage 方法
*/
@Bean
public MessageListenerAdapter listenerAdapter(com.hare.mail.mgr.listener.ReceiverRedisMessage receiver){
return new MessageListenerAdapter(receiver,"receiveMessage");
}
/**
* 注册订阅者
* @param latch
* @return 注册回调函数到容器中
*/
@Bean
com.hare.mail.mgr.listener.ReceiverRedisMessage receiver(CountDownLatch latch) {
return new com.hare.mail.mgr.listener.ReceiverRedisMessage(latch);
}
/**
* 计数器,用来控制线程
* @return 多线程计数器,用于创建 ReceiverRedisMessage 回调函数的时候传递进去初始化
*/
@Bean
public CountDownLatch latch(){
return new CountDownLatch(1);//指定了计数的次数 1
}
}
- 回调函数实现
/**
* @author R
*/
@Slf4j
public class ReceiverRedisMessage {
// CountDownLatch 慎重考虑使用
private CountDownLatch latch;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String SYNCEMAIL = "SYNCEMAIL";
@Autowired
public ReceiverRedisMessage(CountDownLatch latch) {
this.latch = latch;
}
@Autowired
private Jpush jpush;
@Autowired
private UpmsUserServiceApi upmsUserServiceApi;
/**
* 队列消息接收方法
*
* @param jsonMsg
*/
public void receiveMessage(String jsonMsg) {
log.info("[开始消费REDIS消息队列phone数据...]{}",jsonMsg);
try {
// 业务处理逻辑
} catch (Exception e) {
log.error("[消费REDIS消息队列phone数据失败,失败信息:{}]", e.getMessage());
}
// 线程计数器减一
latch.countDown();
}
}
4. RLock 分布式锁
- 使用背景: 在分布式系统中,一些高并发场景下,对于共享资源进行业务处理,需要加上分布式锁,例如:秒杀、抢票、抢购,防止出现库存为负数,不能发货的情况;
- 代码例子:
@Autowired
private RedissonClient redissonClient;
// 获取锁对象名称为 “configId”
RLock lock = redissonClient.getLock(configId);
// 设置锁的过期时间 防止出现死锁
lock.lock(3, TimeUnit.MINUTES);
try {
// 业务处理逻辑
getMail(funtion, config);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
-
加减锁源码解释可参考如下:
- https://www.jianshu.com/p/47fd7f86c848
- https://www.cnblogs.com/cjsblog/p/9831423.html