API灵活定义+极速驱动:货拉拉星图平台技术架构与优化实践

引言

随着货拉拉的持续发展,业务范围不断扩大且逐步趋向细分,作为一线服务渠道,客服系统需要第一时间为司用提供咨询解答、投诉处理、进度跟踪及反馈收集等各项支持,为了更好地满足司用需求,客服系统需要对接大部分业务并作相应处置,为了助力业务发展、降低成本、提升效率,客服系统也在不断迭代升级,逐步向模块化和配置化的架构演进。这样不仅实现了业务的快速对接,也为未来的维护和扩展提供了坚实的基础。

平台概述

项目背景

星图平台是什么?它能做什么?带着这样的疑问,我们来聊一聊。

星图平台是一个全方位的"接口管家"------不仅支持所有HTTP/SOA接口的配置、调试和调用,还能自定义流程编排,对请求参数、请求头和响应数据进行二次加工,真正实现了从"套路"到"花样"的一站式管理。

在实际场景中,业务主要通过API接口对接,为了实现对接配置化,需要建立一个API接口管理平台,方便不同领域的配置人员在无需反复对接接口开发的情况下,选择所需的接口并进行数据绑定,从而快速完成业务对接,这样不仅实现了接口的集中管理与复用,也大大降低了维护成本,有助于提升整体的运营效率。星图平台应运而生,通过提供通用的接口配置能力,以及灵活自定义的辅助手段,可动态覆盖几乎所有业务接口调用场景。

核心功能

接口管理

  • 全域覆盖:支持公司内部所有HTTP/SOA接口配置接入,无须担心遗漏,实现了快捷关联导入,操作更便捷。
  • 版本管理:接口支持多版本管理,调用权限和引用版本都由自己把控,安全又有序。
  • 函数管理:内置Groovy脚本编辑功能(在白名单范围内),可以随心所欲地自定义函数,比如单位转换、数据提取,是用于处理请求和响应的"妙招"。
  • 字段衍生:使用数据字典或函数方式,对接口已有的字段进行异构生成新的结果字段,特别适合针对某些字段做解析处理。
  • 租户隔离:不同业务线之间数据互不干扰,保证数据安全性。
| 自定义函数示例 |

流程编排

  • 流程自定义:通过流程编排方式,组装一个或多个接口结果,或者对接口结果二次处理后,再作为参数传入下一个业务接口,甚至无需调用业务接口,单纯使用函数处理并返回结果。
  • 并行处理:流程支持多节点并行处理,再将结果进行合并,提高整体效率。
  • 黑盒封装:流程对外表现为普通接口,上游无需感知具体实现逻辑,就跟普通接口调用一样得心应手。
  • 流程嵌套:已发布的流程接口可以被复用,和使用普通接口无异。
| 流程接口配置示例 |
接口调用
  • 简化对接:提供简洁的公司内部标准协议接口,能快捷完成配置对接。
  • 统一调用:无论是普通接口还是流程接口,都是使用统一的调用入口,方便业务维护管理。
排障支持
  • 全链路调用追溯:借助TraceId可追踪历史调用情况,查看请求参数、响应结果等信息,便于问题排查。
  • 统计看板:业务租户独立看板,提供单个接口、不同版本和时间范围等纬度统计,一目了然,便于业务分析。
| 流程接口调用流水示例 |
| 产品对比 |

架构设计

良好的架构是系统稳定、高效运转的基石。它不仅为后续的开发迭代和功能扩展提供了坚实基础,减少了重复开发与维护的成本,还能灵活应对业务变化,实现性能的持续优化。较好的架构设计能让开发像玩积木一样轻松灵活,还能帮我们节省后续维护和升级的时间,保证性能不会崩掉,扩展也像拼积木一样简单。

| 整体架构图 |

设计原则

模块化

将系统划分多个模块,减少各个模块之间的强依赖,确保运行稳定性。

  • 配置模块:独立部署的配置服务,支持在业务高峰期间快速新增配置并进行调试,为运营提供更灵活的配置空间。
  • 运行模块:业务的正常运转取决于稳定高效的运行调用,通过拆分部署运行服务,尽量降低其他模块对运行时的干扰,确保接口持续高效调用。
  • 执行流水:执行流水写入及查看要求实时性并不高,通过异步消息解耦接口调用和流水落盘,尽可能提升运行效率。

