java 定时同步数据的任务优化

前言

定时任务在系统中并不少见,主要目的是用于需要定时处理数据或者执行某个操作的情况下,如定时关闭订单,或者定时备份。而常见的定时任务分为2种,第一种:固定时间执行,如:每分钟执行一次,每天执行一次。第二种:延时多久执行,就是当发生一件事情后,根据这件时间发生的时间定时多久后执行任务,如:15分钟后关闭订单付款状态,24小时候后关闭订单并且释放库存,而由于第二种一般都是单一数据的处理(主要是指数据量不大,一般情况下只有一个主体处理对象,如:一个订单以及订单中的N个商品),所以一般情况下第二种出现性能问题的几率不大(不代表没有),所以本文主要是针对第一种定时任务来进行优化,而且主要是针对数据同步或者传递数据来进行优化,而优化的方式也是根据实际项目中的情况在不同阶段进行优化的

第一阶段

第一阶段属于原始阶段,逻辑也最为简单,由于同步分为数据同步和传递数据,而且2种的需求各不一致(主要是在于是否允许丢失),所以分开分析

第一种类型:传递数据

由于传递数据可以允许丢失,常见的场景如调用凭证推送(常见于接口需要暴露给第三方,为了安全性,可以定时推送调用凭证来保证接口安全性),消息推送(订单消费成功后推送消息,由于可能推送失败,所以需要进入定时任务进行重试,但是因为消息实时性,所以重试到一定次数后放弃重试)

传递数据在第一阶段设计非常简单,定时推送,有限的错误次数,同步成功后修改状态,同步失败后对失败次数+1,一旦超过错误次数,就不在继续尝试

第二种类型:同步数据

同步数据跟传递数据不同点在于同步数据一定需要保证数据能投递成功,否则就要一直进行重试,比如2个系统间的订单同步,会员信息同步等

同步数据再第一阶段也非常简单,定时同步数据,失败就设置同步状态为同步失败,每次同步就只查询状态为未同步和同步失败记录

第二阶段

一开始需要传递或者同步的系统很少,数据也少,所以没有什么问题,但是第二阶段不一样了,数据量稍微有所新增,但是增量不大,主要是需要同步的系统多了,打个比方,连锁商店,总部需要把数据下传到所有门店去,这样门店就不用每次去总部获取数据,这样太耗费时间了,当然门店每次从总部获取到数据可以缓存到本地,不过跟本文内容关系不大,所以这里不再讨论。由于需要同步的系统太多,所以延伸出另外一个问题,一旦一个系统的网络环境不好,会影响其他系统数据同步,所以在第二阶段,引入了黑名单机制,由于黑名单机制对于传递数据和同步数据大致相同,所以这里就不分开描述,有差异性的地方也会指出

黑名单具体处理机制

黑名单分二级:

第一级用于控制本次定时任务,当本次运行定时任务时,不同的接受数据服务器可能有0-N条数据需要同步,所以一旦进入第一级黑名单后,本次后面都不会向接受数据服务器发起请求,而是直接失败;

第二级用于控制多长时间内不进入重试,是控制整个的,从查询需要同步的数据时候就直接过滤并且设置为同步失败状态(传递消息需要对失败次数加1)

首先第一级,当请求不到接受数据的服务器的时候(链接失败,或者链接超时),会再重试2次(传递数据由于及时性要求,所以不会重试,并且超时时间也会合理的减少),如果2次都同步失败,这判断本条数据同步失败,并且进入第一级黑名单,并且判断一定时间内进入了几次第一级黑名单,具体使用redis控制,首先是否进入第一级黑名单直接程序中存储就好,一定时间段内进入了几次黑名单,就使用有序集合保存,排序的分值就存储当前时间戳

进入第一级黑名单后,使用一定时间内进入几次的限制条件,来判断是否进入第二级黑名单,比如5分钟进入3次第一级黑名单,就进入第二级黑名单,那么就查询分值大于5分钟前时间戳的数据集合,如果集合结果有3条或以上数据了,那么就进入第二级黑名单,同时清理掉redis中关于第一级黑名单存储的数据,如果没有3条数据,那么就删除分值小于5分钟前的时间戳的数据,避免垃圾数据过多

使用黑名单机制,可以有效避免一些因为服务本来不可访问导致一直还重试的问题,并且由于有二级黑名单,所以也一定程度上避免了因为暂时网络波动,导致数据长久无法同步的问题

