分布式任务调度框架基本能力:
- 任务管理能力(增删改查、执行、定时执行、延时执行、健康监控)
- 集群管理能力(扩展简单、效率高)
- 编程能力(运行代码)
- Web界面管理
目前市面上有很多可用于处理分布式任务的开源框架,比如Elastic-Job
、XXL-Job
、Quartz
等等,它们或多或少都能满足以上列出的各项基本能力,然而,它们都是以Java
系语言编写的,这不禁让人产生疑问,难道Go
就没有自己的开源任务调度框架吗?
经过在网络上一番翻箱倒柜,找到了类似gocron
、gocraft/work
、robfig/cron
等一系列工具。然而它们或者年久失修;或者不足以称之为框架(顶多是个三方库);或者没有界面;等等问题不一而足。但是终于,还是发现了这款堪称满足对分布式调度框架所有幻想的开源项目: Temporal 。目前Star数还不是很多,但是不妨碍它成为一款golang编写的最佳分布式任务调度框架的潜质。不多废话,赶紧来介绍给大家看看。
在Temporal的眼中,对自己的定位是 microservice orchestration platform
,提供工作流编排、C\S架构、状态查询等一系列功能。在我看来,它基本覆盖了以上列出的四点能力,我们以一个架构图开始它的介绍:
我们从左往右看,依次出现的几个角色是Activity
、Workflow
、Workers
、Temporal Server
、CLI
、Web
、SDK
。我们列一张表对它们进行简单介绍:
角色 | 说明 |
---|---|
Activity | 一段可运行代码(function),可以包含任何逻辑。由于Temporal提供了各种语言的SDK(go、java、python、php等等)所以Activity是不限制语言的。 |
Workflow | Activity的集合,多个Activity可以构成一个Workflow,也是调度的最小单位。 |
Workers | 不同语言写的Workflow可以注册到对应语言的Worker中,Worker是代码的真正执行者 |
Temporal | 管理注册到自己的Workers,向Workers下发任务,监听任务状态等等 |
CLI or SDK | 任务的发起者、监控任务进度等 |
Web | 负责任务的监控、查询等。 |
这个架构是不是有点类似生产者-消费者模型,CLI or SDK
是生产者,负责启动某个Workflow;Temporal
类似 kafka\RocktMQ ,负责把请求调度某个Worker;Workers
是消费者,负责执行请求。但深入思考一下,我还是觉得跟消息队列有很大差别:
1. 更关注任务的执行
消息队列的核心是消息,生产者不关心这个消息被谁消费,消息队列关注的是消息是否送达。
而 Temporal ,关注的是任务,关注任务执行进度和结果,是否需要重启等等。
2. 需要提前设计好Workflow
消息队列不需要关注消费者怎么消费消息的。而 Temporal ,你必须先把Workflow的逻辑
写好。
所以这两个系统还是不太一样的,选择 Temporal
,是因为我们想执行某个预定任务,并保证它执行成功。比如,交易系统每晚定时对账;注册系统过段时间自动向用户发送短信等。虽然用MQ也能实现,但可能要做很多错误处理等工作。
Temporal 示例
Talk is cheap, show me the code.
接下来用一个简单的示例展示下 Temporal
的使用流程,场景是延时1小时向用户发送短信和邮件。
1. 先编写Activity
的代码:
type MessageRequest struct {
PhoneNum string
Content string
Tags []string
}
func SendMessage(ctx context.Context, mr MessageRequest) (mresp MessageResponse, error) {
fmt.Printf(
"\nSending message to %s \n Content is %s \n ",
mr.PhoneNum,
mr.Content,
)
return nil, nil
}
type EmailRequest struct{
From string
To string
Content stirng
}
func SendEmail(ctx context.Context, er EmailRequest) error{
fmt.Printf(
"\nSending email to %s \n Content is %s \n ",
er.To,
er.Content,
)
return nil, nil
}
Activity
的代码的限制其实是很少的,实际上,对函数签名没有什么限制,什么样的函数都可以成为Activity
,甚至结构体上的方法也可以。
2. Workflow 的代码
func SendMessageWorkflow(ctx workflow.Context, msq MessageRequest, er EmailRequest) error {
options := workflow.ActivityOptions{
StartToCloseTimeout: time.Minute,
}
ctx = workflow.WithActivityOptions(ctx, options)
// 设计工作流
// 1. 先执行SendMessage 活动
err := workflow.ExecuteActivity(ctx, SendMessage, msq).Get(ctx, nil)
if err != nil {
return err
}
// 2. 再执行 SendEmail 活动
err = workflow.ExecuteActivity(ctx, SendEmail, msq).Get(ctx, nil)
if err != nil {
return err
}
return nil
}
其实 Workflow
也是一段函数,我们可以在这段函数里写逻辑,那为什么还要Activity
呢?我直接把所有逻辑写到Workflow
里不就好了。
这样设计主要是Workflow
里的代码有些限制,比如不能和外部系统交互(读写文件、访问网络)等,而Activity
就没有这种限制。
3. 启动 Workers
func main() {
// 连接到 Temporal Server,注册自己
c, err := client.NewClient(client.Options{})
if err != nil {
log.Fatalln("unable to create Temporal client", err)
}
defer c.Close()
w := worker.New(c, app.TaskQueue, worker.Options{})
w.RegisterWorkflow(app.SendMessageWorkflow)
w.RegisterActivity(app.SendMessage)
w.RegisterActivity(app.SendEmail)
// Start listening to the Task Queue
err = w.Run(worker.InterruptCh())
if err != nil {
log.Fatalln("unable to start Worker", err)
}
}
最后,我们启动一个Worker
,把它注册到TemporalServer,再把Workflow
和Activity
注册到它内部。这个Worker
就启动成功了。
如果结合K8s,我们还能编排不同的Worker
,比如:给Worker
扩缩容,监控Worker
等。
4. 启动 TemplateServer
按照官网的文档启动即可。
5. 发起任务
func main() {
// 先连上 TemplateServer
c, err := client.NewClient(client.Options{})
if err != nil {
log.Fatalln("unable to create Temporal client", err)
}
defer c.Close()
options := client.StartWorkflowOptions{
TaskQueue: app.TaskQueue,
}
r1 := app.MessageRequest{
...
}
r2 := app.EmailRequest{
...
}
we, err := c.ExecuteWorkflow(context.Background(), options, "SendMessageWorkflow", transferDetails)
if err != nil {
log.Fatalln("error starting SendMessageWorkflow", err)
}
printResults(we.GetID(), we.GetRunID())
}
最后,我们使用SDK提供的方法启动Workflow
,TaskQueue用于路由到Worker,再提供Workflow
的函数名,就可以调用成功了。
Temporal 总结
至此,非官方的简单介绍就完毕了,Temporal的官网:docs.temporal.io/ 有更详细的介绍、示例代码等,比我这里的更加详细,可以跳转过去查看。
如果有小伙伴发现 Go
语言中有更好更强大的任务调度框架,也可以在评论区交流,也许还有很好的开源项目,等待被发掘出来~