前言:
现时姐妹们对“数据库bean”大概比较看重,小伙伴们都想要剖析一些“数据库bean”的相关资讯。那么小编也在网络上收集了一些对于“数据库bean””的相关内容,希望咱们能喜欢,兄弟们一起来学习一下吧!任务管理
◎ 任务管理概述
◎ 实战演练
至此,微服务的概念、技术框架、实战用法基本上都介绍了,但在软件开发过程中,无论是项目还是产品,都有自己的独特性,不可能所有的项目都千篇一律。我们会遇到各种各样的场景,万能的解决方案不存在,需求是千变万化的,开发过程中会遇到各种问题,所以我们需要去思考,创造更多的技术框架、开发方法来解决问题。
除了一些宏观的架构和设计,微服务架构在技术细节上也有很多需要注意的地方,如任务管理,当然这可能是分布式架构的特性而不仅限于微服务架构。
任务管理概述
计划任务是软件开发中比较常见的一种手段,如有时需要在指定的时间点生成一些报告,或者定时从系统中同步数据,这些任务有一次性的,也有按照固定时间点执行的,还有些任务是按照间隔不间断地轮询,这些都是根据不同的需求设计出的不同类型的任务,这在单体式服务下很容易。
例如,Java项目可以用Quartz等框架方便地创建和配置出不同的计划任务,甚至一些简单的场景可以直接使用原生的JDK来创建定时任务,那么微服务模式下会遇到什么问题?在微服务模式下,首先需要考虑的就是任务互斥。
如何解决任务互斥
什么是任务互斥?从字面上理解就是任务相互排斥,即多个任务的执行会相互排斥,任务互斥允许任务在不同时间执行,也不限制任务的执行顺序,但一个任务开始执行需要等待另一个任务执行完成才行。
那么问题来了,任务为什么要互斥?通常如果多个任务都会修改同一个资源时,要考虑这些任务是否需要互斥,最常见的场景就是同一个服务多个实例下的任务互斥。在单体式架构下同一个任务只会有一个实例在执行,即同一时间只会有一个相同的任务在执行,只要任务设计在不同时间执行,就不会出现多个任务同时修改一个数据的问题。而在微服务模式下,采用的都是分布式的部署方式,通常我们的服务实例可以水平扩展多个节点,当一个任务在服务中被配置好后,一旦服务部署了多个实例,同一时刻,就会有多个相同的任务在执行,这些相同的任务会访问或修改相同的数据,很可能造成数据错乱的问题。
既然明白了任务互斥的理由,那么微服务下如何做到任务互斥?做法很简单,就像微服务定义说的那样,微服务中会有最小化的集中式服务来管理服务的发现、请求的负载分发,任务互斥也是一样。最简单的做法就是建立一个中心化的任务管理机制。当然,这个任务管理中心可以很小,如一个任务管理中心只负责一个服务下的不同节点的任务管理,以实现最小化任务管理中心,如图10.1所示。
建立的任务管理中心又是如何管理任务,做到任务互斥的呢?我们不妨思考一下,现在一个任务虽然有多个实例,但通过任务管理中心,每个实例可以相互沟通和交互,每个任务实例可以在要执行时知道当前任务的执行状态,即任务有没有被执行,被谁执行,正在执行还是已经执行完。只要知道了这些信息,在任务执行前确认任务没有被执行,就可以让当前实例执行任务。当然,这里的任务检查肯定要线程安全,这样通过任务管理中心就可以实现与本地进程的同步锁一样的效果,从而使同一个任务的不同实例只能有一个可以争抢到任务并执行,而其他实例只能等待下次执行任务时再去争抢执行权。
总结下思路,任务管理中心工作原理如图10.2所示。
任务调度平台
什么是任务调度?对于一些简单的场景,我们采用任务互斥足以解决问题,但是当服务越来越多时,海量的任务不仅需要互斥,还有些任务存在依赖关系,或者执行顺序要求,对于大量的任务更加需要了解任务的执行状态,需要管理任务的生命周期,如对任务执行状态的追踪、任务异常的处理、任务的执行结果报告等。
其实,大部分任务管理的职责都基本相同,一旦微服务数量庞大,针对每个服务都建立一个任务管理中心,可能会造成资源的浪费和带来更高的维护成本。在任务拥有一定规模之后,我们通常将这些最小的管理中心升级为任务调度服务,来提供任务调度的服务,完成统一的任务调度,监控任务的生命周期,所以任务调度平台建立的好处首先是降低重复的开发和维护成本,统一操作,不同项目的开发任务不用再学习和关心任务调度的逻辑,不需要对线程、Timer等机制有很深的了解,只需要考虑任务本身的设计即可,而且平台可以提供一些友好的操作界面,提高任务调度的易用性。
其次,任务调度平台将业务和调度隔离开来互不影响,新的任务只需增加相关配置即可,容易扩展,任务调度平台本身的集群也可以保证任务的高可用,一些分布式的任务调度平台设置可以根据任务执行的状态动态地扩展任务执行的资源配置。
实战演练
在了解微服务系统中任务管理的难点和解决方案后,我们在实际项目中又该如何实现呢?计划任务是软件开发中比较常见的一种手段,下面介绍一些常用框架和平台的用法。
Quartz
下面介绍一下实战的用法,以Java项目为例,提到任务调度,自然会想到Quartz框架,Spring Boot默认集成Quartz来实现任务调度的功能,通过几行简单的注解就可以完成一个任务的配置,代码如下。
在上述代码中,首先需要在 Spring Boot 的启动类中加上@EnableScheduling的注解,表示开启任务调度的功能,然后在任意Spring的配置类中添加任务代码即可,这里为了方便演示,就将任务方法(testJob)直接添加在启动类中,最后只需在方法上添加@Scheduled的注解配置需要的cron表达式来控制任务的调度策略即可。上述代码中的配置为每5s执行一次,我们可以运行这个应用来验证代码是否生效,项目启动后,在控制台中显示如下。
test job : 2019-04-22T22:55:00.003
test job: 2019-04-22T22:55:05.004
test job: 2019-04-22T22:55:10.004
test job: 2019-04-22T22:55:15.002
test job: 2019-04-22T22:55:20.001
test job: 2019-04-22T22:55:25.001
test job: 2019-04-22T22:55:30.004
确实很方便,不过这种用法并不是本节中要介绍的,因为在集群环境中,这种方式并不能做到之前在10.1.1节中提到的任务互斥,如果只想实现一个简单的任务,而不想搭建一个任务调度平台,该如何做到任务互斥呢?
其实要实现任务互斥,最简单的就是要有一个中心节点来负责管理任务的调度,默认的Quartz的任务处理控制的相关数据都保存在内存中,在集群环境下显然不能满足任务互斥的条件,不过Quartz也为我们提供了任务互斥的方式,就是将这些数据保存在数据库中,通过中心数据库来实现任务互斥,其原理如图10.3所示。
Quartz可以在数据库中记录任务执行各种信息,如任务执行状态、执行者(服务实例)、任务执行时间、任务执行类等信息,在任务未执行时,多个实例的Quartz就会争抢任务,一旦有一个实例的Quartz争抢到任务,就会通过修改数据库的数据将任务锁住,其他实例的Quartz就不会执行该任务;当执行任务的实例销毁时,其他实例就会接替任务执行者继续执行任务。
原理清楚了,下面介绍代码如何实现,首先要引入jar包,以Gradle为例,需要在build.gradle中添加如下代码。
Implementation 'org.springframework.boot:spring-boot-starter-quartz'
然后在数据库中创建Quartz所需要的数据表,在Quartz的jar包中可以找到对应各种数据库版 本的初始化脚本,package 路径为org.quartz.impl.jdbcjobstore,Quartz所提供的脚本如下。
tables_cloudscape.sql
tables_cubrid.sqltables_db2.sql
tables_db2_v8.sql
tables_db2_v72.sql
tables_db2_v95.sql
tables_derby.sql
tables_derby previous.sql
tables_firebird.sql
tables_h2.sql
tables_hsqldb.sql
tables_hsqldb_old.sql
tables_informix.sql
tables_mysql.sql
tables_mysql_innodb.sql
tables_oracle.sql
tables _pointbase.sql
tables_postgres.sql
tables_sapdb.sql
tables_solid.sql
tables_sqlServer.sql
tables_sybase.sql
根据我们需要的数据库类型选择一个数据库脚本将数据表初始化,也可以使用flyway来完成数据表的初始化,引入flyway的依赖,内容如下。
implementation 'org.flywaydb:flyway-core'
然后在resources的db/migration文件夹下添加对应的SQL文件,文件名格式为“V+Version(可以是时间)+"__"+”描述,具体如下。
V2019_04_22_19_45_Create_Table_Quartz_Mysql.sql
可以通过docker快速启动一个MySQL数据库,指令如下。
docker run --name mysql -p 3306:3306 -e MYSQL_DATABASE=quartz-test
-e MYSQL_ROOT_PASsWORD=123456 -d mysql:latest
在数据表创建完成之后,进行基础的配置工作,Spring Boot并没有Quartz数据库的默认配置,所以这里需要多一些手动的配置项,内容如下。
由上述配置可知,首先需要配置quartz的job-store-type类型为jdbc(还有一个类型是memory),所以还需要添加jdbc的相关依赖,build.gradle配置如下。
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'mysql:mysql-connector-java'
然后配置任意的实例名称和线程数量,这里需要注意的是任务存储配置,不同的数据库类型需要配置不同的driverDelegateClass,这里使用的是MySQL,所以使用通用的StdJDBCDelegate类,如果是SQLServer,driverDelegateClass就应该是MSSQLDelegate,对应的配置说明在Quartz的官网上可以查询到,官网地址是scheduler.org,最后,数据源的配置使用之前创建好的数据表相同的数据源即可。
关于Quartz的相关配置就配置好了,接下来开发任务代码,首先创建一个TestJob类并继承QuartzJobBean来完成任务的具体执行,代码如下。
import org.quartz.JobExecutionContext;import org.springframework.lang.Nullable;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class TestJob extends QuartzJobBean {
@Override
protected void executeInternal@Nullable JobExecutionContext context) {
System.out.println("test Job: " + LocalDateTime.now(.toString());
}
}
然后配置具体任务调度的策略,最常用的就是使用cron表达式,假设我们的cron表达式配置如下(可以配置在 spring 的application.yml中)。
testJob:
cron: ""0/5****?"
创建一个TestJobConfig类来完成任务调度的相关配置,代码如下。
在上述代码中,通过CronTriggerFactory来配置任务和调度的策略,通过JobDetailFactoryBean来配置一个任务,然后通过配置不同的端口启动多个实例模拟集群环境的场景来验证一下任务是否真的会互斥,关闭正在执行任务的实例,会发现另一个实例会继续执行任务。
这里还需要注意的是,由于所有任务数据都是保存在数据库中,也包括了任务的执行策略,如果我们修改了cron表达式,并不能及时生效,需要手动将数据库中的cron表达式改掉才行。不过,我们可以通过实现SchedulerFactoryBeanCustomizer来完成一些定制化的配置,代码如下。
在上述代码中,设置了覆盖已存在的任务为true,这样就可以在我们修改了服务的cron表达式配置后实时生效了。
XXL-JOB
XXL-JOB是许雪里老师所开发的一款轻量级的开源的分布式任务调度平台,相比其他的一些分布式任务调度平台,其用法更加简单,而且稳定性良好、易扩展,所以这里在演示任务调度平台时选择了它。
当然,这并不是说其他的框架或平台不好,当我们在做一些工具或框架选型时,更多的还是应该根据项目的实际情况来选择最合适的技术实践。
XXL-JOB的官网上有详细的使用说明,这里带着大家一起来搭建一个分布式的任务调度平台,体验一下任务调度平台的一些基础的功能用法,XXL-JOB的官网地址是,感兴趣的读者可以前往学习。
XXL-JOB的设计原理是将任务调度的工作分为了调度中心和执行器两个部分,调度中心与业务解耦,只关心任务的管理、执行器的管理、日志管理及任务的注册等功能,执行器则负责响应调度中心的调度,完成任务的执行使命,并提交相关日志,XXL-JOB的结构如图10.4所示。
在了解XXL-JOB的结构后,可知XXL-JOB分为xxl-job-admin和xxljob-core两个核心模块。其中,xxl-job-admin就是任务调度中心,官方文档上说明了xxl-job-admin也提供了Docker的方式安装部署,不过笔者在实验时发现镜像似乎无法下载,于是采取源码编译的方式进行安装和配置。首先我们需要下载xxl-job-admin的源码,国内用户推荐使用XXL-JOB的码云地址下载。
下载后,通过IDE或maven进行编译运行即可,不过在运行之前需要配置相应的数据库,xxl-job-admin支持MySQL数据库,在源码的该路径下可以找到相应的 MySQL 数 据 库 的 启 动 脚 本
job/tree/master/doc/db/tables_xxl_job.sql,这里数据库的安装不再详细介绍,可以继续使用Docker快速启动一个MySQL数据库,然后将脚本导入数据库中,脚本会创建一个名为xxl-job的数据库及相应的数据表。
因为xxl-job-admin采用的框架是Spring Boot,所以我们只需在对应的application.properties文件中配置数据库的信息即可,代码如下。
在上述配置中,除了可以配置基础的数据源,还可以修改管理员账号密码、邮件信息等配置,配置好后启动该工程,然后访问即可进入XXL-JOB的登录页,如图10.5所示。
这里需要注意的是,项目的logback.xml配置log.path是一个绝对路径,在运行时可能会报错路径不存在,这时修改路径或创建相匹配的路径即可。然后输入我们配置的管理员账号密码(配置项为xxl.job.login)即可登录,成功登录后,XXL-JOB首页如图10.6所示。
如果看到图10.6中的页面,证明任务调度中心已经部署成功,接下来就需要配置任务,也就是执行器。首先在我们需要完成任务的项目中引入xxl-job-core的依赖,假设还是使用Gradle,代码如下。
implementation 'com.xuxucli:xxl-job-core:2.0.2"
同样,在配置文件中配置一些XXL-JOB的信息,代码如下。
在上述配置中,我们需要配置xxl-job-admin的地址,选填配置注释得很清楚,这里不再赘述。配置文件写好后手动配置 一 个XxlJobSpringExecutor的SpringBean,可以参考XXL JOB源码中的Spring Boot集成示例,具体代码如下。
XXL-JOB执行器提供了很多种任务的执行方式,下面简单介绍一个最常用的Bean模式任务执行器,新建一个TestJobHandler类继承IJobHandler,代码如下。
在上述代码中,XxlJobLogger是XXL-JOB提供的日志接口,可以将日志输出并收集到任务调度中心xxl-job-admin上,这里的任务只是将参数输出在日志中,然后启动该执行器所在的项目。
接下来进入xxl-job-admin来进行执行器的配置,首先创建一个执行器,单击左侧的“执行器管理”菜单项,然后选择“新增执行器”选项,输入相关的AppName,与配置文件中的保持一致,如图10.7所示。
接着进入“任务管理”页面,单击“新增任务”,如图10.8所示。
在图10.8中,选择我们刚才创建好的执行器,编辑cron表达式,运行模式配置为“BEAN”,JobHandler配置与代码中@JobHandler注解所配置的value值要一致,这里还可以配置超时时间、失败重试和报警邮件等信息。最后,任务参数传递到任务执行代码中的param,单击“保存”按钮后返回任务管理列表,如图10.9所示。
在列表中,可以看到之前创建的任务,并且状态是“STOP”,在操作项中,可以单击“执行”按钮来执行一次任务,快速地验证任务是否可以正常执行,单击“启动”按钮即可持续运行该任务。然后单击“日志”按钮就可以看到任务的执行记录,任务日志列表如图10.10所示。
在日志列表中单击“操作”列中的“执行日志”,即可看到任务执行日志的详情,如图10.11所示。
除了执行日志,我们还可以在任务调度中心的首页查看相关任务的统计情况,如图10.12所示。
除了常用的基础任务配置,XXL-JOB还提供了多种任务调度的方式,可以实现集群部署、分片执行任务等功能,支持弹性扩容缩容,一旦有新的执行器上线或下线,下次调度时将重新分配任务。
本文给大家讲解的内容是微服务架构在技术细节上的任务管理;下篇文章给大家讲解的是软件开发中的难点——事务管理;觉得文章不错的朋友可以转发此文关注小编;感谢大家的支持!
标签: #数据库bean