轻量化

为实现系统更高效运行,我们遵循轻量化设计原则,不仅提升运行性能,也为未来的优化和扩展留出了空间。

  • 核心功能优先:只保留参数校验、预处理、数据组装及接口请求等关键环节,避免冗余,确保系统简洁高效。
  • 自主研发的流程引擎:采用自主研发的轻量级流程引擎,相较市面上的成熟方案更简洁,自研引擎支持高并发,完美嵌合星图平台流程接口,有效满足复杂流程的高效运行需求。
| 流程引擎运行时(UML)|

可扩展

底层框架采用插件化的设计,可进行灵活扩展,通过实现类似SPI的自动注册机制,底层引擎提供标准的Interface,并完成默认实现,业务通过继承等方式完成扩展,其设计主要包含以下组件:

监听器:服务启动时扫描所有注册的实现,通过业务线和业务类型标识归类。

执行器:定义实现规范,完成动作执行,提供前置预处理和后置处理等钩子,方便业务扩展自定义实现。

执行引擎:对外统一暴露执行器入口,便于直接调用,无需感知内部寻址逻辑。

| 自动注册机制(UML)|

流程引擎通过添加不同的节点类型实现类,完成自定义扩展,并且自动注册可向上延伸,应用层(如星图)通过追加实现,完成更上层的自定义业务,从而覆盖复杂业务场景,每个实现相互解耦,提升可读性。

插件化的设计不仅提高了系统可扩展性,而且有效降低了后续维护升级成本。

难点与挑战

在系统架构设计期间,我们无法预见可能会面临的所有问题,一个好的、稳定运行的系统,也不是一蹴而就,星图平台运行初期,我们同样面临着许许多多的难题。

  • 数据库压力大:高并发的请求,特别是流程接口,将产生大量的运行数据,并发写入造成数据库压力过大。
  • CPU居高不下:业务高峰时段,CPU上涨明显,造成服务负载较高,不得不通过升配和扩容手段缓解。
  • 接口RT慢:服务整体RT比较高,个别流程接口RT甚至达到5s,已经是在不可用边缘。
  • 性能抖动:服务性能呈不稳定趋势,偶现会出现RT突刺,存在明显波动。
  • 稳定性考量:流程接口节点长耗时,以及引入Groovy等,给系统带来了不确定性,如何有效限制灵活自定义配置可能带来的隐患,同样需要我们关注。

针对以上存在的问题,具体采取了哪些措施,我们接下来将会展开讲述。

性能优化剖析

为了确保系统稳定高效运行,针对已经识别或者可能存在的问题,我们做了大量优化工作,具体有哪些,我们一起来看看。

1. 数据存储

| 数据流图 |

1.1 异步存储

为便于复杂条件的高效检索,我们采用Elasticsearch存储运行时接口的调用记录,而流程运行的热数据则存储在MySQL中,MySQL支持高并发读写,性能良好。但由于写入操作需要依赖查询或数据修正,存在一定性能损耗,且由于对数据实时性要求不高,可以通过消息队列(MQ)实现异步落盘,提升整体性能。针对MQ可能的性能瓶颈,我们通过扩展分区等手段进行优化。

1.2 数据压缩

通过异步存储的方式,确实提升了接口请求RT,但随之引起了另外的问题,MySQL出现了性能瓶颈,存在大量的锁等待,这是由并发更新同一行数据导致,这种场景多发生在流程节点执行完成后,节点数据被立即写入MQ。然而,下一个节点可能会再次更新这些数据,导致重复写入,出现锁等待。

为解决此问题,我们实施了以下优化策略:

  • 数据合并:将单次请求流程中所有节点产生的数据进行聚合,待流程全部完成后统一发送,减少重复操作。
  • 消息分割:合并后的数据有时会过大,可能影响MQ性能。为此,我们将大消息拆分成多条较小的消息(如大小1MB),确保消息的稳定传输。

