乐趣区

关于后端:给面试加点硬菜延迟任务场景该如何提高吞吐量和时效性

作者:小傅哥
博客:https://bugstack.cn

积淀、分享、成长,让本人和别人都能有所播种!😄

一、前言

不卷了,能用就行!

哈哈哈,说好的不卷了,能凑活用就行了。但每次接到新需要时都手痒,想联合着上一次的架构设计和落地教训,在这一次需要上在迭代更新,或者找到齐全颠覆之前的更优计划。卷完代码的那一刻总是神清气爽

其实大部分喜爱写代码的一类纯正码农,都是比拟卷的,就比方一个需要在实现上是能用大略 是 P5、如果这个做进去的性能不只是能用还十分好用 是 P6、除了好用还凝练共性需要开发成通用的组件服务 是 P7。每一个成长过去的码农,都是在造轮子的路上一次次验证本人的想法和加以实际,相对不是一篇篇的八股文就能累进去一个高级的技术大牛。

二、提早工作场景

什么是提早工作?

当咱们的理论业务需要场景中,有一些流动开始前的状态变更、订单结算后的 T + 1 对账、贷款单息费的产生,都是须要应用到提早工作来进行触达。理论的操作个别会有 Quartz、Schedule 来对你的库表数据进行定时扫描和解决,当条件满足后做数据状态的变更或者产生新的数据插入到表中。

这样一个简略的需要就是提早工作最后需要,如果需要后期内容较少、应用方不多,可能在理论开发中就只是一个单台机器间接对着表一顿轮训就完事了。但随着业务需要的倒退和性能的复杂度晋升,往往反馈到研发设计和实现,就不那么简略了,比方:你须要保障尽可能低提早实现较大规模的数据量扫描解决,否则就像贷款单息费的产生,曾经到了第二天用户还没看到本人的息费信息或者是还款后的从新对账,可能就这个时候就要产生客诉了。

那么,相似这样的场景该如何设计呢?

三、提早工作设计

通常的工作核心解决流程次要,次要是由定时工作扫描工作库表,把行将达到超时工夫的工作信息扫描到解决队列 ( 内存 /MQ 音讯),再由业务零碎进行解决工作,解决实现后更新库表中的工作状态。

问题

  1. 海量数据规模较大的工作列表数据,在分库分表下该须要疾速扫描。
  2. 工作扫描服务与业务逻辑解决,耦合在一起,不具备通用性和复用性。
  3. 细分工作体系有些是须要低提早解决的,不能期待过长时间。

1. 工作表形式

除了一些较小的状态变更场景,例如在各自业务的库表中,就蕴含了一个状态字段,这个字段一方面有程序逻辑解决变更的状态,也有达到 指定到期 工夫后由工作服务主动变更解决的操作,个别这类性能,间接设计到本人的库表中即可。

那么还有一些较大也较为频繁应用的场景,如果都是在每个零碎的各自所需的 N 多个表中,都增加这样的字段进行保护,就显得十分冗余了,也不那么易于保护。所以针对这样的场景就很适宜做一个通用的工作延时零碎,各业务零碎把须要被延时执行的动作提交到延时零碎中,再有延时零碎在指定工夫下进行回调,回调的动作能够是接口或者 MQ 音讯进行触达。例如能够设计这样一个工作调度表:

  1. 抽取的工作调度表,次要是拿到什么工作,在什么工夫发动动作,具体的动作解决仍交给业务工程解决。
  2. 大批量的各自业务的工作进行集中处理,则须要设计一个分库分表,满足于后续业务体量的增长。
  3. 门牌号设计,针对一张表的扫描,如果数据量较大,又不心愿只是一个工作扫描一个表,能够多个工作扫描一个表,加到扫描的体量。这个时候就须要一个门牌号来隔离不同工作扫描的范畴,防止扫描出反复的工作数据。

2. 低提早形式

