一、背景
目前Soul网关能代理的协议或者叫服务类型有
- http (插件名 divide)
- SpringCloud
- Dubbo
- SOFA
- Tars
Soul 使用SPI做的插件化设计,代码层次清晰。接下来打算深度的去学习代理服务的代码设计。但精力有限,感觉能用上网关的业务架构,不是微服务只是负载加Web服务器的可能性不大,另外SOFA、Tars两种服务框架自己都没用过。
综上,接下来几天打算看 SpringCloud 和 Dubbo 服务接入代码,在周末的时候,补一补类图和时序图。
二、启动服务
- 启动 Eureka
- 启动控制台(关掉不相关插件,只留SpringCloud)
- 网关(去掉不相关的依赖如Dubbo、SOFA)
- 启动两个 SpringCloud-example 实例服务
可以在 Eureka 上看到服务正常
请求多次发现可以正常转发到两台业务服务上。PS:这里日志里有个警告是什么鬼,记录下以后看看
三、知识点
时间有限,我来罗列知识点,同学们课后学吧,哈哈偷懒~~
1、WebHandler
网关使用的是 Spring-WebFlux,所有没有 Servlet 也就没有 Filter,但在 WebFlux 框架中 WebHandler 替代 Filter,其实 zuul、Spring Cloud GateWay 不也是各种 Filter,对流量的链式处理吗
所以入口 SoulWebHandler#handle
@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());
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
.doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
2、SoulPluginChain
使用插件对流量进行链式处理,这里区别了 Zuul 使用 Filter。
插件入口类 DefaultSoulPluginChain#execute
@Override
public Mono<Void> execute(final ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < plugins.size()) {
SoulPlugin plugin = plugins.get(this.index++);
Boolean skip = plugin.skip(exchange);
if (skip) {
return this.execute(exchange);
}
return plugin.execute(exchange, this);
}
return Mono.empty();
});
}
插件列表
虽然我从控制台关了各类干扰插件,但是抵不住pom文件加载了很多无关插件(放心这些插件后续都会学习的)
我们一个个插件连蒙带猜走一遍:
1) GlobalPlugin
算了,车开走了(其实是懒了再看吧)。
2) SpringCloudPlugin
这里有个模板方法提一下, AbstractSoulPlugin#execute写好了处理 selector 和 rule 存在不存在走不同分支的逻辑,然后在再调用具体插件自定义好的 doExecute 方法
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
if (pluginData != null && pluginData.getEnabled()) {
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
selectorLog(selectorData, pluginName);
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
//get last
rule = rules.get(rules.size() - 1);
} else {
rule = matchRule(exchange, rules);
}
if (Objects.isNull(rule)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
ruleLog(rule, pluginName);
return doExecute(exchange, chain, selectorData, rule);
}
return chain.execute(exchange);
}
可以看到只有一个插件是打开的,
在那句 if (pluginData != null && pluginData.getEnabled()) {},我们可以直接略过控制台这些插件,只看打开的SpringCloud插件了。
ps: 我们可以看到Soul为了性能,连日志打印都是支持动态开关 ruleLog(rule, pluginName)
ps: 这里还有一个有意思的地方就是抛错误时,只检查 divide、dubbo、springCloud 三类插件的selector,为什么不检查SOFA、Tars,以后再看看,记录下来
public static Mono<Void> checkSelector(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
if (PluginEnum.DIVIDE.getName().equals(pluginName)
|| PluginEnum.DUBBO.getName().equals(pluginName)
|| PluginEnum.SPRING_CLOUD.getName().equals(pluginName)) {
log.error("can not match selector data: {}", pluginName);
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_SELECTOR.getCode(), SoulResultEnum.CANNOT_FIND_SELECTOR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
return chain.execute(exchange);
}
回到SpringCloudPlugin,可以看到一顿操作,匹配出对应的rule啦
也就是能走一走SpringCloudPlugin#doExecute方法啦,又是一顿操作,其实就是补充了 ServerWebExchange 实现类 DefaultServerWebExchange 请求上下文信息。
3)WebClientPlugin
插件链上不只是会运行控制台配置的插件,还会运行一些默认插件,也就是和控制台无关,为了逻辑看起来清晰有一些默认插件,比如没有上车的 1)GlobalPlugin 和这一小节要讲的 WebClientPlugin
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
String urlPath = exchange.getAttribute(Constants.HTTP_URL);
if (StringUtils.isEmpty(urlPath)) {
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
long timeout = (long) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_TIME_OUT)).orElse(3000L);
int retryTimes = (int) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
log.info("The request urlPath is {}, retryTimes is {}", urlPath, retryTimes);
HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
}
一顿操作在 exchange 中加入了 http 请求需要的参数,然后使用 WebFlux 框架发起http请求,具体怎么实现以后讲讲 Project Reactor 和 Spring-WebFlux 。
4)WebClientResponsePlugin
最后一个插件
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
return chain.execute(exchange).then(Mono.defer(() -> {
ServerHttpResponse response = exchange.getResponse();
ClientResponse clientResponse = exchange.getAttribute(Constants.CLIENT_RESPONSE_ATTR);
if (Objects.isNull(clientResponse)
|| response.getStatusCode() == HttpStatus.BAD_GATEWAY
|| response.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
Object error = SoulResultWrap.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
if (response.getStatusCode() == HttpStatus.GATEWAY_TIMEOUT) {
Object error = SoulResultWrap.error(SoulResultEnum.SERVICE_TIMEOUT.getCode(), SoulResultEnum.SERVICE_TIMEOUT.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
response.setStatusCode(clientResponse.statusCode());
response.getCookies().putAll(clientResponse.cookies());
response.getHeaders().putAll(clientResponse.headers().asHttpHeaders());
return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers()));
}));
}
这是请求拿到结果后的处理。