elasticjob分片

623 阅读5分钟

高性能

分片的作用,本质是为了提高性能。

具体怎么提高?就是单机和分布式的区别。

非分布式定时任务,就是单机。处理的qps有限。

分布式定时任务,就是为了解决集群问题,可以集群部署,然后就可以集群处理。

说白了,就是多个节点可以同时处理同一个任务。

代码

下面的代码是官方的demo。

配置

配置如下,每个配置项的具体作用看注释

jobs:
    simpleJob:
      elasticJobClass: org.apache.shardingsphere.elasticjob.lite.example.job.SpringBootSimpleJob //定时任务
      cron: 0/5 * * * * ? //每隔5s执行一次
      shardingTotalCount: 3 //分片数量是3,表示同一个任务同一时间会执行3次
      shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou //分片参数

更详细的说明,

一. 分片数量

分片数量是3,表示同一个任务同一时间会执行3次。

如果有3个节点,那么每个节点执行一次,而且是在同一时间执行。

如果只有1个节点,那么这个节点会在同一时间执行3次,说白了,就是每次触发定时任务的时候,这个任务执行3次,而且是同时执行,即并发执行。

一般情况下,当然是多个节点并发执行,并发的作用就是高性能。而具体的实现,是基于分布式定时任务。都用了分布式定时任务了,肯定要集群部署,否则就没有必要使用分布式定时任务了,搞这么麻烦,还要引入中间件——分布式定时任务elaticjob和注册中心zookeeper。

那分片数量3具体怎么分布呢?

  1. 如果有3个节点

那每个节点得到一个分片,具体是节点1(分片值是0),节点2(分片值是1),节点3(分片值是2)。

这种情况,同一个任务,每次都会在3个节点同时触发执行。

  1. 如果有2个节点

那节点1得到2个分片(分片值是0,1),节点2得到1个分片(分片值是2)。

这种情况,同一个任务,同一时间,在节点1同时执行2次,在节点2执行1次。

  1. 如果有1个节点

那节点1得到3个分片(分片值是0,1,2)。

这种情况,同一个任务,每次触发的时候都会在节点1同时执行3次。 image.png

所以,无论节点数量是几个,同一个任务,同一时间,执行的次数是固定的,都是3次。

其实说白了就是,非分布式定时任务,即单机,同一个任务,同一时间,只执行1次。

而,分布式定时任务,有多个节点,每个节点都会同时执行同一个任务,这样就提高了并发处理数据的能力。

二. 分片参数

主要是给代码里用的,假设现在有3个节点,那每个节点得到一个分片,具体是节点1(分片值是0),节点2(分片值是1),节点3(分片值是2)。

节点1执行任务的时候,获取到的分片参数对应的是0=Beijing,具体来说其实是Beijing。拿到这个Beijing,有什么用呢?用处就是节点1只处理Beijing数据。

同理,节点2只处理Shanghai数据。节点3只处理Guangzhou数据。

所以,本质其实是让每个节点只处理部分数据。而且不同节点处理的数据,不能重复。

所以,分片数量的作用是,让每个节点得到一个分片。那具体怎么实现呢?在代码层面就要基于这个分片参数的值去实现——更详细的细节,下面再讲。

使用

定时任务类

@Component
public class SpringBootSimpleJob implements SimpleJob {
    
    private final Logger logger = LoggerFactory.getLogger(SpringBootSimpleJob.class);
    
    @Autowired
    private FooRepository fooRepository;
    
    @Override
    public void execute(final ShardingContext shardingContext) {
        logger.info("Item: {} | Time: {} | Thread: {} | {}",
                shardingContext.getShardingItem(), new SimpleDateFormat("HH:mm:ss").format(new Date()), Thread.currentThread().getId(), "SIMPLE");


        List<Foo> data = fooRepository.findTodoData(shardingContext.getShardingParameter(), 10); //shardingContext.getShardingParameter(),可以获取到分片参数的值。比如,节点1获取的值是Beijing。


        for (Foo each : data) {
            fooRepository.setCompleted(each.getId());
        }
    }
}

org.apache.shardingsphere.elasticjob.lite.example.repository.FooRepository#findTodoData

public List<Foo> findTodoData(final String location, final int limit) {
        List<Foo> result = new ArrayList<>(limit);
        int count = 0;
        for (Map.Entry<Long, Foo> each : data.entrySet()) {
            Foo foo = each.getValue();
            //比较当前数据和分片参数
            if (foo.getLocation().equals(location) && foo.getStatus() == Foo.Status.TODO) { //如果当前数据和当前节点的分片参数值一样,才处理这个数据;否则,这个数据由其他节点处理
                result.add(foo);
                count++;
                if (count == limit) {
                    break;
                }
            }
        }
        return result;
    }

总结

所以,使用步骤其实就是

一. 配置

分片数量

分片参数

二. 定时任务

1.从数据库查数据

2.循环处理每个数据
1)比较当前数据和分片参数的值
2)如果等于,就由当前节点处理;如果不等于,就由其他节点处理。

分片参数

具体怎么比较当前数据和分片参数呢?

可以基于不同维度。

官方demo是基于地区维度。

还有一种,更常用,就是基于取模。比如,用当前数据的订单号字段对分片数量进行取模。

具体的步骤,和上面讲的差不多。基本上完全一样。

唯一的不同,就是比较数据的时候,有一点点区别。

基于取模(其实就是求余数)的方法,举个简单的例子。现在假设有2个节点,分片数量是2,从数据库查询出来2个数据,订单号分别是1和2:

  1. 节点1(分片参数是:0=0)

数据1:模值1 = 订单号1 % 分片数量2。

数据2:模值0 = 订单号2 % 分片数量2。

2条数据的模值已经计算出来了,那节点1具体是处理哪个数据呢?节点1只处理数据2,即只处理模值为0的数据——代码层面其实就是比较一下模值和分片参数的值。如果不为0,就交给节点2处理。

  1. 节点2(分片参数是:0=1)

数据1:模值1 = 订单号1 % 分片数量2。

数据2:模值0 = 订单号2 % 分片数量2。

2条数据的模值已经计算出来了,那节点2具体是处理哪个数据呢?节点2只处理数据1,即只处理模值为1的数据。如果不为1,就交给节点1处理。

参考

juejin.cn/post/711481…