# 第1章 SpringCloud
今日內(nèi)容介紹

學(xué)習(xí)目標(biāo):
- 掌握架構(gòu)演進(jìn)過(guò)程
- 理解微服務(wù)拆分流程及遠(yuǎn)程調(diào)用過(guò)程
- 掌握注冊(cè)中心Eureka的使用
- 掌握負(fù)載均衡Ribbon的使用
- 能夠基于Feign實(shí)現(xiàn)服務(wù)遠(yuǎn)程調(diào)用
1 服務(wù)架構(gòu)演進(jìn)[了解]
章節(jié)知識(shí)點(diǎn)
- 單體架構(gòu)
- 分布式架構(gòu)
- 微服務(wù)架構(gòu)
- SpringCloud
過(guò)去的互聯(lián)網(wǎng):
1:用戶量不多
2:并發(fā)低
3:數(shù)據(jù)少
現(xiàn)在的互聯(lián)網(wǎng):
1:用戶多
2:并發(fā)高
3:數(shù)據(jù)龐大
互聯(lián)網(wǎng)架構(gòu)從簡(jiǎn)到繁的演進(jìn)經(jīng)歷了單體架構(gòu)、分布式架構(gòu)、SOA架構(gòu)、微服務(wù)架構(gòu)以及最新的service mesh的演進(jìn)過(guò)程。
1.1 單體架構(gòu)
1)概念
早期互聯(lián)網(wǎng)產(chǎn)品用戶量少,并發(fā)量低,數(shù)據(jù)量小,單個(gè)應(yīng)用服務(wù)器可以滿足需要,這就是最早互聯(lián)網(wǎng)架構(gòu)。我們用一句話總結(jié)什么是單體架構(gòu):將業(yè)務(wù)的所有功能集中在一個(gè)項(xiàng)目中開發(fā),部署為一個(gè)節(jié)點(diǎn)。
2)架構(gòu)圖
3)優(yōu)缺點(diǎn)
#優(yōu)點(diǎn):
1)架構(gòu)簡(jiǎn)單
2)部署成本低
#缺點(diǎn):
1)耦合度高
1.2 分布式架構(gòu)
1)概念
根據(jù)業(yè)務(wù)功能對(duì)系統(tǒng)進(jìn)行拆分,每個(gè)業(yè)務(wù)模塊稱為一個(gè)服務(wù)。
2)架構(gòu)圖
3)優(yōu)缺點(diǎn)
#優(yōu)點(diǎn)
1)降低服務(wù)耦合度
2)有利于服務(wù)升級(jí)拓展
#缺點(diǎn)
1)維護(hù)成本增加
2)服務(wù)間調(diào)用復(fù)雜度增加
4)需要解決的問(wèn)題
1)服務(wù)拆分粒度如何?
2)服務(wù)之間如何實(shí)現(xiàn)調(diào)用?
3)服務(wù)關(guān)系如何管理?
1.3 微服務(wù)
1)概念
微服務(wù)是系統(tǒng)架構(gòu)的一種設(shè)計(jì)風(fēng)格,將一個(gè)原本獨(dú)立的服務(wù)拆分成多個(gè)小型服務(wù),每個(gè)服務(wù)獨(dú)立運(yùn)行在在各自的進(jìn)程中,服務(wù)之間通過(guò) HTTP RESTful API 進(jìn)行通信.每個(gè)小型的服務(wù)都圍繞著系統(tǒng)中的某個(gè)耦合度較高的業(yè)務(wù)進(jìn)行構(gòu)建。
#微服務(wù)是一種經(jīng)過(guò)良好設(shè)計(jì)的分布式架構(gòu)方案,而全球的互聯(lián)網(wǎng)公司都在積極嘗試自己的微服務(wù)落地方案。其中在java領(lǐng)域最引人注目的是SpringCloud提供的方案。
2)架構(gòu)圖
3)微服務(wù)架構(gòu)特征
單一職責(zé):微服務(wù)拆分粒度更小,每個(gè)服務(wù)都應(yīng)對(duì)唯一的業(yè)務(wù)能力,做到單一職責(zé)
自治:團(tuán)隊(duì)獨(dú)立、技術(shù)獨(dú)立、數(shù)據(jù)獨(dú)立,獨(dú)立部署和交付
面向服務(wù):服務(wù)提供統(tǒng)一標(biāo)準(zhǔn)的接口,與語(yǔ)言無(wú)關(guān)、與技術(shù)無(wú)關(guān)
隔離性強(qiáng):服務(wù)調(diào)用做好隔離、容錯(cuò)、降級(jí),避免出現(xiàn)級(jí)聯(lián)問(wèn)題
1.4 SpringCloud
-
SpringCloud是目前國(guó)內(nèi)使用最廣泛的微服務(wù)技術(shù)棧。官網(wǎng)地址:https://spring.io/projects/spring-cloud。
-
SpringCloud集成了各種微服務(wù)功能組件,并基于SpringBoot實(shí)現(xiàn)了這些組件的自動(dòng)裝配,從而提供了良好的開箱即用體驗(yàn):
- SpringCloud與SpringBoot的版本兼容關(guān)系如下:
- 我們課堂學(xué)習(xí)的版本是 Hoxton.SR10,因此對(duì)應(yīng)的SpringBoot版本是2.3.x(2.3.8)版本。
1.5 總結(jié)
-
單體架構(gòu):簡(jiǎn)單方便,高度耦合,擴(kuò)展性差,適合小型項(xiàng)目。例如:學(xué)生管理系統(tǒng),后臺(tái)管理系統(tǒng),ERP,OA 中小級(jí)企業(yè)級(jí)應(yīng)用
-
分布式架構(gòu):松耦合,擴(kuò)展性好,但架構(gòu)復(fù)雜,難度大。適合大型互聯(lián)網(wǎng)項(xiàng)目,例如:京東、淘寶
-
微服務(wù):一種良好的分布式架構(gòu)方案
-
優(yōu)點(diǎn):拆分粒度更小、服務(wù)更獨(dú)立、耦合度更低
-
缺點(diǎn):架構(gòu)非常復(fù)雜,運(yùn)維、監(jiān)控、部署難度提高
-
SpringCloud:SpringCloud是微服務(wù)架構(gòu)的一站式解決方案,集成了各種優(yōu)秀微服務(wù)功能組件
2 服務(wù)拆分及遠(yuǎn)程調(diào)用[掌握]
章節(jié)知識(shí)點(diǎn)
- 遠(yuǎn)程調(diào)用案例業(yè)務(wù)介紹
- 工程導(dǎo)入
- 使用RestTemplate實(shí)現(xiàn)遠(yuǎn)程調(diào)用
- 服務(wù)提供者、服務(wù)消費(fèi)者概念
- RestTemplate源碼剖析
- 服務(wù)調(diào)用出現(xiàn)的問(wèn)題
案例說(shuō)明:管理員查詢訂單詳情->根據(jù)訂單id查詢訂單的同時(shí),把訂單所屬的用戶信息一起返回,如下圖:
2.1 工程導(dǎo)入
- SQL導(dǎo)入
將資料\工程代碼\springcloud-parent\sql腳本中的cloud-order.sql和cloud-user.sql分別導(dǎo)入到兩個(gè)數(shù)據(jù)庫(kù)中。
- 工程導(dǎo)入
將資料\工程\springcloud-parent導(dǎo)入到IDEA中

