通過前面的學習,使用Spring Cloud實現微服務的架構基本成型,大致是這樣的: 我們使用Spring Cloud Netflix中的Eureka實現了服務註冊中心以及服務註冊與發現;而服務間通過Ribbon或Feign實現服務的消費以及均衡負載;通過Spring Cloud Config實現了 ...
通過前面的學習,使用Spring Cloud實現微服務的架構基本成型,大致是這樣的:
我們使用Spring Cloud Netflix中的Eureka實現了服務註冊中心以及服務註冊與發現;而服務間通過Ribbon或Feign實現服務的消費以及均衡負載;通過Spring Cloud Config實現了應用多環境的外部化配置以及版本管理。為了使得服務集群更為健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引起的故障蔓延。
在該架構中,我們的服務集群包含:內部服務Service A和Service B,他們都會註冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,通過均衡負載公開至服務調用方。我們把焦點聚集在對外服務這塊,直接暴露我們的服務地址,這樣的實現是否合理,或者是否有更好的實現方式呢?
先來說說這樣架構需要做的一些事兒以及存在的不足:
-
首先,破壞了服務無狀態特點。
-
-
從具體開發和測試的角度來說,在工作中除了要考慮實際的業務邏輯之外,還需要額外考慮對介面訪問的控制處理。
-
-
其次,無法直接復用既有介面。
-
當我們需要對一個即有的集群內訪問介面,實現外部服務訪問時,我們不得不通過在原有介面上增加校驗邏輯,或增加一個代理調用來實現許可權控制,無法直接復用原有的介面。
-
面對類似上面的問題,我們要如何解決呢?答案是:服務網關!
為瞭解決上面這些問題,我們需要將許可權控制這樣的東西從我們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,我們需要一個更強大一些的均衡負載器的 服務網關。
服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了許可權控制
等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將許可權控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集群主體能夠具備更高的可復用性和可測試性。
一:簡介
官網:https://github.com/Netflix/zuul
Zuul:維基百科:
電影《捉鬼敢死隊》中的怪獸,Zuul,在紐約引發了巨大騷亂。
事實上,在微服務架構中,Zuul就是守門的大Boss!一夫當關,萬夫莫開!
不管是來自於客戶端(PC或移動端)的請求,還是服務內部調用。一切對服務的請求都會經過Zuul這個網關,然後再由網關來實現 鑒權、動態路由等等操作。 Zuul就是我們服務的統一入口。
1.2.1.新建工程
填寫基本信息:
添加Zuul依賴:
1.2.2.編寫啟動類
通過@EnableZuulProxy
註解開啟Zuul的功能:
@SpringBootApplication
@EnableZuulProxy // 開啟Zuul的網關功能
public class ZuulDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication.class, args);
}
}
server:
port: 10010 #服務埠
spring:
application:
name: api-gateway #指定服務名
映射規則:
zuul:
routes:
user-service: # 這裡是路由id,隨意寫
path: /user-service/** # 這裡是映射路徑
url: http://127.0.0.1:8081 # 映射路徑對應的實際url地址
我們將符合path
規則的一切請求,都代理到 url
參數指定的地址
本例中,我們將 /user-service/**
開頭的請求,代理到http://127.0.0.1:8081
1.2.5.啟動測試:
訪問的路徑中需要加上配置規則的映射路徑,我們訪問:http://127.0.0.1:8081/user-service/user/10
在剛纔的路由規則中,我們把路徑對應的服務地址寫死了!如果同一服務有多個實例的話,這樣做顯然就不合理了。
我們應該根據服務的名稱,去Eureka註冊中心查找 服務對應的所有實例列表,然後進行動態路由才對!
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
@SpringBootApplication
@EnableZuulProxy // 開啟Zuul的網關功能
@EnableDiscoveryClient
public class ZuulDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication.class, args);
}
}
eureka:
client:
registry-fetch-interval-seconds: 5 # 獲取服務列表的周期:5s
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
因為已經有了Eureka客戶端,我們可以從Eureka獲取服務的地址信息,因此映射時無需指定IP地址,而是通過服務名稱來訪問,而且Zuul已經集成了Ribbon的負載均衡功能。
zuul:
routes:
user-service: # 這裡是路由id,隨意寫
path: /user-service/** # 這裡是映射路徑
serviceId: user-service # 指定服務名稱
再次啟動,這次Zuul進行代理時,會利用Ribbon進行負載均衡訪問:
日誌中可以看到使用了負載均衡器:
在剛纔的配置中,我們的規則是這樣的:
-
zuul.routes.<route>.path=/xxx/**
: 來指定映射路徑。<route>
是自定義的路由名 -
zuul.routes.<route>.serviceId=/user-service
:來指定服務名。
而大多數情況下,我們的<route>
路由名稱往往和 服務名會寫成一樣的。因此Zuul就提供了一種簡化的配置語法:zuul.routes.<serviceId>=<path>
比方說上面我們關於user-service的配置可以簡化為一條:
zuul:
routes:
user-service: /user-service/** # 這裡是映射路徑
在使用Zuul的過程中,上面講述的規則已經大大的簡化了配置項。但是當服務較多時,配置也是比較繁瑣的。因此Zuul就指定了預設的路由規則:
-
預設情況下,一切服務的映射路徑就是服務名本身。
-
例如服務名為:
user-service
,則預設的映射路徑就是:/user-service/**
-
也就是說,剛纔的映射規則我們完全不配置也是OK的,不信就試試看。
配置示例:
zuul:
prefix: /api # 添加路由首碼
routes:
user-service: # 這裡是路由id,隨意寫
path: /user-service/** # 這裡是映射路徑
service-id: user-service # 指定服務名稱
我們通過zuul.prefix=/api
來指定了路由的首碼,這樣在發起請求時,路徑就要以/api開頭。
路徑/api/user-service/user/1
將會被代理到/user-service/user/1
zuul:
retryable: true
ribbon:
ConnectTimeout: 250 # 連接超時時間(ms)
ReadTimeout: 2000 # 通信超時時間(ms)
OkToRetryOnAllOperations: true # 是否對所有操作重試
MaxAutoRetriesNextServer: 2 # 同一服務不同實例的重試次數
MaxAutoRetries: 1 # 同一實例的重試次數
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 熔斷超時時長:6000ms