| MySQL锁等待 |

以上操作有效解决MySQL并发更新问题,并且不会使MySQL压力过大,而且还降低了MQ队列负载。

2. 缓存策略

数据缓存是常用的优化手段,通过缓存运行时需要的数据,减少直接查询DB的操作,像星图这样的IO密集型的场景,使用缓存后性能可提升至少50%

2.1 分布式缓存

我们采用 Redis 作为主要的分布式缓存方案。Redis具有高性能、高可用,以及丰富的数据结构,带来的优势包括:

  • 大容量:相比本地缓存受限于单机存储,分布式存储容量几乎无限扩展。
  • 数据共享:多个节点或不同服务间可以共享缓存数据,提升资源利用率。
  • 数据一致性:通过Redis的同步机制,保证较低延迟下的数据一致性。

2.2 缓存降级

即使分布式缓存能确保高可用,也不可避免会发生故障。所以需要做好缓存降级策略,简单是方式是当Redis不可用时,直接降级访问MySQL,同时,做好相关的接口限流和熔断措施,确保服务在故障情况下仍能保持一定的可用性,避免完全不可用。

2.3 内存缓存

使用分布式缓存带来了性能提升,但仍存在一定的耗时(平均3-4ms,包括读写、中间件和网络时间消耗)。为进一步提升性能,权衡利弊后,我们引入了本地内存缓存。

  • 内存策略

合理设置最大容量和失效时间,避免因缓存过大导致OOM,或者增大GC压力。如星图接口的定义可设置长时间缓存,防止频繁缓存失效并降级,而接口请求运行时场景,自动失效时间会设置比较短的时间(比如1min),便于资源及时回收。

  • 缓存预热

当服务机器启动后,内存缓存数据是空的,此时接收请求会出现缓存降级,从而导致首批次请求RT上涨,因此在服务完成启动前,高优先级接口预加载缓存,而其他近期存在请求且调用量少的的接口,可以使用异步任务的方式,逐步完成预热。

  • 跨节点同步刷新

配置变更发布后,如何主动刷新各节点缓存也是一个问题,我们通过从注册中心获取所有注册节点,将缓存刷新请求扩散到所有节点,主动完成缓存刷新。

2.4 缓存存储优化

在实际应用场景中,通常使用JSON格式缓存数据,如果读写并发量较大,且对象结构复杂,JSON序列化/反序列化将会带来一定的性能损耗,为此,我们采用了以下优化方案:

  • 高效存取

一般内存缓存的场景,要求内容可读性没那么强,所以可以考虑使用二进制的格式处理缓存数据,以下可以比较直观地对比几种常见的数据处理方式的差异:

综合考虑,我们升级使用Protostuff,加快了缓存读写效率,相比JSON格式,序列化性能提升一倍 ,占用空间节省了1/2左右,而且对开发比较友好。

  • 数据瘦身

星图运行时产生的数据,大部分都会落库,通过对缓存字段做减法,只保留必要的属性字段,减少多余的层级和结构,降低数据结构复杂度,也能对缓存存储性能提升有一定帮助。

3. 并发模型

3.1 串行改并行
  • 并行节点:当多个节点的入参相互不依赖时,我们提供将这些节点改为并行处理的模式,使用CompletableFuture并发处理多个节点,在所有节点完成后,将结果传递给下游节点,提高整体处理效率。

|效果演示|

  • 网关条件组并发处理:当网关节点配置了多个条件组时,系统会自动启用多线程并发处理,进一步提升执行速度。

3.2 资源复用

在Java应用中,随处都有资源复用的身影,数据库连接池、HttpClient连接池、线程池等,资源复用节省了重复初始化的消耗,合理调优这些资源配置,可以达到更优的性能效果。

  • 精细化线程池

根据不同业务场景,分配专属的线程池,这样可以隔离不同业务模块的线程任务,避免资源争抢,同时便于针对性调优,最大程度利用多核处理资源。

  • 线程池调优

