【Java】【Soul源码探秘】插件链实现

【Soul源码探秘】插件链实现

腾业发布于 1 月 21 日

引言

插件是 Soul 的灵魂。

Soul 使用了插件化设计思想,实现了插件的热插拔,且极易扩展。内置丰富的插件支持,鉴权,限流,熔断,防火墙等等。

【Java】【Soul源码探秘】插件链实现

Soul 是如何实现插件化设计的呢?

一切还得从插件链说起,本篇我们来探密 Soul 中插件链的实现。

从插件说起

Soul 中所有插件最终均继承自 SoulPlugin,其完整继承关系如下所示:

【Java】【Soul源码探秘】插件链实现

可以看到,Soul 的插件生态极其丰富,正是如此丰富的插件支撑起了 Soul 网关强大的扩展能力。

我们以常用的 DividePlugin 为例,分析插件内部所做工作。

DividePlugin 继承结构:

【Java】【Soul源码探秘】插件链实现

1、SoulPlugin 插件接口:

【Java】【Soul源码探秘】插件链实现

  • execute 方法:处理方法,需要传入 exchange交换区 和 SoulPluginChain插件链
  • getOrder 方法:取得序号,用作插件排序
  • named 方法:获得插件名
  • skip 方法:判断是否跳过本次处理

每次处理时,将先进行 skip 判断,不跳过则执行 excute 处理方法。

2、AbstractSoulPlugin 抽象插件:

【Java】【Soul源码探秘】插件链实现

重点关注 execute 方法,其核心代码如下:

if (pluginData.getEnable()){

// 获取插件数据

final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);

// 获取选择器数据

final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);

final SelectorData selectorData = matchSelector(exchange, selectors);

// 获取规则

final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());

RuleData rule;

if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {

//get last

rule = rules.get(rules.size() - 1);

} else {

rule = matchRule(exchange, rules);

}

// 执行具体处理

return doExecute(exchange, chain, selectorData, rule);

}

// 继续执行后续插件处理

return chain.execute(exchange);

获取选择器数据和规则,然后传入 doExecute 方法进行具体处理,doExecute 方法为抽象方法,交由子类具体实现。

3、DividePlugin 插件:

【Java】【Soul源码探秘】插件链实现

重点关注 doExecute 方法,以下是核心代码:

// 获取网关上下文和规则处理器

final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);

final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);

// 获取上游列表

final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());

// 选择待分发的目标上游

final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();

DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);

// 设置 http url

String domain = buildDomain(divideUpstream);

String realURL = buildRealURL(domain, soulContext, exchange);

exchange.getAttributes().put(Constants.HTTP_URL, realURL);

// 设置 http timeout

exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());

exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());

return chain.execute(exchange);

很明显,divide 插件只是完成目标上游服务的待分发,即根据选择器和规则找到对应服务,再通过负载均衡策略分配上游服务实例。

而调用上游服务的工作是由其他相应的 client 类插件完成。

插件链实现

借由插件链,Soul 将众多插件整合到一起进行统一调度处理。

插件链继承结构:

【Java】【Soul源码探秘】插件链实现

可以看到,Soul 中插件链 SoulPluginChain 仅有一个默认实现类 DefaultSoulPluginChain。

1、DefaultSoulPluginChain:

【Java】【Soul源码探秘】插件链实现

其持有通过构造方法传入的插件链,看看 execute 方法:

public Mono<Void> execute(final ServerWebExchange exchange) {

// 反应式编程语法:Mono.defer

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();

});

}

DefaultSoulPluginChain 是 SoulWebHandler 的内部类,看下 SoulWebHandler 的实现。

2、SoulWebHandler:

【Java】【Soul源码探秘】插件链实现

SoulWebHandler 是 web 请求处理的起点,在此创建并开始插件链的处理。

同 DefaultSoulPluginChain一样,SoulWebHandler 也是持有通过构造方法传入的插件链。

看看 handle 方法:

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)));

}

handle 方法负责插件链执行指标度量的采集,通过在 DefaultSoulPluginChain 执行时加订阅实现,DefaultSoulPluginChain 在此处完成初始化。

全局查找 SoulWebHandler 构造方法可以定位到 SoulConfiguration 的 soulWebHandler 方法,看下 SoulConfiguration 的实现。

3、SoulConfiguration:

【Java】【Soul源码探秘】插件链实现

SoulConfiguration 是 Soul 的核心配置类,负责自动装配网关所需的核心 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()));

return new SoulWebHandler(soulPlugins);

}

注意此处的插件列表经过了一次重排,重排顺序参见 PluginEnum。

4、初始化 SoulWebHandler

soul-bootstrap启动的过程中,所有插件是怎么形成final ObjectProvider<List<SoulPlugin>> plugins,然后初始化SoulWebHandler的呢?

SoulWebHandler 所在的配置类通过配置 @ComponentScan("org.dromara.soul"),通知 spring 扫描 org.dromara.soul 包。

同属 org.dromara.soul 包下所有插件的 starter项目中,在各自的spring.factories 文件里都指定了自动配置类。

【Java】【Soul源码探秘】插件链实现

因此,List<SoulPlugin>会去收集在soul-bootstrap的 pom 文件里引入了相关依赖的插件,并以此形成插件链。

总结

本篇从单独的插件开始,到探究插件链的实现,基本理清了 Soul 中插件式设计的实现。

  1. 由 SoulConfiguration 自动装配 SoulWebHandler,但此时 SoulWebHandler 持有插件列表但未初始化插件链。
  2. 待调用 handle 方法处理请求时,才初始化插件链进入插件处理。

个人知识库

高性能微服务API网关-Soul

java

阅读 5更新于 1 月 21 日

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

腾业

1 声望

0 粉丝

