【高性能网关soul学习】8. soul 数据同步之http长轮询

【高性能网关soul学习】8. soul 数据同步之http长轮询

本文目标:介绍 soul-admin 与 soul-web 之间通过 Http 进行同步的细节

http长轮询数据同步

配置http进行数据同步
  • 网关服务引入依赖
    • 同时需要注释掉 soul-spring-boot-starter-sync-data-websocket 依赖 和 zk依赖
 <!--soul data sync start use http-->
     <dependency>
          <groupId>org.dromara</groupId>
           <artifactId>soul-spring-boot-starter-sync-data-http</artifactId>
           <version>${last.version}</version>
     </dependency>
  • yaml配置为进行http同步
# 网关服务配置
soul :
     sync:
         zookeeper:
              url: localhost:2181
              sessionTimeout: 5000
              connectionTimeout: 2000
 #url: 配置成你的zk地址,集群环境请使用(,)分隔
 
 # soul-admin 配置,只启用zk进行数据同步
 soul:
  sync:
    websocket:
      enabled: false
    zookeeper:
      url: localhost:2181
      sessionTimeout: 5000
      connectionTimeout: 2000

启动服务 soul-admin 服务和 soul-bootstrap 服务和 测试的业务服务

  • soul-bootstrap 中的 控制台日志:you use http long pull sync soul data
request configs: [http://localhost:9095/configs/fetch?groupKeys=APP_AUTH&groupKeys=PLUGIN&groupKeys=RULE&groupKeys=SELECTOR&groupKeys=META_DATA]

http 数据同步启动流程
private void start() {
    // It could be initialized multiple times, so you need to control that.
    if (RUNNING.compareAndSet(false, true)) {
        // fetch all group configs.(app_auth\plugin\rule\selector\meta_data 等五种数据类型)
        this.fetchGroupConfig(ConfigGroupEnum.values());
        int threadSize = serverList.size();
      // 构建异步线程池 执行长轮询任务
        this.executor = new ThreadPoolExecutor(threadSize, threadSize, 60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                SoulThreadFactory.create("http-long-polling", true));
        // start long polling, each server creates a thread to listen for changes.
        this.serverList.forEach(server -> this.executor.execute(new HttpLongPollingTask(server)));
    } else {
        log.info("soul http long polling was started, executor=[{}]", executor);
    }
}


public DataRefreshFactory(final PluginDataSubscriber pluginDataSubscriber,
                          final List<MetaDataSubscriber> metaDataSubscribers,
                          final List<AuthDataSubscriber> authDataSubscribers) {
    ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataRefresh(pluginDataSubscriber));
    ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataRefresh(pluginDataSubscriber));
    ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataRefresh(pluginDataSubscriber));
    ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AppAuthDataRefresh(authDataSubscribers));
    ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataRefresh(metaDataSubscribers));
}

// 将请求获取到的数据写入缓存
public boolean executor(final JsonObject data) {
    final boolean[] success = {false};
  // ENUM_MAP中存放的是对于各种类型 DataSubscriber 的封装,因此逻辑和之前大致相似,通过 DataSubscriber 写入缓存
    ENUM_MAP.values().parallelStream().forEach(dataRefresh -> success[0] = dataRefresh.refresh(data));
    return success[0];
}
http 数据同步 长轮询机制

soul-bootstrap 端 HttpLongPollingTask

  • 核心方法就是一个 doLongPolling(server)
    • 本质上就是请求 String listenerUrl = server + “/configs/listener”; 然后等待返回
  • 时间相关的配置,如果一直出现异常会重试3次,然后休眠5分钟
class HttpLongPollingTask implements Runnable {

        private String server;

        private final int retryTimes = 3;

        HttpLongPollingTask(final String server) {
            this.server = server;
        }

        @Override
        public void run() {
            while (RUNNING.get()) {
                for (int time = 1; time <= retryTimes; time++) {
                    try {
                        doLongPolling(server);
                    } catch (Exception e) {
                        // print warnning log.
                        if (time < retryTimes) {
                            log.warn("Long polling failed, tried {} times, {} times left, will be suspended for a while! {}",
                                    time, retryTimes - time, e.getMessage());
                            ThreadUtils.sleep(TimeUnit.SECONDS, 5);
                            continue;
                        }
                        // print error, then suspended for a while.
                        log.error("Long polling failed, try again after 5 minutes!", e);
                        ThreadUtils.sleep(TimeUnit.MINUTES, 5);
                    }
                }
            }
            log.warn("Stop http long polling.");
        }
    }

soul-admin 端 类 HttpLongPollingDataChangedListener 负责处理长轮询

  • 比较 soul-bootstrap 端的数据的md5的值 和 最后修改时间 比较得出数据是否变更
    • 如果数据有变更,则直接返回
    • 如果数据没有变更,则创建一个 LongPollingClient 悬挂 60s,等待 scheduler 运行该Runnable,重新判断是否有数据变更,不管有没有,都直接返回结果
/**
     * If the configuration data changes, the group information for the change is immediately responded.
     * Otherwise, the client's request thread is blocked until any data changes or the specified timeout is reached.
     *
     * @param request  the request
     * @param response the response
     */
    public void doLongPolling(final HttpServletRequest request, final HttpServletResponse response) {

        // compare group md5 and lastModifyTime
        List<ConfigGroupEnum> changedGroup = compareChangedGroup(request);
        String clientIp = getRemoteIp(request);

        // response immediately.
        if (CollectionUtils.isNotEmpty(changedGroup)) {
            this.generateResponse(response, changedGroup);
            log.info("send response with the changed group, ip={}, group={}", clientIp, changedGroup);
            return;
        }

        // listen for configuration changed.
        final AsyncContext asyncContext = request.startAsync();

        // AsyncContext.settimeout() does not timeout properly, so you have to control it yourself
        asyncContext.setTimeout(0L);

        // block client's thread.
        scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
    }

class LongPollingClient implements Runnable {
        @Override
        public void run() {
            this.asyncTimeoutFuture = scheduler.schedule(() -> {
              // 把自己从等待的队列中移除
                clients.remove(LongPollingClient.this);
              // 重新比较是否有数据变更
                List<ConfigGroupEnum> changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
              // 不管有没有变更都返回
                sendResponse(changedGroups);
            }, timeoutTime, TimeUnit.MILLISECONDS);
            clients.add(this);
        }
}

总结:

  1. 介绍了 soul 关于 http 进行数据同步的相关配置
  2. 介绍了 soul-admin 关于 http 数据同步的流程
    1. 启动时全量 fetch 拉取数据
    2. 运行期间使用 http 长轮询方式确保 soul-admin 和 soul-bootstrap 之间的数据同步不超过60s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值