1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-14 05:21:42 +08:00

Compare commits

..

3 Commits

Author SHA1 Message Date
guide
1d350536fc [docs update]Redisson 看门狗原理 2022-09-01 15:49:37 +08:00
guide
e29154b0e8 [docs update]Web 实时消息推送详解原文地址 2022-09-01 11:43:35 +08:00
guide
fdc888af26 [docs update]完善对分布式锁的解释 2022-09-01 11:29:56 +08:00
7 changed files with 104 additions and 7 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
/node_modules /node_modules
/package-lock.json /package-lock.json
*.drawio
.DS_Store .DS_Store
# VS Code Config file # VS Code Config file
.vscode/ .vscode/

View File

@ -6,7 +6,6 @@ tag:
--- ---
> 原文地址https://shockerli.net/post/1000-line-mysql-note/ JavaGuide 对本文进行了简答排版,新增了目录。 > 原文地址https://shockerli.net/post/1000-line-mysql-note/ JavaGuide 对本文进行了简答排版,新增了目录。
> 作者:格物
非常不错的总结,强烈建议保存下来,需要的时候看一看。 非常不错的总结,强烈建议保存下来,需要的时候看一看。

View File

@ -7,7 +7,23 @@ category: 分布式
## 什么是分布式锁? ## 什么是分布式锁?
对于单机多线程,在 Java 中,我们通常使用 `ReetrantLock` 这类 JDK 自带的 **本地锁** 来控制本地多个线程对本地共享资源的访问。对于分布式系统,我们通常使用 **分布式锁** 来控制多个服务对共享资源的访问。 对于单机多线程来说,在 Java 中,我们通常使用 `ReetrantLock` 类、`synchronized` 关键字这类 JDK 自带的 **本地锁** 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。
下面是我对本地锁画的一张示意图。
![本地锁](./images/distributed-lock/jvm-local-lock.png)
从图中可以看出,这些线程访问共享资源是互斥的,同一时刻只有一个线程可以获取到本地锁访问共享资源。
分布式系统下,不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。于是,**分布式锁** 就诞生了。
举个例子:系统的订单服务一共部署了 3 份,都对外提供服务。用户下订单之前需要检查库存,为了防止超卖,这里需要加锁以实现对检查库存操作的同步访问。由于订单服务位于不同的 JVM 进程中,本地锁在这种情况下就没办法正常工作了。我们需要用到分布式锁,这样的话,即使多个线程不在同一个 JVM 进程中也能获取到同一把锁,进而实现共享资源的互斥访问。
下面是我对分布式锁画的一张示意图。
![分布式锁](./images/distributed-lock/distributed-lock.png)
从图中可以看出,这些独立的进程中的线程访问共享资源是互斥的,同一时刻只有一个线程可以获取到分布式锁访问共享资源。
一个最基本的分布式锁需要满足: 一个最基本的分布式锁需要满足:
@ -51,6 +67,8 @@ else
end end
``` ```
![Redis 实现简易分布式锁](images/distributed-lock/distributed-lock-setnx.png)
这是一种最简易的 Redis 分布式锁实现,实现方式比较简单,性能也很高效。不过,这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉,可能会导致锁无法被释放,进而造成共享资源无法再被其他线程/进程访问。 这是一种最简易的 Redis 分布式锁实现,实现方式比较简单,性能也很高效。不过,这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉,可能会导致锁无法被释放,进而造成共享资源无法再被其他线程/进程访问。
### 为什么要给锁设置一个过期时间? ### 为什么要给锁设置一个过期时间?
@ -73,20 +91,92 @@ OK
你或许在想: **如果操作共享资源的操作还未完成,锁过期时间能够自己续期就好了!** 你或许在想: **如果操作共享资源的操作还未完成,锁过期时间能够自己续期就好了!**
### 如何实现锁的优雅续期?
对于 Java 开发的小伙伴来说,已经有了现成的解决方案:**[Redisson](https://github.com/redisson/redisson)** 。其他语言的解决方案,可以在 Redis 官方文档中找到地址https://redis.io/topics/distlock 。 对于 Java 开发的小伙伴来说,已经有了现成的解决方案:**[Redisson](https://github.com/redisson/redisson)** 。其他语言的解决方案,可以在 Redis 官方文档中找到地址https://redis.io/topics/distlock 。
![Distributed locks with Redis](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/redis-distributed-lock.png) ![Distributed locks with Redis](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/redis-distributed-lock.png)
Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。 Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。并且Redisson 还支持 Redis 单机、Redis Sentinel 、Redis Cluster 等多种部署架构。
Redisson 中的分布式锁自带自动续期机制,它提供了一个专门用来监控锁的 **Watch Dog 看门狗)**如果操作共享资源的还未完成的话Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。 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 的分布式可重入锁 `RLock` 为例来说明如何使用 Redisson 实现分布式锁: 我这里以 Redisson 的分布式可重入锁 `RLock` 为例来说明如何使用 Redisson 实现分布式锁:
```java ```java
// 1.获取指定的分布式锁对象 // 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock"); RLock lock = redisson.getLock("lock");
// 2.拿锁,具有 Watch Dog 自动续期机制 // 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
lock.lock(); lock.lock();
// 3.执行业务 // 3.执行业务
... ...
@ -94,7 +184,12 @@ lock.lock();
lock.unlock(); lock.unlock();
``` ```
可以看出,代码非常简洁直观。 只有未指定锁超时时间,才会使用到 Watch Dog 自动续期机制。
```java
// 手动给锁设置过期时间,不具备 Watch Dog 自动续期机制
lock.lock(10, TimeUnit.SECONDS);
```
如果使用 Redis 来实现分布式锁的话,还是比较推荐直接基于 Redisson 来做的。 如果使用 Redis 来实现分布式锁的话,还是比较推荐直接基于 Redisson 来做的。
@ -112,7 +207,7 @@ Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redi
Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。 Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。
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 实现比较复杂,性能比较差,发生时钟变迁的情况下还存在安全性隐患。《数据密集型应用系统设计》一书的作者 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 算法,成本和收益不成正比。 实际项目中不建议使用 Redlock 算法,成本和收益不成正比。

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -10,6 +10,8 @@ head:
content: 消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。 content: 消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。
--- ---
> 原文地址https://juejin.cn/post/7122014462181113887JavaGuide 对本文进行了完善总结。
我有一个朋友做了一个小破站,现在要实现一个站内信 Web 消息推送的功能,对,就是下图这个小红点,一个很常用的功能。 我有一个朋友做了一个小破站,现在要实现一个站内信 Web 消息推送的功能,对,就是下图这个小红点,一个很常用的功能。
![站内信 Web 消息推送](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/system-design/web-real-time-message-push/1460000042192380.png) ![站内信 Web 消息推送](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/system-design/web-real-time-message-push/1460000042192380.png)