文章目录
前言
本文使用Java构建三种中间件的分布式锁,下面介绍下三种分布式锁的优缺点,
使用MySQL构建分布式锁,因为数据库数据存储在磁盘中,所以IO速率相对较慢,因此构建出来的分布式锁不适合用在高并发场景,对于一些对并发要求不高的系统中可以使用,进一步提高系统的安全稳定性,提升数据库容错
使用Redis构建分布式锁,都知道redis处理数据是在内存中进行处理,执行效率很高,成为很多开发者设计分布式锁的不二之选,这里需要注意的时,redis在主从切换时,可能会出现锁丢失的情况,也是我们不可忽略的问题。
使用ZooKeeper构建分布式锁,总所周知,ZooKeeper是一个高可用的分布式协调服务,因此使用它来构建分布式锁,无疑性能是三者之中相对来说性能较好的,它可以很好的保证了数据的强一致性,但是使用ZooKeeper需要部署额外的服务,增加了系统复杂度
所以,没有最好的解决方案,只有最适合自己的解决方案。
一、分布式锁简介
分布式锁听名字就才得到,肯定时应用在分布式系统中,锁在Java技术中通常用来解决高并发访问共享资源的问题,那么分布式锁就是解决分布式系统中多个线程或者进程高并发访问共享资源的一种技术,
在分布式系统中,由于各个节点之间的网路通信延迟、故障等原因,可能会导致数据不一致的问题,分布式锁通过协调多个节点的行为,保证在任何时候只有一个节点可以访问资源,以避免数据的不一致性和冲突。
二、分布式锁要求
分布式锁通常需要满足以下几个要求:
- 互斥性:在任意时刻只能有一个客户端持有锁。
- 不会发生死锁:即使持有锁的客户端发生故障,也能保证锁最终会被释放。
- 具有容错性:分布式锁需要能够容忍节点故障等异常情况,保证系统的稳定性。
三、实现方案
在 Java 中,实现分布式锁的方案有多种,包括:
- 基于数据库实现的分布式锁:可以通过数据库的乐观锁或悲观锁实现分布式锁,但是由于数据库的 IO 操作比较慢,不适合高并发场景。
- 基于 ZooKeeper 实现的分布式锁:ZooKeeper 是一个高可用性的分布式协调服务,可以通过它来实现分布式锁。但是使用 ZooKeeper 需要部署额外的服务,增加了系统复杂度。
- 基于 Redis 实现的分布式锁:Redis 是一个高性能的内存数据库,支持分布式部署,可以通过Redis的原子操作实现分布式锁,而且具有高性能和高可用性。
四、数据库分布式锁
数据库的乐观锁或悲观锁都可以实现分布式锁,下面分别来看。
1、悲观锁
在数据库中使用 for update 关键字可以实现悲观锁,我们在 Mapper 中添加 for update 即可对数据加锁,实现代码如下:
<!-- UserMapper.xml --><selectid="selectByIdForUpdate"resultType="User">
SELECT * FROM user WHERE id = #{id} FOR UPDATE
</select>
在 Service 中调用 Mapper 方法,即可获取到加锁的数据:
@TransactionalpublicvoidupdateWithPessimisticLock(int id,String name){User user = userMapper.selectByIdForUpdate(id);if(user !=null){
user.setName(name);
userMapper.update(user);}else{thrownewRuntimeException("数据不存在");}}
2、乐观锁
在 MyBatis 中,可以通过给表添加一个版本号字段来实现乐观锁。在 Mapper 中,使用标签定义更新语句,同时使用 set 标签设置版本号的增量。
<!-- UserMapper.xml --><updateid="updateWithOptimisticLock">
UPDATE user SET
name = #{name},
version = version + 1
WHERE id = #{id} AND version = #{version}
</update>
在 Service 中调用 Mapper 方法,需要传入更新数据的版本号。如果更新失败,说明数据已经被其他事务修改,具体实现代码如下:
@TransactionalpublicvoidupdateWithOptimisticLock(int id,String name,int version){User user = userMapper.selectById(id);if(user !=null){
user.setName(name);
user.setVersion(version);int rows = userMapper.updateWithOptimisticLock(user);if(rows ==0){thrownewRuntimeException("数据已被其他事务修改");}}else{thrownewRuntimeException("数据不存在");}}
五、Zookeeper分布式锁
在 Spring Boot 中,可以使用 Curator 框架来实现 ZooKeeper 分布式锁,具体实现分为以下 3 步:
- 引入 Curator 和 ZooKeeper 客户端依赖;
- 配置 ZooKeeper 连接信息;
- 编写分布式锁实现类。
1、引入Curator和ZooKeeper
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>latest</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>latest</version></dependency><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>latest</version></dependency>
2、配置ZooKeeper连接
在 application.yml 中添加 ZooKeeper 连接配置:
spring:zookeeper:connect-string: localhost:2181namespace: demo
3、编写分布式实现类
@ComponentpublicclassDistributedLock{@AutowiredprivateCuratorFramework curatorFramework;/**
* 获取分布式锁
*
* @param lockPath 锁路径
* @param waitTime 等待时间
* @param leaseTime 锁持有时间
* @param timeUnit 时间单位
* @return 锁对象
* @throws Exception 获取锁异常
*/publicInterProcessMutexacquire(String lockPath,long waitTime,long leaseTime,TimeUnit timeUnit)throwsException{InterProcessMutex lock =newInterProcessMutex(curatorFramework, lockPath);if(!lock.acquire(waitTime, timeUnit)){thrownewRuntimeException("获取分布式锁失败");}if(leaseTime >0){
lock.acquire(leaseTime, timeUnit);}return lock;}/**
* 释放分布式锁
*
* @param lock 锁对象
* @throws Exception 释放锁异常
*/publicvoidrelease(InterProcessMutex lock)throwsException{if(lock !=null){
lock.release();}}}
六、Redis分布式锁
我们可以使用 Redis 客户端 Redisson 实现分布式锁,它的实现步骤如下:
- 添加 Redisson 依赖
- 配置 Redisson 连接信息
- 编写分布式锁代码类
1、添加Redisson依赖
在pom.xml中添加如下配置
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.20.0</version></dependency>
2、配置Redission连接
在 Spring Boot 项目的配置文件 application.yml 中添加 Redisson 配置:
spring:data:redis:host: localhost
port:6379database:0redisson:codec: org.redisson.codec.JsonJacksonCodec
single-server-config:address:"redis://${spring.data.redis.host}:${spring.redis.port}"database:"${spring.data.redis.database}"password:"${spring.data.redis.password}"
3、编写分布式锁代码类
importjakarta.annotation.Resource;importorg.redisson.Redisson;importorg.redisson.api.RLock;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;@ServicepublicclassRedissonLockService{@ResourceprivateRedisson redisson;/**
* 加锁
*
* @param key 分布式锁的 key
* @param timeout 超时时间
* @param unit 时间单位
* @return
*/publicbooleantryLock(String key,long timeout,TimeUnit unit){RLock lock = redisson.getLock(key);try{return lock.tryLock(timeout, unit);}catch(InterruptedException e){Thread.currentThread().interrupt();returnfalse;}}/**
* 释放分布式锁
*
* @param key 分布式锁的 key
*/publicvoidunlock(String key){RLock lock = redisson.getLock(key);
lock.unlock();}}
七、Redis锁与ZooKeeper分布式锁的区别
Redis 和 ZooKeeper 都可以用来实现分布式锁,它们在实现分布式锁的机制和原理上有所不同,具体区别如下:
- 数据存储方式:Redis 将锁信息存储在内存中,而 ZooKeeper 将锁信息存储在 ZooKeeper 的节点上,因此 ZooKeeper 需要更多的磁盘空间。
- 锁的释放:Redis 的锁是通过设置锁的过期时间来自动释放的,而 ZooKeeper 的锁需要手动释放,如果锁的持有者出现宕机或网络中断等情况,需要等待锁的超时时间才能自动释放。
- 锁的竞争机制:Redis 使用的是单机锁,即所有请求都直接连接到同一台 Redis 服务器,容易发生单点故障;而 ZooKeeper 使用的是分布式锁,即所有请求都连接到 ZooKeeper 集群,具有较好的可用性和可扩展性。
- 一致性:Redis 的锁是非严格意义下的分布式锁,因为在多台机器上运行多个进程时,由于 Redis 的主从同步可能会存在数据不一致的问题;而 ZooKeeper 是强一致性的分布式系统,保证了数据的一致性。
- 性能:Redis 的性能比 ZooKeeper 更高,因为 Redis 将锁信息存储在内存中,而 ZooKeeper 需要进行磁盘读写操作。
总结,Redis 适合实现简单的分布式锁场景,而 ZooKeeper 适合实现复杂的分布式协调场景,也就是 ZooKeeper 适合强一致性的分布式系统。
知识点:强一致性是指系统中的所有节点在任何时刻看到的数据都是一致的。ZooKeeper
中的数据是有序的树形结构,每个节点都有唯一的路径标识符,所有节点都共享同一份数据,当任何一个节点对数据进行修改时,所有节点都会收到通知,更新数据,并确保数据的一致性。在
ZooKeeper 中,强一致性体现在数据的读写操作上。ZooKeeper 使用 ZAB(ZooKeeper Atomic
Broadcast)协议来保证数据的一致性,该协议确保了数据更新的顺序,所有的数据更新都需要经过集群中的大多数节点确认,保证了数据的一致性和可靠性。
备注
本文根据51CTO博主磊哥文章一步步操作实现之后,做的笔记
原文连接:https://www.51cto.com/article/766555.html
版权归原作者 三两肉 所有, 如有侵权,请联系我们删除。