一、簡介
Zuul 是一個基於JVM路由和服務的負載均衡器,提供路由、監控、安全等方面的服務框架。Zuul能夠與Eureka、Ribbon、Hystrix等組件配合使用。
Zuul的核心是過濾器,通過這些過濾器我們可以擴展出很多功能,例如:
- 動態路由:動態的將客戶端的請求路由到後端不同的服務,做一些邏輯處理,比如聚合多個服務的數據返回。
- 請求監控:可以對整個系統的請求進行監控,記錄詳細的請求響應日誌,可以試試統計出當前系統的訪問量以及監控狀態。
- 認證鑑權:對每一個訪問的請求做認證。拒絕非法請求,保護好後端服務。
- 壓力測試:通過Zuul可以動態的將請求轉發到後端服務的集羣中,還可以識別測試流量和真實流量,從而做一些特殊處理。
- 灰度發佈:灰度帆布可以保證整體系統的穩定,在初始灰度時就可以發現,調整問題,以保證其影響度。
二、簡單使用
1. 引用依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
2. 在配置 文件中添加配置信息
spring.application.name=zuul-demo
server.port=2103
#通過zuul.routes配置路由轉發 testzuul是自定義的名稱,當訪問 /testzuul/**地址時就會跳轉到http://baidu.com/
zuul.routes.testzuul.path=/testzuul/**
zuul.routes.testzuul.url=http://baidu.com/
3. 在啓動類加入 @EnableZuulProxy 註解
啓動應用後 訪問 http://localhost:2103/testzuul 就會跳轉到 百度。
三、 集成 Eureka
1. 引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
2. 在配置文件中添加配置
eureka.client.serviceUrl.defaultZone=XXXX
注意:因爲 @EnableZuulProxy 中已經自帶了 @EnableDiscoveryClient 所以在啓動類中不用再加Eureka 的註解。
重啓服務,我們可以通過默認的轉發規則來訪問Eureka中的服務,訪問規則是:“API網關地址+訪問的服務名稱+接口URI”,例如 :http://localhost:2103/ribbon/test/fegin?userName=113
四、Zuul路由配置
Zuul的默認的轉發規則 “API網關地址+訪問的服務名稱+接口URI”,在給服務制定名稱時應該儘量短點,這樣就可以使用默認的路由會澤進行請求,不需要爲每個服務都定一個路由規則,默認規則舉例:
API網關地址:http://localhost:2103
用戶服務名稱:ribbon
那麼通過Zuul的訪問登錄接口的規則就是:http://localhost:2103/ribbon/test/fegin?userName=113
1. 指定具體服務路由:
zuul.routes.ribbon.path=/user-service/**
這裏將服務名稱爲 ribbon的服務配置爲user-service,也就是說想訪問 ribbon服務的接口要通過 user-service訪問。
注意:配置文件中的 /user-services/** 這裏一定要是兩個 * ,如果配置一個 * 就只能轉發一級,兩個表示可以轉發任意層級的URL
2. 配置路由前綴
zuul.prefix=/rest
配置了路由前綴後要訪問前加你配置的val,例如我配置的rest,例如:http://localhost:2103/rest/user-service/test/fegin?userName=%E2%80%981111%E2%80%99
3. 本地跳轉
zuul.routes.local.path=/api/**
zuul.routes.local.url=forward:/local
配置後,重啓服務訪問 http://localhost:2103/rest/api/1 自動跳轉本地的local的接口中。
五、Zuul過濾器
1. 過濾器的類型
- pre: 可以在請求被路由之前調用,適用於身份認證的場景,認證通過後再繼續執行下面的流程。
- route:在路由請求時被調用,適用於灰度發佈場景,在將要路由的時候可以做一些自定義的邏輯。
- post:在route 個error 過濾之後被調用,這種過濾器將請求路由到達具體的服務之後執行,適用於需要添加響應頭、記錄響應日誌等應用場景。
- error:處理請求時發生錯誤時被調用,在執行過程中發送錯誤時會進入error過濾器,可以用來統一記錄錯誤信息。
整個執行順序是,請求發過來首先到 pre 過濾器,然後到 routing 過濾器 最後到post 過濾器,任何一個過濾器有異常都會進入到 error過濾器。
2. 過濾器的使用
過濾器的創建
public class IpFilter extends ZuulFilter {
private List<String> blackIpList = Arrays.asList("127.0.0.1");
public IpFilter() {
super();
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
//true爲過期生效 false 爲過濾器不執行
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
String ip = getIpAddr(ctx.getRequest());
if(StringUtils.isNotBlank(ip) && blackIpList.contains(ip)){
//禁止轉發
ctx.setSendZuulResponse(false);
ctx.setResponseBody("非法請求");
//禁止本地轉發
ctx.set("sendForwardFilter.ran",true);
ctx.getResponse().setContentType("application/json;charset=utf-8");
//過濾器中傳遞數據
ctx.set("msg","hello---------");
ctx.set("isSuccess" ,true);
return null;
}
return null;
}
}
說明:
- shouldFilter:是否執行該過濾器,true 爲執行,false 爲不執行,這個也可以利用配置中心的配置來實現,達到動態的開啓和關閉過濾器。filterType:過濾器類型,可選值有pre、route、post、error。
- filterOrder:過濾器執行的順序,數值越小,優先級越高。
- run:執行自己的業務邏輯。
過濾器定義完成之後需要配置過濾器才能生效。
@Configuration
public class FilterConfig {
@Bean
public IpFilter ipFilter(){
return new IpFilter();
}
}
過濾器中的異常處理
ublic class ErrorFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
public ErrorFilter() {
super();
}
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 100;
}
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
logger.error("Filter Error :{}",throwable.getCause().getMessage());
return null;
}
}
@Configuration
public class FilterConfig {
@Bean
public ErrorFilter errorFilter(){
return new ErrorFilter();
}
}
再創建一個error過濾器之後,我們需要在其他額過濾器中製造一個異常,我選擇在 上面的pre的過濾器中 加入下面的代碼製造一個異常
System.out.println(2/0);
重啓應用訪問接口時會出現下圖所示的界面
我們可以自定義異常的返回格式
@RestController
public class ErrorHandlerController implements ErrorController {
private ErrorAttributes errorAttributes;
@Autowired
public ErrorHandlerController(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
@Override
public String getErrorPath() {
return "/error";
}
@GetMapping("/error")
public String error(HttpServletRequest request){
Map<String, Object> errorAttributes = this.errorAttributes.getErrorAttributes(new ServletWebRequest(request), true);
String message = (String) errorAttributes.get("message");
String trace = (String) errorAttributes.get("trace");
if(StringUtils.isNotBlank(trace)){
message += String.format("and trace %s",trace);
}
return message;
}
}
再次重啓應用,訪問同樣的接口,返回的格式如下:
Zuul的容錯機制
引入依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置文件添加如下配置:
#開啓重試
zuul.retryable=true
#請求連接的超時時間
ribbon.connectTimeOut=500
#請求處理的超時時間
ribbon.readTimeOut=5000
#對當前實例的重試次數
ribbon.maxAutoRetries=1
#切換實例的最大重試次數
#ribbon.maxAutoRetriesNextServer=3
#對所有操作請求都進行重試
ribbon.okToRetryOnAllOperations=true
#對制定的Http響應碼進行重試
ribbon.retryableStatusCode=500,404,502
Zuul回退機制
在springcloud中Zuul默認整合了Hystrix,當後端服務異常時可以爲 Zuul 添加回退功能,返回默認的數據給客戶端。
實現回退機制需要實現 FallbackProvider 接口,具體代碼如下:
@Component
public class ServiceConsumerFallbackProvider implements FallbackProvider {
private Logger logger = LoggerFactory.getLogger(ServiceConsumerFallbackProvider.class);
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
if(null != cause){
logger.error("",cause.getCause());
}
RequestContext ctx = RequestContext.getCurrentContext();
return new ByteArrayInputStream("服務器內部錯誤".getBytes());
}
@Override
public HttpHeaders getHeaders() {
MediaType mediaType = new MediaType("application","json", Charset.forName("UTF-8"));
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
return httpHeaders;
}
};
}
}
getRoute 方法中法返回 * 代表對所有服務進行回退操作,如果只想對某個服務進行回退操作,那麼就返回需要回退的服務名稱,這個名稱一定要是註冊到 Eureka 中的名稱。
通過 ClientHttpResponse 構造回退的內容,通過getStatusCode 返回響應的狀態碼,通過getStatusText 返回響應狀態碼對應的文本,通過getBody返回回退的內容。通過getHeaders 返回響應的請求頭信息。