golang+数据库定时任务

golang+数据库定时任务


   项目背景大致如下,楼主在用nodejs写项目时遇到一些需要定时去处理的事情,例如僵尸用户定时清除,一些产品定时下架,邮件定时发送等等! 期初使用nodejs setTimeOut递归嵌套实现,后来发现内存不断飙升,故而放弃,最终改用了性能不错的golang实现

数据库设计



字段名称 含义
id 编号
name 任务名称
create_at 创建时间
type 1. 执行一次 2.循环执行
separate_time 执行间隔
status 执行状态 0.未开始 1. 执行中 -1.执行失败 -2.手动暂停
remark 备注信息
fn 要执行的数据库存储过程或函数
start_time 开始执行时间
next_exec_time 下次执行时间
last_exec_time 上次执行时间
fn_type email, sql 等等

大致实现流程


  1. 需要有一个死循环,sleep 10s启动然后sleep 10 …
    		for {
			time.Sleep(10 * time.Second)
			go execTask(*db) //使用子进程执行,防止卡死主进程
		}
  1. 开始执行,查找需要执行的任务
rows, err := db.Query("SELECT id,name,status,type,fn,fn_type, separate_time FROM public.tasks where (status = 0 and start_time < now()) or (status = 1 and next_exec_time < now());")
  1. 执行任务
res, err := db.Exec(fn)
  1. 执行任务成功后,更新下次执行时间
func setTaskNextExecTime(db sql.DB, taskId string, separateTime int64) error {
	next_exec_time := time.Now().Unix() + separateTime
	nextTime := time.Unix(next_exec_time, 999)
	res, err := db.Exec("UPDATE tasks set status = 1, last_exec_time=now(), next_exec_time=$2 WHERE id = $1::uuid", taskId, nextTime)
	res = nil
	log.Println(res)
	return err;
}

优缺点


    优点:
        1. 所有任务执行状态都可以查询到,例如任务异常或者上次执行时间,下次执行时间
        2. 增加一个定时任务,只需要在数据库插入一条记录就OK
    缺点:
        1. 如果要绑定非数据库可操作任务,需要自己扩展

项目源码


// MTask project main.go
package main

import (
	"database/sql"
	_ "github.com/lib/pq"
	"log"
	"time"
	"os"
	"io/ioutil"
	"encoding/json"
)

//配置结构体
type Conf struct {
	Db map[string] string
}

//读取配置文件
func readConf(path string) (Conf, error) {
	var c Conf
	var err error

	fi, err := os.Open(path)
	if err != nil {
		return c, err
	} else {
		defer fi.Close()

		//读取配置文件
		fd, err := ioutil.ReadAll(fi)
		if err != nil {
			return c, err
		} else {
			var c Conf
			err = json.Unmarshal(fd, &c)
			if err != nil {
				return c, err
			} else {
				return c, err
			}
		}
	}
	return c, err
}

func main() {
	c, err := readConf("./conf.json")
	if err != nil {
		log.Print(err)
		panic(err)
	}
	db, err := sql.Open("postgres", c.Db["postgres"])
	if err != nil {
		log.Print(err)
	} else {
		defer db.Close()
		for {
			time.Sleep(10 * time.Second)
			go execTask(*db)
		}
	}
}

func execTask(db sql.DB) {
	defer func() {
		if err := recover(); err != nil {
			log.Print(err)
			log.Printf("执行任务时发生错误:%s", err)
		}
	}();

	log.Println("开始执行任务.......")
	rows, err := db.Query("SELECT id,name,status,type,fn,fn_type, separate_time FROM public.tasks where (status = 0 and start_time < now()) or (status = 1 and next_exec_time < now());")
	if err != nil {
		log.Print(err)
	} else {
		defer rows.Close()
		for rows.Next() {
			var id string
			var name string
			var status int
			var taskType int
			var separateTime int64
			var fn string
			var fnType string

			err = rows.Scan(&id, &name, &status, &taskType, &fn, &fnType, &separateTime)

			if err != nil {
				//记录错误,同时更新任务信息为异常
				log.Print(err)
				err = setTaskExecFail(db, id)
				if err != nil {
					log.Print(err)
				}
			} else {
				if (fnType == "sql") {
					res, err := db.Exec(fn)
					if err != nil {
						log.Print(err)
						err = setTaskExecFail(db, id)
						if err != nil {
							log.Print(err)
						}
						log.Printf("任务:%s执行时出错", name)
					} else {
						res = nil
						log.Println(res)

						if taskType == 1 {
							err = setTaskExecSuccess(db, id)
							if err != nil {
								log.Print(err)
							}
							log.Printf("任务:%s执行完成", name)
						} else {
							err = setTaskNextExecTime(db, id, separateTime)
							if err != nil {
								log.Print(err)
							}
						}
						log.Printf("任务:%s执行成功", name)
					}
				} else if (fnType == "bash") {
					log.Printf("这是一个bash任务")
				} else if (fnType == "python") {
					log.Printf("这是一个python任务")
				} else if (fnType == "email") {
					//发送email任务
					err = ExecEmailTask(db)
					if err != nil {
						handleFail(db, id)
						log.Println(err)
					} else {
						handleSuccess(db, id)
					}
					log.Printf("发送邮件任务")
					setTaskExecSuccess(db, id)
					setTaskNextExecTime(db, id, separateTime)
				} else if (fnType == "sms") {
					//发送短信任务
					log.Printf("发送短信任务")
				}

			}
		}

		err = rows.Err()
		if err != nil {
			log.Print(err)
		}
	}
	log.Println("结束执行任务....")
}

