读重试
读重试
版本依赖:读重试依赖 tcapdir 3.87.1 及其以后的版本。更早版本的 tcapdir 不会下发
conf://读重试配置,此时即使业务调用了SetReadRetryTimeout,读重试也不会生效(SDK 不会报错,只是不重试)。
1. 功能简介
读请求因为网络抖动、proxy 异常等原因迟迟收不到响应(或返回了指定的错误码)时,SDK 会在内部自动换一个 proxy 重发,业务只会收到最终结果,中间的失败不会抛给业务。
- 默认关闭,需要业务调用
SetReadRetryTimeout开启。 - 只对下列读请求生效,写请求永远不会重试:
| 类型 | 命令字 |
|---|---|
| 简单请求 | TCAPLUS_API_GET_REQ、TCAPLUS_API_PB_FIELD_GET_REQ、TCAPLUS_API_LIST_GET_REQ |
| 复杂请求 | TCAPLUS_API_BATCH_GET_REQ、TCAPLUS_API_PB_BATCH_FIELD_GET_REQ、TCAPLUS_API_LIST_GET_BATCH_REQ、TCAPLUS_API_GET_BY_PARTKEY_REQ、TCAPLUS_API_LIST_GETALL_REQ |
- 具体对哪些命令字、哪些错误码重试,以及最多重试几次,由 tcapdir 下发的配置决定(默认重试 2 次,最多 8 次),业务侧只需要设置超时预算。
- 重试次数不含首次发送:重试 2 次表示一个请求最多发出 3 个包。次数按每个请求各自计算、累积,只有”重发”才计一次,用完即判定超时。
- 重发时会尽量避开已经试过的 proxy:优先换一台没试过的机器,其次换一个没试过的端口,都没有未试过的可用 proxy 时才复用已试过的。
2. 接口说明
/**
@brief 设置读重试的开关与超时预算,Init 之后调用,两个参数默认都是 0,即不开启读重试
@param [IN] simple_timeout_ms 简单请求一次读操作的总超时预算,单位毫秒,推荐 3000,0 表示不开启
@param [IN] complex_timeout_ms 复杂请求分包续期的总超时预算,单位毫秒,推荐 6000,0 表示不开启
*/
void TcaplusServer::SetReadRetryTimeout(IN int simple_timeout_ms, IN int complex_timeout_ms);
/**
@brief 设置单个 zone 最多缓存多少个读重试上下文,Init 之后调用,不调用默认每个 zone 50w 个
@param [IN] max_count 单个 zone 的上下文条数上限,小于等于 0 按默认值 50w 处理
@note 到达上限后新的读请求不再重试,照常发送
*/
void TcaplusServer::SetReadRetryMaxContextCountPerZone(IN int max_count);
- 两个接口在
RegistTable之前或之后调用都可以,之后新加入的 zone 会自动继承已设置的值。 - 运行中可以再次调用
SetReadRetryTimeout调整预算,传 0 即关闭对应类型的读重试。
3. 使用示例
// 1. 初始化
int32_t iRet = g_stTcapSvr.Init(g_pstTlogger, /*module_id*/0, APP_ID, ZONE_ID, SIGNATURE);
if (0 != iRet)
{
return iRet;
}
// 2. 开启读重试:简单请求预算 3000ms,复杂请求预算 6000ms
g_stTcapSvr.SetReadRetryTimeout(3000, 6000);
// 3. (可选)调整单个 zone 的上下文上限
g_stTcapSvr.SetReadRetryMaxContextCountPerZone(500000);
// 4. 注册表、发送请求,与未开启读重试时完全一样
iRet = g_stTcapSvr.RegistTable(TABLE_NAME, g_szTableMeta, 10000);
// ...
// 5. 接收响应:需要周期性调用 RecvResponse,读重试的超时判断和重发都在这里驱动
TcaplusServiceResponse* pstRsp = NULL;
iRet = g_stTcapSvr.RecvResponse(pstRsp);
if (1 == iRet && NULL != pstRsp)
{
int32_t result = pstRsp->GetResult();
if (TcapErrCode::API_ERR_READ_RETRY_TIMEOUT == result)
{
// 重试次数用完仍没有响应,按超时处理
// 通过 GetAsynID / GetUserBuff 等仍可取回发送时的异步 ID 和 userbuffer
}
else if (0 == result)
{
// 正常处理
}
}
开启读重试后业务代码无需改动:
- 响应中的异步 ID、seq、userbuffer 与发送时一致。
- 每个请求只会交付一次最终结果,重试产生的重复响应由 SDK 丢弃。
- 复杂请求依旧按原来的方式处理分包(
HaveMoreResPkgs等),SDK 只补发缺失的部分,保证记录不重复、不遗漏。
4. 超时预算说明
4.1. 简单请求
simple_timeout_ms 是一次读操作从首发到最终超时的总时长。SDK 把它切成 n+1 段(n 为重试次数),第 k 段时长为 k*x:
(1 + 2 + ... + (n+1)) * x = simple_timeout_ms
以推荐值 3000ms、重试 2 次为例,x = 500ms:
| 时间点 | 动作 |
|---|---|
| 0ms | 首次发送 |
| 500ms | 仍无响应,换 proxy 第 1 次重试 |
| 1500ms | 仍无响应,换 proxy 第 2 次重试 |
| 3000ms | 仍无响应,返回错误码 API_ERR_READ_RETRY_TIMEOUT(-19219) |
4.2. 复杂请求
复杂请求会分多个包返回,complex_timeout_ms 按同样的方式切成 n+1 段、第 k 段时长为 k*x,但这一段的时长是”两个分包之间最多等多久”,不是整个请求的总时长:
- 以推荐值 6000ms、重试 2 次为例,x = 1000ms:
- 初始时,等分包超时时间就是1000ms。
- 如果分包超时了,此时重试发送,等分包的超时时间提高到 2000ms,累积重试了1次。
- 如果后面还有分包超时了,此时重试发送,等分包的超时时间提高到 3000ms,累积重试了2次。
- 如果后面再次有分包超时,超过累积总次数2,此时构造错误码API_ERR_READ_RETRY_TIMEOUT返回给业务。
- 重试次数是整个请求累积的:
ReadRetryMaxCnt(默认 2)就是一个请求最多重发几次,收了多少分包都不会把次数清零或补回来,重发满次数仍收不全就返回API_ERR_READ_RETRY_TIMEOUT。
重试时只补发还没收到的部分:批量查询只补发未返回的 key,索引查询(GetByPartKey、ListGetAll)从已收到的位置往后续传。
4.3. 错误码重试
响应的错误码命中 tcapdir 配置的错误码名单时,SDK 会立即换 proxy 重发,不必等到分段超时
5. tcapdir 侧如何开启(运维)
读重试的名单和重试次数由 tcapdir 下发,配置保存在 center 库的 service_api_conf 表,按 (AppId, ZoneId) 粒度生效。只有 tcapdir 3.87.1 及以后的版本才会读取这三列并下发配置。
| 字段 | 含义 | 默认值 |
|---|---|---|
ReadRetryCmd | 参与读重试的命令字,多个值用竖线分隔 | 7|103|15|23|117|153|25|11(8 个读命令字) |
ReadRetryErrorCode | 命中即立即重试的错误码,多个值用竖线分隔 | -7953(PROXY_ERR_SWIFT_TIMEOUT) |
ReadRetryMaxCnt | 最多重试次数,不含首次发送,最大 8 | 2 |
8 个默认命令字与接口名的对应关系:
| 命令字 | 接口 |
|---|---|
| 7 / 103 / 15 | TCAPLUS_API_GET_REQ / TCAPLUS_API_PB_FIELD_GET_REQ / TCAPLUS_API_LIST_GET_REQ |
| 23 / 117 / 153 | TCAPLUS_API_BATCH_GET_REQ / TCAPLUS_API_PB_BATCH_FIELD_GET_REQ / TCAPLUS_API_LIST_GET_BATCH_REQ |
| 25 / 11 | TCAPLUS_API_GET_BY_PARTKEY_REQ / TCAPLUS_API_LIST_GETALL_REQ |
下发方式:tcapdir 把这三列拼成一个 conf:// 元素搭载在 CollectUrlList 中下发,与数据采集地址(tcp://)共用同一列,用 ; 分隔,因此不需要改协议:
conf://ReadRetryCmd=7|103|15|23|117|153|25|11&ReadRetryErrorCode=-7953&ReadRetryMaxCnt=2
生效方式:tcapdir 从数据库加载配置(周期加载,也可通过 ReloadDB 命令强制加载),API 每次向 tcapdir 更新路由时都会重新收到配置,业务进程无需重启。
要关闭读重试,把 ReadRetryCmd 置空(ReadRetryCmd=)或把 ReadRetryMaxCnt 置 0 即可;三列都不下发时(如老版本 tcapdir),SDK 保持当前配置不变。
6. 注意事项
- 超时预算应小于业务上层的超时时间,否则业务已经放弃等待,读重试就失去了意义。
- 内存占用:每个在途的读请求都会在本地缓存一份上下文(含打包后的请求和 userbuffer),开启后额外内存约为
读 qps × 超时秒数 × (key 大小 + userbuffer 大小)。上下文上限按 zone 各自计算,多 zone 场景内存约为zone 个数 × 上限条数 × (key 大小 + userbuffer 大小),调大前请评估。 - 读重试超时返回的错误码是
API_ERR_READ_RETRY_TIMEOUT,与普通等待超时API_ERR_WAIT_RSP_TIMEOUT不同,业务可据此区分。 - 只有在
SetReadRetryTimeout设置了有效预算,并且 tcapdir 下发了读重试配置(重试次数大于 0、命令字名单非空)时,读重试才会生效。 - SQL 查询等其余复杂请求暂不支持读重试。
7. 常见问题
Q:已经调用了 SetReadRetryTimeout,为什么没有触发重试?
按以下顺序排查:
- SDK 日志中搜索
set read retry option,确认设置的预算不为 0。 - SDK 日志中搜索
recv read retry conf,确认 tcapdir 已下发配置,且其中的ReadRetryCmd包含当前命令字、ReadRetryMaxCnt大于 0;没有该日志说明 tcapdir 未下发配置,先确认 tcapdir 版本不低于 3.87.1,再联系运维按 §5 开启。 - 确认上下文是否已达上限(
SetReadRetryMaxContextCountPerZone):日志中搜索reach max context count,有则说明达到上限,达到上限的请求不重试、照常发送。
Q:重试用完后日志里有什么?
SDK 会打印 error 日志 read retry exhausted after N retries, cost Xms,并给业务返回 API_ERR_READ_RETRY_TIMEOUT。