引入
pom.xml文件添加依賴,如果引入了gateway的依賴,但是不想使用,可以在application.yml文件中設置spring.cloud.gateway.enabled=false
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
Gateway組成
-
Route
網關的基礎,由一個唯一ID、一個目標URL(請求的服務端具體地址)、一組斷言(判斷是否會進行路由)、一組過濾器。
只有斷言全部返回true,才會進行路由。
-
Predicate
斷言,使用的是Java8函數,參數類型為ServlerWebExchange,可以匹配任何的http請求,並且可以拿到請求中的請求頭、參數等信息。
-
Filter
使用特定工廠構建的GatewayFilter的實例,可以在此Filter中修改請求和響應信息。
Geyeway工作原理
- 客戶端發送請求到Gateway
- Gateway Handler Mapping判斷請求是否匹配某個路由
- 發送請求到Gateway Web Handler,執行該請求的過濾器。過濾器可以在請求之前和之后執行不同邏輯。
- 當所有的預(pre)過濾請求執行完后,創建代理請求,創建好了代理請求后,才執行post請求
核心GlobalFilter
ForwardRoutingFilter
Order: Integer.MAX_VALUE
請求轉發過濾器。
從exchange對象中獲取gatewayRequestUrl,如果這個url中有forward的scheme,則使用Spring的DispatcherHandler 進行請求轉發。
LoadBalancerClientFilter
Order: 10100
負載均衡過濾器。(核心)
從exchange對象中獲取gatewayRequestUrl、gatewaySchemePrefix,如果url為空或者url的schema不是lb並且gatewaySchemePrefix前綴不是lb,則進入下個過濾器;否則去獲取真正的url。
- 過濾器完整內容
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 拿到請求url
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
// 拿到匹配的路由中url的前綴
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
// 如果url為null或者url的schema不是lb並且請求Route中配置的url不是lb,進入下個過濾器
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// 保留原始的Url,就是將原始的url放入exchange對象的gatewayOriginalRequestUrl參數值中
addOriginalRequestUrl(exchange, url);
// 獲取真正的請求url並組裝成ServiceInstance對象
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
-
使用Ribbon負載時,choose(exchange)調用邏輯
- 拿到對應的服務名,因為是lb模式,對應的host即服務名
protected ServiceInstance choose(ServerWebExchange exchange) { return loadBalancer.choose( ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost()); }
- 調用RibbonLoadBalancerClient的choose()方法
public ServiceInstance choose(String serviceId, Object hint) { // getLoadBalancer(serviceId)根據服務id獲取服務列表 // getServer(ILoadBalancer loadBalancer, Object hint) 根據負載均衡策略選擇服務 Server server = getServer(getLoadBalancer(serviceId), hint); if (server == null) { return null; } // 實例化RibbonServer對象 return new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); }
- getLoadBalancer(serviceId)根據服務id獲取負載均衡器,調用SpringClientFactory的getInstance()方法
public <C> C getInstance(String name, Class<C> type) { C instance = super.getInstance(name, type); if (instance != null) { return instance; } IClientConfig config = getInstance(name, IClientConfig.class); return instantiateWithConfig(getContext(name), type, config); }
- 先從父類NamedContextFactory獲取緩存容器,如果沒有,則創建並放入緩存
// 緩存的內置容器 private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); // 獲取應用上下文的方法 protected AnnotationConfigApplicationContext getContext(String name) { // 如果緩存中沒有該應用,則創建應用 // 雙重檢查鎖,保證線程安全 if (!this.contexts.containsKey(name)) { synchronized (this.contexts) { if (!this.contexts.containsKey(name)) { this.contexts.put(name, createContext(name)); } } } // 返回應用實例 return this.contexts.get(name); }
- 創建容器
// 所有帶有@RibbonClients 的配置類,key為類名全路徑 private Map<String, C> configurations = new ConcurrentHashMap<>(); // 根據注解配置創建內置容器 protected AnnotationConfigApplicationContext createContext(String name) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); /* 加載配置start */ // 如果有該服務需要加載的配置類,將自定義的配置注冊到容器中 if (this.configurations.containsKey(name)) { for (Class<?> configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); } } // 如果配置的key是以default.開頭,則默認注冊的容器中 // RibbonNacosAutoConfiguration和RibbonAutoConfiguration都是以defaule.開頭的 for (Map.Entry<String, C> entry : this.configurations.entrySet()) { if (entry.getKey().startsWith("default.")) { for (Class<?> configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } // 注冊默認的配置,即RibbonClientConfiguration context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType); // 添加環境變量 context.getEnvironment().getPropertySources().addFirst(new MapPropertySource( this.propertySourceName, Collections.<String, Object>singletonMap(this.propertyName, name))); /* 加載配置end */ if (this.parent != null) { // Uses Environment from parent as well as beans context.setParent(this.parent); // jdk11 issue // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101 context.setClassLoader(this.parent.getClassLoader()); } // 設置容器名稱 context.setDisplayName(generateDisplayName(name)); // 刷新容器 context.refresh(); return context; }
-
從容器中獲取服務getServer():根據負載均衡算法獲取服務,Ribbon默認加載的ZoneAvoidanceRule負載策略
public Server choose(Object key) { // 當前服務對應的負載均衡器,包含NacosRibbonClientConfiguration注入的Server列表 ILoadBalancer lb = getLoadBalancer(); // 獲取服務,默認輪詢策略 Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); if (server.isPresent()) { return server.get(); } else { return null; } }
// 輪詢方法,參數為可用服務的數量 private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextIndex.get(); int next = (current + 1) % modulo; if (nextIndex.compareAndSet(current, next) && current < modulo) return current; } }
服務來源
首先看一個接口ServerListUpdater自己核心方法
有個內部接口UpdayeAction,真正執行服務列表更新的接口。
public interface ServerListUpdater {
/**
* an interface for the updateAction that actually executes a server list update
*/
public interface UpdateAction {
void doUpdate();
}
/**
* start the serverList updater with the given update action
* This call should be idempotent.
*
* @param updateAction
*/
void start(UpdateAction updateAction);
}
再看看ServerListUpdater接口的實現類PollingServerListUpdater,只貼核心方法start()。
參數為UpdateAction 對象,定義了一個線程類,線程里調用updateAction.doUpdate()方法。
啟動一個定時任務線程池,默認每隔30S執行一次,也就是說每隔30從注冊中心獲取一次最新的服務列表。
public class PollingServerListUpdater implements ServerListUpdater {
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
if (!isActive.get()) {
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
return;
}
try {
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
}
NettyWriteResponseFilter
Order:-1
在其它過濾器執行完后執行,並將服務端響應的結果寫回客戶端,如果需要重新處理響應結果,則新的過濾器必須在此過濾器之后執行,也就是order比-1小。
GatewayMetricsFilter
Order:0
gateway性能監控核心過濾器,用於采集請求數據,主要包含
routeId: 路由id
routeUri: 路由的url
outcome:
status: 請求狀態
httpStatusCode: 響應狀態碼
httpMethod: 請求方法
用戶自定義過濾器
全局過濾器
實現GlobalFilter, Ordered,並重寫getOrder()和filter()方法
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
Route過濾器
Route過濾器命名必須以GatewayFilterFactory結尾,並且在application.yml文件中只需配置前綴
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> {
public PreGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
return (exchange, chain) -> {
//If you want to build a "pre" filter you need to manipulate the
//request before calling chain.filter
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
//use builder to manipulate the request
return chain.filter(exchange.mutate().request(builder.build()).build());
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}