mirror of
https://github.com/PowerJob/PowerJob.git
synced 2025-07-17 00:00:04 +08:00
[dev] write guide document
This commit is contained in:
parent
b295f81e01
commit
d5a52fef57
40
README.md
40
README.md
@ -3,53 +3,31 @@
|
||||
OhMyScheduler是一个分布式调度平台和分布式计算框架,具有以下特性:
|
||||
* 支持CRON、固定频率、固定延迟和API四种调度策略。
|
||||
* 支持单机、广播、**MapReduce**三种执行模式。
|
||||
* 支持在线查看任务运行过程中的日志,提供分布式日志解决方案。
|
||||
* 支持任意的水平扩展,性能强劲无上限。
|
||||
* 具有强大的故障转移与恢复能力,只要保证集群可用节点数足够,任务就能顺利完成。
|
||||
* 仅依赖数据库,部署简单,上手容易,开发高效,仅需几行代码即可获得整个集群的分布式计算能力。
|
||||
* 最小仅依赖数据库,部署简单,上手容易,开发高效,仅需几行代码即可获得整个集群的分布式计算能力。
|
||||
* 支持SpringBean、普通Java类(内置/外置)、Shell、Python等处理器。
|
||||
|
||||
# 部署
|
||||
### 环境要求
|
||||
* 运行环境:JDK8+
|
||||
* 编译环境:Maven3+
|
||||
* 数据库:Spring Data JPA支持的关系型数据库理论上都可以(MySQL/Oracle/MS SQLServer...)
|
||||
# 接入流程(文档不要太详细,简单强大兼得说的就是在下~)
|
||||
1. [项目部署及初始化](https://github.com/KFCFans/OhMyScheduler/blob/master/others/doc/SystemInitGuide.md)
|
||||
2. [处理器开发](https://github.com/KFCFans/OhMyScheduler/blob/master/others/doc/ProcessorDevGuide.md)
|
||||
3. [任务配置与在线查看](https://github.com/KFCFans/OhMyScheduler/blob/master/others/doc/ConsoleGuide.md)
|
||||
4. [(强大灵活的扩展——OpenAPI)](https://github.com/KFCFans/OhMyScheduler/blob/master/others/doc/OpenApiGuide.md)
|
||||
|
||||
### 依赖坐标
|
||||
>最新依赖版本请参考Maven中央仓库:[推荐地址](https://search.maven.org/search?q=com.github.kfcfans)&[备用地址](https://mvnrepository.com/search?q=com.github.kfcfans)。
|
||||
|
||||
Worker依赖包(需要接入分布式计算能力的应用需要依赖该jar包)
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.kfcfans</groupId>
|
||||
<artifactId>oh-my-scheduler-worker</artifactId>
|
||||
<version>${oms.worker.latest.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
Client依赖包(需要接入OpenAPI的应用需要依赖该Jar包,使用代码调用控制台功能)
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.kfcfans</groupId>
|
||||
<artifactId>oh-my-scheduler-client</artifactId>
|
||||
<version>${oms.client.latest.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 项目部署
|
||||
1. 部署数据库:由于调度Server数据持久化层基于Spring Data Jpa实现,**开发者仅需完成数据库的创建**,即运行SQL`CREATE database if NOT EXISTS oms default character set utf8mb4 collate utf8mb4_unicode_ci;`
|
||||
2. 部署调度服务器(oh-my-scheduler-server):修改配置文件(application.properties),按需修改,之后maven打包部署运行一条龙。
|
||||
3. 部署前端页面(可选,server多实例部署时需要,单Server已经继承在SpringBoot中),自行使用[源码](https://github.com/KFCFans/OhMyScheduler-Console)打包部署即可。
|
||||
4. 被调度任务集成`oh-my-scheduler-worker`依赖,并完成处理器的开发,详细教程见[开发文档](https://github.com/KFCFans/OhMyScheduler/blob/master/others/doc/DevelopmentGuide.md)。
|
||||
|
||||
# 开发日志
|
||||
### 已完成
|
||||
* 定时调度功能:支持CRON表达式、固定时间间隔、固定频率和API四种方式。
|
||||
* 任务执行功能:支持单机、广播和MapReduce三种执行方式。
|
||||
* 执行处理器:支持SpringBean、普通Java对象、Shell脚本、Python脚本的执行
|
||||
* 在线日志:分布式日志解决方案
|
||||
* 高可用与水平扩展:调度服务器可以部署任意数量的节点,不存在调度的性能瓶颈。
|
||||
* 不怎么美观但可以用的前端界面
|
||||
* OpenAPI:通过OpenAPI可以允许开发者在自己的应用上对OhMyScheduler进行二次开发,比如开发自己的定时调度策略,通过API的调度模式触发任务执行。
|
||||
|
||||
### 下阶段目标
|
||||
* 日志的限流 & 本地分表提升在线日志最大吞吐量
|
||||
* 工作流(任务编排):当前版本勉强可以用MapReduce代替,不过工作流挺酷的,等框架稳定后进行开发。
|
||||
* [应用级别资源管理和任务优先级](https://yq.aliyun.com/articles/753141?spm=a2c4e.11153959.teamhomeleft.1.696d60c9vt9lLx):没有机器资源时,进入排队队列。不过我觉得SchedulerX的方案不太行,SchedulerX无抢占,一旦低优先级任务开始运行,那么只能等他执行完成才能开始高优先级任务,这明显不合理。可是考虑抢占的话又要多考虑很多东西...先放在TODO列表吧。
|
||||
* 保护性判断(这个太繁琐了且意义不大,毕竟面向开发者,大家不会乱填参数对不对~)
|
||||
|
@ -10,11 +10,11 @@
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>oh-my-scheduler-client</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<oms.common.version>1.0.1</oms.common.version>
|
||||
<oms.common.version>1.1.0</oms.common.version>
|
||||
<junit.version>5.6.1</junit.version>
|
||||
</properties>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>oh-my-scheduler-common</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.github.kfcfans.oms.common;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* RemoteConstant
|
||||
*
|
||||
|
@ -10,13 +10,13 @@
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>oh-my-scheduler-server</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<swagger.version>2.9.2</swagger.version>
|
||||
<springboot.version>2.2.6.RELEASE</springboot.version>
|
||||
<oms.common.version>1.0.1</oms.common.version>
|
||||
<oms.common.version>1.1.0</oms.common.version>
|
||||
<mysql.version>8.0.19</mysql.version>
|
||||
<h2.db.version>1.4.200</h2.db.version>
|
||||
</properties>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.github.kfcfans.oms.server.common.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
@ -14,6 +15,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
* @author tjq
|
||||
* @since 2020/4/28
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
@ -22,11 +24,16 @@ public class ThreadPoolConfig {
|
||||
public Executor getTimingPool() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
|
||||
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
|
||||
executor.setQueueCapacity(1024);
|
||||
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
|
||||
// use SynchronousQueue
|
||||
executor.setQueueCapacity(0);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("omsTimingPool-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
|
||||
executor.setRejectedExecutionHandler((r, e) -> {
|
||||
log.warn("[OmsTimingService] timing pool can't schedule job immediately, maybe some job using too much cpu times.");
|
||||
// 定时任务优先级较高,不惜一些代价都需要继续执行,开线程继续干~
|
||||
new Thread(r).start();
|
||||
});
|
||||
return executor;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ public class InstanceLogService {
|
||||
// 用户路径
|
||||
private static final String USER_HOME = System.getProperty("user.home", "oms");
|
||||
// 每一个展示的行数
|
||||
private static final int MAX_LINE_COUNT = 500;
|
||||
private static final int MAX_LINE_COUNT = 100;
|
||||
// 过期时间
|
||||
private static final long EXPIRE_INTERVAL_MS = 60000;
|
||||
|
||||
|
@ -1 +1 @@
|
||||
#welcome[data-v-175a435c]{width:100%;height:100%;background-image:url(../img/welcome_background.6d979910.svg);display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center;align-items:center}
|
||||
#welcome[data-v-1e8c2f69]{width:100%;height:100%;background-image:url(../img/welcome_background.6d979910.svg);display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center;align-items:center}#entrance[data-v-1e8c2f69]{margin:20px}
|
@ -1 +1 @@
|
||||
.wrap[data-v-bbc92dba]{background:#fff;display:flex;text-align:center;justify-content:space-around;align-items:center;margin:10px;box-shadow:0 2px 12px 0 rgba(0,0,0,.2);font-size:1.5rem;font-weight:bolder;height:131px}.el-table .warning-row{background:#fdf5e6}.el-table .success-row{background:#8fbc8f}.el-table .error-row{background:#ff5831}
|
||||
.wrap[data-v-46ad8e00]{background:#fff;display:flex;text-align:center;justify-content:space-around;align-items:center;margin:10px;box-shadow:0 2px 12px 0 rgba(0,0,0,.2);font-size:1.5rem;font-weight:bolder;height:131px}.el-table .warning-row{background:#fdf5e6}.el-table .success-row{background:#8fbc8f}.el-table .error-row{background:#ff5831}
|
@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>oms-console</title><link href=/css/chunk-4947264f.596fc436.css rel=prefetch><link href=/css/chunk-7602a25e.b9e33c01.css rel=prefetch><link href=/js/chunk-2d0c76e2.d0248019.js rel=prefetch><link href=/js/chunk-2d21772a.415195dc.js rel=prefetch><link href=/js/chunk-4947264f.10340ff7.js rel=prefetch><link href=/js/chunk-7602a25e.165fb79d.js rel=prefetch><link href=/css/app.46de59ae.css rel=preload as=style><link href=/js/app.1ed552fe.js rel=preload as=script><link href=/js/chunk-vendors.193746e8.js rel=preload as=script><link href=/css/app.46de59ae.css rel=stylesheet></head><body><noscript><strong>We're sorry but oms-console doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.193746e8.js></script><script src=/js/app.1ed552fe.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>oms-console</title><link href=/css/chunk-047e0794.abafcdac.css rel=prefetch><link href=/css/chunk-375d6af8.3a612c4c.css rel=prefetch><link href=/js/chunk-047e0794.f401f9cb.js rel=prefetch><link href=/js/chunk-2d0c76e2.d0248019.js rel=prefetch><link href=/js/chunk-2d21772a.415195dc.js rel=prefetch><link href=/js/chunk-375d6af8.231ec40c.js rel=prefetch><link href=/css/app.46de59ae.css rel=preload as=style><link href=/js/app.b5e3dd5d.js rel=preload as=script><link href=/js/chunk-vendors.193746e8.js rel=preload as=script><link href=/css/app.46de59ae.css rel=stylesheet></head><body><noscript><strong>We're sorry but oms-console doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.193746e8.js></script><script src=/js/app.b5e3dd5d.js></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-047e0794"],{"1ddd":function(e,t,i){"use strict";i.r(t);var s=function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("div",{attrs:{id:"welcome"}},[i("el-button",{attrs:{type:"primary",plain:""},on:{click:function(t){e.appRegisterFormVisible=!0}}},[e._v("应用注册")]),i("div",{attrs:{id:"entrance"}},[i("el-select",{attrs:{id:"appSelect",filterable:"",remote:"","reserve-keyword":"",placeholder:"请输入应用名称","remote-method":e.fetchAppNames,loading:e.loading},on:{change:e.selectedApp},model:{value:e.selectedAppInfo,callback:function(t){e.selectedAppInfo=t},expression:"selectedAppInfo"}},e._l(e.appInfoList,(function(e){return i("el-option",{key:e.id,attrs:{label:e.appName,value:e}})})),1)],1),i("el-button",{attrs:{type:"success",plain:""},on:{click:function(t){e.userRegisterFormVisible=!0}}},[e._v("用户注册")]),i("el-dialog",{attrs:{title:"应用注册",visible:e.appRegisterFormVisible},on:{"update:visible":function(t){e.appRegisterFormVisible=t}}},[i("el-form",{attrs:{model:e.appRegisterForm}},[i("el-form-item",{attrs:{label:"应用名称"}},[i("el-input",{model:{value:e.appRegisterForm.appName,callback:function(t){e.$set(e.appRegisterForm,"appName",t)},expression:"appRegisterForm.appName"}})],1),i("el-form-item",{attrs:{label:"应用描述"}},[i("el-input",{model:{value:e.appRegisterForm.description,callback:function(t){e.$set(e.appRegisterForm,"description",t)},expression:"appRegisterForm.description"}})],1),i("el-form-item",[i("el-button",{attrs:{type:"primary"},on:{click:e.registerApp}},[e._v("注册")]),i("el-button",{on:{click:function(t){e.appRegisterFormVisible=!1}}},[e._v("取消")])],1)],1)],1),i("el-dialog",{attrs:{title:"用户注册",visible:e.userRegisterFormVisible},on:{"update:visible":function(t){e.userRegisterFormVisible=t}}},[i("el-form",{attrs:{model:e.userRegisterForm}},[i("el-form-item",{attrs:{label:"姓名"}},[i("el-input",{model:{value:e.userRegisterForm.username,callback:function(t){e.$set(e.userRegisterForm,"username",t)},expression:"userRegisterForm.username"}})],1),i("el-form-item",{attrs:{label:"手机号"}},[i("el-input",{model:{value:e.userRegisterForm.phone,callback:function(t){e.$set(e.userRegisterForm,"phone",t)},expression:"userRegisterForm.phone"}})],1),i("el-form-item",{attrs:{label:"邮箱地址"}},[i("el-input",{model:{value:e.userRegisterForm.email,callback:function(t){e.$set(e.userRegisterForm,"email",t)},expression:"userRegisterForm.email"}})],1),i("el-form-item",[i("el-button",{attrs:{type:"primary"},on:{click:e.registerUser}},[e._v("注册")]),i("el-button",{on:{click:function(t){e.userRegisterFormVisible=!1}}},[e._v("取消")])],1)],1)],1)],1)},r=[],o={name:"Welcome",data:function(){return{selectedAppInfo:{},appInfoList:[],appRegisterFormVisible:!1,userRegisterFormVisible:!1,appRegisterForm:{appName:"",description:""},userRegisterForm:{username:"",phone:"",email:""}}},methods:{fetchAppNames:function(e){var t=this,i="/appInfo/list?condition="+e;this.axios.get(i).then((function(e){t.appInfoList=e}),(function(e){return t.$message.error(e)}))},selectedApp:function(){this.$store.commit("initAppInfo",this.selectedAppInfo),this.$router.push("/oms/home")},registerApp:function(){var e=this;this.axios.post("/appInfo/save",this.appRegisterForm).then((function(){e.$message.success("应用注册成功!"),e.appRegisterFormVisible=!1}),e.appRegisterFormVisible=!1)},registerUser:function(){var e=this;this.axios.post("/user/save",this.userRegisterForm).then((function(){e.$message.success("用户注册成功!"),e.userRegisterFormVisible=!1}),e.userRegisterFormVisible=!1)}}},n=o,l=(i("5676"),i("2877")),a=Object(l["a"])(n,s,r,!1,null,"1e8c2f69",null);t["default"]=a.exports},4705:function(e,t,i){},5676:function(e,t,i){"use strict";var s=i("4705"),r=i.n(s);r.a}}]);
|
||||
//# sourceMappingURL=chunk-047e0794.f401f9cb.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-375d6af8"],{1579:function(t,s,a){},6337:function(t,s,a){"use strict";var e=a("ffdc"),n=a.n(e);n.a},"7d8a":function(t,s,a){"use strict";a.r(s);var e=function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{attrs:{id:"home"}},[a("el-row",{attrs:{gutter:24}},[a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("任务总数")]),a("div",{staticClass:"text"},[t._v(t._s(t.systemInfo.jobCount))])]),a("i",{staticClass:"el-icon-orange"})])]),a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("当前运行实例数")]),a("div",{staticClass:"text"},[t._v(t._s(t.systemInfo.runningInstanceCount))])]),a("i",{staticClass:"el-icon-loading"})])]),a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("近期失败任务数")]),a("div",{staticClass:"text"},[t._v(t._s(t.systemInfo.failedInstanceCount))])]),a("i",{staticClass:"el-icon-bell"})])]),a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("集群机器数")]),a("div",{staticClass:"text"},[t._v(t._s(t.activeWorkerCount))])]),a("i",{staticClass:"el-icon-cpu"})])])],1),a("el-row",[a("el-col",{attrs:{span:24}},[a("el-table",{staticStyle:{width:"100%"},attrs:{data:t.workerList,height:"400px","row-class-name":t.workerTableRowClassName}},[a("el-table-column",{attrs:{prop:"address",label:"机器地址"}}),a("el-table-column",{attrs:{prop:"cpuLoad",label:"CPU占用"}}),a("el-table-column",{attrs:{prop:"memoryLoad",label:"内存占用"}}),a("el-table-column",{attrs:{prop:"diskLoad",label:"磁盘占用"}})],1)],1)],1)],1)},n=[],i={name:"Home",data:function(){return{systemInfo:{jobCount:"N/A",runningInstanceCount:"N/A",failedInstanceCount:"N/A"},activeWorkerCount:"N/A",workerList:[]}},methods:{workerTableRowClassName:function(t){var s=t.row;switch(s.status){case 1:return"success-row";case 2:return"warning-row";case 3:return"error-row"}}},mounted:function(){var t=this,s=t.$store.state.appInfo.id;t.axios.get("/system/overview?appId="+s).then((function(s){return t.systemInfo=s})),t.axios.get("/system/listWorker?appId="+s).then((function(s){t.workerList=s,t.activeWorkerCount=t.workerList.length}))}},r=i,o=(a("a735"),a("6337"),a("2877")),l=Object(o["a"])(r,e,n,!1,null,"46ad8e00",null);s["default"]=l.exports},a735:function(t,s,a){"use strict";var e=a("1579"),n=a.n(e);n.a},ffdc:function(t,s,a){}}]);
|
||||
//# sourceMappingURL=chunk-375d6af8.231ec40c.js.map
|
File diff suppressed because one or more lines are too long
@ -1,2 +0,0 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4947264f"],{"05fd":function(t,s,a){"use strict";var e=a("ac51"),n=a.n(e);n.a},6337:function(t,s,a){"use strict";var e=a("ffdc"),n=a.n(e);n.a},"7d8a":function(t,s,a){"use strict";a.r(s);var e=function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{attrs:{id:"home"}},[a("el-row",{attrs:{gutter:24}},[a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("任务总数")]),a("div",{staticClass:"text"},[t._v(t._s(t.systemInfo.jobCount))])]),a("i",{staticClass:"el-icon-orange"})])]),a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("当前运行实例数")]),a("div",{staticClass:"text"},[t._v(t._s(t.systemInfo.runningInstanceCount))])]),a("i",{staticClass:"el-icon-loading"})])]),a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("近期失败任务数")]),a("div",{staticClass:"text"},[t._v(t._s(t.systemInfo.failedInstanceCount))])]),a("i",{staticClass:"el-icon-bell"})])]),a("el-col",{attrs:{span:6}},[a("div",{staticClass:"wrap"},[a("div",{staticClass:"grid-content bg-purple"},[a("div",{staticClass:"text"},[t._v("集群机器数")]),a("div",{staticClass:"text"},[t._v(t._s(t.activeWorkerCount))])]),a("i",{staticClass:"el-icon-grape"})])])],1),a("el-row",[a("el-col",{attrs:{span:24}},[a("el-table",{staticStyle:{width:"100%"},attrs:{data:t.workerList,height:"400px","row-class-name":t.workerTableRowClassName}},[a("el-table-column",{attrs:{prop:"address",label:"机器地址"}}),a("el-table-column",{attrs:{prop:"cpuLoad",label:"CPU占用"}}),a("el-table-column",{attrs:{prop:"memoryLoad",label:"内存占用"}}),a("el-table-column",{attrs:{prop:"diskLoad",label:"磁盘占用"}})],1)],1)],1)],1)},n=[],r={name:"Home",data:function(){return{systemInfo:{jobCount:"N/A",runningInstanceCount:"N/A",failedInstanceCount:"N/A"},activeWorkerCount:"N/A",workerList:[]}},methods:{workerTableRowClassName:function(t){var s=t.row;switch(s.status){case 1:return"success-row";case 2:return"warning-row";case 3:return"error-row"}}},mounted:function(){var t=this,s=t.$store.state.appInfo.id;t.axios.get("/system/overview?appId="+s).then((function(s){return t.systemInfo=s})),t.axios.get("/system/listWorker?appId="+s).then((function(s){t.workerList=s,t.activeWorkerCount=t.workerList.length}))}},i=r,o=(a("05fd"),a("6337"),a("2877")),l=Object(o["a"])(i,e,n,!1,null,"bbc92dba",null);s["default"]=l.exports},ac51:function(t,s,a){},ffdc:function(t,s,a){}}]);
|
||||
//# sourceMappingURL=chunk-4947264f.10340ff7.js.map
|
File diff suppressed because one or more lines are too long
@ -1,2 +0,0 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-7602a25e"],{"1ddd":function(e,t,i){"use strict";i.r(t);var s=function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("div",{attrs:{id:"welcome"}},[i("div",{attrs:{id:"entrance"}},[i("el-select",{attrs:{filterable:"",remote:"","reserve-keyword":"",placeholder:"请输入应用名称","remote-method":e.fetchAppNames,loading:e.loading},on:{change:e.selectedApp},model:{value:e.selectedAppInfo,callback:function(t){e.selectedAppInfo=t},expression:"selectedAppInfo"}},e._l(e.appInfoList,(function(e){return i("el-option",{key:e.id,attrs:{label:e.appName,value:e}})})),1)],1),i("div",{attrs:{id:"register"}},[i("el-button",{attrs:{type:"primary",plain:""},on:{click:function(t){e.appRegisterFormVisible=!0}}},[e._v("应用注册")]),i("el-button",{attrs:{type:"success",plain:""},on:{click:function(t){e.userRegisterFormVisible=!0}}},[e._v("用户注册")])],1),i("el-dialog",{attrs:{title:"应用注册",visible:e.appRegisterFormVisible},on:{"update:visible":function(t){e.appRegisterFormVisible=t}}},[i("el-form",{attrs:{model:e.appRegisterForm}},[i("el-form-item",{attrs:{label:"应用名称"}},[i("el-input",{model:{value:e.appRegisterForm.appName,callback:function(t){e.$set(e.appRegisterForm,"appName",t)},expression:"appRegisterForm.appName"}})],1),i("el-form-item",{attrs:{label:"应用描述"}},[i("el-input",{model:{value:e.appRegisterForm.description,callback:function(t){e.$set(e.appRegisterForm,"description",t)},expression:"appRegisterForm.description"}})],1),i("el-form-item",[i("el-button",{attrs:{type:"primary"},on:{click:e.registerApp}},[e._v("注册")]),i("el-button",{on:{click:function(t){e.appRegisterFormVisible=!1}}},[e._v("取消")])],1)],1)],1),i("el-dialog",{attrs:{title:"用户注册",visible:e.userRegisterFormVisible},on:{"update:visible":function(t){e.userRegisterFormVisible=t}}},[i("el-form",{attrs:{model:e.userRegisterForm}},[i("el-form-item",{attrs:{label:"姓名"}},[i("el-input",{model:{value:e.userRegisterForm.username,callback:function(t){e.$set(e.userRegisterForm,"username",t)},expression:"userRegisterForm.username"}})],1),i("el-form-item",{attrs:{label:"手机号"}},[i("el-input",{model:{value:e.userRegisterForm.phone,callback:function(t){e.$set(e.userRegisterForm,"phone",t)},expression:"userRegisterForm.phone"}})],1),i("el-form-item",{attrs:{label:"邮箱地址"}},[i("el-input",{model:{value:e.userRegisterForm.email,callback:function(t){e.$set(e.userRegisterForm,"email",t)},expression:"userRegisterForm.email"}})],1),i("el-form-item",[i("el-button",{attrs:{type:"primary"},on:{click:e.registerUser}},[e._v("注册")]),i("el-button",{on:{click:function(t){e.userRegisterFormVisible=!1}}},[e._v("取消")])],1)],1)],1)],1)},r=[],o={name:"Welcome",data:function(){return{selectedAppInfo:{},appInfoList:[],appRegisterFormVisible:!1,userRegisterFormVisible:!1,appRegisterForm:{appName:"",description:""},userRegisterForm:{username:"",phone:"",email:""}}},methods:{fetchAppNames:function(e){var t=this,i="/appInfo/list?condition="+e;this.axios.get(i).then((function(e){t.appInfoList=e}),(function(e){return t.$message.error(e)}))},selectedApp:function(){this.$store.commit("initAppInfo",this.selectedAppInfo),this.$router.push("/oms/home")},registerApp:function(){var e=this;this.axios.post("/appInfo/save",this.appRegisterForm).then((function(){e.$message.success("应用注册成功!"),e.appRegisterFormVisible=!1}),e.appRegisterFormVisible=!1)},registerUser:function(){var e=this;this.axios.post("/user/save",this.userRegisterForm).then((function(){e.$message.success("用户注册成功!"),e.userRegisterFormVisible=!1}),e.userRegisterFormVisible=!1)}}},n=o,a=(i("570a"),i("2877")),l=Object(a["a"])(n,s,r,!1,null,"175a435c",null);t["default"]=l.exports},4112:function(e,t,i){},"570a":function(e,t,i){"use strict";var s=i("4112"),r=i.n(s);r.a}}]);
|
||||
//# sourceMappingURL=chunk-7602a25e.165fb79d.js.map
|
File diff suppressed because one or more lines are too long
@ -14,7 +14,7 @@
|
||||
|
||||
<properties>
|
||||
<springboot.version>2.2.6.RELEASE</springboot.version>
|
||||
<oms.worker.version>1.0.1</oms.worker.version>
|
||||
<oms.worker.version>1.1.0</oms.worker.version>
|
||||
<fastjson.version>1.2.68</fastjson.version>
|
||||
</properties>
|
||||
|
||||
|
@ -5,6 +5,7 @@ import com.github.kfcfans.oms.worker.core.processor.ProcessResult;
|
||||
import com.github.kfcfans.oms.worker.core.processor.TaskContext;
|
||||
import com.github.kfcfans.oms.worker.core.processor.sdk.BasicProcessor;
|
||||
import com.github.kfcfans.oms.worker.log.OmsLogger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 测试 Oms 在线日志的性能
|
||||
@ -12,6 +13,7 @@ import com.github.kfcfans.oms.worker.log.OmsLogger;
|
||||
* @author tjq
|
||||
* @since 2020/5/3
|
||||
*/
|
||||
@Component
|
||||
public class OmsLogPerformanceTester implements BasicProcessor {
|
||||
|
||||
private static final int BATCH = 1000;
|
||||
@ -19,29 +21,32 @@ public class OmsLogPerformanceTester implements BasicProcessor {
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
|
||||
OmsLogger logger = context.getOmsLogger();
|
||||
OmsLogger omsLogger = context.getOmsLogger();
|
||||
// 控制台参数,格式为 {"num":10000, "interval": 200}
|
||||
JSONObject jobParams = JSONObject.parseObject(context.getJobParams());
|
||||
Long num = jobParams.getLong("num");
|
||||
Long interval = jobParams.getLong("interval");
|
||||
|
||||
omsLogger.info("ready to start to process, current JobParams is {}.", jobParams);
|
||||
|
||||
RuntimeException re = new RuntimeException("This is a exception~~~");
|
||||
|
||||
long times = num / BATCH;
|
||||
long times = (long) Math.ceil(1.0 * num / BATCH);
|
||||
for (long i = 0; i < times; i++) {
|
||||
for (long j = 0; j < BATCH; j++) {
|
||||
long index = i * BATCH + j;
|
||||
System.out.println("send index: " + index);
|
||||
logger.info("[OmsLogPerformanceTester] testing omsLogger performance, current index is {}.", index);
|
||||
|
||||
omsLogger.info("testing omsLogger's performance, current index is {}.", index);
|
||||
}
|
||||
logger.error("[OmsLogPerformanceTester] Oh, we have an exception to log~", re);
|
||||
omsLogger.error("Oh, it seems that we have got an exception.", re);
|
||||
try {
|
||||
Thread.sleep(interval);
|
||||
}catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("[OmsLogPerformanceTester] success!");
|
||||
omsLogger.info("anyway, we finished the job~configuration~");
|
||||
return new ProcessResult(true, "good job");
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,12 @@
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>oh-my-scheduler-worker</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<spring.version>5.2.4.RELEASE</spring.version>
|
||||
<oms.common.version>1.0.1</oms.common.version>
|
||||
<oms.common.version>1.1.0</oms.common.version>
|
||||
<h2.db.version>1.4.200</h2.db.version>
|
||||
<hikaricp.version>3.4.2</hikaricp.version>
|
||||
<junit.version>5.6.1</junit.version>
|
||||
|
@ -3,6 +3,8 @@ package com.github.kfcfans.oms.processors.demo;
|
||||
import com.github.kfcfans.oms.worker.core.processor.ProcessResult;
|
||||
import com.github.kfcfans.oms.worker.core.processor.TaskContext;
|
||||
import com.github.kfcfans.oms.worker.core.processor.sdk.BasicProcessor;
|
||||
import com.github.kfcfans.oms.worker.log.OmsLogger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 示例-单机任务处理器
|
||||
@ -10,11 +12,16 @@ import com.github.kfcfans.oms.worker.core.processor.sdk.BasicProcessor;
|
||||
* @author tjq
|
||||
* @since 2020/4/15
|
||||
*/
|
||||
@Component
|
||||
public class BasicProcessorDemo implements BasicProcessor {
|
||||
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
|
||||
// 在线日志功能,可以直接在控制台查看任务日志,非常便捷
|
||||
OmsLogger omsLogger = context.getOmsLogger();
|
||||
omsLogger.info("BasicProcessorDemo start to process, current JobParams is {}.", context.getJobParams());
|
||||
|
||||
// TaskContext为任务的上下文信息,包含了在控制台录入的任务元数据,常用字段为
|
||||
// jobParams(任务参数,在控制台录入),instanceParams(任务实例参数,通过 OpenAPI 触发的任务实例才可能存在该参数)
|
||||
|
||||
@ -23,12 +30,4 @@ public class BasicProcessorDemo implements BasicProcessor {
|
||||
// 返回结果,该结果会被持久化到数据库,在前端页面直接查看,极为方便
|
||||
return new ProcessResult(true, "result is xxx");
|
||||
}
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
// 初始化处理器
|
||||
}
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
// 释放资源,销毁处理器
|
||||
}
|
||||
}
|
||||
|
59
others/doc/ConsoleGuide.md
Normal file
59
others/doc/ConsoleGuide.md
Normal file
@ -0,0 +1,59 @@
|
||||
# STEP3: 任务管理 & 运行状态查看
|
||||
>通过前端控制台管理调度任务,查看运行情况和结果等。
|
||||
|
||||
### 系统首页
|
||||
> 展示了系统整体的概览和集群Worker列表。
|
||||
|
||||

|
||||
|
||||
### 任务创建
|
||||
> 创建需要被调度执行的任务,入口为**主页 -> 任务管理 -> 新建任务**。
|
||||
|
||||

|
||||
* 任务名称:名称,便于记忆与搜索,无特殊用途,请尽量简短(占用数据库字段空间)
|
||||
* 任务描述:描述,无特殊作用,请尽量简短(占用数据库字段空间)
|
||||
* 任务参数:任务处理时能够获取到的参数(即各个Processor的process方法入参`TaskContext`对象的jobParams字段)(进行一次处理器开发就能理解了)
|
||||
* 定时信息:由下拉框和输入框组成
|
||||
* API -> 不需要填写任何参数(填了也不起作用)
|
||||
* CRON -> 填写 CRON 表达式(可以找个[在线生成网站生成](https://www.bejson.com/othertools/cron/))
|
||||
* 固定频率 -> 填写整数,单位毫秒
|
||||
* 固定延迟 -> 填写整数,单位毫秒
|
||||
* 执行配置:由执行类型(单机、广播和MapReduce)、处理器类型和处理器参数组成,后两项相互关联。
|
||||
* 内置Java处理器 -> 填写该处理器的全限定类名(eg, `com.github.kfcfans.oms.processors.demo.MapReduceProcessorDemo`)
|
||||
* SHELL -> 填写需要处理的脚本(直接复制文件内容)或脚本下载连接(http://xxx)
|
||||
* PYTHON -> 填写完整的python脚本或下载连接(http://xxx)
|
||||
|
||||
* 运行配置
|
||||
* 最大实例数:该任务同时执行的数量(任务和实例就像是类和对象的关系,任务被调度执行后被称为实例)
|
||||
* 单机线程并发数:该实例执行过程中每个Worker使用的线程数量(MapReduce任务生效,其余无论填什么,都只会使用1个线程或3个线程...)
|
||||
* 运行时间限制:限定任务的最大运行时间,超时则视为失败,单位**毫秒**,0代表不限制超时时间。
|
||||
|
||||
* 重试配置:
|
||||
* 任务重试次数:实例级别,失败了整个任务实例重试,会更换TaskTracker(本次任务实例的Master节点),代价较大,大型Map/MapReduce慎用。
|
||||
* 子任务重试次数:Task级别,每个子Task失败后单独重试,会更换ProcessorTracker(本次任务实际执行的Worker节点),代价较小,推荐使用。
|
||||
* 注:对于单机任务来说,假如任务重试次数和子任务重试次数都配置了1且都执行失败,实际执行次数会变成4次!推荐任务实例重试配置为0,子任务重试次数根据实际情况配置。
|
||||
|
||||
* 机器配置:用来标明允许执行任务的机器状态,避开那些摇摇欲坠的机器,0代表无任何限制。
|
||||
* 最低CPU核心数:填写浮点数,CPU可用核心数小于该值的Worker将不会执行该任务。
|
||||
* 最低内存(GB):填写浮点数,可用内存小于该值的Worker将不会执行该任务。
|
||||
* 最低磁盘(GB):填写浮点数,可用磁盘空间小于该值的Worker将不会执行该任务。
|
||||
* 集群配置
|
||||
* 执行机器地址:指定集群中的某几台机器执行任务(debug的好帮手),多值英文逗号分割,如`192.168.1.1:27777,192.168.1.2:27777`
|
||||
* 最大执行机器数量:限定调动执行的机器数量
|
||||
|
||||
* 报警配置:选择任务执行失败后报警通知的对象,需要事先录入。
|
||||
|
||||
### 任务管理
|
||||
>可以方便地查看和管理系统当前录入的任务信息。
|
||||
|
||||

|
||||
|
||||
### 运行状态
|
||||
>可以方便地查看当前运行的任务实例,点击详情即可获取详细的信息,点击日志可以查看通过`omsLogger`上报的日志,点击停止则可以强制终止该任务。
|
||||
|
||||

|
||||
|
||||
#### 在线日志
|
||||
|
||||
|
||||

|
@ -1,209 +0,0 @@
|
||||
[toc]
|
||||
# 任务配置
|
||||
>前端界面目前可能有那么一点点丑...不过问题不是很大 (>▽<)
|
||||
|
||||
### 系统首页
|
||||
> 展示了系统整体的概览和集群Worker列表。
|
||||
|
||||

|
||||
|
||||
|
||||
### 任务录入
|
||||
>一切的起点。
|
||||
|
||||

|
||||
* 任务名称:名称,无特殊作用
|
||||
* 任务描述:描述,无特殊作用,请尽量简短(占用数据库字段空间)
|
||||
* 任务参数:任务处理时能够获取到的参数(即各个Processor的process方法的TaskContext对象的jobParams字段)(进行一次处理器开发就能理解了)
|
||||
* 定时信息:由下拉框和输入框组成
|
||||
* API -> 不需要填写任何参数(填了也不起作用)
|
||||
* CRON -> 填写 CRON 表达式(可以找个在线生成网站生成)
|
||||
* 固定频率 -> 填写整数,单位毫秒
|
||||
* 固定延迟 -> 填写整数,单位毫秒
|
||||
* 执行配置:由执行类型(单机、广播和MapReduce)、处理器类型和处理器参数组成,后两项相互关联。
|
||||
* 内置Java处理器 -> 填写该处理器的全限定类名(eg, `com.github.kfcfans.oms.processors.demo.MapReduceProcessorDemo`)
|
||||
* SHELL -> 填写需要处理的脚本(直接复制文件内容)或脚本下载连接(http://xxx)
|
||||
* PYTHON -> 填写完整的python脚本或下载连接(http://xxx)
|
||||
|
||||
* 运行配置
|
||||
* 最大实例数:该任务同时执行的数量(任务和实例就像是类和对象的关系,任务被调度执行后被称为实例)
|
||||
* 单机线程并发数:该实例执行过程中每个Worker使用的线程数量(MapReduce任务生效,其余无论填什么,都只会使用1个线程或3个线程...)
|
||||
* 运行时间限制:限定任务的最大运行时间,超时则视为失败,单位**毫秒**,0代表不限制超时时间。
|
||||
|
||||
* 重试配置:
|
||||
* 任务重试次数:实例级别,失败了整个重试,会更换TaskTracker(本次任务实例的Master节点)。
|
||||
* 子任务重试次数:Task级别,每个子Task失败后单独重试,会更换ProcessorTracker(本次任务实际执行的Worker节点)。
|
||||
* 注:对于单机任务来说,假如任务重试次数和子任务重试次数都配置了1且都执行失败,实际执行次数会变成4次!推荐任务实例重试配置为0,子任务重试次数根据实际情况配置。
|
||||
|
||||
* 机器配置:用来标明允许执行任务的机器状态,避开那些摇摇欲坠的机器,0代表无任何限制。
|
||||
* 最低CPU核心数:填写浮点数,CPU可用核心数小于该值的Worker将不会执行该任务。
|
||||
* 最低内存(GB):填写浮点数,可用内存小于该值的Worker将不会执行该任务。
|
||||
* 最低磁盘(GB):填写浮点数,可用磁盘空间小于该值的Worker将不会执行该任务。
|
||||
* 集群配置
|
||||
* 执行机器地址:指定集群中的某几台机器执行任务(debug的好帮手),多值英文逗号分割,如`192.168.1.1:27777,192.168.1.2:27777`
|
||||
* 最大执行机器数量:限定调动执行的机器数量
|
||||
|
||||
* 报警配置:选择任务执行失败后报警通知的对象。
|
||||
|
||||
### 任务管理
|
||||
>可以方便地查看和管理系统当前录入的任务信息。
|
||||
|
||||

|
||||
|
||||
### 运行状态
|
||||
>可以方便地查看当前运行的任务实例,点击详情即可获取详细的信息,点击停止则可以强制终止该任务。
|
||||
|
||||

|
||||
|
||||
|
||||
# 处理器开发
|
||||
>搭载处理器的宿主应用需要添加`oh-my-scheduler-worker`依赖,然后编写实现指定接口或抽象类的Java类即可。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.kfcfans</groupId>
|
||||
<artifactId>oh-my-scheduler-worker</artifactId>
|
||||
<version>${oms.worker.latest.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 单机处理器
|
||||
>单机执行的策略下,server会在所有可用worker中选取健康度最佳的机器进行执行。单机执行任务需要实现接口:`com.github.kfcfans.oms.worker.core.processor.sdk.BasicProcessor`,代码示例如下:
|
||||
|
||||
```java
|
||||
@Componet
|
||||
public class BasicProcessorDemo implements BasicProcessor {
|
||||
|
||||
// 支持 Spring Bean
|
||||
@Resource
|
||||
private MysteryService mysteryService;
|
||||
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
|
||||
// TaskContext为任务的上下文信息,包含了在控制台录入的任务元数据,常用字段为
|
||||
// jobParams(任务参数,在控制台录入),instanceParams(任务实例参数,通过 OpenAPI 触发的任务实例才可能存在该参数)
|
||||
|
||||
// 可以根据控制台传递的参数进行实际处理...
|
||||
mysteryService.hasaki(context.getJobParams());
|
||||
|
||||
// 返回结果,该结果会被持久化到数据库,在前端页面直接查看,极为方便
|
||||
return new ProcessResult(true, "result is xxx");
|
||||
}
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
// 初始化处理器
|
||||
}
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
// 释放资源,销毁处理器
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 广播执行处理器
|
||||
>广播执行的策略下,所有机器都会被调度执行该任务。为了便于资源的准备和释放,广播处理器在`BasicProcessor`的基础上额外增加了`preProcess`和`postProcess`方法,分别在整个集群开始之前/结束之后**选一台机器**执行相关方法。代码示例如下:
|
||||
|
||||
```java
|
||||
public class BroadcastProcessorDemo extends BroadcastProcessor {
|
||||
|
||||
@Override
|
||||
public ProcessResult preProcess(TaskContext taskContext) throws Exception {
|
||||
// 预执行,会在所有 worker 执行 process 方法前调用
|
||||
return new ProcessResult(true, "init success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
// 撰写整个worker集群都会执行的代码逻辑
|
||||
return new ProcessResult(true, "release resource success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessResult postProcess(TaskContext taskContext, List<TaskResult> taskResults) throws Exception {
|
||||
|
||||
// taskResults 存储了所有worker执行的结果(包括preProcess)
|
||||
|
||||
// 收尾,会在所有 worker 执行完毕 process 方法后调用,该结果将作为最终的执行结果在
|
||||
return new ProcessResult(true, "process success");
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### MapReduce处理器
|
||||
>MapReduce是最复杂也是最强大的一种执行器,它允许开发者完成任务的拆分,将子任务派发到集群中其他Worker执行,是执行大批量处理任务的不二之选!实现MapReduce处理器需要继承`MapReduceProcessor`类,具体用法如下示例代码所示。
|
||||
|
||||
```java
|
||||
public class MapReduceProcessorDemo extends MapReduceProcessor {
|
||||
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
// 判断是否为根任务
|
||||
if (isRootTask()) {
|
||||
|
||||
// 构造子任务
|
||||
List<SubTask> subTaskList = Lists.newLinkedList();
|
||||
|
||||
/*
|
||||
* 子任务的构造由开发者自己定义
|
||||
* eg. 现在需要从文件中读取100W个ID,并处理数据库中这些ID对应的数据,那么步骤如下:
|
||||
* 1. 根任务(RootTask)读取文件,流式拉取100W个ID,并按1000个一批的大小组装成子任务进行派发
|
||||
* 2. 非根任务获取子任务,完成业务逻辑的处理
|
||||
*/
|
||||
|
||||
// 调用 map 方法,派发子任务
|
||||
return map(subTaskList, "DATA_PROCESS_TASK");
|
||||
}
|
||||
|
||||
// 非子任务,可根据 subTask 的类型 或 TaskName 来判断分支
|
||||
if (context.getSubTask() instanceof SubTask) {
|
||||
// 执行子任务,注:子任务人可以 map 产生新的子任务,可以构建任意级的 MapReduce 处理器
|
||||
return new ProcessResult(true, "PROCESS_SUB_TASK_SUCCESS");
|
||||
}
|
||||
|
||||
return new ProcessResult(false, "UNKNOWN_BUG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessResult reduce(TaskContext taskContext, List<TaskResult> taskResults) {
|
||||
|
||||
// 所有 Task 执行结束后,reduce 将会被执行
|
||||
// taskResults 保存了所有子任务的执行结果
|
||||
|
||||
// 用法举例,统计执行结果
|
||||
AtomicLong successCnt = new AtomicLong(0);
|
||||
taskResults.forEach(tr -> {
|
||||
if (tr.isSuccess()) {
|
||||
successCnt.incrementAndGet();
|
||||
}
|
||||
});
|
||||
return new ProcessResult(true, "success task num:" + successCnt.get());
|
||||
}
|
||||
|
||||
// 自定义的子任务
|
||||
private static class SubTask {
|
||||
private Long siteId;
|
||||
private List<Long> idList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# OpenAPI
|
||||
>OpenAPI允许开发者通过接口来完成手工的操作,让系统整体变得更加灵活,启用OpenAPI需要依赖`oh-my-scheduler-client`库。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.kfcfans</groupId>
|
||||
<artifactId>oh-my-scheduler-client</artifactId>
|
||||
<version>${oms.client.latest.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 简单示例
|
||||
```java
|
||||
// 初始化 client,需要server地址和应用名称作为参数
|
||||
OhMyClient ohMyClient = new OhMyClient("127.0.0.1:7700", "oms-test");
|
||||
// 调用相关的API
|
||||
ohMyClient.stopInstance(1586855173043L)
|
||||
```
|
19
others/doc/OpenApiGuide.md
Normal file
19
others/doc/OpenApiGuide.md
Normal file
@ -0,0 +1,19 @@
|
||||
# STEP4: OpenAPI
|
||||
>OpenAPI允许开发者通过接口来完成手工的操作,让系统整体变得更加灵活,启用OpenAPI需要依赖`oh-my-scheduler-client`库。
|
||||
|
||||
* 最新依赖版本请参考Maven中央仓库:[推荐地址](https://search.maven.org/search?q=com.github.kfcfans)&[备用地址](https://mvnrepository.com/search?q=com.github.kfcfans)。
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.kfcfans</groupId>
|
||||
<artifactId>oh-my-scheduler-client</artifactId>
|
||||
<version>${oms.client.latest.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 简单示例
|
||||
```java
|
||||
// 初始化 client,需要server地址和应用名称作为参数
|
||||
OhMyClient ohMyClient = new OhMyClient("127.0.0.1:7700", "oms-test");
|
||||
// 调用相关的API
|
||||
ohMyClient.stopInstance(1586855173043L)
|
||||
```
|
143
others/doc/ProcessorDevGuide.md
Normal file
143
others/doc/ProcessorDevGuide.md
Normal file
@ -0,0 +1,143 @@
|
||||
# STEP2: 处理器开发
|
||||
>OhMyScheduler支持Python、Shell和Java处理器,前两种处理器为脚本处理器,功能简单,在控制台直接配置即可,本章不再赘述。开发项目内置的Java处理器,宿主应用需要添加`oh-my-scheduler-worker`依赖,并实现指定接口或抽象类的Java类。
|
||||
|
||||
* 最新依赖版本请参考Maven中央仓库:[推荐地址](https://search.maven.org/search?q=com.github.kfcfans)&[备用地址](https://mvnrepository.com/search?q=com.github.kfcfans)。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.kfcfans</groupId>
|
||||
<artifactId>oh-my-scheduler-worker</artifactId>
|
||||
<version>${oms.worker.latest.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 处理器开发示例
|
||||
>更多示例代码请见项目:oh-my-scheduler-worker-samples
|
||||
|
||||
#### 单机处理器
|
||||
>单机执行的策略下,server会在所有可用worker中选取健康度最佳的机器进行执行。单机执行任务需要实现接口:`com.github.kfcfans.oms.worker.core.processor.sdk.BasicProcessor`,代码示例如下:
|
||||
|
||||
```java
|
||||
// 支持 SpringBean 的形式
|
||||
@Component
|
||||
public class BasicProcessorDemo implements BasicProcessor {
|
||||
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
|
||||
// 在线日志功能,可以直接在控制台查看任务日志,非常便捷
|
||||
OmsLogger omsLogger = context.getOmsLogger();
|
||||
omsLogger.info("BasicProcessorDemo start to process, current JobParams is {}.", context.getJobParams());
|
||||
|
||||
// TaskContext为任务的上下文信息,包含了在控制台录入的任务元数据,常用字段为
|
||||
// jobParams(任务参数,在控制台录入),instanceParams(任务实例参数,通过 OpenAPI 触发的任务实例才可能存在该参数)
|
||||
|
||||
// 进行实际处理...
|
||||
|
||||
// 返回结果,该结果会被持久化到数据库,在前端页面直接查看,极为方便
|
||||
return new ProcessResult(true, "result is xxx");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 广播执行处理器
|
||||
>广播执行的策略下,所有机器都会被调度执行该任务。为了便于资源的准备和释放,广播处理器在`BasicProcessor`的基础上额外增加了`preProcess`和`postProcess`方法,分别在整个集群开始之前/结束之后**选一台机器**执行相关方法。代码示例如下:
|
||||
|
||||
```java
|
||||
public class BroadcastProcessorDemo extends BroadcastProcessor {
|
||||
|
||||
@Override
|
||||
public ProcessResult preProcess(TaskContext taskContext) throws Exception {
|
||||
// 预执行,会在所有 worker 执行 process 方法前调用
|
||||
return new ProcessResult(true, "init success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
// 撰写整个worker集群都会执行的代码逻辑
|
||||
return new ProcessResult(true, "release resource success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessResult postProcess(TaskContext taskContext, List<TaskResult> taskResults) throws Exception {
|
||||
|
||||
// taskResults 存储了所有worker执行的结果(包括preProcess)
|
||||
|
||||
// 收尾,会在所有 worker 执行完毕 process 方法后调用,该结果将作为最终的执行结果在
|
||||
return new ProcessResult(true, "process success");
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### MapReduce处理器
|
||||
>MapReduce是最复杂也是最强大的一种执行器,它允许开发者完成任务的拆分,将子任务派发到集群中其他Worker执行,是执行大批量处理任务的不二之选!实现MapReduce处理器需要继承`MapReduceProcessor`类,具体用法如下示例代码所示。
|
||||
|
||||
```java
|
||||
public class MapReduceProcessorDemo extends MapReduceProcessor {
|
||||
|
||||
@Override
|
||||
public ProcessResult process(TaskContext context) throws Exception {
|
||||
// 判断是否为根任务
|
||||
if (isRootTask()) {
|
||||
|
||||
// 构造子任务
|
||||
List<SubTask> subTaskList = Lists.newLinkedList();
|
||||
|
||||
/*
|
||||
* 子任务的构造由开发者自己定义
|
||||
* eg. 现在需要从文件中读取100W个ID,并处理数据库中这些ID对应的数据,那么步骤如下:
|
||||
* 1. 根任务(RootTask)读取文件,流式拉取100W个ID,并按1000个一批的大小组装成子任务进行派发
|
||||
* 2. 非根任务获取子任务,完成业务逻辑的处理
|
||||
*/
|
||||
|
||||
// 调用 map 方法,派发子任务
|
||||
return map(subTaskList, "DATA_PROCESS_TASK");
|
||||
}
|
||||
|
||||
// 非子任务,可根据 subTask 的类型 或 TaskName 来判断分支
|
||||
if (context.getSubTask() instanceof SubTask) {
|
||||
// 执行子任务,注:子任务人可以 map 产生新的子任务,可以构建任意级的 MapReduce 处理器
|
||||
return new ProcessResult(true, "PROCESS_SUB_TASK_SUCCESS");
|
||||
}
|
||||
|
||||
return new ProcessResult(false, "UNKNOWN_BUG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessResult reduce(TaskContext taskContext, List<TaskResult> taskResults) {
|
||||
|
||||
// 所有 Task 执行结束后,reduce 将会被执行
|
||||
// taskResults 保存了所有子任务的执行结果
|
||||
|
||||
// 用法举例,统计执行结果
|
||||
AtomicLong successCnt = new AtomicLong(0);
|
||||
taskResults.forEach(tr -> {
|
||||
if (tr.isSuccess()) {
|
||||
successCnt.incrementAndGet();
|
||||
}
|
||||
});
|
||||
return new ProcessResult(true, "success task num:" + successCnt.get());
|
||||
}
|
||||
|
||||
// 自定义的子任务
|
||||
private static class SubTask {
|
||||
private Long siteId;
|
||||
private List<Long> idList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 处理器上下文(TaskContext)属性说明
|
||||
|属性名称|意义/用法|
|
||||
|----|----|
|
||||
|instanceId|任务实例ID,全局唯一,开发者一般无需关心此参数|
|
||||
|subInstanceId|子任务实例ID,秒级任务使用,开发者一般无需关心此参数|
|
||||
|taskId|采用链式命名法的ID,在某个任务实例内唯一,开发者一般无需关心此参数|
|
||||
|taskName|task名称,Map/MapReduce任务的子任务的值为开发者指定,否则为系统默认值,开发者一般无需关心此参数|
|
||||
|jobParams|任务参数,其值等同于控制台录入的**任务参数**,常用!|
|
||||
|instanceParams|任务实例参数,其值等同于使用OpenAPI运行任务实例时传递的参数,常用!|
|
||||
|maxRetryTimes|Task的最大重试次数|
|
||||
|currentRetryTimes|Task的当前重试次数,和maxRetryTimes联合起来可以判断当前是否为该Task的最后一次运行机会|
|
||||
|subTask|子Task,Map/MapReduce处理器专属,开发者调用map方法时传递的子任务列表中的某一个|
|
||||
|omsLogger|在线日志,用法同Slf4J,记录的日志可以直接通过控制台查看,非常便捷和强大!不过使用过程中需要注意频率,可能对Server造成巨大的压力|
|
35
others/doc/SystemInitGuide.md
Normal file
35
others/doc/SystemInitGuide.md
Normal file
@ -0,0 +1,35 @@
|
||||
# STEP1: 系统部署 & 初始化
|
||||
## 部署
|
||||
#### 要求
|
||||
* 运行环境:JDK8+
|
||||
* 编译环境:Maven3+
|
||||
* 关系数据库:任意Spring Data JPA支持的关系型数据库(MySQL/Oracle/MS SQLServer...)
|
||||
* mongoDB:任意支持GridFS的mongoDB版本(4.2.6测试通过,其余未经测试,仅从理论角度分析可用)
|
||||
|
||||
#### 流程
|
||||
1. 部署数据库:由于任务调度中心的数据持久层基于`Spring Data Jpa`实现,**开发者仅需要完成数据库的创建**,即运行SQL`CREATE database if NOT EXISTS oms-product default character set utf8mb4 collate utf8mb4_unicode_ci;`。
|
||||
* 注1:任务调度中心支持多环境部署(日常、预发、线上),其分别对应三个数据库:oms-daily、oms-pre和oms-product。
|
||||
* 注2:手动建表SQL文件:[oms-sql.sql](../oms-sql.sql)
|
||||
|
||||
2. 部署调度服务器(OhMyScheduler-Server),需要先修改配置文件(同样为了支持多环境部署,采用了daily、pre和product3套配置文件),之后自行编译部署运行。
|
||||
* 注:OhMyScheduler-Server支持集群部署,具备完全的水平扩展能力。建议部署多个实例以实现高可用&高性能。
|
||||
* application-xxx.properties文件配置说明如下表所示:
|
||||
* |配置项|含义|可选|
|
||||
|----|----|----|
|
||||
|spring.datasource.core.xxx|关系型数据库连接配置|否|
|
||||
|spring.mail.xxx|邮件配置|是,未配置情况下将无法使用邮件报警功能|
|
||||
|spring.data.mongodb.xxx|MongoDB连接配置|是,未配置情况下将无法使用在线日志功能|
|
||||
|oms.log.retention.local|本地日志保留天数,负数代表永久保留|否|
|
||||
|oms.log.retention.remote|远程日志保留天数,负数代表永久保留|否|
|
||||
|oms.alarm.bean.names|扩展的报警服务Bean,多值逗号分割,默认为邮件报警|否|
|
||||
|
||||
3. 部署前端页面(可选):每一个OhMyScheduler-Server内部自带了前端页面,不过Tomcat做Web服务器的性能就呵呵了~有需求(追求)的用户自行使用[源码](https://github.com/KFCFans/OhMyScheduler-Console)打包部署即可。
|
||||
* 需要修改`main.js`中的`axios.defaults.baseURL`为实际的OhMyScheduler-Server地址
|
||||
|
||||
## 初始化
|
||||
> 每一个需要接入OhMyScheduler的系统,都需要先在控制台完成初始化,即应用注册与用户录入。初始化操作在首页完成。
|
||||
|
||||

|
||||
* 每一个系统初次接入OhMyScheduler时,都需要通过**应用注册**功能录入`appName`(接入应用的名称,需要保证唯一)和`appDescription`(描述信息,无实际用处),至此,应用初始化完成,准备开发处理器(Processor)享受分布式调度和计算的便利之处吧~
|
||||
* 注册完成后,输入`appName`即可进入控制台。
|
||||
* **用户注册**可录入用户信息,用于之后任务的报警配置。
|
BIN
others/img/oms-console-onlineLog.png
Normal file
BIN
others/img/oms-console-onlineLog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
Binary file not shown.
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 157 KiB |
BIN
others/img/oms-console-welcome.png
Normal file
BIN
others/img/oms-console-welcome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
119
others/oms-sql.sql
Normal file
119
others/oms-sql.sql
Normal file
@ -0,0 +1,119 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_info`;
|
||||
CREATE TABLE `app_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`app_name` varchar(255) DEFAULT NULL,
|
||||
`current_server` varchar(255) DEFAULT NULL,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `appNameUK` (`app_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for instance_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `instance_log`;
|
||||
CREATE TABLE `instance_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`actual_trigger_time` bigint(20) DEFAULT NULL,
|
||||
`app_id` bigint(20) DEFAULT NULL,
|
||||
`expected_trigger_time` bigint(20) DEFAULT NULL,
|
||||
`finished_time` bigint(20) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`instance_id` bigint(20) DEFAULT NULL,
|
||||
`instance_params` varchar(255) DEFAULT NULL,
|
||||
`job_id` bigint(20) DEFAULT NULL,
|
||||
`result` varchar(255) DEFAULT NULL,
|
||||
`running_times` bigint(20) DEFAULT NULL,
|
||||
`status` int(11) NOT NULL,
|
||||
`task_tracker_address` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `IDX1ha9qjdw952k1c22gkht50unp` (`job_id`),
|
||||
KEY `IDXckliwovavlr2s0uh14n94yfmc` (`app_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for job_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `job_info`;
|
||||
CREATE TABLE `job_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`app_id` bigint(20) DEFAULT NULL,
|
||||
`concurrency` int(11) DEFAULT NULL,
|
||||
`designated_workers` varchar(255) DEFAULT NULL,
|
||||
`execute_type` int(11) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`instance_retry_num` int(11) DEFAULT NULL,
|
||||
`instance_time_limit` bigint(20) DEFAULT NULL,
|
||||
`job_description` varchar(255) DEFAULT NULL,
|
||||
`job_name` varchar(255) DEFAULT NULL,
|
||||
`job_params` varchar(255) DEFAULT NULL,
|
||||
`max_instance_num` int(11) DEFAULT NULL,
|
||||
`max_worker_count` int(11) DEFAULT NULL,
|
||||
`min_cpu_cores` double NOT NULL,
|
||||
`min_disk_space` double NOT NULL,
|
||||
`min_memory_space` double NOT NULL,
|
||||
`next_trigger_time` bigint(20) DEFAULT NULL,
|
||||
`notify_user_ids` varchar(255) DEFAULT NULL,
|
||||
`processor_info` varchar(255) DEFAULT NULL,
|
||||
`processor_type` int(11) DEFAULT NULL,
|
||||
`status` int(11) DEFAULT NULL,
|
||||
`task_retry_num` int(11) DEFAULT NULL,
|
||||
`time_expression` varchar(255) DEFAULT NULL,
|
||||
`time_expression_type` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `IDXk2xprmn3lldmlcb52i36udll1` (`app_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for oms_lock
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `oms_lock`;
|
||||
CREATE TABLE `oms_lock` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`lock_name` varchar(255) DEFAULT NULL,
|
||||
`ownerip` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `lockNameUK` (`lock_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for server_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `server_info`;
|
||||
CREATE TABLE `server_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`ip` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `UKtk8ytgpl7mpukhnvhbl82kgvy` (`ip`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_info`;
|
||||
CREATE TABLE `user_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(255) DEFAULT NULL,
|
||||
`gmt_create` datetime(6) DEFAULT NULL,
|
||||
`gmt_modified` datetime(6) DEFAULT NULL,
|
||||
`password` varchar(255) DEFAULT NULL,
|
||||
`phone` varchar(255) DEFAULT NULL,
|
||||
`username` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
Loading…
x
Reference in New Issue
Block a user