微服務之--spring-cloud-sleuth

相信在看sleuth的小伙伴們肯定已經對微服務有一個大致的了解了,因此這篇文章會主要記錄spring-cloud-sleuth,以及在使用過程當中用到的EurekaFeign

調用鏈

隨著服務的拆分,系統的模塊變得越來越多,不同的微服務可能是不同的人來維護,也就造成了當我們請求某一個微服務的某一個接口時,可能是調用了多個微服務。基于Google Dapper論文,用戶每次請求都會生成一個全局ID(traceId),通過它將不同系統的“孤立”的日志串在一起,重組成調用鏈。
簡單的說調用鏈就是當我們發起某個請求后,調用的微服務的先后順序組成的一個調用鏈路。

記錄調用鏈的益處

一個請求可能會涉及n多個微服務的協同處理,牽扯到多人甚至多個團隊的業務系統,一旦中間某個環節出現問題,還得從頭查起。如何可以快速的定位到到底是哪個微服務出現的問題,咱們記錄下來的調用鏈既可以幫助我們進行快速的分析。
除此之外還可以根據此分析各個調用環節的性能問題和數據分析(調用鏈是每次請求的一條完整的業務日志,可以得到用戶的行為路徑,匯總分析應用的很多業務場景)等。

由于我們今天的主角實際上就是對Zipkin的封裝,因此我們先來看下Zipkin的介紹:

Zipkin的設計背景

2010年谷歌發表了其內部使用的分布式跟蹤系統Dapper的論文,講述了Dapper在谷歌內部兩年的演變和設計、運維經驗,Twitter也根據該論文開發了自己的分布式跟蹤系統Zipkin,并將其開源。

Zipkin的設計
圖片.png

上圖為zipkin官網中的結構圖,從上圖我們可以看出數據是由各個應用,中間件甚至是數據庫將跟蹤數據發送到Zipkin服務器,而不是各個服務記錄后,當一條調用鏈路結束后統一發送到Zipkin服務器的,這樣由Zipkin來分析匯總變成調用鏈的好處是可以防止某一次請求調用服務特別多或者很復雜的情況下統一發送造成的性能問題。

簡單的服務調用實例
圖片.png

上圖描述的服務調用場景應該是很常見也很簡單的調用場景了,一個請求通過Gateway服務路由到下游的Service1,然后Service1先調用服務Service2,拿到結果后再調用服務Service3,最后組合Service2和Service3服務的結果,通過Gateway返回給用戶。我們用①②③④⑤⑥表示了調用的順序,什么是span?span直譯過來是"跨度",在谷歌的Dapper論文中表示跟蹤樹中樹節點引用的數據結構體,span是跟蹤系統中的基本數據單元,Dapper的論文中,并沒有具體介紹span中的全部細節,但在Zipkin中,每個span中一般包含如下字段:
traceId:全局跟蹤ID,用它來標記一次完整服務調用,所以和一次服務調用相關的span中的traceId都是相同的,Zipkin將具有相同traceId的span組裝成跟蹤樹來直觀的將調用鏈路圖展現在我們面前。這里直接給出Zipkin官網中的一張Zipkin界面的圖:
id:span的id,理論上來說,span的id只要做到一個traceId下唯一就可以,比如說阿里的鷹眼系統巧妙用span的id來體現調用層次關系(例如0,0.1,0.2,0.1.1等),但Zipkin中的span的id則沒有什么實際含義。
parentId:父span的id,調用有層級關系,所以span作為調用節點的存儲結構,也有層級關系,就像上圖所示,跟蹤鏈是采用跟蹤樹的形式來展現的,樹的根節點就是調用的頂點,從開發者的角度來說,頂級span是從接入了Zipkin的應用中最先接觸到服務調用的應用中采集的。所以,頂級span是沒有parentId字段的,拿上圖所展現的例子來說,頂級span由Gateway來采集,Service1的span是它的子span,而Service2和Service3的span是Service1的span的子span,很顯然Service2和Service3的span是平級關系。
name:span的名稱,主要用于在界面上展示,一般是接口方法名,name的作用是讓人知道它是哪里采集的span,不然某個span耗時高我都不知道是哪個服務節點耗時高。
timestamp:span創建時的時間戳,用來記錄采集的時刻。
duration:持續時間,即span的創建到span完成最終的采集所經歷的時間,除去span自己邏輯處理的時間,該時間段可以理解成對于該跟蹤埋點來說服務調用的總耗時。
annotations:基本標注列表,一個標注可以理解成span生命周期中重要時刻的數據快照,比如一個標注中一般包含發生時刻(timestamp)、事件類型(value)、端點(endpoint)等信息,這里給出一個標注的json結構:
{
"timestamp": 1476197069680000,
"value": "cs",
"endpoint": {
"serviceName": "service1",
"ipv4": "xxx.xxx.xxx.111"
}
}
四種事件類型:cs(客戶端/消費者發起請求)、cr(客戶端/消費者接收到應答)、sr(服務端/生產者接收到請求)和ss(服務端/生產者發送應答)。可以看出,這四種事件類型的統計都應該是Zipkin提供客戶端來做的,因為這些事件和業務無關,這也是為什么跟蹤數據的采集適合放到中間件或者公共庫來做的原因。
binaryAnnotations:業務標注列表,如果某些跟蹤埋點需要帶上部分業務數據(比如url地址、返回碼和異常信息等),可以將需要的數據以鍵值對的形式放入到這個字段中。

