Soul-源码阅读17-Http长轮询1

概念

zookeeper、websocket 数据同步的机制比较简单,而 http 同步会相对复杂一些。Soul 借鉴了 Apollo、Nacos 的设计思想,取其精华,自己实现了 http 长轮询数据同步功能。注意,这里并非传统的 ajax 长轮询!

服务启动

  • 启用http同步配置方式,修改admin的application.yml

    soul:
      database:
        dialect: mysql
        init_script: "META-INF/schema.sql"
        init_enable: true
      sync:
    #    websocket:
    #      enabled: true
    #      zookeeper:
    #          url: localhost:2181
    #          sessionTimeout: 5000
    #          connectionTimeout: 2000
          http:
            enabled: true
    #      nacos:
    #        url: localhost:8848
    #        namespace: soul
      #      acm:
      #        enabled: false
      #        endpoint: acm.aliyun.com
      #        namespace:
      #        accessKey:
      #        secretKey:
    
  • 修改 bootstrap 同步配置,启用http配置方式。

    soul:
        file:
          enabled: true
        corss:
          enabled: true
        dubbo :
          parameter: multi
        sync:
    #        websocket :
    #             urls: ws://localhost:9095/websocket
    #        zookeeper:
    #             url: localhost:2181
    #             sessionTimeout: 5000
    #             connectionTimeout: 2000
            http:
                 url : http://localhost:9095
    #        nacos:
    #          url: localhost:8848
    #          namespace: soul
    #          acm:
    #            enabled: false
    #            endpoint: acm.aliyun.com
    #            namespace:
    #            accessKey:
    #            secretKey:
    
  • bootstrap的pom.xml引入http依赖:

    <!--soul data sync start use http-->
    <dependency>
        <groupId>org.dromara</groupId>
        <artifactId>soul-spring-boot-starter-sync-data-http</artifactId>
        <version>${project.version}</version>
    </dependency>
    

