1. 程式人生 > 其它 >【計理01組58號】【搬運】Spring Cloud系列

【計理01組58號】【搬運】Spring Cloud系列

搬運連結 https://www.cnblogs.com/sam-uncle/category/1205832.html

spring cloud入門系列:初識spring cloud

什麼是spring cloud?


它的中文官網這樣說:

微服務架構集大成者,雲端計算最佳業務實踐。

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的開發便利性巧妙地簡化了分散式系統基礎設施的開發, 如服務發現註冊、配置中心、訊息匯流排、負載均衡、斷路器、資料監控等,都可以用Spring Boot的開發風格做到一鍵啟動和部署。 Spring Cloud並沒有重複製造輪子,它只是將目前各家公司開發的比較成熟、經得起實際考驗的服務框架組合起來, 通過Spring Boot風格進行再封裝遮蔽掉了複雜的配置和實現原理,最終給開發者留出了一套簡單易懂、易部署和易維護的分散式系統開發工具包。

spring cloud入門系列二:使用Eureka進行服務治理

服務治理可以說是微服務架構中最為核心和基礎的模組,它主要用來實現各個微服務例項的自動化註冊和發現。

Spring Cloud Eureka是Spring Cloud Netflix 微服務套件的一部分,主要負責完成微服務架構中的服務治理功能。

本文通過簡單的小例子來分享下如何通過Eureka進行服務治理:

  • 搭建服務註冊中心
  • 註冊服務提供者
  • 服務發現和消費

==========我是華麗的分割線========================

一、搭建服務註冊中心

先列出完整目錄結構:

搭建過程如下:

  1. 建立maven工程:eureka(具體實現略)
  2. 修改pom檔案,引入依賴
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sam</groupId>
    <artifactId>eureka</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>
    <!-- 使用dependencyManagement進行版本管理 -->
    <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>

    <dependencies>
        <!-- 引入eureka server依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

    </dependencies>

</project>
  1. 建立啟動類
/**
* 
* @EnableEurekaServer
* 用來指定該專案為Eureka的服務註冊中心
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaApp {
    
    public static void main(String[] args) {
        SpringApplication.run(EurekaApp.class, args);
    }
}
  1. 配置application.properties檔案
#設定tomcat服務埠號
server.port=1111
#設定服務名稱
spring.application.name=eureka-service

eureka.instance.hostname=localhost
#註冊中心不需要註冊自己
eureka.client.register-with-eureka=false
#註冊中心不需要去發現服務
eureka.client.fetch-registry=false
#設定服務註冊中心的URL
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
  1. 啟動服務並訪問,我們會看到這樣的畫面

二、註冊服務提供者

先列出完整目錄結構:

搭建過程如下:

建立maven工程:hello-service(具體實現略)

修改pom檔案,引入依賴

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sam</groupId>
    <artifactId>hello-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>

    <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>

    <dependencies>
        <!-- 引入eureka 客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

    </dependencies>
</project>

建立啟動類

/***
 * 
 * @EnableDiscoveryClient
 * 讓服務使用eureka伺服器
 * 實現服務註冊和發現
 *
 */
@EnableDiscoveryClient
@SpringBootApplication
public class HelloApp {

    public static void main(String[] args) {

        SpringApplication.run(HelloApp.class, args);
    }

}

建立controller

@RestController
public class HelloController {
    
    Logger logger = LoggerFactory.getLogger(HelloController.class);
    
    @Autowired
    DiscoveryClient discoveryClient;
    
    @RequestMapping("/hello")
    public String hello() {
        ServiceInstance instance = discoveryClient.getLocalServiceInstance();
        //列印服務的服務id
        logger.info("*********" + instance.getServiceId());
        return "hello,this is hello-service";
    }
}

配置application.properties檔案

server.port=9090
#設定服務名
spring.application.name=hello-service
#設定服務註冊中心的URL,本服務要向該服務註冊中心註冊自己
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka

啟動並測試

啟動後再hello-service的控制檯會有這種字樣(xxx代表你的PC名)

Registered instance HELLO-SERVICE/xxx:hello-service:9090 with status UP (replication=false)

eureka的控制檯會打印出如下字樣(xxx代表你的PC名)

Registered instance HELLO-SERVICE/xxx:hello-service:9090 with status UP (replication=false)

再次訪問localhost:1111,會發現有服務註冊到註冊中心了

三、服務發現和消費

完整目錄結構如下:

搭建過程:

  1. 建立maven工程(具體實現略)
  2. 修改pom檔案,引入依賴
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sam</groupId>
    <artifactId>hello-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>

    <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>

    <dependencies>
        <!-- 引入eureka 客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!-- 引入ribbon 依賴 ,用來實現負載均衡,我們這裡只是使用,先不作其他介紹-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

    </dependencies>
</project>這裡比hello-service服務提供者,多了ribbon的依賴

建立啟動類

@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApp {
    
    
    //@Bean 應用在方法上,用來將方法返回值設為為bean
    @Bean
    @LoadBalanced  //@LoadBalanced實現負載均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class, args);
    }
}
這裡也要用到@EnableDiscoveryClient, 讓服務使用eureka伺服器, 實現服務註冊和發現

建立controller

@RestController
public class ConsumerController {

    //這裡注入的restTemplate就是在com.sam.ConsumerApp中通過@Bean配置的例項
    @Autowired
    RestTemplate restTemplate;

