soul源码学习(八)-http长连接数据同步

分析前准备

环境准备

  1. soul-admin开启http同步配置
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
  1. soul-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

数据同步过程

soul-bootstrap启动后,会主动向soul-admin发起一次获取全量配置的请求,并将结果缓存到本地,同时开启线程做以下功能:

  1. 首先向soul-admin发起获取分组信息是否变更的请求,soul-admin接收到该请求后,将其请求保存到阻塞队列中,并在60s后出队列执行分组数据是否变更的判断,无论是否变化,都会给soul-boostrap发回响应信息,soul-bootstrap根据结果来判定是否再次请求获取便发生变化的配置信息
  2. 功能1在线程内为死循环操作,由于发起的请求会被soul-admin阻塞60s,因此在这60s期间,如果有数据发生变更(spring事件机制通知数据变更),会立刻响应soul-bootstrap,而不一直等待到60s再响应
  3. 需要注意的是如果soul-bootstrap内执行线程获取了变更配置信息,会暂停30s后,会再次发出请求,如果没有变更配置信息,会立马发起下次请求
    通过以上办法来实现http长轮询机制,具有以下优点:
  4. 减少请求次数
  5. 数据变更能及时响应客户端.

源码分析

  1. soul-boostrap启动后的处理关键源码(位于HttpSyncDataService类中):
    在这里插入图片描述
    图中第一处标红为首次获取全量配置的请求
    图中第二处标红为线程内的长轮询操作,具体关键源码如下:
    在这里插入图片描述
    可以看到是死循环不断发送请求操作,其中轮询方法关键代码如下:
  private void doLongPolling(final String server) {
       //代码省略
        String listenerUrl = server + "/configs/listener";
        log.debug("request listener configs: [{}]", listenerUrl);
        JsonArray groupJson = null;
        try {
            String json = this.httpClient.postForEntity(listenerUrl, httpEntity, String.class).getBody();//该处为分组信息是否变更的请求
            groupJson = GSON.fromJson(json, JsonObject.class).getAsJsonArray("data");
        } catch (RestClientException e) {
         .....
        }
        if (groupJson != null) {
            ConfigGroupEnum[] changedGroups = GSON.fromJson(groupJson, ConfigGroupEnum[].class);
            if (ArrayUtils.isNotEmpty(changedGroups)) {
                this.doFetchGroupConfig(server, changedGroups);//该处为获取变更的配置信息
            }
        }
    }

获取配置信息的方法fetchGroupConfig,内部获取成功后,会暂停30s,代码不再展示
2. soul-admin接收到请求后,接受处理源码为:
在这里插入图片描述
调用最关键的源码:
在这里插入图片描述
讲请求变为异步并放到阻塞队列中,设置定时事件为60s,其中LongPollingClient为内部类,执行的方法为:
在这里插入图片描述
60s后出队列,执行比较配置信息是否发生变更操作,并响应给客户端,客户端根据是否变更来判断是否发起获取配置信息,soul-bootrap已分析,soul-admin的处理代码如下:
在这里插入图片描述
具体处理过程不再分析

  1. soul-admin在界面进行相关数据变更时,简单分析下流转(基于事件机制不再分析),看关键代码:
    在这里插入图片描述
    接收到事件通知后,通过事件分发器调用以上代码,关键方法为DataChangeTask也是内部方法,关键代码为
    在这里插入图片描述
    可以看到直接告知连接的客户端数据已发生变更,并没有2中的先判断数据是否已变更,再响应,因为此事件是已明确变更了,不用再判断
  2. 最后分析下判断分组信息变更的逻辑:
private boolean checkCacheDelayAndUpdate(final ConfigDataCache serverCache, final String clientMd5, final long clientModifyTime) {
  // 判断客户端传来的md5只要与目前缓存中是否一致
    if (StringUtils.equals(clientMd5, serverCache.getMd5())) {
        return false;
    }

   //判断缓存最后修改时间是否大于客户端修改时间
    if (lastModifyTime >= clientModifyTime) {
        // the client's config is out of date.
        return true;
    }

   //如果最后修改时间小于客户端的时间,要进行加锁,因为要多线程环境下要处理数据库刷新操作
    boolean locked = false;
    try {
        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) {
                //由于admin缓存可能会被更新,两次缓存可能会不一致
                return !StringUtils.equals(clientMd5, latest.getMd5());
            }
            // load cache from db.
            this.refreshLocalCache();
            latest = CACHE.get(serverCache.getGroup());
            return !StringUtils.equals(clientMd5, latest.getMd5());
        } finally {
            LOCK.unlock();
        }
    }    return true;
}

关于为什么要进行区分修改时间的判断,猜测可能是因为事件机制的原因,缓存和数据库操作不在同一事物中导致数据库更新了,但缓存未更新,客户端有可能会出现更新时间比服务器端的缓存中的时间要新,这种情况都可能要根据数据库刷新缓存了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值