0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一种接口依赖关系分层方案

OSC开源社区 来源:OSCHINA 社区 2023-06-30 10:25 次阅读

1、背景

到店商详迭代过程中,需要提供的对外能力越来越多,如预约日历、附近门店、为你推荐等。这其中不可避免会出现多个上层能力依赖同一个底层接口的场景。最初采用的方案是对外 API 入口进来后获取对应的能力,并发调用多项能力,由能力层调用对应的数据链路,进行业务处理。

然而,随着接入功能的增多,这种情况导致了底层数据服务的重复调用,如商品配置信息,在一次 API 调用过程中重复调了 3 次,当流量增大或能力项愈多时,对底层服务的压力会成倍增加。

正值 618 大促,各方接口的调用都会大幅度增加。通过梳理接口依赖关系来减少重复调用,对本系统而言,降低了调用数据接口时的线程占用次数,可以有效降级 CPU。对调用方来说,减少了调用次数,可减少调用方的资源消耗,保障底层服务的稳定性。

原始调用方式:

e6fe81fe-1666-11ee-962d-dac502259ad0.jpg

2、优化

基于上述问题,采用底层接口依赖分层调用的方案。梳理接口依赖关系,逐层向上调用,注入数据,如此将同一接口的调用抽取到某层,仅调用一次,即可在整条链路使用。

改进调用方式:

e717736c-1666-11ee-962d-dac502259ad0.jpg

只要分层后即可在每层采用多线程并发的方式调用,因为同一层级中的接口无先后依赖关系。

3、如何分层?

接下来,如何梳理接口层级关系就至关重要。

接口梳理分层流程如下:

e751f6b8-1666-11ee-962d-dac502259ad0.jpg

第一步:构建层级结构 首先获取到能力层依赖项并遍历,然后调用生成数据节点方法。方法流程如下:构建当前节点,检测循环依赖(存在循环依赖会导致栈溢出),获取并遍历节点依赖项,递归生成子节点,存放子节点。

第二步:节点平铺 定义 Map 维护平铺结构,调用平铺方法。方法流程如下:遍历层级结构,判断当前节点是否已存在 map 中,存在时与原节点比较将层级大的节点放入(去除重复项),不存在时直接放入即可。然后处理子节点,递归调用平铺方法,处理所有节点。

第三步:分层(分组排序) 流处理平铺结构,处理层级分组,存储在 TreeMap 中维护自然排序。对应 key 中的数据节点 Set 需用多线程并发调用,以保证链路调用时间

1 首先,定义数据结构用于维护调用链路

Q1:为什么需要定义祖先节点?

A1:为了判断接口是否存在循环依赖。如果接口存在循环依赖而不检测将导致调用栈溢出,故而在调用过程中要避免并检测循环依赖。在遍历子节点过程中,如果发现当前节点的祖先已经包含当前子节点,说明依赖关系出现了环路,即循环依赖,此时抛异常终止后续流程避免栈溢出。

public class DataNode {
    /**
     * 节点名称
     */
    private String name;
    /**
     * 节点层级
     */
    private int level;
    /**
     * 祖先节点
     */
    private List ancestors;
    /**
     * 子节点
     */
    private List children;
}

2 获取能力层的接口依赖,并生成对应的数据节点

Q1:生成节点时如何维护层级?

A1:从能力层依赖开始,层级从 1 递加。每获取一次底层依赖,底层依赖所生成的节点层级即父节点层级 + 1。

/**
 * 构建层级结构
 *
 * @param handlers 接口依赖
 * @return 数据节点集
 */
private List buildLevel(Set handlers) {
    List 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 ancestors, String parent) {
    AbstractInfraHandler abstractInfraHandler = abstractInfraHandlerMap.get(name);
    Set 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;
}

层级结构如下:

e768abec-1666-11ee-962d-dac502259ad0.png

3 数据节点平铺(遍历出所有后代节点)

Q1:如何处理接口依赖过程中的重复项?

A1:遍历所有的子节点,将所有子节点平铺到一层,平铺时如果节点已经存在,比较层级,保留层级大的即可(层级大说明依赖位于更底层,调用时要优先调用)。

/**
 * 层级结构平铺
 *
 * @param dataNodes 数据节点
 * @param dataNodeMap 平铺结构
 */
private void flatteningNodes(List dataNodes, Map 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);
        }
    }
}

平铺结构如下:

e7b9af24-1666-11ee-962d-dac502259ad0.png

4 分层(分组排序)

Q1:如何分层?

A1:节点平铺后已经去重,此时借助 TreeMap 的自然排序特性将节点按照层级分组即可。

/**
 * @param dataNodeMap 平铺结构
 * @return 分层结构
 */
private TreeMap> processLevel(Map dataNodeMap) {
    return dataNodeMap.values().stream().collect(Collectors.groupingBy(DataNode::getLevel, TreeMap::new, Collectors.toSet()))
}
分层如下:

e7f2bfc6-1666-11ee-962d-dac502259ad0.png

