Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kratos: task & job project layout #411

Closed
Terry-Mao opened this issue Nov 5, 2019 · 18 comments
Closed

kratos: task & job project layout #411

Terry-Mao opened this issue Nov 5, 2019 · 18 comments

Comments

@Terry-Mao
Copy link
Member

背景

使用Kratos的朋友,创建项目后把task类型(定时任务)当作了service常驻在docker或者物理机上运行,这样非常浪费资源且无意义,从设计上我们需要区分三种角色:service(微服务,提供API)、job(流式服务,处理实时流)、task(定时任务,定时处理业务)

需要讨论和设计下task的project layout,欢迎讨论

@realityone
Copy link
Contributor

可以参考 kubernetes 的 Job 设计,每个 Job 在运行时实际上就是创建了一个或一组 Pod,并通过控制器保证其运行完成。对 Job 的管理也会作用于 Job 所产生的 Pod 上。

从简单的跑批任务上看,通过 kubernetes 的 CronJob 即可完成,CronJob 控制器会根据类似 Cron 的定义来调度任务。

例:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

该任务中通过 schedule 字段描述任务的启动规律,而 containers 字段则描述了改任务直接对应容器命令。简单来讲,该配置想表达的任务为:

  • 每分钟运行一次 /bin/sh -c 'data; echo Hello from the Kubernetes cluster'

其中 restartPolicy 从一定程度上表达了对任务异常的处理方案(失败时重启)。

由此为基础对于 kratos 中的 Task 需求,我们可以简单制定一些方向:

  • Task 和 Job 可以共用一份代码,甚至可以直接共用一份编译后的 binary,以达到简化应用配置的目的
  • 各个 Task 可以作为启动的 flag,例:/bin/account-job -task=purge-cache -conf=account-job.toml,其中 -task=purge-cache 就指定了本次运行 Task,-conf=account-job.toml 就指定了本次运行的配置。

从代码结构上看,每个 Task 需要在启动后预先被注册到统一位置以便于 -task flag 统一查找对应的函数。例:

package main

import (
	"context"
	"sync"
)

type taskRegistry struct {
	sync.RWMutex
	tasks map[string]func(*context.Context) error
}

var globalTaskRegistry = taskRegistry{
	tasks: map[string]func(*context.Context) error{},
}

func Register(name string, fn func(*context.Context) error) {
	globalTaskRegistry.Lock()
	globalTaskRegistry[name] = fn
	globalTaskRegistry.UnLock()
}

func Get(name string) (func(*context.Context) error, bool) {
	globalTaskRegistry.RLock()
	fn, ok := globalTaskRegistry[name]
	globalTaskRegistry.RUnLock()
	return fn, ok
}

当业务想要新增一个定时 Task 时,只需关心 Task 的业务逻辑和 Task 所需的依赖配置,并将其注册至 Task 注册中心。而 Task 的运行时间周期、任务失败重试等等功能将交付 kubernetes 来保证,以保证业务逻辑中始终是单纯和朴素的。

@MarsonShine
Copy link

这个没有下文了么?

@MarsonShine
Copy link

我认为 #916 的这种 layout 是高度符合目前 kratos 结构的

@Windfarer
Copy link
Member

Windfarer commented Jul 20, 2021

延续2楼老铁的思路,即task和job(下文简称为“任务”)的调度基于外部系统进行,我们的项目结构设计仅专注于如何整洁地组织启动时的逻辑。

任务特点

  1. 任务不需要kratos.App实例上的生命周期管理(如Start,Stop和服务注册注销等)(有部分应用信息仍然需要)
  2. 任务的定义类似接口定义,CLI调用某个任务(某个API),传入某些参数(请求参数),但是可能不要求返回值
  3. 任务不需要server层进行的接口注册逻辑(不需要提供HTTP或gRPC接口)
  4. 具体的业务在biz层的use case实例中,对于底层数据持久化或者外部数据调用实现在data层的repo中,这些部分结构定义良好,可以沿用或复用
  5. task是跑一次就结束退出,job是持续在线进行处理(如消息队列的消费者)

设计

基于以上特点,我们可以使用proto来定义任务的接口(任务名和参数),编写一个新的protoc插件来进行代码生成,生成的代码主要用来处理任务名和参数的解析,维护任务的生命周期,进行相应的处理。

对于配置/参数的区分,我们认为配置文件是较稳定的,可以继续按照原来的形式进行加载和解析,而参数(命令行参数)可能在每次run的时候需要传入不同的参数,因此进行独立的定义和解析。

