深入浅出探索Java分布式锁原理

目录
  • 什么是分布式锁?它能干什么?
  • 分布式锁实现方案
    • 基于数据库的分布式锁实现方案
      • 实现原理
    • 方案分析
  • 基于Redis的分布式锁实现方案
    • 基于sentnx命令的实现原理
    • 方案分析
  • 基于Redisson实现
    • RedLock
    • 方案分析
  • 基于Zookeeper的分布式锁实现方案
    • 实现原理
    • 方案分析
  • 分布式锁方案到底选哪个?
  • 总结

什么是分布式锁?它能干什么?

相信大家对于Java提供的synchronized关键字以及Lock锁都不陌生,在实际的项目中大家都使用过。如下图所示,在同一个JVM进程中,Thread1获得锁之后,对共享资源进行操作,其他线程未获得锁的线程只能等待Thread1释放后才能进行对应的操作。

但是随着业务的不断发展,原先的单体应用被拆分为多个微服务,每个微服务又会部署多个实例,于是就形成了当下的微服务架构。处理共享资源的请求来自不同的服务实例,也就是在不同的JVM进程中。原先的单体服务中的加锁方式在分布式场景下不能满足共享资源的并发访问要求。因此我们需要一种适用于分布式场景下的共享资源安全的处理机制,此时应对这种问题的分布式锁就应运而生了。

既然JVM进程管不到其他服务实例的线程,那么可以借助于外部组件能力来实现不同服务实例对于共享资源的统一管控,这种能力我们可以称之为分布式锁。因此分布式锁的本质就是在不同服务实例之外建立一种获取锁的机制,形成一种并发互斥能力来确保不同线程对于共享资源的并发安全,从而实现在微服务架构中同一时刻只有一个线程可以对共享资源进行操作。对于分布式锁来说,实际就是需要一个外部的状态存储系统来实现原子化的排他性操作。

通过对于分布式锁的需求分析,总结了如下的分布式锁四大特性,分别是多节点、加锁速度快、排他性以及锁过期实现机制。

分布式锁实现方案

基于数据库的分布式锁实现方案

实现原理

通过数据库的方式实现分布式锁的效果,实际就是借助于数据库的唯一性约束特性或者for update来实现。这里以唯一性约束来举个栗子,在电商领域的库存服务负责对商品的库存进行扣减,首先创建一张专门存放锁信息的锁表,那么库存服务在进行库存操作之前,先向数据库中的锁表插入一条锁资源数据。

create table ‘distributed_lock' (
‘id' BIGINT NOT NULL AUTO_INCREMENT,
‘resource_lock_key‘ varchar(64) NOT NULL
PRIMARY KEY(‘id'),
UNIQUE KEY ‘uk_resource_lock_key‘ (‘resource_lock_key‘) USING BTREE
)

大致的交互流程如下:

1、当库存服务进行手机库存扣减的时候,首先先向数据库中的锁表当中插入一条资源锁信息;

2、如果插入成功,则表示库存服务1可以对手机库存进行库存扣减操作;

3、此时库存服务2也要对库存进行操作,于是同样插入数据到锁表中;

4、但是由于锁表设置了唯一性约束,锁信息插入失败,库存服务进行等待;

5、库存服务1执行完库存扣减之后,删除锁表的信息;

6、库存服务2尝试插入资源锁信息,发现可以插入成功,继续执行后续操作。

方案分析

基于数据库的实现方式,看起来还是比较容易理解的。但是实际上还是有一些问题存在的,我们一起来分析下。

1、性能问题:由于是插入数据数据需要落盘存储,如果平凡进行读写的话会影响数据库性能,另外由于使用唯一键进行判断也会一定程度上影响数据库性能,因此数据库方案适用于并发量不到的简单场景;

2、数据库如果单点部署的话会存在单点故障问题,如果数据库出现故障,可能会导致平台中的业务异常;

3、死锁问题:在上文介绍中,包含了插入数据库的获取锁的步骤,还包含了删除锁信息的释放锁的过程,但是如果库存服务1在加锁之后挂掉了,无法进行锁的释放,而其他服务又无法获取到锁就会造成死锁的问题。当然了我们可以通过一个定时任务去检查锁表中是不是有过时的锁资源。但是这样无疑增加了分布式锁实现的复杂性。

4、不支持可重入:如果想要实现可重入锁,还需要增加主机、线程名等字段来进行标注,通过这几个字段来判断和当前信息是否一致,如果一致则认为已经获取到了锁。 鉴于以上的这些问题,有没有其他的分布式实现方案可以避免上述存在的问题呢?我们再往下来看。

基于Redis的分布式锁实现方案

基于sentnx命令的实现原理

Redis作为一块高性能的数据库中间件,经常被当做缓存在项目中使用。因此通过Redis实现分布式锁,也是比较常见的实现方案。 一样的道理,通过Redis实现分布式锁也需要通过它实现锁的互斥的能力。实际上就是利用了sentnx(set if not exists)命令。同时该命令是否能够设置成功,决定服务是否可以拿到对应的分布式锁。

127.0.0.1:6379> setnx stockLock 10.12.35.12_stockService
(integer) 1

如上图所示,大致的加锁以及释放锁的过程其实和数据库的分布式锁方案还是比较类似的。只不过将其中向数据库插入数据的步骤替换成了向Redis获取锁的步骤,由于Redis是基于内存进行操作的,因此性能上比基于数据库的分布式锁方案更好一点。

方案分析

上述基于Redis的方案的方案在性能上具有优势,我们再来分析下,这个使用命令的方式有没有什么问题。实际上和前面的数据库方案类似,Redis也会有死锁问题,当获取锁之后如果库存服务1挂掉了,库存服务2就获取不到锁了。因此我们要对其进行优化。那么问题的本质是如何让锁可以释放,因此我们需要在设置锁的时候加上过期时间,这样即使库存服务1挂了,无法主动释放锁,那么到了过期时间后锁失效,库存服务2依然可以获取锁,不会再造成死锁问题。

另外还应该注意的是,在我们设置锁的时候,还需要带有自身服务的业务属性,否则容易造成错乱。为什么这么说呢?举个栗子,库存服务在加完锁之后开始执行扣减库存的任务,当扣减库存完成之后,服务挂了,原先需要删除的锁资源,等到过期之后被Redis删除,此时库存服务2可以继续申请锁,如果此时库存服务1恢复了,它并不知道锁资源已经释放,起来后立马删除了库存服务2加的锁,那么此时就会出现两个问题:

1、库存服务执行完库存扣减之后,回头来进行锁资源释放的时候,发现锁实际已经不在了;

2、当库存服务1恢复后发现锁还在,立马删除了该锁,完成了它挂掉之前未完成的工作。但是实际上这个锁是库存服务2加的锁,如果此时库存服务3也要尝试加锁,发现可以加锁成功,和库存服务2一样同样对库存进行操作,那么此时就会出现线程安全问题。

经过上文的分析,这个问题的根源就是在加锁的时候没有具体区分到底是哪个服务加的锁。因此在执行命令的时候,我们需要将带有服务实例关联属性的设置为value,这样在进行锁获取的时候检查下当前锁的持有者是谁,如果不是服务实例自己则不能执行删除操作。

那这样是不是就完美解决问题了呢?实际上还是有问题存在的,有同学会说,怎么这么多问题?实际上这种方案的实现就是在各种不完美的方案中逐渐找到相对完美的方案。

上文提到的获取锁判断是不是自己方服务实例加的锁,再执行删除锁的过程实际并不是原子的。因此还是会出现并发安全问题,这个问题可以通过lua脚本来解决,在lua脚本中实现这个逻辑,而不是在客户端中实现。 但是实际上还是有问题没有解决,比如说我们在加锁的时候会设置过期时间,但是过期时间应该设置多长时间呢?设置短了的话,出现网络超时或者服务还没有执行完业务,锁就失效了。设置长了话,其他服务节点等待获取锁的时间就会变长,降低了服务的性能。

