一、HTTP长轮询概述
http 长轮询机制如上所示,soul-web 网关请求 admin 的配置服务,读取超时时间为 90s,意味着网关层请求配置服务最多会等待 90s,这样便于 admin 配置服务及时响应变更数据,从而实现准实时推送。
二、soul-admin流程分析
- 根据官方文档,soul-admin的入口在ConfigController
//前提注入内容
@ConditionalOnBean(HttpLongPollingDataChangedListener.class)
@RestController
@RequestMapping("/configs")
@Slf4j
public class ConfigController {
@Resource
private HttpLongPollingDataChangedListener longPollingListener;
/**
* Fetch configs soul result.
*
* @param groupKeys the group keys
* @return the soul result
*/
@GetMapping("/fetch")
public SoulAdminResult fetchConfigs(@NotNull final String[] groupKeys) {
Map<String, ConfigData<?>> result = Maps.newHashMap();
for (String groupKey : groupKeys) {
ConfigData<?> data = longPollingListener.fetchConfig(ConfigGroupEnum.valueOf(groupKey));
result.put(groupKey, data);
}
return SoulAdminResult.success(SoulResultMessage.SUCCESS, result);
}
/**
* Listener.
*
* @param request the request
* @param response the response
*/
@PostMapping(value = "/listener")
public void listener(final HttpServletRequest request, final HttpServletResponse response) {
longPollingListener.doLongPolling(request, response);
}
}
- 前提需注入类HttpLongPollingDataChangedListener
/**
* Instantiates a new Http long polling data changed listener.
* @param httpSyncProperties the HttpSyncProperties
*/
public HttpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
// 初始化一个1024的BlockingQueue
this.clients = new ArrayBlockingQueue<>(1024);
// 启动一个定时长轮询用的守护线程
this.scheduler = new ScheduledThreadPoolExecutor(1,
SoulThreadFactory.create("long-polling", true));
this.httpSyncProperties = httpSyncProperties;
}
- 初始化之后全量同步一次
- 收到/configs/listener post请求
这里是一个异步机制,提前会记录每个group的改动情况
/**
* 收到客户端请求,如果admin记录数据该group有变化,则会立即向该客户端发起更新数据
* 否则,这个客户端的请求会等待,直到再有数据变化或者轮询时间到
* Otherwise, the client's request thread is blocked until any data changes or the specified timeout is reached.
*/
public void doLongPolling(final HttpServletRequest request, final HttpServletResponse response) {
// 因为soul-web可能未收到某个配置变更的通知,因此MD5值可能不一致,则立即响应
List<ConfigGroupEnum> changedGroup = compareChangedGroup(request);
String clientIp = getRemoteIp(request);
if (CollectionUtils.isNotEmpty(changedGroup)) {
this.generateResponse(response, changedGroup);
log.info("send response with the changed group, ip={}, group={}", clientIp, changedGroup);
return;
}
// Servlet3.0异步响应http请求
final AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(0L);
// SERVER_MAX_HOLD_TIMEOUT = 60000ms
scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
}
- LongPollingClient的run方法
@Override
public void run() {
this.asyncTimeoutFuture = scheduler.schedule(() -> {
//加入定时任务,如果60s之内没有配置变更,则60s后执行,响应http请求
clients.remove(LongPollingClient.this);
List<ConfigGroupEnum> changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
// 发送变更,不管有没有变化
sendResponse(changedGroups);
}, timeoutTime, TimeUnit.MILLISECONDS);
// 先扔进去,存起来
clients.add(this);
}
- 当收到任何变更通知的时候都会启动DataChangeTask
// soul-admin发生了配置变更,挨个将队列中的请求移除,并予以响应
class DataChangeTask implements Runnable {
DataChangeTask(final ConfigGroupEnum groupKey) {
this.groupKey = groupKey;
}
@Override
public void run() {
try {
for (Iterator<LongPollingClient> iter = clients.iterator(); iter.hasNext(); ) {
LongPollingClient client = iter.next();
iter.remove();
client.sendResponse(Collections.singletonList(groupKey));
}
} catch (Throwable e) {
LOGGER.error("data change error.", e);
}
}
}
当 soul-web 网关层接收到 http 响应信息之后,拉取变更信息(如果有变更的话),然后再次请求 soul-admin 的配置服务,如此反复循环。