首先感谢王晓老师的[接口优化的常见方案实战总结]一文总结,恰巧最近在对稳健理财BFF层聚合查询服务优化治理,针对文章内的串行改并行章节进行展开,分享下实践经验,主要涉及原同步改异步的过程、全异步化后衍生的问题以及治理方面的思考与改进。

希望通过分享这些经验,能够对大家的工作有所启发和帮助。如果有任何问题或建议,请随时提出。

一、问题背景

将不同理财产品(如基金、券商、保险、银行理财等)针对不同投放渠道人群进行个性化商品推荐,每个渠道或人群看到的商品或特性数据又各不相同,为方便渠道快速对接,由BFF层统一对所有数据进行聚合下发,因此BFF层聚集依赖了大量底层原子服务,所以主要问题是在依赖大量上游接口的场景下保障TP99、以及可用率。

案例:

以其中比较典型的商品推荐接口为例,需要依赖本地商品池缓存、算法推荐服务、商品基础信息服务、持仓查询服务、人群标签服务、券配置服务,可领用券服务、其他数据服务ServN……等等,其中大部分上游原子接口对单次批量查询支持有限,所以极端情况,单个推品接口单次推荐1-n个推品,每个商品如果要绑定10个动态属性,至少需要发起(1~n)*10次io调用。

改造前的流程和问题:

流程:

问题:

  • 一是逻辑流程强耦合,很多上下游服务强同步依赖;

  • 二是链路较长,其中某个上游服务不稳定时很容易造成整体链路失败。

改造后的流程和实现的目标:

流程:

目标:

  • 改造目标也很明确,就是对现有逻辑改造,尽可能增加弱依赖比例,一是方便异步提前加载,二是弱依赖代表可摘除,为降级操作奠定基础,减少因某个链路抖动影响整体链路失败;

初步改造后的新问题【【重点解决】】:

逻辑上解耦比较简单,无非就是前置参数或冗余加载,本次不展开探讨;

技术上改造前期异步逻辑主要是采用@Async("tpXXX")标注,这也是最快捷实现的方式,但也存在以下几个问题,主要是涉及治理方面:

  1. 随着项目和人员不断迭代,造成@Async注解满天飞;

  2. 不同人员在不熟悉其他模块的情况下,无法界定不同线程池的是否可公用,大多都会采用声明新的线程池,造成线程池资源泛滥;

  3. 部分调用场景不合理造成@Async嵌套过多或注解失效问题;

  4. 降级机制重复代码太多,需要频繁手动声明各种降级开关;

  5. 缺少统一的请求级别的缓存机制,虽然jsf已经提供了一定程度的支持;

  6. 线程池上下文传递问题;

  7. 缺少线程池状态的统一监控报警,无法观测实际运行过程中的每个线程池状态,可能每次都是拍脑袋觉设置线程池参数。

二、整体改造路径

切入点:

鉴于大部分项目都会封装单独的io调用层,比如 com.xx.package.xxx.client,所以以此为切入点进行重点改造治理。

最终目标:

实现、应用简单,对老代码改造友好,尽可能降低改造成本;

  1. 抽象io调用模板,统一io调用层封装规范,标准化io调用需要的增强属性声明并提供默认配置,如所属线程池分配、超时、缓存、熔断、降级等;

  2. 优化@Async调用,所有io异步操作统一收缩至io调用层,在模板层实现回调机制,老代码仅继承模板即可实现异步回调;

  3. 请求级别的缓存实现,默认支持r2m;

  4. 请求级别的熔断降级支持,在上游故障时使服务实现一定程度的自治理;

  5. 线程池集中管理,对上下文自动传递MDC参数提供支持;

  6. 线程池状态自动可视化监控、报警实现;

  7. 支持配置中心动态设置。

具体实现:

1. io调用抽象模板

模板主要作用是进行规范和增强,目前提供两种模板,默认模板、缓存模板,核心思想就是对io操作涉及的大部分行为进行声明,比如当前服务所属线程池分组、请求分组等,由委托组件按照声明的属性进行增强实现,示例如下:

主要是提供代码级别的默认声明,从日常实践看大部分采用开发时的代码级别的配置即可。

2. 委托代理

此委托属于整个执行过程的桥接实现,io封装实现继承抽象模板后,由模板创建委托代理实例,主要用于对io封装进行增强实现,比如调用前、调用后、以及调用失败自动调用声明的降级方法等处理。

可以理解为:模板专注请求行为,委托关注对象行为进行组合增强。

3. 执行器选型

基于前面的实现目标,减少自研成本,调研目前已有框架,如 hystrix、sentinel、resilience4j,由于主要目的是期望支持线程池级别的壁舱模式实现,且hystrix集成度要优于resilience4j,最终选型默认集成hystrix,备选resilience4j, 以此实现线程池的动态创建管理、熔断降级、半连接重试等机制,HystrixCommander实现如下:

4. hystrix 适配 concrete 动态配置

1、继承concrete.PropertiesNotifier, 注册HystrixPropertiesNotifier监听器,缓存配置中心所有以hystrix起始的key配置;

