高性能
分片的作用,本质是为了提高性能。
具体怎么提高?就是单机和分布式的区别。
非分布式定时任务,就是单机。处理的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具体怎么分布呢?
- 如果有3个节点
那每个节点得到一个分片,具体是节点1(分片值是0),节点2(分片值是1),节点3(分片值是2)。
这种情况,同一个任务,每次都会在3个节点同时触发执行。
- 如果有2个节点
那节点1得到2个分片(分片值是0,1),节点2得到1个分片(分片值是2)。
这种情况,同一个任务,同一时间,在节点1同时执行2次,在节点2执行1次。
- 如果有1个节点
那节点1得到3个分片(分片值是0,1,2)。
这种情况,同一个任务,每次触发的时候都会在节点1同时执行3次。
所以,无论节点数量是几个,同一个任务,同一时间,执行的次数是固定的,都是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(分片参数是:0=0)
数据1:模值1 = 订单号1 % 分片数量2。
数据2:模值0 = 订单号2 % 分片数量2。
2条数据的模值已经计算出来了,那节点1具体是处理哪个数据呢?节点1只处理数据2,即只处理模值为0的数据——代码层面其实就是比较一下模值和分片参数的值。如果不为0,就交给节点2处理。
- 节点2(分片参数是:0=1)
数据1:模值1 = 订单号1 % 分片数量2。
数据2:模值0 = 订单号2 % 分片数量2。
2条数据的模值已经计算出来了,那节点2具体是处理哪个数据呢?节点2只处理数据1,即只处理模值为1的数据。如果不为1,就交给节点1处理。