最终整个任务相关的代码在项目中将被分为四部分:

  1. Manager实例,用于管理任务的应用信息和生命周期(相当于原来的kratos.App)
  2. 通过protoc生成的任务定义代码,提供方法名映射和命令行参数解析等
  3. task层代码,进行基本的业务逻辑组装,相当于原来的service
  4. 底层业务代码,即biz和data与之前保持一致

命令行界面

your_binary -conf=./path/toconf -task=sync_data -some_arg1=foo -some_arg2=1

项目结构

使用方面,可以在cmd下单独开一个task目录,并且使用独立的wire进行依赖注入

.
├── cmd
│   ├── server
│   └── task
│       ├── main.go
│       ├── wire.go
│       └── wire_gen.go
└── internal
    ├── biz
    │   └── biz.go
    ├── data
    │   └── data.go
    └── task
        ├── task.go
        ├── task.proto
        └── task.pb.go

下图左边为普通的service项目结构,右边为任务项目结构

┌──────────────┐             ┌──────────────┐
│     App      │             │   Manager    │
└──────────────┘             └──────────────┘
┌──────────────┐             ┌──────────────┐    ┌───────┐
│    server    │       *     │generated code◀────┤ proto │
└──────────────┘       **    └──────────────┘    └───────┘
┌──────────────┐   *******   ┌──────────────┐
│   service    │   ********  │     task     │
└──────────────┘   *******   └──────────────┘
┌──────────────┐       **    ┌──────────────┐
│     biz      │       *     │     biz      │
└──────────────┘             └──────────────┘
┌──────────────┐             ┌──────────────┐
│     data     │             │     data     │
└──────────────┘             └──────────────┘

Proto文件样例

syntax = "proto3";
package server.task;

option go_package = "server/internal/task;task";

import "google/protobuf/duration.proto";
import "google/protobuf/empty.proto";


service Task {    
    rpc SyncData (SyncDataArgs) returns (google.protobuf.Empty)
    rpc CleanData (CleanDataArgs) returns (google.protobuf.Empty)
}

message SyncDataArgs {
    string some_arg1 = 1;
}

message CleanDataArgs {
    string some_arg1 = 1;
}


service Job {
    rpc JobA (JobAArgs) returns (google.protobuf.Empty)
    rpc JobB (JobBArgs) returns (google.protobuf.Empty)
}

message JobAArgs {
    string some_arg1 = 1;
}

message JobBArgs {
    string some_arg1 = 1;
}

延伸

protoc插件如何判断哪个是task定义,哪个是job定义,是否需要引入额外的option进行标记?

对于持续在线的job类型,是否需要提供元数据接口或者服务注册,提供一定的可观测性?

@MarsonShine
Copy link

MarsonShine commented Jul 20, 2021

对于配置/参数的区分,我们认为配置文件是较稳定的,可以继续按照原来的形式进行加载和解析,而参数(命令行参数)可能在每次run的时候需要传入不同的参数,因此进行独立的定义和解析。

我觉得还有一种启动方式可以考虑一下,不一定非得依靠每次 run 指定参数去启动(当 once task 这类不在这次范围内),而可以直接通过配置文件(类似 k8s),将参数配置化,利用代码生成工具直接启动时注入。

@tonybase tonybase mentioned this issue Jul 25, 2021
16 tasks
@tonybase tonybase added this to the v2.2.0 milestone Jul 28, 2021
@ninjacn
Copy link

ninjacn commented Sep 26, 2021

提个想法
task功能主要包括两块:一是定时任务,二是一次性执行任务。 在k8s中分别对应cronJob和Job。
从代码层面看都是一样的,只是调度走k8s、linux cron或其它三方调度平台。

可以在internal加个目录,如commands或tasks等, 该目录下每个文件对应一个任务、一个指令; cli入口文件需要增加一个命令名就行。

task功能对于框架来说应该是刚需,即使是微服务

@shcw
Copy link

shcw commented Mar 1, 2022

提个想法 task功能主要包括两块:一是定时任务,二是一次性执行任务。 在k8s中分别对应cronJob和Job。 从代码层面看都是一样的,只是调度走k8s、linux cron或其它三方调度平台。

可以在internal加个目录,如commands或tasks等, 该目录下每个文件对应一个任务、一个指令; cli入口文件需要增加一个命令名就行。

task功能对于框架来说应该是刚需,即使是微服务

+1 我现在也遇到了这样的问题 但是觉得应该是从cmd/下加一个 开始

@shcw
Copy link

shcw commented Apr 25, 2022

有计划对Kratos添加一个计划任务的入口 或者 较好的实践嘛

