阿里P7架構(gòu)師的獨(dú)家分享——SpringCloud 微服務(wù)實(shí)戰(zhàn)筆記

一、SpringBoot 構(gòu)建項(xiàng)目

在我們使用傳統(tǒng)的 spring 開發(fā)一個(gè) web 應(yīng)用程序通常會(huì)想到一些基本的需要:

  • Web.xml 文件(配置 SpringMVC 的 DispatcherServlet,各種過濾器等等);
  • 啟用了 SpringMVC 的 spring 配置文件;
  • Mybatis 等數(shù)據(jù)庫配置文件等。

以上的這些僅僅只是基本的需求,無論是開發(fā)一個(gè)大型項(xiàng)目或者只是一個(gè) hello word 程序,都需要配置幾乎同等的配置文件,既然這些都是通用的東西,那有什么東西可以把這些給自動(dòng)配置了呢?這時(shí)候 springboot 的自動(dòng)配置功能就派上用場了,springboot 會(huì)為這些常用的配置進(jìn)行自動(dòng)配置。這些自動(dòng)配置涉及很多方面,比如:java 持久化 api,各種 web 模板,springMVC 等等。

1. 起步依賴

平時(shí)我們使用 maven 創(chuàng)建一個(gè) web 項(xiàng)目的時(shí)候,常常需要想項(xiàng)目需要哪些包,以及包的版本。但是在 springboot 創(chuàng)建 web 應(yīng)用的時(shí)候,你只需你只需添加 springboot 的 Web 起步依賴(org.springframework.boot:spring-boot-starter-web)。它會(huì)根據(jù)依賴傳遞把其他所需依賴引入項(xiàng)目里面。

而其它你需要的功能,你只需要引入相關(guān)的的起步依賴即可。

2. 內(nèi)嵌 Servlet 容器

其實(shí) springboot 并不是一個(gè)應(yīng)用服務(wù)器,它之所以可以運(yùn)行 web 應(yīng)用程序,是因?yàn)槠鋬?nèi)部已經(jīng)內(nèi)嵌了一個(gè) Servlet 容器(Tomcat、Jetty 或 Undertow),其運(yùn)行原理是把 web 應(yīng)用直接打包成為一個(gè) jar/war,然后這個(gè) jar/war 是可以直接啟動(dòng)的,不需要另外配置一個(gè) Web Server。相關(guān)的 embed 類就是它的依賴包。

3. Spring Initializr 構(gòu)建 springboot 應(yīng)用程序

本文使用的是 intellij idea 中的 Spring Initializr 工具創(chuàng)建 springboot 應(yīng)用程序。

菜單欄中選擇File=>New=>Project..,步驟大概是選擇構(gòu)建的工程類型,如:maven,Gradle;language 的選擇;選擇 Spring Boot 版本和起步依賴包等等。具體創(chuàng)建步驟這里就省略了。

spring boot 項(xiàng)目結(jié)構(gòu)如圖所示,整個(gè)項(xiàng)目結(jié)構(gòu)遵循了 maven 項(xiàng)目的布局,主要的應(yīng)用程序代碼位于 src/main/java 目錄里,資源都在 src/main/resources 目錄里,測試代碼則在 src/test/java 目錄里。不同的是,web 頁面模板移到 templates 了,我的項(xiàng)目現(xiàn)在主要用 thymeleaf 模板作為 web 頁面。

在結(jié)構(gòu)圖你會(huì)發(fā)現(xiàn)一些與 springboot 密切項(xiàng)目的文件:

  • WebGatewayApplication.java:應(yīng)用程序的啟動(dòng)引導(dǎo)類(bootstrap class),也是主要的 Spring 配置類;
  • application.properties:用于配置應(yīng)用程序和 Spring Boot 的屬性;
  • ReadingListApplicationTests.java:一個(gè)基本的集成測試類。
  • banner.txt:spring boot 應(yīng)用程序啟動(dòng)時(shí)加載的文件。

