在 soul-bootstrap
工程下的 pom.xml
下引入以下依賴java
<!--if you use http proxy start this--> <dependency> <groupId>org.dromara</groupId> <artifactId>soul-spring-boot-starter-plugin-divide</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.dromara</groupId> <artifactId>soul-spring-boot-starter-plugin-httpclient</artifactId> <version>${project.version}</version> </dependency>
在 soul-examples-http
(你本身的真實服務) pom.xml
新增以下依賴:spring
<dependency> <groupId>org.dromara</groupId> <artifactId>soul-spring-boot-starter-client-springmvc</artifactId> <version>${last.version}</version> </dependency>
在yml中新增以下配置 :bootstrap
soul: http: adminUrl: http://localhost:9095 port: 8188 contextPath: /myhttp appName: http full: false # adminUrl: 爲你啓動的 soul-admin 項目的ip + 端口,注意要加http:// # port: 你本項目的啓動端口 # contextPath: 爲你的這個mvc項目在soul網關的路由前綴,好比/order ,/product 等等,網關會根據你的這個前綴來進行路由 # appName:你的應用名稱,不配置的話,會默認取 `spring.application.name` 的值 # full: 設置true 表明代理你的整個服務,false表示代理你其中某幾個controller
Controller
中添加@SoulSpringMvcClient
註解將註解 Controller
類上面,裏面的path屬性則爲前綴,若是含有 /**
表明你的整個接口須要被網關代理。設計模式
@RestController @RequestMapping("/test") @SoulSpringMvcClient(path = "/test/**") public class HttpTestController { //controller中全部方法被網關代理 } @RestController @RequestMapping("/order") @SoulSpringMvcClient(path = "/order") public class OrderController { /** * order/save會被網關代理,而/order/findById 則不會 */ @PostMapping("/save") @SoulSpringMvcClient(path = "/save" , desc = "Save order") public OrderDTO save(@RequestBody final OrderDTO orderDTO) { orderDTO.setName("hello world save order"); return orderDTO; } @GetMapping("/findById") public OrderDTO findById(@RequestParam("id") final String id) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(id); orderDTO.setName("hello world findById"); return orderDTO; } }
修改 diea 啓動配置,勾選Allow parallel run
,容許並行啓動緩存
修改application.yml
中端口配置,將端口改成8189mvc
server: port: 8189 # 修改端口 address: 0.0.0.0 soul: http: adminUrl: http://localhost:9095 port: 8189 # 修改端口 contextPath: /http appName: http full: false
成功啓動,soul 後臺中會新註冊一個 8189 服務app
修改weight
權重配置,將8188權重改成100負載均衡
採用假設性原則
,根據工程名及文件名稱,在DividePlugin.java
的doExecute
方法中添加斷點。使用 Postman 對網關發起一個請求,遇到斷點,查看調用棧信息dom
一直往上找,發現請求會先進入SoulWebHandler
類的 handle 方法裏:ide
@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))); }
而後調用 DefaultSoulPluginChain
的 execute 方法,從 plugins 中獲取DividePlugin
插件:
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(); }); }
調用了插件的具體執行方法後,進入AbstractSoulPlugin
類的 execute 方法,這裏使用了模板方法
設計模式,會匹配每個插件,直到找到divide
插件
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); }
而後就回到了最初的起點,在DividePlugin.java
的doExecute
方法中打的第一個斷點
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); //獲取訪問的服務地址,這是是8188,8189兩條 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); } 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); }
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
方法爲根據權重獲取訪問的服務地址,一路斷點,最終找到RandomLoadBalance
類
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); }
//根據權重獲取具體的訪問地址信息 private DivideUpstream random(final int totalWeight, final List<DivideUpstream> upstreamList) { // If the weights are not the same and the weights are greater than 0, then random by the total number of weights int offset = RANDOM.nextInt(totalWeight); // Determine which segment the random value falls on for (DivideUpstream divideUpstream : upstreamList) { offset -= getWeight(divideUpstream); if (offset < 0) { return divideUpstream; } } return upstreamList.get(0); }
至此,devide 插件負載均衡權重分析告一段落。