基于Redisson实现

Redisson实际上就是一个封装了Redis操作的客户端,实现了对于常见的Redis操作的封装。如对于Redis的设置锁的步骤以及删除锁的步骤都进行了封装。在设置锁的操作中,还引入了自动给锁续期的机制,SDK检测到业务未完成,但是锁要到期后,执行定续期。这样并可以动态的调节过期时间,避免锁在业务未完成情况下被释放的问题。

同时还封装了删除锁的时候执行的业务判断后再删除的逻辑,这样我们在使用Redisson操作Redis的时候,就和我们使用JDK一样。

RedLock

为了解决Redis作为分布式锁存在的单点问题,Redis的作者又提出了Redlock的解决方案,该解决方案依赖多个Redis的Master节点,官方推荐使用5个Master节点,他们彼此之间是独立的。大致的交互步骤如下所示:

1、首先获取当前节点的系统时间;

2、客户端尝试向所有的Redis实例顺序地发送加锁的请求(官方推荐Redis集群至少5个实例),在设置锁的过程中,使用相同的key以及随机值value,同时请求的超时时间需要远小于锁的有效时间。这样做的目的是为了防止节点不可用的时候导致请求锁的时候被阻塞,当实例没响应的时候可以快速跳过,向下一个节点继续请求锁。

3、假设Redis集群规模为5,那么如果客户端在大多数实例中(超过3个实例)获得了锁,同时计算了当前的时间减去步骤1中获得的时间,这个事件差如果小于锁的有效时间,那么此时可以认为加锁成功,可以操作执行后续的业务;

4、如果不满足步骤3是条件,那么就表示加锁失败,客户端需要向所有的Redis节点发起锁释放请求。

方案分析

为什么Redlock要在集群中多个实例上加锁呢?实际目的是通过锁的冗余来实现分布式锁的高容错性。试想一下如果只有一个Redis实例,一旦它挂掉了,客户端就无法进行加锁操作了或者锁信息就会丢失,影响业务功能。通过在集群中多实例中冗余锁信息,即使出现Redis挂了的情况,其他节点中依然存在锁信息,从而提升了分布式锁的可用性。

那么为什么还要计算几所时间呢?由于我们加锁的时候,每个节点都设置了超时时间,如果整个加锁的时间过长,整个过程的累加时间超过了锁的有效时间,那么加锁完成之后就会哦出现锁失效的情况了,因此我们需要确保加锁的事件尽可能的短,这也是为什么加锁请求都有超时时间的原因了,发现超时立马跳到下一个节点,避免单个节点耗时过长。

虽然Redlock看上去是比较完善的分布式解决方案,但是实际上这个方案是比较重的,需要维护一个Redis集群,另外过程中依赖系统时间,但是如果出现了时间跳变,那么对于整个分布式锁都有非常大的影响。

基于Zookeeper的分布式锁实现方案

实现原理

Zookeeper是一个分布式的应用协调服务中间件,通过它也可以实现分布式锁的效果,这里介绍的是基于临时有序的ZNode分布式锁实现方案。在介绍方案之前,先补充下Zookeeper中和分布式锁息息相关的特性。

我们来看下Zookeeper的数据结构,实际上它是一种树形模型,类似于Linux的文件系统。Zookeeper使用类似于文件目录的层级目录数据结构来组织自身的数据存储节点,这些节点就被称作为ZNode,每个节点都用一个以斜杠(/)分隔的路径来表示,而且每个节点都有父节点(根节点除外)。另外在Zookeeper中,如果我们使用不同的创建参数,可以创建不同类型的ZNode。 1、持久化ZNode:当createModePERSISTENT会创建持久化ZNode,节点存储的数据会永久保存在Zookeeper中,如果createModePERSISTENT_SEQUENTIAL,则会创建有序持久化ZNode,和之前的持久化节点不通的是,有序持久化节点的节点名称会附加上全局有序的递增序号; 2、临时ZNode:当createModeEPHEMERAL时,创建的节点临时节点,在与客户端的session过期后,对应的临时节点也会被删除。当createModeEPHEMERAL_SEQUENTIAL时创建出来的为有序的临时节点,当session过期之后,节点及其存储的数据也是会被删除的。

