,转载请联系bugstack公众号。1.前言不写,只要会用!哈哈哈,约定好了就不写了,只要能灵活运用就行。但是每次接到新的需求,我就心痒痒的。这次想迭代更新需求,或者根据之前的架构设计和实现经验,找到一个更好的完全颠覆之前的方案。码完的那一刻,总觉得神清气爽。其实大部分喜欢写代码的纯coder都比较复杂。例如,如果一个需求可以实现,它可能是P5。如果做到了这一点,功能不仅好用而且非常好用就是P6了。除了简单易用之外,它还把通用的需求浓缩,发展成一个通用的组件服务就是P7。每一个成长起来的coder都在造轮子的路上验证自己的想法,一次又一次的实践。绝对不是一篇千篇一律的文章就能累死一个高水平的技术专家。2.延迟任务场景什么是延迟任务?在我们实际的业务需求场景中,有一些活动开始前的状态变化,订单结算后的T+1对账,贷款单笔利息费用的产生,这些都需要使用延迟任务来达到。实际操作一般使用Quartz和Schedule定时扫描处理你的数据库表数据。当条件满足时,数据状态发生改变或产生新数据插入表中。这样一个简单的需求就是延迟任务的初始需求。如果前期需求内容少,用户不多,实际开发中可能只是单机直面桌子进行一轮培训。但是,随着业务需求的发展,功能复杂度的增加,反馈到研发设计和实现往往不是那么简单。比如你需要保证尽可能低的延迟来完成大规模数据量的扫描和处理,否则就像贷款单利费用的产生已经到了第二天,用户还没有看到他的自己的利息费用信息或还款后对帐,此时可能会出现客户投诉。那么,如何设计这样的场景呢?3、延时任务设计通常的任务中心处理流程主要是定时任务扫描任务库表,将即将到达超时时间的任务信息扫描到处理队列(内存/MQ消息),然后业务系统对任务进行处理,处理完成后更新库表中的任务状态。高延迟任务调度问题:数据量大的任务列表数据需要分库分表快速扫描。任务扫描服务耦合业务逻辑处理,不具备通用性和可复用性。一些细分任务系统需要低延迟处理,不能等待太久。1.除了任务表模式下的一些微小的状态变化场景,比如在各自业务的库表中包含一个状态字段。任务服务自动变更处理的操作,一般这类功能可以直接设计到自己的库表中。然后是一些更大更频繁使用的场景。如果把这样的字段加到每个系统维护需要的N张表中,会很冗余,也不是那么容易。维持。因此,对于这样的场景,非常适合搭建一个通用的任务延时系统。各业务系统将需要延迟的动作提交给延迟系统,延迟系统会在指定时间回调,回调动作可以通过接口或MQ消息到达。比如可以设计这样一个任务调度表:从任务调度库表的设计中提取的任务调度表,主要是获取什么任务,什么时候发起动作。具体的action处理还是留给业务工程。对于大量各自业务任务的集中处理,需要设计分库分表以满足后续业务量的增长。门牌号设计旨在扫描一张桌子。如果数据量很大,不想一个任务只扫描一个表,可以用多个任务扫描一个表,加入扫描量。这时候就需要一个门牌号来隔离不同任务的扫描范围,避免扫描出重复的任务数据。2.低延迟模式低延迟处理方案是基于任务表方式,新增时间控制处理。可以将前段时间即将过期的任务放入Redis集群队中,等消费完再pop出队列,这样可以更快的逼近任务的处理时间,避免扫描间隔过大延迟任务执行。当任务处理进程收到业务系统提交的延迟任务时,根据执行时间的长短放入任务库中,或者也同步到Redis集群中。一些执行时间较晚的任务可以先放入任务库中,然后通过扫描的方式加入到超时任务执行队列中。那么本次设计的核心就在于Redis队列的使用,为了保证消费的可靠性,需要引入两阶段消费,并向ZK注册中心进行注册,保证至少有一次消费过程。本文重点介绍Redis队列的设计。其他更多的逻辑处理可以根据业务需要进行扩展和完善。Redis消费队列是根据消息体计算的。Index=CRC32&7StoreQueue根据SlotKey=#{topic}_#{index}使用Slot和SortedSet的数据结构,按照执行任务得分排序,存储任务执行信息。计时消息使用时间戳作为分数。消费时,每次都会弹出一条分数小于当前时间戳的消息。为了保证每条消息至少被消费一次,消费者不会直接pop有序集合中的元素,而是将元素从StoreQueue中移到PrepareQueue中,并将消息返回给消费者。消费成功后,会从PrepareQueue中删除。如果消费失败,会再次从PreapreQueue移到StoreQueue,这样就进行了两阶段消费。参考文档:2021阿里巴巴技术员白宝黑皮书PDF,低延迟超时中心实现简单案例RDelayedQueue
