详解Gotorch多机定时任务管理系统

前言

先介绍下问题:

组内有十来台机器,上面用 cron 分别定时执行着一些脚本和 shell 命令,一开始任务少的时候,大家都记得哪台机器执行着什么,随着时间推移,人员几经变动,任务也越来越多,再也没人能记得清哪些任务在哪些机器上执行了,排查和解决后台脚本的问题也越来越麻烦。

解决这个问题也不是没有办法:

  • 维护一个 wiki,一旦任务有变动就更新 wiki,但一旦忘记更新 wiki,任务就会变成孤儿,什么时候出了问题更不好查。
  • 布置一台机器,定时拉取各机器的 cron 配置文件,进行对比统计,再将结果汇总展示,但命令的写法各式各样,对比命令也是个没头脑的事。
  • 使用开源分布式任务调度任务,比较重型,而且一般要布置数据库、后台,比较麻烦。

除此之外,任务的修改也非常不方便,如果想给在 crontab 里修改某一项任务,还需要找运维操作。虽然解决这个问题也有办法,使用 crontab cronfile.txt 直接让 crontab 加载文件,但引入新的问题:任务文件加载的实时性不好控制。

为了解决以上问题,我结合 cron 和任务管理,每天下班后花一点时间,实现一个小功能,最后完成了 gotorch 的可用版。看着 GitHub 的 commit 统计,还挺有成就感的~

这里放上 GitHub 链接地址: GitHub-zhenbianshu-gotorch ,欢迎 star/fork/issue。

介绍一下特色功能:

  • cron+,秒级定时,使任务执行更加灵活;
  • 任务列表文件路径可以自定义,建议使用版本控制系统;
  • 内置日志和监控系统,方便各位同学任意扩展;
  • 平滑重加载配置文件,一旦配置文件有变动,在不影响正在执行的任务的前提下,平滑加载;
  • IP、最大执行数、任务类型配置,支持更灵活的任务配置;

下面说一下功能实现的技术要点:

文章欢迎转载,但请带上本文源地址:http://www.cnblogs.com/zhenbianshu/p/7905678.html,谢谢。

cron+

在实现类似 cron 的功能之前,我简单地看了一下 cron 的源码,源码在 https://busybox.net/downloads/ 可以下载,解压后文件在miscutils > crond.c。

cron 的实现设计得很巧妙的,大概如下:

数据结构:

1.cron 拥有一个全局结构体 global ,保存着各个用户的任务列表;

2.每一个任务列表是一个结构体 CronFile, 保存着用户名和任务链表等;

3.每一个任务 CronLine 有 shell 命令、执行 pid、执行时间数组 cl_Time 等属性;

4.执行时间数组的最大长度根据 “分时日月周” 的最大值确定,将可执行时间点的值置为 true,例如 在每天的 3 点执行则 cl_Hrs[3]=true;

执行方式:

1.cron是一个 while(true) 式的长循环,每次 sleep 到下一分钟的开始。

2.cron 在每分钟的开始会依次遍历检查用户 cron 配置文件,将更新后的配置文件解析成任务存入全局结构体,同时它也定期检查配置文件是否被修改。

3.然后 cron 会将当前时间解析为 第 n 分/时/日/月/周,并判断 cal_Time[n] 全为 true 则执行任务。

4.执行任务时将 pid 写入防止重复执行;

5.后续 cron 还会进行一些异常检测和错误处理操作。

明白了 cron 的执行方式后,感觉每个时间单位都遍历任务进行判断于性能有损耗,而且我实现的是秒级执行,遍历判断的性能损耗更大,于是考虑优化成:

给每个任务设置一个 next_time 的时间戳,在一次执行后更新此时间戳,每个时间单位只需要判断 task.next_time == current_time。

后来由于 “秒分时日月周” 的日期格式进位不规则,代码太复杂,实现出来效率也不比原来好,终于放弃了这种想法。。采用了跟 cron 一样的执行思路。

此外,我添加了三种限制任务执行的方式:

  • IP:在服务启动时获取本地内网 IP,执行前校验是否在任务的 IP 列表中;
  • 任务类型:任务为 daemon 的,当任务没有正在执行时则中断判断直接启动;
  • 最大执行数:在每个任务上设置一个执行中任务的 pid 构成的 slice,每次执行前校验当前执行数。

