虽然 Quartz 也可以通过集群方式来保证服务高可用,但是它也有一个的弊端,那就是服务节点数量的增加,并不能提升任务的执行效率,即不能实现水平扩展

之所以产生这样的结果,是因为 Quartz 在分布式集群环境下是通过数据库锁方式来实现有且只有一个有效的服务节点来运行服务,从而保证服务在集群环境下定时任务不会被重复调用

如果需要运行的定时任务很少的话,使用 Quartz 不会有太大的问题,但是如果 现在有这么一个需求,例如理财产品,每天6点系统需要计算每个账户昨天的收益,假如这个理财产品,有几个亿的用户,如果都在一个服务实例上跑,可能第二天都无法处理完这项任务!

类似这样场景还有很多很多,很显然 Quartz 很难满足我们这种大批量、任务执行周期长的任务调度

因此短板,当当网基于 Quartz 开发了一套适合在分布式环境下能高效率的使用服务器资源的 Elastic-Job 定时任务框架!

Elastic-Job-Lite最大的亮点就是支持弹性扩容缩容,怎么实现的呢?

比如现在有个任务要执行,如果将任务进行分片成10个,那么可以同时在10个服务实例上并行执行,互相不影响,从而大大的提升了任务执行效率,并且充分的利用服务器资源!

对于上面的理财产品,如果这个任务需要处理1个亿用户,那么我们可以通过水平扩展,比如对任务进行分片为500,让500个服务实例同时运行,每个服务实例处理20万条数据,不出意外的话,1 - 2个小时可以全部跑完,如果时间还是很长,还可以继续水平扩张,添加服务实例来运行!

2015 年,当当网将其开源,瞬间吸引了一大批程序员的关注,同时登顶开源中国第一名!

下面我们就一起来了解一下这款使用非常广泛的分布式调度框架。

二、项目架构介绍

Elastic-Job 最开始只有一个 elastic-job-core 的项目,定位轻量级、无中心化,最核心的服务就是支持弹性扩容和数据分片!

从 2.X 版本以后,主要分为 Elastic-Job-Lite 和 Elastic-Job-Cloud 两个子项目。

其中,Elastic-Job-Lite 定位为轻量级 无 中 心 化 解 决 方 案 , 使 用jar 包 的 形 式 提 供 分 布 式 任 务 的 协 调 服 务 。

而 Elastic-Job-Cloud 使用 Mesos + Docker 的解决方案,额外提供资源治理、应用分发以及进程隔离等服务(跟 Lite 的区别只是部署方式不同,他们使用相同的 API,只要开发一次)。

今天我们主要介绍的是Elastic-Job-Lite,最主要的功能特性如下:

  • 分布式调度协调:采用 zookeeper 实现注册中心,进行统一调度。
  • 支持任务分片:将需要执行的任务进行分片,实现并行调度。
  • 支持弹性扩容缩容:将任务拆分为 n 个任务项后,各个服务器分别执行各自分配到的任务项。一旦有新的服务器加入集群,或现有服务器下线,elastic-job 将在保留本次任务执行不变的情况下,下次任务开始前触发任务重分片。

当然,还有失效转移、错过执行作业重触发等等功能,大家可以访问官网文档,以获取更多详细资料。

应用在各自的节点执行任务,通过 zookeeper 注册中心协调。节点注册、节点选举、任务分片、监听都在 E-Job 的代码中完成。下图是官网提供得架构图。

image

啥也不用多说了,下面我们直接通过实践介绍,更容易了解里面是怎么玩的!

三、应用实践

3.1、zookeeper 安装

elastic-job-lite,是直接依赖 zookeeper 的,因此在开发之前我们需要先准备好对应的 zookeeper 环境,关于 zookeeper 的安装过程,就不多说了,非常简单,网上都有教程!

3.2、elastic-job-lite-console 安装

elastic-job-lite-console,主要是一个任务作业可视化界面管理系统。

可以单独部署,与平台不关,主要是通过配置注册中心和数据源来抓取数据。

获取的方式也很简单,直接访问https://github.com/apache/shardingsphere-elasticjob地址,然后切换到2.1.5的版本号,然后执行mvn clean install进行打包,获取对应的安装包将其解压,进行bin文件夹启动服务即可!

image

如果你的网速像蜗牛一样的慢,还有一个办法就是从这个地址https://gitee.com/elasticjob/elastic-job获取对应的源码!

启动服务后,在浏览器访问http://127.0.0.1:8899,输入账户、密码(都是root)即可进入控制台页面,类似如下界面!

image

进入之后,将上文所在的 zookeeper 注册中心进行配置,包括数据库 mysql 的数据源也可以配置一下!

3.3、创建工程

本文采用springboot来搭建工程为例,创建工程并添加elastic-job-lite依赖!

<!-- 引入elastic-job-lite核心模块 -->
<dependency><groupId>com.dangdang</groupId><artifactId>elastic-job-lite-core</artifactId><version>2.1.5</version>
</dependency><!-- 使用springframework自定义命名空间时引入 -->
<dependency><groupId>com.dangdang</groupId><artifactId>elastic-job-lite-spring</artifactId><version>2.1.5</version>
</dependency>

在配置文件application.properties中提前配置好 zookeeper 注册中心相关信息!

#zookeeper config
zookeeper.serverList=127.0.0.1:2181
zookeeper.namespace=example-elastic-job-test

3.4、新建 ZookeeperConfig 配置类

@Configuration
@ConditionalOnExpression("'${zookeeper.serverList}'.length() > 0")
public class ZookeeperConfig {/*** zookeeper 配置* @return*/@Bean(initMethod = "init")public ZookeeperRegistryCenter zookeeperRegistryCenter(@Value("${zookeeper.serverList}") String serverList, @Value("${zookeeper.namespace}") String namespace){return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList,namespace));}}

3.5、新建任务处理类

elastic-job支持三种类型的作业任务处理!

  • Simple 类型作业:Simple 类型用于一般任务的处理,只需实现SimpleJob接口。该接口仅提供单一方法用于覆盖,此方法将定时执行,与Quartz原生接口相似。
  • Dataflow 类型作业:Dataflow 类型用于处理数据流,需实现DataflowJob接口。该接口提供2个方法可供覆盖,分别用于抓取(fetchData)和处理(processData)数据。
  • Script类型作业:Script 类型作业意为脚本类型作业,支持 shell,python,perl等所有类型脚本。只需通过控制台或代码配置 scriptCommandLine 即可,无需编码。执行脚本路径可包含参数,参数传递完毕后,作业框架会自动追加最后一个参数为作业运行时信息。

3.6、新建 Simple 类型作业

编写一个SimpleJob接口的实现类MySimpleJob,当前工作主要是打印一条日志。

@Slf4j
public class MySimpleJob implements SimpleJob {@Overridepublic void execute(ShardingContext shardingContext) {log.info(String.format("Thread ID: %s, 作业分片总数: %s, " +"当前分片项: %s.当前参数: %s," +"作业名称: %s.作业自定义参数: %s",Thread.currentThread().getId(),shardingContext.getShardingTotalCount(),shardingContext.getShardingItem(),shardingContext.getShardingParameter(),shardingContext.getJobName(),shardingContext.getJobParameter()));}
}

创建一个MyElasticJobListener任务监听器,用于监听MySimpleJob的任务执行情况。

@Slf4j
public class MyElasticJobListener implements ElasticJobListener {private long beginTime = 0;@Overridepublic void beforeJobExecuted(ShardingContexts shardingContexts) {beginTime = System.currentTimeMillis();log.info("===>{} MyElasticJobListener BEGIN TIME: {} <===",shardingContexts.getJobName(),  DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));}@Overridepublic void afterJobExecuted(ShardingContexts shardingContexts) {long endTime = System.currentTimeMillis();log.info("===>{} MyElasticJobListener END TIME: {},TOTAL CAST: {} <===",shardingContexts.getJobName(), DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"), endTime - beginTime);}}

创建一个MySimpleJobConfig类,将MySimpleJob其注入到zookeeper。

@Configuration
public class MySimpleJobConfig {/*** 任务名称*/@Value("${simpleJob.mySimpleJob.name}")private String mySimpleJobName;/*** cron表达式*/@Value("${simpleJob.mySimpleJob.cron}")private String mySimpleJobCron;/*** 作业分片总数*/@Value("${simpleJob.mySimpleJob.shardingTotalCount}")private int mySimpleJobShardingTotalCount;/*** 作业分片参数*/@Value("${simpleJob.mySimpleJob.shardingItemParameters}")private String mySimpleJobShardingItemParameters;/*** 自定义参数*/@Value("${simpleJob.mySimpleJob.jobParameters}")private String mySimpleJobParameters;@Autowiredprivate ZookeeperRegistryCenter registryCenter;@Beanpublic MySimpleJob mySimpleJob() {return new MySimpleJob();}@Bean(initMethod = "init")public JobScheduler simpleJobScheduler(final MySimpleJob mySimpleJob) {//配置任务监听器MyElasticJobListener elasticJobListener = new MyElasticJobListener();return new SpringJobScheduler(mySimpleJob, registryCenter, getLiteJobConfiguration(), elasticJobListener);}private LiteJobConfiguration getLiteJobConfiguration() {// 定义作业核心配置JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder(mySimpleJobName, mySimpleJobCron, mySimpleJobShardingTotalCount).shardingItemParameters(mySimpleJobShardingItemParameters).jobParameter(mySimpleJobParameters).build();// 定义SIMPLE类型配置SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig, MySimpleJob.class.getCanonicalName());// 定义Lite作业根配置LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).overwrite(true).build();return simpleJobRootConfig;}
}

在配置文件application.properties中配置好对应的mySimpleJob参数!

#elastic job
#simpleJob类型的job
simpleJob.mySimpleJob.name=mySimpleJob
simpleJob.mySimpleJob.cron=0/15 * * * * ?
simpleJob.mySimpleJob.shardingTotalCount=3
simpleJob.mySimpleJob.shardingItemParameters=0=a,1=b,2=c
simpleJob.mySimpleJob.jobParameters=helloWorld

运行程序,看看效果如何?

image

image

在上图demo中,配置的分片数为3,这个时候会有3个线程进行同时执行任务,因为都是在一台机器上执行的,这个任务被执行来3次,下面修改一下端口配置,创建三个相同的服务实例,在看看效果如下:

image

很清晰的看到任务被执行一次!

3.7、新建 DataFlowJob 类型作业

DataFlowJob 类型的任务配置和SimpleJob类似,操作也很简单!

创建一个DataflowJob类型的实现类MyDataFlowJob。

@Slf4j
public class MyDataFlowJob implements DataflowJob<String> {private boolean flag = false;@Overridepublic List<String> fetchData(ShardingContext shardingContext) {log.info("开始获取数据");if (flag) {return null;}return Arrays.asList("qingshan", "jack", "seven");}@Overridepublic void processData(ShardingContext shardingContext, List<String> data) {for (String val : data) {// 处理完数据要移除掉,不然就会一直跑,处理可以在上面的方法里执行。这里采用 flaglog.info("开始处理数据:" + val);}flag = true;}
}

接着创建MyDataFlowJob的配置类,将其注入到zookeeper注册中心。

Configuration
public class MyDataFlowJobConfig {/*** 任务名称*/@Value("${dataflowJob.myDataflowJob.name}")private String jobName;/*** cron表达式*/@Value("${dataflowJob.myDataflowJob.cron}")private String jobCron;/*** 作业分片总数*/@Value("${dataflowJob.myDataflowJob.shardingTotalCount}")private int jobShardingTotalCount;/*** 作业分片参数*/@Value("${dataflowJob.myDataflowJob.shardingItemParameters}")private String jobShardingItemParameters;/*** 自定义参数*/@Value("${dataflowJob.myDataflowJob.jobParameters}")private String jobParameters;@Autowiredprivate ZookeeperRegistryCenter registryCenter;@Beanpublic MyDataFlowJob myDataFlowJob() {return new MyDataFlowJob();}@Bean(initMethod = "init")public JobScheduler dataFlowJobScheduler(final MyDataFlowJob myDataFlowJob) {MyElasticJobListener elasticJobListener = new MyElasticJobListener();return new SpringJobScheduler(myDataFlowJob, registryCenter, getLiteJobConfiguration(), elasticJobListener);}private LiteJobConfiguration getLiteJobConfiguration() {// 定义作业核心配置JobCoreConfiguration dataflowCoreConfig = JobCoreConfiguration.newBuilder(jobName, jobCron, jobShardingTotalCount).shardingItemParameters(jobShardingItemParameters).jobParameter(jobParameters).build();// 定义DATAFLOW类型配置DataflowJobConfiguration dataflowJobConfig = new DataflowJobConfiguration(dataflowCoreConfig, MyDataFlowJob.class.getCanonicalName(), false);// 定义Lite作业根配置LiteJobConfiguration dataflowJobRootConfig = LiteJobConfiguration.newBuilder(dataflowJobConfig).overwrite(true).build();return dataflowJobRootConfig;}
}

最后,在配置文件application.properties中配置好对应的myDataflowJob参数!

#dataflow类型的job
dataflowJob.myDataflowJob.name=myDataflowJob
dataflowJob.myDataflowJob.cron=0/15 * * * * ?
dataflowJob.myDataflowJob.shardingTotalCount=1
dataflowJob.myDataflowJob.shardingItemParameters=0=a,1=b,2=c
dataflowJob.myDataflowJob.jobParameters=myDataflowJobParamter

运行程序,看看效果如何?

image

需要注意的地方是,如果配置的是流式处理类型,它会不停的拉取数据、处理数据,在拉取的时候,如果返回为空,就不会处理数据!

如果配置的是非流式处理类型,和上面介绍的simpleJob类型,处理一样!

3.8、新建 ScriptJob 类型作业

ScriptJob 类型的任务配置和上面类似,主要是用于定时执行某个脚本,一般用的比较少!

因为目标是脚本,没有执行的任务,所以无需编写任务作业类型!

只需要编写一个ScriptJob类型的配置类即可,命令是echo 'Hello World !内容!

@Configuration
public class MyScriptJobConfig {/*** 任务名称*/@Value("${scriptJob.myScriptJob.name}")private String jobName;/*** cron表达式*/@Value("${scriptJob.myScriptJob.cron}")private String jobCron;/*** 作业分片总数*/@Value("${scriptJob.myScriptJob.shardingTotalCount}")private int jobShardingTotalCount;/*** 作业分片参数*/@Value("${scriptJob.myScriptJob.shardingItemParameters}")private String jobShardingItemParameters;/*** 自定义参数*/@Value("${scriptJob.myScriptJob.jobParameters}")private String jobParameters;@Autowiredprivate ZookeeperRegistryCenter registryCenter;@Bean(initMethod = "init")public JobScheduler scriptJobScheduler() {MyElasticJobListener elasticJobListener = new MyElasticJobListener();return new JobScheduler(registryCenter, getLiteJobConfiguration(), elasticJobListener);}private LiteJobConfiguration getLiteJobConfiguration() {// 定义作业核心配置JobCoreConfiguration scriptCoreConfig = JobCoreConfiguration.newBuilder(jobName, jobCron, jobShardingTotalCount).shardingItemParameters(jobShardingItemParameters).jobParameter(jobParameters).build();// 定义SCRIPT类型配置ScriptJobConfiguration scriptJobConfig = new ScriptJobConfiguration(scriptCoreConfig, "echo 'Hello World !'");// 定义Lite作业根配置LiteJobConfiguration scriptJobRootConfig = LiteJobConfiguration.newBuilder(scriptJobConfig).overwrite(true).build();return scriptJobRootConfig;}
}

在配置文件application.properties中配置好对应的myScriptJob参数!

#script类型的job
scriptJob.myScriptJob.name=myScriptJob
scriptJob.myScriptJob.cron=0/15 * * * * ?
scriptJob.myScriptJob.shardingTotalCount=3
scriptJob.myScriptJob.shardingItemParameters=0=a,1=b,2=c
scriptJob.myScriptJob.jobParameters=myScriptJobParamter

运行程序,看看效果如何?

image

3.9、将任务状态持久化到数据库

可能有的人会发出疑问,elastic-job是如何存储数据的,用ZooInspector客户端链接zookeeper注册中心,你发现对应的任务配置被存储到相应的树根上!

[图片上传失败...(image-393d80-1624434162775)]

而具体作业任务执行轨迹和状态结果是不会存储到zookeeper,需要我们在项目中通过数据源方式进行持久化!

将任务状态持久化到数据库配置过程也很简单,只需要在对应的配置类上注入数据源即可,以MySimpleJobConfig为例,代码如下:

@Configuration
public class MySimpleJobConfig {/*** 任务名称*/@Value("${simpleJob.mySimpleJob.name}")private String mySimpleJobName;/*** cron表达式*/@Value("${simpleJob.mySimpleJob.cron}")private String mySimpleJobCron;/*** 作业分片总数*/@Value("${simpleJob.mySimpleJob.shardingTotalCount}")private int mySimpleJobShardingTotalCount;/*** 作业分片参数*/@Value("${simpleJob.mySimpleJob.shardingItemParameters}")private String mySimpleJobShardingItemParameters;/*** 自定义参数*/@Value("${simpleJob.mySimpleJob.jobParameters}")private String mySimpleJobParameters;@Autowiredprivate ZookeeperRegistryCenter registryCenter;@Autowiredprivate DataSource dataSource;;@Beanpublic MySimpleJob stockJob() {return new MySimpleJob();}@Bean(initMethod = "init")public JobScheduler simpleJobScheduler(final MySimpleJob mySimpleJob) {//添加事件数据源配置JobEventConfiguration jobEventConfig = new JobEventRdbConfiguration(dataSource);MyElasticJobListener elasticJobListener = new MyElasticJobListener();return new SpringJobScheduler(mySimpleJob, registryCenter, getLiteJobConfiguration(), jobEventConfig, elasticJobListener);}private LiteJobConfiguration getLiteJobConfiguration() {// 定义作业核心配置JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder(mySimpleJobName, mySimpleJobCron, mySimpleJobShardingTotalCount).shardingItemParameters(mySimpleJobShardingItemParameters).jobParameter(mySimpleJobParameters).build();// 定义SIMPLE类型配置SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig, MySimpleJob.class.getCanonicalName());// 定义Lite作业根配置LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).overwrite(true).build();return simpleJobRootConfig;}
}

同时,需要在配置文件application.properties中配置好对应的datasource参数!

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/example-elastic-job-test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

运行程序,然后在elastic-job-lite-console控制台配置对应的数据源!

image

最后,点击【作业轨迹】即可查看对应作业执行情况!

image

image

四、小结

本文主要围绕elasticjob的使用进行简单介绍,希望大家有所收获!

在分布式环境环境下,elastic-job-lite支持的弹性扩容、任务分片是最大的亮点,在实际使用的时候,任务分片总数尽可能大于服务实例个数,并且是倍数关系,这样任务在分片的时候,会更加均匀!

如果想深入的了解elasticjob,大家可以访问官方文档,获取更加详细的使用教程!


http://www.taodudu.cc/news/show-5446598.html

相关文章:

  • 【elastic-job的使用】
  • 第五章 - 分布式定时任务框架ElasticJob之SpringBoot整合SimpleJob作业(实战一)
  • oracle cusor游标,oracle 游标cursor
  • [杂货铺系列]SpringBoot集成ElasticJob遇到的版本不兼容问题
  • Elastic-Job的快速入门,三分钟带你体验分布式定时任务
  • 【Flink源码】JobManager启动流程
  • ElasticJob分布式定时任务调度框架以及生产遇到的问题
  • RMQ——忠诚题解
  • 海尔集团 | 100道笔试题曝光(判断、问答、名词解释、综合题)附答案
  • Java|Socket编程指南
  • 内网映射到公网实例(APP:花生壳 项目:若依前后端分离版-后端)
  • 黑群晖+花生壳内网穿透
  • 使用花生壳远程连接服务器,服务器的IP地址改变时,重连服务器的方法
  • Ubuntu20.04系统安装驱动后重启黑屏
  • 数学建模 -- 预测模型
  • 数学建模--30+种常用算法模型
  • html获取访客ip,jQuery获取访问者IP地址的方法(基于新浪API与QQ查询接口)
  • 通过QQ查询ip地址
  • 旅游景点信息管理系统C语言,[源码和文档分享]基于C++的景区旅游信息管理系统...
  • Ettercap-arp欺骗
  • Linux的内网渗透入门教程:第一节 通过arp欺骗进行网关劫持,截获别人手机的图片(VMware环境)
  • 计算机网络实验二:ARP欺骗
  • ARP协议,ARP攻击和ARP欺骗
  • 西电计网ARP欺骗实验
  • ARP欺骗与防御
  • 四款python中文分词系统简单测试
  • Python下如何调用NLPIR(ICTCLAS2013)分词系统
  • 基于python大数据设计的汉语分析分词系统(完整的代码+数据)
  • 声波的色散和频散曲线
  • python绘制曲线视频_使用Python绘制各种优美简单曲线

分布式调度框架 elastic-job 实践详解(超详细)相关推荐

  1. Java 泛型详解(超详细的java泛型方法解析)

    Java 泛型详解(超详细的java泛型方法解析) 1. 什么是泛型 泛型:是一种把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型.也就是说在泛型使用过程中,操作的数据类型被指定为 ...

  2. 开源调度框架xxl-job集成SpringBatch详解

    文章目录 一.启动xxl-job调度中心 二.配置部署执行器项目 1.maven依赖 2.执行器配置 3.执行器组件配置 4.springbatch调度任务开发 5.调度中心,新建执行器 6.调度中心 ...

  3. log4j 配置详解(超详细)

    一.Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

  4. 【bind()函数】JavaScript手写bind()及详解-超详细~~~

    这两天学习了手写call.apply.bind,手写bind思考了很久才实现了MDN的示例的结果,所以记录下来~ 因为是第一篇文章,所以可能存在一些错误,希望各位大佬批评指正,不吝赐教. 也欢迎不懂的 ...

  5. @Autowired注解详解——超详细易懂

    @Autowired详解 要搞明白@Autowired注解就是要了解它是什么?有什么作用?怎么用?为什么? 首先了解一下IOC操作Bean管理,bean管理是指(1)spring创建对象 (2)spr ...

  6. 测试用例、测试流程模型、测试方法详解 超详细分解

    1. 测试用例 1.1 测试用例前提 什么是测试用例? 一组由前提条件.测试输入.执行条件以及预期结果等组成,以完成对某个特定需求或者目标测试的数据,体现测试方案.方法.技术和策略的文档. 为什么编写 ...

  7. 斜率优化详解(超详细, 有图有代码有注释)

    文章目录 斜率优化引入 从例题开始 斜率优化Part 1: 推为斜率式 斜率优化Part 2: 合法点集的斜率单调性 Part 3: 找到最优决策点 Part 4: 斜率优化大流程 Part 5: 斜 ...

  8. 一、初识Metasploit(MSF使用详解超详细)

    前言:Metasploit是The Metasploit Framework的简称,也可以叫做MSF! MSF高度模块化即框架由多个module组成,是全球最受欢迎的工具. Metasploit Fr ...

  9. web服务器常见配置搭建详解(超详细)

    前言: 本博客借鉴一些写的比较好的博客,进行归纳总结,整理了一篇比较详细的服务器常见配置搭建教程 一来是和大家一起分享,二来也是作为自己的学习笔记记录一下. 温馨提示: 篇幅较长,请分阶段选择性查看. ...

  10. Spring data JPA使用详解(超详细)

    目录 Jpa 元模型 Criteria查询 CriteriaBuilder 安全查询创建工厂 CriteriaQuery 安全查询主语句 Root Predicate 过滤条件 Predicate 多 ...

最新文章

  1. php每分钟刷新一次的验证码,php如何在进入页面的时候自动刷新一次验证码
  2. 理财心理陷阱之:年终奖——年末的虚假繁荣
  3. jquery ajax中使用jsonp的限制[转]
  4. opencv读取中文路径图片
  5. Sql Server 得到当月第一天
  6. 【跟网上的大多数不一样】rstudio plot不显示图片了
  7. python 原始字符串r的用法
  8. my task doorway - where is it parsed
  9. 软件行业为什么那么多项目经理
  10. 解决nginx无法启动的问题——端口被占用
  11. python function terminated un_Python: 僵尸进程的产生和清除方法
  12. 安装keepalived执行make报错的解决方法
  13. Linux 安装Python3
  14. Java 操作Word书签(二):添加文本、图片、表格到书签内容
  15. exe机器码加密视频如何破解提取脱壳
  16. Vue/ElementUI上传文件检验
  17. 如何判断网线连接状态
  18. SQL 忽略大小写模糊查询
  19. SAP HANA Temporal Table (历史表)
  20. 手机摄影-参数(光圈)

热门文章

  1. iOS中NSDate常用转换操作整合
  2. 化学中计算机模拟试题,化学模拟试题及答案1
  3. D3.js V5.0 词云图(标签云)
  4. 演讲实录 | Service Mesh 时代的选边与站队(附PPT下载)
  5. Jupyter-Notebook笔记-02 基本使用
  6. SNMP(简单网络管理协议)介绍
  7. 为什么很多公司选择在年底裁员?
  8. 资产管理系统测试用例--个人信息功能
  9. 用Python一分钟选出底部放量跳空上扬的强势股,你也可是巴菲特
  10. Hadoop学习笔记之修改主机名IP地址