spring boot rabbitmq_架构 - RabbitMQ消息可靠性投递解决方案(基于SpringBoot实现)

谈到消息的可靠性投递,无法避免的,在实际的工作中会经常碰到,尤其是一些金融行业的核心业务需要保障消息不丢失,接下来我们看一个可靠性投递的流程图,说明可靠性投递的念:

6af2164f5bd24ae58aa9d3dfe0d97c0d

可靠性投递

Step 1: 首先把消息信息(业务数据)存储到数据库中,紧接着,我们再把这个消息记录也存储到一张消息记录表里(或者另外一个同源数据库的消息记录表)

Step 2:发送消息到MQ Broker节点(采用confirm方式发送,会有异步的返回结果)

Step 3、4:生产者端接受MQ Broker节点返回的Confirm确认消息结果,然后进行更新消息记录表里的消息状态。比如默认Status = 0 当收到消息确认成功后,更新为1即可!

Step 5:但是在消息确认这个过程中可能由于网络闪断、MQ Broker端异常等原因导致 回送消息失败或者异常。这个时候就需要发送方(生产者)对消息进行可靠性投递了,保障消息不丢失,100%的投递成功!(有一种极限情况是闪断,Broker返回的成功确认消息,但是生产端由于网络闪断没收到,这个时候重新投递可能会造成消息重复,需要消费端去做幂等处理)所以我们需要有一个定时任务,(比如每5分钟拉取一下处于中间状态的消息,当然这个消息可以设置一个超时时间,比如超过1分钟 Status = 0 ,也就说明了1分钟这个时间窗口内,我们的消息没有被确认,那么会被定时任务拉取出来)

Step 6:接下来我们把中间状态的消息进行重新投递 retry send,继续发送消息到MQ ,当然也可能有多种原因导致发送失败

Step 7:我们可以采用设置最大努力尝试次数,比如投递了3次,还是失败,那么我们可以将最终状态设置为Status = 2 ,最后 交由人工解决处理此类问题(或者把消息转储到失败表中)。

接下来,我们使用SpringBoot2.x 实现这一可靠性投递策略:

  • 数据库库表结构:订单表和消息记录表
