- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
到店商详迭代过程中,需要提供的对外能力越来越多,如预约日历、附近门店、为你推荐等。这其中不可避免会出现多个上层能力依赖同一个底层接口的场景。最初采用的方案是对外API入口进来后获取对应的能力,并发调用多项能力,由能力层调用对应的数据链路,进行业务处理。然而,随着接入功能的增多,这种情况导致了底层数据服务的重复调用,如商品配置信息,在一次API调用过程中重复调了3次,当流量增大或能力项愈多时,对底层服务的压力会成倍增加.
正值618大促,各方接口的调用都会大幅度增加。通过梳理接口依赖关系来减少重复调用,对本系统而言,降低了调用数据接口时的线程占用次数,可以有效降级CPU。对调用方来说,减少了调用次数,可减少调用方的资源消耗,保障底层服务的稳定性.
基于上述问题,采用底层接口依赖分层调用的方案。梳理接口依赖关系,逐层向上调用,注入数据,如此将同一接口的调用抽取到某层,仅调用一次,即可在整条链路使用.
只要分层后即可在每层采用多线程并发的方式调用,因为同一层级中的接口无先后依赖关系.
接下来,如何梳理接口层级关系就至关重要.
第一步:构建层级结构 。
首先获取到能力层依赖项并遍历,然后调用生成数据节点方法。方法流程如下:构建当前节点,检测循环依赖(存在循环依赖会导致栈溢出),获取并遍历节点依赖项,递归生成子节点,存放子节点.
第二步:节点平铺 。
定义Map维护平铺结构,调用平铺方法。方法流程如下:遍历层级结构,判断当前节点是否已存在map中,存在时与原节点比较将层级大的节点放入(去除重复项),不存在时直接放入即可。然后处理子节点,递归调用平铺方法,处理所有节点.
第三步:分层(分组排序) 。
流处理平铺结构,处理层级分组,存储在TreeMap中维护自然排序。对应key中的数据节点Set 需用多线程并发调用,以保证链路调用时间 。
Q1:为什么需要定义祖先节点?
A1:为了判断接口是否存在循环依赖。如果接口存在循环依赖而不检测将导致调用栈溢出,故而在调用过程中要避免并检测循环依赖。在遍历子节点过程中,如果发现当前节点的祖先已经包含当前子节点,说明依赖关系出现了环路,即循环依赖,此时抛异常终止后续流程避免栈溢出.
public class DataNode {
/**
* 节点名称
*/
private String name;
/**
* 节点层级
*/
private int level;
/**
* 祖先节点
*/
private List<String> ancestors;
/**
* 子节点
*/
private List<DataNode> children;
}
Q1:生成节点时如何维护层级?
A1:从能力层依赖开始,层级从1递加。每获取一次底层依赖,底层依赖所生成的节点层级即父节点层级+1.
/**
* 构建层级结构
*
* @param handlers 接口依赖
* @return 数据节点集
*/
private List<DataNode> buildLevel(Set<String> handlers) {
List<DataNode> result = Lists.newArrayList();
for (String next : handlers) {
DataNode dataNode = generateNode(next, 1, null, null);
result.add(dataNode);
}
return result;
}
/**
* 生成数据节点
*
* @param name 节点名称
* @param level 节点层级
* @param ancestors 祖先节点(除父辈)
* @param parent 父节点
* @return DataNode 数据节点
*/
private DataNode generateNode(String name, int level, List<String> ancestors, String parent) {
AbstractInfraHandler abstractInfraHandler = abstractInfraHandlerMap.get(name);
Set<String> infraDependencyHandlerNames = abstractInfraHandler.getInfraDependencyHandlerNames();
// 根节点
DataNode dataNode = new DataNode(name);
dataNode.setLevel(level);
dataNode.putAncestor(ancestors, parent);
if (CollectionUtils.isNotEmpty(dataNode.getAncestors()) && dataNode.getAncestors().contains(name)) {
throw new IllegalStateException("依赖关系中存在循环依赖,请检查以下handler:" + JsonUtil.toJsonString(dataNode.getAncestors()));
}
if (CollectionUtils.isNotEmpty(infraDependencyHandlerNames)) {
// 存在子节点,子节点层级+1
for (String next : infraDependencyHandlerNames) {
DataNode child = generateNode(next, level + 1, dataNode.getAncestors(), name);
dataNode.putChild(child);
}
}
return dataNode;
}
层级结构如下:
Q1:如何处理接口依赖过程中的重复项?
A1:遍历所有的子节点,将所有子节点平铺到一层,平铺时如果节点已经存在,比较层级,保留层级大的即可(层级大说明依赖位于更底层,调用时要优先调用).
/**
* 层级结构平铺
*
* @param dataNodes 数据节点
* @param dataNodeMap 平铺结构
*/
private void flatteningNodes(List<DataNode> dataNodes, Map<String, DataNode> dataNodeMap) {
if (CollectionUtils.isNotEmpty(dataNodes)) {
for (DataNode dataNode : dataNodes) {
DataNode dataNode1 = dataNodeMap.get(dataNode.getName());
if (Objects.nonNull(dataNode1)) {
// 存入层级大的即可,避免重复
if (dataNode1.getLevel() < dataNode.getLevel()) {
dataNodeMap.put(dataNode.getName(), dataNode);
}
} else {
dataNodeMap.put(dataNode.getName(), dataNode);
}
// 处理子节点
flatteningNodes(dataNode.getChildren(), dataNodeMap);
}
}
}
平铺结构如下:
Q1:如何分层?
A1:节点平铺后已经去重,此时借助TreeMap的自然排序特性将节点按照层级分组即可.
/**
* @param dataNodeMap 平铺结构
* @return 分层结构
*/
private TreeMap<Integer, Set<DataNode>> processLevel(Map<String, DataNode> dataNodeMap) {
return dataNodeMap.values().stream().collect(Collectors.groupingBy(DataNode::getLevel, TreeMap::new, Collectors.toSet()))
}
分层如下:
1.根据分层TreeMap的key倒序即为调用的层级顺序 。
对应key中的数据节点Set 需用多线程并发调用,以保证链路调用时间 。
梳理出调用关系并分层后,使用并发编排工具调用即可。这里梳理的层级关系,level越大,表示越优先调用.
这里以京东内部并发编排框架为例,说明调用流程:
/**
* 构建编排流程
*
* @param infraDependencyHandlers 依赖接口
* @param workerExecutor 并发线程
* @return 执行数据
*/
public Sirector<InfraContext> buildSirector(Set<String> infraDependencyHandlers, ThreadPoolExecutor workerExecutor) {
Sirector<InfraContext> sirector = new Sirector<>(workerExecutor);
long start = System.currentTimeMillis();
// 依赖顺序与执行顺序相反
TreeMap<Integer, Set<DataNode>> levelNodes;
TreeMap<Integer, Set<DataNode>> cacheLevelNodes = localCacheManager.getValue("buildSirector");
if (Objects.nonNull(cacheLevelNodes)) {
levelNodes = cacheLevelNodes;
} else {
levelNodes = getLevelNodes(infraDependencyHandlers);
ExecutorUtil.executeVoid(asyncTpExecutor, () -> localCacheManager.putValue("buildSirector", levelNodes));
}
log.info("buildSirector 梳理依赖关系耗时:{}", System.currentTimeMillis() - start);
// 最底层接口执行
Integer firstLevel = levelNodes.lastKey();
EventHandler[] beginHandlers = levelNodes.get(firstLevel).stream().map(node -> abstractInfraHandlerMap.get(node.getName())).toArray(EventHandler[]::new);
EventHandlerGroup group = sirector.begin(beginHandlers);
Integer lastLevel = levelNodes.firstKey();
for (int i = firstLevel - 1; i >= lastLevel; i--) {
EventHandler[] thenHandlers = levelNodes.get(i).stream().map(node -> abstractInfraHandlerMap.get(node.getName())).toArray(EventHandler[]::new);
group.then(thenHandlers);
}
return sirector;
}
作为接入内部RPC、Http接口实现业务处理的项目,在使用过程中要关注调用链路上的资源复用,尤其长链路的调用,要深入考虑内存资源的利用以及对底层服务的压力.
要关注对外服务接口与底层数据接口的响应时差,分析调用逻辑与流程是否合理,是否存在优化项.
多线程并发调用多个平行数据接口时,如何使得各个线程的耗时方差尽可能小?
作者:京东零售 王江波 。
来源:京东云开发者社区 。
最后此篇关于一种接口依赖关系分层方案的文章就讲到这里了,如果你想了解更多关于一种接口依赖关系分层方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试在我的代码库中为我正在编写的游戏服务器更多地使用接口(interface),并了解高级概念以及何时应该使用接口(interface)(我认为)。在我的例子中,我使用它们将我的包相互分离,并使
我有一个名为 Widget 的接口(interface),它在我的整个项目中都在使用。但是,它也用作名为 Widget 的组件的 Prop 。 处理此问题的最佳方法是什么?我应该更改我的 Widget
有一个接口(interface)可以是多个接口(interface)之一 interface a {x:string} interface b {y:string} interface c {z:st
我遇到了一种情况,我需要调用第三方服务来获取一些信息。这些服务对于不同的客户可能会有所不同。我的界面中有一个身份验证功能,如下所示。 interface IServiceProvider { bool
在我的例子中,“RequestHandlerProxy”是一个结构,其字段为接口(interface)“IAdapter”,接口(interface)有可能被调用的方法,该方法的输入为结构“Reque
我有一个接口(interface)Interface1,它已由类A实现,并且设置了一些私有(private)变量值,并且我将类A的对象发送到下一个接受输入作为Interface2的类。那么我怎样才能将
假设我有这样的类和接口(interface)结构: interface IService {} interface IEmailService : IService { Task SendAs
有人知道我在哪里可以找到 XML-RPC 接口(interface)的定义(在 OpenERP 7 中)?我想知道创建或获取对象需要哪些参数和对象属性。每个元素的 XML 示例也将非常有帮助。 最佳答
最近,我一直在阅读有关接口(interface)是抽象的错误概念的文章。一篇这样的帖子是http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstract
如果我有一个由第三方实现的现有 IInterface 后代,并且我想添加辅助例程,Delphi 是否提供了任何简单的方法来实现此目的,而无需手动重定向每个接口(interface)方法?也就是说,给定
我正在尝试将 Article 数组分配给我的 Mongoose 文档,但 Typescript 似乎不喜欢这样,我不知道为什么它显示此警告/错误,表明它不可分配. 我的 Mongoose 模式和接口(
我有两个接口(interface): public interface IController { void doSomething(IEntity thing); } public inte
是否可以创建一个扩展 Serializable 接口(interface)的接口(interface)? 如果是,那么扩展接口(interface)的行为是否会像 Serilizable 接口(int
我试图在两个存储之间创建一个中间层,它从存储 A 中获取数据,将其转换为相应类型的存储 B,然后存储它。由于我需要转换大约 50-100 种类型,我希望使用 map[string]func 并根据 s
我正在处理一个要求,其中我收到一个 JSON 对象,其中包含一个日期值作为字符串。我的任务是将 Date 对象存储在数据库中。 这种东西: {"start_date": "2019-05-29", "
我们的方法的目标是为我们现有的 DAO 和模型类引入接口(interface)。模型类由各种类型的资源 ID 标识,资源 ID 不仅仅是随机数,还带有语义和行为。因此,我们必须用对象而不是原始类型来表
Collection 接口(interface)有多个方法。 List 接口(interface)扩展了 Collection 接口(interface)。它声明与 Collection 接口(int
我有一个 Java 服务器应用程序,它使用 Jackson 使用反射 API 对 DTO 进行一般序列化。例如对于这个 DTO 接口(interface): package com.acme.libr
如果我在 Kotlin 中有一个接口(interface): interface KotlinInterface { val id: String } 我可以这样实现: class MyCla
我知道Java中所有访问修饰符之间的区别。然而,有人问了我一个非常有趣的问题,我很难找到答案:Java 中的 private 接口(interface)和 public 接口(interface)有什
我是一名优秀的程序员,十分优秀!