延时job设计

一.设计初衷

           目前系统系统job任务是每10分钟重试一次,总共重试7次。所有需要重试的请求都在10分钟之内堆积,如果请求量过大,这个时间点压力都有可能落到数据库上面,从而造成数据库崩溃。

    此外时间配置不够智能化。如果两小时之后所依赖的第三方系统稳定,而目前的重发机制相当于失效了。 基于上面两个原因,提出了改进方案,使用延时job实现功能!

二.功能原理

         通过redis的zset数据类型来存储数据,score是过期时间,value可以是封装的记录流水号。使用mysql来确保数据只能被消费一次。

三.流程介绍

    3.1 插入记录

          实现值的插入很简单。只需要将待重发数据先插入数据库,库里的状态设置为待重发,然后在存入redis中,记录初次过期的时间。

    3.2 执行延时job

          

   3.2数据补偿

       如果redis突然挂了,需要做补偿措施,可以取表里面近3天需要重试的记录放到redis中,让其再次重发。

 四.异常情况

     1).如果在【插入记录】数据的时候,插入数据库报错,这样这条数据也无法进入redis中,需要人工处理数据库异常。

     2.) 如果在【插入记录】插入数据库成功,插入redis失败,这个时候通过补偿机制,将mysql中的记录同步到redis中(同步频率待确认)。

     3).在【执行延时job】时,如果redis直接报错,执行停止。执行停止肯定不可取。解决方案:可以使用一个状态量来判断redis是否正常工作,如果正常工作就执行job,否则,让线程sleep2个小时。

    4).在【执行延时job】时,如果调用第三方接口网络不通,目前考虑将该次重试记录入库(这样是否妥当?还是说这次请求不算,数据不入库。),状态是失败,出错消息提示为网络异常。

    5).在【执行延时job】时,删除redis值失败,由于redis中还有本来应该删除的值,会再发一次请求(由于理房通那边会校验,再发一次也没问题)。

    6).在【执行延时job】时,入库异常,库里面的数据一直存在。在数据补偿的时候数据会放进redis中,就算数据库挂的时间很长也没问题。会用数据里面存的时间加上一个定值在放到redis中,作为下次要过期的时间。在redis删除值时,只会删除那些时间小于当前时间的记录。

   注意:添加数据采用异步的方式。

五.库表设计

   5.1 sql

    CREATE TABLE `t_trade_time_delay` (

       `id` BIGINT (20) PRIMARY KEY COMMENT '主键ID', 
       `request_id` BIGINT(20) NOT NULL DEFAULT 0 COMMENT '请求id', 
       `current_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '当次时间',
       `current_level` INT NOT NULL DEFAULT 0 COMMENT '当次级别',
       `is_finished` TINYINT (1) NOT NULL DEFAULT '0' COMMENT '是否完成,1 完成,以后不需要处理,0未完成,还需要处理', 
       KEY `idx_request_id` (`procurement_request_id`) USING BTREE COMMENT '索引:请求id'
    ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '延时处理表';

   5.2 关键字段说明

      current_time  该字段存每次需要过期的时间

     current_level  改字段存每次需要过期时间的等级。

     举例说明,    延时job设置的值为 :

      level   time(秒)

       1        5

       2       30

       3       60

       4       600(十分钟)

       5       1800(30分钟)

       6        3600(1小时)

       7        6*3600(6小时)

       8        24*3600(1天)

       9       2*24*3600(2天)

在今天10:00:00 将一个请求编号3003,存入表中,此时 

request_id为3003,current_time 为10:00:05 ,current_level为1,is_finished为 0。

如果时间到了10:00:05,执行了这条记录,结果还是失败的,这时

  request_id为3003,current_time 为10:00:35 ,current_level为2,is_finished为 0。

依次类推。。。

如果在level为3第三方接口通了。此时

request_id为3003,current_time 为10:01:35 ,current_level为3,is_finished为 1。

六.注意点

1.在生产环境中我们都是集群部署的,所以需要使用分布式锁来实现资源的控制。可以你们会说,不是使用了redis吗?它不是天然的适用于redis吗?没错,往redis里面插入中是没问题的,但是考虑到我们的业务场景:第一步:从redis里面获取值。第二步:通过上一步拿到的值,调用下游接口。那么问题就来了,在第一步的时候是没有问题的,但是到了第二步,如果没有锁住,多个客户端可能针对于同一个请求体发多次请求,在下游没有控制好幂等的情况下就gg了。加了分布式锁控住第一步和第二步,这个问题就不存在了。

2.在生产中,我们目前的是在程序系统启动完成之后起一个线程死循环消费redis里面的数据,这里就需要考虑了,如果网络抖动或者redis先挂后好,我们的线程可能就挂了,也就是说虽然项目是一直启动着,但是redis里面的数据就是消费不了。我们采用了一种最笨的方案来解决这个问题。做一个定时任务监控这个线程是否一直都是活的,如果线程挂了,就新建一个再次去消费redis。线程的监控工具也有很多,jstack,jvisualvm等。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值