定时任务扫表缺点解决方案

通过定时任务扫表,是我们在业务中经常会做的事情,一般是直接用xxl-job等定时任务去分页查询数据库,然后进行业务操作,这个方案,一般是最简单的,也是最有效的。

但是,他还是有一些缺点的,如:

  1. 数据量多扫表慢
  2. 集中式扫表会影响正常业务
  3. 定时扫表存在延迟问题

数据量多,扫表慢

随着需要扫描的表中的数据量越来越大,通过定时任务扫表的方式会越来越慢,那么想要解决这个问题,

  1. 首先可以考虑加索引。
  2. 其次,可以考虑多线程并发扫表.
    这里可以考虑采用线程池,在任务中开多个线程并发的从数据库中扫描数据进行处理。

但是这样做,会带来一个问题,那就是多个线程之间如何做好隔离,如何确保不会出现并发导致同一条记录被多个线程执行多次呢?

  1. 首先最基本的保障,扫表之后的处理逻辑要做好幂等控制,一旦出现了重复的情况,下游也能因为做了幂等而不会重复处理。
  2. 除此以外,在扫表的时候,可以通过分段的思想进行数据隔离。举个例子:
Long minId = messageService.getMinInitId();


for(int i=1; i <= threadPool.size(); i++){
    Long maxId = minId + segmentSize() * i;

    List<Message> messages = messageService.scanInitMessages(minId,maxId);

    proccee(messages);
    minId = maxId + 1;
}

像上面的例子中,假设有10个线程,那么第一个线程就扫描ID处于0-1000的数据,第二个线程扫描1001-2000的数据,第三个线程扫描2001-3000的数据。这样以此类推,线程之间通过分段的方式就做好了隔离,可以避免同一个数据被多个线程扫描到。

这个做法,有个小问题,那就是数据的ID可能不是连续的,那么就需要考虑其他的分段方式,比如在时间表中增加一个业务ID,然后根据这个biz_id做分片也可以。

比如:

for(int i=1; i <= threadPool.size(); i++){
    List<Message> messages = messageService.scanInitMessages(i);
    proccee(messages);
}

这样在SQL中:

SELECT * FROM RETRY_MESSAGE WHERE 
STATE = "1"
AND BIZ_ID LIKE "${frontNumber}%"

那么,不同的线程执行的SQL就不一样了分别是:

SELECT * FROM RETRY_MESSAGE WHERE 
STATE = "1"
AND BIZ_ID LIKE "1%"

SELECT * FROM RETRY_MESSAGE WHERE 
STATE = "1"
AND BIZ_ID LIKE "2%"

SELECT * FROM RETRY_MESSAGE WHERE 
STATE = "1"
AND BIZ_ID LIKE "3%"

SELECT * FROM RETRY_MESSAGE WHERE 
STATE = "1"
AND BIZ_ID LIKE "4%"

这样也是可以做分段的。

集中式扫表会影响正常业务

如果业务量比较大的话,集中式的扫描数据库势必给数据库带来一定的压力,那么就会影响到正常的业务。

那么想要解决这个问题,

  1. 首先可以考虑,不扫主库,而是扫描备库。之所以能这么做,是因为这个业务场景一般都是可以接受一定的数据延迟的,那么备库带来延迟就可以忽略,但是备库是没有业务操作的,所以对备库的扫描是不会对业务造成影响的。

当然,这里还要考虑一个问题,那就是备库扫描数据之后的执行,执行完该如何同步到主库,这里可以直接修改主库,主备库数据ID一致的,直接去修改主库的就行了。不建议直接在备库上修改。

但是不管怎么样,备库还是可以分担扫表的这个大量高峰请求的。

除了扫备库,还有一个方案,

  1. 那就是做分库了。把原来集中在同一个数据库的数据分散到不同的数据库中,这样用集群代替单库来整体对外提供服务,可以大大的提升吞吐量。

因为多个数据库的话,每个库提供的连接数就会多,并且多个实例的话,CPU、IO、LOAD这些指标也可以互相分担。

定时扫表存在延迟问题

定时任务都是集中式的定时执行的,那么就会存在延迟的问题。随着数据库越来越大,延时会越来越长。

想要降低延迟,那就要抛弃定时任务的方案,可以考虑延迟消息,基于延迟消息来做定时执行。

用了延迟消息之后,还可以缓解数据库的压力。也能比定时扫表的性能要好,实时性也更高。

当然,引入另外一个中间件也需要考虑成本的。

你可能感兴趣的:(实习,java,数据库,jvm)