soul-admin端

  • http长轮询同步数据方式的初始化同websocket、zk和nacos一样,都是在DataSyncConfiguration启动的时候,实例化一个监听类。

  • 程序启动的时候因为加了这个注解@Configuration,会去加载这个bean DataSyncConfiguration,因为刚才我们在yml启用了soul.sync.http.enabled = true,满足了条件注解@ConditionalOnProperty,所以会初始化一个HttpLongPollingListener

    @Configuration
    public class DataSyncConfiguration {
    
        /**
         * http long polling.
         */
        @Configuration
        @ConditionalOnProperty(name = "soul.sync.http.enabled", havingValue = "true")
        @EnableConfigurationProperties(HttpSyncProperties.class)
        static class HttpLongPollingListener {
    
            @Bean
            @ConditionalOnMissingBean(HttpLongPollingDataChangedListener.class)
            public HttpLongPollingDataChangedListener httpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
                return new HttpLongPollingDataChangedListener(httpSyncProperties);
            }
    
        }
    
  • admin启动的时候,控制台可以看到打印日志:

    2021-01-30 22:06:34.378  INFO 19164 --- [           main] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh interval: 300000ms
    ......
    2021-01-30 22:06:34.378  INFO 19164 --- [           main] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh interval: 300000ms
    
    
    
  • 根据日志打印的地方debug到http长轮询监听类HttpLongPollingDataChangedListener 。scheduler.scheduleWithFixedDelay,设置一个延迟执行的线程池,刷新本地缓存*

    protected void afterInitialize() {
        long syncInterval = httpSyncProperties.getRefreshInterval().toMillis();
        // Periodically check the data for changes and update the cache
        scheduler.scheduleWithFixedDelay(() -> {
            log.info("http sync strategy refresh config start.");
            try {
                this.refreshLocalCache();
                log.info("http sync strategy refresh config success.");
            } catch (Exception e) {
                log.error("http sync strategy refresh config error!", e);
            }
        }, syncInterval, syncInterval, TimeUnit.MILLISECONDS);
        log.info("http sync strategy refresh interval: {}ms", syncInterval);
    }
    
  • 看到打印了同步开始和同步成功日志,时间刚好对应上面的5分钟(300000ms),判断admin每隔5分钟会从数据库刷新一次配置。

    2021-01-30 22:11:34.383  INFO 19164 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config start.
    2021-01-30 22:11:34.386  INFO 19164 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener    : update config cache[APP_AUTH], old: {group='APP_AUTH', md5='d751713988987e9331980363e24189ce', lastModifyTime=1612015594064}, updated: {group='APP_AUTH', md5='d751713988987e9331980363e24189ce', lastModifyTime=1612015894386}
    2021-01-30 22:11:34.390  INFO 19164 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener    : update config cache[PLUGIN], old: {group='PLUGIN', md5='0b72170e66c6ae436cef1f214504936a', lastModifyTime=1612015594089}, updated: {group='PLUGIN', md5='0b72170e66c6ae436cef1f214504936a', lastModifyTime=1612015894390}
    2021-01-30 22:11:34.504  INFO 19164 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener    : update config cache[RULE], old: {group='RULE', md5='d64c9964f7837662af61c5e1981e3c41', lastModifyTime=1612015594333}, updated: {group='RULE', md5='d64c9964f7837662af61c5e1981e3c41', lastModifyTime=1612015894504}
    2021-01-30 22:11:34.512  INFO 19164 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener    : update config cache[SELECTOR], old: {group='SELECTOR', md5='4231dce5f8ece7b7956c25e9db088e44', lastModifyTime=1612015594354}, updated: {group='SELECTOR', md5='4231dce5f8ece7b7956c25e9db088e44', lastModifyTime=1612015894512}
    2021-01-30 22:11:34.515  INFO 19164 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener    : update config cache[META_DATA], old: {group='META_DATA', md5='f8e578e88c1fd20821ba26fd5647e406', lastModifyTime=1612015594377}, updated: {group='META_DATA', md5='f8e578e88c1fd20821ba26fd5647e406', lastModifyTime=1612015894515}
    2021-01-30 22:11:34.515  INFO 19164 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config success.
    
  • 刷新5种数据的本地缓存,权限、插件、规则、选择器和元数据。

    private void refreshLocalCache() {
        this.updateAppAuthCache();
        this.updatePluginCache();
        this.updateRuleCache();
        this.updateSelectorCache();
        this.updateMetaDataCache();
    }
    
  • HttpLongPollingDataChangedListener继承AbstractDataChangedListener,afterPropertiesSet方法调用了上面说的 afterInitialize方法,AbstractDataChangedListener 又实现了 DataChangedListener InitializingBean

  • 当数据发生更改时(比如选择器或者插件),将创建线程以异步通知客户端,看内部类DataChangeTask 实现了Runnable

    class DataChangeTask implements Runnable {
    
        /**
         * The Group where the data has changed.
         */
        private final ConfigGroupEnum groupKey;
    
        /**
         * The Change time.
         */
        private final long changeTime = System.currentTimeMillis();
    
        /**
         * Instantiates a new Data change task.
         *
         * @param groupKey the group key
         */
        DataChangeTask(final ConfigGroupEnum groupKey) {
            this.groupKey = groupKey;
        }
    
        @Override
        public void run() {
            for (Iterator<LongPollingClient> iter = clients.iterator(); iter.hasNext();) {
                LongPollingClient client = iter.next();
                iter.remove();
                client.sendResponse(Collections.singletonList(groupKey));
                log.info("send response with the changed group,ip={}, group={}, changeTime={}", client.ip, groupKey, changeTime);
            }
        }
    }
    
  • 核心方法run()里又调用了一个内部类 LongPollingClient 的方法 sendResponse(),这个run() 创建了一个延迟线程池的任务方法。

    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 发现一个核心方法,会去判断数据的MD5,数据有没有更新,还有最后更新的时间戳,如果需要数据更新,返回数据。

    private List<ConfigGroupEnum> compareChangedGroup(final HttpServletRequest request) {
            List<ConfigGroupEnum> changedGroup = new ArrayList<>(ConfigGroupEnum.values().length);
            for (ConfigGroupEnum group : ConfigGroupEnum.values()) {
                // md5,lastModifyTime
                String[] params = StringUtils.split(request.getParameter(group.name()), ',');
                if (params == null || params.length != 2) {
                    throw new SoulException("group param invalid:" + request.getParameter(group.name()));
                }
                String clientMd5 = params[0];
                long clientModifyTime = NumberUtils.toLong(params[1]);
                ConfigDataCache serverCache = CACHE.get(group.name());
                // do check.
                if (this.checkCacheDelayAndUpdate(serverCache, clientMd5, clientModifyTime)) {
                    changedGroup.add(group);
                }
            }
            return changedGroup;
        }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值