1.分布式鏈路監控與追蹤產生背景
2.SpringCloud Sleuth + Zipkin
3.分布式服務追蹤實現原理
4.搭建Zipkin服務追蹤系統
5.搭建Zipkin集成RabbitMQ異步傳輸
6.SpringCloud2.x新知識介紹
分布式鏈路監控與追蹤產生背景
在微服務系統中,隨着業務的發展,系統會變得越來越大,那么各個服務之間的調用關系也就變得越來越復雜。一個 HTTP 請求會調用多個不同的微服務來處理返回最后的結果,在這個調用過程中,可能會因為某個服務出現網絡延遲過高或發送錯誤導致請求失敗,這個時候,對請求調用的監控就顯得尤為重要了。Spring Cloud Sleuth 提供了分布式服務鏈路監控的解決方案。下面介紹 Spring Cloud Sleuth 整合 Zipkin 的解決方案。
Zipkin框架介紹
Zipkin 是 Twitter 的一個開源項目,它基於 Google Dapper 實現的。我們可以使用它來收集各個服務器上請求鏈路的跟蹤數據,並通過它提供的 REST API 接口來輔助查詢跟蹤數據以實現對分布式系統的監控程序,從而及時發現系統中出現的延遲過高問題。除了面向開發的 API 接口之外,它還提供了方便的 UI 組件來幫助我們直觀地搜索跟蹤信息和分析請求鏈路明細,比如可以查詢某段時間內各用戶請求的處理時間等。
Zipkin 和 Config 結構類似,分為服務端 Server,客戶端 Client,客戶端就是各個微服務應用。
微服務中,如果服務與服務之間的依賴關系非常復雜,如果某個服務出現一些問題,很難知道原因。
Spring Cloud提供ZipKin組件
SpringCloud Zipkin 與Sleuth
Zipkin 是一個開放源代碼分布式的跟蹤系統,由Twitter公司開源,它致力於收集服務的定時數據,以解決微服務架構中的延遲問題,包括數據的收集、存儲、查找和展現。
每個服務向zipkin報告計時數據,例如用戶每次請求服務的處理時間等,可方便的監測系統中存在的瓶頸。
zipkin會根據調用關系通過Zipkin UI生成依賴關系圖。
Spring Cloud Sleuth為服務之間調用提供鏈路追蹤。通過Sleuth可以很清楚的了解到一個服務請求經過了哪些服務,每個服務處理花費了多長。從而讓我們可以很方便的理清各微服務間的調用關系。此外Sleuth可以幫助我們:
耗時分析: 通過Sleuth可以很方便的了解到每個采樣請求的耗時,從而分析出哪些服務調用比較耗時;
可視化錯誤: 對於程序未捕捉的異常,可以通過集成Zipkin服務界面上看到;
鏈路優化: 對於調用比較頻繁的服務,可以針對這些服務實施一些優化措施。
Spring Cloud Sleuth可以結合Zipkin,將信息發送到Zipkin,利用Zipkin的存儲來存儲信息,利用Zipkin Ui來展示數據。
搭建Zipkin服務追蹤系統
在 Spring Boot 2.0 版本之后,官方已不推薦自己搭建定制了,而是直接提供了編譯好的 jar 包。詳情可以查看官網:https://zipkin.io/pages/quickstart.html
注意:zipkin官網已經提供定制了,使用官方jar運行即可。
啟動方式:
默認端口號啟動zipkin服務
java –jar zipkin.jar 默認端口號; 9411
訪問地址:http://192.168.18.220:9411
指定端口號啟動8080啟動zipkin服務
java -jar zipkin.jar --server.port=8080
訪問地址:http://192.168.18.220:8080
指定訪問rabbitmq 啟動
java -jar zipkin.jar --zipkin.collector.rabbitmq.addresses=127.0.0.1
訪問:http://192.168.8.159:9411/zipkin/
默認的值是在內存中 需要設置持久化到內存中哦
案例展示 order ---> member ---> msg
在Order服務、Member、Msg服務里面引入:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
pom:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web組件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot整合eureka客戶端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> </dependencies> <!-- 注意: 這里必須要添加, 否者各種依賴有問題 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
yml配置 收集方式有抽樣收集 有全部收集 收集到平台的ip+端口號
###會員項目的端口號
server:
port: 8000
###服務別名----服務注冊到注冊中心名稱
spring:
application:
name: app-toov5-member
zipkin:
base-url: http://127.0.0.1:9411/
###全部采集
sleuth:
sampler:
probability: 1.0
eureka:
client:
service-url:
##### 當前會員服務注冊到eureka服務地址
defaultZone: http://localhost:8100/eureka
### 需要將我的服務注冊到eureka上
register-with-eureka: true
####需要檢索服務
fetch-registry: true
底層原理:
服務跟蹤原理
為了實現請求跟蹤,當請求發送到分布式系統的入口端點時, 只需要服務跟蹤框架為該請求創建一個唯的跟蹤標識, 同時在分布式系統內部流轉的時候,框架始終保持傳遞該唯一標識, 直到返回給請求方為止,這個唯一標識就是前 文中提到的Trace ID。通過Trace ID的記錄,我們就能將所有請求過程的日志關聯起來。
為了統計各處理單元的時間延遲,當請求到達各個服務組件時,或是處理邏輯到達某個狀態時,也通過一個唯一 標識來標記它的開始、 具體過程以及結束,該標識就是前文中提到的Span ID。對於每個Span來說,它必須有開始和結束兩個節點,通過記錄開始Span和結束Span的時間戳,就能統計出該Span的時間延遲,除了時間戳記錄之外,它還可以包含一些其他元數據, 比如事件名稱、請求信息等
SpanId記錄每一次請求, TraceID記錄整個調用鏈全局ID
TraceId 和 SpanId 在微服務中傳遞追蹤
TraceId記錄每一次請求,耗時時間、接口調用關系
TraceId和SpanId在微服務中傳遞追蹤
在微服務中,使用請求頭傳遞TraceId和SpanId,一個TraceId由多個SpanId組合起來。獲取到整個微服務調用依賴關系
下一級的parentId就是上一級的spanId 形成一個鏈
每次請求生成一個新的spanId
在微服務中,使用請求頭傳遞TraceId和SpanId,一個TraceId由多個SpanId組合起來,獲取到整個微服務調用依賴關系。
案例如下:
Eureka略
Order:
controller:
import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class OrderControler { // RestTemplate 是有SpringBoot Web組件提供 默認整合ribbon負載均衡器 // rest方式底層是采用httpclient技術 @Autowired private RestTemplate restTemplate; /** * 在SpringCloud 中有兩種方式調用 rest、fegin(SpringCloud) * * @return */ // 訂單服務調用會員服務 @RequestMapping("/getOrder") public String getOrder() { // 有兩種方式,一種是采用服務別名方式調用,另一種是直接調用 使用別名去注冊中心上獲取對應的服務調用地址 String url = "http://app-itmayiedu-member/getMember"; String result = restTemplate.getForObject(url, String.class); System.out.println("訂單服務調用會員服務result:" + result); return result; } @RequestMapping("/orderToMemberMsg") public String orderToMemberMsg(HttpServletRequest request) { System.out.println( "TraceId:" + request.getHeader("X-B3-TraceId") + ",spanid:" + request.getHeader("X-B3-SpanId")); // 有兩種方式,一種是采用服務別名方式調用,另一種是直接調用 使用別名去注冊中心上獲取對應的服務調用地址 String url = "http://app-itmayiedu-member/memberAndMsg"; String result = restTemplate.getForObject(url, String.class); System.out.println("訂單服務調用會員服務result:" + result); return result; } }
yml:
###訂單服務的端口號
server:
port: 8001
###服務別名----服務注冊到注冊中心名稱
spring:
application:
name: app-itmayiedu-order
zipkin:
base-url: http://127.0.0.1:9411/
###全部采集
sleuth:
sampler:
probability: 1.0
eureka:
client:
service-url:
##### 當前會員服務注冊到eureka服務地址
defaultZone: http://localhost:8100/eureka
### 需要將我的服務注冊到eureka上
register-with-eureka: true
####需要檢索服務
fetch-registry: true
啟動類:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient public class AppOrder { public static void main(String[] args) { SpringApplication.run(AppOrder.class, args); // 如果使用rest方式以別名方式進行調用依賴ribbon負載均衡器 @LoadBalanced // @LoadBalanced就能讓這個RestTemplate在請求時擁有客戶端負載均衡的能力 } // 解決RestTemplate 找不到原因 應該把restTemplate注冊SpringBoot容器中 @bean @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }
Member:
import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class MemberApiController { @Value("${server.port}") private String serverPort; @Autowired private RestTemplate restTemplate; @RequestMapping("/getMember") public String getMember(HttpServletRequest request) { return "this is member,我是會員服務,springcloud2.0版本!端口號:" + serverPort + request.getHeader("X-B3-TraceId") + ",spanid:" + request.getHeader("X-B3-SpanId"); } @RequestMapping("/memberAndMsg") public String sndMsg() { String url = "http://app-itmayiedu-msg/sndMsg"; String result = restTemplate.getForObject(url, String.class); System.out.println("會員服務調用消息服務result:" + result); return result; } }
yml:
###會員項目的端口號
server:
port: 8000
###服務別名----服務注冊到注冊中心名稱
spring:
application:
name: app-toov5-member
zipkin:
base-url: http://127.0.0.1:9411/
###全部采集
sleuth:
sampler:
probability: 1.0
eureka:
client:
service-url:
##### 當前會員服務注冊到eureka服務地址
defaultZone: http://localhost:8100/eureka
### 需要將我的服務注冊到eureka上
register-with-eureka: true
####需要檢索服務
fetch-registry: true
啟動類:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient public class AppMember { // @EnableEurekaClient 將當前服務注冊到eureka上 public static void main(String[] args) { SpringApplication.run(AppMember.class, args); } // 解決RestTemplate 找不到原因 應該把restTemplate注冊SpringBoot容器中 @bean @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }
Msg
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class MsgController { @RequestMapping("/sndMsg") public String sndMsg() { try { Thread.sleep(5000); } catch (Exception e) { // TODO: handle exception } return "我是消息服務平台"; } public static void main(String[] args) { SpringApplication.run(MsgController.class, args); } }
啟動類:
###訂單服務的端口號 server: port: 8003 ###服務別名----服務注冊到注冊中心名稱 spring: application: name: app-toov5-msg zipkin: base-url: http://127.0.0.1:9411/ ###全部采集 sleuth: sampler: probability: 1.0 eureka: client: service-url: ##### 當前會員服務注冊到eureka服務地址 defaultZone: http://localhost:8100/eureka ### 需要將我的服務注冊到eureka上 register-with-eureka: true ####需要檢索服務 fetch-registry: true
Eureka:
訪問: http://127.0.0.1:8001/orderToMemberMsg
點擊上面的實際三5.024s