而任务启动方式,则直接使用 goroutine 配合 exec 包,每次执行任务都启动一个新的 goroutine,保存 pid,同时进行错误处理。由于服务可能会在一秒内多次扫描任务,我给每个任务添加了一个进程上次执行时间戳的属性,待下次执行时对比,防止任务在一秒内多次扫描执行了多次。

守护进程

本服务是做成了一个类似 nginx 的服务,我将进程的 pid 保存在一个临时文件中,对进程操作时通过命令行给进程发送信号,只需要注意下异常情况下及时清理 pid 文件就好了。

这里说一下 Go 守护进程的创建方式:

由于 Go 程序在启动时 runtime 可能会创建多个线程(用于内存管理,垃圾回收,goroutine管理等),而 fork 与多线程环境并不能和谐共存,所以 Go 中没有 Unix 系统中的 fork 方法;于是启动守护进程我采用 exec 之后立即执行,即 fork and exec 的方式,而 Go 的 exec 包则支持这种方式。

在进程最开始时获取并判断进程 ppid 是否为1 (守护进程的父进程退出,进程会被“过继”给 init 进程,其进程号为1),在父进程的进程号不为1时,使用原进程的所有参数 fork and exec 一个跟自己相同的进程,关闭新进程与终端的联系,并退出原进程。

filePath, _ := filepath.Abs(os.Args[0]) // 获取服务的命令路径
cmd := exec.Command(filePath, os.Args[1:]...) // 使用自身的命令路径、参数创建一个新的命令
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil // 关闭进程标准输入、标准输出、错误输出
cmd.Start() // 新进程执行
return // 父进程退出

信号处理

将进程制作为守护进程之后,进程与外界的通信就只好依靠信号了,Go 的 signal 包搭配 goroutine 可以方便地监听、处理信号。同时我们使用 syscall 包内的 Kill 方法来向进程发送信号。

我们监听 Kill 默认发送的信号SIGTERM,用来处理服务退出前的清理工作,另外我还使用了用户自定义信号SIGUSR2 用来作为终端通知服务重启的消息。

一个信号从监听到捕捉再到处理的完整流程如下:

1.首先我们使用创建一个类型为 os.Sygnal 的无缓冲channel,来存放信号。

2.使用 signal.Notify() 函数注册要监听的信号,传入刚创建的 channel,在捕捉到信号时接收信号。

3.创建一个 goroutine,在 channel 中没有信号时 signal := <-channel 会阻塞。

4.Go 程序一旦捕捉到正在监听的信号,就会把信号通过 channel 传递过来,此时 goroutine 便不会继续阻塞。

5.通过后面的代码处理对应的信号。

对应的代码如下:

c := make(chan os.Signal)
signal.Notify(c, syscall.SIGTERM, syscall.SIGUSR2) 

// 开启一个goroutine异步处理信号
go func() {
    s := <-c
    if s == syscall.SIGTERM {
        task.End()
        logger.Debug("bootstrap", "action: end", "pid "+strconv.Itoa(os.Getpid()), "signal "+fmt.Sprintf("%d", s))
        os.Exit(0)
    } else if s == syscall.SIGUSR2 {
        task.End()
        bootStrap(true)
    }
}()

小结

gotorch 的开发共花了三个月,每天半小时左右,1~3 个 commits,经历了三次大的重构,特别是在代码格式上改得比较频繁。 不过使用 Go 开发确实是挺舒心的,Go 的代码很简洁, gofmt 用着非常方便。另外 Go 的学习曲线也挺平滑,熟悉各个常用标准包后就能进行简单的开发了。 简单易学、高效快捷,难怪 Go 火热得这么快了。

以上就是详解Gotorch多机定时任务管理系统的详细内容,更多关于Gotorch多机定时任务管理系统的资料请关注我们其它相关文章!

(0)