通过上述对于节点特性的描述,可以看出来它的全局递增有序以及过期删除的特性与分布式锁实现的原理非常契合。因此通过Zookeeper实现分布式锁的大致可以分为以下几个步骤:

1、首先创建一个持久化节点也就是父节点,这个持久化节点代表着一个分布式锁实例;

2、当有线程想要申请分布式锁的时候,则在该持久化节点下创建临时有序节点;

3、如果此时新建的临时有序节点是该父节点小所有有序节点中序号最小的节点,那么此时就表示申请到了分布式锁;

4、如果新建的临时节点当前不是最小序号的节点,则需要不断检查是否最小,知道最终获取到锁,或者节点超时。实际上这个是通过Zookeeperwatch机制实现的,在当前节点的上一序号的节点设置监听器,检查是否为最小节点的任务可以一直阻塞,直到收到上一节点被删除的时间事件,则唤醒检查事件,检查当前节点是不是最小序号节点。

5、当线程执行完业务之后,可以手动删除该临时节点以便于释放持有的锁。另外即使服务挂掉,由于对应的session失效,对应的临时节点也会被删除,防止出现死锁问题。

Redisson类似,我们在实际使用Zookeeper作为分布式锁的时候可以用Curator来作为开发SDK,它同样封装了很多实现,包括可重入锁的实现,减轻了使用者的负担。

方案分析

看上去通过Zookeeper实现分布式锁还是比较好的一种解决方案,但是它是完美的吗?从上面的分布式锁的流程可知,客户端线程想要获取锁就需要创建临时节点,这个时候客户端和Zookeeper之间就会维护一个session,来表示该客户端还在排队等待获取锁。因此这个方案的潜在问题就在于一旦出现网络异常,或者客户端发生STW GC,那么就可能导致session关闭,从而导致临时节点被关闭,此时就会出现原来客户端持有的锁被删除了,如果有另外的客户端过来加锁的话可以成功获取,那么此时就出现并发安全问题了。因此在这种极端条件下,Zookeeper的分布式锁实现方案也不是100%保证安全的。

另外实际上还有基于etcd的分布式锁实现方案,其基本原理和Zookeeper差不多,感兴趣的同学可以再进行了解下。

分布式锁方案到底选哪个?

通过上述几种分布式锁方案原理的阐述以及问题分析,每个方案都有自己的长处以及缺点。所以在实际项目落地的时候,我么需要结合实际来进行分布式锁方案的选择。比如如果平台中本身已经有Redis集群了,但是没有Zookeeper集群,那么我们就可以借助于现有的基础实施来落地分布式锁,不需要再去维护一套Zookeeper集群。

另外根据实际的业务场景,如果并发量并不是很高,也可以通过简单的数据库的分布式锁方案来实现。

总结

本文首先对从单机时代到分布式场景下的分布式锁的产生的背景进行了分析,通过对分布式锁的本质问题的探究,引出了数据库分布式锁方案、Redis分布式锁方案以及Zookeeper分布式锁方案,并对每一种方案的优点以及不足进行了分析,相信大家可以在落地实现分布式锁的时候可以按照自身的情况选择合适的方案。