@shenqidebaozi shenqidebaozi removed this from the v2.3.0 milestone May 24, 2022
@pwli0755
Copy link
Contributor

boom

@huiwanggo
Copy link

关注,没下文了吗?

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Pay attention, no more text?

@xhsui
Copy link

xhsui commented Jun 19, 2023

我们现在也碰到这个问题了

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


We also have this problem now

@flc1125
Copy link
Contributor

flc1125 commented Aug 7, 2023

我建议通过 https://github.com/urfave/cli 作为主应用入口,而 server 启动只是其中一个命令之一。这样可以解决我扩充其他一次性命令的内容。但依此,就需要实现一个共同初始化的服务(比如 bootstrap ),这样多命令下(比如:读取配置等)可复用。

PS: 如果是定时服务,需要考虑多 Pod 服务下的同时执行问题。我在想如果依靠如上服务,我是不是也可以理解为单独启动一个 定时服务(单独 Pod)就好,就可以解决多 Pod 同时执行的问题。否则就得依赖第三放分布式存储解决 OneServer 的问题。

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


I recommend using https://github.com/urfave/cli as the main application entry, and server startup is just one of the commands. This solves my content of augmenting other one-off commands. But according to this, it is necessary to implement a common initialization service (such as bootstrap), so that the commands can be reused.

PS: If it is a scheduled service, you need to consider the simultaneous execution of multi-Pod services. I am wondering if I rely on the above services, can I also understand that starting a timing service (single Pod) alone can solve the problem of simultaneous execution of multiple Pods. Otherwise, you have to rely on third-party distributed storage to solve OneServer problems.

@flc1125
Copy link
Contributor

flc1125 commented Aug 7, 2023

我建议通过 urfave/cli 作为主应用入口,而 server 启动只是其中一个命令之一。这样可以解决我扩充其他一次性命令的内容。但依此,就需要实现一个共同初始化的服务(比如 bootstrap ),这样多命令下(比如:读取配置等)可复用。

PS: 如果是定时服务,需要考虑多 Pod 服务下的同时执行问题。我在想如果依靠如上服务,我是不是也可以理解为单独启动一个 定时服务(单独 Pod)就好,就可以解决多 Pod 同时执行的问题。否则就得依赖第三放分布式存储解决 OneServer 的问题。

补充:

  • Kratos 更像是一个基于网络协议层面的启动服务。我觉得格局可以打开些,可以是一个应用启动的服务,而不限于网络协议层的启动服务。(PS:前阵子通过 Cron,基于 Transport 的 Server 实现了个定时服务,但 Transport 是网络服务,设计起来就很奇怪。。。)

@kratos-ci-bot
Copy link
Collaborator

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


I recommend using urfave/cli as the main application entry, and server startup is just one of the commands. This solves my content of augmenting other one-off commands. But according to this, it is necessary to implement a common initialization service (such as bootstrap), so that it can be reused under multiple commands (such as: reading configuration, etc.).

PS: If it is a scheduled service, you need to consider the simultaneous execution of the multi-Pod service. I am wondering if I rely on the above services, can I also understand that starting a timing service (single Pod) alone can solve the problem of simultaneous execution of multiple Pods. Otherwise, you have to rely on third-party distributed storage to solve OneServer problems.

Replenish:

  • Kratos is more like a startup service based on the network protocol layer. I think the pattern can be more open, it can be a service started by an application, not limited to the start service of the network protocol layer.

Copy link

dosubot bot commented Nov 6, 2023

Hi, @Terry-Mao. I'm Dosu, and I'm helping the Kratos team manage their backlog. I wanted to let you know that we are marking this issue as stale.

From what I understand, you are requesting a discussion and design for the project layout of the task role in the Kratos framework. You propose distinguishing between service, job, and task roles and believe that treating task types as services is wasteful and unnecessary. There have been discussions and suggestions from other users, including the recommendation to use urfave/cli as the main application entry and to implement a common initialization service. However, there hasn't been a resolution yet.

Before we close this issue, we wanted to check if it is still relevant to the latest version of the Kratos repository. If it is, please let us know by commenting on the issue. Otherwise, feel free to close the issue yourself or it will be automatically closed in 7 days.

Thank you for your contribution to the Kratos framework!

@dosubot dosubot bot added the stale Issue has not had recent activity or appears to be solved. Stale issues will be automatically closed label Nov 6, 2023
@dosubot dosubot bot closed this as not planned Won't fix, can't repro, duplicate, stale Nov 13, 2023
@dosubot dosubot bot removed the stale Issue has not had recent activity or appears to be solved. Stale issues will be automatically closed label Nov 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests