分布式任务调度Elastic-Job保证RabbitMQ 消息投递可靠性

一种可靠性方案

在这里插入图片描述

数据库设计

-- 表 broker_message_log 消息记录结构
DROP TABLE IF EXISTS `broker_message_log`;
CREATE TABLE `broker_message_log` (
  `message_id` varchar(128) NOT NULL, -- 消息唯一ID
  `message` varchar(4000) DEFAULT NULL, -- 消息内容
  `try_count` int(4) DEFAULT '0', -- 重试次数
  `status` varchar(10) DEFAULT '', -- 消息投递状态  0 投递中 1 投递成功   2 投递失败
  `next_retry` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',--下一次重试时间 或 超时时间
  `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',--创建时间
  `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' --更新时间
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

数据库表消息记录可能的状态

在这里插入图片描述
发送成功流程:
1:消息入库,状态如:message_id=1
2:发送消息
3:收到ack,状态如:message_id=2
发送失败流程:
1:消息入库,状态如:message_id=3
2:发送消息
3:定时任务扫描数据表中状态为发送中且超时的数据,如果重试次数小于指定次数,将重试次数增加,重新发送消息,状态如:message_id=4,message_id=5,message_id=6,否则更改状态为失败,状态如:message_id=7
若发送失败,则人工进行干预

rabbitmq 发送Confirm消息

rabbitmq 有原生channel,rabbitTemplate,spring cloud stream等多种发送方式,此处演示一种。

// 消息入库
brokerMessageLogMapper.insert(brokerMessageLog);
// 设置回调函数
rabbitTemplate.setConfirmCallback(confirmCallback);
// 回调函数
 final ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
     @Override
     public void confirm(CorrelationData correlationData, boolean ack, String cause) {
         String messageId = correlationData.getId();
         if (ack) {
             brokerMessageLogMapper.changeBrokerMessageLogStatus(messageId, Constants.ORDER_SEND_SUCCESS, new Date());
         } else {
             //失败则进行具体的后续操作:重试 或者补偿等手段
         }
     }
 };
// 开始发送
rabbitTemplate.convertAndSend

分布式任务调度Elastic-Job

用传统的Thread+Sleep、 java.util.Timer、Schedule都不满足分布式场景,因为一个应用会部署多个实例,导致重复执行,分布式任务调度有多种,拿Elastic-Job 为例,他依赖于Zookeeper注册中心,负责管理具体的调度任务。
在这里插入图片描述
Elastic-Job 依赖于zookeeper,zookeeper用来选举节点与存储调度任务。
上图zookeeper状态表示我们启动了两个实例,一个分片,真正正在执行调度的是192.168.132.1@-@23620实例。当一个实例挂掉,我们会看到sharding下面会有临时节点,这样会被重新调度到另外一个实例。

启动定时任务

import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.simple.SimpleJobConfiguration;
import com.dangdang.ddframe.job.lite.api.JobScheduler;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.reg.base.CoordinatorRegistryCenter;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;

public final class JavaMain {
    
    private static final int EMBED_ZOOKEEPER_PORT = 2181;
    private static final String ZOOKEEPER_CONNECTION_STRING = "localhost:" + EMBED_ZOOKEEPER_PORT;
    private static final String JOB_NAMESPACE = "rabbit";
    private static final String JOB_NAME = "msg_sql_check";

    public static void main(final String[] args) {
        CoordinatorRegistryCenter regCenter = setUpRegistryCenter();
        setUpSimpleJob(regCenter);
    }

    /**
     * 获取zookeeper注册中心
     *
     * @return CoordinatorRegistryCenter zookeeper注册中心
     */
    private static CoordinatorRegistryCenter setUpRegistryCenter() {
        ZookeeperConfiguration zkConfig = new ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, JOB_NAMESPACE);
        CoordinatorRegistryCenter result = new ZookeeperRegistryCenter(zkConfig);
        result.init();
        return result;
    }

    /**
     * 任务的配置与启动
     *
     * @param regCenter zookeeper注册中心
     */
    private static void setUpSimpleJob(final CoordinatorRegistryCenter regCenter) {
        JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder(JOB_NAME, "0/30 * * * * ?", 1).build();
        SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(coreConfig, JavaSimpleJob.class.getCanonicalName());
        new JobScheduler(regCenter, LiteJobConfiguration.newBuilder(simpleJobConfig).build()).init();
    }
}

定时任务核心逻辑

import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;

public class JavaSimpleJob implements SimpleJob {

    @Override
    public void execute(final ShardingContext shardingContext) {
             // 1. 扫描数据表中状态为发送中且超时的数据
        List<BrokerMessageLog> list = brokerMessageLogMapper.query4StatusAndTimeoutMessage();
        list.forEach(messageLog -> {
            // 2. 如果重试次数大于指定次数, 将消息状态更改为失败
            if (messageLog.getTryCount() >= 3) {
                brokerMessageLogMapper.changeBrokerMessageLogStatus(messageLog.getMessageId(), Constants.ORDER_SEND_FAILURE, new Date());
            } else {
                // 3. 将重试次数增加,并重新发送消息
                brokerMessageLogMapper.updateReSend(messageLog.getMessageId(), new Date());
                // TODO: 重新发送
            }
        });
    }
}

更多前沿技术,面试技巧,内推信息请扫码关注公众号“云计算平台技术”
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值