[dev] write guide document

This commit is contained in:
tjq 2020-05-11 21:07:17 +08:00
parent b295f81e01
commit d5a52fef57
33 changed files with 432 additions and 279 deletions

View File

@ -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列表吧。
* 保护性判断(这个太繁琐了且意义不大,毕竟面向开发者,大家不会乱填参数对不对~)

View File

@ -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>

View File

@ -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>

View File

@ -1,7 +1,5 @@
package com.github.kfcfans.oms.common;
import java.time.Duration;
/**
* RemoteConstant
*

View File

@ -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>

View File

@ -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;
}

View File

@ -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;

View File

@ -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}

View File

@ -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}

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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");
}
}

View File

@ -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>

View File

@ -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 {
// 释放资源销毁处理器
}
}

View File

@ -0,0 +1,59 @@
# STEP3: 任务管理 & 运行状态查看
>通过前端控制台管理调度任务,查看运行情况和结果等。
### 系统首页
> 展示了系统整体的概览和集群Worker列表。
![首页](../img/oms-console-main.png)
### 任务创建
> 创建需要被调度执行的任务,入口为**主页 -> 任务管理 -> 新建任务**。
![任务创建](../img/oms-console-jobCreator.png)
* 任务名称:名称,便于记忆与搜索,无特殊用途,请尽量简短(占用数据库字段空间)
* 任务描述:描述,无特殊作用,请尽量简短(占用数据库字段空间)
* 任务参数任务处理时能够获取到的参数即各个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`
* 最大执行机器数量:限定调动执行的机器数量
* 报警配置:选择任务执行失败后报警通知的对象,需要事先录入。
### 任务管理
>可以方便地查看和管理系统当前录入的任务信息。
![任务管理](../img/oms-console-jobManager.png)
### 运行状态
>可以方便地查看当前运行的任务实例,点击详情即可获取详细的信息,点击日志可以查看通过`omsLogger`上报的日志,点击停止则可以强制终止该任务。
![运行状态](../img/oms-console-runningStatus.png)
#### 在线日志
![在线日志](../img/oms-console-onlineLog.png)

View File

@ -1,209 +0,0 @@
[toc]
# 任务配置
>前端界面目前可能有那么一点点丑...不过问题不是很大 (>▽<)
### 系统首页
> 展示了系统整体的概览和集群Worker列表。
![首页](../img/oms-console-main.png)
### 任务录入
>一切的起点。
![任务创建](../img/oms-console-jobCreator.png)
* 任务名称:名称,无特殊作用
* 任务描述:描述,无特殊作用,请尽量简短(占用数据库字段空间)
* 任务参数任务处理时能够获取到的参数即各个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`
* 最大执行机器数量:限定调动执行的机器数量
* 报警配置:选择任务执行失败后报警通知的对象。
### 任务管理
>可以方便地查看和管理系统当前录入的任务信息。
![任务管理](../img/oms-console-jobManager.png)
### 运行状态
>可以方便地查看当前运行的任务实例,点击详情即可获取详细的信息,点击停止则可以强制终止该任务。
![运行状态](../img/oms-console-runningStatus.png)
# 处理器开发
>搭载处理器的宿主应用需要添加`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)
```

View 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)
```

View 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|子TaskMap/MapReduce处理器专属开发者调用map方法时传递的子任务列表中的某一个|
|omsLogger|在线日志用法同Slf4J记录的日志可以直接通过控制台查看非常便捷和强大不过使用过程中需要注意频率可能对Server造成巨大的压力|

View 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的系统都需要先在控制台完成初始化即应用注册与用户录入。初始化操作在首页完成。
![Welcome Page](../img/oms-console-welcome.png)
* 每一个系统初次接入OhMyScheduler时都需要通过**应用注册**功能录入`appName`(接入应用的名称,需要保证唯一)和`appDescription`描述信息无实际用处至此应用初始化完成准备开发处理器Processor享受分布式调度和计算的便利之处吧
* 注册完成后,输入`appName`即可进入控制台。
* **用户注册**可录入用户信息,用于之后任务的报警配置。

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

119
others/oms-sql.sql Normal file
View 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;