并发编程中,通过多机分片执行异步任务的一种实现姿势,实用性非常强!
大家好,我是楼仔!往期精选汇总于此 👉🏻👉🏻👉🏻《原创精选荟萃》,便于大家查阅。
执行异步任务时,比如需要处理10W个订单,如果是PHP,我们一般会配置一个定时任务,然后该定时任务就会在单机上执行;如果是GO或者JAVA,我们也需要使用相应的策略,保证该任务只在单机上执行,比如分布式锁。可能有同学会问,我直接在多机上执行同一个任务不行么,我只想说,你胆子真大,当多机同时处理一条数据,你会死的很惨的。
那我们是否有一种方法,可以让任务在多机同时执行,然后又可以避免多机同时处理相同数据的问题呢?这里给大家介绍一种多机分片的方式,也是最近在公司Get到的新技能。
应用场景
最近在做异步任务迁移,要求对DB中的订单进行处理,因为订单的数量非常大,10W的数量级是常规状态,如果只通过一台机器去处理,执行效率非常低,所以需要通过多机并发处理。
对于上述方式,其实还有另外一种解决方案,就是单机执行任务,然后把任务放入消息队列,再新增一个接口,用于消费队列中的数据,然后进行数据处理,因为接口对应的服务是集群部署,所以执行速度很快,不过这里在设计方案时,需要考虑消息重复消费,多机可能同时处理单条消息,网络异常导致消息未得到处理等问题,具体解决方案,欢迎大家线下和我讨论哈。
多机分片
什么是多机分片呢?说的通俗一点,就是把数据分成N份,分别给每一台机器执行。比如我们有1000条数据,通过相应策略,将数据分成5份,每份数据200条,如果我们有5台机器,那么每台机器可以分别处理200条数据。
那么具体是怎么实现?
为了更好讲解,我先简单模拟一下场景:
-
DB包含20条数据,DB主键ID为0、1、2、3 ... 19;
-
有3台机器,每台机器起一个线程跑任务,共起3个线程;
-
需要将数据分成10份,每份数据有2条,然后分给这3个线程。
令牌获取
将数据分成10分,就有10个令牌,即number=10,分别为0、1、2 ... 9,处理逻辑如下:
-
每个任务有任务名,以任务名为key,通过increase=redis.incr(key)计数,然后将increase值通过number取模,得到令牌token=increase%number,第一次执行的increase=1,所以token=1%10=1;
-
构造令牌tokenKey=key+token=key1,然后通过redis对tokenKey加一个分布式锁,如果加锁成功,返回令牌值;
-
如果加锁失败,循环执行increase=redis.incr(key),此时increase=2,token=2%10=2,拿到令牌tokenKey=key2,再执行分布式锁,成功返回,未成功,同上依次反复。
Redis Incr 命令将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
分片获取DB数据
机器的线程拿到令牌后,就可以去分片获取数据了,假如DB的数据结构如下,且只有20条数据:
订单号orderId | 商品名productName | 配送状态status |
---|---|---|
0 | 数据线 | 0 |
1 | 键盘 | 0 |
2 | 显示器 | 0 |
... | ... | ... |
19 | 鼠标 | 0 |
下面看一下分片获取数据流程:
-
当线程拿到的令牌为token=0,可以通过select * from tableName wwhere orderId % 10 = token and status = 0 limit pageSize;(假如pageSize=100),因为取模匹配的数据的orderId=0和10,所以该线程可以拿到0和10这两条数据,如果pageSize=1,那就只能拿到0这条数据,数据10等下次处理时再获取;