到此这篇关于深入浅出探索Java分布式锁原理的文章就介绍到这了,更多相关Java 分布式锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java redisson实现分布式锁原理详解

    Redisson分布式锁 之前的基于注解的锁有一种锁是基本redis的分布式锁,锁的实现我是基于redisson组件提供的RLock,这篇来看看redisson是如何实现锁的. 不同版本实现锁的机制并不相同 引用的redisson最近发布的版本3.2.3,不同的版本可能实现锁的机制并不相同,早期版本好像是采用简单的setnx,getset等常规命令来配置完成,而后期由于redis支持了脚本Lua变更了实现原理. <dependency> <groupId>org.redisson&

  • Java注解如何基于Redission实现分布式锁

    这篇文章主要介绍了Java注解如何基于Redission实现分布式锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.定义注解类 @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DistributedLock { //锁名称 String lockName() default ""; /

  • Java Redis分布式锁的正确实现方式详解

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性.在任意时刻,只有一个客户端能持有锁. 不会发生死锁.即使有一个客户端在

  • Javas使用Redlock实现分布式锁过程解析

    一.redlock简介 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段.实现高效的分布式锁有三个属性需要考虑: 安全属性:互斥,不管什么时候,只有一个客户端持有锁 效率属性A:不会死锁 效率属性B:容错,只要大多数redis节点能够正常工作,客户端端都能获取和释放锁. Redlock是redis官方提出的实现分布式锁管理器的算法.这个算法会比一般的普通方法更加安全可靠.关于这个算法的讨论可以看下官方文档. 二.怎么用java使用 redlock 在pom文件引入redis和

  • Java基于redis实现分布式锁代码实例

    为什么会有这个需求: 例如一个简单用户的操作,一个线程去修改用户状态,首先在在内存中读出用户的状态,然后在内存中进行修改,然后在存到数据库中.在单线程中,这是没有问题的.但是在多线程中由于读取,修改,写入是三个操作,不是原子操作(同时成功或失败),因此在多线程中会存在数据的安全性问题. 这个问题的话,就可以用分布式锁在限制程序的并发执行. 实现思路: 就是进来一个先占位,当别的线程进来操作的时候,发现有人占位了,就会放弃或者稍后再试. 占位的实现: 在redis中的setnx命令来实现,redi

  • 浅谈Java(SpringBoot)基于zookeeper的分布式锁实现

    通过zookeeper实现分布式锁 1.创建zookeeper的client 首先通过CuratorFrameworkFactory创建一个连接zookeeper的连接CuratorFramework client public class CuratorFactoryBean implements FactoryBean<CuratorFramework>, InitializingBean, DisposableBean { private static final Logger LOGG

  • 通过实例解析Java分布式锁三种实现方法

    分布式锁三种实现方式: 一, 基于数据库实现分布式锁 1. 悲观锁 利用select - where - for update 排他锁 注意: 其他附加功能与实现一基本一致,这里需要注意的是"where name=lock ",name字段必须要走索引,否则会锁表.有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题. 2. 乐观锁 所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update ve

  • Java基于redis实现分布式锁

    为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchronized进行互斥控制.但是这仅仅对单机环境有效.我们实现分布式锁大概通过三种方式. redis实现分布式锁 数据库实现分布式锁 zk实现分布式锁 实际上这三种和java对比看属于一类.都是属于程序外部锁. 原理剖析 上述三种分布式锁都是通过各自为依据对各个请求进行上锁,解锁从而控制放行还是拒绝.redis锁是基于其提供的setnx命令. setnx当且仅当key不存在.若给定key已

  • 浅谈Java分布式架构下如何实现分布式锁

    01分布式锁运用场景 互联网秒杀,抢优惠卷,接口幂等性校验.咱们以互联网秒杀为例. @RestController @Slf4j publicclassIndexController{ @Autowired privateRedissonredission; @Autowired privateStringRedisTemplatestringRedisTemplate; @RequestMapping("/deduct_stock") publicStringdeductStock(

  • 深入浅出探索Java分布式锁原理

    目录 什么是分布式锁?它能干什么? 分布式锁实现方案 基于数据库的分布式锁实现方案 实现原理 方案分析 基于Redis的分布式锁实现方案 基于sentnx命令的实现原理 方案分析 基于Redisson实现 RedLock 方案分析 基于Zookeeper的分布式锁实现方案 实现原理 方案分析 分布式锁方案到底选哪个? 总结 什么是分布式锁?它能干什么? 相信大家对于Java提供的synchronized关键字以及Lock锁都不陌生,在实际的项目中大家都使用过.如下图所示,在同一个JVM进程中,T

  • 详细解读分布式锁原理及三种实现方式

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们"任何一个分布式系统都无法同时满足一致性(Consistency).可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项."所以,很多系统在设计之初就要对这三者做出取舍.在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证"最终一致性",只要这个最终

  • java乐观锁原理与实现案例分析

    本文实例讲述了java乐观锁原理与实现.分享给大家供大家参考,具体如下: 简单说说乐观锁.乐观锁是相对于悲观锁而言.悲观锁认为,这个线程,发生并发的可能性极大,线程冲突几率大,比较悲观.一般用synchronized实现,保证每次操作数据不会冲突.乐观锁认为,线程冲突可能性小,比较乐观,直接去操作数据,如果发现数据已经被更改(通过版本号控制),则不更新数据,再次去重复 所需操作,知道没有冲突(使用递归算法). 因为乐观锁使用递归+版本号控制  实现,所以,如果线程冲突几率大,使用乐观锁会重复很多

  • 浅析JAVA Lock锁原理

    同样是锁,先说说synchronized和lock的区别: synchronized是java关键字,是用c++实现的:而lock是用java类,用java可以实现 synchronized可以锁住代码块,对象和类,但是线程从开始获取锁之后开发者不能进行控制和了解:lock则用起来非常灵活,提供了许多api可以让开发者去控制加锁和释放锁等等. 写个Demo static Lock lock = new ReentrantLock();public static void main(String[

  • InterProcessMutex实现zookeeper分布式锁原理

    原理简介: zookeeper实现分布式锁的原理就是多个节点同时在一个指定的节点下面创建临时会话顺序节点,谁创建的节点序号最小,谁就获得了锁,并且其他节点就会监听序号比自己小的节点,一旦序号比自己小的节点被删除了,其他节点就会得到相应的事件,然后查看自己是否为序号最小的节点,如果是,则获取锁. zookeeper节点图分析 InterProcessMutex实现的锁机制是公平且互斥的,公平的方式是按照每个请求的顺序进行排队的. InterProcessMutex实现的InterProcessLo

  • Spring Boot 实现Redis分布式锁原理

    目录 分布式锁实现 引入jar包 封装工具类 模拟秒杀扣减库存 测试代码 方案优化 问题1:扣减库存逻辑无法保证原子性, 问题2:如果加锁失败,则会直接访问,无法重入锁 总结 分布式锁实现 引入jar包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclus

  • Java分布式锁的概念与实现方式详解

    什么是分布式锁?在回答这个问题之前,我们先回答一下什么是锁. 普通的锁,即在单机多线程环境下,当多个线程需要访问同一个变量或代码片段时,被访问的变量或代码片段叫做临界区域,我们需要控制线程一个一个的顺序执行,否则会出现并发问题. 如何控制呢?就是设置一个各个线程都能看的见的标志.然后,每个线程想访问临界区域时,都要先查看标志,如果标志没有被占用,则说明目前没有线程在访问临界区域.如果标志被占用了,则说明目前有线程正在访问临界区域,则当前线程需要等待. 这个标志,就是锁. 在单机多线程的java程

  • redisson实现分布式锁原理

    Redisson分布式锁 之前的基于注解的锁有一种锁是基本redis的分布式锁,锁的实现我是基于redisson组件提供的RLock,这篇来看看redisson是如何实现锁的. 不同版本实现锁的机制并不相同 引用的redisson最近发布的版本3.2.3,不同的版本可能实现锁的机制并不相同,早期版本好像是采用简单的setnx,getset等常规命令来配置完成,而后期由于redis支持了脚本Lua变更了实现原理. <dependency> <groupId>org.redisson&

  • Java分布式锁的三种实现方案

    方案一:数据库乐观锁 乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的left_count减1,在并发的情况下如何要保证left_count不为负数,乐观锁的实现方式为在红包表上添加一个版本号字段(version),默认为0. 异常实现流程 -- 可能会发生的异常情况 -- 线程1查询,当前left_count为1,则有记录 select * from t_bonus

随机推荐