1.引入起步依赖
首先确保你的SpringBoot项目已经引入了Redis的起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.创建config配置类
这里主要是将消息监听器注入到你的Spring容器当中
@Configuration
public class RedisConfig {
// 注册消息监听器
@Bean
public RedisMessageListenerContainer messageListenerContainer(RedisConnectionFactory connectionFactory,
MessageListener redisMessageListener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//xx为订阅的频道
Topic topic = new PatternTopic("xx");
container.addMessageListener(redisMessageListener, topic);
return container;
}
}
3.创建监听器
这里使用Redis的SETNX 命令构建的简单分布式锁. 这里是防止你的项目为多实例部署,这样会出现多个实例同时监听一条消息,造成一条消息多次被消费. 如果你的项目是单实例 完全可以不使用分布式锁. 当然 你也可以使用类似Redisson这种分布式框架进行上锁.
@Slf4j
@Service
@RequiredArgsConstructor
public class RedisMessageListener implements MessageListener {
private final StringRedisTemplate stringRedisTemplate;
//这里为锁的前缀
private static final String LOCK_PREFIX = "lock:xxx:";
//锁的过期时间
private static final long LOCK_EXPIRE_TIME = 10L;
@Override
public void onMessage(@NotNull Message message, byte[] pattern) {
String messageBody = new String(message.getBody());
ObjectMapper objectMapper = new ObjectMapper();
try {
//这里可以对 接收到的消息进行处理
Map<String, Object> messageMap = objectMapper.readValue(messageBody, Map.class);
// 生成锁的 key (这只是一个前缀)
String lockKey = LOCK_PREFIX;
// 获取当前线程唯一标识
String threadId = String.valueOf(Thread.currentThread().getId());
// 尝试获取分布式锁
boolean isLocked = tryLock(lockKey, threadId);
if (isLocked) {
try {
//这里执行你具体要执行的业务代码 比如发送通知 扣减积分这种 边缘业务
} finally {
unLock(lockKey, threadId);
}
} else {
log.info("消息已被其他实例处理,跳过此消息");
}
} catch (Exception e) {
log.error("通知队列异常", e);
}
}
// 获取分布式锁
private boolean tryLock(String lockKey, String threadId) {
// 使用 SETNX 命令,如果 lockKey 不存在,则设置成功并返回 true
// 同时将线程 ID 存储在 Redis 中
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, threadId, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
// 释放锁
private void unLock(String lockKey, String threadId) {
// 获取 Redis 中存储的线程 ID
String currentThreadId = stringRedisTemplate.opsForValue().get(lockKey);
// 只有当前线程持有锁时才能释放
if (threadId.equals(currentThreadId)) {
stringRedisTemplate.delete(lockKey);
} else {
log.warn("当前线程无权限释放锁,lockKey: {}, 当前线程 ID: {}, 持有锁的线程 ID: {}", lockKey, threadId, currentThreadId);
}
}
}
4.构建消息体
将实体类对象转成 String字符串类型
由于Redis发布订阅 只能发送String类型的消息 所以在这里我们可以通过将实体类对象转成Map 在转成JSON字符串当做消息传递给监听器
//方法里参数可以自定义 完全取决于你 然后构建Map
private String buildAnnouncementMessage(XxDTO xxDTO) throws JsonProcessingException {
Map<String, Object> message = new HashMap<>();
message.put(key,xxDTO.get());
return new ObjectMapper().writeValueAsString(message);
}
5.发送通知
将上面方法构建的消息通过convertAndSend()方法进行发送
//第一个参数为发布的频道,第二个参数即为消息体
stringRedisTemplate.convertAndSend("频道","消息");