这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」
他山之石
那么,我们不妨来看看为了保证线程安全,常见的解法有哪些:
-
悲观锁 - 这个没什么好说的,上锁呗,拿不到锁?要么等着,要么走
-
乐观锁 - 这里就有2个解决方法(其实MVCC我觉得更类似于CAS的一种实现):
-
CAS:通过比较当前值和我预期的更新前的值,来确定我是否能进行变更,常见的就是JDK的JUC中的AQS以及依赖AQS实现的容器
- 版本号:CAS比较麻烦的一个地方,就是ABA问题;其实现在数据存储便宜了,我们就可以加个类似于更改次数的东西,修改之前先查一下改了多少次,最后准备改的时候再一比,如果这俩不一样那就说明不能改了,否则修改,并且让版本号+1。其实也是CAS的一种实现,只是通过版本号的控制,规避了ABA的问题。
-
可以攻玉
既然这俩面对的困境类似,那么看看如何照抄了。
悲观锁
我们知道这老哥最可靠了:就一个可以拿锁。
这样至少可以保证,多请求条件下,我们同一时间只有一个请求可以被执行。
但这样也并不能完全解决问题:如果执行完了,我们却再来执行一次,这时候加锁就没用了,难道上一辈子的锁吗?这里我们就得加上使用版本号类似的机制了。
乐观锁
这里我们要设置个状态链路,总之是一路往前不能后退的,例如上面提到的版本号,也可以是不能回退的状态链路。
方法
那么我们就可以有个思路了:
-
带个状态申请悲观锁,状态在后面了?那就回去吧,没你啥事了
-
悲观锁被锁了?那也不执行你了,你回去或者等着都行
-
拿到锁了:再检查一下状态,如果状态还是被改了:这代表前面有人给你搞定了,那就释放锁回去了
- 这里就是线程安全里DCL的做法
-
状态和我带的一样:那我就干活了呗
-
活干完了:改状态,改完就释放锁了
落地
经过上面一同分析,思路有了,那么我们怎么做呢?
悲观锁
首先是上锁:
-
这个大家轻车熟路了,无非是:
-
土一点,我就一台机器,那就多线程那套,上锁!
-
我多机,县官还能现管?那就公共数据源来查:
- 能用同一个库,我也没redis啥的,那就数据库加个记录上锁呗,记得加个过期时间咯
- 不能用一个库/我有redis:那好说,上分布式锁呗
-
这里就得保证锁的是一个玩意了,我们可以用唯一键(可以是某个字段,也可以是组合的一些字段,也可以是token之类的东西)来保证。
乐观锁
根据上面的,直接照搬最简单的就是给具体的记录加个不可回退的标记了。
加了标记之后我们就可以通过版本控制和DCL,来帮助上锁过程了。
但这里我们就会发现额外的问题:
- 有记录的话,当然可以控制整个流程了
- 但是,如果没有记录,这问题就大了,第二个请求进来了并不会认为自己在这个scope里,我给你再来条记录,这咋整?
因此,我们的请求就得带点其他的东西来保证没有数据的时候(也就是说插入的时候),不会发生这种问题,这里就有几种办法:
分布式id
-
我们对表做一下改造:新增一列叫分布式的id,这个就得是全局唯一的了
-
请求方:
- 在请求之前,去获取一下这个分布式id
- 请求方请求的时候,带上这个id,以及我预期此时的状态(其实这里一般在接收方接口中就直接处理掉了,因为如果要满足幂等性,外部的状态其实不能算准,我们要按照预期在这里是什么状态,来反推接收方请求的时候,预期是什么状态)
-
处理方:
- 接收到请求了,先数据库捞一把:这个分布式id有没有,状态对不对的上
- 分布式id,或者状态也对不上,那就回去吧
- 都对上了,那就请进吧,数据库里的记录把这个分布式id再插上去,后面的拿着这个分布式id就进不去了
这里就得保证这个分布式id是唯一的了,一般来说会有一个第三者(当然也可以是处理方给这个id)来担当这个造id的角色:根据一些唯一键,来生成对应的id。
其实这里就可能会涉及到额外问题了,我们假设以下场景:
- 用户下了订单A,此刻订单还未创建,我们来走流程
如果这部分我们用了消息队列,那么就会有这么一个问题:
订单要唯一吧?那你怎么生成唯一id?
- 范围大一点,用户信息加商品信息生成一个:那用户一个商品不得一辈子只能下一个单啊,做不做生意了?
- 上面的加时间生成一个:那不得单机啊,多机同时去申请分布式id,时间难免不一样,不控制的话,那不得多个订单?买个东西多个订单,该滚蛋了吧
一种比较合适的方法是:
上分布式锁,让同一个请求,只能获取一个分布式id,后面就采用第二个方案,方法就如下:
- 下单如果是分布式处理的情况,那么大概率是从MQ里拿请求了,MQ生产者此时就得去生成一个唯一id,这个就不能重复了吧?
- 然后再第二个方案。
不难发现:还是唯一id,这里也能看出来:
- 哦,幂等性原来也可以分层的啊(如果我们采取了上面的方案,那么就有2个地方用到唯一id了,那么其实是不是只需要有一个地方申请就可以了?),最后总归是要到单机上的,毕竟一个发往确定服务器的确定的请求,终归会在某个步骤只有一台机器在执行嘛。
token机制
其实这里的token和上面的分布式id有异曲同工之妙,只是token作用的地方就属于悲观锁那部分的了。