Java Second Kill System Actual Series ~Integrating RabbitMQ to Achieve Asynchronous Message Sending

Posted by ksukat on Thu, 08 Aug 2019 09:45:15 +0200

Summary:

This blog is the eighth in the "Java Second Kill System Practical Series Articles". In this article, we will integrate the message middleware RabbitMQ, including adding dependencies, adding configuration information and customizing injection of related operational components, such as Rabbit Template, etc. Finally, we will initially realize the sending and receiving of messages, and in the next article. Chapter integrates it with mail service to realize the function of "user secondkill successfully sending mail notification message"!

Contents:

RabbitMQ is a message middleware which has been widely used in the market. It can realize the functions of asynchronous message communication, decoupling of business service module, interface current limiting, message distribution and so on. It can be said in the framework of micro-service and distributed system. It's playing a great role! (Detailed introduction, Debug will not be repeated here, you can go to the official website to see more of its introduction and its typical application scenarios)!

In this blog post, we will use RabbitMQ as the component of message sending, and combine it with the "mail service" introduced in the following chapter to realize "Asynchronous sending mail notification message after the success of the second killing, informing the user that the second killing has been successful!" Now let's get into the code war.

(1) To use RabbitMQ, you need to install RabbitMQ service in your local development environment or server. As shown in the following figure, Debug accesses the home page of its back-end console application after successfully installing RabbitMQ service locally:

Then we started integrating it with SpringBook. First, you need to add its dependencies. The version number is the same as that of SpringBoot. The version number is 1.5.7.RELEASE:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

Then you need to add RabbitMQ service-related configurations in the configuration file application.properties, such as Host, Port, etc. where the service is located:

#rabbitmq
spring.rabbitmq.virtual-host=/
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

spring.rabbitmq.listener.simple.concurrency=5
spring.rabbitmq.listener.simple.max-concurrency=15
spring.rabbitmq.listener.simple.prefetch=10

(2) Next, we use SpringBoot's natural features to automatically inject the configuration of RabbitMQ components, including its "single instance consumer" configuration, the "multi-instance consumer" configuration and the operation component instance "RabbitTemplate" configuration for sending messages:

//Universal Rabbitmq configuration

@Configuration
public class RabbitmqConfig {
  private final static Logger log = LoggerFactory.getLogger(RabbitmqConfig.class);

  @Autowired
  private Environment env;

  @Autowired
  private CachingConnectionFactory connectionFactory;

  @Autowired
  private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;

  //Single consumer
  @Bean(name = "singleListenerContainer")
  public SimpleRabbitListenerContainerFactory listenerContainer(){
      SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
      factory.setConnectionFactory(connectionFactory);
      factory.setMessageConverter(new Jackson2JsonMessageConverter());
      factory.setConcurrentConsumers(1);
      factory.setMaxConcurrentConsumers(1);
      factory.setPrefetchCount(1);
      factory.setTxSize(1);
      return factory;
  }

  //Multiple consumers
  @Bean(name = "multiListenerContainer")
  public SimpleRabbitListenerContainerFactory multiListenerContainer(){
      SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
      factoryConfigurer.configure(factory,connectionFactory);
      factory.setMessageConverter(new Jackson2JsonMessageConverter());
      //Confirmation of consumption patterns - NONE
      factory.setAcknowledgeMode(AcknowledgeMode.NONE);
      factory.setConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.simple.concurrency",int.class));
      factory.setMaxConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.simple.max-concurrency",int.class));
      factory.setPrefetchCount(env.getProperty("spring.rabbitmq.listener.simple.prefetch",int.class));
      return factory;
  }

  @Bean
  public RabbitTemplate rabbitTemplate(){
      connectionFactory.setPublisherConfirms(true);
      connectionFactory.setPublisherReturns(true);
      RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
      rabbitTemplate.setMandatory(true);
      rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
              log.info("Message sent successfully:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
          }
      });
      rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
          @Override
          public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
              log.warn("Message Loss:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
          }
      });
      return rabbitTemplate;
  }
}

In the configuration of RabbitTemplate, the message sending component of RabbitMQ, we also specially add the output configuration of "message sending confirmation" and "message loss callback", that is, when the message enters the queue correctly, it represents the success of message sending; when the message cannot find the corresponding queue (to some extent, it is actually a search). Output messages will be lost before switching and routing.

(3) After that, we are ready to start using RabbitMQ to send and receive messages. First, we need to create Bean components such as queues, switches, routing, and bindings in the RabbitmqConfig configuration class, as follows:

//Constructing a Message Model for Asynchronous Sending Mailbox Notification
    @Bean
    public Queue successEmailQueue(){
        return new Queue(env.getProperty("mq.kill.item.success.email.queue"),true);
    }

    @Bean
    public TopicExchange successEmailExchange(){
        return new TopicExchange(env.getProperty("mq.kill.item.success.email.exchange"),true,false);
    }

    @Bean
    public Binding successEmailBinding(){
        return BindingBuilder.bind(successEmailQueue()).to(successEmailExchange()).with(env.getProperty("mq.kill.item.success.email.routing.key"));
    }

