一种可靠性方案
数据库设计
-- 表 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: 重新发送
}
});
}
}
更多前沿技术,面试技巧,内推信息请扫码关注公众号“云计算平台技术”