深圳网站建设推荐q479185700顶上威海哪家做网站好
- 作者: 五速梦信息网
- 时间: 2026年04月20日 09:20
当前位置: 首页 > news >正文
深圳网站建设推荐q479185700顶上,威海哪家做网站好,集安网站建设,电子商务网站建设规划论文目录 分布式锁解释作用特性实现方式MySQL、Redis、Zookeeper三种方式对比 原理 reids分布式锁原理目的容错redis简单分布式锁实现锁接口实现类下单场景的实现容错场景1解决思路优化代码 容错场景2Lua脚本Redis利用Lua脚本解决多条命令原子性问题 释放锁的业务流程Lua脚本来表示… 目录 分布式锁解释作用特性实现方式MySQL、Redis、Zookeeper三种方式对比 原理 reids分布式锁原理目的容错redis简单分布式锁实现锁接口实现类下单场景的实现容错场景1解决思路优化代码 容错场景2Lua脚本Redis利用Lua脚本解决多条命令原子性问题 释放锁的业务流程Lua脚本来表示 优化代码 总结 分布式锁
解释
分布式锁是一种用于协调分布式系统中多个节点对共享资源进行访问的机制。在分布式系统中多个节点可能同时竞争同一个资源并且可能同时进行修改操作这就会导致数据的不一致性和并发冲突的问题。为了解决这个问题引入了分布式锁机制。
作用
分布式锁可以确保在同一时刻只有一个节点能够对共享资源进行访问操作其他节点需要等待该节点释放锁之后才能进行操作。分布式锁可以通过网络通信来实现常见的实现方式有基于数据库的锁、基于缓存的锁、基于ZooKeeper的锁等。使用场景分布式任务调度、分布式缓存、分布式事务等场景
特性
互斥性 同一时刻只有一个节点能够获取到锁其他节点需要等待。可重入性 同一个节点在获取到锁之后可以再次获取锁而不会被阻塞。容错性 锁的释放需要能够容忍节点的故障确保锁能够被正常释放。高性能 分布式锁的实现需要保证高性能避免成为系统的瓶颈。
实现方式
基于数据库使用关系型数据库或者其他支持事务的数据库来实现分布式锁。可以通过在数据库中创建一个带有唯一索引的表或者行来确保只有一个进程能够成功获取锁。基于文件系统使用共享的文件系统来实现分布式锁。可以通过创建一个特定的文件来表示锁的状态进程需要先创建文件或者尝试获得文件的独占写锁来获取锁。基于ZooKeeper使用ZooKeeper来实现分布式锁。可以通过在ZooKeeper中创建一个临时节点来表示锁的状态只有创建成功的进程才能获取锁。基于Redis使用Redis的原子操作来实现分布式锁。可以通过在Redis中设置一个带有过期时间的键来表示锁的状态只有成功设置锁的进程才能获取锁。
MySQL、Redis、Zookeeper三种方式对比 MySQLRedisZookeeper互斥利用MySQL本身的互斥锁的机制利用redis中setnx的互斥命令利用节点的唯一性和有序性来实现互斥高可用好好好高性能一般好一般安全性断开连接自动释放锁利用锁超时时间。到期自动释放临时节点断开连接自动释放
原理 reids分布式锁原理
Redis分布式锁的原理基于Redis的单线程特性以及原子操作的特点。具体原理如下 获取锁当一个节点要获取分布式锁时它会向Redis发送一个SETNX命令将一个特定的键值对设置到Redis中。如果该键不存在节点成功获取锁并将该键值对设置为锁的持有者标识。如果该键已经存在表示锁已经被其他节点持有节点获取锁失败。 释放锁当一个节点要释放分布式锁时它会向Redis发送一个DEL命令将该键值对从Redis中删除。只有持有锁的节点才能成功释放锁。
目的
这样的实现基于Redis的SETNX命令的原子性保证SETNX命令的语义是 当键不存在时设置键值对并返回1当键已存在时不设置值并返回0。 通过SETNX命令的原子性可以保证同一时刻只有一个节点能够成功获取锁。
容错
为了防止分布式锁的死锁问题可以为获取锁的操作设置一个过期时间。节点在获取锁的同时可以为该键设置一个带有过期时间的键值对确保即使节点在获取锁之后发生故障如果过期时间到了Redis也会自动释放该锁。为了提高分布式锁的可用性和容错性还需要引入一些额外的机制例如设置一个超时时间避免长时间持有锁导致的问题。还可以使用分布式锁的续约机制即在获取锁之后定期向Redis发送续约命令更新锁的过期时间确保节点在持有锁的期间不会被自动释放。
redis简单分布式锁实现
锁接口
public interface ILock {/*** 非阻塞方式尝试获取锁* param timeoutSec 锁持有的超时时间过期后自动释放* return true代表获取锁成功; false代表获取锁失败/boolean tryLock(long timeoutSec);/** 释放锁有加锁就要有释放锁*/void unlock();
}实现类
public class SimpleRedisLock implements ILock {// 业务名称private String name;private StringRedisTemplate stringRedisTemplate;// 通过构造方法将name和stringRedisTemplate传入public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}private static final String KEY_PREFIX lock:;Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标识long threadId Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId , timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}Overridepublic void unlock() {//通过del删除锁stringRedisTemplate.delete(KEY_PREFIX name);}
}下单场景的实现
// 使用Redis分布式锁
// 创建锁对象
SimpleRedisLock lock new SimpleRedisLock(order: userId, stringRedisTemplate);
// 获取锁对象
boolean isLock lock.tryLock(5);
// 加锁失败
if (!isLock) {return Result.fail(不允许重复下单);
}
try {// 获取代理对象(事务)IVoucherOrderService proxy (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);
} finally {// 释放锁lock.unlock();
}容错场景1
线程1先获取锁后由于业务阻塞还没执行完成线程1的锁超时后自动释放线程2在线程1的锁超时自动释放后进行加锁成功正好线程1将业务接着执行完后需要释放锁此时释放的就是线程2的锁造成了误删问题误删后线程3又加锁成功此时线程2和线程3就出现了并发执行业务造成并发安全问题 解决思路
在获取锁时存入线程标识比如可以用UUID这类的唯一序列在释放锁时先获取锁中的线程标识判断是否与当前线程标识一致 如果一致则释放锁如果不一致则不释放锁 不要直接将线程id作为线程标识因为不同JVM中的线程id可能一样所以可以用 线程idUUID 作为线程标识
优化代码
public class SimpleRedisLock implements ILock {// 业务名称private String name;private StringRedisTemplate stringRedisTemplate;// 通过构造方法将name和stringRedisTemplate传入public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}private static final String KEY_PREFIX lock:;private static final String ID_PREFIX UUID.randomUUID().toString(true) -;Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标识String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}Overridepublic void unlock() {// 获取线程标识String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁中的标识String id stringRedisTemplate.opsForValue().get(KEY_PREFIX name);// 判断标识是否一致if(threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX name);}}
}容错场景2
线程1执行完业务后准备释放锁先判断完锁一致后正准备释放时发生了阻塞例如GC时所有线程会阻塞恰好线程1在阻塞期间锁超时被释放线程2获取锁成功此时线程1被唤醒后继续释放锁由于之前判断过锁的标识所以直接释放锁但是此时的锁是线程2的线程3又加锁成功此时线程2和线程3就出现了并发执行业务造成并发安全问题
Lua脚本
Lua脚本是一种轻量级的编程语言用于嵌入式系统和游戏开发中。其设计目标是为了简单、可扩展和快速。Lua脚本具有简洁的语法和功能强大的特性包括动态类型、自动内存管理和高阶函数支持。它可以被嵌入到其他程序中以提供脚本化的功能。由于其轻量级和高性能的特点Lua脚本被广泛应用于游戏脚本、应用程序的扩展和配置文件等方面。Lua脚本可以通过与其他编程语言的接口交互例如C、C和Java使开发人员可以在应用程序中使用Lua脚本来实现灵活的功能和逻辑。此外Lua还具有丰富的标准库和大量的第三方库使开发人员能够快速开发出各种类型的应用程序。
Redis利用Lua脚本解决多条命令原子性问题 Redis提供了Lua脚本功能在一个脚本中编写多条Redis命令确保多条命令执行时的原子性 # 执行Redis命令
redis.call(命令名称, key, 其他参数, …)例如我们要先执行set name zhangsan再执行get name则脚本如下 # 先执行 set name zhangsan
redis.call(set, name, zhangsan)
再执行 get name
local name redis.call(get, name)
返回
return name写好脚本以后需要用Redis命令来调用脚本例如我们要执行 redis.call(‘set’, ‘name’, ‘jack’) 这个脚本语法如下 双引号内表示脚本内容最后的0表示脚本需要的key类型的参数个数EVAL return redis.call(set,name,zhangsan) 0如果脚本中的key、value不想写死可以作为参数传递。key类型参数会放入KEYS数组其它参数会放入ARGV数组在脚本中可以从KEYS和ARGV数组获取这些参数 name传给KEYS[1]zhangsan传给ARGV[1]EVAL return redis.call(set,KEYS[1],ARGV[1]) 1 name zhangsan释放锁的业务流程
获取锁中的线程标识判断是否与指定的标识当前线程标识一致 如果一致则释放锁删除如果不一致则什么都不做
Lua脚本来表示
– 这里的 KEYS[1] 就是锁的key这里的ARGV[1] 就是当前线程标识
– 获取锁中的标识判断是否与当前线程标识一致
if (redis.call(GET, KEYS[1]) ARGV[1]) then– 一致则删除锁return redis.call(DEL, KEYS[1])
end
– 不一致则直接返回
return 0优化代码
基于Lua脚本实现分布式锁的释放锁逻辑RedisTemplate调用Lua脚本的API如下
public class SimpleRedisLock implements ILock {// 业务名称private String name;private StringRedisTemplate stringRedisTemplate;// 通过构造方法将name和stringRedisTemplate传入public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}private static final String KEY_PREFIX lock:;private static final String ID_PREFIX UUID.randomUUID().toString(true) -;// 加载Lua脚本private static final DefaultRedisScriptLong UNLOCK_SCRIPT;static {UNLOCK_SCRIPT new DefaultRedisScript();//将编写的Lua脚本放在resources目录下比如名称为unlock.luaUNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua));UNLOCK_SCRIPT.setResultType(Long.class);}Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标识String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}Overridepublic void unlockL() {// 调用Lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX name),ID_PREFIX Thread.currentThread().getId());}
总结 Redis的分布式锁实现其实就是利用setnx/setex获取锁并设置过期时间保存线程标识 释放锁时先判断线程标识是否与自己一致一致则删除锁 Redis的分布式的优点 利用setnx满足互斥性利用setex保证故障时锁依然能释放避免死锁提高安全性利用Redis集群保证高可用和高并发特性
- 上一篇: 深圳网站建设深圳网络免费在线图片设计制作生成器
- 下一篇: 深圳网站建设推进云服务器管理
相关文章
-
深圳网站建设深圳网络免费在线图片设计制作生成器
深圳网站建设深圳网络免费在线图片设计制作生成器
- 技术栈
- 2026年04月20日
-
深圳网站建设设计平台天津建筑工程信息平台
深圳网站建设设计平台天津建筑工程信息平台
- 技术栈
- 2026年04月20日
-
深圳网站建设软件开发公司哪家好网页制作软件电脑版
深圳网站建设软件开发公司哪家好网页制作软件电脑版
- 技术栈
- 2026年04月20日
-
深圳网站建设推进云服务器管理
深圳网站建设推进云服务器管理
- 技术栈
- 2026年04月20日
-
深圳网站建设外贸公司排名服装设计公司名称大全
深圳网站建设外贸公司排名服装设计公司名称大全
- 技术栈
- 2026年04月20日
-
深圳网站建设网络公司百度指数app
深圳网站建设网络公司百度指数app
- 技术栈
- 2026年04月20日
