分布式中的锁

一.Mysql中的锁

1. mysql 中有哪些锁

  • 按加锁机制可分为:乐观锁、悲观锁
  • 基于锁的属性分类:共享锁(读锁)、排他锁(写锁)
  • 基于锁的粒度分类:行级锁((innodb )、表级锁( innodb、myisam)、页级锁( innodb引擎)、记录锁、间隙锁、临键锁
  • 基于锁的状态分类:意向共享锁、意向排它锁(一般不用)

2. 什么是共享锁和排他锁

  • 什么是共享锁(share Lock):允许多个事务同时获得该锁。共享锁(share lock)又称读锁,简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持[并发]的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。在select语句末尾加上lock in share mode关键字

  • 什么是排它锁(exclusive Lock):排他锁又称写锁,简称×锁;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读的问题。 在select语句末尾加上for update关键字。

3.按粒度分类

  • 行锁:行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问,特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高

  • 锁(table lock):表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;特点:粒度大,加锁简单,容易冲突;

  • 页锁:页级锁是MysQL中锁定粒度介于行级锁和表级锁中间的一种锁.表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。特点:开销和加锁时间界于表锁和行锁之间,会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

  • 记录锁(Record lock):记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录,加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题

  • 间隙锁:是属于行锁的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。间隙锁只会出现在REPEATABLE_READ(重复读)的事务级别中。间隙锁本质上是用于阻止其他事务在该间隙内插入新记录,而自身事务是允许在该间隙内插入数据的。举例:update user set age=age+1 where id>1 and id<10;会加间隙锁

  • 临键锁(Next-Key lock):也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住。如果你使用select … in share mode或者select … for update语句,那么InnoDB会使用临键锁,因而可以防止幻读

  • 什么是自增锁 :自增锁是一种特殊的表级锁,主要用于事务中插入自增字段,也就是我们最常用的自增主键id

4.什么是悲观锁和乐观锁

什么是悲观锁
悲观锁认为总是有线程并发问题导致数据不安全,所以在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。通常是在 select语句后面增加 for update 来锁定数据。

注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。可以使用命令设置MySQL为非autocommit模式:set autocommit=0;复制代码,举例:

1.开始事务 : begin;

2.查询出商品信息 : select status from goods where id=1 for update;

3.根据商品信息生成订单:insert into t_orders (goods_id) values (1);

4.修改商品status2:update goods set status=2;

5.提交事务:commit;

那么在执行 商品查询 语句的时候,这行数据就被锁定,直到事务被commit ,其他事务再此期间想要查询或修改该放数据,将会等待锁的释放。
什么是乐观锁

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,在数据处理的过程中会不加锁,在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

乐观锁如何使用

直接用:表中添加一个时间戳或者版本号的字段来实现,update account set version = version + 1 where id = #{id} and version = #{oldVersion} 当更新不成功,客户端重试,重新读取最新的版本号或时间戳,再次尝试更新,类似 CAS 机制,推荐使用。
伪代码实现:

Resrouce resource = exeSql("select * from resource where resource_name = xxx");
boolean succ = exeSql("update resource set version= version + 1 where resource_name = xxx and version =#{version}");

if (!succ) {
    // 发起重试
}

实际代码中可以写个while循环不断重试,版本号不一致,更新失败,重新获取新的版本号,直到更新成功。

二.分布式锁

1.为什么要分布式锁

1.1.单体项目中的并发问题

在单进程(启动一个jvm)的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。
在java中可以通过synchronizelock等手段来实现。

1.2.什么情况要用分布式锁

很多时候我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,通过 Java 提供的并发 API 我们可以解决,但是在分布式环境下,就没有那么简单啦。

  • 分布式与单机情况下最大的不同在于其不是多线程而是多进程。

  • 多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。

那么原来的方案就不行了
在这里插入图片描述
分布式锁作用就是在分布式系统,保证某个方法只能在同一时间某个进程的某个线程执行。在整个分布式环境下都只有一份。
在这里插入图片描述

2.分布式锁概述

2.1. 什么是分布式锁

就是在在分布式/集群 环境下,保证某个公共资源只能在同一时间被多进程应用的某个进程的某一个线程访问时使用锁。

2.2.需要什么样的分布式锁

  • 这把锁是多个应用共同访问的

  • 这把锁要是一把可重入锁(避免死锁)

  • 这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)

  • 这把锁最好是一把公平锁(根据业务需求考虑要不要这条)

  • 有高可用的获取锁和释放锁功能

  • 获取锁和释放锁的性能要好

2.3.分布式锁有哪些方案

  • 基于数据库操作(基于主键或唯一索引)

  • 基于redis缓存(setnx)

  • 基于zookeeper 临时顺序节点+watch

3.基于Redis实现分布式锁[重要]

3.1.Redis实现分布式锁的原理

3.2.Redission如何实现分布式锁

3.3.Redission有哪些锁,你用过哪些

3.4.Redission的看门狗了解吗?

3 .5.要拿到多个线程执行后的结果再进行业务处理,怎么做

5.基于Zookeeper的分布式锁[了解]

5.1.ZooKeeper概述

ZooKeeper(zookeeper+dubbo)是Apache下的一个Java开源项目(最初由Yahoo开发, 后捐献给了Apache)。ZooKeeper的原始功能很简单,基纡它的层次型的目录树的数据结构,并通过对树上的节点进行有效管理,可以设计出各种各样的分布式集群管理功能。此外, ZooKeeper本身 也是分布式的。

5.2.ZK数据模型

Zookeeper会维护一个具有层次关系的树状的数据结构,它非常类似于一个标准的文件系统,如下图所示:同一个目录下不能有相同名称的目录节点
在这里插入图片描述

5.3.节点分类

ZooKeeper 节点是有生命周期的这取决于节点的类型,在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。

  • 持久节点(PERSISTENT)
    所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。

  • 持久顺序节点(PERSISTENT_SEQUENTIAL)
    这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。

  • 临时节点(EPHEMERAL)
    和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。

  • 临时顺序节点(EPHEMERAL_SEQUENTIAL)
    在临时几点的基础上增加了顺序,可以用来实现分布式锁

顺序节点可以用来为所有的事件进行全局排序,这样客户端可以通过序号推断事件的顺序。

5.4.分布式锁实现

特点:CAP模型属于CP | ZAB一致性算法实现 | 稳定性好 , 开发常用,如果你的项目中正好使用了zk集群,推荐使用。业界有Apache Curator框架提供了现成的分布式锁功能,现成的,推荐直接使用。

另外,可基于Zookeeper自身的特性和原生Zookeeper API自行实现分布式锁。

方案1 临时节点+重试

根据Zookeeper的临时节点的特性实现分布式锁,先执行的线程在zookeeper创建一个临时节点,代表获取到锁,后执行的线程需要等待,直到临时节点被删除说明锁被释放,第二个线程可以尝试获取锁。

方案2 临时顺序节点+watch
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值