- 修改數(shù)據(jù)庫(kù)配置,并測(cè)試
查詢某用戶詳情信息:http://localhost:18081/user/1
查詢某訂單詳情信息:http://localhost:18082/order/101
2.2 遠(yuǎn)程調(diào)用
1)RestTemplate介紹
RestTemplate 是spring家族中一款基于http協(xié)議的組件(HttpURLConnection),他的作用就是:用來(lái)實(shí)現(xiàn)基于http的協(xié)議方式的服務(wù)之間的通信(也就是遠(yuǎn)程服務(wù)調(diào)用)。
RestTemplate 采用同步方式執(zhí)行 HTTP 請(qǐng)求,底層使用 JDK 原生 HttpURLConnection API 。
#概念總結(jié):RestTemplate是spring提供的一個(gè)用來(lái)模擬瀏覽器發(fā)送請(qǐng)求和接收響應(yīng)的一個(gè)類,它能基于Http協(xié)議實(shí)現(xiàn)遠(yuǎn)程調(diào)用。
2)注冊(cè)RestTemplate
在itheima-order的OrderApp中注冊(cè)RestTemplate`:
3)遠(yuǎn)程調(diào)用
修改itheima-order中的OrderServiceImpl的findById方法

4)測(cè)試

2.3 服務(wù)提供者、服務(wù)消費(fèi)者
服務(wù)提供者:一次業(yè)務(wù)中,被其它微服務(wù)調(diào)用的服務(wù)。(提供接口給其它微服務(wù))
服務(wù)消費(fèi)者:一次業(yè)務(wù)中,調(diào)用其它微服務(wù)的服務(wù)。(調(diào)用其它微服務(wù)提供的接口)
在上面案例中itheima-order調(diào)用了itheima-user提供的接口,所以itheima-order是服務(wù)消費(fèi)者,itheima-user是服務(wù)提供者。
2.4 RestTemplate源碼剖析
下面是RestTemplate部分源碼,我們可以看到執(zhí)行過(guò)程中采用了Http請(qǐng)求。
沿著RestTemplate.doExecute()往下看相關(guān)源碼:
一直往后跟蹤,在SimpleBufferingClientHttpRequest類中的executeInternal方法中,可以發(fā)現(xiàn)會(huì)調(diào)用sun.net.www.protocol.http.HttpURLConnection.connect()實(shí)現(xiàn)遠(yuǎn)程調(diào)用:
2.5 服務(wù)調(diào)用出現(xiàn)的問(wèn)題

按照上面調(diào)用流程,消費(fèi)者調(diào)用服務(wù)者存在很多問(wèn)題:
1:服務(wù)消費(fèi)者該如何獲取服務(wù)提供者的地址信息?
2:如果有多個(gè)服務(wù)提供者,消費(fèi)者該如何選擇?
3:消費(fèi)者如何得知服務(wù)提供者的健康狀態(tài)?
2.6 總結(jié)
- RestTemplate使用有2個(gè)步驟:
- 1)注冊(cè)RestTemplate
- 2)使用restTemplate.getForObject(url,T.class)遠(yuǎn)程調(diào)用
- RestTemplate底層是封裝了Http請(qǐng)求
- 服務(wù)提供者、服務(wù)消費(fèi)者
- 服務(wù)提供者:一次業(yè)務(wù)中,被其它微服務(wù)調(diào)用的服務(wù)。(提供接口給其它微服務(wù))
- 服務(wù)消費(fèi)者:一次業(yè)務(wù)中,調(diào)用其它微服務(wù)的服務(wù)。(調(diào)用其它微服務(wù)提供的接口)
3 注冊(cè)中心-Eureka[會(huì)搭建]
章節(jié)知識(shí)點(diǎn)
- 注冊(cè)中心的作用講解
- EurekaServer搭建
- 服務(wù)提供者注冊(cè)
- 服務(wù)消費(fèi)者注冊(cè)
3.1 Eureka的作用
Eureka注冊(cè)中心如何解決上面的問(wèn)題?
Eureka工作原理
#1:消費(fèi)者該如何獲取服務(wù)提供者具體信息?
服務(wù)提供者啟動(dòng)時(shí)向eureka注冊(cè)自己的信息
eureka保存這些信息
消費(fèi)者根據(jù)服務(wù)名稱向eureka拉取提供者信息
#2:如果有多個(gè)服務(wù)提供者,消費(fèi)者該如何選擇?
服務(wù)消費(fèi)者利用負(fù)載均衡算法,從服務(wù)列表中挑選一個(gè)
#3:消費(fèi)者如何感知服務(wù)提供者健康狀態(tài)?
服務(wù)提供者會(huì)每隔30秒向EurekaServer發(fā)送心跳請(qǐng)求,報(bào)告健康狀態(tài)
EurekaServer在90秒內(nèi)沒(méi)有接收到某個(gè)微服務(wù)節(jié)點(diǎn)的心跳,EurekaServer將會(huì)注銷該微服務(wù)的節(jié)點(diǎn)
消費(fèi)者就可以拉取到最新的信息
3.2 Eureka注冊(cè)中心實(shí)戰(zhàn)
3.2.1 搭建EurekaServer
搭建EurekaServer服務(wù)步驟如下:
1)pom.xml
創(chuàng)建項(xiàng)目itheima-eurekaserver,引入spring-cloud-starter-netflix-eureka-server的依賴:
<dependencies>
<!--EurekaServer包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2)啟動(dòng)類
創(chuàng)建啟動(dòng)類com.itheima.EurekaServerApp,代碼如下:
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApp.class,args);
}
}
3)核心配置文件application.yml
server:
port: 8001 #端口號(hào)
spring:
application:
name: eureka-server # 應(yīng)用名稱,會(huì)在Eureka中作為服務(wù)的id標(biāo)識(shí)(serviceId)
eureka:
client:
register-with-eureka: false #是否將自己注冊(cè)到Eureka中
fetch-registry: false #是否從eureka中獲取服務(wù)信息
service-url:
defaultZone: http://localhost:8001/eureka
此時(shí)我們?cè)L問(wèn)EurekaServer地址http://localhost:8001/,效果如下:
3.2.2 服務(wù)提供者注冊(cè)
將itheima-user服務(wù)注冊(cè)到EurekaServer步驟如下:
1)pom.xml
在itheima-user添加如下依賴:
<!--EurekaClient包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)修改application.yml
修改itheima-user的application.yml,添加如下配置:
...
eureka:
client:
service-url:
# EurekaServer的地址
defaultZone: http://localhost:8001/eureka
instance:
#以IP地址注冊(cè)到服務(wù)中心
prefer-ip-address: true
#服務(wù)向eureka注冊(cè)時(shí),注冊(cè)名默認(rèn):“IP名:應(yīng)用名:應(yīng)用端口名”
#現(xiàn)在配置:注冊(cè)名:應(yīng)用名:端口:項(xiàng)目版本號(hào)
instance-id: ${spring.application.name}:${server.port}:@project.version@
prefer-ip-address:true 效果圖

prefer-ip-address:flase 效果圖

3)多實(shí)例啟動(dòng)

分別啟動(dòng)3個(gè)服務(wù)配置,Eureka(http://localhost:8001/)信息如下:

3.3.3 服務(wù)消費(fèi)者注冊(cè)
itheima-order雖然是消費(fèi)者,但與itheima-user一樣都是eureka的client端,同樣可以實(shí)現(xiàn)服務(wù)注冊(cè):
在itheima-order項(xiàng)目引入spring-cloud-starter-netflix-eureka-client的依賴
1)pom.xml
在itheima-order的pom.xml中引入如下依賴
<!--EurekaClient包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)修改application.yml
修改itheima-order的application.yml,添加如下配置:
server:
port: 18082
spring:
application:
name: itheima-order
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud-order?characterEncoding=UTF-8&&serverTimezone=GMT
username: root
password: 123456
eureka:
client:
service-url:
# EurekaServer的地址
defaultZone: http://localhost:8001/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${server.port}:@project.version@
3.3.4 遠(yuǎn)程調(diào)用
在itheima-order完成服務(wù)拉取實(shí)現(xiàn)遠(yuǎn)程調(diào)用,服務(wù)拉取是基于服務(wù)名稱獲取服務(wù)列表,然后在對(duì)服務(wù)列表做負(fù)載均衡。
修改itheima-order的OrderServiceImpl的代碼,修改訪問(wèn)的url路徑,用服務(wù)名代替ip、端口,代碼如下:
在itheima-order項(xiàng)目的啟動(dòng)類OrderApplication中的RestTemplate添加負(fù)載均衡注解:
在itheima-order工程啟動(dòng)類OrderApp中,開啟負(fù)載均衡
/***
* 注冊(cè)RestTemplate
*/
@Bean
@LoadBalanced//開啟負(fù)載均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
我們?cè)L問(wèn)http://localhost:18082/order/101測(cè)試效果如下:
3.3.5 Eureka配置說(shuō)明
服務(wù)注冊(cè)時(shí)默認(rèn)使用的是主機(jī)名,如果我們想用ip進(jìn)行注冊(cè),可以在客戶端(提供者與消費(fèi)者)中的application.yml添加配置:
eureka:
client:
service-url:
# EurekaServer的地址
defaultZone: http://localhost:8001/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${server.port}:@project.version@
lease-renewal-interval-in-seconds: 30 #心跳周期,默認(rèn)是30秒
lease-expiration-duration-in-seconds: 90 #心跳失敗最長(zhǎng)超時(shí)間,默認(rèn)90秒
itheima-eurekaserver 服務(wù)端,可以關(guān)閉保護(hù)機(jī)制
eureka:
...
server:
enable-self-preservation: false # false關(guān)閉保護(hù)機(jī)制。 15分鐘內(nèi),如果心跳成功率<85%,則啟動(dòng)保護(hù)(服務(wù)提供者列表不再變化)
3.3.6 總結(jié)
-
搭建EurekaServer
- 引入eureka-server依賴
- 啟動(dòng)類上添加@EnableEurekaServer注解
- 在application.yml中配置eureka地址
-
服務(wù)注冊(cè)
- 引入eureka-client依賴
- 在application.yml中配置eureka地址
-
服務(wù)發(fā)現(xiàn)
- 引入eureka-client依賴
- 在application.yml中配置eureka地址
- 給RestTemplate添加@LoadBalanced注解
- 用服務(wù)提供者的服務(wù)名稱遠(yuǎn)程調(diào)用(由原來(lái)的ip:port改服務(wù)名(spring.application.name))
4 負(fù)載均衡Ribbon
章節(jié)知識(shí)點(diǎn)
- Ribbon是什么
- 負(fù)載均衡流程講解
- 負(fù)載均衡算法學(xué)習(xí)
- Ribbon負(fù)載均衡使用
Ribbon是什么?
Ribbon是Netflix發(fā)布的負(fù)載均衡器,有助于控制HTTP客戶端行為。為Ribbon配置服務(wù)提供者地址列表后,Ribbon就可基于負(fù)載均衡算法,自動(dòng)幫助服務(wù)消費(fèi)者請(qǐng)求。
概念:Ribbon是基于Http協(xié)議請(qǐng)求的客戶端負(fù)載均衡器,能實(shí)現(xiàn)很豐富的負(fù)載均衡算法。
4.1 負(fù)載均衡流程

負(fù)載均衡流程如上圖所示:
1:用戶發(fā)起請(qǐng)求,會(huì)先到達(dá)itheima-order服務(wù)
2:itheima-order服務(wù)通過(guò)Ribbon負(fù)載均衡器從eurekaserver中獲取服務(wù)列表
3:獲取了服務(wù)列表后,輪詢(負(fù)載均衡算法)調(diào)用
4.2 負(fù)載均衡算法【面試】

輪詢調(diào)用會(huì)涉及到很多負(fù)載均衡算法,負(fù)載均衡算法比較多,關(guān)系圖如下:

Ribbon的負(fù)載均衡算法策略如下表:
| 內(nèi)置負(fù)載均衡規(guī)則類 | 規(guī)則描述 |
|---|---|
| RoundRobinRule | 簡(jiǎn)單輪詢服務(wù)列表來(lái)選擇服務(wù)器。 |
| AvailabilityFilteringRule | 對(duì)以下兩種服務(wù)器進(jìn)行忽略: (1)在默認(rèn)情況下,這臺(tái)服務(wù)器如果3次連接失敗,這臺(tái)服務(wù)器就會(huì)被設(shè)置為“短路”狀態(tài)。短路狀態(tài)將持續(xù)30秒,如果再次連接失敗,短路的持續(xù)時(shí)間就會(huì)幾何級(jí)地增加。 (2)并發(fā)數(shù)過(guò)高的服務(wù)器。如果一個(gè)服務(wù)器的并發(fā)連接數(shù)過(guò)高,配置了AvailabilityFilteringRule規(guī)則的客戶端也會(huì)將其忽略。并發(fā)連接數(shù)的上限,可以由客戶端的 <clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit屬性進(jìn)行配置。 |
| WeightedResponseTimeRule | 為每一個(gè)服務(wù)器賦予一個(gè)權(quán)重值。服務(wù)器響應(yīng)時(shí)間越長(zhǎng),這個(gè)服務(wù)器的權(quán)重就越小。這個(gè)規(guī)則會(huì)隨機(jī)選擇服務(wù)器,這個(gè)權(quán)重值會(huì)影響服務(wù)器的選擇。 |
| ZoneAvoidanceRule【默認(rèn)】 | 以區(qū)域可用的服務(wù)器為基礎(chǔ)進(jìn)行服務(wù)器的選擇。使用Zone對(duì)服務(wù)器進(jìn)行分類,這個(gè)Zone可以理解為一個(gè)機(jī)房、一個(gè)機(jī)架等。而后再對(duì)Zone內(nèi)的多個(gè)服務(wù)做輪詢。它是Ribbon默認(rèn)的負(fù)載均衡規(guī)則。 |
| BestAvailableRule | 忽略哪些短路的服務(wù)器,并選擇并發(fā)數(shù)較低的服務(wù)器。 |
| RandomRule | 隨機(jī)選擇一個(gè)可用的服務(wù)器。 |
| RetryRule | 重試機(jī)制的選擇邏輯 |
4.3 Ribbon負(fù)載均衡算法使用
Ribbon負(fù)載均衡算法的使用有2種方式
-
代碼方式
-
注冊(cè)IRule接口的實(shí)現(xiàn)類(負(fù)載均衡算法):在
itheima-order的啟動(dòng)類中添加如下負(fù)載均衡注冊(cè)代碼:/** * 隨機(jī)負(fù)載均衡算法 * @return */ @Bean public IRule randomRule() { return new RandomRule(); }
-
-
配置方式
-
為指定服務(wù)配置負(fù)載均衡算法:在
itheima-order的核心配置文件中添加如下配置:#注意配置到跟節(jié)點(diǎn) #指定服務(wù)使用指定負(fù)載均衡算法 itheima-user: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #負(fù)載均衡規(guī)則
-
從懶加載 變?yōu)?饑餓加載
Ribbon默認(rèn)是采用懶加載,即第一次訪問(wèn)時(shí)才會(huì)去創(chuàng)建LoadBalanceClient,請(qǐng)求時(shí)間會(huì)很長(zhǎng)。
而饑餓加載則會(huì)在項(xiàng)目啟動(dòng)時(shí)創(chuàng)建,降低第一次訪問(wèn)的耗時(shí),在itheima-order的核心配置文件中,添加如下配置開啟饑餓加載:
#注意配置到跟節(jié)點(diǎn)
#饑餓加載
ribbon:
eager-load:
clients: itheima-user #指定對(duì)user這個(gè)服務(wù)饑餓加載
enabled: true #開啟饑餓加載
4.4. 總結(jié)
-
Ribbon負(fù)載均衡規(guī)則
- 規(guī)則接口是IRule
- 默認(rèn)實(shí)現(xiàn)是ZoneAvoidanceRule,根據(jù)zone選擇服務(wù)列表,然后輪詢
-
負(fù)載均衡自定義方式
- 代碼方式:配置靈活,但修改時(shí)需要重新打包發(fā)布
- 配置方式:直觀,方便,無(wú)需重新打包發(fā)布,但是無(wú)法做全局配置
-
饑餓加載
- 開啟饑餓加載
- 指定饑餓加載的微服務(wù)名稱
5 http客戶端Feign[掌握]
章節(jié)知識(shí)點(diǎn)
- Feign介紹
- Feign入門案例學(xué)習(xí)
- Feign日志功能、性能優(yōu)化、最佳實(shí)踐講解
5.1 Feign介紹
先來(lái)看我們以前利用RestTemplate發(fā)起遠(yuǎn)程調(diào)用的代碼:
User user = restTemplate.getForObject("http://itheima-user/user/"+orderInfo.getUserId(), User.class);
存在下面的問(wèn)題:
- 代碼可讀性差,編程體驗(yàn)不統(tǒng)一
- 參數(shù)復(fù)雜URL難以維護(hù)
上面RestTemplate存在的問(wèn)題可以使用Feign解決,那么什么是Feign?
Feign是一個(gè)聲明式的http客戶端,官方地址:https://github.com/OpenFeign/feign
其作用就是幫助我們優(yōu)雅的實(shí)現(xiàn)http請(qǐng)求的發(fā)送,解決上面提到的問(wèn)題。

5.2 Feign入門案例
定義和使用Feign客戶端的步驟如下:
1:引入依賴包 spring-cloud-starter-openfeign
2:添加注解@EnableFeignClients開啟Feign功能
3:定義遠(yuǎn)程調(diào)用接口,在接口中知名遠(yuǎn)程調(diào)用的【服務(wù)名字】、【方法簽名】
4:注入接口,執(zhí)行遠(yuǎn)程調(diào)用(接口)
1)引入依賴
在itheima-order中引入如下依賴:
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2)開啟Feign功能
在itheima-order的啟動(dòng)類OrderApplication添加@EnableFeignClients注解開啟Feign功能,代碼如下:
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//...其他略
}
3)定義遠(yuǎn)程調(diào)用接口
在itheima-order中創(chuàng)建接口UserClient,代碼如下:

上圖代碼如下:在itheima-order工程中添加
package com.itheima.client;
import com.itheima.user.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* order調(diào)用user服務(wù)(代替了 String url = "http://itheima-user/user/" + orderInfo.getUserId();)
* 1.接口上使用@FeignClient(value="被調(diào)用服務(wù)名")
* 2.定義被調(diào)用接口中的方法(基于被調(diào)用的controller編寫)
* 2.1 requestMapping中的路徑必須是全路徑(controller類上的+方法上的)
* 2.2 使用PathVariable注解,必須取別名
*/
@FeignClient(value = "itheima-user")
public interface UserClient {
/**
* 調(diào)用用戶微服中controller的方法
*/
@GetMapping(value = "/user/{id}")
public User one(@PathVariable(value = "id") Long id);
}
主要是基于SpringMVC的注解來(lái)聲明遠(yuǎn)程調(diào)用的信息,比如:
- 服務(wù)名稱:user
- 請(qǐng)求方式:GET
- 請(qǐng)求路徑:/user/
- 請(qǐng)求參數(shù):String username
- 返回值類型:User
4)遠(yuǎn)程調(diào)用
修改itheima-order的OrderServiceImpl.one()方法,執(zhí)行遠(yuǎn)程調(diào)用,代碼如下:
@Autowired
private UserClient userClient;
/**
* 根據(jù)ID查詢訂單信息
*/
@Override
public OrderInfo findById(Long id) {
//1.查詢訂單
OrderInfo orderInfo = orderDao.selectById(id);
//2.根據(jù)訂單查詢用戶信息->需要調(diào)用 【item-user】 服務(wù)
User user = userClient.one(orderInfo.getUserId());
//3.封裝user
orderInfo.setUser(user);
//4.返回訂單信息
return orderInfo;
}
5.3 Feign其他功能
Feign運(yùn)行自定義配置來(lái)覆蓋默認(rèn)配置,可以修改的配置如下:
| 類型 | 作用 | 說(shuō)明 |
|---|---|---|
| feign.Logger.Level | 修改日志級(jí)別 | 包含四種不同的級(jí)別:NONE、BASIC、HEADERS、FULL |
| feign.codec.Decoder | 響應(yīng)結(jié)果的解析器 | http遠(yuǎn)程調(diào)用的結(jié)果做解析,例如解析json字符串為java對(duì)象 |
| feign.codec.Encoder | 請(qǐng)求參數(shù)編碼 | 將請(qǐng)求參數(shù)編碼,便于通過(guò)http請(qǐng)求發(fā)送 |
| feign. Contract | 支持的注解格式 | 默認(rèn)是SpringMVC的注解 |
| feign. Retryer | 失敗重試機(jī)制 | 請(qǐng)求失敗的重試機(jī)制,默認(rèn)是沒(méi)有,不過(guò)會(huì)使用Ribbon的重試 |
NONE:默認(rèn)的,不顯示任何日志
BASIC:僅記錄請(qǐng)求方法、URL、響應(yīng)狀態(tài)碼以及執(zhí)行時(shí)間
HEADERS:除了BASIC中定義的信息以外,還有請(qǐng)求和響應(yīng)的頭信息
FULL:除了HEADERS中定義的信息之外,還有請(qǐng)求和響應(yīng)的正文及元數(shù)據(jù)
SpringBoot日志配置;
5.3.1 Feign日志配置
要想讓Feign日志生效,得結(jié)合著SpringBoot的日志配置一起使用
SpringBoot日志配置
logging:
level:
# feign 日志以什么級(jí)別監(jiān)控哪個(gè)接口
com.itheima: debug
配置Feign日志有兩種方式:
-
配置文件方式
-
全局生效
feign: client: config: default: #這里用default就是全局配置,如果是寫服務(wù)名稱,則是針對(duì)某個(gè)微服務(wù)的配置 loggerLevel: FULL #日志級(jí)別 -
局部生效
feign: client: config: itheima-user: #指定服務(wù) loggerLevel: FULL #日志級(jí)別
-
-
代碼方式
-
注冊(cè)日志級(jí)別
/** * 注冊(cè)日志級(jí)別 * @return */ @Bean public Logger.Level feignLogLevel() { return Logger.Level.FULL; } -
全局生效
#如果是全局配置,則把它放到@EnableFeignClients這個(gè)注解中 @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class) -
局部生效
#如果是局部配置,則把它放到@FeignClient這個(gè)注解中 @FeignClient(value = "itheima-user",configuration = FeignClientConfiguration.class)
-
5.3.2 Feign性能優(yōu)化
Feign底層的客戶端實(shí)現(xiàn):
- URLConnection:默認(rèn)實(shí)現(xiàn),不支持連接池
- Apache HttpClient :支持連接池
- OKHttp:支持連接池
因此優(yōu)化Feign的性能主要包括:
- 使用連接池代替默認(rèn)的URLConnection
- 日志級(jí)別,最好用basic或none
Feign切換Apache HttpClient步驟如下:
1:引入依賴
2:配置連接池
1)引入依賴
在itheima-order中引入如下依賴:
<!--httpClient依賴-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2)配置連接池
在itheima-order的核心配置文件application.yml中添加如下配置:
feign:
client:
config:
default: #這里用default就是全局配置,如果是寫服務(wù)名稱,則是針對(duì)某個(gè)微服務(wù)的配置
loggerLevel: BASIC #日志級(jí)別
itheima-user: #指定服務(wù)
loggerLevel: BASIC #日志級(jí)別
httpclient:
enabled: true #開啟feign對(duì)HttpClient的支持
max-connections: 200 #最大的連接數(shù)
max-connections-per-route: 50 #每個(gè)路徑的最大連接數(shù)
5.3.3 Feign最佳實(shí)現(xiàn)
方式一(繼承):給消費(fèi)者的FeignClient和提供者的controller定義統(tǒng)一的父接口作為標(biāo)準(zhǔn)。
- 服務(wù)緊耦合
- 父接口參數(shù)列表中的映射不會(huì)被繼承

方式二(抽取):將FeignClient抽取為獨(dú)立模塊,并且把接口有關(guān)的POJO、默認(rèn)的Feign配置都放到這個(gè)模塊中,提供給所有消費(fèi)者使用

Feign最佳實(shí)現(xiàn)流程如上圖所示:
實(shí)現(xiàn)最佳實(shí)踐方式二的步驟如下:
1:創(chuàng)建itheima-api,然后引入feign的starter依賴 itheima-user依賴
2:將itheima-order中編寫的UserClient復(fù)制到itheima-api項(xiàng)目中
3:在itheima-order中引入itheima-api的依賴
4:重啟測(cè)試
1)引入依賴
創(chuàng)建itheima-api,然后引入feign的starter依賴 itheima-user依賴
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--httpClient依賴-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>itheima-pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2)編寫的UserClient
將itheima-order中編寫的UserClient復(fù)制到itheima-api項(xiàng)目中
package com.itheima.client;
import com.itheima.user.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* order調(diào)用user服務(wù)(代替了 String url = "http://itheima-user/user/" + orderInfo.getUserId();)
* 1.接口上使用@FeignClient(value="被調(diào)用服務(wù)名")
* 2.定義被調(diào)用接口中的方法(基于被調(diào)用的controller編寫)
* 2.1 requestMapping中的路徑必須是全路徑(controller類上的+方法上的)
* 2.2 使用PathVariable注解,必須取別名
*/
@FeignClient(value = "itheima-user")
public interface UserClient {
/**
* 調(diào)用用戶微服中controller的方法
*/
@GetMapping(value = "/user/{id}")
public User one(@PathVariable(value = "id") Long id);
}
3)在itheima-order中引入itheima-api的依賴
<!--引入feign-api-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>itheima-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
4)當(dāng)定義的FeignClient不在SpringBootApplication的掃描包范圍時(shí),這些FeignClient無(wú)法使用。
有兩種方式解決:
方式一:指定FeignClient所在包
@EnableFeignClients(basePackages = "com.itheima.user.feign")
方式二:指定FeignClient字節(jié)碼
@EnableFeignClients(clients = {UserClient.class})
5.3.4. 總結(jié)
-
Feign的使用步驟
- 引入依賴
- 添加@EnableFeignClients注解
- 編寫FeignClient接口
- 使用FeignClient中定義的方法代替RestTemplate
-
Feign的日志配置:
- 方式一是配置文件,feign.client.config.xxx.loggerLevel
- 如果xxx是default則代表全局
- 如果xxx是服務(wù)名稱,例如userservice則代表某服務(wù)
-
方式二是java代碼配置Logger.Level這個(gè)Bean
- 如果在@EnableFeignClients注解聲明則代表全局
- 如果在@FeignClient注解中聲明則代表某服務(wù)
-
Feign的優(yōu)化
- 日志級(jí)別盡量用basic
- 使用HttpClient或OKHttp代替URLConnection
- 引入feign-httpClient依賴
- 配置文件開啟httpClient功能,設(shè)置連接池參數(shù)
-
Feign的最佳實(shí)踐:
- 讓controller和FeignClient繼承同一接口
- 將FeignClient、POJO、Feign的默認(rèn)配置都定義到一個(gè)項(xiàng)目中,供所有消費(fèi)者使用
浙公網(wǎng)安備 33010602011771號(hào)