    @RequestMapping("/hello-consumer")
    public String helloConsumer() {
        //呼叫hello-service服務,注意這裡用的是服務名,而不是具體的ip+port
        restTemplate.getForObject("http://hello-service/hello", String.class);
        return "hello consumer finish !!!";
    }
}

配置application.properties檔案

server.port=9999

spring.application.name=hello-consumer
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka


#這裡的配置專案和服務提供者hello-service一樣

啟動,測試

啟動eureka。為了展示負責均衡的效果,我們的hello-service啟動兩個服務,啟動兩個服務的具體步驟如下

以上是hello-service1的啟動步驟,埠號為9090;同樣方法設定hello-service2,埠號為9091(具體實現略)。

啟動hello-consumer

再次訪問http://localhost:1111/,會發現有2個hello-service服務(埠號一個是9090,一個是9091),1個hello-consume服務

多次訪問http://localhost:9999/hello-consumer,會發現hello-service1和hello-service2會輪流被呼叫(已經實現了負責均衡),可以通過兩者的控制檯列印內容確認(還記得我們在hello-service的controller中有個loggerlogger.info("*********" + instance.getServiceId());嗎?對,就是這個列印)

四、總結

以上例項實現了基本的服務治理:

  • 通過spring-cloud-starter-eureka-server和@EnableEurekaServer實現服務註冊中心
  • 通過spring-cloud-starter-eureka和@EnableDiscoveryClient使用並註冊到服務註冊中心
  • 通過spring-cloud-starter-eureka和@EnableDiscoveryClient使用註冊中心並發現服務,通過spring-cloud-starter-ribbon來實現負載均衡消費服務

PS:這裡說明下,我用的IDE是Spring Tool Suite,是spring定製版的eclipse,方便我們使用spring進行開發,有興趣的朋友可以自行百度瞭解下。

spring cloud入門系列三:使用Eureka搭建高可用服務註冊中心

一、程式碼實現

還是使用上篇文章中的eureka服務例子,在此基礎上我們進行修改:

新增/eureka/src/main/resources/application-peer1.properties

server.port=1111

spring.application.name=eureka-service
#設定主機名為peer1
eureka.instance.hostname=peer1

#eureka.client.register-with-eureka=false
#eureka.client.fetch-registry=false
#設定eureka的serviceUrl為peer2
eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka

新增/eureka/src/main/resources/application-peer2.properties

server.port=1112

spring.application.name=eureka-service
eureka.instance.hostname=peer2
#eureka.client.register-with-eureka=false
#eureka.client.fetch-registry=false
#設定eureka的serviceUrl為peer1
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka

由於需要peer1和peer2相互註冊,因此第1和第2步中,eureka.client.register-with-eureka=false和eureka.client.fetch-registry=false這兩個配置就不能要了,需要註釋掉或刪掉。

設定host,在C:\Windows\System32\drivers\etc目錄下的hosts檔案中新增

127.0.0.1 peer1

127.0.0.1 peer2

以將peer1和peer2進行解析。

配置兩個啟動服務

啟動eureka1和eureka2

頁面訪問http://localhost:1111/我們會發現,在DS Replicas(分片)中會有peer2; 在服務instance中會有2個,一個是1111,一個是1112; 在registered-replicas(註冊分片)和available-replicas(可用分片)中出現了peer2:1112。 同樣道理,我們訪問http://localhost:1112/也能看到相似效果。 如果將peer2停掉,那麼在訪問http://localhost:1111/的時候就會發現peer2已經跑到unavailable-replicas(不可用分片)中,具體截圖略。

二、除錯中遇到的問題

我在除錯以上內容的時候遇到一個問題,就是在peer1和peer2一直不能彼此註冊成功,頁面表現就是服務instance為空並且available-replicas為空,對應的服務在unavailable-replicas中。後來經過多次實現和分析,終於發現,原來是在我的application.properties檔案中還保留著下面兩個配置,服務在啟動的時候是會讀取到這個配置的,

雖然這兩個配置預設是true,但是卻被application.properties覆蓋了,這個時候講這兩個配置註釋掉就好了。

如果在application-{profiles}.properties中再配置一遍就又可以覆蓋application.properties裡面的配置。

程式碼讀取順序是這樣的:

先讀取預設配置-->然後讀取application.properties-->讀取application-{profiles}.properties。

spring cloud入門系列四:使用Hystrix實現斷路器進行服務容錯保護

在微服務中,我們將系統拆分為很多個服務單元,各單元之間通過服務註冊和訂閱消費的方式進行相互依賴。但是如果有一些服務出現問題了會怎麼樣?

比如說有三個服務(ABC),A呼叫B,B呼叫C。由於網路延遲或C本身程式碼有問題導致B遲遲得不到迴應,這樣B呼叫C的請求就會被掛起,等待。

在高併發的訪問的情況下,這些掛起的執行緒得不到釋放,使後續的請求阻塞,最終導致B也掛掉了。依次類推,A可能也會掛掉,進而使整個系統全部崩潰。

為了解決整個問題,Spring Cloud 使用Hystrix進行服務容錯保護,包括斷路器、執行緒隔離等一系列的保護功能,今天我們就來看下如何通過Hystrix實現斷路器。

一、什麼是Spring Cloud Hystrix?什麼是斷路器?

Spring Cloud Hystrix是基於Netflix的開源框架Hystrix實現的,其目的是為了通過控制那些訪問遠端系統、服務和第三方的節點,從而對延遲和故障提供強大的容錯能力。

