目录
前言
forupdate属于数据库的锁,此种锁的使用会加重数据库负担,也有某些数据库不支持锁。当前使用数据库是oracle,目前业务代码中,所有不允许并发的地方都是使用此种锁。对于这种一刀切的方式,需要按情况进行分析,应用合适方式加锁或去除锁
关于for update慢问题思考
对于真正要用悲观锁的地方,还是要用,是避免不了。我们目前只是换种上锁的方式。
从业务角度进行分析
是否出现此问题
- 锁的跨度太大,正确的加锁,应该是在使用的地方去进行。
是否会存在,加锁的时候,在等待某接口的响应结果的这种情况?
关于redis锁的参考
https://editor.csdn.net/md/?articleId=116399887
情况分析
场景
金额处理
用户或者统计的账号的金额的加减。
状态变更
账户的状态,某些记录的处理状态进行变更,不允许同时修改
业务层
业务层面要求不能进行同时操作。
分析
金额处理
进行账号金额的加减,目前来说,只能进行串行操作。需要考虑的一点是用for update显示加锁,还是其它锁代替。
- 对于金额的加减,为什么我们不利用数据库的行级锁,在数据库层面进行金额操作
- 涉及到不允许并发的操作,专用操作,专用方法。其它涉及到的操作,不允许更新此字段。
状态变更
状态的变更是随着业务的流转,进行变更的。要进行下一个状态的变更,条件是当前状态必须满足是期待值,才能进行状态变更。目前项目中存在一刀切的现象,只要修改状态,就加行级锁。
- 前后状态的变更有逻辑的关联,这种情况,我们可以去除行级锁。
如果要更新此条记录的状态,必须满足当前是期待的状态值。也就是说,进行状态更新时,带上状态的期待值(eg:退货,用户必须点已收货),如果更新失败,数据库返回的影响行数是-1
- 涉及到不允许并发的操作,专用操作,专用方法。其它涉及到的操作,不允许更新此字段。
结论
- 对于状态变更的情况,进行业务梳理,通过强制控制状态流转,可以去除锁
- 对于金额加减的特殊情况,可能还需要加锁
- 业务层的同步控制,涉及分布式锁
改造关注点
高效性
- 获取锁,释放锁的操作过程高效
并发性
- 能抗住一定的高并发,在高访问量情况下,性能发挥依然出色
- 锁的跨度,决定并发性。(只针对某些字段加锁,不涉及的字段可以自由进行更改)
高可用性
- 架构高可用
- 容错性
成本性
- 代码改动量小,达到之前的锁的功能
- 提供的锁功能易于应用
扩展性
方案
设计
版本号实现乐观锁
数据库增加一个版本字段,每次修改都需要带上一个版本号。cas原理,如果不满足版本号要求,数据库层更新时返回的行数为-1。
逻辑图
表结构
字段名称 | 类型 | 长度 | 备注 |
version | int |
| 版本号字段 |
问题和注意点
- 批量更新操作,不知道更新哪些记录。
针对需要批量更新时,需要根据符合条件的记录,更新单条操作。才能知道那条记录被更新
- 涉及到手动修改数据时,关联修改版本号。关联版本号的修改可能会导致并发时业务层因为版本号不一致,操作失败。
需要注意容错的处理
- 并发环境中,因为版本操作不一致,导致操作失败。需做好自旋处理,直到数据更新操作完成
实现
目前数据层技术框架,hibernate,mybatis,springjdbc
- 目前hibernate针对此乐观锁有做实现,但需要自己实现,更新时如遇到期待版本不一致,导致失败的自旋
- 手动实现版本号持续增长功能,需要自己实现,更新时期待版本不一致,导致失败的自旋
总结
优势:
- 一定的高可用性,高可用性依赖数据库的架构(目前数据库层面都有主从机制);
- 并发高,依赖数据库
劣势:
- 对业务代码一定的侵入性
- 成本高,对于不支持版本号乐观锁的框架,需自身进行版本号增长。做好并发更新,因版本不一致的问题。数据库需增加字段。
- 锁的跨度灵便调节度低
- 自旋操作会增加数据库操作
redis分布式锁
利用redis的串行操作,加上setnx的特性,实现分布式锁。
表结构
问题和注意点
- 目前我们的redis是出现过几次故障,redis出故障时,锁将失效
怎么去解决redis的高可用性。
- 有效时间没有操作执行完业务,锁失效
一般是不会这种情况,除非故障等特殊情况
- redis如重启,锁将失效
- 目前我们的环境复杂,有双活,生产等,如需用redis锁,需要是同一套redis集群。在环境故障切换时,才不会出现锁失效问题的。
实现
- Redisson开源工具
- watch dog自动延时功能
- 丰富的数据操作类型
- 获取锁的竞争有公平,非公平机制
- 锁的重入
- 实现redis推荐的redlock算法(redlock带有缺陷的高可用(不是很高的高可用),而且付出的是性能代价)
- 用luna脚本实现针对redis单实例分布式锁,自身实现获取不到锁的自旋
总结
优势:
- 锁跨度调节灵活
- 高可用性中等(依赖架构的高可用性)
- 代码倾入性中等
- 轻量级
- 拓展性高
劣势:
- 重启过程中,出现锁丢失
Zookeeper分布式锁
项目和zookeeper通信,根据key创建临时有序的的节点。获取到锁,当前创建的节点序列号最小
逻辑图
表结构
问题和注意点
- 因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。大家知道,ZK中创建和删除节点只能通过Leader服务器来执行,然后Leader服务器还需要将数据同不到所有的Follower机器上,这样频繁的网络通信,性能的短板是非常突出的
- linux系统一个文件夹最多创建3.2个万文件及文件
实现
- Curator工具
总结
优势:
- 高可用
- 锁跨度灵活
- 实现简单
劣势:
- 频繁创建和删除节点,性能开销大
- 高效性中等
综合对比
序列号 | 方案 | 高效性 | 并发性 | 高可用性 | 成本性 | 扩展性 | 改造代码量 | 备注 |
1 | 版本号实现乐观锁 | 中 | 中 | 高 | 高 | 低 | 高 | 需做好自旋的处理,每一个涉及到业务的方法 |
2 | redis分布式锁 | 高 | 高 | 中 | 低 | 高 | 中 |
|
3 | zookeeper分布式锁 | 低 | 高 | 高 | 中 | 高 | 中 |
|
进行for update锁的去除,一定要梳理完整的业务流程(eg:金额的加减处,有多少。对于不需要进行金额的操作更新处,按需进行更新,谨防金额字段误覆盖)
总结
- 通过金额加减,在数据库端操作(注意精度问题),对状态的流转控制手段,可以消减部分锁(目前对于大多数场景,不是掌握全面。所以只能是部分)
- redis和zookeeper的使用是性能和高可用的取舍。根据场景具体分析