(十二)soul源码的divide插件底层原理,负载均衡及ip端口探活

(十二)soul源码的divide插件底层原理,负载均衡及ip端口探活

目标

  • divide插件的类信息
  • divide插件底层原理
  • soul-bootstrap端ip端口探活

divide插件的类信息

divide的具体使用使用在小节一里有分析了,这里不阐述
soul-booostrap依赖包soul-spring-boot-starter-plugin-divide,里面DividePlugin(divide插件),DividePluginDataHandler(divide插件数据处理器),WebSocketPlugin,ReactorNettyWebSocketClient的初始化
DividePlugin继承AbstractSoulPlugin (AbstractSoulPlugin实现SoulPlugin)
soul-spring-boot-starter-plugin-divide 包同时依赖了包soul-plugin-divide,看如下:

在这里插入图片描述

balance负载均衡,UpstreamCacheManager探活

soul :
   file:
     enabled: true
   corss:
     enabled: true
   dubbo :
     parameter: multi
   sync:
     websocket :
       urls: ws://localhost:9095/websocket,ws://localhost:9096/websocket,ws://localhost:9097/websocket

divide插件底层原理

  • divide插件核心使用责任链模式和模板方法模式

责任链模式

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

模板方法模式 在小节十三中,对模板方法做了详细阐述,

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

  • DividePlugin#doExecute核心处理代码

1:soulContext拿到从上下文拿到配置信息,即admin的配置同步打到soul-bootstrap
2:ruleHandle 获取负载均衡的配置规则,重试次数,超时时间
3:UpstreamCacheManager从获取可用调用ip信息,DivideUpstream带权重
4:LoadBalanceUtils.selector通过负载选择哪个具体调用ip
5:realURL真实的请求地址
6:httpUrl,重试次数,超时时间添加到上下文中
7: chain.execute(exchange):继续执行下一个插件链,则直接回到SoulWebHandler#execute

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

divide负载均衡

DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip)
Divide插件是通过SPI的方式将负载均衡的类加载进来,然后通过getJoin方法真正选择出使用的具体算法实现类,soul-plugin-divide根目录存在org.dromara.soul.plugin.divide.balance.LoadBalance

在这里插入图片描述

AbstractLoadBalanced的继承类有三个,HashLoadBalance,RandomLoadBalance,RoundRobinLoadBalance,
具体通过 doSelect调用到具体哪个继承类处理,此处也是模板方法运用

   @Override
   public DivideUpstream select(final List<DivideUpstream> upstreamList, final String ip) {
       if (CollectionUtils.isEmpty(upstreamList)) {
           return null;
       }
       if (upstreamList.size() == 1) {
           return upstreamList.get(0);
       }
       return doSelect(upstreamList, ip);
   }

soul-bootstrap端ip端口探活

  • 具体在UpstreamCacheManager类,UpstreamCacheManager初始化时,会新建ScheduledThreadPoolExecutor一个定时线程池,每间隔30秒调用当前类的scheduled方法
   private UpstreamCacheManager() {
       boolean check = Boolean.parseBoolean(System.getProperty("soul.upstream.check", "false"));
       if (check) {
           new ScheduledThreadPoolExecutor(1, SoulThreadFactory.create("scheduled-upstream-task", false))
                   .scheduleWithFixedDelay(this::scheduled,
                           30, Integer.parseInt(System.getProperty("soul.upstream.scheduledTime", "30")), TimeUnit.SECONDS);
       }
   }
  • scheduled方法

UPSTREAM_MAP全部的ip信息 ,遍历 UPSTREAM_MAP值,检查如果存在则放入UPSTREAM_MAP_TEMP,不存在则移除,UPSTREAM_MAP_TEMP可用ip信息。
check(v):检查是否可用的核心方法

   private void scheduled() {
       if (UPSTREAM_MAP.size() > 0) {
           UPSTREAM_MAP.forEach((k, v) -> {
               List<DivideUpstream> result = check(v);
               if (result.size() > 0) {
                   UPSTREAM_MAP_TEMP.put(k, result);
               } else {
                   UPSTREAM_MAP_TEMP.remove(k);
               }
           });
       }
   }
  • check(v)方法:先checkIP(url))检查ip的正确性

isHostConnector

   public static boolean checkUrl(final String url) {
       if (StringUtils.isBlank(url)) {
           return false;
       }
       if (checkIP(url)) {
           String[] hostPort;
           if (url.startsWith(HTTP)) {
               final String[] http = StringUtils.split(url, "\\/\\/");
               hostPort = StringUtils.split(http[1], Constants.COLONS);
           } else {
               hostPort = StringUtils.split(url, Constants.COLONS);
           }
           return isHostConnector(hostPort[0], Integer.parseInt(hostPort[1]));
       } else {
           return isHostReachable(url);
       }
   }

新建Socket类,Socket使用同步IO的方式直接尝试连接,判断ip是否可用,到处里即是我们soul-bootstrap端探活

   private static boolean isHostConnector(final String host, final int port) {
       try (Socket socket = new Socket()) {
           socket.connect(new InetSocketAddress(host, port));
       } catch (IOException e) {
           return false;
       }
       return true;
   }

总结

  • 目前只分析soul-bootstrap端ip端口探活,admin端探活缺失,后续继续分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值