目录
主要侧重于各个微服务之间发送消息,至于业务逻辑会简化。
前一章的交换机和队列的绑定会放到异步线程中,同时将该异步线程放到线程池中,然后在另一个类中通过@Configuration 和 @Autowired 配合,自动调用线程池。
微服务发送消息使用channel ,使用channel.basicPublish发送消息,使用channel.basicConsume 和DeliverCallback来监听消息的到达
1.订单微服务
1.用户下单,订单微服务向商家微服务发送消息:
使用channel来发送消息,至于队列和交换的绑定,在上一章节已经实现了,这里直接使用
channel发送消息
/**
* 使用channel发送消息【因为channel是客户端和mq通信的逻辑对象】
* 参数:
* 1. exchange 是原先声明的
* 2. "key.restaurant" 是 routing key ,这是餐厅微服务声明的,
* 餐厅需要将自己的队列用routing key 将自己的队列绑定在交换机上
* 3. 消息特殊参数没有,所以设置为null
* 4. rabbitmq 的收发是通过字节进行的,所以将 String 转成字节
*/
代码是service层的,在controller层直接调用即可
public void createOrder(OrderCreateVO orderCreateVO) throws IOException, TimeoutException {
log.info("createOrder:orderCreateVO:{}", orderCreateVO);
OrderDetailPO orderPO = new OrderDetailPO();
orderPO.setAddress(orderCreateVO.getAddress());
//省略...
orderPO.setStatus(OrderStatus.ORDER_CREATING);
orderPO.setDate(new Date());
orderDetailDao.insert(orderPO);
OrderMessageDTO orderMessageDTO = new OrderMessageDTO();
orderMessageDTO.setOrderId(orderPO.getId());
orderMessageDTO.setProductId(orderPO.getProductId());
orderMessageDTO.setAccountId(orderCreateVO.getAccountId());
/***
* 重要内容
*/
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
//就是将 orderMessageDTO 转成json 字符串
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
}
2.订单微服务还需要接收商家微服务的消息:
接收使用 DeliverCallback这个接口,要在原先设置交换机和队列类中设置监听,队列收到消息后,就要调用DeliverCallback这个接口。
在handleMessage【上一章节,自定义的用来设置交换机和队列类】方法中,添加:
channel.basicConsume("queue.order", true, deliverCallback, consumerTag -> {
});
channel.basicConsume参数含义: 1."queue.order" 是订单微服务用来接收消息设置的routing key, 2..true这个先不用管,后面章节会讲解, 3.deliverCallback是用来接收消息的具体实现方法,当channel监听到有其他微服务向订单微服务发送消息,就调用该方法, 4.consumerTag暂时用不到,所以{}为空。
DeliverCallback deliverCallback = (consumerTag, message) -> {
String messageBody = new String(message.getBody());
log.info("deliverCallback:messageBody:{}", messageBody);
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try {
OrderMessageDTO orderMessageDTO = objectMapper.readValue(messageBody,
OrderMessageDTO.class);
OrderDetailPO orderPO = orderDetailDao.selectOrder(orderMessageDTO.getOrderId());
switch (orderPO.getStatus()) {
case ORDER_CREATING:
if (orderMessageDTO.getConfirmed() && null != orderMessageDTO.getPrice()) {
orderPO.setStatus(OrderStatus.RESTAURANT_CONFIRMED);
orderPO.setPrice(orderMessageDTO.getPrice());
orderDetailDao.update(orderPO);
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.deliveryman", "key.deliveryman", null,
messageToSend.getBytes());
}
} else {
orderPO.setStatus(OrderStatus.FAILED);
orderDetailDao.update(orderPO);
}
break;
case RESTAURANT_CONFIRMED:
if (null != orderMessageDTO.getDeliverymanId()) {
orderPO.setStatus(OrderStatus.DELIVERYMAN_CONFIRMED);
orderPO.setDeliverymanId(orderMessageDTO.getDeliverymanId());
orderDetailDao.update(orderPO);
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.settlement", "key.settlement", null,
messageToSend.getBytes());
}
} else {
orderPO.setStatus(OrderStatus.FAILED);
orderDetailDao.update(orderPO);
}
break;
case DELIVERYMAN_CONFIRMED:
if (null != orderMessageDTO.getSettlementId()) {
orderPO.setStatus(OrderStatus.SETTLEMENT_CONFIRMED);
orderPO.setSettlementId(orderMessageDTO.getSettlementId());
orderDetailDao.update(orderPO);
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.reward", "key.reward", null, messageToSend.getBytes());
}
} else {
orderPO.setStatus(OrderStatus.FAILED);
orderDetailDao.update(orderPO);
}
break;
case SETTLEMENT_CONFIRMED:
if (null != orderMessageDTO.getRewardId()) {
orderPO.setStatus(OrderStatus.ORDER_CREATED);
orderPO.setRewardId(orderMessageDTO.getRewardId());
orderDetailDao.update(orderPO);
} else {
orderPO.setStatus(OrderStatus.FAILED);
orderDetailDao.update(orderPO);
}
break;
}
} catch (JsonProcessingException | TimeoutException e) {
e.printStackTrace();
}
};
将handleMessage()方法使用异步线程调用,即在 handleMessage方法上添加@Async 注解
@Async
public void handleMessage() throws IOException, TimeoutException, InterruptedException {
略....
}
还需要在启动类上添加 @EnableAsync 注解
@SpringBootApplication
@MapperScan("com.imooc.food")
@ComponentScan("com.imooc.food")
@EnableAsync
public class OrderServiceManagerApplication {
略....
}
为了安全起见,需要将该异步线程放入线程池中,即使目前只有一个线程
@Configuration
@EnableAsync
public class AsyncTaskConfig implements AsyncConfigurer {
// ThredPoolTaskExcutor的处理流程
// 当池子大小小于corePoolSize,就新建线程,并处理请求
// 当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去workQueue中取任务并处理
// 当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
// 当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁
@Override
@Bean
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
//设置核心线程数
threadPool.setCorePoolSize(10);
//设置最大线程数
threadPool.setMaxPoolSize(100);
//线程池所使用的缓冲队列
threadPool.setQueueCapacity(10);
//等待任务在关机时完成--表明等待所有线程执行完
threadPool.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
threadPool.setAwaitTerminationSeconds(60);
// 线程名称前缀
threadPool.setThreadNamePrefix("Rabbit-Async-");
// 初始化线程
threadPool.initialize();
return threadPool;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
线程的方法由另一个类启动:
@Slf4j
@Configuration
public class RabbitConfig {
@Autowired
OrderMessageService orderMessageService;
@Autowired
public void startListenMessage() throws IOException, TimeoutException, InterruptedException {
orderMessageService.handleMessage();
}
}
3.总结:
springboot启动时,会找配有@Configuration的所有的类,然后会自动调用类中含有@Autowired的方法,所以处理消息的handleMessage()方法会被调用,由于该方法上有 @Async注解,会启动异步线程来调用该方法,由于声明了一个线程池,所以异步线程会在线程池中启动
2.商家微服务:
商户微服务的业务的起点就是收到订单微服务的消息,才开始工作
@Slf4j
@Service
public class OrderMessageService {
ObjectMapper objectMapper = new ObjectMapper();
@Autowired
ProductDao productDao;
@Autowired
RestaurantDao restaurantDao;
@Async
public void handleMessage() throws IOException, TimeoutException, InterruptedException {
log.info("start linstening message");
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
channel.exchangeDeclare(
"exchange.order.restaurant",
BuiltinExchangeType.DIRECT,
true,
false,
null);
channel.queueDeclare(
"queue.restaurant",
true,
false,
false,
null);
channel.queueBind(
"queue.restaurant",
"exchange.order.restaurant",
"key.restaurant");
channel.basicConsume("queue.restaurant", true, deliverCallback, consumerTag -> {
});
while (true) {
Thread.sleep(100000);
}
}
}
DeliverCallback deliverCallback = (consumerTag, message) -> {
String messageBody = new String(message.getBody());
log.info("deliverCallback:messageBody:{}", messageBody);
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try {
OrderMessageDTO orderMessageDTO = objectMapper.readValue(messageBody,
OrderMessageDTO.class);
ProductPO productPO = productDao.selsctProduct(orderMessageDTO.getProductId());
log.info("onMessage:productPO:{}", productPO);
RestaurantPO restaurantPO = restaurantDao.selsctRestaurant(productPO.getRestaurantId());
log.info("onMessage:restaurantPO:{}", restaurantPO);
if (ProductStatus.AVALIABIE == productPO.getStatus() && RestaurantStatus.OPEN == restaurantPO.getStatus()) {
orderMessageDTO.setConfirmed(true);
orderMessageDTO.setPrice(productPO.getPrice());
} else {
orderMessageDTO.setConfirmed(false);
}
log.info("sendMessage:restaurantOrderMessageDTO:{}", orderMessageDTO);
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.restaurant", "key.order", null, messageToSend.getBytes());
}
} catch (JsonProcessingException | TimeoutException e) {
e.printStackTrace();
}
};
}
和 订单微服务类似,都要先声明交换机和队列,绑定自己的routing key,然后才发送消息,也是用异步线程启动handleMessage,同时将异步线程放到线程池中。【和订单微服务一样,这里省略】
3. 目前不足之处
1.消息真的发出去了吗?
1.1 消息发送后,发送端不知道RabbitMQ是否真的收到了消息
1.2 若RabbitMQ异常,消息丢失后,订单处理流程停止,业务异常
所以需要使用RabbitMQ的发送端确认机制,确认消息发送
2.消息真的被路由了吗?
消息发送给,发送端不知道消息是否被正确路由,若路由异常,消息会被丢弃
消息丢弃后,订单处理流程停止,业务异常
所以需要使用RabbitMQ的消息返回机制,确认消息被正确路由
3.消费端处理的过来吗?
业务高峰期,可能会出现发送端与接收端性能不一致,大量消息被同时推送给接收端,造成接收端服务崩溃
所以需要使用RabbitMQ的 消费端限流机制,限制消息推送速度,保障接收端服务稳定。
4.消费端处理异常怎么办?
默认情况下,消费端接收消息时,消息会被自动确认(ACK),这样消费端处理异常时,发送端与消息中间件无法得知消息处理情况。
所以需要使用RabbitMQ的消费端确认机制,正确消息被正确处理。
5.队列爆满怎么办?
默认情况下,消息进入队列,会永远存在,直到被消费,
大量堆积的消息会给RabbitMQ产生很大的压力,
需要使用RabbitMQ消息过期时间机制,防止消息大量积压
6.如何转移过期消息?
消息被设置了过期时间,过期后会直接被丢弃
直接被丢弃的消息,是无法对系统运行异常发出警报
需要使用RabbitMQ死信队列,收集过期消息,以供分析
4.总结
目前项目急需引入的RabbitMQ新特性:
1.发送端确认机制
2.消息返回机制
3.消费端限流机制
4.消费端确认机制
5.消息过期机制
6.死信队列