斷路器類似於我們家裡面強電箱裡面用到的漏電斷路保護器,當服務單元出現故障(類似於電器發生短路),通過斷路器的故障監控功能(類似於保險絲),向呼叫方返回一個錯誤響應,避免長時間等待,從而避免故障蔓延到整個系統。

二、沒有斷路器的情況下,頁面展示

還記得我們前面寫的spring cloud 入門系列二:使用Eureka 進行服務治理裡面的三個服務(eureka/hello-service/hello-consumer)嗎?我們基於這個進行實驗。

  1. 啟動eureka服務註冊中心,埠號1111
  2. 啟動hello-service服務提供者,這裡我們啟動兩個服務,埠號分別為9090,9091
  1. 啟動hello-consumer服務消費者,埠號為9999;這個時候我們多次訪問http://localhost:9999/hello-consumer是沒有問題的
  2. 將hello-service埠號為9091的服務關掉,再去多次訪問http://localhost:9999/hello-consumer,報錯了PS:這裡說明下,為什麼要多次訪問,是因為我們通過ribbon實現了負載均衡,訪問http://localhost:9999/hello-consumer的時候,會輪詢訪問hello-service的兩個服務,當訪問到埠號是9091的服務時才報錯,訪問9090的服務就不會有問題。

三、斷路器程式碼實現

接下來我們看下如何進行程式碼實現,我們不去修改服務註冊中心和服務提供者,只需要修改服務消費者hello-consumer。

修改POM檔案,引入Hystrix依賴

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sam</groupId>
    <artifactId>hello-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>

    <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>

    <dependencies>
        <!-- 引入eureka 客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!-- 引入ribbon 依賴 ,用來實現負載均衡,我們這裡只是使用先不作其他介紹 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
        <!-- 引入hystrix 依賴 ,用來實現服務容錯保護-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>

    </dependencies>
</project>

修改啟動類,追加註解@EnableCircuitBreaker,開啟斷路器

@EnableDiscoveryClient
@SpringBootApplication
@EnableCircuitBreaker
public class ConsumerApp {


    //@Bean 應用在方法上,用來將方法返回值設為為bean
    @Bean
    @LoadBalanced  //@LoadBalanced實現負載均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class, args);
    }
}

這個時候你會發現,這個啟動類加了三個註解,這個是不是很麻煩?沒關係,我們可以使用註解@SpringCloudApplication

@SpringCloudApplication
public class ConsumerApp {


    //@Bean 應用在方法上,用來將方法返回值設為為bean
    @Bean
    @LoadBalanced  //@LoadBalanced實現負載均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class, args);
    }
}


@SpringCloudApplication = @EnableDiscoveryClient +@SpringBootApplication+@EnableCircuitBreaker,從原始碼就能看出來:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}


追加service

@Service
public class ConsumerService {
    
    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "errorMsg")
    public String consumer() {
        // 呼叫hello-service服務,注意這裡用的是服務名,而不是具體的ip+port
        restTemplate.getForObject("http://hello-service/hello", String.class);
        return "hello consumer finish !!!";
    }

    public String errorMsg() {
        return "error!!!";
    }
}

我們把原來controller裡面的呼叫RestTemplate的實現放到service裡面,並且通過@HystrixCommand來指定回撥方法,當出現錯誤時呼叫該方法。

修改controller

/**
 *這裡不再直接呼叫restTemplate,
 *而是通過呼叫service進行實現 
 *
 */
@RestController
public class ConsumerController {

    @Autowired
//    RestTemplate restTemplate;
    ConsumerService service;
    
    
    @RequestMapping("/hello-consumer")
    public String helloConsumer() {
//        //呼叫hello-service服務,注意這裡用的是服務名,而不是具體的ip+port
//        restTemplate.getForObject("http://hello-service/hello", String.class);
        return service.consumer();
    }
}

測試,多次訪問,當報錯的時候,會顯示如下內容

spring cloud入門系列五:使用Feign實現宣告式服務呼叫

一、Spring Cloud Feign概念引入

通過前面的隨筆,我們瞭解如何通過Spring Cloud ribbon進行負責均衡,如何通過Spring Cloud Hystrix進行服務斷路保護,
兩者作為基礎工具類框架應用在各種基礎設施類微服務和業務類微服務中,並且成對存在,那麼有沒有更高層的封裝,將兩者的使用
進一步簡化呢? 有! 他就是Spring Cloud Feign。它基於Netflix Feign實現,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,
除了提供兩者強大的功能外,還提供了一種宣告式的Web服務客戶端定義方式。

二、入門例項

我們還是繼續使用前面隨筆中的hello-service服務,這裡通過Spring Cloud Feign提供的宣告式服務繫結功能來實現對服務介面的呼叫。
我們需要新建一個feign-consumer來代替之前的hello-consumer

先給出程式碼結構:

程式碼實現:

新建maven工程(feign-consumer)

修改pom檔案,引入eureka和feign依賴

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sam</groupId>
  <artifactId>feign-consumer</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>

    <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>

    <dependencies>
        <!-- 引入eureka 客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!-- 引入feign 依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

    </dependencies>
    
</project>

新建啟動類

/**
 * 通過@EnableFeignClients來開啟spring cloud feign的支援功能
 *
 */
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeiApp {

    public static void main(String[] args) {
        SpringApplication.run(FeiApp.class, args);
    }

}