Among them, the attributes read by env, the instance of environment variable, are configured in the application.properties file, as follows:

mq.env=test

#A Message Model for Successful Asynchronous Sending of Mail by Secondkill
mq.kill.item.success.email.queue=${mq.env}.kill.item.success.email.queue
mq.kill.item.success.email.exchange=${mq.env}.kill.item.success.email.exchange
mq.kill.item.success.email.routing.key=${mq.env}.kill.item.success.email.routing.key

Next, we need to write a method of sending a message in the general messaging service class RabbitSenderService. This method is used to receive the "order number" parameter, then query its corresponding detailed order record in the database, and send the record as a "message" to the queue of RabbitMQ, waiting to be monitored. Listen to consumption:

/**
* RabbitMQ Universal messaging service
* @Author:debug (SteadyJack)
* @Date: 2019/6/21 21:47
**/
@Service
public class RabbitSenderService {

  public static final Logger log= LoggerFactory.getLogger(RabbitSenderService.class);

  @Autowired
  private RabbitTemplate rabbitTemplate;

  @Autowired
  private Environment env;

  @Autowired
  private ItemKillSuccessMapper itemKillSuccessMapper;

  //Successful asynchronous email notification message
  public void sendKillSuccessEmailMsg(String orderNo){
      log.info("Successful asynchronous email notification message-Prepare to send a message:{}",orderNo);

      try {
          if (StringUtils.isNotBlank(orderNo)){
              KillSuccessUserInfo info=itemKillSuccessMapper.selectByCode(orderNo);
              if (info!=null){
                  //TODO:rabbitmq's logic for sending messages
                  rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
                  rabbitTemplate.setExchange(env.getProperty("mq.kill.item.success.email.exchange"));
                  rabbitTemplate.setRoutingKey(env.getProperty("mq.kill.item.success.email.routing.key"));

                  //TODO: Send info as a message to the queue
                  rabbitTemplate.convertAndSend(info, new MessagePostProcessor() {
                      @Override
                      public Message postProcessMessage(Message message) throws AmqpException {
                          MessageProperties messageProperties=message.getMessageProperties();
                          messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                          messageProperties.setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME,KillSuccessUserInfo.class);
                          return message;
                      }
                  });
              }
          }
      }catch (Exception e){
          log.error("Successful asynchronous email notification message-An exception occurred with the message:{}",orderNo,e.fillInStackTrace());
      }
  }
}

(4) Finally, it implements message reception in the general message receiving service class RabbitReceiver Service. Its complete source code is as follows:

/**
 * RabbitMQ Universal Message Receiving Service
 * @Author:debug (SteadyJack)
 * @Date: 2019/6/21 21:47
 **/
@Service
public class RabbitReceiverService {

  public static final Logger log= LoggerFactory.getLogger(RabbitReceiverService.class);

  @Autowired
  private MailService mailService;

  @Autowired
  private Environment env;

  @Autowired
  private ItemKillSuccessMapper itemKillSuccessMapper;

  //Second Kill Asynchronous Mail Notification - Receive Messages
  @RabbitListener(queues = {"${mq.kill.item.success.email.queue}"},containerFactory = "singleListenerContainer")
  public void consumeEmailMsg(KillSuccessUserInfo info){
      try {
          log.info("Second Kill Asynchronous Mail Notification-receive messages:{}",info);
      //At that time, we will integrate the logic of mail service to send mail notification messages.

      }catch (Exception e){
          log.error("Second Kill Asynchronous Mail Notification-receive messages-Abnormal:",e.fillInStackTrace());
      }
  }
}

So far, this article has covered the code warfare of SpringBoot Integration Message Middleware RabbitMQ.

Finally, we need to test that RabbitMQ sends and receives a message after the user initiates a "snap-up" request operation on the interface, if the second kill succeeds, as follows:

Okay, this article will be finished for the time being about the use of RabbitMQ. In the next article, we will integrate RabbitMQ with the mail service to realize the function of asynchronous sending email notification message to the user's mailbox after the success of the second kill. In addition, we will introduce in a later chapter "How to use RabbitMQ's Dead Letter Queue to process orders that have been successfully placed by users but have not been paid in time - where we will take invalid actions".

Supplement:

1. Since the updates of the corresponding blogs may not be very fast, you can read if you want to get a quick start and a real-world system. Design and Practical Video Tutorial of Java Mall Secondary Kill System (SpringBook Edition)

2. At present, the whole construction and code battle of this second kill system have been completed. The complete source code database address can be downloaded here: https://gitee.com/steadyjack/SpringBoot-SecondKill

Topics: Java RabbitMQ Spring SpringBoot