实现思路:采取消息冗余+定时器
实际上就是保证消息的成功投递
例子:订单(生产者)+派送(消费者) springboot+rabbitmq在Windows环境下实现
pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置文件yml:
server:
port: 8012
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/rabbit_test_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
driver-class-name: com.mysql.jdbc.Driver
rabbitmq:
# username: guest
# password: guest
# host: 127.0.0.1
# virtual-host: /
# port: 5567
publisher-confirm-type: correlated
由于是本地搭建的环境,只需要配置一下手动确认就可以了,账号密码用的也是默认的,如果修改了,可以配置一下。
一般有订单,肯定会有一个订单表,在此基础上,加上一个订单的消息表,来存储订单的消息投递情况,默认值为0,表示消息投递失败(定时器主要跑的就是这类订单);如果消息投递成功,修改订单状态为1;如果异常处理也失败了,修改单子的状态为2,这个可以人为排查。
order表
order_message消息状态表
也就是消息冗余的意思;两个表用order_id关联;order_id作为主键,避免出现幂等性问题;创建两个的model,不创建也可以,本例采用的jdbc;
构建交换机和队列
@Configuration
public class OrderConfiguration {
@Bean
public FanoutExchange orderExchange(){
return new FanoutExchange("order_fanout_exchange",true,false);
}
@Bean
public Queue orderQueue(){
Map<String,Object> args = new HashMap<>();//绑定死信队列的交换机
args.put("x-message-ttl",60000);//key是一定的,设置队列中消息的过期时间
args.put("x-dead-letter-exchange","order_dead_exchange");//死信队列
return new Queue("order_queue",true,false,false,args);
}
@Bean
public Queue orderDeadQueue(){
return new Queue("order_dead_queue",true,false,false);
}
@Bean
public FanoutExchange orderDeadExchange(){
return new FanoutExchange("order_dead_exchange",true,false);
}
@Bean
public Binding orderBinding(){
return BindingBuilder.bind(orderQueue()).to(orderExchange());
}
@Bean
public Binding orderDeadBinding(){
return BindingBuilder.bind(orderDeadQueue()).to(orderDeadExchange());
}
}
生产者产出消息
/**
* 开启事务保证订单操作一致性
* 采用分布式事务确保各个服务之间的数据一致性
* @throws Exception
*/
@Transactional(rollbackFor = Exception.class)
public void makeOrder() throws Exception{
String addOrderSql = "insert into `order`(order_id,order_name,description,user_id,create_time) values(?,?,?,?,?)";
Order order = new Order();
String orderId = UUID.randomUUID().toString();
order.setOrderId(orderId);
order.setOrderName("小浣熊");
order.setDescription("太给力了叭");
order.setUserId("123456789");
order.setCreateTime(new Date());
//记录订单信息
int result = jdbcTemplate.update(addOrderSql,
order.getOrderId(),
order.getOrderName(),
order.getDescription(),
order.getUserId(),
order.getCreateTime());
if(result != 1){
throw new Exception("订单创建失败,失败原因:数据库操作失败");
}
//记录订单的消息状态,初始为0,消息投递成功在回调方法中修改为1,若果异常处理中没有处理成功,状态修改为2
String orderMessSql = "insert into order_message(order_id,status,create_time) values(?,?,?)";
jdbcTemplate.update(orderMessSql,orderId,0,new Date());
//发送消息
rabbitTemplate.convertAndSend("order_fanout_exchange","", JSONObject.toJSONString(order),new CorrelationData(orderId));
}
使用java的注解@PostConstruct实现回调
/**
* 设置消息发送确认回调,修改发送成功消息的状态,确保消息投递成功
*/
@PostConstruct
public void regCallBack(){
//@param correlationData 相关配置信息
//@param ack 布尔类型,消息是否投递到交换机,是true否false
//@param cause 字符串String,投递失败的原因
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
System.out.println(correlationData);
System.out.println(ack);
System.out.println(cause);
String orderId = correlationData.getId();
//如果ack为true,表名消息 已经收到
if(!ack){
System.out.println("队列应答失败");
return;
}
try {
//消息投递成功,修改消息状态
String updateMessSql = "update order_message set `status` = ? ,modify_time = ? where order_id = ?";
jdbcTemplate.update(updateMessSql,1,new Date(),orderId);
}catch (Exception e){
System.out.println("本地消息状态修改失败,出现异常:"+e.getMessage());
}
});
}
该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
包不要搞错了!!!详解查看csdn,直接搜索。
import javax.annotation.PostConstruct;
注入
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
JdbcTemplate jdbcTemplate;
测试类启动
@Test
void contextLoads() throws Exception {
orderServiceImpl.makeOrder();
}
消息投递成功