星图流程接口核心执行线程池,会配置较大核心线程数(结合机器核数),保证响应速度,而函数执行线程池非核心且允许延迟,可调大最大线程数及队列。值得注意的是,不合理的线程池配置,可能会引发严重后果,比如将所有队列核心线程数、最大线程数、队列数设置非常大时,高并发场景下,会导致线程任务持续堆积,最终导致整体服务性能严重下降。线程池优化可配合相应的监控,通过持续观察线程执行状态、任务处理效率以及线程池队列堆积情况,动态调整配置并最终达到优化平衡。

| 线程池监控 |

线程状态

线程任务

4. 代码优化

经过前期的优化措施,系统整体运行效率已大幅提升。为追求更高的性能,我们需要从细粒度入手,重新审视系统代码编写,分析潜在的性能瓶颈,通过优化提升系统的运行效率。

4.1 代码规范与优化的重要性

良好的编码规范对性能、可维护性和可靠性具有直接影响。遵循规范的编码习惯不仅能够减少潜在的性能损耗,还能提升团队协作效率。例如,星图通过将网关节点的条件组变量全量统一预处理,透传至不同条件组,避免每个条件组的各个条件项都单独进行数据查询,即使数据都是从内存缓存读取,频繁的内存读写仍可能造成性能瓶颈。星图做了许多类似的优化,减少了不必要的I/O操作,并保持代码简洁、清晰,有助于后续维护与优化。

4.2 代码性能分析

在多人协作或代码复杂度较高的项目中,手动识别性能瓶颈困难重重,我们推荐使用专业的性能分析工具,如火焰图(Flame Graph)或强大的服务监控与诊断工具Arthas,通过trace命令追踪方法调用路径,输出各节点耗时,从细节上定位性能热点,为下一步优化提供科学依据。

4.3 减少耗时代码操作

现在主流的编程语言生态都比较好,我们可以从外部获取各种各样的第三方工具类,使用起来也比较方便,可以提升代码编写效率,但我们需要谨慎些,有时不起眼的第三方库方法可能会造成严重后果,例如下面这个例子:

scss 复制代码
// 获取执行器
public LinkedList<AbsAutoRegisterProcessor> getProcessors(String bizScope, String bizType) {
    ProcessorBO processorBO = ProcessorBO.builder().bizScope(bizScope).bizType(bizType).build();
    ValidationUtils.validateThrowException(processorBO);
    return getAbsAutoRegisterProcessors(processorBO);
}
typescript 复制代码
// ValidationUtils工具类方法
public static void validateThrowException(@Valid Object object) throws ServiceException {
    Set<ConstraintViolation<@Valid Object>> validateSet = Validation.buildDefaultValidatorFactory().getValidator().validate(object, new Class[0]);
    if (!CollectionUtils.isEmpty(validateSet)) {
        String messages = validateSet.stream().map(ConstraintViolation::getMessage).reduce((m1, m2) -> m1 + ";" + m2).orElse("参数输入有误!");
        throw new ServiceException(messages);
    }
}

这是一个根据指定业务类型获取执行器的方法,如果我们主动分析代码,直观判断ValidationUtils.validateThrowException不会成为瓶颈,主要耗时应该在getAbsAutoRegisterProcessors方法上。

然而,通过trace追踪后发现,主要耗时居然花在ValidationUtils.validateThrowException上,并且平均耗时10ms以上,作为基础工具类,该方法会被频繁调用,最终会因占用大量资源导致服务性能严重下降。深入代码分析后,确认元凶是该方法每次调用都需要初始化验证器工厂,该过程涉及实例初始化、配置解析以及资源初始化等,非常损耗资源,优化的方案是只完成一次工厂初始化并复用,这一优化大幅提升了执行效率(见下图),性能提升达到了微秒级别。

5. JVM调优

除了以上的程序优化,硬件优化暂时不做讨论,其实还有一个比较重要的,就是JVM启动参数配置,JVM调优应该是个老生常谈的话题了,需要结合实际场景完成参数配置,使效果达到更优,在这里我们针对星图平台聊下几个场景。

5.1 GC耗时突刺

在服务运行过程中,偶尔会出现GC耗时过长,通过调整-XX:MaxGCPauseMillis=50ms,可缓解GC突刺的情况,同时我们也会关注调整最长暂停时间可能引起频繁Full GC等,这是一个持续优化的过程。

