Soul网关(十一)
Soul 中的反应式编程
soul-bootstrap 引入了 webflux 依赖来实现反应式编程:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
soul-bootstrap通过soul-spring-boot-starter-gateway这个starter来启动网关,soul-spring-boot-starter-gateway依赖soul-web,soul-web的自动配置类:SoulConfiguration中初始化了 webHandler, dispatcherHandler, pluginDataSubscriber。
Soul-Bootstrap中初始化了一个 反应式 web 服务器:
/**
* 反应式web 服务器
* @return the netty reactive web server factory
*/
@Bean
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory();
webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer());
return webServerFactory;
}
soul-web 从bean工厂加载所有插件并初始化 SoulWebHandler:
/**
* 从bean工厂加载插件并初始化 SoulWebHandler.
*/
@Bean("webHandler")
public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
final List<SoulPlugin> soulPlugins = pluginList.stream()
.sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
// 初始化 SoulWebHandler 并将插件加载到本地内存
return new SoulWebHandler(soulPlugins);
}
/**
* SoulWebHandler 构造函数
*/
public SoulWebHandler(final List<SoulPlugin> plugins) {
// 将插件持久化到堆内存
this.plugins = plugins;
String schedulerType = System.getProperty("soul.scheduler.type", "fixed");
if (Objects.equals(schedulerType, "fixed")) {
int threads = Integer.parseInt(System.getProperty(
"soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));
scheduler = Schedulers.newParallel("soul-work-threads", threads);
} else {
scheduler = Schedulers.elastic();
}
}
SoulWebHandler 实现了 WebHandler 类的 handler() 方法,来处理 web 请求:
@Override
public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());
Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());
// 从堆内存加载已初始化的插件,来处理web请求
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
.doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
Soul 插件链调用源码分析
接着上面的handler()方法,handler()方法从堆内存中加载出来的插件链 List 如下:
[
org.dromara.soul.plugin.global.GlobalPlugin@1cc8416a, org.dromara.soul.plugin.sign.SignPlugin@3bc69ce9, org.dromara.soul.plugin.waf.WafPlugin@6eb089e6, org.dromara.soul.plugin.ratelimiter.RateLimiterPlugin@58324c9f, org.dromara.soul.plugin.hystrix.HystrixPlugin@5d7d8613, org.dromara.soul.plugin.resilience4j.Resilience4JPlugin@abad89c, org.dromara.soul.plugin.divide.DividePlugin@331ff3ac, org.dromara.soul.plugin.springcloud.SpringCloudPlugin@35d60381, org.dromara.soul.plugin.httpclient.WebClientPlugin@2785db06, org.dromara.soul.plugin.divide.websocket.WebSocketPlugin@2e5e6fc4, org.dromara.soul.plugin.alibaba.dubbo.param.BodyParamPlugin@6cae2e4d, org.dromara.soul.plugin.sofa.SofaPlugin@834e986, org.dromara.soul.plugin.alibaba.dubbo.AlibabaDubboPlugin@68ab6ab0, org.dromara.soul.plugin.monitor.MonitorPlugin@715a70e9, org.dromara.soul.plugin.sofa.response.SofaResponsePlugin@604d23fa, org.dromara.soul.plugin.httpclient.response.WebClientResponsePlugin@79980d8d, org.dromara.soul.plugin.alibaba.dubbo.response.DubboResponsePlugin@61ffd148
]
web请求有下面的execute()方法处理,该方法会从插件链中匹配出插件来处理对应的 web 请求:
@Override
public Mono<Void> execute(final ServerWebExchange exchange) {
return Mono.defer(() -> {
// 遍历插件链
if (this.index < plugins.size()) {
SoulPlugin plugin = plugins.get(this.index++);
// 判断是否跳过此插件。所有插件都实现了SoulPlugin接口
// SoulPlugin接口的default方法skip()默认返回false表示不跳过
Boolean skip = plugin.skip(exchange);
if (skip) {
return this.execute(exchange);
}
// 执行未跳过的插件的execute()方法
return plugin.execute(exchange, this);
}
return Mono.empty();
});
}
很多插件都覆写了SoulPlugin接口中的skip()方法,如 DividePlugin:
/**
* 通过请求上下文中的 RPC 类型来判断web请求是否跳过此Plugin
*/
@Override
public Boolean skip(final ServerWebExchange exchange) {
// 从ServerWebExchange中取出上下文context
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
// 判断 rpc 类型 是否为当前插件的 rpc 类型
return !Objects.equals(Objects.requireNonNull(soulContext).getRpcType(), RpcTypeEnum.HTTP.getName());
}
当我们发起HTTP请求:curl http://localhost:9195/http/order/findById?id=111 时,可以在此处断点看到上下文信息如下,其中 rpcType=http,正好对应 DividePlugin 的RPC类型,因此不会跳过DividePlugin,而会跳过其他 RPC 类型的插件,比如说 SpringCloudPlugin。
SoulContext(module=/http, method=/order/findById, rpcType=http, httpMethod=GET, sign=null, timestamp=null, appKey=null, path=/http/order/findById, contextPath=/http, realUrl=/order/findById, dubboParams=null, startDateTime=2021-01-27T00:43:57.447)
因此,通过在 Boolean skip = plugin.skip(exchange);处断点,可以得出 HTTP 请求(RPC类型为:http)会跳过/不跳过的插件如下:
GlobalPlugin (默认不跳过) --- 有execute()方法
SignPlugin (默认不跳过)
WafPlugin (默认不跳过)
RateLimiterPlugin (默认不跳过)
HystrixPlugin (默认不跳过)
Resilience4JPlugin (默认不跳过)
DividePlugin (只匹配RPC类型为 http 的请求) --- 有execute()方法
SpringCloudPlugin (【跳过】因为RPC类型不匹配,此插件的rpc类型=springcloud)
WebClientPlugin (只匹配RPC类型为 http 或 springcloud 的请求) --- 有execute()方法
WebSocketPlugin (【跳过】因为RPC类型不匹配,此插件的rpc类型=websocket)
BodyParamPlugin (【跳过】只有dubbo、sofa、tars 这几个Plugin里有用到)
SofaPlugin (【跳过】因为RPC类型不匹配,此插件的rpc类型=sofa)
AlibabaDubboPlugin (【跳过】因为RPC类型不匹配,此插件的rpc类型=dubbo)
MonitorPlugin (默认不跳过)
SofaResponsePlugin (【跳过】因为RPC类型不匹配,此插件的rpc类型=sofa)---有execute()方法
WebClientResponsePlugin(只匹配RPC类型为 http 或 springcloud 的请求)---有execute()方法
DubboResponsePlugin (【跳过】因为RPC类型不匹配,此插件的rpc类型=dubbo)---有execute()方法
由于很多插件都没有覆写 execute()方法,所以 HTTP 请求实际经过的插件链如下:
GlobalPlugin ----> DividePlugin ----> WebClientPlugin ----> WebClientResponsePlugin
Divide 插件源码分析
DividePlugin 处理 HTTP 请求的关键代码如下:
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
// 获取请求上下文
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
// 获取规则的负载配置(负载均衡策略,重试次数)
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
// 根据选择器获取请求转发配置
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
if (CollectionUtils.isEmpty(upstreamList)) {
log.error("divide upstream configuration error: {}", rule.toString());
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// 目的IP
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
// 针对目的IP的负载配置(请求协议,请求地址,权重等配置)
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
if (Objects.isNull(divideUpstream)) {
log.error("divide has no upstream");
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// 设置请求目的IP,端口
String domain = buildDomain(divideUpstream);
// 拼装请求的接口URL
String realURL = buildRealURL(domain, soulContext, exchange);
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// 设置超时时间
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
}
本文深入分析Soul网关的反应式编程原理及插件链调用流程,重点解析Divide插件如何处理HTTP请求,并介绍插件间的调用顺序。
反应式编程&插件链调用源码分析&spm=1001.2101.3001.5002&articleId=113214977&d=1&t=3&u=97c9d6c0d0ee480f893ad9c9af864419)
1029

被折叠的 条评论
为什么被折叠?



