Java框架-如何利用Redisson实现延时队列

在分布式系统中,延时队列是一种常见的需求。它允许我们在指定的时间后执行某个操作,或者在指定的时间后发送一条消息。Redisson是一个在Redis上实现的Java客户端,它提供了丰富的分布式对象和服务,包括延时队列。

和之前讲过的事件实现一样,我们首先定义事件

java 复制代码
public class RedissonEvent<T extends BaseEvent> implements Serializable {

    /**
     * DATA
     */
    private T data;
    /**
     * TAG
     */
    private String tag;

    private String className;

    public RedissonEvent() {
    }

    public RedissonEvent(T data, String tag, String className) {
        this.data = data;
        this.tag = tag;
        this.className = className;
    }

    public RedissonEvent(T data, String tag) {
        this.data = data;
        this.tag = tag;
        this.className = data.getClass().getName();
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getTag() {
        return tag;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getClassName() {
        return className;
    }
}

发送者:

java 复制代码
@Slf4j
public class RedissonEventPublisher implements EventPublisher {

    private final RedissonClient redissonClient;


    public RedissonEventPublisher(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }


    /*
     **
     * @Date: 2024/1/15 18:09
     * @MethodName: sendMessage
     * @Description: 发送消息
     * @param: payload 消息体
     * @param: msgTag 消息的tag
     * @Return: boolean
     **/
    @Override
    public <T extends BaseEvent> boolean sendMessage(T message, String msgTag) {
        return sendMessage(message, msgTag, 0, TimeUnit.SECONDS);
    }

    @Override
    public <T extends BaseEvent> boolean sendMessage(T payload, String msgTag, long delayedTimes, TimeUnit timeUnit) {
        sendMessageByRedisson(payload, msgTag, delayedTimes, timeUnit);
        return true;
    }

    private <T extends BaseEvent> void sendMessageByRedisson(T message, String msgTag, long delayedTimes, TimeUnit timeUnit) {
        RedissonEvent<T> redissonEvent = new RedissonEvent<>(message, msgTag);
        log.info("往消息队列中添加Redission延迟消息:{}", message);
        RBlockingQueue<RedissonEvent<T>> blockingQueue = redissonClient.getBlockingQueue(RedissonMessageListener.queue + ":" + msgTag);
        RDelayedQueue<RedissonEvent<T>> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
        delayedQueue.offer(redissonEvent, delayedTimes, timeUnit);
    }

}

消费者:

java 复制代码
@Slf4j
public class RedissonMessageListener {

    public static String queue = "dragon:jjb-saas-common:redis:queue";

    private final RedissonClient redissonClient;

    public RedissonMessageListener(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public void init(String tag) {
        RBlockingQueue<RedissonEvent<?>> blockingQueue = redissonClient.getBlockingQueue(RedissonMessageListener.queue + ":" + tag);
        RDelayedQueue<RedissonEvent<?>>  delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
        ThreadUtil.execAsync(() -> {
            while (true) {
                try {
                    RedissonEvent<?> redissonEvent = blockingQueue.poll();
                    if (null != redissonEvent) {
                        onMessage(redissonEvent);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public void onMessage(RedissonEvent redissonEvent) {

        assert redissonEvent != null;
        ConsumerType msgProcessorType = ConsumerCache.getConsumer(redissonEvent.getTag());
        if (msgProcessorType == null) {
            log.warn("No message processor present for tag: " + redissonEvent.getTag());
            return;
        }
        BaseEvent baseMessage = redissonEvent.getData();
        TraceUtils.putSid(baseMessage.getSid());
        TraceUtils.putTid(baseMessage.getTid());
        AuthContext.set(baseMessage.getLoginUser());
        MessageConsumer<BaseEvent> messageConsumer = ApplicationContextHelper.getBean(msgProcessorType.getConsumerClass());

        try {
            //判断消息是否需要幂等消费
            if (baseMessage.isIdempotentEnabled()) {
                //计算key
                String key = DigestUtils.md5Hex(redissonEvent.getTag() + "#" + baseMessage.getMsgSerialNum()
                        + "#" + baseMessage + "#" + messageConsumer.getClass().getName());
                Boolean consumed = ApplicationContextHelper.getBean(RedisService.class).setNx(key, 1, 24, TimeUnit.HOURS);

                //判断消息是否已经被当前服务消费过,如果消费过,则忽略当前消息
                if (Boolean.FALSE.equals(consumed)) {
                    log.warn("repeat consume message:{}", baseMessage);
                    return;
                }
                try {
                    messageConsumer.consume(baseMessage);
                } catch (Throwable e) {
                    //执行错误,删除key,下次可以重复消费
                    ApplicationContextHelper.getBean(RedisService.class).delete(key);
                    throw e;
                }
            } else {
                messageConsumer.consume(baseMessage);
            }
        } catch (Throwable e) {
            throw e;
        } finally {
            TraceUtils.removeTid();
            TraceUtils.removeSid();
            AuthContext.remove();
        }
    }
}

代码有问题,请留言

相关推荐
转转技术团队2 分钟前
多代理混战?用 PAC(Proxy Auto-Config) 优雅切换代理场景
前端·后端·面试
南囝coding3 分钟前
这几个 Vibe Coding 经验,真的建议学!
前端·后端
阿杆5 分钟前
服务一挂就手忙脚乱?教你用 Amazon Lambda 打造 0 成本服务监控!
后端·自动化运维
德育处主任31 分钟前
在亚马逊云上,如何基于 VPC IPAM 的 ALB 公网 IP 预测分配?
后端
不吃肉的羊43 分钟前
PHP设置文件上传最大值
后端·php
专注物联网全栈开发1 小时前
ESP32的IRAM用完了怎么优化
后端
雨落倾城夏未凉1 小时前
7.QObject定时器和QTimer定时器的区别
后端·qt
洗澡水加冰1 小时前
RAG系统工程化
后端·aigc
paopaokaka_luck1 小时前
智能推荐社交分享小程序(websocket即时通讯、协同过滤算法、时间衰减因子模型、热度得分算法)
数据库·vue.js·spring boot·后端·websocket·小程序
程序员NEO1 小时前
Spring AI 对话记忆大揭秘:服务器重启,聊天记录不再丢失!
人工智能·后端