今天电台问了个问题,说的是批量获取某些数据的时候有并发,那么怎么保证大家获取的数据不冲突.
模拟场景:
我们鸡蛋饼摊位里面有100个饼,现在有好几个人都来买东西,他们之中A要3份,B要5份,C要2份,我们当然希望按顺序一个个发,避免冲突,但是又希望大家等待的时间少,应该怎么做呢?
比如说1号到100号饼,我们最理想的方案就是 A要3份 那我就给他 1 2 3号饼,B要5份,那我就给他 4 5 6 7 8号饼,C要2份,那我就给他 9 10 号饼,但是这是理想的方案,想让程序知道你要这么做,就要站在更高的角度提前分配资源,并设定好这些东西给谁.
A | 1号饼 |
A | 2号饼 |
A | 3号饼 |
B | 4号饼 |
B | 5号饼 |
B | 6号饼 |
B | 7号饼 |
B | 8号饼 |
C | 9号饼 |
C | 10号饼 |
这是我们理想的情况下直接给你计算好了要怎么样分配,到时候直接A 查出自己要1号 2号 3号,B查出自己要的4号 5号 6号 7号 8号就行了C 查出自己要的9号 10号.
问题来了 怎么样才能提前算好?
那我至少得知道A 要几个 B要几个 C要几个,然后计算顺序分配,怎么知道呢?我们让这个请求分为两部分 第一部分 告诉我你要几个 第二部分我给你这几个数据的id
方案1: redis顺序排队加锁
进来的时候告诉我你要几个,对redis的某个变量加锁 然后更新现有变量值,将我排队进去,指出我是A要3个,给我最前面的3个数据,查出来了1 2 3 号饼给我,B来了,发现A还在锁里面,那他就等着.直到A结束,B拿到锁告诉你我要5个,然后给你5个,C也一样等B结束才能拿到锁.那这样的话是不是并发高了,大家就都在等锁,10个人排队买饼的故事出现了,某个人买的时候磨磨唧唧还在绑定微信支付银行卡,那完了,大家在后面骂娘了,真下头,哼!
这样我们能不能做个改进,A既然都知道我拿1 2 3 号饼,B来的时候我不给你1 2 3号饼不就行了,我给你 4 5 6 7 8号饼不就完了? 于是出现了我们进一步的方案2
方案2: redis并发加锁
A进来的时候查一下能否获取一个当前最大id的锁,如果要到了,查询id>0的3个饼,锁定1 2 3号饼 放进A的redis key,然后释放最大id的锁,同步更新最新的最大id是3号,那么B来的时候,就直接按照id>3查出5个给B进行锁定,放4 5 6 7 8进行锁定到B的一个redis key,最大id变为8,同理C获取id>8的9号 10号进行锁定.然后各自去更新自己的数据库
可是你有没有发现一个问题,他们其实还是有一个小小的排队,排的是什么呢?就是这个最大id,这个最大id是不能同步修改的,只能有一个人在修改,就像卖饼的时候有个人看到你付钱成功了才给你拿饼.所以这里还是有一些等待的虽然这样的冲突和等待会比方案1更小,但不意味着它不存在.
方案3: redis list出栈
我们知道redis pop出数据是需要自己内部排队的,不会造成并发抢占到同一条资源,那么我们直接把100个饼的编号一下放进redis的list,A来了,给他 pop 3次,B来了给他 pop 5次,C来了给他pop 2次,自然而然,大家都拿到了自己对应的id,然后自己去更新数据库就行了,不过这里有个问题,由于是同时并发,所以可能会出现 A拿到的饼不是1 2 3号 而是 1 3 6号,B拿到的饼是 2 4 7 8 10 号,C拿到的饼是 5 9 号,虽然号码上不连续了,但是个数是对的,倒也没啥问题,大家几乎同时拿到自己想要的id,自己去做自己的更新吧,互不影响了.
方案4: mysql前置更新
郭大师提出了一个Mysql前置更新方案,就是先用mysql 去强制更新数据,更新成功,获得抢占的id,再执行后面的逻辑,把更新数据库步骤提前,其核心SQL为
update table_name set used = 1 , uid = 'A' where used = 0 order by id asc limit 20
就是说提前抢20个给你,进行更新前置,这样把问题交给mysql做,其实也是可行的.但是我没测试过这样做mysql那边会有会有压力,应该来说mysql也能处理好这种事情,这个方案和我之前写的这个有点类似
https://www.cnblogs.com/lizhaoyao/p/9199540.html
方案5: 其实还有个简单但是不安全的办法,但是这样id会不连续
A来的时候一次性取出100条数据的id,打乱顺序,随机抽3个给A
B来的时候一次性取出100条数据的id,打乱顺序,随机抽5个给B
C来的时候一次性取出100条数据的id,打乱顺序,随机抽2个给B
这样能简单的帮你避免一部分redis锁的抢占和等待,当你抢到这个id后要去数据库查一下看看是不是都是没问题的,如果是 再update 更新,如果有问题,那你就再来一次即可.
这些方案中,有容易理解的方法1,依赖Redis比较严重的方法2 3,有依赖数据库严重的方法4,有并发量小的时候效率最高的方法5但是具体选什么,还是要看你们的业务场景是不是够复杂,并发是不是高,锁的时间是不是长,mysql配置是不是好,自己去做一个选型衡量吧!