Skip to content

Latest commit

 

History

History
176 lines (133 loc) · 13.6 KB

项目经历介绍.md

File metadata and controls

176 lines (133 loc) · 13.6 KB

Crony分布式定时任务平台

这个项目其实是本人在XXXX公司实习时做的一个项目,感觉挺有意思,就自己写了一份代码(与原版还是有很大差别的,因为忘记了)上传到了github,可以看看源码,不是很难

具体的介绍可以看tmnhs/Crony

简历书写:

  • 技术栈: Go + MySQL + Etcd + 公司自研微服务框架Coa

  • 项目简介:对公司内部众多的定时任务进行集成化管理,提供可视化界面,支持Coa微服务(rpc)回调和多节点部署,提供任务加权轮询分配和故障转移等高可用功能

  • 功能实现:

1.研读公司Coa微服务框架源码,熟悉其rpc调用流程、服务注册发现和负载均衡等,实现Coa微服务回调

2.项目采用中心化方案,分为调度中心和执行节点两个模块,调度中心通过Etcd实现服务发现、定时任务的分配、故障转移和大盘等功能,支持加权轮询分配

3.执行节点负责定时任务的执行,实现服务注册,支持http和微服务回调,将执行结果存入MySQL,支持重试、超时等配置,任务执行失败可邮件或飞书通知告警,实现日志收集,接入监控报警

4.研发go-sdk,允许接入go-sdk的微服务异步响应任务执行结果,并编写使用说明文档

  • **责任描述:**调研、输出技术方案,宣讲技术方案并答疑,负责完成执行节点模块和go-sdk的研发

简述:

这是本人在XXX公司实习做的一个中台项目,公司在立项的时候讨论过为什么要专门做一个定时任务管理平台,因为公司过去开发的项目中存在许多定时任务,而代码写法都是采用见缝插针式的写法或者直接丢到task服务里面写。这样做存在很多问题,比如定时任务执行情况和执行时间没有统一管理,任务没有按时执行甚至失败了很久才发现,需要对应开发者结合代码和日志分析情况重试或排查,带来很高的维护成本。本项目旨在对公司内部众多的定时任务进行集成化管理,并提供可视化界面管理。

我们花了两三天时间完成对市面上开源定时任务平台(goland实现)的调研,通过对gocron和cronsun两个开源项目的分析(gocron不是分布式的,可用性不高,cronsun是分布式的,但是代码有点臃肿,不适合二次开发),最终决定自研。然后输出技术方案,并且答疑,之后开始研发测试。

总体上采用中心化系统架构,分为调度中心和执行节点两部分,通过ETCD实现服务注册与发现(此处可以讲一讲怎样实现的,或者和coa微服务架构怎样实现服务注册发现做比较...),MySQL实现持久化存储。

  • 其中调度中心实现服务发现、定时任务的分配(增删改查)和大盘功能,支持最少任务数量分配和加权轮询分配(默认)两种方式,还支持故障转移功能,一旦某个执行节点发生故障宕机了,调度中心会检测到并将该节点上的任务全部转移至其他正常的节点上。

  • 执行节点负责定时任务的执行,实现服务注册,支持http回调和微服务回调,将执行结果存入MySQL,支持重试、超时等配置,任务执行失败后可通过邮件或飞书通知对应开发人员,实现日志收集,接入监控报警(一旦定时任务执行的失败率过高会告警)。

  • 考虑到某些定时任务会执行很长时间,这时候执行节点不能一直等着,需要提供一个sdk的方法供调用方将异步执行结果返回给定时任务平台

    如果在某个项目中想要启动一个定时任务,可以专门启动一个微服务或者API服务,在这个服务中,只需要负责该定时任务需要执行的方法,不负责时间调度(即不进行cron表达式的执行),由Crony定时任务平台负责时间调度,通知该服务执行。任务的执行结果支持同步和异步响应。同步即需要Crony定时任务平台同步等待回调结果返回,可以设置超时时间,若执行失败,在平台内部重试(再次通知),在多次重试都失败后,需要发邮件或者飞书通知特定的负责人任务执行失败。异步需要引入Crony平台的go-sdk将结果写入Mysql,此时需要平台将此任务的超时时间,重试次数传入,在微服务内部进行失败重试,重试失败后需要调用go-sdk(http调用)发邮件或者飞书告警。

