BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / java / #50525同步于 2016/5/23
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖

请问如何防止多个消费者线程读取数据库中同一条记录

oncedream520
2016/5/23镜像同步22 回复
目前,数据库中每条记录都有一个表示该条记录处理状态的字段(Waiting,Running、fail、success),我现在是单线程用hibernate每次从数据库中取出创建时间最早的状态为Waiting的记录,将状态设置为Running,处理结束后(大概3min左右)将状态设置为fail或者success。 由于单线程处理效率低下,所以现在考虑使用多线程,但是存在多条线程同时读出一条状态为Waiting的记录同时处理的问题,请问如何解决这个问题? 现在的想法是使用信号量,线程每次从数据库读取最老的Waiting记录的时候,先acquire(),修改成running后先commit(),再release(),使得读取数据库这一步从并行变成串行,不知道是否可行? 设置数据库隔离级别能不能防止这种情况?
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
nuanyangyang机器人#1 · 2016/5/23
还是不要用关系数据库来当队列吧。这里有介绍 http://programmers.stackexchange.com/questions/231410/why-database-as-queue-so-bad 我理解,关系数据库的表结构天生就不适合做为队列,它的ACID风格的transaction很昂贵。 文中提到了几种消息队列。但我都没用过,所以不好推荐。但我的直觉是,用一种专门用于做队列的技术来做这件事。 另外,如果是多线程而不是多进程,那么用java.util.concurrent.BlockingQueue就可以,这个数据结构是线程安全的。 https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html 至于数据库表对并发(但各个事物之间没有重叠的行)修改的支持怎么样,我对数据库不了解,但我直觉觉得效率不会很高,起码要进行一次进程间通信。多个线程一起连接数据库恐怕会造成负担。如果通信是瓶颈,我觉得让一个线程专门来与数据库通信也可以,别的线程用消息队列告诉它想要改的行以及值,而这个线程循环地从队列里取值(每次全都取出来,用一个SQL请求批量更新)。 还有java.util.concurrent.ForkJoinPool https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ForkJoinPool.html 感觉这就是你想要的工具。
hero210700机器人#2 · 2016/5/23
加个字段例如acquired 每次获取waiting状态时, 用update XXX set acquired=1 where record_id=#{record_id} and status=waiting and acquired=0,如果返回更新条数为1那么表示获取到了这个记录,处理即可,最后再update成acquired=0,否则表示被其他线程抢先了,进行下一条记录的处理。 原理类似乐观锁。
kuangfengwin机器人#3 · 2016/5/23
redis.....天然的队列。。。
aiquestion机器人#4 · 2016/5/23
这样其实从数据库中读这个动作还是单线程的。相当于去update这个函数加个全局锁。因为如果同时有多个线程去执行这个,只有一个会成功。 以前碰到过类似的问题,最后每次从数据库中取50条,然后random一条去update,如果成功了就去处理,失败了就继续random一下。当时测试的结果比之前好很多。=。=不过感觉挺奇葩的。 【 在 hero210700 的大作中提到: 】 : 加个字段例如acquired 每次获取waiting状态时, 用update XXX set acquired=1 where status=waiting and acquired=0,如果返回更新条数为1那么表示获取到了这个记录,处理即可,最后再update成acquired=0,否则表示被其他线程抢先了,进行下一条记录的处理。 : 原理类似乐观锁。
nuanyangyang机器人#5 · 2016/5/23
【 在 kuangfengwin 的大作中提到: 】 : redis.....天然的队列。。。 一直听人说redis,今天第一次玩了玩,感觉像简化了的mongodb,挺好玩的。 就是担心: 作为一个单独的进程,比起一个简单的LinkedBlockingQueue对象来说是不是还是太重了? 还是担心内存管理。了解了GC以后难免担心内存碎片问题。 p.s. 作为考据党,还是忍不住去看了看redis的源代码。 redis是用C语言写的。可以想象,作为一个C程序员,要写一个in-mem的数据结构,很理所当然地会想到用malloc。确实,redis里面的数据单元是用malloc分配的(如: https://github.com/antirez/redis/blob/unstable/src/dict.c ),而redis可以选用libc自带的malloc,或者改用tcmalloc或者jemalloc(两者都以多线程而且效率高著称)。但是,毕竟是C程序,在没有移动式垃圾回收(moving garbage collector)的前提下,必然会造成内存碎片。 然后用google搜索redis fragmentation,印证了我的担忧。redis本身提供运行时监控参数可以监视碎片率。不少文章也介绍redis的内存碎片问题。很多人也在stack overflow, google group等社区讨论redis的内存碎片问题。然后,我看到了这个教程: https://www.datadoghq.com/wp-content/uploads/2013/09/Understanding-the-Top-5-Redis-Performance-Metrics.pdf 然后看到了这一段,内心一下子几千只草泥马奔腾而过: 重新启动redis。。。。。。。 好吧,总结一下,长期运行、动态内存分配,且没有移动式的垃圾回收,必然造成内存碎片。如果担心这个的话,就不要用redis了。JVM的垃圾回收是很不错的。 顺便吐槽一下redis。明明和外界的通信方式是网络通信,数据都是通过服务器访问的,数据在内存中分配的方式明明是不为外界所知的。为什么不把内存管理封装一下呢?就算redis内部做垃圾回收,把整个哈希表里的键、值都拷贝到另外一个块里,然后把根指针指向新的块,然后回收原来的块,这样还是可以保持100%时间在线的。只是因为不想自己写一个semi-space垃圾回收器吗?我想都不需要semi-space那么复杂,redis的数据类型非常有限,内存管理应该非常容易,就像Lua语言一样,对象扫描只需要考虑哈希表(因为Lua里除了哈希表以外没有任何“对象”),整个GC用900行C代码就搞定了。
nuanyangyang机器人#6 · 2016/5/23
顺便再提一下redis是单线程poll式的服务器,所以嘛……如果瓶颈是网络,自然是极佳方案;但如果本来就是为了协调多个线程,那么redis显然不是好的选择。
lzrak47机器人#7 · 2016/5/24
如果你这个场景需要分布式锁,也就是说你是不同机器上的java应用去起线程消费,那肯定需要中间件了,用redis的数据结构做一个分布式锁是可以的。 如果不需要分布式锁,单台机器上的单个java应用去消费,直接cas(status,waiting,running)就行了,失败的话自旋或阻塞。如果场景不是这么简单,上java里的BlockingQueue/ConcurrentLinkedQueue也可以,看你在争锁失败的时候是需要阻塞还是自旋。 ls有同学提到用MySQL的乐观锁机制也是可以的,但那个方案适合读多写少(写线程争锁失败率很低)的场景,你这个场景貌似写线程争锁失败率不低。如果你非要用,需要仔细考虑好争锁失败后的处理,是自旋、阻塞、超时还是直接返回失败并丢弃。 还有同学提到forkjoinpool,forkjoinpool适合子任务拆分后合并汇总的并发场景,也就是并发递归的场景,跟lz的场景没有一丝一缕的联系。 另外,java里的信号量好像不太适合你这个场景啊?你这个场景只有一个线程可以持锁,而信号量是可以多个线程持锁的。
lzrak47机器人#8 · 2016/5/24
不需要搞这么复杂,跟gc、内存碎片也没关系。 只需要说明redis里的队列和java里的并发队列应用场景完全不一样就行了。 【 在 nuanyangyang 的大作中提到: 】 : : 一直听人说redis,今天第一次玩了玩,感觉像简化了的mongodb,挺好玩的。 : 就是担心: : ...................
kuangfengwin机器人#9 · 2016/5/24
(⊙o⊙)哇。。。 借楼学习。。 【 在 nuanyangyang (暖羊羊) 的大作中提到: 】 : 一直听人说redis,今天第一次玩了玩,感觉像简化了的mongodb,挺好玩的。 : 就是担心: : 作为一个单独的进程,比起一个简单的LinkedBlockingQueue对象来说是不是还是太重了? : ................... 通过『我邮2.0』发布