一个高并发架构问题,求指点

2020-03-07 12:17:45 +08:00
 Jinnrry

目前我们在做一个审核后台,每天早上运营需要领取任务。领任务逻辑大致这样

// 1、先取数据
select * from data where status = xxx limit 50


// 2、写入任务表
insert into task .......



// 3、修改数据状态

update data set status = xxx where xxxx

但是!这样有个缺陷,如果多个人同时点领取,那么可能导致多个人领到同一条任务。目前想到的解决办法: 1、把这个操作写成一个事务,然后使用 serializable 隔离级别,保证每次只执行一个 2、把分任务的操作单独出来做出一个服务,使用单线程实现,保证每次只处理一个人的领取

但是这 2 种方法好像都有点影响性能,虽然我们后台没什么关系,但是本着对技术的追求,想来问问各位大佬,有没有什么更好的解决方案?

4910 次点击
所在节点    问与答
45 条回复
sjw199166
2020-03-07 12:19:46 +08:00
为什么不用队列呢
Jinnrry
2020-03-07 12:27:02 +08:00
@sjw199166 #1 thanks,队列的话,把所有数据存一份进队列?而且,就算用队列,又如何保证队列每条数据只消费一次呢
lhx2008
2020-03-07 12:28:35 +08:00
INNODB 第一行加个 for update 就可以了,然后再测试一下有没有其他问题
lhx2008
2020-03-07 12:29:08 +08:00
这样性能会比较差,不过你这个场景不会有太多并发
jadec0der
2020-03-07 12:30:13 +08:00
没看懂,最简单的做法不是乐观锁吗?

假设 status 0 是 未领取,1 是已领取
先 update data set status = 1 where xxxx and status = 0
返回值是 affected row,如果返回 1 说明抢到了,再 insert task,如果返回 0 说明没抢到,告诉员工重新取数据。
Jinnrry
2020-03-07 12:42:24 +08:00
@lhx2008 #4 感谢,确实没啥问题,但是想学习一下高并发的时候咋办
@jadec0der 直接更新数据表,那我咋知道领取到的 id 是哪些呢,因为返回的是只是一个修改了多少行啊
cabing
2020-03-07 12:43:46 +08:00
// 1、先取数据
select * from data where status = xxx limit 50


// 2、写入任务表
insert into task .......



// 3、修改数据状态

update data set status = xxx where xxxx


就像楼上说的 innodb 支持行锁

1 select * from data id for update[这个时候其他的读都是阻塞的]
2 update data set status = xxx where xxxx
3 如果成功 insert into task .......
cabing
2020-03-07 12:44:11 +08:00
是 select * from data id = xxx
Jinnrry
2020-03-07 12:46:56 +08:00
@cabing #7 加行锁确实没问题,但是因为遇到这个问题想就此学习下,如果是高并发的情况下咋办
cabing
2020-03-07 12:47:27 +08:00
目前这种方式是比较简单的。
1 你不用引入额外的业务逻辑,比如你说的任务发号器,如果是多机部署就会有问题吧。
2 不用引入 redis 之类的,这样你引入了外部依赖

明白业务的关键点,简化根本复杂性,避免为了解决问题引入偶发可用性。
Jinnrry
2020-03-07 12:50:28 +08:00
@cabing #10 引入 redis 之类的也没关系,发帖主要目的是学习有什么好的解决方案。单业务来说,我们后台就算领重复了也没关系,性能就算慢出翔问题也不大
jadec0der
2020-03-07 12:51:25 +08:00
@Jinnrry 一次更新多条是吗,那可以一条一条的 update,或者用一个事务重新 select 一遍状态再 update。

我理解你的 1 2 条之间是隔了用户手动操作的时间吧?这样直接在 1 上加 select for update 是没有用的。
Jinnrry
2020-03-07 12:54:05 +08:00
@jadec0der #12 不,没有手动操作,我的意思是,update 操作不能拿到数据 id 呀,没有 id 的情况下怎么插入任务表
cabing
2020-03-07 12:57:54 +08:00
@Jinnrry

行锁的话最简单,50 条全选也没啥。50*20ms 算也很快。

业务的难点是区分可领取任务多用户领取问题。重复领取的问题。只要标注出:task 状态,uid 和 task_id 关系就行。

如果大的量,都是分布式 cache。你也可以考虑用 redis 玩一下。
jadec0der
2020-03-07 12:59:11 +08:00
哦,我理解错了,我以为是用户先取 50 个任务显示在界面上,然后手动勾选一些领任务。后台收到 id 之后改状态创建任务,如果有任务被抢了就部分成功。

没有手动勾选的话就用一个事务包起来然后 select for update 就行,并不会慢很多。
Jinnrry
2020-03-07 13:02:41 +08:00
@cabing #14 redis 的话,我的理解是,维护一个待领任务队列吧,比如让这个队列随时保持 1 万条待领取的任务,然后每次领任务操作从这个队列取数据。但是如何向这个队列补充数据又成难点了
sagaxu
2020-03-07 13:03:36 +08:00
高并发?几万个运营同时领任务吗?
Jinnrry
2020-03-07 13:04:46 +08:00
@sagaxu #17 哈哈,只是假设高并发哈,借此问题向各位大佬学习下
opengps
2020-03-07 13:12:26 +08:00
同问并发点在哪?每个高并发业务其实都是有几个个特别需要着重处理的点,核心解决了,其他的也就顺便解决了
codingadog
2020-03-07 13:18:24 +08:00
后台起线程,一直往 redis 队列里塞任务,这里加个分布式锁,始终只有一个实例在干塞任务的活,任务加入队列同时更新数据库标记任务已入队列。
前台点击领取的时候直接从队列里取 50 个出来,更新对应的数据库行表明执行中,执行完成后更新数据库表示完成。
如果塞任务的线程挂了,redis 里有任务但数据库入列状态未被更新,基本不会产生影响。
如果数据库状态始终是已被领取,但长时间未产生变化的任务标记为失效,重新入列重新领取。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/650648

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX