傳統的項目開發中業務流程以串行方式,執行了模塊1—》模塊2–》模塊3
而我們知道,這個執行流程其實對於整個程序來講是有一定的弊端的,主要有幾點:
(1)整個流程的執行響應等待時間比較長;
(2)如果某一個模塊發生異常,可能會影響其他 模塊甚至整個系統的執行流程與結果;
(3)程序的代碼上造成冗余,模塊與模塊需要進行強通信以及數據的交互,出現問題時難以定位與維護。耦合度過高!
因此需要進行優化,將強關聯的業務模塊解耦以及某些模塊之間實行異步通信!
下面我將通過一些實際案例來進行一個介紹
一、異步記錄用戶操作日志
用戶操作日志對於每一個系統來說是不可或缺的,並且操作日志應該單獨抽取為一個模塊業務,不應該與主業務系統之間耦合在一起。
故而我們需要將其單獨抽出並以異步的方式與主模塊進行異步通信交互數據。
要求:采用 RabbitMQ 的 DirectExchange+RoutingKey 消息模型來實現【異步記錄用戶操作日志】
前提:SpringBoot與RabbitMQ的整合前面章節已經講述過了,就不再重述了。
第一步:創建消息模型:包括 Queue、Exchange、RoutingKey 等的建立
package com.springboot.rabbitmq.example.demo1.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
* Direct Exchange(路由模式)—異步記錄用戶操作日志
* @author Mr yi
* @time 2019年6月19日
*/
@Configuration
public class RabbitConfigDemo1 {
//消息隊列名稱
final static String queue = "queue_demo1";
/**
* 交換機名稱
*/
final static String exchange = "deom1Exchange";
@Bean
public Queue queueDemo1() {
return new Queue(RabbitConfigDemo1.queue);
}
/**
*
* @method 聲明一個direct類型的交換機
* @author Mr yi
* @time 2019年6月19日
* @return
*/
@Bean
DirectExchange exchangeDemo1() {
return new DirectExchange(RabbitConfigDemo1.exchange);
}
/**
*
* @method 綁定Queue隊列到交換機,並且指定routingKey
* @author Mr yi
* @time 2019年6月19日
* @param queueDemo1 對應注入queueDemo1()方法
* @param exchangeDemo1 對應exchangeDemo1()
* @return
*/
@Bean
Binding bindingDirectExchangeDemo1(Queue queueDemo1, DirectExchange exchangeDemo1) {
return BindingBuilder.bind(queueDemo1).to(exchangeDemo1).with("keyDemo1");
}
}
第二步:創建生產者,這里的生產者負責發送用戶日志信息到rabbitmq
其中需要建立User 用戶實體類、UserLog用戶日志實體類,這里就不介紹了。
注意一點:rabbitmq消息隊列可以接受任何形式的消息數據。
package com.springboot.rabbitmq.example.demo1.producers;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.springboot.rabbitmq.example.demo1.entity.UserLog;
/**
*
* @method 生產者
* @author Mr yi
* @time 2019年6月19日
*/
@Component
public class ProducersDemo1 {
@Autowired
private AmqpTemplate amqpTemplate;
/**
*
* @method 生產者發送消息,direct模式下需要傳遞一個routingKey
* @author Mr yi
* @time 2019年6月19日
* @throws Exception
*/
public void send(UserLog userLog) throws Exception {
System.out.println("發送用戶日志信息到rabbitmq: " + userLog.toString());
this.amqpTemplate.convertAndSend("deom1Exchange", "keyDemo1", userLog);
}
}
第三步:創建消費者,即接受到rabbitmq傳過來的消息后進行操作(如持久化用戶日志信息)
我這里只提供了核心代碼,如持久化操作模擬執行即可。
package com.springboot.rabbitmq.example.demo1.consumers;
import java.util.Date;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import com.springboot.rabbitmq.example.demo1.entity.UserLog;
/**
*
* @method 抽離日志系統為一個消費者
* @author Mr yi
* @time 2019年6月19日
*/
@Component
@RabbitListener(queues = "queue_demo1")
public class ConsumersLogDemo1 {
@RabbitHandler
public void process(UserLog userLog) {
System.out.println("log_name="+userLog.getLog_name());
System.out.println("user_name="+userLog.getUser_name());
System.out.println("log_type="+userLog.getLog_type());
//將日志信息進行持久化操作(這里沒有寫操作數據庫方法,模擬執行)
//save(userLog);無錫人流多少錢 http://www.bhnfkyy.com/
System.out.println("用戶日志成功錄入!");
}
}
第四步:在 Controller 中執行用戶登錄邏輯
我們這里模擬用戶登錄操作,用戶登錄后,會發送用戶登錄操作日志到rabbitmq隊列中,由綁定了此隊列的消費者(即用戶日志模塊系統)接受消息
package com.springboot.rabbitmq.example.demo1.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.springboot.rabbitmq.example.demo1.entity.UserLog;
import com.springboot.rabbitmq.example.demo1.producers.ProducersDemo1;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j
@RequestMapping("/userdemo1")
public class UserController {
@Autowired
private ProducersDemo1 producers;
@RequestMapping("/login")
public String login() throws Exception {
String user_name = "admin";
String pwd = "123456";
if(user_name=="admin" && pwd=="123456") {
log.info("用戶登錄成功!");
UserLog userLog = new UserLog();
userLog.setLog_name("用戶登錄").setLog_type("login").setUser_name(user_name);
producers.send(userLog);
log.info("異步記錄用戶操作日志!");
}
return "success";
}
}
控制台數據結果
隊列
隊列綁定的routing key
exchange交換機
