分布式 - 分布式锁
目录
Redisson客户端与SpringBoot整合
分布式锁流行的三种方式与分析
实现原理
关系型数据库实现
- SELECT lock_name FROM lock WHERE lock_name = 'order' FOR UPDATE;
- for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行.如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。
- 也就是说,在业务中,FOR UPDATE 标记该查询已加锁,当前查询上了锁,只能串行,当前操作结束后,其他查询数据的请求才可以执行,利用这一点,将该操作与写操作进行捆绑,即达到分布式锁的目的。
Redis实现
#格式:SETNX KEY VALUE
setnx keyname value
#格式:SET KEY VALUE [EX SECONDS] [PX MILLISECODS] [NX|XX]
set keyname value ex 100 nx
- setnx命令,如果有该key值,则设置失败;没有该key设置成功。但setnx命令没有过期时间
- 需要额外对key设置过期时间,但是这是两种不同意义的操作操作,不能保证其原子性
- “SET KEY VALUE [EX SECONDS] [PX MILLISECODS] [NX|XX]”与“SETNX KEY VALUE”意义基本相通,只是多了个过期时间
- 也就是说,在业务中,先为一个指定的key赋值,如果成功赋值,就代表抢占了锁,与写操作进行捆绑,事务结束后释放锁即可,即使服务宕机没来得及释放锁,缓存过期后也会自动释放。
Zookeeper实现
- Zookeeper临时节点是有序的且不允许有父节点,watch可以对节点监视,而且居3.6.0 中的新功能:客户端还可以在 znode 上设置永久的递归监视,这些监视在触发时不会被删除,并且会以递归方式触发注册的 znode 以及任何子 znode 上的更改
- 也就是说,在业务中,执行时先到 Zookeeper 创建一个父节点,区分锁的用途,然后在该节点下创建临时节点,临时节点都是有序的,watch特性获取该父节点下的所有子节点,对比自己创建的节点在所有节点中是否是第一个,是就执行业务,否就等watch监视特性监视前一个节点,当前一个节点消失,自己是第一个节点时执行,执行时与写操作捆绑,执行结束后删除节点。
自动释放锁
- 例如 redis 与 Zookeeper 封装获取锁的实体类,获取锁的实体类实现 AutoCloseable接口,接口中就一个 close 关闭方法,只需在 close 中补全自己的逻辑操作即可
- 表示该对象生命周期结束,自动执行关闭操作
- AutoCloseable类解释:一个可能持有资源(例如文件或套接字句柄)直到关闭的对象。 当退出在资源规范标头中声明了对象的-with-resources 块时,将自动调用对象的close()方法。这种构造确保及时释放,避免资源耗尽异常和否则可能发生的错误。AutoCloseabletry,当然也可以封装IO操作
Redisson客户端与SpringBoot整合
- maven依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
- 方法一
- application.yml
redis:
host: 192.168.50.201
port: 6379
timeout: 1000
database: 0
password: chenyb111
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
- 方法二
- application.yml
spring:
redis:
redisson:
# 配置单点模式
config: classpath:redisson.yml
jedis:
pool:
max-active: 8 #最大连接数
max-wait: -1 #最大阻塞等待时间(负数表示没限制)
max-idle: 8 #最大空闲
- redisson.yml
# 单节点配置
singleServerConfig:
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 连接超时,单位:毫秒
connectTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。
# 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
retryAttempts: 3
# 命令重试发送时间间隔,单位:毫秒
retryInterval: 1500
# # 重新连接时间间隔,单位:毫秒
# reconnectionTimeout: 3000
# # 执行失败最大次数
# failedAttempts: 3
# 密码
password:
# 单个连接最大订阅数量
subscriptionsPerConnection: 5
# 客户端名称
clientName: null
# # 节点地址
address: redis://ip
# 发布和订阅连接的最小空闲连接数
subscriptionConnectionMinimumIdleSize: 1
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# 数据库编号
database: 0
# DNS监测时间间隔,单位:毫秒
dnsMonitoringInterval: 5000
- 对象注入即可
@Autowired
privete RedissonClient redissonClient
public viod redisLockDemo (){
//获取锁
redissonClient.getLock("keyName").tryLock(0L,-1L, TimeUnit.SECONDS);
//释放锁
redissonClient.getLock("keyName").unlock();
}
Curator客户端与SpringBoot整合
- maven依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
</dependency>
- 创建对象即可
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3)
CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
client.start();
InterProcessMutex lock = new InterProcessMutex(client, "ZK父级目录");
if ( lock.acquire("有效时间", "TimeUnit.SECONDS时间单位") )
{
try
{ //获得锁
// do some work inside of the critical section here
}
finally
{
//释放锁
lock.release();
}
}
- 可以将创建client用Bean封装
- @Autowired注入即可