3.1 啟動(dòng)引導(dǎo) Spring

前面我們看到的 WebGatewayApplication.java 在 springboot 應(yīng)用程序中主要有兩個(gè)作用:配置和啟動(dòng)引導(dǎo)。而也是 Spring 的主要配置類。雖然 springboot 的自動(dòng)配置免除了很多 Spring 配置,但你還需要進(jìn)行少量配置來啟用自動(dòng)配置。

程序清單:

package com.crm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication  // 開啟組件掃描和自動(dòng)配置
public class WebGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebGatewayApplication.class, args);// 啟動(dòng)引導(dǎo)應(yīng)用程序
    }
}

3.2 配置應(yīng)用程序?qū)傩?/h3>

用 Spring Initializr 生成的 application.properties 文件只是一個(gè)空文件,它可以刪除完全不影響應(yīng)用程序的運(yùn)行,但是,如果你想修改應(yīng)用程序的屬性,你就得在里面配置相關(guān)屬性了,比如你在里面配置了 server.port=9010,嵌入式的 tomcat 服務(wù)器的監(jiān)聽端口就不是默認(rèn)的 8080 了,變成了 9010。而且這個(gè)屬性文件是自動(dòng)被加載的。

這是我的項(xiàng)目 application.properties 屬性配置:

###### MySQL配置
spring.datasource.name=test
spring.datasource.url=jdbc:mysql://localhost:3306/crm?characterEncoding=UTF8
spring.datasource.username=zch
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.filters=stat
spring.datasource.maxActive=20
spring.datasource.initialSize=1
spring.datasource.maxWait=60000
spring.datasource.minIdle=1
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=select 'x'
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxOpenPreparedStatements=20
###### mybatis
mybatis.typeAliasesPackage=com.joosure.integral.cloud.pojo.cloud
mybatis.mapperLocations=classpath:mapper/*.xml
####### thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.check-template-location=true
spring.thymeleaf.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.excluded-view-names=
spring.thymeleaf.mode=HTML5
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.template-resolver-order=

3.3 構(gòu)建過程解釋

我的項(xiàng)目用的是 maven 作為構(gòu)建工具,因此用 Spring Initializr 會(huì)生成 pom.xml 文件,這與創(chuàng)建普通的 maven 項(xiàng)目一樣,代碼清單如下:

<version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>crm</name>
    <description>crm-system</description>

    <parent> <!-- 從spring-boot-starterparent繼承版本號(hào) -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies><!-- 起步依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--web及模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--數(shù)據(jù)庫-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>

        <!--測試-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build><!-- 運(yùn)行spring boot插件 -->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 其中 Artifact ID 為 spring-boot-starter-xxx 的都是 spring boot 起步依賴;
  • 構(gòu)建插件的主要功能是把項(xiàng)目打包成一個(gè)可執(zhí)行的超級 JAR(uber-JAR),包括把應(yīng)用程序的所有依賴打入 JAR 文件內(nèi),并為 JAR 添加一個(gè)描述文件,其中的內(nèi)容能讓你用 java -jar 來運(yùn)行應(yīng)用程序;
  • Maven 構(gòu)建說明中還將 spring-boot-starter-parent 作為上一級,這樣一來就能利用 Maven 的依賴管理功能,繼承很多常用庫的依賴版本,在你聲明依賴時(shí)就不用再去指定版本號(hào)了。

二、服務(wù)注冊與發(fā)現(xiàn)

現(xiàn)在公司的積分聯(lián)盟平臺(tái)系統(tǒng)構(gòu)建于公司內(nèi)部的第 4 代架構(gòu)中,而第 4 代就是 基于 SpringCloud 的微服務(wù)架構(gòu),趁著項(xiàng)目上手,花了幾天研究了一下。

SpringCloud 是一個(gè)龐大的分布式系統(tǒng),它包含了眾多模塊,其中主要有:服務(wù)發(fā)現(xiàn)(Eureka),斷路器(Hystrix),智能路由(Zuul),客戶端負(fù)載均衡(Ribbon)等。也就是說微服務(wù)架構(gòu)就是將一個(gè)完整的應(yīng)用從數(shù)據(jù)存儲(chǔ)開始垂直拆分成多個(gè)不同的服務(wù),每個(gè)服務(wù)都能獨(dú)立部署、獨(dú)立維護(hù)、獨(dú)立擴(kuò)展,服務(wù)與服務(wù)間通過諸如 RESTful API 的方式互相調(diào)用。

1. 創(chuàng)建服務(wù)注冊中心

在搭建 SpringCloud 分布式系統(tǒng)前我們需要?jiǎng)?chuàng)建一個(gè)注冊服務(wù)中心,以便監(jiān)控其余模塊的狀況。這里需要在 pom.xml 中引入:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>

并且在 SpringBoot 主程序中加入@EnableEurekaServer 注解:

@EnableEurekaServer
@SpringCloudApplication
public class EurekaServerApplication {

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

接下來在 SpringBoot 的屬性配置文件 application.properties 中如下配置:

server.port=9100
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

server.port 就是你指定注冊服務(wù)中心的端口號(hào),在啟動(dòng)服務(wù)后,可以通過訪問http://localhost:9100服務(wù)發(fā)現(xiàn)頁面,如下:

2. 創(chuàng)建服務(wù)方

我們可以發(fā)現(xiàn)其它系統(tǒng)在這里注冊并顯示在頁面上了,想要注冊到服務(wù)中心,需要在系統(tǒng)上做一些配置,步驟跟創(chuàng)建服務(wù)注冊中心類似,這里 web-gateway 系統(tǒng)做例子:

首先在 pom.xml 中加入:

 <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka</artifactId>
 </dependency>

在 SpringBoot 主程序中加入@EnableDiscoveryClient 注解,該注解能激活 Eureka 中的DiscoveryClient實(shí)現(xiàn),才能實(shí)現(xiàn) Controller 中對服務(wù)信息的輸出:

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

在 SpringBoot 的屬性配置文件 application.properties 中如下配置:

spring.application.name=web-gateway
server.port=9010
eureka.client.serviceUrl.defaultZone=http://localhost:9100/eureka/
eureka.instance.leaseRenewalIntervalInSeconds=5

再次啟動(dòng)服務(wù)中心,打開鏈接:http://localhost:9100/,就可以看到剛剛創(chuàng)建的服務(wù)了。

三、服務(wù)消費(fèi)者

在系統(tǒng)與系統(tǒng)之間,如何進(jìn)行相互間的調(diào)用呢?也就是說怎么去調(diào)用服務(wù)提供的接口內(nèi)容呢?這里就要說一下 Ribbon 了,Ribbon 是一個(gè)基于 http 和 tcp 客戶端的負(fù)載均衡器。

下面我來簡單介紹如何在 SpringCloud 分布式系統(tǒng)下使用 Ribbon 來實(shí)現(xiàn)負(fù)載均衡。

首先在 pom.xml 中引入一下依賴:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

然后在 spring boot 主程序中創(chuàng)建 RestTemplate 類,并為它加上@LoadBalanced 注解開啟負(fù)載均衡的能力:

@EnableDiscoveryClient
@SpringBootApplication
public class WebGatewayApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

RestTemplate 類是 Spring 用于構(gòu)建 Restful 服務(wù)而提供的一種 Rest 服務(wù)可客戶端,RestTemplate 提供了多種便捷訪問遠(yuǎn)程 Http 服務(wù)的方法。

在 apllication.properties 配置文件中配置 eureka 服務(wù),并注冊到服務(wù)中心:

spring.application.name=integral-server
server.port=9600
eureka.client.serviceUrl.defaultZone=http://localhost:9100/eureka/

在公司項(xiàng)目中正是通過 RestTemplate 來訪問各個(gè)微服務(wù)提供的接口,比如在項(xiàng)目中要訪問積分系統(tǒng) integral-server,添加積分用戶:

JSONObject integralServerResult = restTemplate.postForObject("http://integral-server/shop/add", RequestHandler.getRestRawRequestEntity(integralShopJson), JSONObject.class);

這樣就可以調(diào)用 integral-server 系統(tǒng)的添加用戶的接口實(shí)現(xiàn)在別的系統(tǒng)中添加用戶了。

我們也可以在 application.properties 配置文件中加入:

###### Ribbon
ribbon.ReadTimeout=60000

這個(gè)是設(shè)置負(fù)載均衡的超時(shí)時(shí)間的。

四、斷路器

微服務(wù)架構(gòu)中,各個(gè)系統(tǒng)被拆分成一個(gè)個(gè)服務(wù)單元,鏈路調(diào)用可能包括很多個(gè)服務(wù)單元,而每個(gè)單元又會(huì)個(gè) N 個(gè)服務(wù)單元提供服務(wù),因此如果有一個(gè)服務(wù)單元出現(xiàn)故障,就可能導(dǎo)致其它依賴此服務(wù)的服務(wù)單元出現(xiàn)延遲,導(dǎo)致整個(gè)微服務(wù)系統(tǒng)出現(xiàn)雪崩效應(yīng)。

在 SpringCloud 模塊中有一個(gè)叫 Netflix Hystrix 的斷路器模塊,就是專門解決這個(gè)問題而生的,Hystrix 是 Netflix 開源的微服務(wù)框架套件之一,該框架目標(biāo)在于通過控制那些訪問遠(yuǎn)程系統(tǒng)、服務(wù)和第三方庫的節(jié)點(diǎn),從而對延遲和故障提供更強(qiáng)大的容錯(cuò)能力。

下面來說一下 Hystrix 在微服務(wù)系統(tǒng)中的具體用法:

首先還是在 pom.xml 中加入以下依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

在 spring boot 主程序中加入@EnableCircuitBreaker 注解開啟斷路器模式:

@EnableEurekaClient
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class WebGatewayApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

如果在調(diào)用過程中返回類似這樣的響應(yīng):

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sat May 13 00:10:22 CST 2017
There was an unexpected error (type=Internal Server Error, status=500).
400 null

斷路器也就開啟了。

我們也可以在 application.properties 配置文件中加入:

## hystrix
hystrix.commond.default.execution.isolation.thread.timeoutInMilliseconds=60000

這個(gè)設(shè)置可以更改返回錯(cuò)誤響應(yīng)的超時(shí)時(shí)間。

如果不想返回默認(rèn)的錯(cuò)誤響應(yīng)信息,我們還可以通過自定義來更改錯(cuò)誤響應(yīng)信息,我們需要一個(gè)類中注入一個(gè) RestTemplate 類:

 @Autowired
 RestTemplate restTemplate;

這個(gè)類在上面已經(jīng)通過 Spring 創(chuàng)建好了,這里直接注入在類中即可,接下來我們在類中寫一個(gè)方法:

@HystrixCommand(fallbackMethod = "addServiceFallback")
    public String addService() {
        return restTemplate.postForObject("http://integral-server/shop/add", RequestHandler.getRestRawRequestEntity(integralShopJson), JSONObject.class);
    }
    public String addServiceFallback() {
        return "error";
    }

當(dāng)調(diào)用 integral-server 系統(tǒng)的添加接口超出延時(shí)的時(shí)間時(shí),就會(huì)返回“error”。

五、服務(wù)網(wǎng)關(guān)

前面我們通過 Ribbon 實(shí)現(xiàn)服務(wù)的消費(fèi)和負(fù)載均衡,但還有些不足的地方,舉個(gè)例子,服務(wù) A 和服務(wù) B,他們都注冊到服務(wù)注冊中心,這里還有個(gè)對外提供的一個(gè)服務(wù),這個(gè)服務(wù)通過負(fù)載均衡提供調(diào)用服務(wù) A 和服務(wù) B 的方法,那么問題來了,每個(gè)服務(wù)都變得有狀態(tài)了,即每個(gè)服務(wù)都需要維護(hù)一套校驗(yàn)邏輯,這樣會(huì)帶來對外接口有污染。而且權(quán)限等不好集中管理,整個(gè)集群處于混亂之中。

最好的方法就是把所有請求都集中在最前端的地方,這地方就是 zuul 服務(wù)網(wǎng)關(guān)。

服務(wù)網(wǎng)關(guān)是微服務(wù)架構(gòu)組件中處于最外一層,通過服務(wù)網(wǎng)關(guān)統(tǒng)一,可以將鏈路前端集中管理起來,除了具備服務(wù)路由、均衡負(fù)載功能之外,它還需要具備權(quán)限控制等功能。Spring Cloud Netflix 中的 Zuul 就擔(dān)任了這樣的一個(gè)角色,為微服務(wù)披上了一層保護(hù)層,也方便了權(quán)限校驗(yàn)集中管理,增加了接口的通用性。

1. 配置服務(wù)路由

要使用 zuul,就要引入它的依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

在 spring boot 主程序中加入@EnableZuulProxy 注解開啟 zuul:

@EnableEurekaClient
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class WebGatewayApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

在 application.properties 配置文件中配置 zuul 路由 url:

spring.application.name=web-gateway
server.port=9010

到這里,一個(gè)微服務(wù) zuul 服務(wù)網(wǎng)關(guān)系統(tǒng)已經(jīng)可以運(yùn)行了,接下來就是如何配置訪問其它微服務(wù)系統(tǒng)的 url,zuul 提供了兩種配置方式,一種是通過 url 直接映射,另一種是利用注冊到 eureka server 中的服務(wù) id 作映射:

url 直接映射:

zuul.routes.api-integral.path=/api-integral-url/**
zuul.routes.api-integral.url=http://localhost:8080/

以上規(guī)則意思是 /api-integral-url/** 的訪問都會(huì)被路由到 http://localhost:8080/上。

但是這么做必須得知道所有的微服務(wù)的地址,才能完成配置,這時(shí)我們可以利用注冊到 eureka server 中的服務(wù) id 作映射:

###### Zuul配置
zuul.routes.api-integral.path=/integral/**
zuul.routes.api-integral.serviceId=integral-server

zuul.routes.api-member.path=/member/**
zuul.routes.api-member.serviceId=member-server

integral-server 和 member-server 是這倆微服務(wù)系統(tǒng)注冊到微服務(wù)中心的一個(gè) serverId,我們通過配置,訪問http://localhost:9010/integual/add?a=1&b=2,該請求就會(huì)訪問 integral-server 系統(tǒng)中的 add 服務(wù)。

2. 服務(wù)過濾

在定義 zuul 網(wǎng)關(guān)服務(wù)過濾只需要?jiǎng)?chuàng)建一個(gè)繼承 ZuulFilter 抽象類并重寫四個(gè)方法即可,下面是 ZuulFilter 的一些解釋:

  • filterType:過濾類型,具體如下:
  • pre:請求路由之前執(zhí)行;
  • routing:請求路由時(shí)執(zhí)行;
  • post:在 routing 和 error 過濾器之后執(zhí)行;
  • error:在請求發(fā)生錯(cuò)誤的時(shí)候執(zhí)行;
  • filterOrder:定義過濾器的執(zhí)行順序
  • shouldFilter:判斷該過濾器是否要執(zhí)行,
  • run:過濾器的具體邏輯。

標(biāo)準(zhǔn)實(shí)例程序:

public class ErrFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        Object accessToken = request.getParameter("accessToken");
        if(accessToken == null) {
            log.warn("access token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            return null;
        }
        return null;
    }
}

在自定過濾器之后,我們還需要在 SpringBoot 主程序中加入@EnableZuulProxy 注解來開啟 zuul 路由的服務(wù)過濾:

@EnableZuulProxy
@EnableEurekaClient
@RibbonClients
@SpringCloudApplication
public class ApiGatewayApplication {

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

    @Bean
    PosPreFilter posPreFilter(){
        return new PosPreFilter();
    }

到這里,微服務(wù)系統(tǒng)的 zuul 路由功能基本搭建完成。

六、Feign

之前說過了微服務(wù)間,我是通過 Spring 的 RestTemplate 類來相互調(diào)用的,它可通過整合 Ribbon 實(shí)現(xiàn)負(fù)載均衡,但發(fā)現(xiàn)了這樣寫不夠優(yōu)雅,且不夠模板化,因此本篇介紹一下 Feign。

Feign 是一種聲明式、模板化的 HTTP 客戶端,在 Spring Cloud 中使用 Feign 其實(shí)就是創(chuàng)建一個(gè)接口類,它跟普通接口沒啥兩樣,因此通過 Feign 調(diào)用 HTTP 請求,開發(fā)者完全感知不到這是遠(yuǎn)程方法。

1. 整合 Feign

添加 Feign 依賴:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

創(chuàng)建 一個(gè) Feign 接口:

@FeignClient(value = FeignConst.COUPON_PROVIDER, url = "${feign.coupon.url:}")
public interface CouponClient {

  @GetMapping(value = "/coupon/list/page", headers = LocalsEncoder.CONTENT_TYPE_LOCALS_GET)
  RestResponse couponList(@ModelAttribute CouponCriteria criteria);

}

啟動(dòng) Feign 類

@EnableFeignClients(basePackages = {"com.objcoding"})
@SpringCloudApplication
public class ProviderApplication {

}

2. 服務(wù)降級

當(dāng)網(wǎng)絡(luò)不穩(wěn)定時(shí),一個(gè)接口響應(yīng)非常慢,就會(huì)一直占用這個(gè)連接資源,如果長時(shí)間不做處理,會(huì)導(dǎo)致系統(tǒng)雪崩,幸好,F(xiàn)eign 已經(jīng)繼承了熔斷器 Hystrix

@FeignClient(value = FeignConst.COUPON_PROVIDER, url = "${feign.coupon.url:}", fallback = CouponClient.CouponClientFallBack.class)
public interface CouponClient {

  @GetMapping(value = "/coupon/list/page", headers = LocalsEncoder.CONTENT_TYPE_LOCALS_GET)
  RestResponse couponList(@ModelAttribute CouponCriteria criteria);

  @Component
  class CouponClientFallBack implements CouponClient {
    @Override
    public RestResponse couponList(CouponCriteria criteria) {
      return RestResponse.failed("網(wǎng)絡(luò)超時(shí)");
    }
  }

}

3. 攔截器

有時(shí)候微服務(wù)間的調(diào)用,需要傳遞權(quán)限信息,這些信息都包含在請求頭了,這時(shí)我們可以通過 Feign 攔截器實(shí)現(xiàn)權(quán)限穿透:

@Configuration
public class WebRequestInterceptor {

  @Bean
  public RequestInterceptor headerInterceptor() {
    return template -> {
      ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      if (attributes == null) {
        return;
      }
      HttpServletRequest request = attributes.getRequest();
      Enumeration<String> headerNames = request.getHeaderNames();
      if (headerNames != null) {
        while (headerNames.hasMoreElements()) {
          String name = headerNames.nextElement();
          String values = request.getHeader(name);
          template.header(name, values);
        }
      }
    };
  }
}

讀者福利:點(diǎn)擊下方傳送門,即可免費(fèi)領(lǐng)取筆者整理的Java后端面試題及Java架構(gòu)師成長路線圖?。?!

傳送門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容