今天我们使用 http长轮询来同步数据到soul-bootstrap,相比websocket方式更为轻量,但同时时效性会略为降低。首先我们来看看什么是http长轮询。
短连接:client每向server发起一次http请求,就建立一个tpc连接,任务结束则中断。
在http长轮询机制中,client一样向server请求数据。然而,如果server没有可以立即返回给client的数据,则不会立刻返回一个空结果,而是保持这个请求等待数据到来(或者恰当的超时:小于ajax的超时时间),此时client进入之pending状态,server将数据准备好或超时后返回给client。
整个流程如图所示:
网关配置(记得重启)
首先在 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
深入源码
/**
* 根据配置项加载HttpLongPollingDataChangedListener这个bean
*/
@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);
}
}
/**
* 使用 ArrayBlockingQueue 存放客户端,并创建一个单线程的 ScheduledThreadPoolExecutor 来实现周期性任务的调度
* @param httpSyncProperties the HttpSyncProperties
*/
public HttpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
this.clients = new ArrayBlockingQueue<>(1024);
this.scheduler = new ScheduledThreadPoolExecutor(1,
SoulThreadFactory.create("long-polling", true));
this.httpSyncProperties = httpSyncProperties;
}
HttpLongPollingDataChangedListener 中比较精髓的函数是 checkCacheDelayAndUpdate:
/**
* 检查客户端是否需要更新缓存
* @param serverCache the admin local cache
* @param clientMd5 the client md5 value
* @param clientModifyTime the client last modify time
* @return true: the client needs to be updated, false: not need.
*/
private boolean checkCacheDelayAndUpdate(final ConfigDataCache serverCache, final String clientMd5, final long clientModifyTime) {
// 如果MD5 值相等,则不用更新
if (StringUtils.equals(clientMd5, serverCache.getMd5())) {
return false;
}
// 如果MD5 值不相等,则比较 lastModifyTime
long lastModifyTime = serverCache.getLastModifyTime();
if (lastModifyTime >= clientModifyTime) {
// the client's config is out of date.
return true;
}
// the lastModifyTime before client, then the local cache needs to be updated.
// Considering the concurrency problem, admin must lock,
// otherwise it may cause the request from soul-web to update the cache concurrently, causing excessive db pressure
boolean locked = false;
try {
//尝试去获取锁, 超过5秒后放弃
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) {
// the cache of admin was updated. if the md5 value is the same, there's no need to update.
// 如果 admin 的缓存更新了 但 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;
}
总结
其实 checkCacheDelayAndUpdate 跟最近做的 安卓更新检测接口很像, 巧妙地通过 时间 和 数据MD5 这两个因子对缓存做比较, 同时考虑到了并发场景, 添加了一个锁保证更新的原子性.