springcloud 路由閘道器 - zuul
spring cloud微服務系統中,一種常見的負載均衡方式是,客戶端的請求首先經過負載均衡(zuul、ngnix),再到達服務閘道器(zuul叢集),然後再到具體的服務。服務統一註冊到高可用的服務註冊中心叢集,服務的所有配置檔案由配置服務管理,配置服務的配置檔案放在git倉庫,方便開發人員隨時改變配置。
簡介
zuul的主要功能是路由轉發和過濾器。路由功能是微服務的一部分。
zuul有以下功能:
(1)Authentication(認證)
(2)Insights
(3)Stress Testing(壓力測試)
(4)Canary Testing(金絲雀測試)
(5)Dynamic Routing(動態路由)
(6)Service Migration(服務遷移)
(7)Load Shedding(負載排程)
(8)Security(案例)
(9)Static Response handling(靜態響應處理)
(10)Active traffic management
zuul中預設實現的filter
型別 | 順序 | 過濾器 | 功能 |
pre | -3 | ServletDetectionFilter | 標記處理Servlet的型別 |
pre | -2 | Servlet3oWrapperFilter | 包裝HttpServletRequests請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標記除錯標誌 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉發 |
route | 100 | SimpleHostRoutingFilter | url請求轉發 |
route | 500 | SendForwardFilter | forward請求轉發 |
post | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
pos | 1000 | SendResponseFilter | 處理正常的請求響應 |
可以在application.yml中配置需要禁用的filter,格式 :
zuul:
FormBodyWrapperFilter:
pre:
disable: true
自定義Filter
實現自定義Filter,需要繼承ZuulFilter的類,並覆蓋其中的4個方法。
public class MyFilter extends ZuulFilter {
@Override
String filterType() {
return "pre"; //定義filter的型別,有pre、route、post、error四種
}
@Override
int filterOrder() {
return 10; //定義filter的順序,數字越小表示順序越高,越先執行
}
@Override
boolean shouldFilter() {
return true; //表示是否需要執行該filter,true表示執行,false表示不執行
}
@Override
Object run() {
return null; //filter需要執行的具體操作
}
}
實踐
1、在原有的工程上,建立一個的新的工程service-zuul
2、其pom.xml檔案如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.forezp</groupId>
<artifactId>service-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>service-zuul</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>com.forezp</groupId>
<artifactId>sc-f-chapter5</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
</project>
3、在其入口application類加上註解@EnableZuulProxy,開啟zuul的功能
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableDiscoveryClient
public class ServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run( ServiceZuulApplication.class, args );
}
}
4、加上配置檔案application.yml加上以下配置程式碼
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8769
spring:
application:
name: service-zuul
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-ribbon
api-b:
path: /api-b/**
serviceId: service-feign
服務過濾
zuul不僅只是路由,並且還能過濾,做一些安全驗證。
@Component
public class MyFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@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();
log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getParameter("token");
if(accessToken == null) {
log.warn("token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty");
}catch (Exception e){}
return null;
}
log.info("ok");
return null;
}
}
1、fiterType: 返回一個字串代表過濾器的型別,在zuul中定義了四種不同生命週期的過濾喊器型別,具體如下:
(1)pre: 路由之前
我們可以利用這種過濾器實現身份驗證、在叢集中執行請求的微服務,記錄呼叫資訊等。
(2)routing:路由之時
(3)post:路由之後
(4)error:傳送錯誤呼叫
2、filterOrder: 過濾順序
3、shouldFilter:這裡可以寫邏輯判斷,是否要過濾。
4、run:過濾器的具體邏輯
5、訪問http://localhost:8769/api-a/hi?name=xxx
6、訪問http://localhost:8769/api-a/hi?name=xxx&token=22
zuul的高可用
因為外部請求到後端服務的流量都會經過zuul,故而在生產環境中,我們一般都需要部署高可用的zuul以避免單點故障。
1、zuul客戶端也註冊到了eureka server上
這種情況下,zuul的高可用非常簡單,只需將多個zuul節點註冊到eureka server上,就可實現zuul的高可用。此時,zuul的高可用與其他微服務的高可用沒什麼區別。
當zuul客戶端也註冊到eureka server上時,只需部署多個zuul節點即可實現其高可用。zuul客戶端會自動從eureka server中查詢zuul server的列表,並使用ribbon負載均衡的請求zuul叢集。
2、zuul客戶端未註冊到eureka server上
現實中,這種場景往往更常見,例如,zuul客戶端是一個手機app,我們不可能讓所有手機端都註冊到eureka server上,這種情況下,我們可藉助一個額外的負載均衡器來實現zuul的高可用,例如nginx、haproxy、f5等。
問題
1、跨域訪問
正常情況下,跨域是這樣的:
微服務配置跨域+zuul不配置=有跨域問題
微服務配置+zuul配置=有跨域問題
微服務不配置+zuul不配置=有跨域問題
微服務不配置+zuul配置=ok
(1)zuul配置忽略頭部資訊
zuul:
ignored-headers: Access-Control-Allow-Origin,H-APP-Id,Token,APPToken
(2)跨域配置
@Component
@Configuration
public class GateWayCorsConfig
{
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);//允許cookie跨域
//允許訪問的頭資訊,*表示全部
corsConfiguration.addAllowedHeader("*");
//允許向伺服器提交請求的URI,*表示全部允許,
corsConfiguration.addAllowedOrigin("*");
//允許提交請求的方法,*表示全部允許
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setMaxAge(18000L);// 預檢請求的快取時間(秒),即在這個時間段裡,對於相同的跨域請求不會再預檢了
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
}
只要閘道器配置好了,底下的服務不需要配置。
2、中文亂碼
解決辦法:
(1)注意編碼,全站api和前端全部使用utf-8,zuul中強制編碼為utf-8.
spring:
http:
encoding:
charset: UTF-8
enabled: true
force: true
3、post json中獲取引數token
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String method = request.getMethod();
String token = null;
if(method.equalsIgnoreCase("GET")){
token = request.getParameter("token");
}else if(method.equalsIgnoreCase("POST")){
try {
InputStream inputStream = request.getInputStream();
String body = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
JSONObject jsonObject = JSONObject.parseObject(body);
token = jsonObject.getString("token");
}catch (Exception e){
e.printStackTrace();
token = null;
}
}