新建service介面

/**
 * 通過@FeignClient註解指定服務名來繫結服務,這裡的服務名字不區分大小寫
 * 然後再通過@RequestMapping來繫結服務下的rest介面
 *
 */
@FeignClient(name="hello-service")
public interface FeignConsumerService{

    @RequestMapping("/hello")
    public void hello();
}

新建controller

@RestController
public class FeiConsumerController {

    @Autowired
    FeignConsumerService consumerService;

    @RequestMapping("feign-consumer")
    public String feignConsumer() {
        consumerService.hello();
        return "feign consumer call finished!!!";
    }

}

新建application.properties

server.port=9001

spring.application.name=feign-consumer

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka

測試,

啟動服務註冊中心eureka、啟動兩個hello-service(埠號分別為9090和9091),啟動feign-consumer

訪問http://localhost:9001/feign-consumer

並且多次訪問的話,會輪詢呼叫兩個hello-service服務。

三、引數繫結

在上面的例子中,我們實現的只是一個不帶引數的rest服務繫結,然而現實的業務中不會這麼簡單,往往會有各種引數,
這個時候我們做如下事情:

  1. 如果服務提供方有物件引數(如User物件),那麼feign-consumer工程中需要建一個路徑和類名完全一樣的類。
  2. 然後將服務提供方controller裡面的所有方法宣告進行copy(包括前面的@RequestMapping),貼上到feign-consumer的service接口裡面。

四、繼承特性

根據上面引數繫結的做法,我們需要進行很多copy操作,這樣比較麻煩,可以通過繼承的方式進行簡化。

這種實現步驟如下:

1.我們需要新建一個基礎工程hello-service-api,

程式碼結構:

程式碼實現:

新建maven專案hello-service-api

修改pom檔案

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sam</groupId>
  <artifactId>hello-service-api</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>

    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
    </dependencies>
</project>

你會發現其實就是一個普通的spring boot專案。

考慮到需要掩飾引數中有物件的情況,我們加個User

package com.sam.entity;

public class User {
    
    private String name;
    private Integer age;
    
    
    public User(String name, Integer age) {
        super();
        this.name = name;
        this.age = age;
    }
    
    
    public User() {
        super();
    }
    
    
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Integer getAge() {
        return age;
    }
    
    public void setAge(Integer age) {
        this.age = age;
    }
    
}

User必須有一個無引數的構造器

新建service介面

/**
 * 為了同前面那個hello 介面區分開了,我們加了refactor字首
 *
 */
@RequestMapping("/refactor")
public interface HelloService {

    @RequestMapping("/hello2")
    public String hello2();
    
    @RequestMapping("/hello3")
    public User printUser(@RequestBody User user);
}

重構hello-service

修改pom檔案

<!-- 引入 hello-service-api的依賴,以繼承其提供的介面 -->
        <dependency>
            <groupId>com.sam</groupId>
            <artifactId>hello-service-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

HelloController implements HelloService,並實現interface中的介面

@RestController
public class HelloController implements HelloService{
    
    Logger logger = LoggerFactory.getLogger(HelloController.class);
    
    @Autowired
    DiscoveryClient discoveryClient;
    
    @RequestMapping("/hello")
    public String hello() throws Exception {
        ServiceInstance instance = discoveryClient.getLocalServiceInstance();
        //列印服務的服務id
        logger.info("*********" + instance.getServiceId());
        return "hello,this is hello-service";
    }
    
    @Override
    public String hello2() {
        return "hello,this is hello2-service";
        
    }
    
    @Override
    public User printUser(@RequestBody User user) {
        return user;
    }
    
}

controller實現介面的方法時,不需要@RequestMapping註解,只需要類註解@RestController即可。

重構feign-consumer服務

修改POM檔案

<!-- 引入 hello-service-api的依賴,以繼承其提供的介面 -->
<dependency>
  <groupId>com.sam</groupId>
  <artifactId>hello-service-api</artifactId>
  <version>0.0.1-SNAPSHOT</version>
        </dependency>

讓FeignConsumerService extends hello-service-api中的HelloService

/**
* 通過@FeignClient註解指定服務名來繫結服務,這裡的服務名字不區分大小寫
* 然後再通過@RequestMapping來繫結服務下的rest介面
*
*/
@FeignClient(name="hello-service")
public interface FeignConsumerService extends HelloService{
    
    @RequestMapping("/hello")
    public void hello();
}

只需要繼承即可

修改controller,追加方法

@RestController
public class FeiConsumerController {

    @Autowired
    FeignConsumerService consumerService;

    @RequestMapping("feign-consumer")
    public String feignConsumer() {
        consumerService.hello();
        return "feign consumer call finished!!!";
    }

    @RequestMapping("feign-consumer-user")
    public User feignConsumer2(User user) {
        consumerService.hello2();
        return consumerService.printUser(user);
    }
}

測試

五、其他

由於Spring Cloud Feign是通過ribbon和hystrix實現具體功能的,因此可以直接通過配置這兩個來實現功能
1.ribbon配置方式:
通過ribbon.<key>=<value>的方式進行全域性配置,比如
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000
通過<client>.ribbon.<key>=<value>的方式進行指定服務配置,比如
#這裡的<client>為@FeignClient(value="hello-service")指定的服務名
hello-service.ribbon.ConnectTimeout=500
hello-service.ribbon.ReadTimeout=500
2.hystrix配置方式:
通過hystrix.command.default.xxx進行全域性配置
如:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
通過hystrix.command.<commandKey>.xxx進行指定配置,這裡的<commandKey>可以為方法名
如:hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000
3.請求壓縮配置,支援對請求和響應進行GZIP壓縮,以減少通訊過程中的效能損耗。
feign.compression.request.enabled=true;