可靠性说明:执行节点有多个,并且支持故障转移,调度中心只有一个,唯一不可靠的来源就是调度中心的单点故障,不过这个可以通过服务多实例和负载均衡预防

亮点:

  • 分布式的,中心化系统架构,支持多节点部署,提高系统的可用性和并发能力
  • 支持故障转移,提高系统的容错性
  • 支持定时任务加权轮询分配,支持重试和超时等配置,支持任务执行失败后可通过邮件或飞书报警通知对应负责人
  • 异步响应任务执行结果,sdk的引入让执行节点无需同步等待服务返回,提高系统的资源利用率

遇到的困难:

  • 微服务回调:这部分需要熟悉公司内部自研的微服务框架,通过阅读源码熟悉其rpc调用流程(待补充)
  • 如何实现服务注册与发现:使用etcd监听各个节点状态,实现服务注册和发现。当启动一个agent时,我们把服务的地址写进etcd,注册服务。同时绑定租约(lease),并以续租约(keep leases alive)的方式检测服务是否正常运行,从而实现健康检查。服务器需要在心跳周期之内向 etcd 发送数据包,表示自己能够正常工作。如果在规定的心跳周期内,etcd 没有收到心跳包,则表示该服务器异常,etcd 会将该服务器对应的信息进行删除。如果心跳包正常,但是服务器的租约周期结束,则需要重新申请新的租约,如果不申请,则 etcd 会删除对应租约的所有信息。

定时器的实现参考(7条消息) Golang 定时任务 github/robfig/cron/v3 使用与源码解析_Junebao的博客-CSDN博客

其中上述corn/v3包的实现是基于go的time.Timer+排序数组,time.Timer底层是一个四叉小顶堆Golang 定时器底层实现深度剖析 - 知乎 (zhihu.com) ,至于为什么不直接使用time.Tricker,可能会跳过某次执行,当然这个也可以使用异步解决(go关键字)

基于go底层timer实现的定时任务管理性能不是很高,在一些追求性能的高并发场景(比如商品的订单取消(如果用户未支付,取消订单),qps可能达到百万级别)下显然不合适,但是本项目主要是用于公司内部运维的场景,比如数据的统计(在线时长、今日收益等),过期数据的清理(日志),并不追求性能

基于小顶堆的定时任务的添加和删除的时间复杂度都是O(logN),而基于时间轮算法实现的定时任务的添加和删除的时间复杂度都是O(1),在高并发和追求性能的定时任务显然使用时间轮算法实现更优

定时器的实现方式:小顶堆、红黑树、跳表、时间轮

1)项目背景

MisU项目存在许多定时任务,过去的代码写法都是采取见缝插针式的写法或者直接丢到task服务里面写,存在以下问题

  1. 服务多实例时执行定时任务要考虑抢占锁来争夺定时任务执行权,未抢到锁的任务计算资源被浪费了
  2. 定时任务执行情况和执行时间没有统一管理,任务没有按时执行,甚至失败了很久才发现,需要对应开发者结合代码和日志分析情况重试或排查,带来很高的维护成本

2)具体需求(模块化)

1.管理平台模块(crony-web)(调度中心)
  1. 实现对定时任务的增删改查和大盘功能(查询日志)
  2. 新建任务支持http回调、微服务回调和本地化方案
  3. 支持将任务通过轮询均匀调度到不同节点执行(每次选取任务数最少的节点)
2.节点模块(corny-agent)
  1. 实现多节点部署,支持当agent自身故障时,能够将任务转移至其他节点执行(故障转移)
  2. 配置回调返回错误时,根据平台重试规则进行重试回调,当返回成功时认为通知成功
  3. 实现监控报警,接入到mia监控报警平台,agent存活情况,执行任务失败数,重试数,超时数, 通知耗时
  4. 对agent的任务执行情况进行日志收集,并且在管理平台进行查看
3.sdk模块
  1. 研发go-sdk,java-sdk,允许接入sdk的通知服务调用sdk异步响应任务执行结果
  2. 编写使用说明文档

3)拆解

  1. 完成对市面上定时任务平台的调研(对gocron和cronsun开源项目进行调研)
  2. 输出技术方案,确认是否使用开源二次修改或者自研
  3. 宣讲技术方案,并且答疑
  4. 研发测试,并且在测试环境灰度测试,并且压测确定稳定性
  5. 灰度生产环境使用

4)实现方案

1.微服务(rpc)回调方案
  1. 模拟coa(公司自研微服务框架)客户端对服务端进行连接,rpc调用
  2. 在某个项目中,如果想要启动一个定时任务,可以专门启动一个微服务,在这个微服务中,只需要负责该定时任务需要执行的方法,不负责时间调度(即不进行cron表达式的执行),由Crony定时任务平台负责时间调度,通知改微服务执行。微服务回调支持同步和异步回调。同步即需要Crony定时任务平台同步等待回调结果返回,可以设置超时时间,若执行失败,在平台内部重试,在多次重试都失败后,需要发邮件或者飞书通知特定的负责人。异步需要引入Crony平台的go-sdk将结果(http调用)写入Mysql,此时需要平台将此任务的超时时间,重试次数传入,在微服务内部进行失败重试,重试失败后需要调用go-sdk(http调用)发邮件或者飞书告警。
2.多agent部署方案
  1. 使用etcd监听各个节点状态,实现服务注册和发现当启动一个agent时,我们把服务的地址写进etcd,注册服务。同时绑定租约(lease),并以续租约(keep leases alive)的方式检测服务是否正常运行,从而实现健康检查。服务器需要在心跳周期之内向 etcd 发送数据包,表示自己能够正常工作。如果在规定的心跳周期内,etcd 没有收到心跳包,则表示该服务器异常,etcd 会将该服务器对应的信息进行删除。如果心跳包正常,但是服务器的租约周期结束,则需要重新申请新的租约,如果不申请,则 etcd 会删除对应租约的所有信息。etcd主要存储了结点的存活情况
  2. 在etcd结点注册一个watcher,agent改变后会通知etcd,用于处理 watch 请求。创建一个 serverWatchStream 结构体,开启两个 goroutine,其中 sendLoop 是用于发送 watch 消息,recvLoop 接收请求。其他agent如果订阅该服务就会知道
  3. 在调度中心注册一个监听器,定时监听其他agent状态
3.轮询调度方案
  1. 有两种身份,调度中心和执行器(agent),调度中心分配任务,执行器执行任务
  2. 每个agent有自己的任务调度器,调度中心将任务轮询均匀分配给各agent(将node的ip当作key注册到etcd上,每个agent持续监听各自的key值(create or modify),从这个key中获取分配的任务job)。调度中心维护一张各agent的任务表,新增任务时将任务添加到任务最少的节点上。调度中心监听到某节点挂掉时将该节点任务重新分配(故障转移)
4.sdk方案
  1. 模拟客户端进行coa回调,rpc任务执行方可能是异步的,因为定时任务都会执行很长时间,这时候invoke不能一直等着,需要提供一个sdk的方法供调用方将异步执行结果返回给定时任务平台

5)可靠性说明

1.crony-node节点

crony-node 节点是负责调度和执行任务的,对于crony-node 节点提供以下可靠性保障:

  • crony-node 被设计成一个常驻进程,追求稳定和高可用。

    crony-node和etcd服务的连接中断了:

    • 断开连接之前已经下发的任务会正常执行;
    • 在断开连接期间新建、修改、删除的任务无法更新到节点;
    • 会自动和etcd进行重连;
    • etcd重新连接上后,会重新加载和该节点相关的全部任务,保证正确性;
  • cron-node和数据库的连接中断了:

    • 所有任务依然会正常执行;
    • 在断开连接期间执行完成的任务,日志会因为无法写入到数据库而看不到任务的执行记录和任务日志,不影响任务正常执行;

    • 会自动和数据库进行重连,重连后执行记录和任务日志会正常写入数据库;

2.crony-admin节点

crony-admin节点是负责管理任务、查看任务执行日志的,对于Cronadmin节点提供以下可靠性保障:

  • crony-node进程崩溃了:

    • 不影响crony-node节点和任务正常执行;
    • 报警邮件无法发送;
  • crony-admin和etcd

    服务的连接中断了:

    • crony-admin无法访问;
    • 报警邮件无法发送;
  • crony-admin和数据库服务的连接中断了:

    • crony-admin无法访问;
    • 报警邮件无法发送;
  • 可以部署多个crony-admin节点,可以访问任一节点正常管理任务和查看日志;