低提早解决计划,是在工作表形式的根底上,新减少的工夫把控解决。它能够把行将到期的前一段时间的工作,搁置到 Redis 集群队里中,在生产的时候再从队列中 pop 进去,这样能够更快的靠近工作的解决时效,防止因为扫库距离较大提早工作执行。

  • 在接管业务零碎提交进来的提早工作时,依照执行工夫的长短搁置到工作库或者也同步到 Redis 集群中,一些执行工夫较晚的工作则能够先放到工作库,再通过扫描的形式增加到超时工作执行队列中。
  • 那么对于这块的设计外围在于 Redis 队列的应用,以及为了保障生产的可靠性须要引入二阶段生产、注册 ZK 注册核心至多保障一次生产的解决。本文重点次要放在 Redis 队列的设计,其余更多的逻辑解决,能够依照业务需要进行扩大和欠缺

Redis 生产队列

  • 依照音讯体计算对应数据所属的槽位 index = CRC32 & 7
  • StoreQueue 采纳 Slot 依照 SlotKey = #{topic}_#{index} 和 Sorted Set 的数据结构按执行工作分数排序,寄存工作执行信息。定时音讯将工夫戳作为分数,生产时每次弹出分数小于以后工夫戳的一个音讯
  • 为了保障每条音讯至多可生产一次,消费者不是间接 pop 有序汇合中的元素,而是将元素从 StoreQueue 挪动到 PrepareQueue 并返回音讯给消费者。生产胜利后再从 PrepareQueue 从删除,如果生产失败则从 PreapreQueue 从新挪动到 StoreQueue,这样二阶段生产的形式进行解决。
  • 参考文档:2021 阿里技术人的百宝黑皮书 PDF 文,低提早的超时核心实现形式

简略案例

@Test
public void test_delay_queue() throws InterruptedException {RBlockingQueue<Object> blockingQueue = redissonClient.getBlockingQueue("TASK");
    RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
    new Thread(() -> {
        try {while (true){Object take = blockingQueue.take();
                System.out.println(take);
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {e.printStackTrace();
        }
    }).start();
    int i = 0;
    while (true){delayedQueue.offerAsync("测试" + ++i, 100L, TimeUnit.MILLISECONDS);
        Thread.sleep(1000L);
    }
}

测试数据

2022-02-13  WARN 204760 --- [Finalizer] i.l.c.resource.DefaultClientResources    : io.lettuce.core.resource.DefaultClientResources was not shut down properly, shutdown() was not called before it's garbage-collected. Call shutdown() or shutdown(long,long,TimeUnit) 
测试 1
测试 2
测试 3
测试 4
测试 5

Process finished with exit code -1
  • 源码:https://github.com/fuzhengwei/TimeOutCenter
  • 形容:应用 redisson 中的 DelayedQueue 作为音讯队列,写入后期待生产工夫进行 POP 生产。

四、总结

  • 调度工作的应用在理论的场景中十分频繁,例如咱们常常应用 xxl-job,也有一些大厂自研的分布式任务调度组件,这些可能本来都是很小很简略的性能,但通过形象、整合、提炼,变成了一个个外围通用的中间件服务。
  • 当咱们在思考应用任务调度的时候,无论哪种形式的设计和实现,都须要思考这个性能应用时候的认为迭代和维护性,如果仅仅是一个十分小的场景,又没多少人应用的话,那么在本人机器上折腾就能够。过渡的设计和应用有时候也会把研发资源代入泥潭
  • 其实各项技术的知识点,都像是一个个工具,刀枪棍棒斧钺钩,那能怎么联合各自的特点,把这些兵器用起来,才是一个程序员一直成长的过程。如果你心愿理解更多此类有深度的技术内容,能够退出 Lottery 分布式抽奖秒杀零碎 学习更有价值的更抗用的实战伎俩。

五、系列举荐

  • 金三银四面试前,把本人弄成卷王!
  • 方案设计:基于 IDEA 插件开发和字节码插桩技术,实现研发交付品质主动剖析
  • 工作两三年了,整不明确架构图都画啥?
  • 工作两年简历写成这样,谁要你呀!
  • BATJTMD,大厂招聘,都招什么样 Java 程序员?
退出移动版