feigan.compression.response.enabled=true;


4.日誌配置
Spring Cloud Feign在構建被@FeignClient註解修飾的服務客戶端是,會為每一個客戶端都建立一個feign.Logger例項,我們可以利用該日誌物件進行Log分析。

spring cloud入門系列六:使用Zuul 實現API閘道器服務

通過前面幾次的分享,我們瞭解了微服務架構的幾個核心設施,通過這些元件我們可以搭建簡單的微服務架構系統。比如通過Spring Cloud Eureka搭建高可用的服務註冊中心並實現服務的註冊和發現;

通過Spring Cloud Ribbon或Feign進行負載均衡;通過Spring Cloud Hystrix進行服務容錯保護以避免故障蔓延。微服務搭建好了之後我們肯定會提供給外部系統一些統一的RESTFul API服務介面進行呼叫,

但是當外部系統呼叫我們的RESTful API的時候,怎麼確定它需要的功能具體是哪個服務提供的呢?這個就涉及到一個路由規則和服務例項列表的維護問題。

這就引入了我們今天的主角--Spring Cloud Zuul,它是基於Netflix Zuul實現的API閘道器元件。它可以解決兩個大問題:

  1. 就是我們上面提到的路由規則和服務例項的維護問題
  2. 對於一些校驗(比如登入校驗等)冗餘問題。 按照我們的習慣的做法,是在每個服務中都需要加入這些校驗,但是這樣會導致程式碼冗餘並且維護也比較麻煩,有了Spring Cloud Zuul這個閘道器服務之後,我們可以將這些共通的校驗放到閘道器裡面統一維護。

好,接下來我們就來看下怎麼實現這個閘道器服務。

一、構建閘道器,配置路由

這裡我們還是需要使用到前面的hello-service和feign-consumer服務。我們之前把feign-consumer作為服務消費者,但是別忘了在eureka體系裡面,每個服務既是服務提供者又是服務消費者,所以feign-consumer也是一個服務提供者,並且http://localhost:9001/feign-consumer等介面就是它提供的服務。

接下來我們構建一個閘道器服務,程式碼結構如下:

程式碼實現步驟

新建maven工程api-gateway

修改pom檔案

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sam</groupId>
  <artifactId>api-gateway</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
  </parent>
  
  <properties>
    <javaVersion>1.8</javaVersion>
  </properties>
  <!-- 使用dependencyManagement進行版本管理 -->
  <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>
  
  <dependencies>
    <!-- 引入zuul依賴 , 它依賴了spring-boot-starter-actuator/spring-boot-starter-hystrix/spring-boot-starter-ribbon-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-zuul</artifactId>
    </dependency>
    
  </dependencies>
  
</project>

新建啟動類

/**
* @EnableZuulProxy 開啟Zuul 的API閘道器服務功能
*
*/
@EnableZuulProxy
@SpringCloudApplication
public class GateWayApp {
    
    public static void main(String[] args) {
        SpringApplication.run(GateWayApp.class, args);
    }
}

新建application.properties

server.port=5555
spring.application.name=api-gateway

