相信在看sleuth
的小伙伴們肯定已經(jīng)對(duì)微服務(wù)有一個(gè)大致的了解了迫靖,因此這篇文章會(huì)主要記錄spring-cloud-sleuth
矩欠,以及在使用過程當(dāng)中用到的Eureka
和 Feign
吮便。
調(diào)用鏈
隨著服務(wù)的拆分邓了,系統(tǒng)的模塊變得越來越多始绍,不同的微服務(wù)可能是不同的人來維護(hù)拔恰,也就造成了當(dāng)我們請(qǐng)求某一個(gè)微服務(wù)的某一個(gè)接口時(shí),可能是調(diào)用了多個(gè)微服務(wù)闹获∑谌基于Google Dapper論文,用戶每次請(qǐng)求都會(huì)生成一個(gè)全局ID(traceId)避诽,通過它將不同系統(tǒng)的“孤立”的日志串在一起龟虎,重組成調(diào)用鏈。
簡(jiǎn)單的說調(diào)用鏈就是當(dāng)我們發(fā)起某個(gè)請(qǐng)求后沙庐,調(diào)用的微服務(wù)的先后順序組成的一個(gè)調(diào)用鏈路鲤妥。
記錄調(diào)用鏈的益處
一個(gè)請(qǐng)求可能會(huì)涉及n多個(gè)微服務(wù)的協(xié)同處理,牽扯到多人甚至多個(gè)團(tuán)隊(duì)的業(yè)務(wù)系統(tǒng)拱雏,一旦中間某個(gè)環(huán)節(jié)出現(xiàn)問題棉安,還得從頭查起。如何可以快速的定位到到底是哪個(gè)微服務(wù)出現(xiàn)的問題铸抑,咱們記錄下來的調(diào)用鏈既可以幫助我們進(jìn)行快速的分析垂券。
除此之外還可以根據(jù)此分析各個(gè)調(diào)用環(huán)節(jié)的性能問題和數(shù)據(jù)分析(調(diào)用鏈?zhǔn)敲看握?qǐng)求的一條完整的業(yè)務(wù)日志,可以得到用戶的行為路徑,匯總分析應(yīng)用的很多業(yè)務(wù)場(chǎng)景)等菇爪。
由于我們今天的主角實(shí)際上就是對(duì)Zipkin的封裝算芯,因此我們先來看下Zipkin的介紹:
Zipkin的設(shè)計(jì)背景
2010年谷歌發(fā)表了其內(nèi)部使用的分布式跟蹤系統(tǒng)Dapper的論文,講述了Dapper在谷歌內(nèi)部?jī)赡甑难葑兒驮O(shè)計(jì)凳宙、運(yùn)維經(jīng)驗(yàn)熙揍,Twitter也根據(jù)該論文開發(fā)了自己的分布式跟蹤系統(tǒng)Zipkin,并將其開源氏涩。
Zipkin的設(shè)計(jì)
上圖為zipkin官網(wǎng)中的結(jié)構(gòu)圖届囚,從上圖我們可以看出數(shù)據(jù)是由各個(gè)應(yīng)用,中間件甚至是數(shù)據(jù)庫(kù)將跟蹤數(shù)據(jù)發(fā)送到Zipkin服務(wù)器是尖,而不是各個(gè)服務(wù)記錄后意系,當(dāng)一條調(diào)用鏈路結(jié)束后統(tǒng)一發(fā)送到Zipkin服務(wù)器的,這樣由Zipkin來分析匯總變成調(diào)用鏈的好處是可以防止某一次請(qǐng)求調(diào)用服務(wù)特別多或者很復(fù)雜的情況下統(tǒng)一發(fā)送造成的性能問題饺汹。
簡(jiǎn)單的服務(wù)調(diào)用實(shí)例
上圖描述的服務(wù)調(diào)用場(chǎng)景應(yīng)該是很常見也很簡(jiǎn)單的調(diào)用場(chǎng)景了蛔添,一個(gè)請(qǐng)求通過Gateway服務(wù)路由到下游的Service1,然后Service1先調(diào)用服務(wù)Service2兜辞,拿到結(jié)果后再調(diào)用服務(wù)Service3迎瞧,最后組合Service2和Service3服務(wù)的結(jié)果,通過Gateway返回給用戶逸吵。我們用①②③④⑤⑥表示了調(diào)用的順序凶硅,什么是span?span直譯過來是"跨度"扫皱,在谷歌的Dapper論文中表示跟蹤樹中樹節(jié)點(diǎn)引用的數(shù)據(jù)結(jié)構(gòu)體足绅,span是跟蹤系統(tǒng)中的基本數(shù)據(jù)單元,Dapper的論文中韩脑,并沒有具體介紹span中的全部細(xì)節(jié)氢妈,但在Zipkin中,每個(gè)span中一般包含如下字段:
traceId:全局跟蹤ID扰才,用它來標(biāo)記一次完整服務(wù)調(diào)用,所以和一次服務(wù)調(diào)用相關(guān)的span中的traceId都是相同的厕怜,Zipkin將具有相同traceId的span組裝成跟蹤樹來直觀的將調(diào)用鏈路圖展現(xiàn)在我們面前衩匣。這里直接給出Zipkin官網(wǎng)中的一張Zipkin界面的圖:
id:span的id,理論上來說粥航,span的id只要做到一個(gè)traceId下唯一就可以琅捏,比如說阿里的鷹眼系統(tǒng)巧妙用span的id來體現(xiàn)調(diào)用層次關(guān)系(例如0,0.1递雀,0.2柄延,0.1.1等),但Zipkin中的span的id則沒有什么實(shí)際含義。
parentId:父span的id搜吧,調(diào)用有層級(jí)關(guān)系市俊,所以span作為調(diào)用節(jié)點(diǎn)的存儲(chǔ)結(jié)構(gòu),也有層級(jí)關(guān)系滤奈,就像上圖所示摆昧,跟蹤鏈?zhǔn)遣捎酶櫂涞男问絹碚宫F(xiàn)的,樹的根節(jié)點(diǎn)就是調(diào)用的頂點(diǎn)蜒程,從開發(fā)者的角度來說绅你,頂級(jí)span是從接入了Zipkin的應(yīng)用中最先接觸到服務(wù)調(diào)用的應(yīng)用中采集的。所以昭躺,頂級(jí)span是沒有parentId字段的忌锯,拿上圖所展現(xiàn)的例子來說,頂級(jí)span由Gateway來采集领炫,Service1的span是它的子span偶垮,而Service2和Service3的span是Service1的span的子span,很顯然Service2和Service3的span是平級(jí)關(guān)系驹吮。
name:span的名稱针史,主要用于在界面上展示,一般是接口方法名碟狞,name的作用是讓人知道它是哪里采集的span啄枕,不然某個(gè)span耗時(shí)高我都不知道是哪個(gè)服務(wù)節(jié)點(diǎn)耗時(shí)高。
timestamp:span創(chuàng)建時(shí)的時(shí)間戳族沃,用來記錄采集的時(shí)刻频祝。
duration:持續(xù)時(shí)間,即span的創(chuàng)建到span完成最終的采集所經(jīng)歷的時(shí)間脆淹,除去span自己邏輯處理的時(shí)間常空,該時(shí)間段可以理解成對(duì)于該跟蹤埋點(diǎn)來說服務(wù)調(diào)用的總耗時(shí)。
annotations:基本標(biāo)注列表盖溺,一個(gè)標(biāo)注可以理解成span生命周期中重要時(shí)刻的數(shù)據(jù)快照漓糙,比如一個(gè)標(biāo)注中一般包含發(fā)生時(shí)刻(timestamp)员舵、事件類型(value)对蒲、端點(diǎn)(endpoint)等信息,這里給出一個(gè)標(biāo)注的json結(jié)構(gòu):
{
"timestamp": 1476197069680000,
"value": "cs",
"endpoint": {
"serviceName": "service1",
"ipv4": "xxx.xxx.xxx.111"
}
}
四種事件類型:cs(客戶端/消費(fèi)者發(fā)起請(qǐng)求)择卦、cr(客戶端/消費(fèi)者接收到應(yīng)答)蝇庭、sr(服務(wù)端/生產(chǎn)者接收到請(qǐng)求)和ss(服務(wù)端/生產(chǎn)者發(fā)送應(yīng)答)醉鳖。可以看出哮内,這四種事件類型的統(tǒng)計(jì)都應(yīng)該是Zipkin提供客戶端來做的盗棵,因?yàn)檫@些事件和業(yè)務(wù)無(wú)關(guān),這也是為什么跟蹤數(shù)據(jù)的采集適合放到中間件或者公共庫(kù)來做的原因。
binaryAnnotations:業(yè)務(wù)標(biāo)注列表纹因,如果某些跟蹤埋點(diǎn)需要帶上部分業(yè)務(wù)數(shù)據(jù)(比如url地址喷屋、返回碼和異常信息等),可以將需要的數(shù)據(jù)以鍵值對(duì)的形式放入到這個(gè)字段中辐怕。
接下來進(jìn)入正題,記錄一下spring-cloud-sleuth在工程中的使用:
1.首先創(chuàng)建一個(gè)zipkinserver
工程逼蒙,在pom文件中添加如下依賴:
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
//這個(gè)是用來自動(dòng)適配版本的
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置文件中添加如下配置:
server.port=32071
spring.application.name=zipkinService
#設(shè)置采樣率,測(cè)試時(shí)可將此設(shè)置為1寄疏,表示每條請(qǐng)求都記錄下來
spring.sleuth.sampler.percentage=0.2
在主程序入口處添加注解@EnableZipkinServer
2.創(chuàng)建一個(gè)helloserver
工程是牢,在pom文件中添加如下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置文件中添加如下配置:
server.port=8988
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hello
新建一個(gè)接口類,并實(shí)現(xiàn)如下方法:
@RestController
@RequestMapping("/servicehello")
public class controller
{
private static final Logger LOG = Logger.getLogger(controller.class.getName());
@Autowired
private RestTemplate restTemplate;
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
@RequestMapping("/hello")
public String callHome(){
LOG.log(Level.INFO, "calling trace service-hi ");
return restTemplate.getForObject("http://localhost:8989/servicehi/hi", String.class);
}
@RequestMapping("/test")
public String info(){
LOG.log(Level.INFO, "calling trace service-hello ");
return "i'm service-hello";
}
@Bean
public AlwaysSampler defaultSampler(){
return new AlwaysSampler();
}
}
3.創(chuàng)建hiserver
工程陕截,在pom文件中添加和2中相同
在配置文件中設(shè)置如下:
server.port=8989
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hi
添加請(qǐng)求類并實(shí)現(xiàn)如下方法:
@RestController
@RequestMapping("/servicehi")
public class controller {
private static final Logger LOG = Logger.getLogger(ServiceHiApplication.class.getName());
@Autowired
private RestTemplate restTemplate;
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
@RequestMapping("/hi")
public String callHome(){
LOG.log(Level.INFO, "calling trace service-hello ");
return restTemplate.getForObject("http://localhost:8988/servicehello/test", String.class);
// LOG.log(Level.INFO, "calling trace service-new ");
// return restTemplate.getForObject("http://localhost:32061/Designer/getPromotionlist/?orgid=283", String.class);
}
@RequestMapping("/info")
public String info(){
LOG.log(Level.INFO, "calling trace service-hi ");
return "i'm service-hi";
}
@Bean
public AlwaysSampler defaultSampler(){
return new AlwaysSampler();
}
}
依次運(yùn)行這三個(gè)項(xiàng)目驳棱,然后訪問接口如下結(jié)果:
訪問依次這個(gè)接口后我們可以看到localhost:32071的頁(yè)面變成如下樣子:
點(diǎn)擊進(jìn)入詳情頁(yè):
如上圖可以清晰的看到到底是哪個(gè)服務(wù)的哪個(gè)方法調(diào)用的哪個(gè)服務(wù)的哪個(gè)方法。接下來咱們看一下依賴關(guān)系(直接點(diǎn)擊上面圖中最上面一行的Dependentices):
上面的例子主要是通過RestTemplate
來調(diào)用其他服務(wù)的农曲,接下來我們看一下如果使用Feign
來調(diào)用其他服務(wù)怎么使用spring-cloud-sleuth
來監(jiān)控器調(diào)用過程社搅。
接下來我們使用feign來調(diào)用其他服務(wù)
修改原來的hiserver
,zipkinserver
,工程,并新建spring-cloud-eureka
,spring-cloud-feign
工程乳规。
1.新建spring-cloud-eureka
工程
在pom文件中添加依賴如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置文件中添加如下配置(application.properties):
server.port=11111
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.application.name=eureka-server
在程序入口文件中添加注解@EnableEurekaServer
后運(yùn)行該項(xiàng)目后可看到界面如下:
2.新建
spring-cloud-feign
工程形葬,并在pom文件中添加如下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后在配置文件中增加如下配置:
server.port=11112
#配置eureka地址
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.application.name=feign-server
#配置zipkin地址
spring.zipkin.base-url=http://localhost:32071
#配置采樣率
spring.sleuth.sampler.percentage=1.0
創(chuàng)建一個(gè)接口類如下:
//此注解后面的value值是想要調(diào)用的服務(wù)的名稱,也就是在配置文件中的spring.application.name
@FeignClient(value = "service-hi")
public interface schedualservicehi {
//此value后面是你想調(diào)用微服務(wù)的接口地址
@RequestMapping(value = "/servicehi/info",method = RequestMethod.GET)
//此接口相當(dāng)于中間層暮的,通過訪問此接口便可以訪問到上面配置的服務(wù)路徑
String sayHiFromClientOne();
}
接下來創(chuàng)建一個(gè)類笙以,在此類中測(cè)試方法調(diào)用
@RestController
public class controller {
@Autowired
schedualservicehi schedualservicehi;
@RequestMapping(value = "/feign",method = RequestMethod.GET)
public String sayInfo(){
return schedualservicehi.sayHiFromClientOne();
}
最后在工程入口文件中添加注解如下:
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SpringcloudfeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudfeignApplication.class, args);
}
}
3.在hiserver
工程中做如下修改:
pom文件中除了之前添加的有關(guān)zipkin的依賴外再添加如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
配置文件中再增加如下:
server.port=8989
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hi
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.sleuth.sampler.percentage=1.0
工程入口文件中添加注解
@SpringBootApplication
@EnableEurekaClient
public class ServiceHiApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHiApplication.class, args);
}
}
其他的不用剛修改,再次運(yùn)行這幾個(gè)工程:
訪問feign的接口:
然后再看ereuka的界面冻辩,發(fā)現(xiàn)其他項(xiàng)目也已經(jīng)注冊(cè)上
再來看一下sleuth的界面出現(xiàn)了剛才咱們?cè)L問feign的那個(gè)接口的記錄:
點(diǎn)擊一條記錄看一下依賴關(guān)系:
以上結(jié)果均正確猖腕,說明sleuth可以監(jiān)控到使用feign來調(diào)用服務(wù)的調(diào)用鏈路