0 条评论

得票时间

avatar

腾业

1 声望

0 粉丝

宣传栏

引言

插件是 Soul 的灵魂。

Soul 使用了插件化设计思想,实现了插件的热插拔,且极易扩展。内置丰富的插件支持,鉴权,限流,熔断,防火墙等等。

【Java】【Soul源码探秘】插件链实现

Soul 是如何实现插件化设计的呢?

一切还得从插件链说起,本篇我们来探密 Soul 中插件链的实现。

从插件说起

Soul 中所有插件最终均继承自 SoulPlugin,其完整继承关系如下所示:

【Java】【Soul源码探秘】插件链实现

可以看到,Soul 的插件生态极其丰富,正是如此丰富的插件支撑起了 Soul 网关强大的扩展能力。

我们以常用的 DividePlugin 为例,分析插件内部所做工作。

DividePlugin 继承结构:

【Java】【Soul源码探秘】插件链实现

1、SoulPlugin 插件接口:

【Java】【Soul源码探秘】插件链实现

  • execute 方法:处理方法,需要传入 exchange交换区 和 SoulPluginChain插件链
  • getOrder 方法:取得序号,用作插件排序
  • named 方法:获得插件名
  • skip 方法:判断是否跳过本次处理

每次处理时,将先进行 skip 判断,不跳过则执行 excute 处理方法。

2、AbstractSoulPlugin 抽象插件:

【Java】【Soul源码探秘】插件链实现

重点关注 execute 方法,其核心代码如下:

if (pluginData.getEnable()){

// 获取插件数据

final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);

// 获取选择器数据

final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);

final SelectorData selectorData = matchSelector(exchange, selectors);

// 获取规则

final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());

RuleData rule;

if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {

//get last

rule = rules.get(rules.size() - 1);

} else {

rule = matchRule(exchange, rules);

}

// 执行具体处理

return doExecute(exchange, chain, selectorData, rule);

}

// 继续执行后续插件处理

return chain.execute(exchange);

获取选择器数据和规则,然后传入 doExecute 方法进行具体处理,doExecute 方法为抽象方法,交由子类具体实现。

3、DividePlugin 插件:

【Java】【Soul源码探秘】插件链实现

重点关注 doExecute 方法,以下是核心代码:

// 获取网关上下文和规则处理器

final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);

final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);

// 获取上游列表

final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());

// 选择待分发的目标上游

final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();

DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);

// 设置 http url

String domain = buildDomain(divideUpstream);

String realURL = buildRealURL(domain, soulContext, exchange);

exchange.getAttributes().put(Constants.HTTP_URL, realURL);

// 设置 http timeout

exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());

exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());

return chain.execute(exchange);

很明显,divide 插件只是完成目标上游服务的待分发,即根据选择器和规则找到对应服务,再通过负载均衡策略分配上游服务实例。

而调用上游服务的工作是由其他相应的 client 类插件完成。

插件链实现

借由插件链,Soul 将众多插件整合到一起进行统一调度处理。

插件链继承结构:

【Java】【Soul源码探秘】插件链实现

可以看到,Soul 中插件链 SoulPluginChain 仅有一个默认实现类 DefaultSoulPluginChain。

1、DefaultSoulPluginChain:

【Java】【Soul源码探秘】插件链实现

其持有通过构造方法传入的插件链,看看 execute 方法:

public Mono<Void> execute(final ServerWebExchange exchange) {

// 反应式编程语法:Mono.defer

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();

});

}

DefaultSoulPluginChain 是 SoulWebHandler 的内部类,看下 SoulWebHandler 的实现。

2、SoulWebHandler:

【Java】【Soul源码探秘】插件链实现

SoulWebHandler 是 web 请求处理的起点,在此创建并开始插件链的处理。

同 DefaultSoulPluginChain一样,SoulWebHandler 也是持有通过构造方法传入的插件链。

看看 handle 方法:

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)));

}

handle 方法负责插件链执行指标度量的采集,通过在 DefaultSoulPluginChain 执行时加订阅实现,DefaultSoulPluginChain 在此处完成初始化。

全局查找 SoulWebHandler 构造方法可以定位到 SoulConfiguration 的 soulWebHandler 方法,看下 SoulConfiguration 的实现。

3、SoulConfiguration:

【Java】【Soul源码探秘】插件链实现

SoulConfiguration 是 Soul 的核心配置类,负责自动装配网关所需的核心 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()));

return new SoulWebHandler(soulPlugins);

}

注意此处的插件列表经过了一次重排,重排顺序参见 PluginEnum。

4、初始化 SoulWebHandler

soul-bootstrap启动的过程中,所有插件是怎么形成final ObjectProvider<List<SoulPlugin>> plugins,然后初始化SoulWebHandler的呢?

SoulWebHandler 所在的配置类通过配置 @ComponentScan("org.dromara.soul"),通知 spring 扫描 org.dromara.soul 包。

同属 org.dromara.soul 包下所有插件的 starter项目中,在各自的spring.factories 文件里都指定了自动配置类。

【Java】【Soul源码探秘】插件链实现

因此,List<SoulPlugin>会去收集在soul-bootstrap的 pom 文件里引入了相关依赖的插件,并以此形成插件链。

总结

本篇从单独的插件开始,到探究插件链的实现,基本理清了 Soul 中插件式设计的实现。

  1. 由 SoulConfiguration 自动装配 SoulWebHandler,但此时 SoulWebHandler 持有插件列表但未初始化插件链。
  2. 待调用 handle 方法处理请求时,才初始化插件链进入插件处理。

个人知识库

高性能微服务API网关-Soul

以上是 【Java】【Soul源码探秘】插件链实现 的全部内容, 来源链接: utcz.com/a/106214.html

回到顶部