优化文档刷新逻辑

1.x
tanghc 5 years ago
parent 66e378816c
commit aabc7b90a7
  1. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ZookeeperContext.java
  2. 5
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConstants.java
  3. 18
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ZookeeperTool.java
  4. 9
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java
  5. 19
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/route/ServiceRouteInfo.java
  6. 2
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/config/OpenServiceConfig.java
  7. 18
      sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java
  8. 3
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java
  9. 5
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/WebsiteConstants.java
  10. 103
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/ZookeeperContext.java
  11. 6
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java
  12. 3
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java
  13. 98
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java

@ -288,7 +288,7 @@ public class ZookeeperContext {
}
/**
* 监听一个节点
* 监听一个临时节点
*
* @param path
* @param listenCallback 回调

@ -8,4 +8,9 @@ public class ServiceConstants {
* zookeeper存放接口路由信息的根目录
*/
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
/**
* 服务临时节点
*/
public static final String SOP_SERVICE_TEMP_PATH = "/com.gitee.sop.service.tmp";
}

@ -9,6 +9,7 @@ import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.springframework.core.env.Environment;
import java.io.Closeable;
@ -94,6 +95,23 @@ public class ZookeeperTool implements Closeable {
.forPath(path, data.getBytes());
}
/**
* 创建临时序列节点
* @param path 节点路径
* @param data 数据
* @return 返回节点路径
* @throws Exception
*/
public String createOrUpdateEphemeralSequentialPath(String path, String data) throws Exception {
return getClient().create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(path, data.getBytes());
}
/**
* 新建或保存节点
*

@ -10,7 +10,6 @@ import com.gitee.sop.servercommon.route.ServiceRouteInfo;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.DigestUtils;
@ -81,6 +80,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
String nodeData = JSON.toJSONString(serviceRouteInfo);
log.info("serviceId:{}, zookeeper保存路径:{}", serviceId, savePath);
this.zookeeperTool.createPath(savePath, nodeData);
this.zookeeperTool.createPath(serviceRouteInfo.getZookeeperTempServiceIdPath(), "");
} catch (Exception e) {
throw new IllegalStateException("zookeeper操作失败");
}
@ -91,8 +91,11 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
try {
ServiceRouteInfo serviceRouteInfo = this.buildServiceGatewayInfo(serviceApiInfo);
this.uploadServiceRouteInfoToZookeeper(serviceRouteInfo);
} finally {
IOUtils.closeQuietly(zookeeperTool);
// 同时上传一个临时节点
String tempPath = serviceRouteInfo.getZookeeperTempServiceIdChildPath();
this.zookeeperTool.createOrUpdateEphemeralSequentialPath(tempPath, JSON.toJSONString(serviceRouteInfo));
} catch (Exception e) {
log.error("上传一个临时节点失败, serviceApiInfo:{}", serviceApiInfo, e);
}
}

@ -13,6 +13,7 @@ import java.util.List;
@Data
public class ServiceRouteInfo {
private static final String SOP_SERVICE_ROUTE_PATH = ServiceConstants.SOP_SERVICE_ROUTE_PATH;
private static final String SOP_SERVICE_TEMP_PATH = ServiceConstants.SOP_SERVICE_TEMP_PATH;
/** 服务名称,对应spring.application.name */
private String serviceId;
@ -39,4 +40,22 @@ public class ServiceRouteInfo {
public String getZookeeperPath() {
return SOP_SERVICE_ROUTE_PATH + '/' + serviceId;
}
/**
* 返回zookeeper路径
* @return 返回zookeeper路径
*/
@JSONField(serialize = false)
public String getZookeeperTempServiceIdPath() {
return SOP_SERVICE_TEMP_PATH + '/' + serviceId;
}
/**
* 返回zookeeper路径
* @return 返回zookeeper路径
*/
@JSONField(serialize = false)
public String getZookeeperTempServiceIdChildPath() {
return getZookeeperTempServiceIdPath() + "/" + serviceId;
}
}

@ -31,7 +31,7 @@ public class OpenServiceConfig extends AlipayServiceConfiguration {
/**
* 开启文档
* 开启文档本地微服务文档地址http://localhost:2222/doc.html
* http://ip:port/v2/api-docs
*/
@Configuration

@ -31,4 +31,22 @@ public class CuratorTest extends TestCase {
} catch (Exception e) {
}
}
/**
* 删除临时节点
*/
public void testDelTemp() {
String tempRoot = "/com.gitee.sop.service.tmp";
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
try {
client.delete().deletingChildrenIfNeeded().forPath(tempRoot);
} catch (Exception e) {
}
}
}

@ -1,5 +1,6 @@
package com.gitee.sop.websiteserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.List;
@ -10,5 +11,7 @@ import java.util.List;
@Data
public class DocInfo {
private String title;
@JSONField(serialize = false)
private String serviceId;
private List<DocModule> docModuleList;
}

@ -8,4 +8,9 @@ public class WebsiteConstants {
* zookeeper存放接口路由信息的根目录
*/
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
/**
* 服务临时节点
*/
public static final String SOP_SERVICE_TEMP_PATH = "/com.gitee.sop.service.tmp";
}

