我們做分佈式系統,爲了不暴露具體的服務,以及實現各種統一處理,常常使用網關來管理接口。SpringCloud分佈式系統中常用zuul來實現網關功能。zuul最基本的功能,就是把所有的接口都收到自己這裏,按照規則和負載均衡的配置分發。
zuul實現路由最常用的方法是在屬性文件properties或者 yml中進行配置。
我們首先創建幾個必要的服務:
1. eureka註冊中心
按照正常方式創建,無需多餘內容。application.yml配置:
server:
port: 8101
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
spring:
application:
name: eureka
2. 兩個微服務user和 student
依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml
server:
port: 8107
spring:
application:
name: user
eureka:
instance:
hostname: studio.chris.com
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:8101/eureka
和
server:
port: 8108
spring:
application:
name: student
eureka:
instance:
hostname: studio.chris.com
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:8101/eureka
並沒有什麼差別,只是服務名不同而已。這裏的服務名設計的比較簡單,主要爲方便進行服務發現路由的時候使用簡潔。要注意避免歧義,也就是說解析的時候不要出現歧義,就是一個微服務名稱不能是另一個微服務名稱頭部的子字符串,否則可能在某些情況下會導致路由錯誤。比如stu和student就不合適。
在每個微服務下面寫個接口,如
@RestController
@RequestMapping("/api")
public class StudentController {
@Value("${server.port}")
String port;
@GetMapping("/test")
public String test() {
return "Called test api from student module.port: " + port;
}
}
我們用port做標記來區別不同的實例,以驗證zuul給我們做了負載均衡。
3. zuul
pom依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
application.yml
server:
port: 8080
spring:
application:
name: zuul
eureka:
instance:
hostname: studio.chris.com
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:8101/eureka
可以看出yml的配置基本上都是一樣的,無非區別了個端口和服務名稱。而那個端口,在我們進行多實例部署的時候又是需要更改的。
啓動類註解
@EnableEurekaClient
@EnableDiscoveryClient
@EnableZuulProxy
好了,所有服務我們已經構建好,現在需要配置zuul
zuul路由的最常見方式實在application.yml文件中配置
添加:
zuul:
routes:
user-manage:
path: /user/**
serviceId: user
student-manage:
path: /student/**
serviceId: student
user-manage和student-manage是兩個路由,這個名稱只要區別開就好了。serviceId和path是配對的,比如user-manage的配置,凡是/user開頭的接口調用都會被分發到在註冊中心註冊爲user的服務中去均衡調用。
如:請求localhost:8080/user/api/test,結果會出現類似以下這樣的結果
Called test api from user module.port: 8107
這是的zuul也算是配置完成了,我們把各個服務啓動起來測試。註冊中心和zuul啓動起來,其他兩個微服務要啓動多個實例,我們在Idea中打開多個終端,運行以下命令:
mvn spring-boot:run -e -Dmaven.test.skip=true -Dserver.port=8211
最後的端口號每次運行的時候都要修改。
我們在瀏覽器中打開註冊中心的監控頁面:
http://localhost:8101/
可以看到我們啓動起來的微服務,其中user和student是多實例的。
我們通過網關8080調用接口,會發現返回的字符串端口號在變化,這就是因爲負載均衡分發到了不同的實例。
其實這種路由還可以這樣配置
zuul:
user: /user/**
student: /student/**
這種鍵值對的方式更簡潔,key爲註冊服務名稱,value爲匹配的路徑表達式。
注意:這種方法最常見,但是也很笨拙,如果我們增加了一個微服務,都要在yml文件中配置,也就要重啓網關。但是一般我們不會這樣做,網關不要經常重啓,配置文件也不需要經常修改。我們把yml中關於zuul的配置全部刪除乾淨。
接着,我們在pom中依賴一個包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
好了,這就完了,既不用配置,也不用寫代碼,直接啓動就好了。就算新添加一個微服務,也就是照常從網關調用就好了。zuul會根據註冊中心中的發現的服務來自動進行路由。我們的zuul就再也不用關了。
最後需要補充的一個點,網關就是個關,大鬼小鬼都要走這裏。它的作用除了路由、負載均衡之外,還有統一處理。由於網關在ji節後方法調用之前,所以可以做一些路由前的預處理。比如,我們分佈式架構中zu最重視權限,一般的權限驗證都是在微服務中根據自己的需求去做驗證邏輯,但是架構層面統一要求的邏輯可以在網關處理,以節省資源。比如,我們要求所有的網絡請求都要攜帶身份驗證信息,沒有就不允許ji進行任何接口調用,我們就可以在zuul過濾器中來實現。
實現方式就是寫一個過濾器,來繼承ZuulFilter
package com.chris.zuul;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by Chris Chan
* 2020/3/14 12:52
* Use for:
* Explain:
*/
@Component
@WebFilter(urlPatterns = "/*")
public class ChrisZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
HttpServletResponse response = context.getResponse();
//授權過濾 不帶身份證不讓進場 立即退出
String token = request.getHeader("Authorization");
if (StringUtils.isEmpty(token)) {
try {
response.sendError(401, "Authorization is empty");
} catch (Exception e) {
}
return null;
}
return null;
}
}
這樣我們請求的時候就一定要帶一個Basic或者Bearer類似的Authorization頭信息,不帶的不再進行路由分發。至於這些頭信息對不對,可以在這裏進行檢測,也可以放行到微服務中自己進行處理。