使用 MySQL 实现无锁任务队列(using MySQL as a job queue)
来源:互联网 发布:爱淘宝领红包网址 编辑:程序博客网 时间:2024/06/05 21:12
场景
N 个生产者往 db 里面插入新任务,N 个消费者从 db 取出新任务执行并更新任务状态为“已执行”。
CREATE TABLE `t_job_queue` ( `id` INT NOT NULL AUTO_INCREMENT, `status` INT NOT NULL DEFAULT '0', `params` VARCHAR(1024) NOT NULL DEFAULT '', `result` VARCHAR(1024) NOT NULL DEFAULT '', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
关键问题
避免多个消费者取到同一个新任务。
有问题的解决方案
方案1 - 锁表
LOCK TABLES t_job_queue WRITE, t_job_queue as t1 READ;SELECT id, status, params FROM t_job_queue WHERE status=0 ORDER BY id ASC LIMIT 1;UPDATE t_job_queue SET status=STATUS_PROCESSING WHERE id=xxx;UNLOCK TABLES;-- execute taskUPDATE t_job_queue SET status=STATUS_FINISHED WHERE id=xxx;
这样做的问题在于锁住整张表,性能会比较低,而且多业务使用同一个库时可能因为交叉锁表造成死锁。
方案2 - SELECT … FOR UPDATE
-- disable autocommitSTART TRANSACTION;SELECT id, status, params FROM t_job_queue WHERE status=0 ORDER BY id ASC LIMIT 1 FOR UPDATE;UPDATE t_job_queue SET status=STATUS_PROCESSING WHERE id=xxx;COMMIT;-- execute taskUPDATE t_job_queue SET status=STATUS_FINISHED WHERE id=xxx;
使用 SELECT ... FOR UPDATE
可以发挥 innodb 行锁(row-level locking) 的特性,但终究还是会加锁,只是锁的粒度降低了。
无锁任务队列
-- enable autocommitSELECT id, status, params FROM t_job_queue WHERE status=0 ORDER BY id ASC LIMIT 1;UPDATE t_job_queue SET status=STATUS_PROCESSING WHERE id=xxx;-- if affected_rows == 1 then execute task else continue endUPDATE t_job_queue SET status=STATUS_FINISHED WHERE id=xxx;
但是这种方案有个问题,就是可能多个消费者会取到同一个任务去 update, 尽管不会造成一个任务对多个消费者执行,但是终究还是有瑕疵,因此进一步完善,改成先 UPDATE, 再 SELECT 的方式。
关键问题及解决方案
由于 UPDATE 只能返回 affected rows, 并不能返回被更新的记录内容,所以需要一种方式来取到被更新的任务。这里有 2 种思路:
- 使用 MySQL 特有的 User-Defined Variables
UPDATE t_job_queue SET status=STATUS_PROCESSING, id=(@id:=id) WHERE status=0 ORDER BY id ASC LIMIT 1;SELECT id, status, params FROM t_job_queue WHERE id=@id;
- 增加唯一标识码字段
CREATE TABLE `t_job_queue` ( `id` INT NOT NULL AUTO_INCREMENT, `status` INT NOT NULL DEFAULT '0', `params` VARCHAR(1024) NOT NULL DEFAULT '', `result` VARCHAR(1024) NOT NULL DEFAULT '', `update_id1` INT UNSIGNED NOT NULL DEFAULT '0', `update_id2` INT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
UPDATE t_job_queue SET status=STATUS_PROCESSING, id=(@id:=id) WHERE status=0 ORDER BY id ASC LIMIT 1;SELECT * FROM t_job_queue WHERE update_id1=xxx AND update_id2=xxx;
使用“自定义变量”的好处是无需增加额外的字段,但缺点是自定义变量只在一个 session 内生效,也就是说,如果 UPDATE 和 SELECT 之间连接断开的话,自定义变量会失效:
User-defined variables are session specific. A user variable defined by one client cannot be seen or used by other clients.
所以,推荐的做法是:
- 优先使用“自定义变量”
- 当 UPDATE 和 SELECT 之间连接断开时重连时使用唯一标识码
笔者设置了两个唯一标识码字段,update_id1 填入时间戳,update_id2 填入随机生成的 seq.
Note: 使用该方法必须设置 autocommit 为 1
References
5 subtle ways you’re using MySQL as a queue, and why it’ll bite you
A job queue in MySQL
- 使用 MySQL 实现无锁任务队列(using MySQL as a job queue)
- 使用 MySQL 实现无锁任务队列(using MySQL as a job queue)
- Using MySQL as a NoSQL
- Using mysql as Nosql ------------- Mysql HandlerSockets使用
- MYSQL查询、创建任务job
- Mysql:using,having,as关键字
- boost无锁队列queue
- Implement Queue using Stacks 用俩栈实现队列
- 多线程无锁(lock-free)队列(queue)的实现探讨
- PVFS2 源代码分析之输入输出src/io/job/job-desc-queue任务描述符队列
- 队列-Implement Queue using Stacks(用栈实现队列)
- Mysql Event SCHEDULE Job 定时任务
- mysql如何设置job任务自动启动
- LeetCode OJ 之 Implement Queue using Stacks(使用栈实现队列)
- mysql消息队列/定时任务实现思路(一)
- 无锁队列 lock free queue
- 用栈实现队列,用队列实现栈 Queue Using Stacks and Stack Using Queues
- Implement Queue using Stacks(用栈实现队列)
- Ubuntu下,用键盘定义鼠标按键
- 导航菜单
- 在react-native中使用redux
- TCP流量控制
- Annotation
- 使用 MySQL 实现无锁任务队列(using MySQL as a job queue)
- React Native常用IDE推荐与安装配置
- android中Invalidate和postInvalidate的更新view区别
- .NET学习从入门到精通100+源代码(申明:来源于网络)
- 开始
- 1622-5 孔富晨 总结《2016年12月7日》 【连续第68天总结】
- React Native应用设备运行及调试
- C++动态转换类型static_cast,dynamic_cast,reinterpret_cast和const_cast探究
- Shell脚本攻略05-数组和关联数组