第三阶段

由于需要传递的数据和需要同步数据的服务越来越多,并且由于各种问题导致很多数据不能一次性同步成功,所以每次定时任务都需要同步大量数据,这样就导致及时性很差了,比如几千条数据同步下来,就算一条只需要几十毫秒,从开始到最后一条数据同步成功也是几十秒之后了,所以需要再次对定时任务进行优化,数据量大而导致同步慢原因很简单,是由于单个线程串行同步的,也就是说必须要上一条数据处理了才能处理下一条数据,所以可以使用多线程来优化,提高硬件使用率

多线程的定时任务

当然肯定不可能给每条数据创建一个线程,先不说得创建多少条线程,仅仅是创建线程的消耗就已经很大了,而且线程数量太多,频繁切换线程上下文也会导致性能损耗,所以最合适的就是将数据分配到机器CPU核心数量的线程,或者核心数量*2的线程上去处理更合适,当然具体情况具体分析,最好还是具体测试得出合适的线程数量,同时由于肯定是会存在多个定时任务,所以可以多个定时任务使用同一个线程池,但是每个任务只使用合适线程数量来处理

线程数据分配原则

同一个被接受调用的数据的服务器的数据肯定是分配到一个线程中去处理,比如要分配8个线程来处理,那么可以创建8个集合,先保存查询出来需要被同步的数据,同时查询出来的数据根据被接受数据的服务器标识排序,用接受数据的服务器标识的hash值来%8来确定放入哪个集合,或者使用轮询的方式放入指定集合,分配好之后则创建8个runable放入线程池中去执行

防止定时任务叠加

开启多线程处理后,由于主线程在把任务放入线程池中运行的时候就会返回了,所以一定需要防止定时任务叠加,比如任务是10秒执行一次的,每次定时任务本身的线程只执行了1秒,下次定时任务的时候会发现定时任务已经处理完成,但是实际上真正同步数据的8个线程都没有执行完成,就会出现一条数据重复同步,或者把数据累加到上次任务的集合中去(看具体的处理方式导致不同的结果),最后就跟滚雪球一样,整个服务就算不崩溃,也会出现各种问题,或者就是浪费大量资源去做重复同步,所以为了防止任务叠加,需要使用闭锁来防止定时任务本身返回的情况,同时使用闭锁也要注意处理异常的情况,防止发生异常后,闭锁没有执行操作,导致定时任务一直不能返回

闭锁

使用闭锁防止定时任务返回,8个线程的情况下创建闭锁

CountDownLatch latch = new CountDownLatch(8); 

每个线程执行完数据后需要countDown方法来通知,或者叫关闭一个栅栏吧,创建闭锁的传入的8我们可以看成创建了8个栅栏

latch.countDown(); 

同时在定时任务的线程中,需要等待所有栅栏关闭才能继续执行,所以需要调用方法

latch.await();

这样只有所有线程执行完成后,定时任务的线程才会继续执行,防止任务叠加

使用多线程了,一定要注意多线程的一些线程安全以及其他的一些问题,如果对闭锁和多线程本身不够了解的话,可以自行去查阅一些相关资料

第四阶段

数据量非常大,接受数据的服务也非常多

一台服务器的硬件资源始终有限,尤其是网络资源,由于接受数据的服务不一定是内网服务,加上各种问题导致链接失败,所以数据量大的情况下,就算使用了多线程,还是会造成数据延迟很久才同步成功(主要延迟原因是网络问题),这时候就需要使用多台服务器了,而使用多台服务器定时执行就存在一个问题,数据分片,简单来说怎么保证一条数据只能被一台服务器处理,数据分片有2种方式,第一种:不同服务器处理不同的表的数据。第二种:数据本身主键或者某种标志分配处理

2种处理方式有各自的优缺点

第一种:

优点:简单,只需要简单拆分或者配置即可

缺点:无法扩展更多,最多只能可能扩展到数据表数量台服务器,并且对于热点数据无法更优处理,比如订单这些热点数据,始终都在一台服务器

第二种:

优点:理论上可无限扩展,可以针对热点数据专门扩展

缺点:配置麻烦,每次新增服务器需要重新配置

实现分片定时任务

由于第一种配置简单,而且扩展性不强,所以本文主要讲述第二种方式的实现;

如果所有数据有生成都有自增型主键id,那么最简单也最公平的就是给每台服务器配置一个从0开始连续的服务器id,每台服务器查询数据的时候加一个条件id%服务器台数=当前服务器id,注意这样会导致id列的索引可能无法命中(根据数据库不同,是否命中情况不一致),这样配置的好处就是绝对公平,每台服务器分配到的数据量是平等的,坏处就是一台服务器可能会给所有接受数据服务发起请求,无法更好的利用链接复用,另外也无法针对服务器配置来增加或者降低权重(当然可以一个服务器配置2个id的方式来实现,但是这样也不友好)

如果为了更好的利用链接复用,可以使用先计算出接受数据服务标志的hashcode值,然后跟进hashcode值%服务器台数=当前服务器id的形式,这样就可以将接受数据服务分组式的配置到某个服务器上去处理,当然如果接受数据服务本身存在很大的数据量差异,就不推荐这种方式了,毕竟这样容易把大量数据堆积到某台服务器上去处理

当然还有其他多种分片的配置方式,比如采用表配置的方式来配置哪台服务器处理哪些数据,也可以使用上面种方式的结合体,可以根据具体情况分析到底怎么样才能更适合的进行数据分片处理,当然常规情况下,采用id%服务器的台数是能满足大部分需求的

其他优化

当系统针对性能优化到一定程度的时候,就可以考虑从业务或者其他方面进行优化了,比如一旦有系统进入二级黑名单了,就发出警告通知,或者没有进入二级黑名单,但是却经常进入一级黑名单,也提出一个报警,这样可以让人去排查原因,确认是程序问题还是网络本身的问题。另外也可以设置一个阈值,某个接受数据的服务一直响应很慢,或者经常响应时间超过某个阈值的时候,可以考虑进行降权处理,或者排查程序已经网络相关的原因

