6.微服务之间通过mq发送消息

目录

1.订单微服务

1.用户下单,订单微服务向商家微服务发送消息:

2.订单微服务还需要接收商家微服务的消息:

3.总结:

2.商家微服务:

3. 目前不足之处

1.消息真的发出去了吗?

2.消息真的被路由了吗?

3.消费端处理的过来吗?

4.消费端处理异常怎么办?

5.队列爆满怎么办?

6.如何转移过期消息?

4.总结


主要侧重于各个微服务之间发送消息,至于业务逻辑会简化。

前一章的交换机和队列的绑定会放到异步线程中,同时将该异步线程放到线程池中,然后在另一个类中通过@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.死信队列

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值