5.2 Humongous Allocation

服务还会偶现G1 Humongous Allocation的情况,导致个别请求RT上涨,通过日志分析,主要原因是由于接口请求后返回大量数据,导致创建大对象,鉴于业务上确实存在返回大量数据的场景,无法对数据进行分页等处理,我们通过调大-Xmx和-XX:G1HeapRegionSize,有效降低出现Humongous Allocation的概率。

JVM的配置调优五花八门,需要结合实际业务场景,权衡利弊,必要时再做适当的调整,持续观察,最终达到相对较优的配置平衡。

6. 稳定性设计

6.1 节点运行限制

为了防止流程引擎因异常情况(如上层扩展的慢操作或非安全编程引发的死循环)导致单节点运行时间过长,影响整体服务性能,我们针对每个节点设置时间上限(比如不允许超过3s),同时监控节点耗时,并设立预警机制,确保及时发现异常并进行调整。

6.2 Groovy脚本管控

引入Groovy脚本极大提升了系统配置运行的灵活性,但也带来了隐患,为确保系统稳定,我们采取了多项措施:

  • 依赖&操作白名单:通过限制脚本可访问的类和方法,禁用IO读写等操作,降低安全风险和性能影响。
  • 沙箱运行:采用沙箱机制,将脚本限制在受控环境中执行,通过限制访问范围、资源使用和执行时间,确保脚本安全、稳定运行。

优化总结

通过完成以上优化手段,星图平台从最开始基本无法提供有效的服务,到目前稳定高效地运行,服务性能得到质的提升,服务机器数减少一半 ,支撑更高请求处理量,整体RT下降约2/3,并且服务状况明显更稳定健康。

| CPU使用情况 |

| GC平均耗时与异常情况 |

| JVM堆内存使用情况 |

其中具有代表性的一个业务流程接口,配置节点200+,单次执行节点50+,节点请求业务接口数10+,从原本请求耗时5s以上,目前稳定在500ms左右,性能提升了近10倍

| 性能优化方法导向 |

项目收益

星图平台已成功接入超过100个业务方服务 ,业务接口400+ ,自定义流程接口70+ ,日均调用量300w+

值得一提的是,星图台已稳定运行两年,其系统稳定性达到了"4个9"(即99.99%),为业务的连续性提供了坚实保障。这一切助力于业务发展、成本降低及效率提升。

业务收益

通过星图平台对接实现的业务场景有很多,我们简单看下几个典型业务案例。

研发效能

未来与展望

结语

星图平台的诞生源于对"效率 "与"性能"的极致追求。通过统一接口管理、轻量化架构设计与持续性能优化,我们不仅解决了接口碎片化、流程低效的痛点,更构建了一套可扩展、高可用的技术基座。技术为业务服务,而优秀的架构是这一目标的基石。星图平台将坚持"灵活而不失稳健,极速而兼顾沉淀"的设计哲学,与团队共同探索更高效、更优雅的解决方案。

让技术驱动业务,让创新没有边界。

相关推荐
程序员爱钓鱼2 小时前
Go同步原语与数据竞争:原子操作(atomic)
后端·面试·go
天天摸鱼的java工程师2 小时前
Kafka是如何保证消息队列中的消息不丢失、不重复?
java·后端·kafka
天天摸鱼的java工程师2 小时前
SpringBoot 自动配置原理?@EnableAutoConfiguration 是如何工作的?
java·后端
郭尘帅6662 小时前
Spring依赖注入的四种方式(面)
java·后端·spring
风象南2 小时前
SpringBoot防重放攻击的5种实现方案
java·spring boot·后端
[email protected]2 小时前
Asp.Net Core SignalR导入数据
前端·后端·asp.net·.netcore
callJJ3 小时前
从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(1)
java·开发语言·spring boot·后端·spring·restful·ioc di
编程乐学(Arfan开发工程师)8 小时前
56、原生组件注入-原生注解与Spring方式注入
java·前端·后端·spring·tensorflow·bug·lua
Elcker10 小时前
Springboot+idea热更新
spring boot·后端·intellij-idea