forupdate的数据库悲观锁改造

本文探讨了数据库forupdate锁的效率问题和优化思路,分析了金额处理和状态变更的业务场景。提出了版本号乐观锁、Redis分布式锁和Zookeeper分布式锁三种解决方案,并对它们的优缺点进行了比较。强调在优化时需考虑高效性、并发性、高可用性和成本性。最终建议根据业务需求选择合适的锁策略。
摘要由CSDN通过智能技术生成

目录

 

前言

关于for update慢问题思考

关于redis锁的参考

情况分析

场景

金额处理

状态变更

业务层

分析

金额处理

状态变更

结论

改造关注点

高效性

并发性

高可用性

成本性

扩展性

方案

设计

版本号实现乐观锁

redis分布式锁

Zookeeper分布式锁

综合对比


前言

forupdate属于数据库的锁,此种锁的使用会加重数据库负担,也有某些数据库不支持锁。当前使用数据库是oracle,目前业务代码中,所有不允许并发的地方都是使用此种锁。对于这种一刀切的方式,需要按情况进行分析,应用合适方式加锁或去除锁

关于for update慢问题思考

对于真正要用悲观锁的地方,还是要用,是避免不了。我们目前只是换种上锁的方式。

从业务角度进行分析

是否出现此问题

  1. 锁的跨度太大,正确的加锁,应该是在使用的地方去进行。

是否会存在,加锁的时候,在等待某接口的响应结果的这种情况?

 

关于redis锁的参考

https://editor.csdn.net/md/?articleId=116399887

情况分析

场景

金额处理

用户或者统计的账号的金额的加减。

状态变更

账户的状态,某些记录的处理状态进行变更,不允许同时修改

业务层

业务层面要求不能进行同时操作。

分析

金额处理

进行账号金额的加减,目前来说,只能进行串行操作。需要考虑的一点是用for update显示加锁,还是其它锁代替。

  1. 对于金额的加减,为什么我们不利用数据库的行级锁,在数据库层面进行金额操作
  2. 涉及到不允许并发的操作,专用操作,专用方法。其它涉及到的操作,不允许更新此字段。

状态变更

状态的变更是随着业务的流转,进行变更的。要进行下一个状态的变更,条件是当前状态必须满足是期待值,才能进行状态变更。目前项目中存在一刀切的现象,只要修改状态,就加行级锁。

  1. 前后状态的变更有逻辑的关联,这种情况,我们可以去除行级锁。

如果要更新此条记录的状态,必须满足当前是期待的状态值。也就是说,进行状态更新时,带上状态的期待值(eg:退货,用户必须点已收货),如果更新失败,数据库返回的影响行数是-1

  1. 涉及到不允许并发的操作,专用操作,专用方法。其它涉及到的操作,不允许更新此字段。

结论

  1. 对于状态变更的情况,进行业务梳理,通过强制控制状态流转,可以去除锁
  2. 对于金额加减的特殊情况,可能还需要加锁
  3. 业务层的同步控制,涉及分布式锁

 

改造关注点

高效性

  1. 获取锁,释放锁的操作过程高效

并发性

  1. 能抗住一定的高并发,在高访问量情况下,性能发挥依然出色
  2. 锁的跨度,决定并发性。(只针对某些字段加锁,不涉及的字段可以自由进行更改)

高可用性

  1. 架构高可用
  2. 容错性

成本性

  1. 代码改动量小,达到之前的锁的功能
  2. 提供的锁功能易于应用

扩展性

 

 

方案

设计

版本号实现乐观锁

数据库增加一个版本字段,每次修改都需要带上一个版本号。cas原理,如果不满足版本号要求,数据库层更新时返回的行数为-1。

逻辑图

 

表结构

字段名称

类型

长度

备注

version

int

 

版本号字段

 

问题和注意点

  1. 批量更新操作,不知道更新哪些记录。

针对需要批量更新时,需要根据符合条件的记录,更新单条操作。才能知道那条记录被更新

  1. 涉及到手动修改数据时,关联修改版本号。关联版本号的修改可能会导致并发时业务层因为版本号不一致,操作失败。

需要注意容错的处理

  1. 并发环境中,因为版本操作不一致,导致操作失败。需做好自旋处理,直到数据更新操作完成

实现

目前数据层技术框架,hibernate,mybatis,springjdbc

  1. 目前hibernate针对此乐观锁有做实现,但需要自己实现,更新时如遇到期待版本不一致,导致失败的自旋
  2. 手动实现版本号持续增长功能,需要自己实现,更新时期待版本不一致,导致失败的自旋

总结

优势:

  1. 一定的高可用性,高可用性依赖数据库的架构(目前数据库层面都有主从机制);
  2. 并发高,依赖数据库

劣势:

  1. 对业务代码一定的侵入性
  2. 成本高,对于不支持版本号乐观锁的框架,需自身进行版本号增长。做好并发更新,因版本不一致的问题。数据库需增加字段。
  3. 锁的跨度灵便调节度低
  4. 自旋操作会增加数据库操作

 

redis分布式锁

利用redis的串行操作,加上setnx的特性,实现分布式锁。

表结构

问题和注意点

  1. 目前我们的redis是出现过几次故障,redis出故障时,锁将失效

怎么去解决redis的高可用性。

  1. 有效时间没有操作执行完业务,锁失效

一般是不会这种情况,除非故障等特殊情况

  1. redis如重启,锁将失效
  2. 目前我们的环境复杂,有双活,生产等,如需用redis锁,需要是同一套redis集群。在环境故障切换时,才不会出现锁失效问题的。

 

实现

  1. Redisson开源工具
    1. watch dog自动延时功能
    2. 丰富的数据操作类型
    3. 获取锁的竞争有公平,非公平机制
    4. 锁的重入
    5. 实现redis推荐的redlock算法(redlock带有缺陷的高可用(不是很高的高可用),而且付出的是性能代价)
  2. 用luna脚本实现针对redis单实例分布式锁,自身实现获取不到锁的自旋

总结

优势:

  1. 锁跨度调节灵活
  2. 高可用性中等(依赖架构的高可用性)
  3. 代码倾入性中等
  4. 轻量级
  5. 拓展性高

劣势:

  1. 重启过程中,出现锁丢失

 

Zookeeper分布式锁

项目和zookeeper通信,根据key创建临时有序的的节点。获取到锁,当前创建的节点序列号最小

逻辑图

表结构

问题和注意点

  1. 因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。大家知道,ZK中创建和删除节点只能通过Leader服务器来执行,然后Leader服务器还需要将数据同不到所有的Follower机器上,这样频繁的网络通信,性能的短板是非常突出的
  2. linux系统一个文件夹最多创建3.2个万文件及文件

实现

  1. Curator工具

总结

优势:

  1. 高可用
  2. 锁跨度灵活
  3. 实现简单

劣势:

  1. 频繁创建和删除节点,性能开销大
  2. 高效性中等

综合对比

序列号

方案

高效性

并发性

高可用性

成本性

扩展性

改造代码量

备注

1

版本号实现乐观锁

需做好自旋的处理,每一个涉及到业务的方法

2

redis分布式锁

 

3

zookeeper分布式锁

 

 

进行for update锁的去除,一定要梳理完整的业务流程(eg:金额的加减处,有多少。对于不需要进行金额的操作更新处,按需进行更新,谨防金额字段误覆盖)

总结

  1. 通过金额加减,在数据库端操作(注意精度问题),对状态的流转控制手段,可以消减部分锁(目前对于大多数场景,不是掌握全面。所以只能是部分)
  2. redis和zookeeper的使用是性能和高可用的取舍。根据场景具体分析

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值