相关推荐

  • 解析Go的Waitgroup和锁的问题

    学 Go 的时候知道 Go 语言支持并发,最简单的方法是通过 go 关键字开启 goroutine 即可.可在工作中,用的是 sync 包的 WaitGroup,然而这样还不够,当多个 goroutine 同时访问一个变量时,还要考虑如何保证这些 goroutine 之间不会相互影响,这就又使用到了 sync 的 Mutex.它们是如何串起来的呢? 一.Goroutinue 先说 goroutine,我们都知道它是 Go 中的轻量级线程.Go 程序从 main 包的 main() 函数开始,在程

  • go xorm框架的使用

    一.创建数据库连接 xorm支持单引擎和多引擎,多引擎的场景对于初学者来说,没有必要考虑,所以我们主要学习的是单引擎的数据库连接,为了方便检查SQL,需要启用打印功能,默认不开启 var engine *xorm.Engine func main() { var err error engine, err = xorm.NewEngine("mysql", "root:123456@tcp(127.0.0.1:3306)/myschool?charset=utf8"

  • go 实现简易端口扫描的示例

    我在代码里定义了两个通道,分别用于生产端口和限制连接数,如果不限制连接数,容易被对方检测到或导致对方服务器不能正常运行. // 生产端口 var port = make(chan int, 10) // 限制并发数 var connect = make(chan string, 5) 可以使用net库的Dial函数做为socket客户端,需要注意的是要设置超时时间,因为若主机不存在,或目标端口是关闭的,往往需要花费数秒才返回错误,这样扫描大量端口时效率会极其低下.在go中可以使用net.Dial

  • Go语言 如何实现RSA加密解密

    RSA是一种非对称加密算法,它的名字是由它的三位开发者,即RonRivest.AdiShamir和LeonardAdleman 的姓氏的首字母组成的(Rivest-Shamir-Adleman ),可用于数据加密和数字签名. 用于数据加密时,消息发送方利用对方的公钥进行加密,消息接受方收到密文时使用自己的私钥进行解密. 实现代码如下: import ( "crypto/rsa" "crypto/rand" "crypto/x509" "

  • Go语言快速入门图文教程

    推荐阅读: go语言最新版激活教程可以点下这个链接查看. goland永久安装教程,点击此处查看. Go 这几年很火,小哈也蹭业余时间悄咪咪学习一下(我大 Java 依旧无敌

  • Go 自定义package包设置与导入操作

    package 包导入记录 首先需要知道2个值 $GOROOT $GOPATH 第二个值可以设置多个,2个值之间使用冒号:做连接. 系统找包的时候,会去对应路径下的src中去寻找.比如,我在path目录的src目录下新建文件夹HelloWorld,其中有一个文件printHelloWorld.go. 其对应的代码为: package HelloWorld import "fmt" func PrintHelloWorld(){ fmt.Print("Hello World\r

  • go语言基础 seek光标位置os包的使用

    语法: os.Open()-->*File,err ***** File.Seek(offset, whence),设置光标的未知 offset,偏移量 whence,从哪开始:0从头,1当前,2末尾 package main import ( "os" "fmt" ) func main() { /* seek(offset int, whence int),表示设置光标的位置 offset int,设置多少个字节 whence int,从哪开始 0,距离文

  • Go语言获取文件的名称、前缀、后缀

    示例代码: package main import ( "fmt" "path" ) func main() { filename := "device/sdk/CMakeLists.txt" filenameall := path.Base(filename) filesuffix := path.Ext(filename) fileprefix := filenameall[0:len(filenameall) - len(filesuffi

  • 详解Gotorch多机定时任务管理系统

    前言 先介绍下问题: 组内有十来台机器,上面用 cron 分别定时执行着一些脚本和 shell 命令,一开始任务少的时候,大家都记得哪台机器执行着什么,随着时间推移,人员几经变动,任务也越来越多,再也没人能记得清哪些任务在哪些机器上执行了,排查和解决后台脚本的问题也越来越麻烦. 解决这个问题也不是没有办法: 维护一个 wiki,一旦任务有变动就更新 wiki,但一旦忘记更新 wiki,任务就会变成孤儿,什么时候出了问题更不好查. 布置一台机器,定时拉取各机器的 cron 配置文件,进行对比统计,

  • springboot详解实现车险理赔信息管理系统代码

    目录 一,项目简介 二,环境介绍 三,系统展示 四,核心代码展示 五,项目总结 一,项目简介 客户的主要功能:个人资料管理,购买的保险信息管理,理赔的申请 事故调查员功能:个人资料管理,事故调查管理,现场勘察管理 管理员功能:个人资料管理,用户管理,理赔审请审核,赔偿金发放管理 二,环境介绍 语言环境:Java: jdk1.8 数据库:Mysql: mysql5.7 应用服务器:Tomcat: tomcat8.5.31 开发工具:IDEA或eclipse 开发技术:后台springboot+sp

  • Java详解实现ATM机模拟系统

    目录 一.概述 二.程序概要设计 三.程序详细设计 四.程序演示 一.概述 (1)选题分析 (2) 开发环境 开发环境,选择IDEA这一Java开发软件,基于JDK1.8版本,在本机window上开发本ATM模拟程序. 二.程序概要设计 (1) 功能模块设计 经过对题目的分析,把本ATM模拟程序分为管理员端和用户模式两大模块.其中,管理员具有查询所有账户.导出所有账户信息到文件.注销功能.用户模块具有查询余额.ATM转账.ATM存款.ATM取款.修改密码.查询交易记录.导出记录.退卡等功能. 系

  • 详解Python做一个名片管理系统

    名片管理系统有两个模块组成:cards_main.py 和 cards_tools.py一个是主程序,另一个是封装增删改查函数的被调用程序 代码如下 cards_main.py #! /usr/bin/python # -*- coding:utf-8 -*- import cards_tools # 无限循环,用户决定什么时候退出循环! while True: # TODO(作者) 显示功能菜单 # TODO 提醒要做的事情 cards_tools.show_menu() action_str

  • Navicat11全系列激活教程图文详解(Navicat注册机)

    目录 工具: Navicat注册机 Navicat是一款商业软件,所以他仅提供试用. 下面介绍一下通过注册机的方式激活Navicat 11 for Windows系列软件的方法. 工具: 链接: https://pan.baidu.com/s/1dCSChAD1hVGzLsZ5ZcOE3g 提取码: 6dhe 网盘下载地址: 需要的小伙伴,请关注微信公众号: Java技术迷, 或者扫描下方公众号二维码,回复关键字:210809, 即可免费无套路获取. 1. 安装 Navicat 11 for W

  • 详解spring多线程与定时任务

    本篇主要描述一下spring的多线程的使用与定时任务的使用. 1.spring多线程任务的使用 spring通过任务执行器TaskExecutor来实现多线程与并发编程.通常使用ThreadPoolTaskExecutor来实现一个基于线程池的TaskExecutor. 首先你要实现AsyncConfigurer 这个接口,目的是开启一个线程池 代码如下: package com.foreveross.service.weixin.test.thread; import java.util.co

  • 详解django中使用定时任务的方法

    今天介绍在django中使用定时任务的两种方式. 方式一: APScheduler 1)安装: pip install apscheduler 2)使用: from apscheduler.scheduler import Scheduler from django.core.cache import cache # 实例化 sched = Scheduler() # 每30秒执行一次 @sched.interval_schedule(seconds=30) def sched_test():

  • 详解PHP实现执行定时任务

    PHP在这方面应该说是比较弱,如果只用php去实现可以如下: <?php ignore_user_abort();//关闭浏览器后,继续执行php代码 set_time_limit(0);//程序执行时间无限制 $sleep_time = 1;//多长时间执行一次 do{ $fp = fopen('test.txt','a+'); fwrite($fp,"这是一个php博客:phpddt.com \n"); fclose($fp); sleep($sleep_time); }wh

  • 详解使用VUE搭建后台管理系统(vue-cli更新至3.0)

    最近还没来得及更新文章,就发现vue-cli已经更新到3.0版本了. //想了想还是用升级吧,反正最终都逃不掉,不如在这个项目上实验一下3.0的威力(并不会). 升级vue-cli npm install -g vue@cli vue -V 使用图形化界面创建项目 这里需要到自定义目录下,方便以后的项目管理:执行命令后,会打开一个localhost:8080的窗口,是vue项目的图形化管理界面 cd targetFolder vue ui 创建新项目 这里不一一介绍了,随便截了两张图看看 这里配

  • Linux定时任务Crontab命令使用详解与总结

    crontab命令是Unix和Linux用于设置周期性被执行的指令,是互联网很常用的技术,很多任务都会设置在crontab循环执行 如果不使用crontab,那么任务就是常驻程序,这对你的程序要求比较高,一个要求你的程序是24X7小时不宕机,一个是要求你的调度程序比较可靠,实际工作中,90%的程序都没有必要花这么多时间和精力去解决上面的两个问题的,只需要写好自己的业务逻辑,通过Crontab这 工业级程序去调度就行了,Crontab的可靠性,健壮性,大家应该是毫无疑问的. crontab命令简易

随机推荐