网站没备案做淘宝客,网站前端用什么语言,广东新闻联播直播今天回播,贵阳美容网站建设基于数据库的分布式锁#xff08;基于主键id和唯一索引#xff09;
1基于主键实现分布式锁
2基于唯一索引实现分布式锁
其实原理一致#xff0c;都是采用一个唯一的标识进行判断是否加锁。
原理#xff1a;通过主键或者唯一索性两者都是唯一的特性#xff0c;如果多个…基于数据库的分布式锁基于主键id和唯一索引
1基于主键实现分布式锁
2基于唯一索引实现分布式锁
其实原理一致都是采用一个唯一的标识进行判断是否加锁。
原理通过主键或者唯一索性两者都是唯一的特性如果多个服务器同时请求到数据库数据库只会允许同一时间只有一个服务器的请求在对数据库进行操作其他服务器的请求就需要进行阻塞等待或者进行自旋。如何实现的呢可以理解为同一时间只有一个请求能够拿到锁当方式执行完成过后对锁进行释放过后其他请求就可以拿到锁再对数据库进行操作这样就避免了数据不安全问题。
阻塞线程等待锁释放的一种方式
自旋自旋包括了递归自旋while自旋。意思就是不断地去尝试获取锁只有获取锁才会停止自旋过程没有拿到就会一直尝试获取锁。
以下是一个基于MySQL实现分布式锁的示例代码
import java.sql.*;
import java.util.Properties;public class DatabaseLock {private Connection conn;private static final String dbUrl jdbc:mysql://localhost:3306/test;private static final String username xxxx;private static final String password xxxxx;// 构造函数建立数据库连接public DatabaseLock() throws SQLException {Properties props new Properties();props.setProperty(user, username);props.setProperty(password, password);conn DriverManager.getConnection(dbUrl, props);}// 尝试获取锁public boolean tryLock(String lockId) throws SQLException {PreparedStatement stmt conn.prepareStatement(INSERT INTO locks (id) VALUES (?));stmt.setString(1, lockId);try {stmt.executeUpdate();return true;} catch (SQLException e) {if (e.getErrorCode() 1062) { // 锁已存在return false;} else {throw e;}}}// 释放锁public void releaseLock(String lockId) throws SQLException {PreparedStatement stmt conn.prepareStatement(DELETE FROM locks WHERE id?);stmt.setString(1, lockId);stmt.executeUpdate();}
}
在使用时通过创建一个DatabaseLock实例来获取和释放锁
DatabaseLock databaseLock new DatabaseLock();// 尝试获取锁获取成功返回true获取失败返回false
if (databaseLock.tryLock(lockId)) {try {// do something} finally {databaseLock.releaseLock(lockId); // 释放锁}
} else {// get lock failed
}
基于Redis的分布式锁
下面就来介绍基于Redis的分布式锁直接上图 虚拟机A和虚拟机B两个虚拟机都想对可变的共享资源广义的概念可以是数据库的某一张表和数据库中的某一行数据 就会出现线程安全问题就需要基于锁模型实现同步互斥的手段保证只有一个虚拟机中的线程进而实现这个线程的相对安全。现在虚拟机要对共享资源上锁锁对象是Redis操作的对象就是这个共享资源假如数据库的一行数据。
基于Redis实现分布式锁执行流程
1虚拟机实例A根据Hash算法选择Redis节点Redis采用的是集群部署每个服务器都是一个节点执行Lua脚本加锁就是通过setnx方法即set一个key主键id到Redis当中判断Redis中是否有当前key没有就返回1表示加锁成功有就返回0表示加锁失败,并且设置锁的过期时间。当虚拟机A对共享资源上锁成功过后就拥有了对共享数据的操作权限然后就可以对共享数据的操作处理执行事务处理。
问题在从Redis获取锁的过程和进行设置锁的过期时间过程中出现宕机就会出现锁一辈子不会被释放出现死锁问题 这时候就需要保证获取锁和设置锁的过期时间两行代码的原子性就是要么同时成功要么同时失败如何实现呢 这时候只要保证将两行代码变成一行代码即可。 原本的setnx和expire是两行代码
ifjedis.setnxlock_stock1 1{ //获取锁
//在这里出现宕机了死锁问题出现了expirelock_stock5 //设置锁超时try {业务代码} finally {jedis.dellock_stock //释放锁}
} 通过set变成一行代码过后解决了死锁问题。
ifset(lock_stock,1,NX,EX,5) 1{ //获取锁并设置超时try {业务代码} finally {dellock_stock //释放锁}
} 2这时候如果虚拟机B也需要对共享资源进行操作也去执行lua脚本进行加锁就是采用setnx的方式--通过set一个key到redis判断redis中是否已经存储了这个key行数据的主键id如果查询到redis中没有就会返回1表示加锁成功如果有就会返回0表示加锁失败 这就能保证共享资源同一时间不会被多个虚拟机同时操作。 3当虚拟机A执行完对自己已经加锁的共享资源执行操作完成之后必须要执行DEL释放锁不然其他虚拟机包括虚拟机A都不能再对当前共享资源进行加锁操作
问题虚拟机A能保证会执行完成过后一定执行DEL释放锁吗 答案是不一定的 4当虚拟机A执行对共享资源事务操作完成之后在执行DEL释放锁之前代码出现问题抛出异常就会出现这种问题虚拟机A就永远不能执行DEL释放锁了,就会导致后续上锁都会失败。
问题所以就出现上面这个执行流程如何解决呢 5这时候起初指执行lua脚本加锁的时候存储一个过期时间当不能主动进行DEL释放锁时到达Redis设置的过期时间锁就会过期。
这个时候又会出现另一个问题? 就是虚拟机A在设置的过期时间以内还没有执行完对共享资源的操作锁就过期了如何解决呢 6这时候就会执行最后一个流程后台守护线程类似于Redission内部提供一个监控锁的看门狗来定期的检查锁是否存在如果存在延长key的过期时间还需要判断事务是否还在正常执行如果是异常已经抛出异常就不用进行后台守护线程了然后等待锁自动过期。
说这么多其实就是明白其执行原理而实际开发过程中大佬们已经使用Redission封装好了上面的具体实现细节。Redission实现分布式锁【封装了基于Redis的分布式锁】
什么是Redission? 简单理解为就是操作Redis的一个工具包让我们使用Redis更加简单让使用者能够将精力更集中地放在处理业务逻辑上。
Redisson实现分布式锁 Redisson官方文档对分布式锁的解释总结下来有两点 1Redisson加锁自动有过期时间30s监控锁的看门狗发现业务没执行完会自动进行锁的续期(重回30s)这样做的好处是防止在程序还没有执行结束锁自动过期被删除问题 2当业务执行完成不再给锁续期即使没有手动释放锁锁的过期时间到了也会自动释放锁。 // 获取锁RLock lock redisson.getLock(LOCK_KEY);try {// 加锁lock.lock();int s Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(REDIS_KEY)));if (s 0) {// 扣库存s--;System.out.printf(秒杀商品个数剩余 s \n);// 更新库存stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(s));} else {System.out.println(活动太火爆了商品已经被抢购一空了);}} catch (Exception e) {System.out.println(Thread.currentThread().getName() 异常);e.printStackTrace();} finally {// 释放锁lock.unlock();}
基于Zookeeper的分布式锁
什么是Zookeep
ZooKeeper是一个分布式的协调服务Zookeeper是基于CP注重数据的一致性若主机挂掉则Zookeeper不会对外进行提供服务了需要选择一个新的Leader出来才能提供服务不保证高可用性。简单来说zookeeper文件系统监听通知机制。
Zookeeper数据模型
Zookeeper会维护一个具有层次关系的树状的数据结构它非常类似于一个标准的文件系统,如下图所示:同一个目录下不能有相同名称的 每个子目录项如 NameService 都被称作为 znode(目录节点)和文件系统一样我们能够自由的增加、删除znode在一个znode下增加、删除子znode唯一的不同在于znode是可以存储数据的。
有四种类型的znode
PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后该节点依旧存在
PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后该节点依旧存在只是Zookeeper给该节点名称进行顺序编号
EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后该节点被删除
EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后该节点被删除只是Zookeeper给该节点名称进行顺序编号
监听通知机制
客户端注册监听它关心的目录节点当目录节点发生变化数据改变、被删除、子目录节点增加删除时zookeeper会通知客户端。
实现方案临时顺序目录节点监听机制
在项目中可以使用curator这个是Apache封装好的基于zookeeper的分布式锁方案。
总结
1基于数据库实现通常基于主键或者唯一索引来实现分布式锁,但是性能比较差一般不建议使用
2基于Redis实现分布式锁可以使用setnx来加锁 但是需要设置锁的过期时间来防止死锁所以要结合expire使用.为了保证setnx和expire两个命令的原子性可以使用set命令组合【将setnx和expire结合成一行代码】。 总之自己封装Redis的分布式锁是很麻烦的我们可以使用Redissoin来实现分布式锁Redissoin已经封装好了。
3.基于zookeeper 使用临时顺序节点监听实现线程进来都去创建临时顺序节点,第一个节点的创建线程获取到锁后面的节点监听自己的上一个节点的删除事件如果第一个节点被删除释放锁第二个节点就成为第一个节点获取到锁。
在项目中可以使用curator这个是Apache封装好的基于zookeeper的分布式锁方案。