Soul网关源码学习【第八篇】-数据同步(http长轮询)

http长轮询

Soul 借鉴了 Apollo、Nacos 的设计思想,取其精华,自己实现了 http 长轮询数据同步功能。注意,这里并非传统的 ajax长轮询!
http 长轮询机制如下所示,soul-web 网关请求 admin 的配置服务,读取超时时间90s,意味着网关层请求配置服务最多会等待 90s,这样便于 admin 配置服务及时响应变更数据,从而实现准实时推送。
http 请求到达 sou-admin 之后,并非立马响应数据,而是利用 Servlet3.0的异步机制,异步响应数据。首先,将长轮询请求任务 LongPollingClient 扔到 BlocingQueue中,并且开启调度任务,60s 后执行,这样做的目的是 60s后将该长轮询请求移除队列,即便是这段时间内没有发生配置数据变更。因为即便是没有配置变更,也得让网关知道,总不能让其干等吧,而且网关请求配置服务时,也有90s 的超时时间。
在这里插入图片描述

这是官方对于http长轮询的解释,下面我们结合这段解释和代码实际来看一下。

启动后台和网关

  • 网关配置(记得重启)

    • 首先在 pom.xml 文件中 引入以下依赖:

       <!--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>
      
    • 在 springboot的 yml 文件中进行如下配置:

      soul :
         sync:
             http:
                  url: http://localhost:9095
                  #url: 配置成你的 soul-admin的 ip与端口地址,多个admin集群环境请使用(,)分隔。
      
  • soul-admin 配置, 或在 soul-admin 启动参数中设置 --soul.sync.http='',然后重启服务。

    soul:
      sync:
         http:
            enabled: true
    

代码调试

  • 启动完成后,我们找到soul-sync-data-http下面的HttpSyncDataService,可以看出我们这个类加载后会找到我们配置的http url,针对于每个url启动一个HttpLongPollingTask的任务线程。
    	public HttpSyncDataService(final HttpConfig httpConfig, final PluginDataSubscriber pluginDataSubscriber,
                                   final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
            this.factory = new DataRefreshFactory(pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
            this.httpConfig = httpConfig;
            this.serverList = Lists.newArrayList(Splitter.on(",").split(httpConfig.getUrl()));
            this.httpClient = createRestTemplate();
            this.start();
        }
        
    	private void start() {
            // It could be initialized multiple times, so you need to control that.
            if (RUNNING.compareAndSet(false, true)) {
                // fetch all group configs.
                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 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.");
            }
    
  • 跟着走进去看,会发现在调用了doLongPolling方法里的这一段的时候,会卡住很长时间,而这里访问的是localhost:9095/configs/listener
String json = this.httpClient.postForEntity(listenerUrl, httpEntity, String.class).getBody();
  • 进入到这个方法里面看,这边是使用了定时任务去执行LongPollingClient的方法

    @PostMapping(value = "/listener")
        public void listener(final HttpServletRequest request, final HttpServletResponse response) {
            longPollingListener.doLongPolling(request, response);
        }
        public void doLongPolling(final HttpServletRequest request, final HttpServletResponse response) {
    
            // compare group md5
            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));
        }
    
  • LongPollingClient这里面的操作就是将这次的任务移除列表,并且将数据发送出去,然后再次往任务列表中添加一个任务。通过这种循环往复的执行,会一直保持监听的状态。

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

小结

这次的代码没看懂,只是跟着debug走了一遭。
网关这边对每一个url开启一个线程,线程中通过后台的一个listener循环拉去数据,但是每次数据没有变化,所有这个任务只是空跑(目前看下来是这样的)。
当实际在后台改动数据的时候,会主动触发我们网关的数据更新方法。
后续全部看明白后会再更新。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值