「订单创建后半小时没有支付就自动关闭」这句话写在需求文档里通常只占一行,落到实现上却会同时牵动四件事:关闭动作必须恰好生效一次;用户在倒计时最后一秒完成支付时,不能出现「先关后付」或「先付后关」的错单;遇到改期——用户点了延长支付、客服手工挪动截止时间、支付渠道回调迟到导致截止时间后移——新时间必须立刻生效;大促当天几十万笔订单挤在同一分钟到期时,这套机制不能被一次性压垮。真正让线上出问题的,往往不是「延迟怎么发出来」这一件事,而是这四件事里至少有一件从头到尾没人负责。
更麻烦的是,这几个坑会互相放大。重复触发本来只是一次多余的关闭,一旦叠加「改期不生效」,就会把用户刚刚延长过的订单直接关掉;机器之间时钟不一致让扫描起点偏移,积压又把这点偏移放大成几分钟的实际偏差;积压期间失败重试变多,重复触发的概率随之升高,而重复触发又会制造新的重试。于是选型之前,值得先把「延迟触发」这四个字拆开,看清自己到底在为哪一项付复杂度。
把延迟触发类需求拆开看,真正影响选型的只有四个变量。第一个是延迟精度。业务说的「半小时」其实是一个带容差的说法:用户侧看到的是倒计时,早关半分钟和晚关半分钟的体验差别很小,真正要避免的是「支付成功回调」和「关闭动作」撞在同一瞬间时谁先落库。多数交易类业务的真实容差在分钟量级,也就是说,只要偏差稳定地小于这个容差,精度就不是瓶颈。
第二个是取消与改期的频率。订单支付成功后,那条关闭任务必须消失;截止时间被改动后,任务必须跟着改。这一条是选型的分水岭:如果一条任务一旦创建就再也不会变,「什么时候执行」只是一个计时问题;如果它会频繁被取消、被改期,那本质上是在维护一张「按时间排序且可动态增删」的表,复杂度完全不同。
第三个是数据规模,更准确说是到期密度。绝对记录数并不关键,关键是在任意一个处理周期里有多少条同时到期,以及到期时间是均匀分布还是高度集中。均匀分布的百万级记录很容易处理;集中在同一秒的十万级记录会瞬间吃掉全部处理能力。整点开抢、整点发券、批量扣款这类业务天生是集中型,扫描类方案在这里最容易被打爆。
第四个是重复消费的容忍度。同样一条关闭动作执行两次,代价是什么?如果是「更新状态为已关闭」,多数情况下第二次什么也不会改变;如果它带副作用——发短信、退款、扣库存——那两次就是两次真实损失。容忍度越低,就越要在方案之外单独补一层去重,而不是指望方案本身不重复。
还有一个常被忽略的隐藏变量:这件事要不要跨服务、跨机器、跨语言栈。如果触发方和执行方是两个团队维护的两个服务,方案必须有一个双方都能读写的共享事实来源(通常是一张表或一个可查询的状态接口);如果整套东西只在单个进程内自循环,选择面会宽得多,但也就意味着它无法承受进程重启。
最朴素的做法是:在业务表里存一个到期时间字段,给它建索引,然后用一个常驻任务每隔一段时间执行一次「取状态为待处理且到期时间早于当前时间的一批记录」。当精度要求是分钟级、改期靠改字段实现、取消靠改状态实现时,这套方案几乎总是够用的,而且它的三个优点是别的方案很难同时具备的:可查询——任何时刻都能直接查到还有哪些订单将在什么时候关闭;可重放——处理逻辑挂了,数据还在,重启后自然补上;可审计——每条记录什么时候被谁改成了什么状态,都在同一张表里。
取消能力是它最大的优势。订单支付成功后,状态从「待支付」变成「已支付」,这条记录自动离开扫描结果集,不需要去某个队列里删除一条消息,也不需要去某个内存结构里摘掉一个节点。改期同理,改一下到期时间字段就完成了。对高频取消的业务来说,这一点抵得过其他方案在精度上的全部优势。
但它有几个必须一开始就设计好的细节。第一,扫描的起点要用水位线或游标,不要用分页偏移:深翻页会随着数据增长越来越慢,而水位线只需要记录「上次处理到哪个时间点」。第二,每一轮必须限量分批,并且在处理完一批后重新判断剩余量,否则高峰期一轮就会拉回几十万条,把内存和事务都撑爆。第三,捞取和落地要分开:先用一个条件更新把记录标记为「处理中」,处理成功再改成终态,这样进程中途退出时,既不会丢(处理中的记录可以被下一轮按超时重新捞起),也不会因为重复捞取而重复执行副作用。第四,判断状态的那条更新语句必须带状态条件,让「只有一次更新能生效」由数据库保证,而不是靠应用层先查后写。
它的边界也很清楚。精度受扫描周期约束,想做到秒级就得把周期压到很短,代价是大量空转扫描;到期时间高度集中时,单轮处理耗时可能超过扫描周期,进而进入「越扫越慢」的正反馈。这两个边界都可以靠分批、限流和降级缓解,但缓解不了的是:它无法保证某条记录在到期后的某几秒内一定被处理。
第二种常见做法是给消息设置存活时间,过期后转入死信队列,由消费者从死信队列里接走。它写起来很轻——生产者发一条消息就算完事,不需要建表、不需要扫描器——所以常被当成「延迟队列」的默认实现。但它的精度问题几乎必然出现,原因在过期判定的时机上。
多数实现对过期的检查是惰性的:只有当一条消息被读取、被投递到队头时才会判断它是否已经过期。这意味着如果队头是一条存活时间很长的消息,排在它后面、存活时间很短的消息必须排队等着,真实的投递时间会被前面的长存活消息整体拖后。换句话说,不同延迟档位的消息混在同一个队列里时,短延迟会受到长延迟的牵连。表现就是:单独测试时准得离谱,一旦几种延迟档位的消息混发,偏差就变得不可预测,而且堆积越多偏差越大。
第二个偏差来源是起算点。存活时间从消息入队那一刻开始算,而不是从业务事件发生的那一刻算。如果生产者到队列之间有一段堆积,或者消息因为网络原因被重发过一次,实际起算点就已经偏了。业务上以为「下单后半小时」,实际上可能是「下单后半小时再叠加一段不确定的入队延迟」。
至于取消,它基本没有原生能力:已经进入队列的消息通常无法按业务键删除。可行做法只有一种——消费时回查业务状态,发现订单已支付就直接丢弃。也就是说,取消能力其实是靠状态位实现的,队列只承担了计时。既然如此,就要问一句:既然状态位已经是事实来源,为什么不让状态位所在的存储直接承担计时?
所以它适合的场景是:延迟档位固定、条数不多、基本不会取消、对偏差不敏感的任务,比如失败重试的第一档退避、几分钟后的缓存补偿刷新。它不适合的场景是:延迟档位跨度大、会取消、偏差要可控、处理完还要能追溯的任务——因为消息消费完就没了,事后想查「这条为什么没执行」只能翻日志。
时间轮的结构可以用钟表来类比:一个环形数组,每一格代表一个时间刻度,格子上挂着到期时间落在这个刻度内的任务;指针每前进一格,就把该格子里的任务取出来执行。为了兼顾精度和跨度,通常做成多层——类似时针、分针、秒针的关系,高精度层走完一圈就把任务降级到更粗的一层。它的核心优势不是精度本身,而是取消和改期的成本极低:任务在内存结构里,取消就是从链表上摘掉一个节点,改期就是从旧槽位移到新槽位,都是常数级操作。
因此,只有在下面几种情况同时成立时,它的额外复杂度才划算:需要秒级甚至更细的精度;到期后还要支持随时取消(用户支付成功、运营撤回);改期非常频繁(用户反复修改提醒时间、反复调整预约时间);或者到期密度高度集中,需要在很短的窗口内稳定吐出大量任务。整点开抢、秒级倒计时关闭、高频改期的提醒类业务属于这一类;半小时关单、七天后自动确认收货、二十四小时后未评价自动好评,通常不属于。
代价必须提前认账。第一,任务必须常驻内存,进程重启就意味着任务可能消失,所以必须有一份持久化副本,并在启动时重建未到期的任务集;这份持久化和业务表之间的一致性要有人负责。第二,多实例部署必须分片:要么按业务键哈希到固定实例,要么引入选主或租约,否则同一个任务会在多个实例上各跑一遍,或者为了保证不重复而退化成单点。第三,单个刻度上的任务过多会让那一格变慢,进而拖慢后续所有刻度,所以集中到期场景要在设计时就做分散——把整点拆成整点后的一段随机窗口,是常见的缓解手段。第四,驱动时间轮的那个时钟本身要可靠,时钟回拨会导致任务被跳过或重复执行。
内存占用这件事问得很多,但答案只能给量级关系而不是数字:它大致由「刻度粒度决定的槽位总数」和「同时存活的任务条数」两部分构成,精度要求越细、覆盖的时间跨度越大,槽位就越多;每条任务还要携带业务键、重试次数与少量上下文。也就是说,把精度从分钟级提到秒级,槽位数会成倍上升,而任务条数是另一条独立的增长曲线。真正需要警惕的是后者——如果任务里塞了完整业务对象而不是一个键,内存占用会随着业务对象膨胀而失控。这也是为什么工程上普遍只放键、执行时再回查。
第四种是外部定时器:操作系统层面的周期任务、调度平台上的定时作业、或者云上的定时触发器,按固定周期去调一个接口或跑一段脚本。它非常成熟、运维可见性好、告警接入方便,但它的定位是「周期性地启动一件事」,而不是「为每一条记录各记一个闹钟」。
适合的用法有两类。一类是与单条业务记录无关的周期任务:日终对账、报表生成、过期日志清理、索引维护。这类任务的输入是「整个系统」,天然适合交给定时器。另一类是作为兜底扫描器:让定时器每分钟唤醒一次扫描程序,由扫描程序去捞「到期但还没处理」的记录。这种用法值得强调——定时器只负责启动,真正的到期判断仍然落在数据层的到期时间字段上,于是定时器挂一轮、晚几分钟,最多是延迟,不会漏;而如果把定时器当成唯一的计时依据,它一旦跳过一次,那一批记录就永远不会被处理。
不适合的用法也很明确:为每一笔订单注册一个定时作业。任务数量会随订单量线性膨胀,调度器本身要承担全部状态,取消和改期都要通过调度接口完成,还要处理「调度成功但执行失败」的补跑。更现实的问题是,多数调度器的最小粒度和可靠性承诺,是为一个数量级小得多的任务集设计的。
还有两个部署层面的边界。一是多实例重复触发:同一份定时配置在多台机器上部署时,会被同时触发多次,必须靠选主、租约或者下游幂等来收敛。二是定时器所在机器的时区与时钟:把「每天零点」这类配置写在调度器上时,实际执行时刻取决于调度器所在机器的本地时区与时钟同步状态,而不是业务期望的那个时区。
无论选哪一种实现,最后都要回答同一个问题:这条关闭动作被执行两次会发生什么。答案不能是「应该不会吧」,必须是可验证的设计。最稳的做法是把幂等交给状态机:记录上只有「待支付」才能流转到「已关闭」,这条更新带状态条件,成功影响的条数为零就说明有人先动过了,直接返回。这样即使两个实例同时处理同一条,也只有一个能真正改状态,另一个自然退出。
带副作用的动作要单独去重。发短信、退款、释放库存这类操作不能靠状态机兜底,因为它们发生在状态变更之后或者跨系统。常见做法是建一张以「业务键加动作类型」为唯一键的去重表,插入成功才执行,插入冲突就跳过;或者在消费侧记录已处理的消息标识,处理前先查。去重键的粒度要仔细定:以订单号为粒度,「关闭」和「关单后退款」就会互相挡住;以「订单号加动作类型」为粒度才对。
还有一个必须想清楚的顺序问题:支付回调和关闭动作会并发。用户点支付的瞬间,关闭任务也刚好到期,两个请求同时落在不同的机器上。正确的处理不是加锁串行化,而是让两个动作都走同一条带条件的更新语句,谁先改成功谁生效,另一个读到新状态后自行判断要不要补偿。如果支付赢了,订单正常完成;如果关闭赢了,支付回调会写失败,这时必须有一条明确的逆操作路径——重开订单、原路退回、或者转人工。这是业务规则,不属于任何一种技术方案能自动解决的范围。
最后要区分「幂等」和「不重复执行」。幂等保证重复执行一次没有额外后果,但重复执行仍然会消耗连接、产生日志、触发告警。所以去重应该尽量前置:在捞取阶段就靠条件更新挡掉大部分重复,而不是等执行到发短信那一步才发现。
延迟触发本质上是比较两个数:记录的到期时间,和当前的「现在」。只要这两个数来自不同的时间源,整套逻辑就会出现一种很难复现的偏差。最常见的情况是:应用服务器用本地时间生成到期时间并写入数据库,扫描程序却用数据库的时间函数取「现在」。两台机器只要差几秒,就会出现「到期时间还没到但扫描已经捞走了」或者反过来;差上几十秒,业务侧就会看到「倒计时还剩一分钟订单却关了」。
处理办法只有一条:到期时间和判断所用的现在,必须来自同一个时间源。要么统一用数据库时间,要么统一用一个明确的时间服务,不要在写和读之间混用。这条规则看起来简单,但在有多套服务、多个数据源、还有离线脚本补数据的系统里,破坏它的往往是一次小小的「顺手用了一下本地时间」。
机器本身的时钟也要当成配置项管理。周期任务的机器要靠时间同步服务持续校准,并且要监控偏移量本身,而不只是在出问题后才发现;虚拟化环境下的实例在宿主机之间迁移、暂停、恢复之后,时钟可能出现跳变。扫描逻辑不要依赖「上一次扫描的时间点」做起点,而要用「上一次扫描的水位线减去一个安全回退窗口」,这样即使时钟回拨或者一轮扫描漏掉一小段,也能被下一轮覆盖回来,不会因为一次跳变留下永久空洞。
时区是另一类问题。存储层面统一用协调世界时或者一个明确指定的时区,展示层再做转换,这是没有争议的部分;有争议的是「按本地时间定义的周期」——比如「每天早上九点推送」。这类任务不能只存一个时间戳,必须同时存下它由哪个时区的本地时间推导出来,否则一次时区配置变更就会让所有用户的推送整体平移。跨时区业务还涉及夏令时切换:在切换日,按本地时间定义的任务会提前或延后一小时执行一次,跨区域部署的系统里,这一小时足以让「每天一次」变成「一天两次」或者「一天零次」。
跑这类批处理与定时任务的机器,时间基准本身也要当成一项配置来管理。一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源,提供服务器租用、机柜托管与带宽接入的一体化交付;具体机型、带宽与报价以官网实时信息或咨询确认为准。
积压不是突然发生的,它有一个可观测的前兆:单轮处理耗时逐渐接近扫描周期。所以监控的第一个指标不是「队列有多长」,而是单轮耗时与周期的比值。这个比值逼近一时,系统就已经没有余量了,任何一次抖动都会把延迟推高;超过一之后,即使到达速率不变,待处理量也会单调增长。
第二个指标是最早未处理记录的老化时间,也就是「当前时间减去最早那条已到期但未处理记录的到期时间」。这个数直接对应业务感受到的延迟,比队列长度更能说明问题:队列长但老化时间短,说明都在容差内;队列短但老化时间长,说明有少数记录卡住了,往往是某条记录反复失败。第三个指标是到达速率与处理速率的差值,它可以提前几十分钟预警——当新增到期的速度连续高于处理速度时,积压只是时间问题。
光有指标不够,还要有对应的动作。第一,上一轮没跑完时下一轮不能启动,否则多轮叠加会把数据库和下游同时压垮;用租约或者进程内的运行状态锁来保证单飞。第二,要有降级策略:超过一定积压量时,优先处理老化最久的那部分,暂时放弃「按顺序处理」;或者临时提高扫描频率,但同时必须确认单飞机制还在生效。第三,失败记录不能无限重试:重试固定次数后转入一张人工可见的滞留表,并单独告警。滞留表里的记录数量突然增长,通常意味着下游接口变了或者数据里有脏记录,这类问题靠重试永远解决不了。
还有一类积压是隐形的:记录压根没进入待处理集合——写入到期时间失败、状态机提前流转、消息在发送端就丢了。所以还需要一条对账口径:定期按业务规则重新扫描全量,找出「应该已经关闭但状态仍然不对」的记录。这条对账本身就是最好的兜底,它把「延迟触发是否可靠」这个问题,从「中间件是否可靠」降级成了「数据是否自洽」。
分钟级延迟要不要上消息队列?如果取消和改期是常态,先不要。分钟级的容差意味着带索引的定时扫描完全够用,而它天然具备取消、改期、可查询、可重放这四项能力,恰恰是队列方案要额外补的。真正需要队列的理由通常是解耦与削峰——生产端和执行端是两个服务、峰值需要缓冲——而不是「需要一个延迟」。把这两件事分开判断,结论往往是不上,或者上了队列也只是用来传递,计时仍然放在数据层。
TTL 加死信为什么会不准?主要是两个原因。一是过期判定在许多实现里是惰性的,只有轮到队头才检查,排在长存活时间消息后面的短存活时间消息会被整体拖后,延迟档位混用、堆积变多时偏差会明显放大。二是存活时间从入队时刻起算,而不是从业务事件发生时刻起算,生产端一旦有堆积或重发,起算点就已经偏了。想让它准一点,通常要把不同延迟档位拆到不同队列,并且让消费侧回查业务状态来兜底——而后者其实又回到了幂等。
时间轮要占多少内存?只能给量级关系,不能给具体数字,因为它取决于两件你自己决定的事:一是刻度粒度带来的槽位总数,精度越细、覆盖跨度越大,槽位越多;二是同时存活的任务条数,这是另一条独立曲线。工程上真正影响内存的是任务里放什么——只放业务键,代价很低;放完整业务对象,内存会随对象膨胀而失控,而且重建时还要反序列化一遍。估算时按「槽位开销加任务条数乘以单条开销」这个关系去推,并用真实业务对象在预发环境里观察一段时间,比套用别人的数字靠谱。
消费失败怎么重试?分两层。瞬时失败(下游超时、连接被重置)用有限次数的退避重试,退避间隔递增,避免把下游刚恢复的服务再打垮。持续失败(参数非法、状态已流转、业务规则不满足)不应该重试,直接落到滞留表等待人工。关键是「重试次数」和「下次到期时间」都要记在记录上,而不是靠队列的重投机制隐式表达,否则一旦需要人工介入,没人知道这条到底失败过几次、为什么。另外,重试必须复用同一套幂等键,否则重试本身就会变成重复执行。
服务器时间不同步会怎样?轻则偏差,重则漏处理。如果写到期时间用一台机器的本地时间,扫描取「现在」用另一台机器的时间,两者差几秒就会出现「还没到期就被关掉」或者「到期了却迟迟不关」;差得多的时候,业务侧看到的就是倒计时和实际行为对不上。更麻烦的是漏扫:如果扫描起点用「上次扫描时间」,时钟回拨会让一段区间被永久跳过。所以要用时间同步服务持续校准、监控偏移量本身,并且用带安全回退窗口的水位线而不是时间点做扫描起点。
跨时区怎么算?存储统一用协调世界时或一个明确指定的时区,展示层再转换,这是基础。难点在「按本地时间定义」的任务——「每天早上九点」这种需求必须把时区标识一起存下来,否则一次配置变更就会整体平移。还要考虑夏令时切换日,那一天本地时间会少一小时或多一小时,按本地时间定义的周期任务可能执行两次或一次都不执行。稳妥的做法是把这类周期任务在每天展开时,按用户所属时区一次性算出当天的绝对执行时刻并落库,而不是让执行器每次都去解释「九点」。
积压怎么发现?看三个数:单轮处理耗时与扫描周期的比值、最早未处理记录的老化时间、到达速率与处理速率的差值。第一个数逼近一说明余量耗尽,第二个数直接对应业务感受到的延迟,第三个数能提前预警。三者都要配具体的动作——周期比值过高时提高频率并保证单飞,老化时间超阈值时优先处理最老的一批,差值持续为正时扩容或降级。另外别忘了对账:定期按业务规则全量比对,找出「应该已经处理但状态不对」的记录,这是唯一能发现隐形丢失的手段。
扫描类方案的坑集中在查询形态上:用分页偏移而不是水位线,数据一大就开始慢;一轮不限量,高峰期直接把事务撑爆;扫描周期和批量大小不匹配,导致要么空转要么积压;多实例同时扫描却没有单飞机制,几台机器互相抢同一批记录。还有一个隐蔽的坑:索引建了,但查询条件里混进了函数或者隐式类型转换,索引实际没生效。
TTL 类方案的坑集中在偏差和可见性上:不同延迟档位混在一个队列里产生队头阻塞;以为精度是秒级,实际偏差随堆积放大;死信队列没有独立的消费监控,消息堆在那里没人知道;消息消费完即消失,事后无法追溯某一笔为什么没执行。
时间轮类方案的坑集中在生命周期上:进程重启后任务没恢复;多实例部署导致重复执行或被迫退化成单点;单个刻度上的任务过多,拖慢整个轮盘;时钟回拨导致任务被跳过。此外还有一类设计坑——把完整业务对象放进任务里,结果内存占用和重建成本都随业务对象一起膨胀。
外部定时器的坑集中在使用方式上:为每条记录注册一个任务导致任务数爆炸;多实例部署时同一份配置被重复触发;调度器所在机器的时区与业务时区不一致;把定时器当成唯一计时依据,结果跳过一轮就永久漏掉一批记录。通用的一条坑则是:只盯着「延迟怎么发出来」,却没有人负责取消、改期和重复执行。
| 方案 | 精度 | 取消与改期支持 | 适用规模 | 主要风险 |
|---|---|---|---|---|
| 带索引的定时扫描 | 分钟级,受扫描周期约束,无法承诺秒级 | 强,改状态即可取消,改字段即可改期 | 万级到百万级待处理记录,靠分批与索引维持 | 周期与批量不匹配造成积压;到期高度集中时单轮超时;多实例重复扫 |
| 消息存活时间加死信 | 名义上细,实际受队内排队影响,偏差随堆积放大 | 弱,通常只能靠消费时回查业务状态兜底 | 延迟档位固定、条数不多的中小规模 | 延迟档位混用产生队头阻塞;起算点是入队时刻;消费后无痕迹难追溯 |
| 延迟队列与时间轮 | 秒级甚至更细,由刻度粒度决定 | 强,取消与改期都是常数级操作 | 到期密度高、改期频繁、精度要求细的场景 | 内存常驻需持久化与重启重建;多实例要分片;单刻度任务过多拖慢轮盘 |
| 外部定时器 | 取决于调度周期,通常到分钟级 | 弱,改期要改动调度配置本身 | 与单条记录无关的周期任务,或作为兜底扫描入口 | 按记录注册会任务爆炸;多实例重复触发;调度器时区与业务时区不一致 |
| 扫描加队列的组合 | 分钟级,队列只负责削峰不负责计时 | 强,取消与改期仍由数据层承担 | 执行端与生产端分离、峰值需要缓冲的中大规模 | 两处都有待处理量,积压监控要同时覆盖;队列堆积时会掩盖扫描侧问题 |
把四个变量填完,结论通常不需要讨论。精度是分钟级、改期和取消是常态、规模在百万级以内——带索引的定时扫描加上幂等就够了,多出来的任何组件都是纯粹的额外依赖。如果执行端和生成端是两个团队维护的服务,需要削峰和解耦,那就用「扫描加队列」:到期判断仍然落在数据层的到期时间字段上,队列只负责把任务平滑地送到执行端,这样队列堆积时也不会改变「什么时候该执行」这个事实。
只有当精度要求进入秒级,或者取消与改期频率高到「改字段再等下一轮扫描」已经不满足业务时,时间轮或延迟队列的额外复杂度才划算。需要注意的是,引入它们并没有取消对幂等和时间基准的要求:内存里的任务一样会因为重启、分片漂移、时钟跳变而重复或丢失,持久化副本与业务表之间的一致性问题反而变多了。
至于消息存活时间加死信,它适合的定位是「一件小事的延时」,而不是「一类业务的计时基础设施」。一旦业务开始问「能不能取消」「偏差怎么查」「这笔为什么没执行」,就说明它已经超出了这个方案的适用范围。同理,外部定时器适合的定位是「启动器」和「与记录无关的周期任务」,把它当成每条记录的闹钟,是这条路上最常见也最难收拾的错误。
第一个是实际执行时刻相对到期时刻的偏差分布。不要看均值,要看高位分位数:均值会被大量「恰好按时」的记录拉得很漂亮,而用户投诉永远来自尾巴上那部分。这个数要按方案分开统计——扫描侧的偏差应该集中在扫描周期附近,如果分位数突然变长,通常是单轮耗时上升或者轮次被跳过;时间轮侧的偏差如果出现周期性的尖刺,往往是某个刻度上的任务过多或者发生了时钟跳变。偏差一旦超过业务容差,就说明该调整周期或批量了,而不是先去加机器。
第二个是待处理量与最早未处理记录的老化时间。待处理量说明总量,老化时间说明最坏情况,两者要一起看:总量大但老化时间短,说明只是密度高、还在容差内;总量小但老化时间长,说明有记录卡住了,通常是某几条反复失败或者状态流转异常。这个数还要配上一条硬规则——老化超过某个阈值就告警,并且自动转入滞留表,避免少数几条把整个指标拖住却没人处理。
第三个是幂等被触发的次数,也就是「本该执行一次的动作,实际被挡掉了多少次」。这个数在正常情况下应该接近零并且平稳,它突然上升只有几种可能:多个实例在抢同一批记录、时钟或水位线出现异常导致重复捞取、上游重发、或者下游超时后触发了重试。每一种的修法完全不同,所以这个数要从上线第一天就开始采集,并把它和失败重试次数、滞留表增量放在同一张图上看。这三个数稳定了,延迟触发这件事才算真的可控。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品