新版本SpringCloud sleuth整合zipkin
SpringCloud Sleuth 簡介
Spring Cloud Sleuth為Spring Cloud實現了分散式跟蹤解決方案。
Spring Cloud Sleuth借鑑了Dapper的術語。
Span:基本的工作單元。Span包括一個64位的唯一ID,一個64位trace碼,描述資訊,時間戳事件,key-value 註解(tags),span處理者的ID(通常為IP)。
Trace:一組Span形成的樹形結構。
Annotation:用於及時記錄存在的事件。常用的Annotation如下:
- cs:客戶端傳送(client send) 客戶端發起一個請求,表示span開始
- sr:伺服器接收(server received) 伺服器接收到客戶端的請求並開始處理,sr - cs 的時間為網路延遲
- ss:伺服器傳送(server send) 伺服器處理完請求準備返回資料給客戶端。ss - sr 的時間表示伺服器端處理請求花費的時間
- cr:客戶端接收(client received) 客戶端接收到處理結果,表示span結束。 cr - cs 的時間表示客戶端接收服務端資料的時間
下圖展示了Span和Trace在系統中的聯絡
SpringCloud Sleuth 預設採用 Http 方式將 span 資訊傳輸給 Zipkin
在application.properties檔案中指定
spring.zipkin.sender.type=web
使用 RabbitMQ 非同步傳送 span 資訊
為什麼選擇 RabbitMQ 訊息中介軟體傳送 span 資訊
- sleuth 預設採用 http 通訊方式,將資料傳給 zipkin 作頁面渲染,但是 http 傳輸過程中如果由於不可抗因素導致 http 通訊中斷,那麼此次通訊的資料將會丟失。而使用中介軟體的話,RabbitMQ 訊息佇列可以積壓千萬級別的訊息,下次重連之後可以繼續消費。
- 隨著執行緒增多,併發量提升之後,RabbitMQ 非同步傳送資料明顯更具有優勢。
- RabbitMQ 支援訊息、佇列持久化,可以通過訊息狀態落庫、重回佇列、映象佇列等技術手段保證其高可用。
示例
示例簡介
示例包含sleuth-search、sleuth-cart、sleuth-order三個系統,用來模擬電商系統中下單的流程,使用者可以搜尋商品然後立即下單,也可以搜尋多個商品後加入購物車,然後下單,呼叫情況即 search -> cart -> order,或 search -> order。
示例使用 RestTemplate
來完成三個系統間的 http
請求響應,請求方式也都遵循Restful
風格。
版本說明
版本一定要對應好,一些低版本的SpringBoot無法相容新版本的SpringCloud和zipkin
工具 | 版本 |
---|---|
SpringBoot | 2.1.6.RELEASE |
SpringCloud | Greenwich.SR3 |
zipkin | 2.16.2 |
專案結構
demo-cloudsleuth
|- sleuth-search
|- sleuth-cart
|- sleuth-order
pom.xml
匯入依賴
<!-- 引入 springboot 和 springcloud 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
<!-- Springboot 相關 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置 RestTemplate,RestTemplate是SpringBoot提供的封裝好的http工具類,可以幫助我們簡化http的使用。
package com.anqi.cart.resttmplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(5000);
return factory;
}
}
三個系統下的application.properties,埠分別是8081 8082 8083
#server.port=8081 server.port=8082
server.port=8083
server.servlet.context-path=/
spring.zipkin.base-url=http://localhost:9411/
spring.zipkin.service.name=sleuth-cart
#使用預設 http 方式收集 span 需要配置此項
#spring.zipkin.sender.type=web
#sleuth 使用 rabbitmq 來向 zipkin 傳送資料
spring.zipkin.sender.type=rabbit
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#設定取樣率預設為 0.1 注意之前的版本是percentage 新版本中更換為 probability
spring.sleuth.sampler.probability=1
三個系統下的RestTemplate的配置,用來簡化 http 請求
package com.anqi.cart.resttmplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(5000);
return factory;
}
}
@RequestMapping("cart")
@RestController
public class CartController {
@Autowired
RestTemplate restTemplate;
@Autowired
CartService cartService;
private static final String orderUrl = "http://localhost:8084/order/create";
@GetMapping("/add/{cartId}")
public String addToCart(@PathVariable("cartId") String cartId) {
cartService.addProductToCart(cartId, "小米8");
ResponseEntity<String> res = restTemplate.getForEntity(orderUrl, String.class);
return res.getBody();
}
}
@RequestMapping("order")
@RestController
public class OrderController {
@GetMapping("/create")
public String creatOrder() {
System.out.println("create order");
return "create_order";
}
}
@RestController
public class SearchController {
@Autowired
RestTemplate restTemplate;
private static final String cartUrl = "http://localhost:8083/cart/add/1";
private static final String orderUrl = "http://localhost:8084/order/create";
@GetMapping("/search")
public String search() {
ResponseEntity<String> cartRes = restTemplate.getForEntity(cartUrl, String.class);
ResponseEntity<String> orderRes = restTemplate.getForEntity(orderUrl, String.class);
return "cart:" + cartRes.getBody() + "- order:" + orderRes.getBody();
}
}
執行結果分析
預設 http 傳輸 span 資訊
啟動Zipkin
java -jar zipkin-server-2.16.2-exec.jar
網頁中手動訪問
http://localhost:8082/search
我們訪問zipkin站點查詢呼叫情況
http://localhost:9411/zipkin/traces/94b954d843012ca9
可以從下圖中完整清晰的看到三個系統的呼叫關係
下圖為zipkin呼叫預覽,我們請求四次http://localhost:8082/search
來更直觀的觀察資料。在以下介面中,較為簡潔的顯示Span的個數以及呼叫總時延。
我們進入一個完整的呼叫鏈後訪問其中的一個節點得到以下資料。
以下為一次全鏈路追蹤的詳細資訊,包含7個span的所有資訊,以上看到的頁面展示均有以下資料加以渲染而成。
[
{
"traceId": "94b954d843012ca9",
"parentId": "bab70b1e69a5f3e3",
"id": "96387b33a823ca8f",
"kind": "SERVER",
"name": "get /order/create",
"timestamp": 1569060494069123,
"duration": 1161,
"localEndpoint": {
"serviceName": "sletuth-order",
"ipv4": "192.168.0.107"
},
"remoteEndpoint": {
"ipv4": "127.0.0.1",
"port": 49863
},
"tags": {
"http.method": "GET",
"http.path": "/order/create",
"mvc.controller.class": "OrderController",
"mvc.controller.method": "creatOrder"
},
"shared": true
},
{
"traceId": "94b954d843012ca9",
"parentId": "94b954d843012ca9",
"id": "90f7e5cfa89e0d80",
"kind": "SERVER",
"name": "get /order/create",
"timestamp": 1569060494076287,
"duration": 1296,
"localEndpoint": {
"serviceName": "sletuth-order",
"ipv4": "192.168.0.107"
},
"remoteEndpoint": {
"ipv4": "127.0.0.1",
"port": 49864
},
"tags": {
"http.method": "GET",
"http.path": "/order/create",
"mvc.controller.class": "OrderController",
"mvc.controller.method": "creatOrder"
},
"shared": true
},
{
"traceId": "94b954d843012ca9",
"parentId": "94b954d843012ca9",
"id": "bab70b1e69a5f3e3",
"kind": "CLIENT",
"name": "get",
"timestamp": 1569060494063693,
"duration": 10374,
"localEndpoint": {
"serviceName": "sleuth-search",
"ipv4": "192.168.0.107"
},
"tags": {
"http.method": "GET",
"http.path": "/cart/add/1"
}
},
{
"traceId": "94b954d843012ca9",
"parentId": "94b954d843012ca9",
"id": "90f7e5cfa89e0d80",
"kind": "CLIENT",
"name": "get",
"timestamp": 1569060494074966,
"duration": 2848,
"localEndpoint": {
"serviceName": "sleuth-search",
"ipv4": "192.168.0.107"
},
"tags": {
"http.method": "GET",
"http.path": "/order/create"
}
},
{
"traceId": "94b954d843012ca9",
"id": "94b954d843012ca9",
"kind": "SERVER",
"name": "get /search",
"timestamp": 1569060494062631,
"duration": 16332,
"localEndpoint": {
"serviceName": "sleuth-search",
"ipv4": "192.168.0.107"
},
"remoteEndpoint": {
"ipv6": "::1",
"port": 49859
},
"tags": {
"http.method": "GET",
"http.path": "/search",
"mvc.controller.class": "SearchController",
"mvc.controller.method": "search"
}
},
{
"traceId": "94b954d843012ca9",
"parentId": "bab70b1e69a5f3e3",
"id": "96387b33a823ca8f",
"kind": "CLIENT",
"name": "get",
"timestamp": 1569060494067090,
"duration": 3197,
"localEndpoint": {
"serviceName": "sleuth-cart",
"ipv4": "192.168.0.107"
},
"tags": {
"http.method": "GET",
"http.path": "/order/create"
}
},
{
"traceId": "94b954d843012ca9",
"parentId": "94b954d843012ca9",
"id": "bab70b1e69a5f3e3",
"kind": "SERVER",
"name": "get /cart/add/{cartid}",
"timestamp": 1569060494066140,
"duration": 8150,
"localEndpoint": {
"serviceName": "sleuth-cart",
"ipv4": "192.168.0.107"
},
"remoteEndpoint": {
"ipv4": "127.0.0.1",
"port": 49862
},
"tags": {
"http.method": "GET",
"http.path": "/cart/add/1",
"mvc.controller.class": "CartController",
"mvc.controller.method": "addToCart"
},
"shared": true
}
]
使用 RabbitMQ 情況
啟動 zipkin,注意引數
java -jar zipkin-server-2.16.2-exec.jar --RABBIT_ADDRESSES=localhost:5672 --RABBIT_USER=guest --RABBIT_PASSWORD=guest --RABBIT_VIRTUAL_HOST=/
啟動 rabbitmq
rabbitmq-server
在測試的時候發現 mq 和以上方式時延相差無幾,但是隨著執行緒數的增加也就是併發量的增加,mq 傳輸時延將會大大低於 http