func setTaskExecFail(db sql.DB, taskId string) error {
	res, err := db.Exec("UPDATE tasks set status = -2 WHERE id = $1::uuid", taskId)
	err = nil
	log.Println(res)
	return err
}

func setTaskExecSuccess(db sql.DB, taskId string) error {
	res, err := db.Exec("UPDATE tasks set status = 2 WHERE id = $1::uuid", taskId)
	err = nil
	log.Println(res)
	return err
}

func setTaskNextExecTime(db sql.DB, taskId string, separateTime int64) error {
	next_exec_time := time.Now().Unix() + separateTime
	nextTime := time.Unix(next_exec_time, 999)
	res, err := db.Exec("UPDATE tasks set status = 1, last_exec_time=now(), next_exec_time=$2 WHERE id = $1::uuid", taskId, nextTime)
	res = nil
	log.Println(res)
	return err;
}
时间: 2024-10-10 05:21:33

golang+数据库定时任务的相关文章

【原创】数据库定时任务特性的妙用

数据库定时执行任务,在业界流行的各种数据库基本都有这项功能,这项功能极大的方便了我们定时处理一些事务,在笔者的web应用开发经验中发现其两大妙用: 一.判断浏览器用户是否在线 在我们的web应用里经常会遇到用户登录后要长期保持登录状态,系统要能统计当前在线用户数量这样的需求,我们可以使用ajax定时触发调用后台脚本向数据库登录状态表中写入最近一次的脚本调用时间,假设我们每1分钟触发一次ajax脚本,我们不妨称之为心跳,在后台数据库中我们启用一个定时任务,假设也是每1分钟触发,用于检查当前时间距离

Golang 数据库操作

刚开始接触Golang,只是随笔记下几个方法,原理还不是很清晰,暂时只是能实现的地步,随笔几点吧 1.数据库连接 var { dbhostip = "" dbhostport = "" dbhostuser = "" dbhostpassword = "" } //返回db连接 func DbOpen() (db_obj *sql.DB, err error){ db_obj,err_open := sql.Open(&quo

oracle数据库定时任务

应用系统运行中,经常需要定时执行一些任务,例如:定时更新汇总数据,定时更新状态数据等,目前 Treesoft数据库管理系统 增加[定时任务]功能,直接通过页面简单配置,即可按调度规则定时执行SQL任务.执行结果日志直观查看,定时任务维护方便. 原文地址:https://www.cnblogs.com/treesoft/p/9424932.html

golang数据库操作初体验

在golang中,提供了标准的数据库接口database/sql包,做过数据库开发的应该知道,不同的数据库有不同的数据库驱动.比如mysql等,我们可以去找 https://golang.org/s/sqldrivers 这里找自已需要的驱动,这里我就以mysql的驱动为例,用的是go-sql-driver这个. 安装 直接执行go get,然后会下载到你的$GOPATH中,如果用的go mod也一样,只不过下载的路径不一样. go get -u github.com/go-sql-driver

golang cron定时任务简单实现

目录 星号(*) 斜线(/) 逗号(,) 连字符 (-) 问好 (?) 使用说明 golang 实现定时服务很简单,只需要简单几步代码便可以完成,不需要配置繁琐的服务器,直接在代码中实现. 使用 https://github.com/robfig/cron 这个包,它实现了 cron 规范解析器和任务运行器. cron 介绍 参见:https://godoc.org/github.com/robfig/cron 用法 注册在指定时间上运行的函数,cron 将会在协程中运行这些注册函数. AddF

数据库定时任务

<!--删除定时任务-->DROP EVENT blog_info_delete; <!--创建存储过程-->CREATE PROCEDURE blog_info() BEGIN delete from blog_info where DATE(creater_time) <= DATE(DATE_SUB(now(),INTERVAL 7 DAY)); END; <!--创建定时任务调用存储过程-->create event blog_info_delete on

golang数据库操作

使用go-sql-driver操作myql数据库 package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" "fmt" ) func main() { //连接数据库,获得连接句柄 db, err := sql.Open("mysql", "root:[email protected](localhost:3306)/huifang

oracles数据库定时任务

创建定时任务使用plsql工具: 1. 一.dbms_job涉及到的知识点 1.创建job: variable jobno number;dbms_job.submit(:jobno, --job号 'your_procedure;',--执行的存储过程, ';'不能省略 next_date, --下次执行时间 'interval' --每次间隔时间,interval以天为单位);-–系统会自动分配一个任务号jobno.2.删除job: dbms_job.remove(jobno); 3.修改要

ORACLE数据库定时任务—DBMS_JOB

创建 DBMS_JOB 使用以下语句: ? 1 2 3 4 5 6 7 8 9 10 VARIABLE jobno number; begin   DBMS_JOB.SUBMIT(     :jobno, --job号,ORACLE自动分配     'your_procedure;',  --执行的存储过程或SQL语句,';'不能省略     next_date, --下次执行时间     'interval' --每次间隔时间,以天为单位   );     commit; end; 例子: 作