一、概述
在微服務架構中,涉及服務與服務間的依賴關系非常複雜,SpringCloud 官方推薦 sleuth + zipkin 方式實作鍊路追蹤,zipkin web 管理頁面中,可以每個請求的依賴關系、請求時間、傳回時間、服務與服務間的依賴圖等。
在本文中主要介紹兩種服務依賴情況
- 微服務調用微服務
- 微服務通過RabbitMQ發送消息
環境依賴:
名稱 | 值 | 備注 |
---|---|---|
JDK | 1.8 | |
RabbitMQ | 3.5.6 | 在示範中需要啟用RabbitMQ,詳細介紹 https://blog.csdn.net/qq_36918149/article/details/100006373 |
SpringCloud | Greenwich.SR1 | |
Consul | 1.5.2 | 注冊中心,Consul安裝及介紹 https://mp.csdn.net/mdeditor/95372805# |
zipkin | 最新版 | zipkin安裝配置 https://mp.csdn.net/mdeditor/100068323# |
示範資料流轉圖:
customer-service 請求下單
waiter-service 生産訂單,MQ通知barista-service制作咖啡,咖啡制作完成後MQ通知customer-service取coffee
barista-service 制作coffee,制作完成後MQ通知waiter-service
二 、代碼
2.1 zipkin 啟動并連上RabbitMQ
java -jar zipkin.jar --zipkin.collector.rabbitmq.addresses=100.61.11.211:5672
2.2 customer-service (顧客服務)
1 ) 主要pom依賴
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
</dependencies>
2 ) 配置檔案
application.properties
spring.application.name=customer-service
server.port=8090
customer.name=spring-${server.port}
#zipkin 位址
spring.zipkin.base-url=http://127.0.0.1:9411/
#zipkin 日志采樣比例
spring.sleuth.sampler.probability=1.0
#zipkin 推送方式
spring.zipkin.sender.type=web
# spring總是輸出日志
spring.output.ansi.enabled=ALWAYS
#rabbitmq配置
spring.rabbitmq.host=100.61.11.211
spring.rabbitmq.port=5672
spring.rabbitmq.username=1234567
spring.rabbitmq.password=1234567
# 名稱為:notifyOrders 的Exchange綁定消費端路由key為spring-8090
spring.cloud.stream.rabbit.bindings.notifyOrders.consumer.binding-routing-key=${customer.name}
3 ) CoffeeOrderService (rpc調用waiter-service接口)
@FeignClient(name = "waiter-service", contextId = "coffeeOrder")
public interface CoffeeOrderService {
@PostMapping("/order/{id}")
boolean createOrder(@PathVariable("id") Long id);
}
4 ) CustomerController(客戶訂單觸發接口)
@RestController
@RequestMapping("/customer")
@Slf4j
public class CustomerController {
@Autowired
private CoffeeOrderService coffeeOrderService;
@PostMapping(value = "/create/order/{id}")
public Boolean createOrder(@PathVariable("id") Long id){
log.info("customer create order no: {}",id);
return coffeeOrderService.createOrder(id);
}
5 ) Waiter (服務啟動時消息綁定接口)
public interface Waiter {
String NOTIFY_ORDERS = "notifyOrders";
@Input(NOTIFY_ORDERS)
SubscribableChannel notification();
}
6 ) NotificationListener(接受coffee訂單用餐通知)
@Component
@Slf4j
public class NotificationListener {
@StreamListener(Waiter.NOTIFY_ORDERS)
public void takeOrder(@Payload Long id) {
log.info("Order No: {} is Fish, I'll take it.", id);
}
}
7 ) CustomerServiceApplication 啟動類
@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
@EnableFeignClients
@EnableAspectJAutoProxy
@EnableBinding(Waiter.class)
public class CustomerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerServiceApplication.class, args);
}
@Bean
public CloseableHttpClient httpClient() {
return HttpClients.custom()
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
.evictIdleConnections(30, TimeUnit.SECONDS)
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.disableAutomaticRetries()
.setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
.build();
}
}
備注:@EnableBinding(Waiter.class) 綁定了MQ接口
2.3 waiter-service(服務員服務)
1 ) 主要pom依賴
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
2 ) 配置檔案
application.properties
spring.application.name=customer-service
server.port=8080
#zipkin 位址
spring.zipkin.base-url=http://127.0.0.1:9411/
#zipkin 日志采樣比例
spring.sleuth.sampler.probability=1.0
#zipkin 推送方式
spring.zipkin.sender.type=web
# spring總是輸出日志
spring.output.ansi.enabled=ALWAYS
#rabbitmq配置
spring.rabbitmq.host=100.61.11.211
spring.rabbitmq.port=5672
spring.rabbitmq.username=1234567
spring.rabbitmq.password=1234567
#Exchange 交換機: finishedOrders 綁定到Queue 隊列: finishedOrders.waiter-service
spring.cloud.stream.bindings.finishedOrders.group=waiter-service
#notifyOrders 生産者路由根據 headers.customer
spring.cloud.stream.rabbit.bindings.notifyOrders.producer.routing-key-expression=headers.customer
3 ) Barista(服務啟動時消息綁定接口)
public interface Barista {
String NEW_ORDERS = "newOrders";
String FINISHED_ORDERS = "finishedOrders";
@Input
SubscribableChannel finishedOrders();
@Output
MessageChannel newOrders();
}
4 ) Customer(服務啟動時消息綁定接口)
public interface Customer {
String NOTIFY_ORDERS = "notifyOrders";
@Output(NOTIFY_ORDERS)
MessageChannel notification();
}
5 ) CoffeeOrderController(提供rpc下單接口,及給barista發下單MQ)
@RestController
@RequestMapping("/order")
@Slf4j
public class CoffeeOrderController {
@Autowired
private Barista barista;
@PostMapping("/{id}")
public boolean createOrder(@PathVariable( "id") Long id){
// 通知咖啡師有新的coffee有新的訂單
barista.newOrders().send(MessageBuilder.withPayload(id).build());
return true ;
}
}
6 ) OrderListener(監聽咖啡完成MQ及給customer-service 發取餐MQ)
@Component
@Slf4j
public class OrderListener {
@Autowired
private Customer customer;
@Autowired
private CoffeeOrderService orderService;
@StreamListener(Barista.FINISHED_ORDERS)
public void listenFinishedOrders(Long id) {
log.info("We've finished an order no [{}].", id);
String customerName = "張三";
Message<Long> message = MessageBuilder.withPayload(id)
.setHeader("customer", customerName)
.build();
log.info("Notify the customer: {}", customerName);
customer.notification().send(message);
}
}
7 ) WaiterServiceApplication啟動類(綁定啟動了對應MQ接口)
@SpringBootApplication
@EnableDiscoveryClient
@EnableBinding({ Barista.class, Customer.class })
public class WaiterServiceApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(WaiterServiceApplication.class, args);
}
}
備注:@EnableBinding({ Barista.class, Customer.class }) 這裡是在綁定相應的MQ
2.4 barista-service(制作coffee服務)
1 ) 主要pom依賴
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
2 ) 配置檔案
application.properties
#服務名稱
spring.application.name=barista-service
#服務端口,0表示随機端口
server.port=0
#總是推送鍊路到zipkin中
spring.output.ansi.enabled=ALWAYS
#zipkin中日志抽樣比例
spring.sleuth.sampler.probability=1.0
#zipkin中采集日志的模式
spring.zipkin.sender.type=rabbit
#actuator 相關配置
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
spring.rabbitmq.host=100.61.11.211
spring.rabbitmq.port=5672
spring.rabbitmq.username=1234567
spring.rabbitmq.password=1234567
#生成名稱為newOrders的exchange,并綁定到 名稱為 newOrders.barista-service 的Queue 上
spring.cloud.stream.bindings.newOrders.group=barista-service
3 ) Waiter (服務啟動時消息綁定接口)
public interface Waiter {
String NEW_ORDERS = "newOrders";
String FINISHED_ORDERS = "finishedOrders";
@Input(NEW_ORDERS)
SubscribableChannel newOrders();
@Output(FINISHED_ORDERS)
MessageChannel finishedOrders();
}
4 ) OrderListener (消息監聽及發送類)
@Component
@Slf4j
public class OrderListener {
@Autowired
@Qualifier(Waiter.FINISHED_ORDERS)
private MessageChannel finishedOrdersMessageChannel;
@StreamListener(Waiter.NEW_ORDERS)
public void processNewOrder(Long id) {
log.info("Barista start make coffer order no {} .", id);
try {
Thread.sleep(200L);
} catch (Exception e) {
}
finishedOrdersMessageChannel.send(MessageBuilder.withPayload(id).build());
log.info("Barista notify Waiter finish coffee order no {} .", id);
}
}
5 ) BaristaServiceApplication(啟動類)
@EnableJpaRepositories
@SpringBootApplication
@EnableBinding(Waiter.class)
public class BaristaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BaristaServiceApplication.class, args);
}
}
注意:@EnableBinding(Waiter.class) 是綁定消息接口。
三、結果示範
3.1 客戶下單
url :http://localhost:8090/customer/create/order/230
3.2 服務調用鍊路
3.3 服務依賴關系圖
3.4 rabbitMQ
四、總結
1)在項目中,可以通過http 向zipkin推送鍊路資訊, 但是如果在生成環境資料量太大勢必會有性能問題;
2)zipkin 關于海量日志資料如何儲存, 還需在後期研究一下。