背景
在《分布式领域扩展点设计稿》一文中,我们提到针对业务横向扩展点和纵向扩展点的编排能力。
那有这样的一种场景:针对于一次会话,同时会调很多外部服务,同时这些RPC服务会有多种直接或间接的关系,是否有更高效的方式能够让我们的一次会话时间变得更高效,同时也能够保证系统的相对稳定性呢?
设计思想
如果开发者在设计之初,对于领域边界以及子域能力划分比较清晰,那我们编排的业务扩展点就不会杂乱无章。
在SpringBoot启动时,伴随着IOC容器的初始化,领域扩展点容器也随之完成全量向量视图初始化、向量深度视图初始化、拓扑排序之后的容器初始化。
基于动态线程池思想,可以通过扩展点容器中的编排任务,动态调整线程池?根据拓扑排序后的结果,异步执行编排的任务,完成召回、过滤以及核心业务逻辑。
设计图
详细设计
- 容器初始化时,从云端配置中心拉取扩展点编排配置文件,本地编排配置文件可以做兜底,也可以调节两者权重
- 拿到编排数据源之后,渲染本地向量广度视图以及向量深度视图
- 通过Kahn算法,将两个视图清洗成拓扑排序之后的容器
- 通过权衡算法,初始化动态线程池,并预热核心线程
- 将上述操作数据上传到我们的扩展点监控中心
- 通过监控水位线,可动态配置线程池相关核心参数
- 当一次会话开始时,会通过拓扑排序,并发去执行扩展点任务
- 通过向量视图,可以针对所有扩展点做召回和过滤处理
核心算法伪代码
/**
* Kahn算法
*
* @author issavior
*/
public class KahnTopologicalSort {
/**
* 向量广度视图
*/
private final Map<Integer, List<Integer>> adjList = new HashMap<>();
/**
* 向量深度视图
*/
private final Map<Integer, Integer> inDegree = new HashMap<>();
/**
* 拓扑排序结果
*/
private final List<Integer> topoOrder = new ArrayList<>();
/**
* 渲染向量视图和向量深度
*
* @param u 向量头
* @param v 向量尾
*/
public void addEdge(int u, int v) {
// 渲染视图
adjList.putIfAbsent(u, new ArrayList<>());
adjList.get(u).add(v);
// 更新入度
inDegree.put(v, inDegree.getOrDefault(v, 0) + 1);
inDegree.putIfAbsent(u, 0);
}
/**
* 拓扑排序
*
* @return 业务节点顺序
*/
public List<Integer> topologicalSort() {
Queue<Integer> queue = new LinkedList<>();
// 将所有入度为0的节点加入队列
for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
queue.offer(entry.getKey());
}
}
while (!queue.isEmpty()) {
int current = queue.poll();
topoOrder.add(current);
// 遍历当前节点的所有邻接点
for (int neighbor : adjList.getOrDefault(current, Collections.emptyList())) {
// 减少邻接点的入度
int newInDegree = inDegree.get(neighbor) - 1;
inDegree.put(neighbor, newInDegree);
// 如果邻接点的入度变为0,则加入队列
if (newInDegree == 0) {
queue.offer(neighbor);
}
}
}
// 检查是否所有节点都被访问过,若为有环图,初始化报错
if (topoOrder.size() != inDegree.size()) {
throw new IllegalStateException("Graph has a cycle and cannot be topologically sorted.");
}
return topoOrder;
}
}