以上就是java 定时同步数据的任务优化的详细内容,更多关于Java 定时任务的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java学习教程之定时任务全家桶

    定时任务应用非常广泛,Java提供的现有解决方案有很多. 本次主要讲schedule.quartz.xxl-job.shedlock等相关的代码实践. 一.SpringBoot使用Schedule 核心代码: @Component public class ScheduleTask { private Logger logger = LoggerFactory.getLogger(ScheduleTask.class); @Scheduled(cron = "0/1 * * * * ? &quo

  • java多线程编程之为什么要进行数据同步

    Java中的变量分为两类:局部变量和类变量.局部变量是指在方法内定义的变量,如在run方法中定义的变量.对于这些变量来说,并不存在线程之间共享的问题.因此,它们不需要进行数据同步.类变量是在类中定义的变量,作用域是整个类.这类变量可以被多个线程共享.因此,我们需要对这类变量进行数据同步.数据同步就是指在同一时间,只能由一个线程来访问被同步的类变量,当前线程访问完这些变量后,其他线程才能继续访问.这里说的访问是指有写操作的访问,如果所有访问类变量的线程都是读操作,一般是不需要数据同步的.那么如果不

  • java 中Spring task定时任务的深入理解

    java 中Spring task定时任务的深入理解 在工作中有用到spring task作为定时任务的处理,spring通过接口TaskExecutor和TaskScheduler这两个接口的方式为异步定时任务提供了一种抽象.这就意味着spring容许你使用其他的定时任务框架,当然spring自身也提供了一种定时任务的实现:spring task.spring task支持线程池,可以高效处理许多不同的定时任务.同时,spring还支持使用Java自带的Timer定时器和Quartz定时框架.

  • Java多线程编程实战之模拟大量数据同步

    背景 最近对于 Java 多线程做了一段时间的学习,笔者一直认为,学习东西就是要应用到实际的业务需求中的.否则要么无法深入理解,要么硬生生地套用技术只是达到炫技的效果. 不过笔者仍旧认为自己对于多线程掌握不够熟练,不敢轻易应用到生产代码中.这就按照平时工作中遇到的实际问题,脑补了一个很可能存在的业务场景: 已知某公司管理着 1000 个微信服务号,每个服务号有 1w ~ 50w 粉丝不等.假设该公司每天都需要将所有微信服务号的粉丝数据通过调用微信 API 的方式更新到本地数据库. 需求分析 对此

  • Quartz实现JAVA定时任务的动态配置的方法

    先说点无关本文的问题,这段时间特别的不爽,可能有些同学也遇到过.其实也可以说是小事一桩,但感觉也是不容忽视的.我刚毕业时的公司,每个人每次提交代码都有着严格的规范,像table和space的缩进都有严格的要求,可以说你不遵守开发规范就相当于线上bug问题,还是比较严重的.现在发现外面的公司真的是没那么重视这个不重要却又特别重要的问题啊,啊啊啊啊啊啊!!! 什么是动态配置定时任务? 回归正题,说下这次主题,动态配置.没接触过定时任务的同学可以先看下此篇:JAVA定时任务实现的几种方式 定时任务实现

  • Java定时任务的三种实现方式

    前言 现代的应用程序早已不是以前的那些由简单的增删改查拼凑而成的程序了,高复杂性早已是标配,而任务的定时调度与执行也是对程序的基本要求了. 很多业务需求的实现都离不开定时任务,例如,每月一号,移动将清空你上月未用完流量,重置套餐流量,以及备忘录提醒.闹钟等功能. Java 系统中主要有三种方式来实现定时任务: Timer和TimerTask ScheduledExecutorService 三方框架 Quartz 下面我们一个个来看. Timer和TimerTask 先看一个小 demo,接着我

  • Java 中Timer和TimerTask 定时器和定时任务使用的例子

    这两个类使用起来非常方便,可以完成我们对定时器的绝大多数需求 Timer类是用来执行任务的类,它接受一个TimerTask做参数 Timer有两种执行任务的模式,最常用的是schedule,它可以以两种方式执行任务:1:在某个时间(Data),2:在某个固定的时间之后(int delay).这两种方式都可以指定任务执行的频率 TimerTest.Java: package com.cn; import java.io.IOException; import java.util.Timer; pu

  • Java自带定时任务ScheduledThreadPoolExecutor实现定时器和延时加载功能

    java.util.concurrent.ScheduledThreadPoolExecutor 是JDK1 .6之后自带的包,功能强大,能实现定时器和延时加载的功能 各类功能和处理方面优于Timer 1.定时器: ScheduledThreadPoolExecutor  有个scheduleAtFixedRate(command, initialDelay, period, unit) ;方法 command: 执行的线程(可自己New一个) initialDelay:初始化执行的延时时间 p

  • Java实现终止线程池中正在运行的定时任务

    最近项目中遇到了一个新的需求,就是实现一个可以动态添加定时任务的功能.说到这里,有人可能会说简单啊,使用quartz就好了,简单粗暴.然而quartz框架太重了,小项目根本不好操作啊.当然,也有人会说,jdk提供了timer的接口啊,完全够用啊.但是我们项目的需求完全是多线程的模型啊,而timer是单线程的,so,楼主最后还是选择了jdk的线程池. 线程池是什么 Java通过Executors提供四种线程池,分别为: newCachedThreadPool :创建一个可缓存线程池,如果线程池长度

  • java 定时同步数据的任务优化

    前言 定时任务在系统中并不少见,主要目的是用于需要定时处理数据或者执行某个操作的情况下,如定时关闭订单,或者定时备份.而常见的定时任务分为2种,第一种:固定时间执行,如:每分钟执行一次,每天执行一次.第二种:延时多久执行,就是当发生一件事情后,根据这件时间发生的时间定时多久后执行任务,如:15分钟后关闭订单付款状态,24小时候后关闭订单并且释放库存,而由于第二种一般都是单一数据的处理(主要是指数据量不大,一般情况下只有一个主体处理对象,如:一个订单以及订单中的N个商品),所以一般情况下第二种出现

  • PHP使用SWOOLE扩展实现定时同步 MySQL 数据

    南宁公司和几个分公司之间都使用了呼叫系统,然后现在需要做一个呼叫通话数据分析,由于分公司的呼叫服务器是在内网,通过技术手段映射出来,分公司到南宁之间的网络不稳定,所以需要把分公司的通话数据同步到南宁. 本身最简单的方法就是直接配置MySQL的主从同步就可以同步数据到南宁来了.但是销售呼叫系统那边的公司不给MySQL权限我们. 所以这个方法只能放弃了. 于是我们干脆的想,使用PHP来实现定时一个简易的PHP定时同步工具,然后PHP进程常驻后台运行,所以首先就先到了一个PHP组件:SWOOLE,经过

  • java 较大数据量取差集,list.removeAll性能优化详解

    今天在优化项目中的考勤同步功能时遇到将考勤机中的数据同步到数据库, 两边都是几万条数据的样子,老代码的做法差不多半个小时,优化后我本机差不多40秒,服务器速度会更加理想. 两个数据集取差集首先想到的方法便是List.removeAll方法,但是实验发现jdk自带的List.removeAll效率很低 List.removeAll效率低原因: List.removeAll效率低和list集合本身的特点有关 : List底层数据结构是数组,查询快,增删慢 1.List.contains()效率没有h

  • 基于Java方式实现数据同步

    本文实例为大家分享了Java方式实现数据同步的具体代码,供大家参考,具体内容如下 使用java方式实现两个系统之间数据的同步. 业务背景 在新系统中设置定时任务需要实时把客户系统中的数据及时同步过来,保持数据的一致性. 实现逻辑 1.根据客户提供的接口,本系统中采用Http的Post请求方式获取接口数据.2.由于客户提供的接口必带页码和页面容量,因此会涉及到多次请求接口才能拿到全量数据,因此相同的操作可以采用递归的方式进行.3.每次请求一次接口根据页面容量(pageSize)可获取多条数据,此时

  • Java从同步容器到并发容器的操作过程

    引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的,即在多线程的环境下,都需要其他额外的手段来保证数据的正确性,最简单的就是通过synchronized关键字将所有使用到非线程安全的容器代码全部同步执行.这种方式虽然可以达到线程安全的目的,但存在几个明显的问题:首先编码上存在一定的复杂性,相关的代码段都需要添加锁.其次这种一刀切的做法在高并发情况下性

  • Java 处理高并发负载类优化方法案例详解

    java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据) 一:高并发高负载类网站关注点之数据库 没错,首先是数据库,这是大多数应用所面临的首个SPOF.尤其是Web2.0的应用,数据库的响应是首先要解决的. 一般来说MySQL是最常用的,可能最初是一个mysql主机,当数据增加到100万以上,那么,MySQL的效能急剧下降.常用的优化措施是M-S(主-从)方式进行同步复制,将查询和操作和分别在不同的服务器上进行操作.我推荐的是M-M-Slaves

  • Java中synchronized 的4个优化技巧

    目录 前言 1.锁膨胀 2.锁消除 3.锁粗化 4.自适应自旋锁 总结 前言 synchronized 在 JDK 1.5 时性能是比较低的,然而在后续的版本中经过各种优化迭代,它的性能也得到了前所未有的提升,上一篇文章我们谈到了锁膨胀对 synchronized 性能的提升,然而它也只是“众多” synchronized 性能优化方案中的一种,那么我们本文就来盘点一下 synchronized 的核心优化方案. synchronized 核心优化方案主要包含以下 4 个: 锁膨胀 锁消除 锁粗

  • java 中同步、异步、阻塞和非阻塞区别详解

    java 中同步.异步.阻塞和非阻塞区别详解 简单点说: 阻塞就是干不完不准回来,一直处于等待中,直到事情处理完成才返回: 非阻塞就是你先干,我先看看有其他事没有,一发现事情被卡住,马上报告领导. 我们拿最常用的send和recv两个函数来说吧... 比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来保存你Copy过来的数据的话

  • java 线程同步详细介绍及实例代码

    java 线程同步 概要: 为了加快代码的运行速度,我们采用了多线程的方法.并行的执行确实让代码变得更加高效,但随之而来的问题是,有很多个线程在程序中同时运行,如果它们同时的去修改一个对象,很可能会造成讹误的情况,这个时候我们需要用一种同步的机制来管理这些线程. (一)竞争条件 记得操作系统中,让我印象很深的有一张图.上面画的是一块块进程,在这些进程里面分了几个线程,所有这些线程齐刷刷统一的指向进程的资源.Java中也是如此,资源会在线程间共享而不是每个线程都有一份独立的资源.在这种共享的情况下

  • Java线程同步机制_动力节点Java学院整理

    在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系.可是有的时候,可能存在多个线程多同一个数据进行操作,这样,可能就会引用各种奇怪的问题.现在就来学习多线程对数据访问的控制吧. 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 一.多线程引起的数据访问安全问题 下面看一个经典的问题,银行取钱的问题: 1).你有一张银行卡,里面有5000

随机推荐