Achieved points: 1, and the timing of the message table build a native task of ensuring reliable message transmission; 2, RabbitMQ reliable consumption; 3, redis ensure idempotent
Two services: order service and messaging services
Order service relevant code, the key code has notes
spring: datasource: druid: url: jdbc:postgresql://127.0.0.1:5432/test01?characterEncoding=utf-8 username: admin password: 123456 driver-class-name: org.postgresql.Driver initial-size: 1 max-active: 20 max-wait: 6000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000 min-idle: 1 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: select 1 test-while-idle: true test-on-borrow: false test-on-return: false rabbitmq: username: guest password: guest host: 127.0.0.1 virtual-host: / port: 5672 publisher-confirms: true server: port: 8087 logging: level: org.springframework.jdbc.core.JdbcTemplate: DEBUG
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
package com.jlwj.mqtransaction.bean; import lombok.Data; import java.io.Serializable; /** * @author hehang on 2019-06-27 * @descriptionsdf */ @Data public class OrderBean implements Serializable { private String orderNo; private String orderInfo; }
package com.jlwj.mqtransaction.service; import com.alibaba.fastjson.JSON; import com.jlwj.mqtransaction.bean.OrderBean; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; /** * @author hehang ON 2019-07-01 * @description launch message to the MQ * / @Service @ SLF4J public class RabbitMQService { @Autowired Private RabbitTemplate rabbitTemplate; @Autowired Private the JdbcTemplate the jdbcTemplate; @PostConstruct Private void initRabbitTemplate () { // setup message callback sends an acknowledgment, a state table update message has been sent successfully rabbitTemplate.setConfirmCallback ((correlationData correlationData, Boolean ACK, the cause is String) -> { log.info (String.valueOf (ACK)); IF (ACK) { String SQL= "update t_confirm set send_status = ? where id = ?"; jdbcTemplate.update(sql,1,correlationData.getId()); } }); } public void sendMessage(OrderBean orderBean){ rabbitTemplate.convertAndSend("orderExchange","orderRoutingKey", JSON.toJSONString(orderBean), message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);return message;}, new CorrelationData(orderBean.getOrderNo())); } }
package com.jlwj.mqtransaction.service; import com.alibaba.fastjson.JSON; import com.jlwj.mqtransaction.bean.OrderBean; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; importorg.springframework.stereotype.Service; Import org.springframework.transaction.annotation.Propagation; Import org.springframework.transaction.annotation.Transactional; Import javax.annotation.PostConstruct; / ** * @author hehang ON 2019-06-27 * @description order service, no longer write simple interfaces period * / @Service @ SLF4J public class OrderService { @Autowired Private the jdbcTemplate jdbcTemplate; @Autowired Private RabbitMQService rabbitMQService; // note plus Affairs notes the @Transactional (propagation = Propagation.REQUIRED) public void save(OrderBean orderBean){ String sql1 = "insert into t_order(order_no,order_info) values (?,?)"; String sql2 = "insert into t_confirm(id,message_info,send_status) values (?,?,?)"; jdbcTemplate.update(sql1,orderBean.getOrderNo(),orderBean.getOrderInfo()); jdbcTemplate.update(sql2,orderBean.getOrderNo(), JSON.toJSONString(orderBean),0); rabbitMQService.sendMessage(orderBean); } }
package com.jlwj.mqtransaction.service; import com.jlwj.mqtransaction.bean.OrderBean; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.util.List; /** * @author hehang on 2019-07-01 * @description 定时扫描confirm表,重复发送未发送的消息 */ @Service @Slf4j public class OrderScheduleService { @Autowired Private the JdbcTemplate the jdbcTemplate; @Autowired Private RabbitMQService rabbitMQService; // timing of scanning the recording sheet, and sends the status message sent again 0, the number of retransmissions can be recorded even when the need for human intervention, the need for separate production environment Timing deployment task @Scheduled (the cron = "* * * * 30/30?" ) public void scanOrder () { log.info ( "scan plane confirm the timing table" ); String SQL . = "SELECT * O O t_order from the Join ON = C o.order_no c.ID t_confirm WHERE c.send_status = 0 " ; List <the OrderBean> orderBeanList jdbcTemplate.queryForList = (. SQL, the OrderBean class ); for (OrderBean orderBean : orderBeanList) { rabbitMQService.sendMessage(orderBean); } } }
Messaging services relevant code
spring: rabbitmq: username: guest password: guest host: 127.0.0.1 virtual-host: / port: 5672 listener: simple: acknowledge-mode: manual redis: host: 127.0.0.1 port: 6379 timeout: 5000 jedis: pool: max-idle: 8 min-idle: 0 max-active: 8 max-wait: 1
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
package com.jlwj.messageservice.config; import org.springframework.amqp.core.*; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; /** * @author hehang on 2019-06-27 * @descriptionmq配置类 */ @Configuration public class RabbitConfig { /** * 死信队列 * @return */ @Bean public Queue dlQueue(){ return QueueBuilder.durable("dlQueue") .build(); } @Bean public DirectExchange dlExchange(){ return (DirectExchange) ExchangeBuilder.directExchange("dlExchange").build(); } @Bean public Binding dlMessageBinding(){ return BindingBuilder.bind(dlQueue()).to(dlExchange()).with("dlRoutingKey"); } @Bean public DirectExchange messageDirectExchange() { return (DirectExchange) ExchangeBuilder.directExchange("orderExchange") .durable(true) .build(); } @Bean public Queue messageQueue() { return QueueBuilder.durable("orderQueue") //配置死信 .withArgument("x-dead-letter-exchange","dlExchange") .withArgument("x-dead-letter-routing-key","dlRoutingKey") .build(); } @Bean public Binding messageBinding() { return BindingBuilder.bind(messageQueue()) .to(messageDirectExchange()) .with("orderRoutingKey"); } }
package com.jlwj.messageservice.listener; import com.alibaba.fastjson.JSON; import com.jlwj.messageservice.bean.OrderBean; import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.stereotype.Component; import java.io.IOException; /** * @author hehang on 2019-06-28 * @description订单监听 */ @Component @Slf4j public class OrderListener { @Autowired private StringRedisTemplate stringRedisTemplate; @RabbitListener(queues = "orderQueue") public void HandlerMessage(Channel channel, @Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long tag, @Header(AmqpHeaders.REDELIVERED) boolean reDelivered ) throws IOException { log.info(message); OrderBean orderBean = JSON.parseObject(message,OrderBean.class); try { log.info("收到的消息为{}",JSON.toJSONString(orderBean)); //保证幂等性 if(stringRedisTemplate.opsForValue().get(orderBean.getOrderNo())==null){ sendMessage(orderBean); stringRedisTemplate.opsForValue () SET (orderBean.getOrderNo (),. ". 1" ); } channel.basicAck (Tag, to false ); } the catch (Exception E) { IF (redelivered) { log.info ( "processed message has been repeated failure: {} " , message); channel.basicReject (Tag, to false ); } the else { log.error ( " treatment failure message " , E); // re-queued one channel.basicNack (Tag, to false , to true ) ; } } } Private void the sendMessage (the OrderBean orderBean) throws Exception { IF (. OrderBean.getOrderNo () the equals ( "0007" )) { int A =. 3/0 ; } log.info ( "analog SMS" ); } }
package com.jlwj.messageservice.listener; import com.alibaba.fastjson.JSON; import com.jlwj.messageservice.bean.OrderBean; import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.stereotype.Component; import java.io.IOException; /** * @author hehang on 2019-06-28 * @description订单监听 */ @Component @Slf4j public class DlListener { @Autowired private StringRedisTemplate stringRedisTemplate; @RabbitListener(queues = "dlQueue") public void HandlerMessage(Channel channel, Message message, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException { log.info ( new new String (message.getBody ())); // manual processing, dead letter queue message handlerDl ( new new String (message.getBody ())); channel.basicAck (Tag, to false ); } Private void handlerDl (String message) { log.info ( "E-mail, requesting manual intervention: {}" , message); } }