1. 根据分层 TreeMap 的 key 倒序即为调用的层级顺序 对应 key 中的数据节点 Set 需用多线程并发调用,以保证链路调用时间

4、分层级调用

梳理出调用关系并分层后,使用并发编排工具调用即可。这里梳理的层级关系,level 越大,表示越优先调用。 这里以京东内部并发编排框架为例,说明调用流程:

/**
 * 构建编排流程
 *
 * @param infraDependencyHandlers 依赖接口
 * @param workerExecutor 并发线程
 * @return 执行数据
 */
public Sirector buildSirector(Set infraDependencyHandlers, ThreadPoolExecutor workerExecutor) {
    Sirector sirector = new Sirector<>(workerExecutor);
    long start = System.currentTimeMillis();
    // 依赖顺序与执行顺序相反
    TreeMap> levelNodes;
    TreeMap> 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;
}

5、 个人思考

作为接入内部 RPC、Http 接口实现业务处理的项目,在使用过程中要关注调用链路上的资源复用,尤其长链路的调用,要深入考虑内存资源的利用以及对底层服务的压力。

要关注对外服务接口与底层数据接口的响应时差,分析调用逻辑与流程是否合理,是否存在优化项。

多线程并发调用多个平行数据接口时,如何使得各个线程的耗时方差尽可能小?




审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • RPC
    RPC
    +关注

    关注

    0

    文章

    111

    浏览量

    11533
  • HTTP接口
    +关注

    关注

    0

    文章

    21

    浏览量

    1790

原文标题:一种接口依赖关系分层方案

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    一种PCIe接口的视频采集解决方案

    一种PCIe接口的视频采集解决方案
    发表于 04-30 06:29

    一种低成本高速USB接口的设计方案

    一种基于DSP平台的低成本高速USB接口方案
    发表于 05-10 07:13

    分享一种智能卡接口的设计方案

    分享一种智能卡接口的设计方案
    发表于 05-27 06:01

    介绍一种差分串行接口方案

    折叠式手机面临哪些问题?一种满足手机高速图像数据传输的差分串行接口方案
    发表于 06-01 06:51

    如何去实现一种高速通信接口的设计?

    一种FPGA与DSP的高速通信接口设计与实现方案
    发表于 06-02 06:07

    一种基于TMS320C6xll接口的图像获取方案

    本文提出了一种基于TMS320C6xll接口的图像获取方案
    发表于 06-03 06:53

    分享一种CH451与AMEG32的接口方案

    分享一种CH451与AMEG32的接口方案
    发表于 06-04 06:06

    基于mcu的一种分层软件架构的相关资料分享

    基于mcu的一种分层软件架构()1、写在前面先来个图:经过了段时间的琢磨与思考。借鉴操作系统的分层原理,也搞出来了
    发表于 11-03 06:46

    一种基于负载均衡的分层副本定位方法

    提出一种基于负载均衡的分层副本定位方法――RepliLoc。从副本信息存储和副本定位计算2负载考虑,将副本定位问题划分为社区层和社区联合层。分别采用应用层广播方式和基于P2
    发表于 04-16 08:49 16次下载

    一种高效、低延时的会议密钥管理方案

    提出一种高效、低延时的会议密钥管理方案——分层分组式密钥管理(DCKM)方案,DCKM解决了其他密钥管理方案中数据在子组间传递时必须经过多次
    发表于 04-23 10:48 21次下载

    一种包含异常传播的类间数据依赖分析方法

    类间数据依赖分析是类间数据流测试的基础。本文通过分析类簇级测试中的异常传播对程序数据依赖的影响,提出一种包括异常结构在内的类间C++程序数据依赖分析方法,根据类间
    发表于 03-01 14:54 9次下载

    一种有效的多关系聚类算法

    一种有效的多关系聚类算法_邓左祥
    发表于 01-03 15:24 0次下载

    一种新的识别和保留模型特征的自适应分层算法

    针对3D打印中已有自适应分层算法不能有效保留模型特征的问题,提出了一种新的识别和保留模型特征的自适应分层算法。首先,扩展了模型特征的定义,引入了模型特征丢失和偏移的概念;然后,提出了一种
    发表于 01-05 10:48 0次下载
    <b class='flag-5'>一种</b>新的识别和保留模型特征的自适应<b class='flag-5'>分层</b>算法

    基于mcu的一种分层软件架构

    基于mcu的一种分层软件架构()1、写在前面先来个图:经过了段时间的琢磨与思考。借鉴操作系统的分层原理,也搞出来了
    发表于 10-28 09:51 13次下载
    基于mcu的<b class='flag-5'>一种</b><b class='flag-5'>分层</b>软件架构

    介绍一种基于分层聚类方法的木马通信行为检测模型

    一种基于分层聚类方法的木马通信行为检测模型
    的头像 发表于 07-30 11:51 1201次阅读
    介绍<b class='flag-5'>一种</b>基于<b class='flag-5'>分层</b>聚类方法的木马通信行为检测模型