-- 表 order 订单结构CREATE TABLE IF NOT EXISTS `t_order` ( `id` varchar(128) NOT NULL, -- 订单ID `name` varchar(128), -- 订单名称 其他业务熟悉忽略 `message_id` varchar(128) NOT NULL, -- 消息唯一ID PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- 表 broker_message_log 消息记录结构CREATE TABLE IF NOT EXISTS `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', -- 更新时间 PRIMARY KEY (`message_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 整合SpringBoot 实现生产端代码如下:pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>4.0.0com.bfxy rabbitmq-springboot-producer 0.0.1-SNAPSHOTjarrabbitmq-springboot-producerrabbitmq-springboot-producerorg.springframework.boot spring-boot-starter-parent 2.0.2.RELEASEUTF-8UTF-81.8org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test testorg.springframework.boot spring-boot-starter-amqp org.mybatis.spring.boot mybatis-spring-boot-starter 1.1.1tk.mybatis mapper-spring-boot-starter 1.1.0com.alibaba druid 1.0.24mysql mysql-connector-java com.github.miemiedev  mybatis-paginator  1.2.17org.mybatis mybatis org.apache.commons commons-lang3 commons-io commons-io 2.4com.alibaba fastjson 1.1.26javax.servlet javax.servlet-api providedlog4j log4j 1.2.17org.springframework.boot spring-boot-maven-plugin 
  • application.properties配置:
spring.rabbitmq.addresses=192.168.11.76:5672spring.rabbitmq.username=guestspring.rabbitmq.password=guestspring.rabbitmq.virtual-host=/spring.rabbitmq.connection-timeout=15000 spring.rabbitmq.publisher-confirms=truespring.rabbitmq.publisher-returns=truespring.rabbitmq.template.mandatory=true server.servlet.context-path=/server.port=8001 spring.http.encoding.charset=UTF-8spring.jackson.date-format=yyyy-MM-dd HH:mm:ssspring.jackson.time-zone=GMT+8spring.jackson.default-property-inclusion=NON_NULL spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=truespring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.username=rootspring.datasource.password=root mybatis.type-aliases-package=com.bfxy.springbootmybatis.mapper-locations=classpath:com/bfxy/springboot/mapping/*.xml logging.level.tk.mybatis=TRACE
  • 数据源druid.properties配置
##下面为连接池的补充设置,应用到上面所有数据源中#初始化大小,最小,最大druid.initialSize=5druid.minIdle=10druid.maxActive=300#配置获取连接等待超时的时间druid.maxWait=60000#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 druid.timeBetweenEvictionRunsMillis=60000#配置一个连接在池中最小生存的时间,单位是毫秒druid.minEvictableIdleTimeMillis=300000druid.validationQuery=SELECT 1 FROM DUALdruid.testWhileIdle=truedruid.testOnBorrow=falsedruid.testOnReturn=false#打开PSCache,并且指定每个连接上PSCache的大小druid.poolPreparedStatements=truedruid.maxPoolPreparedStatementPerConnectionSize=20#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 druid.filters=stat,wall,log4j#通过connectProperties属性来打开mergeSql功能;慢SQL记录druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000#合并多个DruidDataSource的监控数据druid.useGlobalDataSourceStat=true
  • 实体对象:
package com.bfxy.springboot.entity; import java.io.Serializable; public class Order implements Serializable {  private static final long serialVersionUID = 9111357402963030257L;  private String id;  private String name;  private String messageId;  public String getId() { return id; }  public void setId(String id) { this.id = id == null ? null : id.trim(); }  public String getName() { return name; }  public void setName(String name) { this.name = name == null ? null : name.trim(); }  public String getMessageId() { return messageId; }  public void setMessageId(String messageId) { this.messageId = messageId == null ? null : messageId.trim(); }}
package com.bfxy.springboot.entity; import java.util.Date; public class BrokerMessageLog { private String messageId;  private String message;  private Integer tryCount;  private String status;  private Date nextRetry;  private Date createTime;  private Date updateTime;  public String getMessageId() { return messageId; }  public void setMessageId(String messageId) { this.messageId = messageId == null ? null : messageId.trim(); }  public String getMessage() { return message; }  public void setMessage(String message) { this.message = message == null ? null : message.trim(); }  public Integer getTryCount() { return tryCount; }  public void setTryCount(Integer tryCount) { this.tryCount = tryCount; }  public String getStatus() { return status; }  public void setStatus(String status) { this.status = status == null ? null : status.trim(); }  public Date getNextRetry() { return nextRetry; }  public void setNextRetry(Date nextRetry) { this.nextRetry = nextRetry; }  public Date getCreateTime() { return createTime; }  public void setCreateTime(Date createTime) { this.createTime = createTime; }  public Date getUpdateTime() { return updateTime; }  public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }}
  • 数据库连接池代码:
package com.bfxy.springboot.config.database;  import java.sql.SQLException; import javax.sql.DataSource; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement; import com.alibaba.druid.pool.DruidDataSource;  @Configuration@EnableTransactionManagementpublic class DruidDataSourceConfig {  private static Logger logger = LoggerFactory.getLogger(DruidDataSourceConfig.class);  @Autowired private DruidDataSourceSettings druidSettings;  public static String DRIVER_CLASSNAME ;  @Bean public static PropertySourcesPlaceholderConfigurer propertyConfigure(){ return new PropertySourcesPlaceholderConfigurer(); }   @Bean public DataSource dataSource() throws SQLException { DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(druidSettings.getDriverClassName()); DRIVER_CLASSNAME = druidSettings.getDriverClassName(); ds.setUrl(druidSettings.getUrl()); ds.setUsername(druidSettings.getUsername()); ds.setPassword(druidSettings.getPassword()); ds.setInitialSize(druidSettings.getInitialSize()); ds.setMinIdle(druidSettings.getMinIdle()); ds.setMaxActive(druidSettings.getMaxActive()); ds.setTimeBetweenEvictionRunsMillis(druidSettings.getTimeBetweenEvictionRunsMillis()); ds.setMinEvictableIdleTimeMillis(druidSettings.getMinEvictableIdleTimeMillis()); ds.setValidationQuery(druidSettings.getValidationQuery()); ds.setTestWhileIdle(druidSettings.isTestWhileIdle()); ds.setTestOnBorrow(druidSettings.isTestOnBorrow()); ds.setTestOnReturn(druidSettings.isTestOnReturn()); ds.setPoolPreparedStatements(druidSettings.isPoolPreparedStatements()); ds.setMaxPoolPreparedStatementPerConnectionSize(druidSettings.getMaxPoolPreparedStatementPerConnectionSize()); ds.setFilters(druidSettings.getFilters()); ds.setConnectionProperties(druidSettings.getConnectionProperties()); logger.info(" druid datasource config : {} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值