Soul源码分析–一次请求的旅行之路
我们知道soul是一个异步的、高性能的、跨语言的、响应式的API网关,既然做为一个网关,当然是需要对外暴露,接收外部请求,然后转发到后端真实的机器,这一篇就来分析一下,一个请求经过网关会经历什么,最后安全到达目的地。
divide 插件原理
这里先来看看 divide 插件的原理,这里的启动 admin、bootstrap、excample-http 项目的骚操作就不说了。
-
首先配置好选择器的规则,这里选择默认的随机访问负载策略,然后访问 http://localhost:9195/http/order/findById?id=11 请求地址
-
请求走到网关之后,会找到相应的插件,这里找到的是 DividePlugin 这个插件,执行 doExcecute 方法。首先会找到这个插件配置的相应参数,确保配置没有错误,然后找到相应的 LoadBalance 然后根据 LoadBalance 来决定访问哪个后端的真实服务
-
先看看第一步是怎么确认负载策略是哪一个,这里首先会调用 LoadBalanceUtils.selector 方法,这里会根据负载策略名称来生成相应的负载类,这里生成的 Random 所以会跳到 RandomLoadBalance 这个类中来。
public static DivideUpstream selector(final List<DivideUpstream> upstreamList, final String algorithm, final String ip) { LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm); return loadBalance.select(upstreamList, ip); }
然后执行 doSelect 方法,这个方法首先会计算出所有的权重,然后看所有权重是否相同,如果相同就用 RANDOM.nextInt(upstreamList.size()) 方法生成一个可访问长度中的一个随机数,说白了就是随便选一个访问。如果权重都设置为0的话,和权重相同的随机是一样的。
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) { int totalWeight = calculateTotalWeight(upstreamList); boolean sameWeight = isAllUpStreamSameWeight(upstreamList); if (totalWeight > 0 && !sameWeight) { return random(totalWeight, upstreamList); } // If the weights are the same or the weights are 0 then random return random(upstreamList); }
如果权重不同的话,需要把 RANDOM.nextInt(totalWeight) 方法算出来的随机数再减去每一个的权重,直到 offset 减到小于0,说明当前这一个权重比上一个权重大,直接返回这个权重大的,如果这个随机数没有小于0,说明第一个权重特别大,返回第一个
-
最后就是根据返回的随机访问地址,拼出 domain + realURL (比如http://192.168.3.4:8188/order/findById?id=11) 看到这个地址是不是很熟悉,没错,之前在浏览器中访问的地址是网关的地址,这里拼出来的是真实的地址,拿着真实的地址去请求后端服务器
springcloud插件原理
-
在 soul 中默认开启的插件是 divide ,其它的手动去开启和关闭,这里先需要把网关中 pom.xml 的依赖给引用上。
<dependency> <groupId>org.dromara</groupId> <artifactId>soul-spring-boot-starter-plugin-springcloud</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> <version>2.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>2.2.0.RELEASE</version> </dependency>
然后在 admin 后台系统管理中的插件管理把 springCloud 插件给打开,然后启动 examples-springcloud 测试项目。
启动完之后,在浏览器输入 http://localhost:9195/springcloud/order/findById?id=11 看返回结果是否成功,不过看这个样子,第一次访问是不成功的,serviceId不存在,下面我们来跟一下到后端去发生了什么。
-
首先经过的是 SoulWebHandler 类中的 handle 方法,主要是得到默认插件的列表,然后执行对应插件的方法,重点是 execute 方法。
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))); }
在 execute 方法中主要做了下列几件事,先判断启用的哪个插件,这里启用的是 springCloud 插件,然后去查询 selectors 选择器,然后找到对应访问的选择器数据,判断 rules 对应上匹配的规则,最后去执行插件相应名称的 doExecute 方法。
输出相应的日志,说明已经匹配上了 springcloud 选择器和选择器中的规则。2021-02-06 02:24:10.312 INFO 97277 --- [-work-threads-4] o.d.soul.plugin.base.AbstractSoulPlugin : springCloud selector success match , selector name :/springcloud 2021-02-06 02:31:33.706 INFO 97277 --- [-work-threads-4] o.d.soul.plugin.base.AbstractSoulPlugin : springCloud rule success match , rule name :/springcloud/order/findById
-
然后执行 doExecute 方法,和 divide 插件套路差不多,判断关键的配置是否正确,然后这里的 loadBalancer 用的是 springcloud 里的的方法,后面就是拼真实的地址,然后根据真实地址去请求真实的服务器了