@ -5,11 +5,18 @@ import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.function.Consumer;
/**
* @author tanghc
@ -50,6 +57,10 @@ public class ZookeeperContext {
return WebsiteConstants.SOP_SERVICE_ROUTE_PATH;
}
public static String getServiceTempRootPath() {
return WebsiteConstants.SOP_SERVICE_TEMP_PATH;
}
public static CuratorFramework getClient() {
return client;
}
@ -58,10 +69,99 @@ public class ZookeeperContext {
ZookeeperContext.client = client;
}
public static boolean isPathExist(String path) {
try {
return client.checkExists().forPath(path) != null;
} catch (Exception e) {
return false;
}
}
/**
* 是否有子节点
* @param parentPath
* @return
* @throws Exception
*/
public static boolean hasChildren(String parentPath) throws Exception {
List<String> children = client.getChildren().forPath(parentPath);
return !CollectionUtils.isEmpty(children);
}
/**
* 创建path如果path存在不报错静默返回path名称
*
* @param path
* @param data
* @return
* @throws Exception
*/
public static String createPath(String path, String data) throws Exception {
if (isPathExist(path)) {
return path;
}
return getClient().create()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
}
/**
* 获取子节点信息并监听子节点
*
* @param parentPath 父节点路径
* @param listConsumer 子节点数据
* @param listener 监听事件
* @throws Exception
*/
public static void getChildrenAndListen(String parentPath, Consumer<List<ChildData>> listConsumer, PathChildrenCacheListener listener) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
List<ChildData> childDataList = childrenCache.getCurrentData();
listConsumer.accept(childDataList);
log.info("监听子节点增删改,监听路径:{}", parentPath);
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(listener);
}
/**
* 监听子节点的增删改
*
* @param parentPath 父节点路径
* @param listener
* @throws Exception
*/
public static void listenChildren(String parentPath, PathChildrenCacheListener listener) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(listener);
}
/**
* 监听子节点可以自定义层级
* @param parentPath 父节点路径
* @param maxDepth 层级从1开始比如当前监听节点/t1目录最深为/t1/t2/t3/t4,则maxDepth=3,说明下面3级子目录全
* @param maxDepth 层级从1开始比如当前监听节点/t1目录最深为/t1/t2/t3/t4,则maxDepth=3,说明下面3级子目录全部监听
* @param listener
* @throws Exception
*/
@ -73,7 +173,6 @@ public class ZookeeperContext {
.build();
treeCache.getListenable().addListener(listener);
//没有开启模式作为入参的方法
treeCache.start();
}

@ -1,7 +1,6 @@
package com.gitee.sop.websiteserver.controller;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.manager.DocManager;
import com.gitee.sop.websiteserver.vo.DocBaseInfoVO;
import com.gitee.sop.websiteserver.vo.DocInfoVO;
@ -59,11 +58,6 @@ public class DocController {
return docManager.getByTitle(title);
}
@GetMapping("/item/{method}/{version}/")
public DocItem getDocItem(@PathVariable("method") String method, @PathVariable("version") String version) {
return docManager.get(method, version);
}
// 后门地址,可手动更新文档内容,一般情况下用不到
@GetMapping("/reload")

