基于redis发布订阅实现消息队列

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("频道","消息");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值