作业调度框架相关(Z级)

       Spring中有用于开启定时任务的@Scheduled注解,该注解的使用也十分的简单,只需要在注解内定义好调度策略,然后写好业务逻辑,Spring会自动将其作为一个定时作业进行执行。初学@Scheduled注解时,感觉使用起来确实很爽,但是前几天进行代码评审的时候,提到了一个这种问题:有些作业多次执行,导致对数据进行了多次提交,最后导致数据被污染。我想了下,这还真是我以前没想过的,@Scheduled注解在单节点的情况下去执行肯定没什么问题,因为一个程序操作一份数据,不会出现并发的情况,而多节点就会出现并发的情况,控制不好数据就容易被污染。我一想,这个问题很有意思啊,然后我就问现在是怎么解决的:

       “现在的解决方案是在数据库中加锁,但是现在这种方式效率太低,等忙完这段有时间了再用别的方式去解决。”(意思就是给数据加锁是最简单,同时也是最糟糕的解决方案)

       杜老大:“当当有个elastic-job可以解决这个问题。它的思路就是。。。。。你们有兴趣可以研究一下”

       哦?还有这东西,听起来好像还不错,等会儿看看。开完会看了一下,说实话,虽然已经很优秀了,毕竟这个方面的解决方案还是不算多的。但是我觉得或许还有更高效的方式,因为elastic-job包含选主及任务分配,而且它的弹性扩容我感觉比较坑啊,还有其他别的小问题。搜了下这个问题解决方案,发现还有个Quartz,也是做这个的,网上有人说elastic-job用的Quartz的东西(原话不是这个,但是是类似的意思),那就不用看Quartz了。要不自己写一个这种小框架?

       在决定自己写之后,其实是有点儿小激动的,因为Spring内部的东西我只是了解一点儿(当然肯定不只是容器用什么存bean之类的那种白痴问题),所以要想自己写,不知道难度会有多大。

       本来是想复用Spring的@Scheduled注解,做了一天以后我发现,复用Spring内部的东西还是有难度的,而且如果Spring以后的版本对@Scheduled注解的处理发生改变,我写的这个小破框架就要跟着改,这样对Spring的版本依赖性太强(专业术语:耦合度太高),解耦啊兄弟,咱得解耦啊,but,how?跟Spring总是要有交互的(不交互也行,但是可能使用起来就没那么简单了),所以耦合度只能降低,不能根除,Spring是随时变化的,但是其内部一些基本的东西是不会变的(比如BeanPostProcessor和Listener),可以利用这些不会变化的东西进行开发,所以我就用了BeanPostProcessor和Listener(其实只用BeanPostProcessor或者只用Listener就可以,但是为了模仿Spring,就搞了俩)。

不多说,先介绍用法。只需要在相应的方法上添加如下注解即可:

@ScheduleJob(cron="2018-03-31,22",jobName=“updateBalance”type=ScheduleJobType.SIMPLE, retryTimes=5,size=1000, threads=5)

各属性说明:

        cron:调度策略的表达式,不多解释。

        jobName:作业名。如果不显式指定该项的值,默认会使用使用了该注解的方法的方法名作为作业名称。

扫描二维码关注公众号,回复: 1644474 查看本文章

        type:作业类型。目前暂定两种:SIMPLE("simple"),DATA_FLOW("dataflow");默认是SIMPLE。

        retryTimes:并不是每个作业分片都能成功处理,如果作业分片处理失败,就直接被列为失败任务吗?好像不太合理,因为有时失败的原因可能是存在某个节点网络出现问题、或者出现宕机的情况,这个时候因为失败一次就直接被列为失败任务,不太合理。所以设置了这么个属性来由开发人员自定义失败重试次数,这次失败了?没问题,换个节点再来。默认是重试3次。

        size:单个作业分片的大小。一个定时作业规模可能很大,比如有10亿条数据需要处理,我们需要对它进行分片,每片的数据量固定大小,使得每个作业分片大小相同,size就是指这个大小。默认为1000。

        threads:单节点开启几个线程去跑某个作业。比如前面说有10亿条数据,使用默认分片大小的话,就有100万个作业分片需要去执行,但是我们现在只有20个节点,也就是说,每个节点要处理5万个作业分片。如果每个节点只开启一个线程去执行作业,老铁,就算是一个作业分片每个都只需要1S,5万秒,就是将近十五个小时,老铁爽不爽?你的系统别干别的了,就做这个吧。这个时候,可以通过threads属性来指定每个节点执行作业的线程数,如果每个节点开启五个线程,则10亿条数据就可以三个多小时执行完。(一般规模很大的作业可能不会由系统去做,而是通过MR或者Spark做处理,我在这只是举一个例子)

然后在配置文件中写好ZooKeeper集群地址、ZK超时时间等。(默认是在schedulejob.properties配置文件中进行配置)。

       使用了该注解的方法会被封装成一个ExecutorModel。如果该注解放在类上说明该类的每个方法都是一个调度作业,这些调度作业拥有相同的调度策略(拥有相同的cron属性)。但是不建议放在类上,因为多个作业共用同一套调度策略会导致系统负载过大。也可以同时放在类和方法上,此时用在方法上的注解的属性会覆盖用在类上的注解的属性,如果方法注解中某个属性没有配置,而类上的注解中配置了,则用类上的注解配置的属性,如果都没有,则使用默认值。

       elastic-job需要选主,然后由主节点对作业进行分片,各节点执行作业分片。主节点挂了,重新选主重新分片;如果有新的节点在作业分好片后加进来,不好意思,这次干活没你的份,下次再带你玩,你现在一边玩儿去。

       哎,我这个就很吊了,不选主,自动对作业进行分片(就是自己给自己分,多节点之间还不会重复)。而且不管什么什么时候加进来,只要你的任务处于触发状态,随时都可以执行作业分片;不管什么时候退出,你的作业会立刻被认为执行失败,会有其他节点根据配置的重试次数再次执行你的这个任务,知道执行成功或者失败次数超过重试次数为止。

       现在只是实现了简单任务(SIMPLE("simple"))基于ZooKeeper的实现,大头还没有真正去做,而且没有实际使用,只是三个节点进行过测试,最终数据显示没什么问题。(吐槽一下,我电脑太垃圾了,启动三个Tomcat竟然崩了一个,辛亏有日志为证,要不然我还以为我写的有问题)

       以后会做DATA_FLOW的处理逻辑。而且会提供基于数据库的实现,因为基于数据库的话,可以记录处理结果(譬如什么时间哪个作业分片在哪个节点上执行失败/成功,失败原因是什么,成功的话用了多少时间;失败的任务分片后来又被哪个节点执行了等等。其实基于ZooKeeper也可以实现,但是ZK一般只存少量数据,存储大量数据可能会导致ZK集群爆炸,所以还是采用数据库好点儿),然后做一个管理后台,可以通过后台直接查看作业的执行情况和进度。还会添加是否设置自动延时等。

       目前的实现在设计风格上都尽量遵循SpringBoot,因为万一哪天真的做出来了,肯定是要做成SpringBoot的starter(毕竟原生Spring已经快要步入老年时代了),为了日后方便,先尽量往上面靠。

       至于具体代码,我是不会给你们的,哈哈哈。等我哪天完全做完、测试没问题了,然后再开源。

       这个东西现在还没起名字,各位可以帮忙取个名字,反正我也不用。还有就是,这个东西属于Z级哦。嘿嘿嘿。

猜你喜欢

转载自blog.csdn.net/shi_chang_zhi/article/details/79815874