#增加路由規則的配置
#通過zuul.routes.<route>.path和zuul.routes.<route>.url進行配置,<route>為路由的名字,可以任意指定,但是一組path和url的路由名要相同
  #如下面的例子:所有滿足/api-a/** 規則的訪問都會被路由轉發到//localhost:9001的地址
  #也就是說,我們訪問http://localhost:5555/api-a/hello的時候,API閘道器服務就會將該請#求路由到 http://localhost:9001/hello提供的微服務介面上
  zuul.routes.api-a.path=/api-a/**
  zuul.routes.api-a.url=http://localhost:9001
  
  
  zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.url=http://localhost:9090

測試,啟動eureka、hello-service、feign-consumer以及本次新加的api-gateway服務,然後訪問http://localhost:5555/api-a/feign-consumer

成功訪問到了feign-consumer的服務介面--feign-consonsumer。

以上步驟實現了傳統路由的配置,這種配置有個大的缺點,就是需要手工在application.properties檔案中進行路由規則的配置,當服務很多的時候,維護工作量就會很大。為了減小維護成本,還有另外一種路由--面向服務的路由。

二、面向服務的路由

Spring Cloud Zuul和Eureka進行整合,我們可以讓路由的path不是對映具體的url,而是具體的某個服務,而服務的url則交給Eureka服務發現機制自動維護,這類路由就是面向服務的路由。具體程式碼配置如下:

修改POM檔案,引入Eureka依賴

<!-- 引入eureka依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

修改application.properties配置檔案

server.port=5555
spring.application.name=api-gateway

zuul.routes.api-a.path=/api-a/**

#這裡用serviceId代替url,用服務名代替ip+埠號
zuul.routes.api-a.serviceId=hello-serviceeureka.client.service-url.defaultZone=http://localhost:1111/eureka

注意:zuul.routes.api-a.url=hello-service也能實現功能,但是它不能進行正常的負載均衡和容錯保護。

測試,訪問http://localhost:5555/api-a/hello

三、服務路由的預設規則

在面向服務的路由中,由於<route>名字是隨意起的,那麼是不是可以這樣:

zuul.routes.hello-service.path=/hello-service/**

zuul.routes.hello-service.serviceId=hello-service

<route>名字就是服務名,其實在實際的應用中,我們往往就是這樣命名的。如果有這樣的規則的話,那Zuul就可以幫我們預設實現這樣的功能,進一步省去了配置的麻煩。

我們來做個實驗,將配置檔案改為:

server.port=5555

spring.application.name=api-gateway

eureka.client.service-url.defaultZone=http://localhost:1111/eureka

然後頁面訪問驗證

訪問成功。

但是由於預設情況下,Eureka上的服務都會被Zuul建立預設的對映關係來進行路由,使得我們不想對外開放的服務也被外部訪問到,這個時候可以通過配置zuul.ignored-services來進行配置不需要自動建立路由的規則。當zuul.ignored-services=*的時候,所有的服務都不會自動建立路由規則,這個時候需要通過前面的配置進行相關路由配置了。

================華麗的分割線===================

前面說了那麼多都是圍繞一個問題展開的:路由規則和服務例項的維護問題,那麼怎麼解決第二個問題(校驗冗餘問題)呢?

四、請求過濾

為了在API閘道器中實現對客戶端請求的校驗,我們可以通過過濾器來實現對請求的攔截和過濾,實現方法比較簡單,只需要繼承ZuulFilter抽象類並實現其四個方法就行了。

修改api-gateway:

新增過濾器類

/**
* 繼承ZuulFilter,並且實現其4個介面
*
* 用來進行請求過濾
*
*/
public class AccessFilter extends ZuulFilter {
    Logger logger = LoggerFactory.getLogger(AccessFilter.class);
    /* 
    * shouldFilter 判斷該過濾器是否需要被執行
    * 
    * 這裡直接返回true,表示該過濾器對所有請求都會生效。
    * 實際運用中我們可以利用該函式指定過濾器的有效範圍
    */
    @Override
    public boolean shouldFilter() {
        return true;
    }
    
    /*
    * 過濾器的具體邏輯
    * 
    * 這裡我們通過ctx.setSendZuulResponse(false)讓zuul過來請求,不對其進行路由
    * 然後通過ctx.setResponseStatusCode(401)設定了返回的錯誤碼
    * 
    */
    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        Object accessToken = request.getParameter("accessToken");
        
        logger.info("send {} request to {}", request.getMethod(),request.getRequestURL().toString());
        if(accessToken == null) {
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(401);
        }
        
        return null;
    }
    
    /* filterType 返回過濾器型別
    * 他決定了過濾器在請求的哪個生命週期中執行。這裡定義為pre,代表會在請求被路由前執行。
    * 
    * pre:請求執行之前filter 
    * route: 處理請求,進行路由 
    * post: 請求處理完成後執行的filter 
    * error:出現錯誤時執行的filter
    */
    @Override
    public String filterType() {
        return "pre";
    }
    
    
    /* 
    * filterOrder 返回過濾器的執行順序
    * 
    * 當請求在一個階段有多個過濾器是,需要根據該方法的返回值來依次執行
    * 
    */
    @Override
    public int filterOrder() {
        return 0;
    }
    
}

修改啟動類

/**
 * @EnableZuulProxy 開啟Zuul 的API閘道器服務功能
 *
 */
@EnableZuulProxy
@SpringCloudApplication
public class GateWayApp {

    //追加bean的是實現
    @Bean
    public AccessFilter accessFilter() {
        return new AccessFilter();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(GateWayApp.class, args);
    }
}

測試

訪問http://localhost:5555/hello-service/hello,訪問失敗

訪問http://localhost:5555/hello-service/hello?accessToken=token,正常訪問

修改後的程式碼結構:

五、拓展延伸

其實路由功能在真正執行時,他的路由對映和請求轉發都是由幾個不同的過濾器完成的。

路由對映主要通過pre型別的過濾器完成,他將請求路徑與配置的路由規則進行匹配,找到需要轉發的目標地址。

而請求轉發的部分則是由route型別的過濾器完成的,對pre型別過濾器獲取的路由地址進行轉發。

所以,過濾器可以說是Zuul實現API閘道器功能最為核心的部件,每一個進入Zuul的HTTP請求都會經過一系列的過濾器處理鏈得到請求響應並返回給客戶端。

spring cloud入門系列七:基基Git儲存的分散式配置中心--Spring_ Cloud Config

我們前面接觸到的spring cloud元件都是基於Netflix的元件進行實現的,這次我們來看下spring cloud 團隊自己建立的一個全新專案:Spring Cloud Config.
它用來為分散式系統中的基礎設施和微服務提供集中化的外部配置支援,分為服務端和客戶端兩個部分。

其中服務端也稱為分散式配置中心,他是獨立的微服務應用,用來連線配置倉庫併為客戶端提供獲取介面(這些介面返回配置資訊、加密、解密資訊等);

客戶端是微服務架構中的各個微服務應用或基礎設施,它們通過制定的配置中心來管理應用資源與業務相關的配置內容,並在啟動的時候從配置中心獲取和載入配置資訊。
由於配置中心預設採用Git來儲存配置資訊,因此我們會用到Git相關的內容,如果沒有用過Git或者忘記怎麼用了,可以參考下廖雪峰老師的
Git教程
另外,我自己用的Git遠端倉庫是碼雲。
====================華麗的分割線===================
接下來看下程式碼怎麼實現。

