mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-08-14 05:21:42 +08:00
Compare commits
No commits in common. "1d350536fc4d5de49cf76973a94e690f3cad1759" and "9a321c7504c4bffb3fc247e9af4148c4d5f7cbe3" have entirely different histories.
1d350536fc
...
9a321c7504
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
*.drawio
|
||||
.DS_Store
|
||||
# VS Code Config file
|
||||
.vscode/
|
||||
|
@ -6,6 +6,7 @@ tag:
|
||||
---
|
||||
|
||||
> 原文地址:https://shockerli.net/post/1000-line-mysql-note/ ,JavaGuide 对本文进行了简答排版,新增了目录。
|
||||
> 作者:格物
|
||||
|
||||
非常不错的总结,强烈建议保存下来,需要的时候看一看。
|
||||
|
||||
|
@ -7,23 +7,7 @@ category: 分布式
|
||||
|
||||
## 什么是分布式锁?
|
||||
|
||||
对于单机多线程来说,在 Java 中,我们通常使用 `ReetrantLock` 类、`synchronized` 关键字这类 JDK 自带的 **本地锁** 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。
|
||||
|
||||
下面是我对本地锁画的一张示意图。
|
||||
|
||||

|
||||
|
||||
从图中可以看出,这些线程访问共享资源是互斥的,同一时刻只有一个线程可以获取到本地锁访问共享资源。
|
||||
|
||||
分布式系统下,不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。于是,**分布式锁** 就诞生了。
|
||||
|
||||
举个例子:系统的订单服务一共部署了 3 份,都对外提供服务。用户下订单之前需要检查库存,为了防止超卖,这里需要加锁以实现对检查库存操作的同步访问。由于订单服务位于不同的 JVM 进程中,本地锁在这种情况下就没办法正常工作了。我们需要用到分布式锁,这样的话,即使多个线程不在同一个 JVM 进程中也能获取到同一把锁,进而实现共享资源的互斥访问。
|
||||
|
||||
下面是我对分布式锁画的一张示意图。
|
||||
|
||||

