本次目标:体验Soul网关dubbo使用及divide插件分析
说明:
- dubbo插件是将
http协议
转换成dubbo协议
的插件,也是网关实现dubbo泛化调用的关键; - dubbo插件需要配合元数据才能实现dubbo的调用;
- apache dubbo 和 alibaba dubbo用户,都是使用该同一插件;
登录管理后台,找到插件管理,把dubbo插件状态改为开启;
- 在项目pom文件中引入以下依赖
<!--if you use dubbo start this-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-plugin-alibab-dubbo</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-plugin-apache-dubbo</artifactId>
<version>2.2.1</version>
</dependency>
- 因为要用到zookeeper作为注册中心,所以还需要在本地启动一个zookeeper服务
- 在dubbo插件的配置中,配置如下:配置dubbo的注册中心
{"register":"zookeeper://localhost:2181"} or {"register":"nacos://localhost:8848"}
- 然后是在方法上添加@SoulDubboClient注解
启动服务之后,可以在 soul-admin -->元数据管理,进行查看;每一个dubbo接口方法,都会对应一条元数据。
通过postman用下面的地址访问
http://localhost:9195/dubbo/findById?id=9
divide插件源码分析
Plugins的链式处理核心类是:SoulWebHandler,从这里开始打断点调试:
//委托给链中的下一个
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();
});
}
根据名字找到soul-plugin-divide模块,找到下面的DividePlugin类。DividePlugin继承AbstractSoulPlugin抽象类,AbstractSoulPlugin实现了SoulPlugin接口,接口定义如下:
public interface SoulPlugin {
//处理Web请求并(可选地)委托给下一个插件
Mono<Void> execute(ServerWebExchange exchange, SoulPluginChain chain);
//此属性用于确定同一类型插件中的插件执行顺序
int getOrder();
//这是插件名称定义,您必须提供正确的名称。如果实现了AbstractSoulPlugin,这个属性就不用了
default String named() {
return "";
}
//是否跳过,如果返回true则不执行
default Boolean skip(ServerWebExchange exchange) {
return false;
}
AbstractSoulPlugin类里面的execute()方法,如果匹配到了就执行相应的规则,否则继续筛选下一个插件数据和规则
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
//通过pluginName去缓存里面获取pluginData
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
//如果pluginData不为空并且插件开启
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);
}
然后我们看一下实现类DividePlugin里面的doExecute()方法,如果匹配到了plugin配置信息,则执行对应方法:
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
//获取context配置信息
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
//判断soulContext不为空
assert soulContext != null;
//获取rule配置数据
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();
//获取父级入口负载策略
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);
}
// set the http url
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// set the http timeout
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
}
总结
Soul是如何实现把http协议转为dubbo协议的呢?修改的配置数据是如何进行数据同步的呢?是如何实现插件的热插拔?转发是如何实现的?限流是如何实现的?敬请期待接下来的文章。