一、準備遠端Git倉庫

  1. 在Gitee上新建一個專案https://gitee.com/sam-uncle/spring-cloud-learning
  2. 在專案下新建子目錄spring-cloud-config-file,然後新建三個檔案
    1. 內容分別是from=git-dev-1.0、from=git-test-1.0、from=git-1.0
  1. 新建一個分支config-lable-test,新分支裡面新建三個同名的檔案,不過內容分別是from=git-dev-2.0、from=git-test-2.0、from=git-2.0

二、構建配置中心

先給出最終程式碼結構:

搭建過程如下:

新建maven工程config-server

修改POM檔案

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sam</groupId>
  <artifactId>config-server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
  </parent>
  
  <properties>
    <javaVersion>1.8</javaVersion>
  </properties>
  <!-- 使用dependencyManagement進行版本管理 -->
  <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>
  
  <dependencies>
    <!-- 引入config server依賴 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    
  </dependencies>
  
</project>

建立啟動類

/**
* @EnableConfigServer
* 
* 開啟Spring Cloud Config 的服務端功能
*
*/
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApp {
    
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApp.class, args);
    }
}

配置application.properties檔案,指定遠端倉庫資訊

server.port=7001
spring.application.name=config-server

#配置Git倉庫的地址
spring.cloud.config.server.git.uri=https://gitee.com/sam-uncle/spring-cloud-learning/
#配置倉庫路徑下的相對搜尋位置,可以配置多個
spring.cloud.config.server.git.search-paths=spring-cloud-config-file
#這裡配置你的Git倉庫的使用者名稱
spring.cloud.config.server.git.username=使用者名稱
#這裡配置你的Git倉庫的密碼
spring.cloud.config.server.git.password=密碼

啟動並驗證

訪問配置資訊的URL與配置檔案的對映關係如下:

    • /{application}/{profile} [/{label}]
    • /{application}-{profile}.yml
    • /{label}/{application}-{profile}.yml
    • /{application}-{profile}.properties
    • /{label}/{appliction}-{profile}.properties

上面的url會對映{application}-{profile}.properties對應的配置檔案,其中{label}對應Git上不同的分支,預設是master。

通過瀏覽器訪問http://localhost:7001/sam/dev/config-label-test,結果如下:

三、實現客戶端

最終程式碼結構:

搭建過程如下:

新建maven工程config-client

修改POM檔案

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sam</groupId>
  <artifactId>config-client</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
  </parent>
  
  <properties>
    <javaVersion>1.8</javaVersion>
  </properties>
  <!-- 使用dependencyManagement進行版本管理 -->
  <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>
  
  <dependencies>
    <!-- 引入config依賴 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  </dependencies>
  
</project>

建立啟動類

@SpringBootApplication
public class ConfigClientApp {
    
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApp.class, args);
    }
    
}

配置bootstrap.properties檔案,制定config-server位置

server.port=7002
#{application}
spring.application.name=sam
#{profile}
spring.cloud.config.profile=dev
#{label}
spring.cloud.config.label=master

#config server uri
spring.cloud.config.uri=http://localhost:7001/

建立controller

@RefreshScope
@RestController
public class TestController {
    
    
    /**
    * 通過@Value 來講配置檔案中的值寫入到程式碼中
    */
    @Value("${from}")
    private String from;
    
    @RequestMapping("/from")
    public String from() {
        return from;
    }
}

啟動並測試

四、工作原理

Spring Cloud Config配置中心的工作原理如下:

  1. 客戶端啟動時,根據bootstrap.properties中配置的應用名{application}、環境名{profile}、分支名{label},向Config Server請求獲取配置資訊。
  2. Config Server根據自己維護的Git倉庫資訊和客戶傳遞過來的配置定位資訊去查詢配置資訊。
  1. 通過git clone命令將找到的配置資訊下載到本地(Config Server的檔案系統中)。在通過頁面訪問或啟動客戶端的時候,我們在服務端能看到如下下載的log:

2018-05-14 22:51:58.055 INFO 3084 --- [nio-7001-exec-1] o.s.c.c.s.e.NativeEnvironmentRepository : Adding property source: file:/C:/Users/sam/AppData/Local/Temp/config-repo-8627749771720918793/spring-cloud-config-file/sam-dev.properties

2018-05-14 22:51:58.055 INFO 3084 --- [nio-7001-exec-1] o.s.c.c.s.e.NativeEnvironmentRepository : Adding property source: file:/C:/Users/sam/AppData/Local/Temp/config-repo-8627749771720918793/spring-cloud-config-file/sam.properties

  1. Config Server建立Spring 的ApplicationContext例項,並從Git本地倉庫中載入配置檔案,最後將這些配置內容讀取出來返回給客戶端。
  2. 客戶端在獲取外部配置資訊後加載到客戶端的applicationContext例項。

spring cloud入門系列八:使用spring_ cloud sleuth整合zipkin進行服務鏈路追蹤

微服務架構是一種分散式架構,微服務系統按照業務劃分服務單元,一個微服務往往會有很多個服務單元,一個請求往往會有很多個單元參與,一旦請求出現異常,想要去定位問題點真心不容易,因此需要有個東西去跟蹤請求鏈路,記錄一個請求都呼叫了哪些服務單元,呼叫順序是怎麼樣的以及在各個服務單元處理的時間長短。常見的服務鏈路追蹤元件有google的dapper、twitter的zipkin、阿里的鷹眼等,它們都是出眾的開源鏈路追蹤元件。

