分布式异常重试服务平台 X-RETRY - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
zsg1994
V2EX    程序员

分布式异常重试服务平台 X-RETRY

  •  
  •   zsg1994 2022-07-08 16:48:38 +08:00 1935 次点击
    这是一个创建于 1192 天前的主题,其中的信息可能已经有所发展或是发生改变。

    开源地址 :https://github.com/byteblogs168/x-retry

    简介

    X-RETRY 基于服务治理的思想我们开发了重试治理的功能,支持动态配置,接入方式基本无需入侵业务代码,并使用多种策略结合的方式在链路层面控制重试放大效应,兼顾易用性、灵活性、安全性的分布式异常重试服务平台

    社区

    https://www.byteblogs.com/chat

    官方 QQ 群

    使用过程中有任何问题,或者对项目有什么想法或者建议,可以加入社群,跟群友一起交流讨论

    qq.png

    特性

    1. 管控重试流量,预防重试风暴,及早发现和预警,并且提供流程管理手段
    2. 保证易用性: 业务接入成本小。避免依赖研发人员的技术水平,保障重试的稳定性
    3. 灵活性: 能够动态调整配置,启动 /停止任务,以及终止运行中的重试数据
    4. 操作简单:一分钟上手,支持 WEB 页面对重试数据 CRUD 操作。
    5. 数据大盘: 实时管控系统重试数据。
    6. 多样化退避策略: Cron 、固定间隔、等级触发、随机时间触发
    7. 容器化部署: 服务端支持 docker 容器部署
    8. 高性能调度平台: 支持服务端节点动态扩容和缩容
    9. 多样化重试类型: 支持 ONLY_LOCAL 、ONLY_REMOTE 、LOCAL_REMOTE 多种重试类型
    10. 重试数据管理: 可以做到重试数据不丢失、重试数据一键回放
    11. 支持多样化的告警方式: 邮箱、企业微信、钉钉

    快速入门

    添加依赖

    <dependency> <groupId>com.x.retry</groupId> <artifactId>x-retry-client-starter</artifactId> <version>0.0.4.0</version> </dependency> 

    配置

    添加注解开启 X-RETRY 功能

    @SpringBootApplication @EnableXRetry(group = "example_group") public class ExampleApplication { public static void main(String[] args) { SpringApplication.run(ExampleApplication.class, args); } } 

    为需要重试的方法添加重试注解

    @Retryable(scene = "errorMethodForLocalAndRemote", localTimes = 3, retryStrategy = RetryType.LOCAL_REMOTE) public String errorMethodForLocalAndRemote(String name) { double i = 1 / 0; return "这是一个简单的异常方法"; } 

    Retryable 详解

    属性 类型 必须指定 默认值 描述
    scene String 场景
    include Throwable 包含的异常
    exclude Throwable 排除的异常
    retryStrategy RetryType LOCAL_REMOTE 重试策略
    retryMethod RetryMethod RetryAnnotationMethod 重试处理入口
    bizId BizIdGenerate SimpleBizIdGenerate 自定义业务 id ,默认为 hash(param),传入成员列表,全部拼接取 hash
    bizNo String bizNo spel 表达式
    localTimes int 3 本地重试次数 次数必须大于等于 1
    localInterval int 2 本地重试间隔时间(s)

    配置部署服务端调度平台

    初始化数据库

    数据库脚本位置

    doc/sql/x_retry.sql 

    系统配置

    spring: datasource: name: x_retry url: jdbc:mysql://localhost:3306/x_retry?useSSL=false&characterEncoding=utf8&useUnicode=true username: root password: root type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver hikari: connection-timeout: 30000 minimum-idle: 5 maximum-pool-size: 20 auto-commit: true idle-timeout: 30000 pool-name: x_retry max-lifetime: 1800000 connection-test-query: SELECT 1 resources: static-locations: classpath:admin/ mybatis-plus: mapper-locations: classpath:/mapper/*.xml typeAliasesPackage: com.x.retry.server.persistence.mybatis.po global-config: db-config: field-strategy: NOT_EMPTY capital-mode: false logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true cache-enabled: true x-retry: lastDays: 30 # 拉取重试数据的天数 retryPullPageSize: 100 # 拉取重试数据的每批次的大小 nettyPort: 1788 # 服务端 netty 端口 totalPartition: 32 # 重试和死信表的分区总数 

    ##项目部署

    下载源码部署

    • 下载源码

       https://github.com/byteblogs168/x-retry.git 
    • maven 打包镜像

    maven clean install 
    • 修改配置
    /x-retry-server/src/main/resources/application.yml 
    • 启动
    java -jar x-retry-server.jar 

    docker 部署

    /** * 如需自定义 mysql 等配置,可通过 "-e PARAMS" 指定,参数格式 PARAMS="--key1=value1 --key2=value2" ; * 配置项参考文件:/x-retry-server/src/main/resources/application.yml * 如需自定义 JVM 内存参数 等配置,可通过 "-e JAVA_OPTS" 指定,参数格式 JAVA_OPTS="-Xmx512m" ; */ docker run \ -e PARAMS="--spring.datasource.username=root --spring.datasource.password=123456 --spring.datasource.url=jdbc:mysql://ip:3306/x_retry?useUnicode=true&characterEncoding=UTF-8&autoRecOnnect=true&serverTimezOne=Asia/Shanghai " \ -p 8080:8080 \ -p 1788:1788 \ --name x-retry-server-1 \ -d registry.cn-shanghai.aliyuncs.com/byteblogs/x-retry:{最新版本} 

    如果你已经正确按照系统了,那么你可以输入

    http://localhost:8080 

    会出现登陆页面:

    login.png

    输入用户名: admin, 密码: 123456

    仪表板

    仪表盘直观展示系统的任务量、调度量、在线节点展示等

    总任务量

    统计当前系统总的任务量

    完成

    已经调度成功的异常数据

    运行中

    处于调度中的异常数据

    最大次数

    调度次数超过配置的最大执行次数的异常数据

    总调度量

    展示系统触发调度的总数量

    失败

    包括调度客户端执行失败、调度超时等异常执行的数据

    成功

    调用客户端执行重试成功的数据

    总在线机器

    实时展示当前活跃的客户端与服务端

    dashboard.png

    组配置

    通过新建按钮配置点开配置组、场景、通知界面

    group_list.png

    组配置

    每个系统对应一个组,服务端通过一致性 hash 环来分配当前已启用的 Group 在集群中哪节点上消费

    • 组名称: 名称是数字、字母、下划线组合,最长 64 个字符长度
    • 状态: 开启 /关闭, 通过状态开启或关闭组状态
    • 路由策略: 随机算法、一致性 hash 算法、最近最久未使用算法
    • 描述: 对组进行描述
    • 指定分区: 不指定则系统随机分区,指定则使用指定的分区

    goup_config.png

    场景配置

    场景负责管理收集重试现场的数据,比如 方法名、参数、类等信息; 对照代码 中可以理解为需要重试的方法; 每个业务服务对应 N 个场景值,即系统配置的最小单位。

    • 场景名称: 名称是数字、字母、下划线组合,最长 64 个字符长度
    • 场景状态: 开启 /关闭, 通过状态开启或关闭场合状态
    • 退避策略: 延迟等级、固定时间、CRON 表达式、随机等待
    • 最大重试次数: 重试上限值
    • 描述: 对场景进行描述
    • 操作: 新增-添加场景配置、删除-未提交则删除临时场景配置,已提交则删除数据库中的场景配置

    scene_config.png

    通知配置

    及时告知系统管理人员,系统运行状态,如出现大量重试的数据、或者大量重试失败的数据

    • 通知类型: 钉钉通知、邮箱通知、企业微信通知

    • 通知场景:

      • 重试数量超过阈值: 作用于服务端,重试中的数量到达阈值发送通知
      • 重试失败数量超过阈值: 作用于服务端,达到最大重试次数的数量到达阈值发送通知
      • 客户端上报失败: 作用于客户端,上报数据失败 发送 通知
      • 客户端组件异常: 作用于客户端,重试组件内部异常,发送通知
    • 通知阈值: 到达阈值发送通知

    • 通知地址: 发送通知的地址

    • 描述: 对通知进行描述

    • 操作: 新增-添加场景配置、删除-未提交则删除临时通知配置,已提交则删除数据库中的通知配置

    notify_config.png

    重试列表

    查询当前处理重试中的数据,存在三种状态

    • 重试中: 会一直存在重试列表中
    • 最大重试次数: 重试失败次数到达最大重试次数之后,由清除线程负责迁移至死信队列列表中
    • 重试完成: 重试成功之后,由清除线程负责删除重试完成的数据

    支持的搜索条件:

    • 组名称: 下拉选择相应的组进行精确搜索
    • 场景名称: 下拉选择相应的场景进行精确搜索
    • 业务编号: 根据业务编号精确搜索
    • 业务 id: 根据业务 id 精确搜索

    retry_task_list.png

    重试日志列表

    支持的搜索条件:

    • 组名称: 下拉选择相应的组进行精确搜索
    • 场景名称: 下拉选择相应的场景进行精确搜索
    • 业务编号: 根据业务编号精确搜索
    • 业务 id: 根据业务 id 精确搜索

    retry_log_list.png

    死信队列列表

    支持的搜索条件:

    • 组名称: 下拉选择相应的组进行精确搜索
    • 场景名称: 下拉选择相应的场景进行精确搜索
    • 业务编号: 根据业务编号精确搜索
    • 业务 id: 根据业务 id 精确搜索

    回滚

    死信队列数据迁移至重试任务重,并删除死信队列数据

    retry_task_dead_letter.png

    用户列表

    搜索系统用户信息 支持的搜索条件:

    • 用户名: 模糊搜索用户名

    user_list.png

    新增用户

    为系统新增用户

    • 用户名: 用户名是数字、字母、下划线组合,最长 64 个字符长度

    • 密码: 密码是数字、字母、下划线组合,最长 64 个字符长度

    • 角色:

      • 普通用户: 负责分配的组权限
      • 管理员: 管理所有的 组谦虚
    • 权限: 需要管理的组

    user_add.png

    系统剖析

    客户端与服务端数据交互图

    client_server_data_flow.jpg

    客户端核心能力

    • 负责发现异常,标记事故现场

    • 根据不同阶段进行本地重试和远程重试

    • 失败上报和执行服务端下发的重试指令

    • 避免服务间调用产生重试放大风险

    • 重试流量管控

      • 单机多注解循环引用问题
      • 标记重试流量
      • 调用链超时控制(Deadline Request)
      • 特殊的 status code 限制链路重试

    服务端核心能力

    • 收集上报信息,统一预警
    • 通过组协调器为不同的 POD 分配需要调度的 Group 信息
    • 管理死信队列和重试数据状态以及触发时间
    • 支持配置中心可视化

    系统架构图

    系统架构图-v1.0.jpg

    客户端剖析

    重试流量管控

    单机重试管控

    单机多注解嵌套方法,通过标记重试现场入口,发生异常重试只重试现场入口,防止每个方法都重试, 从而避免了重试风暴

    链路重试管控

    • 特殊的 status code 限制链路重试: 让被调用方有反抗的权利(统一约定一个特殊的 status code 它表示:调用失败,但别重试)
    • 调用链超时控制(Deadline Request): 当剩余时间不够时不再发起重试请求
    • 特殊的 retry flag 保障重试请求不重试: 通请求头传递 retry flag 保障即使发生异常也不重试

    重试流速管控

    通过路由策略和限流措施对每个组的集群进行流量控制

    支持多种退避策略

    • 线性退避: 每次等待固定时间后重试
    • 随机退避: 在一定范围内随机等待一个时间后重试
    • 延迟等级退避: 依据延迟等级, 等待每个延迟等级设置的时间, 延迟等级枚举DelayLevelEnum
    • Cron 表达式退避: 使用 Cron 表达式计算重试触发时间

    客户端功能模块图

    客户端功能模块-v1.0.jpg

    • 启动模块

      • 滑动窗口模块: 监听需要上报的数据
      • Netty 启动器: 启动客户端 Netty 组件,建立与服务端心跳机制
      • 远程配置获取器: 获取最新版本的配置信息
      • 注解扫描模块: 负责扫描添加到方法上的 @Retryable 注解,获取参数信息、类信息、方法路径等;解析注解元数据信息,构建执行器
    • 重试阶段

      • 重试模式

        • 本地重试: 当发生异常时候, 若注解上的配置RetryType.ONLY_LOCAL或者RetryType.LOCAL_REMOTE, 则会触发本地内存重试
        • 远程重试: 本地重试没有成功,若注解上的配置RetryType.ONLY_REMOTE或者RetryType.LOCAL_REMOTE, 则会触发上报服务端重试
      • 执行器

        • 重试组件: 对 guava retry 的深度封装

        • 重试执行器

          • 类反射执行器: 即重试执行原方法
          • 自定义方法执行器: 用户通过实现RetryMethod接口, 即可实现自定义重试, 发生重试时直接重试自定义方法执行器
    • 重试流量管控

      • 单机多注解循环引用问题

        标记重试入口,触发重试时只从标记的重试入口进入

    单机多注解循环引用问题.jpg - 标记重试流量

     > 对于重试的请求,我们在请求头中下发一个特殊的标识(xRetry:boolean), 在 Service A ->Service B ->Service C 的调用链路中,当 Service B 收到 Service A 的请求时会先读取这个 xRetry 判断这个请求是不是重试请求, 如果是,那它调用 Service C 即使失败也不会重试;否则将触发重试 。 同时 Service B 也会把这个 xRetry 下传,它发出的请求也会有这个标志,它的下游也不会再对这个请求重试 

    重试流量标识.jpg

    • 调用链超时控制(Deadline Request)

    DDL 是“ Deadline Request 调用链超时”的简称,我们知道 TCP/IP 协议中的 TTL 用于判断数据包在网络中的时间是否太长而应被丢弃,DDL 与之类似, 它是一种全链路式的调用超时,可以用来判断当前的 RPC 请求是否还需要继续下去。如下图,在 RPC 请求调用链中会带上超时时间, 并且每经过一层就减去该层处理的时间,如果剩下的时间已经小于等于 0 ,则可以不需要再请求下游,直接返回失败即可。DDL.jpg

    - 特殊的 status code 限制链路重试 

    如果每层都配置重试可能导致调用量指数级扩大,这样对底层服务来说压力是非常之大的, 通过对流量的标记 ,用户可以判断是否是重试的流量来判断是否继续处理,我们使用 Google SRE 中提出的内部使用特殊错误码的方式来实现:

    ``` 1 统一约定一个特殊的 status code ,它表示:调用失败,但别重试。 2 任何一级重试失败后,生成该 status code 并返回给上层。 3 上层收到该 status code 后停止对这个下游的重试,并将错误码再传给自己的上层。 ``` 

    这种方式理想情况下只有最下一层发生重试,它的上游收到错误码后都不会重试,但是这种策略依赖于业务方传递错误码, 对业务代码有一定入侵,而且通常业务方的代码差异很大, 调用 RPC 的方式和场景也各不相同,需要业务方配合进行大量改造, 很可能因为漏改等原因导致没有把从下游拿到的错误码传递给上游。

    重试特殊的 statuscode.jpg

    服务端剖析

    分布式调度模块

    • master thread

      1. 扫描所有启用的组,通过客户端协调算法,分配当前节点需要重试的组
      2. 生成 Actor,并扫描当前分配组下面所有待重试的数据
    • 重试分发组件

      • ScanGroupActor

        1. 通过配置的时间范围扫描待重试数据
        2. 通过条件过滤器,过滤出满足条件的重试数据
      • ExecUnitActor

        1. 通过远程调用下发重试指令,
        2. 标记重试流量
      • 结果处理

        1. FailureActor: 处理重试失败数据,累加重试次数
        2. FinishActor: 处理重试成功数据并更新状态为重试成功
    • 算法

      • 客户端轮询算法

        1. 一致性 Hash 算法
        2. LRU 算法
        3. 随机算法
      • 服务端 Rebalance 算法

        1. 一致性 hash 算法
    7 条回复    2022-07-09 16:51:43 +08:00
    XRR
        1
    XRR  
       2022-07-08 17:42:52 +08:00
    mark 一下 这周看看
    wolfie
        2
    wolfie  
       2022-07-08 17:54:01 +08:00
    写过基于 http 的 日志 + 重试。(生产环境正常运行)

    想过加反射支持,考虑 代码迭代(方法改变)、ThreadLocal 等上下文问题就放弃了。
    zsg1994
        3
    zsg1994  
    OP
       2022-07-08 17:56:13 +08:00
    @XRR 欢迎入群讨论 678571433
    zsg1994
        4
    zsg1994  
    OP
       2022-07-08 17:56:28 +08:00
    @wolfie 欢迎入群讨论 678571433
    zsg1994
        5
    zsg1994  
    OP
       2022-07-08 17:56:57 +08:00
    @wolfie 我这个也是反射做的 也有 ThreadLocal
    leipengcheng
        6
    leipengcheng  
       2022-07-09 09:24:38 +08:00
    zsg1994
        7
    zsg1994  
    OP
       2022-07-09 16:51:43 +08:00
    @leipengcheng 说明大家对重试都是有需求的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5496 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 08:30 PVG 16:30 LAX 01:30 JFK 04:30
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86