Soul-源码阅读18-Http长轮询2

回顾上一篇: admin 启动之后,会每隔5分钟查询一次数据库,把数据刷新到缓存

soul-bootstrap端

  • 根据上一节yml文件配置的http同步配置方式,实例化httpSyncDataService类。

    @Configuration
    @ConditionalOnClass(HttpSyncDataService.class)
    @ConditionalOnProperty(prefix = "soul.sync.http", name = "url")
    @Slf4j
    public class HttpSyncDataConfiguration {
    @Bean
        public SyncDataService httpSyncDataService(final ObjectProvider<HttpConfig> httpConfig, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
                                               final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
            log.info("you use http long pull sync soul data");
            return new HttpSyncDataService(Objects.requireNonNull(httpConfig.getIfAvailable()), Objects.requireNonNull(pluginSubscriber.getIfAvailable()),
                    metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
        }
        
        @Bean
        @ConfigurationProperties(prefix = "soul.sync.http")
        public HttpConfig httpConfig() {
            return new HttpConfig();
        }
    }
    
    2021-02-01 17:22:04.279  INFO 17272 --- [           main] .s.s.b.s.s.d.h.HttpSyncDataConfiguration : you use http long pull sync soul data
    
    
  • bootstrap 启动之后,会先从 admin 拉取数据,看 HttpSyncDataService的start 方法 fetchGroupConfig(),启用一个存活时间60秒的线程去更新缓存。

    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);
            }
        }
    
  • httpClient请求数据,更新本地缓存

    private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
        StringBuilder params = new StringBuilder();
        for (ConfigGroupEnum groupKey : groups) {
            params.append("groupKeys").append("=").append(groupKey.name()).append("&");
        }
        String url = server + "/configs/fetch?" + StringUtils.removeEnd(params.toString(), "&");
        log.info("request configs: [{}]", url);
        String json = null;
        try {
            json = this.httpClient.getForObject(url, String.class);
        } catch (RestClientException e) {
            String message = String.format("fetch config fail from server[%s], %s", url, e.getMessage());
            log.warn(message);
            throw new SoulException(message, e);
        }
        // update local cache
        boolean updated = this.updateCacheWithJson(json);
        if (updated) {
            log.info("get latest configs: [{}]", json);
            return;
        }
        // not updated. it is likely that the current config server has not been updated yet. wait a moment.
        log.info("The config of the server[{}] has not been updated or is out of date. Wait for 30s to listen for changes again.", server);
        ThreadUtils.sleep(TimeUnit.SECONDS, 30);
    }
    
  • 看到控制台打印的日志:同步的数据内容

