diff --git a/README.md b/README.md
index 226ad5ab8..74b8b9536 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ https://www.byteblogs.com/chat
com.x.retry
x-retry-client-starter
- 0.0.2.0
+ 0.0.4.0
```
@@ -291,3 +291,144 @@ http://localhost:8080
- 管理员: 管理所有的 组谦虚
- 权限: 需要管理的组

+
+### 系统剖析
+#### 客户端与服务端数据交互图
+
+
+> 客户端核心能力
+- 负责发现异常,标记事故现场
+- 根据不同阶段进行本地重试和远程重试
+- 失败上报和执行服务端下发的重试指令
+- 避免服务间调用产生重试放大风险
+- 重试流量管控
+ - 单机多注解循环引用问题
+ - 标记重试流量
+ - 调用链超时控制(Deadline Request)
+ - 特殊的 status code 限制链路重试
+
+> 服务端核心能力
+- 收集上报信息,统一预警
+- 通过组协调器为不同的POD分配需要调度的Group信息
+- 管理死信队列和重试数据状态以及触发时间
+- 支持配置中心可视化
+
+#### 系统架构图
+
+
+### 客户端剖析
+#### 重试流量管控
+> 单机重试管控
+
+单机多注解嵌套方法,通过标记重试现场入口,发生异常重试只重试现场入口,防止每个方法都重试, 从而避免了重试风暴
+
+> 链路重试管控
+
+- 特殊的status code限制链路重试: 让被调用方有反抗的权利(统一约定一个特殊的 status code
+它表示:调用失败,但别重试)
+- 调用链超时控制(Deadline Request): 当剩余时间不够时不再发起重试请求
+- 特殊的retry flag 保障重试请求不重试: 通请求头传递retry flag 保障即使发生异常也不重试
+
+> 重试流速管控
+
+通过路由策略和限流措施对每个组的集群进行流量控制
+
+#### 支持多种退避策略
+- 线性退避: 每次等待固定时间后重试
+- 随机退避: 在一定范围内随机等待一个时间后重试
+- 延迟等级退避: 依据延迟等级, 等待每个延迟等级设置的时间, 延迟等级枚举 `DelayLevelEnum`
+- Cron表达式退避: 使用Cron表达式计算重试触发时间
+
+#### 客户端功能模块图
+
+
+- 启动模块
+ - 滑动窗口模块: 监听需要上报的数据
+ - Netty启动器: 启动客户端Netty组件,建立与服务端心跳机制
+ - 远程配置获取器: 获取最新版本的配置信息
+ - 注解扫描模块: 负责扫描添加到方法上的@Retryable注解,获取参数信息、类信息、方法路径等;解析注解元数据信息,构建执行器
+
+- 重试阶段
+ - 重试模式
+ - 本地重试: 当发生异常时候, 若注解上的配置`RetryType.ONLY_LOCAL`或者`RetryType.LOCAL_REMOTE`, 则会触发本地内存重试
+ - 远程重试: 本地重试没有成功,若注解上的配置`RetryType.ONLY_REMOTE`或者`RetryType.LOCAL_REMOTE`, 则会触发上报服务端重试
+
+ - 执行器
+ - 重试组件: 对guava retry 的深度封装
+ - 重试执行器
+ - 类反射执行器: 即重试执行原方法
+ - 自定义方法执行器: 用户通过实现`RetryMethod`接口, 即可实现自定义重试, 发生重试时直接重试自定义方法执行器
+- 重试流量管控
+ - 单机多注解循环引用问题
+ > 标记重试入口,触发重试时只从标记的重试入口进入
+
+ 
+
+ - 标记重试流量
+ > 对于重试的请求,我们在请求头中下发一个特殊的标识(xRetry:boolean),
+ 在 Service A ->Service B ->Service C 的调用链路中,当Service B 收到Service A 的请求时会先读取这个 xRetry 判断这个请求是不是重试请求,
+ 如果是,那它调用Service C 即使失败也不会重试;否则将触发重试 。
+ 同时Service B 也会把这个 xRetry 下传,它发出的请求也会有这个标志,它的下游也不会再对这个请求重试
+
+ 
+
+ - 调用链超时控制(Deadline Request)
+ > DDL 是“ Deadline Request 调用链超时”的简称,我们知道 TCP/IP 协议中的 TTL 用于判断数据包在网络中的时间是否太长而应被丢弃,DDL 与之类似,
+ 它是一种全链路式的调用超时,可以用来判断当前的 RPC 请求是否还需要继续下去。如下图,在 RPC 请求调用链中会带上超时时间,
+ 并且每经过一层就减去该层处理的时间,如果剩下的时间已经小于等于 0 ,则可以不需要再请求下游,直接返回失败即可。
+ 
+
+ - 特殊的 status code 限制链路重试
+ > 如果每层都配置重试可能导致调用量指数级扩大,这样对底层服务来说压力是非常之大的, 通过对流量的标记
+ ,用户可以判断是否是重试的流量来判断是否继续处理,我们使用 Google SRE 中提出的内部使用特殊错误码的方式来实现:
+
+ 1 统一约定一个特殊的 status code ,它表示:调用失败,但别重试。
+ 2 任何一级重试失败后,生成该 status code 并返回给上层。
+ 3 上层收到该 status code 后停止对这个下游的重试,并将错误码再传给自己的上层。
+
+ > 这种方式理想情况下只有最下一层发生重试,它的上游收到错误码后都不会重试,但是这种策略依赖于业务方传递错误码,
+ 对业务代码有一定入侵,而且通常业务方的代码差异很大, 调用 RPC 的方式和场景也各不相同,需要业务方配合进行大量改造,
+ 很可能因为漏改等原因导致没有把从下游拿到的错误码传递给上游。
+
+ 
+
+### 服务端剖析
+#### 分布式调度模块
+
+- master thread
+ 1. 扫描所有启用的组,通过客户端协调算法,分配当前节点需要重试的组
+ 2. 生成Actor,并扫描当前分配组下面所有待重试的数据
+
+- 重试分发组件
+ - ScanGroupActor
+ 1. 通过配置的时间范围扫描待重试数据
+ 2. 通过条件过滤器,过滤出满足条件的重试数据
+
+ - ExecUnitActor
+ 1. 通过远程调用下发重试指令,
+ 2. 标记重试流量
+
+ - 结果处理
+ 1. FailureActor: 处理重试失败数据,累加重试次数
+ 2. FinishActor: 处理重试成功数据并更新状态为重试成功
+
+- 算法
+ - 客户端轮询算法
+ 1. 一致性Hash算法
+ 2. LRU算法
+ 3. 随机算法
+ - 服务端Rebalance算法
+ 1. 一致性hash 算法
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/images/DDL.jpg b/doc/images/DDL.jpg
new file mode 100644
index 000000000..356301565
Binary files /dev/null and b/doc/images/DDL.jpg differ
diff --git a/doc/images/client_server_data_flow.jpg b/doc/images/client_server_data_flow.jpg
new file mode 100644
index 000000000..121f50fec
Binary files /dev/null and b/doc/images/client_server_data_flow.jpg differ
diff --git a/doc/images/单机多注解循环引用问题.jpg b/doc/images/单机多注解循环引用问题.jpg
new file mode 100644
index 000000000..bddac1baf
Binary files /dev/null and b/doc/images/单机多注解循环引用问题.jpg differ
diff --git a/doc/images/客户端功能模块-v1.0.jpg b/doc/images/客户端功能模块-v1.0.jpg
new file mode 100644
index 000000000..6889dddc0
Binary files /dev/null and b/doc/images/客户端功能模块-v1.0.jpg differ
diff --git a/doc/images/系统架构图-v1.0.jpg b/doc/images/系统架构图-v1.0.jpg
new file mode 100644
index 000000000..e8a2445c7
Binary files /dev/null and b/doc/images/系统架构图-v1.0.jpg differ
diff --git a/doc/images/重试流量标识.jpg b/doc/images/重试流量标识.jpg
new file mode 100644
index 000000000..849ca28a3
Binary files /dev/null and b/doc/images/重试流量标识.jpg differ
diff --git a/doc/images/重试特殊的statuscode.jpg b/doc/images/重试特殊的statuscode.jpg
new file mode 100644
index 000000000..2192a2cc0
Binary files /dev/null and b/doc/images/重试特殊的statuscode.jpg differ
diff --git a/doc/sql/x_retry.sql b/doc/sql/x_retry.sql
index d71b56716..636a552a3 100644
--- a/doc/sql/x_retry.sql
+++ b/doc/sql/x_retry.sql
@@ -19,7 +19,7 @@ CREATE TABLE `notify_config`
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`notify_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '通知类型 1、钉钉 2、邮件 3、企业微信',
- `notify_attribute` varchar(512) NOT NULL COMMENT '通知地址',
+ `notify_attribute` varchar(512) NOT NULL COMMENT '配置属性',
`notify_threshold` int(11) NOT NULL DEFAULT '0' COMMENT '通知阈值',
`notify_scene` tinyint(4) NOT NULL DEFAULT '0' COMMENT '通知场景',
`description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述',
@@ -101,6 +101,7 @@ CREATE TABLE `scene_config`
`max_retry_count` int(11) NOT NULL DEFAULT '5' COMMENT '最大重试次数',
`back_off` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1、默认等级 2、固定间隔时间 3、CRON 表达式',
`trigger_interval` varchar(16) NOT NULL DEFAULT '' COMMENT '间隔时长',
+ `deadline_request` bigint(20) unsigned NOT NULL DEFAULT '60000' COMMENT 'Deadline Request 调用链超时 单位毫秒',
`description` varchar(256) NOT NULL DEFAULT '' COMMENT '描述',
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
@@ -151,7 +152,8 @@ CREATE TABLE `system_user`
UNIQUE KEY `uk_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';
-INSERT INTO x_retry.system_user (username, password, role) VALUES ('admin', 'cdf4a007e2b02a0c49fc9b7ccfbb8a10c644f635e1765dcf2a7ab794ddc7edac', 2);
+INSERT INTO x_retry.system_user (username, password, role)
+VALUES ('admin', 'cdf4a007e2b02a0c49fc9b7ccfbb8a10c644f635e1765dcf2a7ab794ddc7edac', 2);
CREATE TABLE `system_user_permission`
(
diff --git a/frontend/src/views/config/basicConfigForm/SceneList.vue b/frontend/src/views/config/basicConfigForm/SceneList.vue
index 42ab1f514..109590d73 100644
--- a/frontend/src/views/config/basicConfigForm/SceneList.vue
+++ b/frontend/src/views/config/basicConfigForm/SceneList.vue
@@ -75,6 +75,17 @@
@change="value => handleChange(value, record.key, 'maxRetryCount')"/>
{{ text }}
+
+ handleChange(value, record.key, 'deadlineRequest')"/>
+ {{ text }}(毫秒)
+
handleChange(e.target.value, record.key, 'triggerInterval')"
/>
- {{ text }}
+ {{ text }}(秒)
@@ -138,7 +149,7 @@ export default {
title: '场景状态',
dataIndex: 'sceneStatus',
key: 'sceneStatus',
- width: '12%',
+ width: '10%',
scopedSlots: { customRender: 'sceneStatus' }
},
{
@@ -155,18 +166,25 @@ export default {
width: '12%',
scopedSlots: { customRender: 'maxRetryCount' }
},
+ {
+ title: '调用链超时时间',
+ dataIndex: 'deadlineRequest',
+ key: 'deadlineRequest',
+ width: '15%',
+ scopedSlots: { customRender: 'deadlineRequest' }
+ },
{
title: '间隔时间',
dataIndex: 'triggerInterval',
key: 'triggerInterval',
- width: '12%',
+ width: '10%',
scopedSlots: { customRender: 'triggerInterval' }
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
- width: '25%',
+ width: '15%',
scopedSlots: { customRender: 'description' }
},
{
@@ -237,7 +255,7 @@ export default {
this.data = []
res.data.map(record => {
this.loading = false
- const { id, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description } = record
+ const { id, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description, deadlineRequest } = record
this.data.push({
key: id,
sceneName: sceneName,
@@ -246,6 +264,7 @@ export default {
backOff: backOff.toString(),
triggerInterval: triggerInterval,
description: description,
+ deadlineRequest: deadlineRequest,
editable: false,
isNew: false
})
@@ -262,7 +281,7 @@ export default {
},
remove (delKey) {
const delData = this.data.find(item => item.key === delKey)
- const { key, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description } = delData
+ const { key, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description, deadlineRequest } = delData
this.formData.push({
key: key,
sceneName: sceneName,
@@ -270,6 +289,7 @@ export default {
maxRetryCount: maxRetryCount,
backOff: backOff,
triggerInterval: triggerInterval,
+ deadlineRequest: deadlineRequest,
description: description,
isDeleted: 1
})
@@ -279,7 +299,7 @@ export default {
},
saveRow (record) {
this.memberLoading = true
- const { key, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description } = record
+ const { key, sceneName, sceneStatus, maxRetryCount, backOff, triggerInterval, description, deadlineRequest } = record
if (!sceneName || !sceneStatus || !maxRetryCount || !backOff || (backOff === '1' ? false : !triggerInterval)) {
this.memberLoading = false
this.$message.error('请填写完整成员信息。')
@@ -296,6 +316,7 @@ export default {
backOff: backOff,
triggerInterval: triggerInterval,
description: description,
+ deadlineRequest: deadlineRequest,
isDeleted: 0
})
}
@@ -354,6 +375,7 @@ export default {
maxRetryCount: null,
backOff: '1',
triggerInterval: '',
+ deadlineRequest: '60000',
description: '',
editable: true,
isNew: true
diff --git a/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/model/XRetryHeaders.java b/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/model/XRetryHeaders.java
index c064d089c..b68f16cb9 100644
--- a/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/model/XRetryHeaders.java
+++ b/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/model/XRetryHeaders.java
@@ -24,5 +24,5 @@ public class XRetryHeaders {
/**
* 调用链超时时间 单位毫秒(ms)
*/
- private long ddl = 60 * 10 * 1000;
+ private long ddl = 60000;
}
diff --git a/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/mapper/SceneConfigMapper.java b/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/mapper/SceneConfigMapper.java
index 52834c7f5..2bfc1cbb0 100644
--- a/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/mapper/SceneConfigMapper.java
+++ b/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/mapper/SceneConfigMapper.java
@@ -1,11 +1,10 @@
package com.x.retry.server.persistence.mybatis.mapper;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.x.retry.server.persistence.mybatis.po.SceneConfig;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.List;
+@InterceptorIgnore(tenantLine = "false")
public interface SceneConfigMapper extends BaseMapper {
-}
\ No newline at end of file
+}
diff --git a/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/po/SceneConfig.java b/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/po/SceneConfig.java
index c0571f834..c6d79425a 100644
--- a/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/po/SceneConfig.java
+++ b/x-retry-server/src/main/java/com/x/retry/server/persistence/mybatis/po/SceneConfig.java
@@ -27,6 +27,8 @@ public class SceneConfig implements Serializable {
private String description;
+ private Long deadlineRequest;
+
private LocalDateTime createDt;
private LocalDateTime updateDt;
@@ -34,4 +36,4 @@ public class SceneConfig implements Serializable {
private static final long serialVersionUID = 1L;
-}
\ No newline at end of file
+}
diff --git a/x-retry-server/src/main/java/com/x/retry/server/web/model/request/GroupConfigRequestVO.java b/x-retry-server/src/main/java/com/x/retry/server/web/model/request/GroupConfigRequestVO.java
index 5f5005843..6d5d9e1c1 100644
--- a/x-retry-server/src/main/java/com/x/retry/server/web/model/request/GroupConfigRequestVO.java
+++ b/x-retry-server/src/main/java/com/x/retry/server/web/model/request/GroupConfigRequestVO.java
@@ -102,6 +102,14 @@ public class GroupConfigRequestVO {
*/
private String triggerInterval;
+ /**
+ * Deadline Request 调用链超时 单位毫秒
+ * 默认值为 60*10*1000
+ */
+ @Max(message = "最大60000毫秒", value = 60000)
+ @Min(message = "最小100ms", value = 100)
+ private Long deadlineRequest;
+
/**
* 是否删除
*/
diff --git a/x-retry-server/src/main/java/com/x/retry/server/web/model/response/SceneConfigResponseVO.java b/x-retry-server/src/main/java/com/x/retry/server/web/model/response/SceneConfigResponseVO.java
index 7b9610842..610999975 100644
--- a/x-retry-server/src/main/java/com/x/retry/server/web/model/response/SceneConfigResponseVO.java
+++ b/x-retry-server/src/main/java/com/x/retry/server/web/model/response/SceneConfigResponseVO.java
@@ -28,6 +28,8 @@ public class SceneConfigResponseVO {
private String description;
+ private Long deadlineRequest;
+
private LocalDateTime createDt;
private LocalDateTime updateDt;
diff --git a/x-retry-server/src/main/resources/mapper/SceneConfigMapper.xml b/x-retry-server/src/main/resources/mapper/SceneConfigMapper.xml
index 85d18ecc0..282dad49c 100644
--- a/x-retry-server/src/main/resources/mapper/SceneConfigMapper.xml
+++ b/x-retry-server/src/main/resources/mapper/SceneConfigMapper.xml
@@ -9,11 +9,12 @@
+
- id, scene_name, group_name, scene_status, max_retry_count, back_off, `trigger_interval`, description, create_dt, update_dt
+ id, scene_name, group_name, scene_status, max_retry_count, back_off, `trigger_interval`, deadline_request, description, create_dt, update_dt
-
\ No newline at end of file
+