@ -1,7 +1,6 @@
package com.gitee.sop.websiteserver.manager;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import java.util.Collection;
@ -12,8 +11,6 @@ public interface DocManager {
void load(String serviceId);
DocItem get(String method, String version);
DocInfo getByTitle(String title);
Collection<DocInfo> listAll();

@ -7,12 +7,12 @@ import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance;
import com.gitee.sop.registryapi.service.RegistryService;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.bean.ZookeeperContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
@ -43,21 +43,17 @@ import java.util.stream.Collectors;
public class DocManagerImpl implements DocManager {
// key:title
Map<String, DocInfo> docDefinitionMap = new HashMap<>();
private Map<String, DocInfo> docDefinitionMap = new HashMap<>();
// key: name+version
Map<String, DocItem> docItemMap = new HashMap<>();
private RestTemplate restTemplate = new RestTemplate();
private DocParser swaggerDocParser = new SwaggerDocParser();
RestTemplate restTemplate = new RestTemplate();
private DocParser easyopenDocParser = new EasyopenDocParser();
DocParser swaggerDocParser = new SwaggerDocParser();
private ExecutorService executorService = Executors.newSingleThreadExecutor();
DocParser easyopenDocParser = new EasyopenDocParser();
ExecutorService executorService = Executors.newSingleThreadExecutor();
DelayQueue<Msg> queue = new DelayQueue<>();
private DelayQueue<Msg> queue = new DelayQueue<>();
private String secret = "b749a2ec000f4f29";
@ -70,14 +66,10 @@ public class DocManagerImpl implements DocManager {
@Value("${doc.refresh-seconds:60}")
private String refreshSeconds;
private volatile boolean listenInited;
@Override
public void load(String serviceId) {
try {
List<ServiceInfo> serviceInfoList = registryService.listAllService(1, 9999);
log.info("服务列表:{}", serviceInfoList);
serviceInfoList
.stream()
// 网关没有文档提供,需要排除
@ -110,6 +102,7 @@ public class DocManagerImpl implements DocManager {
JSONObject docRoot = JSON.parseObject(docInfoJson, Feature.OrderedField, Feature.DisableCircularReferenceDetect);
DocParser docParser = this.buildDocParser(docRoot);
DocInfo docInfo = docParser.parseJson(docRoot);
docInfo.setServiceId(serviceInstance.getServiceId());
docDefinitionMap.put(docInfo.getTitle(), docInfo);
} catch (Exception e) {
// 这里报错可能是因为有些微服务没有配置swagger文档,导致404访问不到
@ -134,11 +127,6 @@ public class DocManagerImpl implements DocManager {
}
}
@Override
public DocItem get(String method, String version) {
return docItemMap.get(method + version);
}
@Override
public DocInfo getByTitle(String title) {
return docDefinitionMap.get(title);
@ -164,28 +152,73 @@ public class DocManagerImpl implements DocManager {
executorService.execute(new Consumer(queue, this));
ZookeeperContext.setEnvironment(environment);
String routeRootPath = ZookeeperContext.getRouteRootPath();
String serviceTempRootPath = ZookeeperContext.getServiceTempRootPath();
ZookeeperContext.createPath(serviceTempRootPath, "{}");
// 如果节点内容有变化则自动更新文档
ZookeeperContext.listenChildren(routeRootPath, 1, (client, event) -> {
if (listenInited) {
long id = System.currentTimeMillis();
ZookeeperContext.getChildrenAndListen(serviceTempRootPath, childDataList -> {
for (ChildData childData : childDataList) {
String serviceIdPath = childData.getPath();
try {
boolean hasChildren = ZookeeperContext.hasChildren(serviceIdPath);
if (hasChildren) {
log.info("加载文档服务器,path:{}", serviceIdPath);
listenServiceIdPath(serviceIdPath);
}
} catch (Exception e) {
log.error("监听路径失败,serviceIdPath:{}", serviceIdPath);
}
}
}, (client, event) -> {
PathChildrenCacheEvent.Type type = event.getType();
if (type == PathChildrenCacheEvent.Type.CHILD_ADDED) {
String serviceIdPath = event.getData().getPath();
log.info("新增文档服务器,path:{}", serviceIdPath);
listenServiceIdPath(serviceIdPath);
}
});
}
private void listenServiceIdPath(String serviceIdPath) throws Exception {
ZookeeperContext.listenChildren(serviceIdPath, (client, event) -> {
String path = event.getData().getPath();
PathChildrenCacheEvent.Type type = event.getType();
log.info("服务节点变更,path:{}, eventType:{}", path, event.getType().name());
if (type == PathChildrenCacheEvent.Type.CHILD_ADDED
|| type == PathChildrenCacheEvent.Type.CHILD_UPDATED) {
byte[] data = event.getData().getData();
String serviceInfoJson = new String(data);
if (StringUtils.isEmpty(serviceInfoJson)) {
return;
}
ZKServiceInfo serviceInfo = JSON.parseObject(serviceInfoJson, ZKServiceInfo.class);
String serviceId = serviceInfo.getServiceId();
log.info("微服务[{}]推送更新", serviceId);
Msg msg = new Msg(id, 1000 * NumberUtils.toInt(refreshSeconds, 60));
int delaySeconds = NumberUtils.toInt(refreshSeconds, 60);
log.info("微服务[{}]推送更新,{}秒后加载文档内容", serviceId, delaySeconds);
long id = System.currentTimeMillis();
Msg msg = new Msg(id, delaySeconds * 1000);
msg.serviceId = serviceId;
// 延迟20秒执行
queue.offer(msg);
}
TreeCacheEvent.Type type = event.getType();
if (type == TreeCacheEvent.Type.INITIALIZED) {
listenInited = true;
} else if (event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) {
byte[] data = event.getData().getData();
String serviceInfoJson = new String(data);
ZKServiceInfo serviceInfo = JSON.parseObject(serviceInfoJson, ZKServiceInfo.class);
String serviceId = serviceInfo.getServiceId();
boolean hasChildren = ZookeeperContext.hasChildren(serviceIdPath);
// 如果没有子节点就删除
if (!hasChildren) {
log.info("服务节点已删除,删除对应文档信息,path:{}", event.getData().getPath());
removeDoc(serviceId);
}
}
});
}
public void removeDoc(String serviceId) {
docDefinitionMap.entrySet().removeIf(entry -> serviceId.equalsIgnoreCase(entry.getValue().getServiceId()));
}
static class Msg implements Delayed {
private long id;
private long delay;
@ -202,8 +235,7 @@ public class DocManagerImpl implements DocManager {
@Override
public int compareTo(Delayed delayed) {
Msg msg = (Msg) delayed;
return Long.valueOf(this.id) > Long.valueOf(msg.id) ? 1
: (Long.valueOf(this.id) < Long.valueOf(msg.id) ? -1 : 0);
return Long.compare(this.id, msg.id);
}
// 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期否则还没到期

Loading…
Cancel
Save