2021-02-01 17:37:02.231  INFO 17272 --- [           main] o.d.s.s.data.http.HttpSyncDataService    : get latest configs: [{"code":200,"message":"success","data":{"META_DATA":{"md5":"f8e578e88c1fd20821ba26fd5647e406","lastModifyTime":1612172026454,"data":[{"id":"1354786856173363200","appName":"sofa","contextPath":null,"path":"/sofa/insert","rpcType":"sofa","serviceName":"org.dromara.soul.examples.sofa.api.service.SofaSingleParamService","methodName":"insert","parameterTypes":"org.dromara.soul.examples.sofa.api.entity.SofaSimpleTypeBean","rpcExt":"{\"loadbalance\":\"hash\",\"retries\":3,\"timeout\":-1}","enabled":true},{"id":"1354786857481986048","appName":"sofa","contextPath":null,"path":"/sofa/findById","rpcType":"sofa","serviceName":"org.dromara.soul.examples.sofa.api.service.SofaSingleParamService","methodName":"findById","parameterTypes":"java.lang.String","rpcExt":"{\"loadbalance\":\"hash\",\"retries\":3,\"timeout\":-1}","enabled":true},{"id":"1354786858190823424","appName":"sofa","contextPa。。。。。。。。
  • 接着看到 进入doLongPolling(final String server) 方法,每隔 60s 调用一次 /configs/listener ,把当前网关里缓存的数据和最后更新的时间戳发给 admin

    //向 admin 发送请求

    String json = this.httpClient.postForEntity(listenerUrl, httpEntity, String.class).getBody();
    

在这里插入图片描述

  • admin 收到请求后,会去判断数据的MD5,数据有没有更新,还有最后更新的时间戳,检查数据是否需要更新,有数据更新的话,会返回更新数据的 group 给 bootstrap。

    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;
        }
        // ......
    }
    
    private boolean checkCacheDelayAndUpdate(final ConfigDataCache serverCache, final String clientMd5, final long clientModifyTime) {
        // iMD5相等,说明数据不需要更新
        if (StringUtils.equals(clientMd5, serverCache.getMd5())) {
            return false;
        }
        // // MD5不相等,admin 缓存里的最后更新时间戳比 bootstrap 端的最后更新时间戳大,说明数据过期了
        long lastModifyTime = serverCache.getLastModifyTime();
        if (lastModifyTime >= clientModifyTime) {
            // the client's config is out of date.
            return true;
        }
        // admin 缓存里的最后更新时间戳比 bootstrap 端的最后更新时间戳小, 说明本地缓存需要更新.
        // // 考虑到并发问题,admin需要加锁,否则,可能会导致soul-web的请求同时更新缓存,从而导致数据库压力过大
        boolean locked = false;
        try {
            // 5s内尝试去获取锁,如果没获取到就抛异常
            locked = LOCK.tryLock(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            //  // 没获取到锁,说明有其他线程在修改数据,直接返回有数据更新,中断当前线程
            return true;
        }
        if (locked) {
            try {
                ConfigDataCache latest = CACHE.get(serverCache.getGroup());
                if (latest != serverCache) {
                    //  在并发的时候,数据有变动,两次从本地缓存获取的数据不一致,看下两个对象的MD5值是否相同,不相同,说明有更新。
                    return !StringUtils.equals(clientMd5, latest.getMd5());
                }
                // 从数据库拉取数据
                this.refreshLocalCache();
                latest = CACHE.get(serverCache.getGroup());
                return !StringUtils.equals(clientMd5, latest.getMd5());
            } finally {
                LOCK.unlock();
            }
        }
        // not locked, the client need to be updated.
        return true;
    }
    

在这里插入图片描述

  • bootstrap 收到响应后,会判断有没有数据更新,如果有数据更新,就调用 admin 的 /configs/fetch 获取数据

    try {
        String json = this.httpClient.postForEntity(listenerUrl, httpEntity, String.class).getBody();
        log.debug("listener result: [{}]", json);
        groupJson = GSON.fromJson(json, JsonObject.class).getAsJsonArray("data");
    } catch (RestClientException e) {
        String message = String.format("listener configs fail, server:[%s], %s", server, e.getMessage());
        throw new SoulException(message, e);
    }
    // 有数据更新时
    if (groupJson != null) {
        // fetch group configuration async.
        ConfigGroupEnum[] changedGroups = GSON.fromJson(groupJson, ConfigGroupEnum[].class);
        if (ArrayUtils.isNotEmpty(changedGroups)) {
            log.info("Group config changed: {}", Arrays.toString(changedGroups));
            this.doFetchGroupConfig(server, changedGroups);
        }
    }
    
    private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
        ......
        String url = server + "/configs/fetch?" + StringUtils.removeEnd(params.toString(), "&");
       ......
        try {
            json = this.httpClient.getForObject(url, String.class);
        } catch (RestClientException e) {
            String message = String.format("fetch config fail from server[%s], %s", url, e.getMessage());
            log.warn(message);
            throw new SoulException(message, e);
        }
        // update local cache
        boolean updated = this.updateCacheWithJson(json);
        if (updated) {
            log.info("get latest configs: [{}]", json);
            return;
        }
    
  • 从缓存里获取配置ConfigController# fetchConfigs

  • ConfigController# listener如果配置数据更改,则会立即响应更改的信息。否则,客户端的请求线程将被阻塞,直到数据发生变化或达到指定的超时时间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值