接下來進入正題,記錄一下spring-cloud-sleuth在工程中的使用:
1.首先創建一個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>
//這個是用來自動適配版本的
    <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 
#設置采樣率,測試時可將此設置為1,表示每條請求都記錄下來
spring.sleuth.sampler.percentage=0.2

在主程序入口處添加注解@EnableZipkinServer
2.創建一個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

新建一個接口類,并實現如下方法:

@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.創建hiserver工程,在pom文件中添加和2中相同
在配置文件中設置如下:

server.port=8989
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hi

添加請求類并實現如下方法:

@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();
    }

}

依次運行這三個項目,然后訪問接口如下結果:

CUF891OOFH@3VO8%D7EQ$KH.png

訪問依次這個接口后我們可以看到localhost:32071的頁面變成如下樣子:

K}SO)@O%CR)R)700V6K_KEJ.png

點擊進入詳情頁:

{5({JH0KDLI)EW_DOYWQHLT.png

如上圖可以清晰的看到到底是哪個服務的哪個方法調用的哪個服務的哪個方法。接下來咱們看一下依賴關系(直接點擊上面圖中最上面一行的Dependentices):

依賴關系.png

上面的例子主要是通過RestTemplate來調用其他服務的,接下來我們看一下如果使用Feign來調用其他服務怎么使用spring-cloud-sleuth來監控器調用過程。

接下來我們使用feign來調用其他服務

修改原來的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后運行該項目后可看到界面如下:

eureka.png

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

創建一個接口類如下:

//此注解后面的value值是想要調用的服務的名稱,也就是在配置文件中的spring.application.name
@FeignClient(value = "service-hi")
public interface schedualservicehi {
//此value后面是你想調用微服務的接口地址
    @RequestMapping(value = "/servicehi/info",method = RequestMethod.GET)
//此接口相當于中間層,通過訪問此接口便可以訪問到上面配置的服務路徑
    String sayHiFromClientOne();
}

接下來創建一個類,在此類中測試方法調用

@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文件中除了之前添加的有關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);
    }
}

其他的不用剛修改,再次運行這幾個工程:
訪問feign的接口:

feign.png

然后再看ereuka的界面,發現其他項目也已經注冊上

eureka.png

再來看一下sleuth的界面出現了剛才咱們訪問feign的那個接口的記錄:

調用鏈.png

點擊一條記錄看一下依賴關系:

依賴關系 (2).png

以上結果均正確,說明sleuth可以監控到使用feign來調用服務的調用鏈路

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容