spring cloud 有自己的元件來整合這些開源元件,它就是spring cloud sleuth,它為服務鏈路追蹤提供了一套完整的解決方案。

今天的主題就是如何使用spring cloud sleuth整合zipkin進行服務鏈路追蹤。本部落格將圍繞下面的線索進行展開:

  1. Server端程式碼實現
  2. Client端程式碼實現
  1. 執行測試

由上面的線索可以發現,zipkin分服務端和客戶端。

客戶端就是我們的服務單元,用來發送鏈路資訊到服務端;

服務端用來接收客戶端傳送來的鏈路資訊,並進行處理,它包括4個部分:

  • Collector元件:用來接收客戶端傳送的鏈路資訊然後整理成zipkin能處理的格式,供後續儲存或向外部提供查詢使用。
  • Storage元件:對鏈路資訊進行儲存,預設儲存在記憶體,通過配置還可以儲存到mysql等地方。
  • Restful API元件:對其他服務單元提供api介面進行查詢鏈路資訊。
  • Web UI元件:呼叫API 元件的介面並將資訊顯示到web 畫面。

廢話不多說,直接上程式碼。

一、Server端程式碼實現

先給出程式碼結構:

結構比較簡單,搭建過程如下:

新建maven工程sleuth-zipkin

修改pom檔案

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sam</groupId>
  <artifactId>sleuth-zipkin</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
  </parent>
  
  <properties>
    <javaVersion>1.8</javaVersion>
  </properties>
  <!-- 使用dependencyManagement進行版本管理 -->
  <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>
  
  <dependencies>
    <!-- 引入zipkin-server依賴,提供server端功能 -->
    <dependency>
      <groupId>io.zipkin.java</groupId>
      <artifactId>zipkin-server</artifactId>
    </dependency>
    <!-- 引入zipkin-autoconfigure-ui依賴,用來提供zipkin web ui元件的功能,方便檢視相關資訊 -->
    <dependency>
      <groupId>io.zipkin.java</groupId>
      <artifactId>zipkin-autoconfigure-ui</artifactId>
    </dependency>
    
    <!-- 引入eureka依賴 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    
  </dependencies>
</project>

新建啟動類

/**
 * @EnableZipkinServer
 * 
 * 用於開啟Zipkin Server功能
 *
 */
@EnableZipkinServer
@SpringBootApplication
@EnableDiscoveryClient
public class SleuthZipkinApp {

    public static void main(String[] args) {
        SpringApplication.run(SleuthZipkinApp.class, args);
    }

}

新建配置檔案

server.port=9411
spring.application.name=sleuth-zipkin
#需要使用到eureka服務註冊中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka

二、Client端程式碼實現

這裡我們準備使用前面的隨筆中已經實現好的微服務(閘道器服務api-gateway消費者hello-consumer和生產者hello-server,可以點選連結檢視搭建過程,這裡就不詳細描述了)。在這幾個微服務中都做如下修改:

引入依賴

<!-- 引入zipkin 依賴 ,提供zipkin客戶端的功能 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

修改配置檔案,追加兩項配置#指定zipkin服務端的url

#指定zipkin服務端的url
spring.zipkin.base-url=http://localhost:9411
#設定樣本收集的比率為100%
spring.sleuth.sampler.percentage=1.0

由於分散式系統的請求量一般比較大,不可能把所有的請求鏈路進行收集整理,因此sleuth採用抽樣收集的方式,設定一個抽樣百分比。在開發階段,我們一般設定百分比為100%也就是1。

三、執行測試

依次啟動微服務:服務註冊中心eureka、zipkin服務端sleuth-zipkin、閘道器服務api-gateway、消費者hello-consumer和生產者hello-server

訪問http://localhost:1111/,確認4個微服務已經成功註冊到了服務註冊中心

訪問http://localhost:5555/hello-consumer/hello-consumer?accessToken=111,通過zuul 閘道器進行訪問,

檢視api-gateway控制檯:

2018-07-19 18:02:34.999 INFO [api-gateway,4c384ab23da1ae35,4c384ab23da1ae35,true] 9296 --- [nio-5555-exec-3] com.sam.filter.AccessFilter : send GET request to http://localhost:5555/hello-consumer/hello-consumer

2018-07-19 18:02:45.088 INFO [api-gateway,,,] 9296 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration

請看紅字部分,有4部分,以逗號分隔。

第一部分是服務名;

第二部分是TranceId,每次請求都會有唯一的tranceId;

第三部分是spanId,每個工作單元傳送一次請求就會產生一個spanId,每個請求會產生一個tranceId和多個spanId,根據tranceId和spanId就能分析出一個完整的請求都經歷了哪些服務單元;

第四部分是boolean型的,用來標記是否需要將該請求鏈路進行抽樣收集傳送到zipkin等進行整理。

訪問zipkin服務端http://localhost:9411/,檢視UI頁面選擇api-gateway,然後點選 "Find Trances"能看到請求都經歷了哪些服務節點。再點相關link,可以檢視呼叫順序,並且還能看到在各個服務節點的處理的時間長度。切換到依賴畫面,能檢視服務節點的依賴關係