2、实现HystrixDynamicProperties,注册ConcreteHystrixDynamicProperties替换默认实现,最终支持所有的hystrix配置项,具体用法参考hystrix文档。

5. hystrix 线程池上下文传递改造

hystrix已经提供了改造点,主要是对HystrixConcurrencyStrategy#wrapCallable方法重写实现即可,在submit任务前暂存主线程上下文进行传递。

6. hystrix、jsf、spring注册线程池状态多维可视化监控、报警

主要依赖以下三个自定义组件,注册一个状态监控处理器,单独启动一个线程,定期(每秒)收集所有实现数据上报模板的实例,通过指定的通道实现状态数据推送,目前默认使用PFinder上报:

  • ThreadPoolMonitorHandler 定义一个线程状态监控处理器,定期执行上报过程;

  • ThreadPoolEndpointMetrics 定义要上报的数据模板,包括应用实例、线程类型(spring、jsf、hystrix……)、类型线程分组、以及线程池的几个核心参数;

  • AbstractThreadPoolMetricsPublisher 定义监控处理器执行上报时依赖的通道(Micrometer、PFinder、UMP……)。

例如以下是hystrix的状态收集实现,最终可实现基于机房、分组、实例、线程池类型、名称等不同维度的状态监控:

PFinder实际效果:支持不同维度组合查看及报警

7. 提供统一await future工具类

由于大部分调用是基于列表形式的异步结果List<Future>、Map<String,Future>,并且hystrix目前暂不支持返回CompletableFuture,方便统一await,提供工具类:

8. 其他小功能

1、除了sgm traceId支持,同时内置自定义的traceId实现,主要是处理sgm在子线程内打印traceId需要在控制台手动添加监控方法的问题以及提供对部分无sgm环境的链路Id支持,方便日志跟踪;

2、比如针对jsf调用,基于jsf过滤器实现跨应用级别的前后请求id传递支持;

3、默认增加jsf过滤器实现日志打印,同时支持provider、consume的动态日志打印开关,方便线上随时开关jsf日志,不再需要在client层重复logger.isDebugerEnabled();

4、代理层自动上报io调用方法、fallback等信息至ump,方便监控报警。

日常使用示例:

1. 一个最简单的io调用封装

仅增加继承即可支持异步回调,不重写线程池分组时使用默认分组。

2. 一个支持请求级别熔断的io调用封装

默认支持的熔断级别是服务级别,老服务仅需要继承原请求参数,实现FallbackRequest接口即可,可防止因为某一个特殊参数引起的整体接口熔断。

3. 一个支持请求级别缓存、接口级别熔断降级、独立线程池的io调用封装

4. 上层调用,实际效果

1、直接将一个商品列表转换成一个异步属性绑定任务;

2、利用工具类await List<Future>;

3、在上层无感知的状态下,实现线程池的管理、熔断、降级、或缓存逻辑的增强,且可根据pfinder监控的可视化线程池状态,通过concrete实时调整线程池及超时或熔断参数;

4、举例:比如某接口频繁500ms超时,可通过配置直接打开短路返回降级结果,或者调低超时为100ms,快速触发熔断,默认10s内请求总数达到20个,50%失败时打开断路器,每隔5s半链接重试。

三、最后

本篇主要是思考如何依赖现有框架、环境的能力,从代码层面系统化的实现相关治理规范。

最后仍引用王晓老师文章结尾来结束

接口性能问题形成的原因思考我相信很多接口的效率问题不是一朝一夕形成的,在需求迭代的过程中,为了需求快速上线,采取直接累加代码的方式去实现功能,这样会造成以上这些接口性能问题。 变换思路,更高一级思考问题,站在接口设计者的角度去开发需求,会避免很多这样的问题,也是降本增效的一种行之有效的方式。 以上,共勉!

作者:京东科技 刘大朋

来源:京东云开发者社区

