Spring Cloud Sleuth 整合 Zipkin 進行服務鏈路追蹤
爲何要進行服務鏈路追蹤?
在一個微服務系統架構中,一個完整的請求可能涉及到多個微服務的調用,這個調用形成一個鏈路。
比如,下單的請求,需要經過網關去調用業務服務,業務服務去調用訂單服務,而訂單服務同步調用商品服務和用戶服務,用戶服務又去調用積分服務:
業務要求整個下單的請求要在 1s 內完成,測試發現請求下單接口耗時超過 2s ,這時我們就需要去定位發現是調用鏈路上的哪個環節耗時異常,進而去解決問題。
Spring Cloud 就有這樣一個組件專門做鏈路追蹤,那就是 Spring Cloud Sleuth ,有如下功能:
-
跟蹤請求的調用情況並將它發送到日誌系統,從而可以從日誌收集其中看到相關的調用情況。
-
檢測請求來自 Spring 應用程序的公共入口和出口點(servlet 過濾器、rest 模板、計劃操作、消息通道、feign 客戶端)。
-
如果使用了 Zipkin 結合 Sleuth ,則應用程序將通過 HTTP 或者其他方式將 Sleuth 跟蹤的請求情況發送到 Zipkin。
這裏提到的另一個組件 Zipkin 是一個能夠收集所有服務監控數據的跟蹤系統。有了 Zipkin 我們可以直觀的查看調用鏈路,並且可以方便的看出服務之間的調用關係以及調用耗時。
Spring Cloud Sleuth 和 Zipkin 的使用非常簡單,官網上有很詳細的文檔:
Sleuth :https://spring.io/projects/spring-cloud-sleuth
Zipkin :https://zipkin.io/pages/quickstart.html
下面我們來實操一下。
微服務調用鏈路環境搭建
我們以開篇舉的例子來搭建這樣一個環境:
還是以本 Spring Cloud Alibaba 系列文章的代碼 SpringCloudAlibabaDemo 爲例,目前已有 gatwway-service
,order-service
和 user-service
,我們再創建兩個微服務項目 product-service
和 loyalty-service
,並形成一個調用鏈路。
❝
完整代碼倉庫:https://github.com/ChenDapengJava/SpringCloudAlibabaDemo 。
爲了展示,這裏貼出了調用邏輯上的關鍵代碼。
product-service
查詢商品信息:
@RestController
@RequestMapping("/product")
public class ProductController {
@GetMapping("/price/{id}")
public BigDecimal getPrice(@PathVariable("id") Long id) {
if (id == 1) {
return new BigDecimal("5899");
}
return new BigDecimal("5999");
}
}
loyalty-service
積分服務中獲取用戶積分和增加積分的 API :
@RestController
@Slf4j
public class LoyaltyController {
/**
* 獲取用戶當前積分
* @param id 用戶id
*/
@GetMapping("/score/{id}")
public Integer getScore(@PathVariable("id") Long id) {
log.info("獲取用戶 id={} 當前積分", id);
return 1800;
}
/**
* 爲當前用戶增加積分
* @param id 用戶id
* @param lastScore 用戶當前積分
* @param addScore 要增加的積分
*/
@GetMapping("/addScore")
public Integer addScore(@RequestParam(value = "id") Long id,
@RequestParam(value = "lastScore") Integer lastScore,
@RequestParam(value = "addScore") Integer addScore) {
log.info("用戶 id={} 增加 {} 積分", id, addScore);
return lastScore + addScore;
}
}
user-service
通過 OpenFeign 調用積分服務:
FeignClient 類:
@Service
@FeignClient("loyalty-service")
public interface LoyaltyService {
@GetMapping("/score/{id}")
Integer getScore(@PathVariable("id") Long id);
@GetMapping("/addScore")
Integer addScore(@RequestParam(value = "id") Long id,
@RequestParam(value = "lastScore") Integer lastScore,
@RequestParam(value = "addScore") Integer addScore);
}
Controller 調用:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
private LoyaltyService loyaltyService;
@GetMapping("/score/{id}")
public Integer getScore(@PathVariable("id") Long id) {
return loyaltyService.getScore(id);
}
@GetMapping("/addScore")
public Integer addScore(@RequestParam Long id,
@RequestParam Integer lastScore,
@RequestParam Integer addScore) {
return loyaltyService.addScore(id, lastScore, addScore);
}
@Autowired
public void setLoyaltyService(LoyaltyService loyaltyService) {
this.loyaltyService = loyaltyService;
}
}
order-service
訂單服務通過 OpenFeign 調用 user-service
和 product-service
:
FeignClient 類 :
@Service
@FeignClient("product-service")
public interface ProductService {
BigDecimal getPrice(@PathVariable("id") Long id);
}
@Service
@FeignClient("user-service")
public interface UserService {
/**
* 由於 user-service 使用了統一返回結果,所以此處的返回值是 ResponseResult
* @param id 用戶id
* @return ResponseResult<Integer>
*/
@GetMapping("/user/score/{id}")
ResponseResult<Integer> getScore(@PathVariable("id") Long id);
/**
* 由於 user-service 使用了統一返回結果,所以此處的返回值是 ResponseResult
*/
@GetMapping("/user/addScore")
ResponseResult<Integer> addScore(@RequestParam(value = "id") Long id,
@RequestParam(value = "lastScore") Integer lastScore,
@RequestParam(value = "addScore") Integer addScore);
}
Controller 調用 :
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
private UserService userService;
private ProductService productService;
@GetMapping("/create")
public String createOrder(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId) {
log.info("創建訂單參數,userId={}, productId={}", userId, productId);
// 商品服務-獲取價格
BigDecimal price = productService.getPrice(productId);
log.info("獲得 price={}", price);
// 用戶服務-查詢當前積分,增加積分
Integer currentScore = userService.getScore(userId).getData();
log.info("獲得 currentScore={}", price);
// 增加積分
Integer addScore = price.intValue();
Integer finalScore = userService.addScore(userId, currentScore, addScore).getData();
log.info("下單成功,用戶 id={} 最終積分:{}", userId, finalScore);
return "下單成功,用戶 id=" + userId + " 最終積分:" + finalScore;
}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired
public void setProductService(ProductService productService) {
this.productService = productService;
}
}
網關 gateway-service
配置 Nacos 註冊中心和路由:
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.242.112:81
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
啓動網關以及其他四個服務,
然後可以在 Nacos 中看到註冊進來的實例:
所有服務啓動成功之後,通過網關調用下單 API :
整個調用鏈路沒有問題。
Spring Cloud Sleuth 的使用
要想使用 Sleuth ,只需簡單幾個操作即可。
除了 gateway-service
網關服務,其他四個服務均執行以下步驟:
1, 導入 spring-cloud-starter-sleuth
依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
2, org.springframework.web.servlet.DispatcherServlet
日誌級別調整爲 DEBUG :
logging:
level:
org.springframework.web.servlet.DispatcherServlet: DEBUG
然後重啓這四個服務,再次通過網關訪問下單 API ,看到每個服務都打印了這樣的日誌:
user-service :
product-service :
loyalty-service :
order-service :
這樣形式的日誌:
# [服務名,總鏈路ID,子鏈路ID]
[order-service,5eda5d7bdcca0118,5eda5d7bdcca0118]
就是整體的一個調用鏈路信息。
Zipkin 服務部署與使用
部署 Zipkin 服務
簡單來說, Zipkin 是用來圖形化展示 Sleuth 收集來的信息的。
Zipkin 需要單獨安裝,它是一個 Java 編寫的 Web 項目,我們使用 Docker Compose 進行部署安裝 Zipkin 。
Tip: 我已經非常體貼的把 Docker Compose 的使用分享了,詳見:用 docker-compose 部署服務真是好用,根本停不下來! 。
部署步驟:
1, 創建 /usr/local/zipkin
目錄,進入到該目錄:
mkdir /usr/local/zipkin
cd /usr/local/zipkin
2, 創建 docker-compose.yml
文件,文件內容如下:
version: "3"
services:
zipkin:
image: openzipkin/zipkin
restart: always
container_name: zipkin
ports:
- 9411:9411
這是簡化版的 docker-compose.yml
配置文件,這樣的配置就能啓動 Zipkin 。更多的配置詳見:https://github.com/openzipkin-attic/docker-zipkin/blob/master/docker-compose.yml 。
3, 使用 docker-compose up -d
命令(-d
表示後臺啓動)啓動:
部署成功後,訪問 Zipkin ,端口爲 9411 ,訪問地址:http://192.168.242.112:9411/zipkin/
這樣,一個 Zipkin 服務就部署完成了。
將 Sleuth 收集到的日誌信息發送到 Zipkin
首先,還是需要在微服務項目中導入 spring-cloud-sleuth-zipkin 的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
然後,增加一些配置,讓 Sleuth 收集的信息發送到 Zipkin 服務上:
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.242.112:81
sleuth:
enabled: true
sampler:
# 設置 Sleuth 收集信息的百分比,一般情況下,10%就夠用了,這裏設置100%觀察
rate: 100
zipkin:
sender:
type: web
base-url: http://192.168.242.112:9411/
好了,再來啓動每個服務,然後訪問下單接口再看下 Zipkin 的面板。訪問 http://192.168.242.112:9411/zipkin/
:
可以看到有一個請求出來了,點擊 SHOW
查看詳情:
可以清楚地看到調用鏈路上每一步的耗時。
小結
Spring Cloud Sleuth 結合 Zipkin 可以對每個微服務進行鏈路追蹤,從而幫助我們分析服務間調用關係以及調用耗費的時間。
本文只簡單介紹了通過 web 方式(配置項:spring.zipkin.sender.type=web
):
也就是通過 HTTP 的方式發送數據到 Zipkin ,如果請求量比較大,這種方式其實性能是比較低的,一般情況下我們都是通過消息中間件來發送,比如 RabbitMQ 。
如果日誌數據量比較大,一般推薦擁有更高吞吐量的 Kafka 來進行日誌推送。
這種方式就是讓服務將 Sleuth 收集的日誌推給 MQ ,讓 Zipkin 去監控 MQ 的信息,通過 MQ 的隊列獲取到服務的信息。這樣就提高了性能。
而日誌的存儲則可以採用 Elasticsearch 對數據進行持久化,這樣可以保證 Zipkin 重啓後,鏈路信息不會丟失。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/SIpk1aAnJHoFbK1FxaPmuQ