|
||||
|
||||
从图中可以看出,这些独立的进程中的线程访问共享资源是互斥的,同一时刻只有一个线程可以获取到分布式锁访问共享资源。
|
||||
对于单机多线程,在 Java 中,我们通常使用 `ReetrantLock` 这类 JDK 自带的 **本地锁** 来控制本地多个线程对本地共享资源的访问。对于分布式系统,我们通常使用 **分布式锁** 来控制多个服务对共享资源的访问。
|
||||
|
||||
一个最基本的分布式锁需要满足:
|
||||
|
||||
@ -67,8 +51,6 @@ else
|
||||
end
|
||||
```
|
||||
|
||||

|
||||
|
||||
这是一种最简易的 Redis 分布式锁实现,实现方式比较简单,性能也很高效。不过,这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉,可能会导致锁无法被释放,进而造成共享资源无法再被其他线程/进程访问。
|
||||
|
||||
### 为什么要给锁设置一个过期时间?
|
||||
@ -91,92 +73,20 @@ OK
|
||||
|
||||
你或许在想: **如果操作共享资源的操作还未完成,锁过期时间能够自己续期就好了!**
|
||||
|
||||
### 如何实现锁的优雅续期?
|
||||
|
||||
对于 Java 开发的小伙伴来说,已经有了现成的解决方案:**[Redisson](https://github.com/redisson/redisson)** 。其他语言的解决方案,可以在 Redis 官方文档中找到,地址:https://redis.io/topics/distlock 。
|
||||
|
||||

|
||||
|
||||
Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。并且,Redisson 还支持 Redis 单机、Redis Sentinel 、Redis Cluster 等多种部署架构。
|
||||
Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。
|
||||
|
||||
Redisson 中的分布式锁自带自动续期机制,使用起来非常简单,原理也比较简单,其提供了一个专门用来监控和续期锁的 **Watch Dog( 看门狗)**,如果操作共享资源的线程还未执行完成的话,Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。
|
||||
|
||||
看门狗名字的由来于 `getLockWatchdogTimeou()` 方法,这个方法返回的是看门狗给锁续期的过期时间,默认为 30 秒([redisson-3.17.6](https://github.com/redisson/redisson/releases/tag/redisson-3.17.6))。
|
||||
|
||||
```java
|
||||
//默认 30秒,支持修改
|
||||
private long lockWatchdogTimeout = 30 * 1000;
|
||||
|
||||
public Config setLockWatchdogTimeout(long lockWatchdogTimeout) {
|
||||
this.lockWatchdogTimeout = lockWatchdogTimeout;
|
||||
return this;
|
||||
}
|
||||
public long getLockWatchdogTimeout() {
|
||||
return lockWatchdogTimeout;
|
||||
}
|
||||
```
|
||||
|
||||
`renewExpiration()` 方法包含了看门狗的主要逻辑:
|
||||
|
||||
```java
|
||||
private void renewExpiration() {
|
||||
//......
|
||||
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
|
||||
@Override
|
||||
public void run(Timeout timeout) throws Exception {
|
||||
//......
|
||||
// 异步续期,基于 Lua 脚本
|
||||
CompletionStage<Boolean> future = renewExpirationAsync(threadId);
|
||||
future.whenComplete((res, e) -> {
|
||||
if (e != null) {
|
||||
// 无法续期
|
||||
log.error("Can't update lock " + getRawName() + " expiration", e);
|
||||
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (res) {
|
||||
// 递归调用实现续期
|
||||
renewExpiration();
|
||||
} else {
|
||||
// 取消续期
|
||||
cancelExpirationRenewal(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 延迟 internalLockLeaseTime/3(默认 10s,也就是 30/3) 再调用
|
||||
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
|
||||
|
||||
ee.setTimeout(task);
|
||||
}
|
||||
```
|
||||
|
||||
默认情况下,每过 10 秒,看门狗就会执行续期操作,将锁的超时时间设置为 30 秒。看门狗续期前也会先判断是否需要执行续期操作,需要才会执行续期,否则取消续期操作。
|
||||
|
||||
Watch Dog 通过调用 `renewExpirationAsync()` 方法实现锁的异步续期:
|
||||
|
||||
```java
|
||||
protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {
|
||||
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
|
||||
// 如果指定的锁存在就续期,将其过期时间设置为 30s(默认)
|
||||
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
|
||||
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
|
||||
"return 1; " +
|
||||
"end; " +
|
||||
"return 0;",
|
||||
Collections.singletonList(getRawName()),
|
||||
internalLockLeaseTime, getLockName(threadId));
|
||||
}
|
||||
```
|
||||
|
||||
可以看出, `renewExpirationAsync` 方法其实是调用 Lua 脚本实现的续期,这样做主要是为了保证续期操作的原子性。
|
||||
Redisson 中的分布式锁自带自动续期机制,它提供了一个专门用来监控锁的 **Watch Dog( 看门狗)**,如果操作共享资源的还未完成的话,Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。
|
||||
|
||||
我这里以 Redisson 的分布式可重入锁 `RLock` 为例来说明如何使用 Redisson 实现分布式锁:
|
||||
|
||||
```java
|
||||
// 1.获取指定的分布式锁对象
|
||||
RLock lock = redisson.getLock("lock");
|
||||
// 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
|
||||
// 2.拿锁,具有 Watch Dog 自动续期机制
|
||||
lock.lock();
|
||||
// 3.执行业务
|
||||
...
|
||||
@ -184,12 +94,7 @@ lock.lock();
|
||||
lock.unlock();
|
||||
```
|
||||
|
||||
只有未指定锁超时时间,才会使用到 Watch Dog 自动续期机制。
|
||||
|
||||
```java
|
||||
// 手动给锁设置过期时间,不具备 Watch Dog 自动续期机制
|
||||
lock.lock(10, TimeUnit.SECONDS);
|
||||
```
|
||||
可以看出,代码非常简洁直观。
|
||||
|
||||
如果使用 Redis 来实现分布式锁的话,还是比较推荐直接基于 Redisson 来做的。
|
||||
|
||||
@ -207,7 +112,7 @@ Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redi
|
||||
|
||||
Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。
|
||||
|
||||
Redlock 实现比较复杂,性能比较差,发生时钟变迁的情况下还存在安全性隐患。《数据密集型应用系统设计》一书的作者 Martin Kleppmann 曾经专门发文([How to do distributed locking - Martin Kleppmann - 2016](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html))怼过 Redlock,他认为这是一个很差的分布式锁实现。感兴趣的朋友可以看看[Redis 锁从面试连环炮聊到神仙打架](https://mp.weixin.qq.com/s?__biz=Mzg3NjU3NTkwMQ==&mid=2247505097&idx=1&sn=5c03cb769c4458350f4d4a321ad51f5a&source=41#wechat_redirect)这篇文章,有详细介绍到 antirez 和 Martin Kleppmann 关于 Redlock 的激烈辩论。
|
||||
Redlock 实现比较复杂,性能比较差,发生时钟变迁的情况下还存在安全性隐患。《数据密集型应用系统设计》一书的作者 Martin Kleppmann 曾经专门发文怼过 Redlock,他认为这是一个很差的分布式锁实现。感兴趣的朋友可以看看[Redis 锁从面试连环炮聊到神仙打架](https://mp.weixin.qq.com/s?__biz=Mzg3NjU3NTkwMQ==&mid=2247505097&idx=1&sn=5c03cb769c4458350f4d4a321ad51f5a&source=41#wechat_redirect)这篇文章,有详细介绍到 antirez 和 Martin Kleppmann 关于 Redlock 的激烈辩论。
|
||||
|
||||
实际项目中不建议使用 Redlock 算法,成本和收益不成正比。
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
@ -10,8 +10,6 @@ head:
|
||||
content: 消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。
|
||||
---
|
||||
|
||||
> 原文地址:https://juejin.cn/post/7122014462181113887,JavaGuide 对本文进行了完善总结。
|
||||
|
||||
我有一个朋友做了一个小破站,现在要实现一个站内信 Web 消息推送的功能,对,就是下图这个小红点,一个很常用的功能。
|
||||
|
||||

|
||||
|
Loading…
x
Reference in New Issue
Block a user