BFF层聚合查询服务异步改造及治理实践 | 京东云技术团队的更多相关文章

  1. {django模型层(二)多表操作}一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询、分组查询、F查询和Q查询

    Django基础五之django模型层(二)多表操作 本节目录 一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询.分组查询.F查询和Q查询 六 xxx 七 ...

  2. (21)模型层 -ORM之msql 聚合查询,F和Q(与、或、非查询)、分组查询

    什么是聚合查询,就是使用聚合函数做计算 from django.db.models import Count,Avg,Max,Min   #聚合函数要从模块中导入 from django.db.mod ...

  3. Web框架之Django_05 模型层了解(单表查询、多表查询、聚合查询、分组查询)

    摘要: 单表查询 多表查询 聚合查询 分组查询 一.Django ORM 常用字段和参数: 常用字段:#AutoFieldint自增列,必须填入参数primary_key = True,当model中 ...

  4. Django框架第七篇(模型层)--多表操作:一对多/多对多增删改,跨表查询(基于对象、基于双下划线跨表查询),聚合查询,分组查询,F查询与Q查询

    一.多表操作 一对多字段的增删改(book表和publish表是一对多关系,publish_id字段) 增  create publish_id 传数字   (publish_id是数据库显示的字段名 ...

  5. TYPESDK手游聚合SDK服务端设计思路与架构之三:流程优化之订单保存与通知

    经过前两篇文字的分析与设计,我们已经可以搭建出一个能够支持多游戏多渠道的聚合SDK服务端,但这只是理想化状态下的一个简化模型.如果接入渠道的逻辑都是按照理想化的简化过程来构建,那么对于支付的请求,我们 ...

  6. MVC项目实践,在三层架构下实现SportsStore-09,ASP.NET MVC调用ASP.NET Web API的查询服务

    ASP.NET Web API和WCF都体现了REST软件架构风格.在REST中,把一切数据视为资源,所以也是一种面向资源的架构风格.所有的资源都可以通过URI来唯一标识,通过对资源的HTTP操作(G ...

  7. 聚合查询、分组查询、ORM中如何给表再次添加新的字段、F与Q查询、ORM查询优化、ORM事务操作、ORM常用字段类型、ORM常用字段参数、Ajax、数据编码格式(Content-Type)、ajax携带文件数据

    今日内容 聚合查询 在ORM中支持单独使用聚合函数,需要使用aggregate方法. 聚合函数:Max最大.Min最小.Sum总和.Avg平均.count统计 from django.db.model ...

  8. [转]向facebook学习,通过协程实现mysql查询的异步化

    FROM : 通过协程实现mysql查询的异步化 前言 最近学习了赵海平的演讲,了解到facebook的mysql查询可以进行异步化,从而提高性能.由于facebook实现的比较早,他们不得不对php ...

  9. Ocelot简易教程(四)之请求聚合以及服务发现

    上篇文章给大家讲解了Ocelot的一些特性并对路由进行了详细的介绍,今天呢就大家一起来学习下Ocelot的请求聚合以及服务发现功能.希望能对大家有所帮助. 作者:依乐祝 原文地址:https://ww ...

  10. ES系列九、ES优化聚合查询之深度优先和广度优先

    1.优化聚合查询示例 假设我们现在有一些关于电影的数据集,每条数据里面会有一个数组类型的字段存储表演该电影的所有演员的名字. { "actors" : [ "Fred J ...

随机推荐

  1. [ACM]Uva839-Not So Mobile(树状天平)

    在输入过程中同时进行数据处理,代码简洁,效率较高 #include<iostream> #include<cstdio> using namespace std; bool s ...

  2. 全网最详细中英文ChatGPT-GPT-4示例文档-快速创意生成从0到1快速入门——官网推荐的48种最佳应用场景(附python/node.js/curl命令源代码,小白也能学)

    目录 Introduce 简介 setting 设置 Prompt 提示 Sample response 回复样本 API request 接口请求 python接口请求示例 node.js接口请求示 ...

  3. 【CTF】系统调用号查询表

    32位 #ifndef _ASM_X86_UNISTD_32_H #define _ASM_X86_UNISTD_32_H 1 #define __NR_restart_syscall 0 #defi ...

  4. pysimplegui之使用多线程,避免程序卡死

    这个问题我也遇到过,就是还需要一个while循环的时候,放到gui本身循环会卡死,这时候就需要启动多线程 需要"长时间"的操作 如果您是 Windows 用户,您会在其标题栏中看到 ...

  5. [软件过程/软件生命周期模型]软件过程的工具链&技术链【待续】

    0 宣言:DevOps & RUP统一过程建模 1 项目管理 (需求管理 / 缺陷管理 / ...) 禅道(前身:bugfree) [在线协作] JIRA(项目与事务跟踪工具) 与禅道类同,但 ...

  6. Tkinter库的使用

    from tkinter import *import tkinter as tkfrom tkinter import Tk, Label,ttkfrom PIL import Image, Ima ...

  7. day96:flask:flask-migrate&flask-session&蓝图Blueprint&蓝图的运行机制&基于flask仿照django进行项目架构

    目录 1.flask-migrate 2.flask-session 3.蓝图:Blueprint 4.蓝图的运行机制 5.基于flask仿照django进行项目架构 1.准备工作 2.加载配置文件 ...

  8. Redis 日志showlog 和 管道pileline

    redis日志 slowlog-log-slower-than:指定执行时间超过多少微秒(1秒等于1000000微秒) 的命令请求会被记录到日志上 slowlog-max-len:指定服务器最多保存多 ...

  9. JUC(一)JUC简介与Synchronized和Lock

    1 JUC简介 JUC就是java.util.concurrent的简称,这是一个处理线程的工具包,JDK1.5开始出现的. 进程和线程.管程 进程:系统资源分配的基本单位:它是程序的一次动态执行过程 ...

  10. React Native 开发环境搭建——nodejs安装、yarn安装、JDK安装多个版本、安装Android Studio、配置Android SDK的环境变量

    一.React Native介绍 二.开发环境的搭建 2.1.Node.js安装 Node.js要求14版或更新 https://nodejs.org/en 查看版本: 2.2.yarn安装 Yarn ...