
@@ -26,27 +27,27 @@ GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专
因为刚刚推出公众号,目前有以下几篇文章,来先睹为快吧:
-* [硬核!亿级流量秒杀系统设计](https://mp.weixin.qq.com/s/Mo_knIRBQQL2s-D2aieZLg)
-* [技术面试是否要看面经?面试官/面试者有话说!](https://mp.weixin.qq.com/s/fNiUmbY395rsPdEC0QDIrw)
-* [如何破解极验滑动验证码?成功率 100%!](https://mp.weixin.qq.com/s/Fsl6qYN5Dw4s6Du893MkFQ)
-* [免费且好用的图床,就你了,「图壳」!](https://mp.weixin.qq.com/s/0HhgHLo_tTRFZcC-CVjDbw)
-* [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson!](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow)
-* [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw)
-* [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA)
-* [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12!](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw)
-* [Google 搜索的即时自动补全功能究竟是如何“工作”的?](https://mp.weixin.qq.com/s/YlMISSc3Sn890BzTLytcLA)
-* [厉害了,原来 Redisson 这么好用!](https://mp.weixin.qq.com/s/lpZ7eRdImy0MyTEVH68HYw)
-* [一文带你搞懂 “缓存策略”](https://mp.weixin.qq.com/s/47A_iXY_nArURwUTPHr2IQ)
-* [Java Getter/Setter “防坑指南”](https://mp.weixin.qq.com/s/TZqcAw7NTlcvU-p930-eHA)
-* [太棒了,GitHub Review 代码能力小升级](https://mp.weixin.qq.com/s/Lok0epqn91Q51ygZo_FLkg)
-* [巧用 Redis Hyperloglog,轻松统计 UV 数据](https://mp.weixin.qq.com/s/w1r-M6YVvQSfUtzO_xe44Q)
-* [如何开启「GitHub+码云」双工作流模式?](https://mp.weixin.qq.com/s/byxAjr3-ifWfDYQcR7YA8Q)
+- [硬核!亿级流量秒杀系统设计](https://mp.weixin.qq.com/s/Mo_knIRBQQL2s-D2aieZLg)
+- [技术面试是否要看面经?面试官/面试者有话说!](https://mp.weixin.qq.com/s/fNiUmbY395rsPdEC0QDIrw)
+- [如何破解极验滑动验证码?成功率 100%!](https://mp.weixin.qq.com/s/Fsl6qYN5Dw4s6Du893MkFQ)
+- [免费且好用的图床,就你了,「图壳」!](https://mp.weixin.qq.com/s/0HhgHLo_tTRFZcC-CVjDbw)
+- [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson!](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow)
+- [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw)
+- [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA)
+- [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12!](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw)
+- [Google 搜索的即时自动补全功能究竟是如何“工作”的?](https://mp.weixin.qq.com/s/YlMISSc3Sn890BzTLytcLA)
+- [厉害了,原来 Redisson 这么好用!](https://mp.weixin.qq.com/s/lpZ7eRdImy0MyTEVH68HYw)
+- [一文带你搞懂 “缓存策略”](https://mp.weixin.qq.com/s/47A_iXY_nArURwUTPHr2IQ)
+- [Java Getter/Setter “防坑指南”](https://mp.weixin.qq.com/s/TZqcAw7NTlcvU-p930-eHA)
+- [太棒了,GitHub Review 代码能力小升级](https://mp.weixin.qq.com/s/Lok0epqn91Q51ygZo_FLkg)
+- [巧用 Redis Hyperloglog,轻松统计 UV 数据](https://mp.weixin.qq.com/s/w1r-M6YVvQSfUtzO_xe44Q)
+- [如何开启「GitHub+码云」双工作流模式?](https://mp.weixin.qq.com/s/byxAjr3-ifWfDYQcR7YA8Q)
后续将推出一系列原创干货文章,敬请期待。
## 是否有交流群?
-有的,目前微信群“**Doocs的技术朋友们**”已经接近 200 号人,如果你希望加入,请通过微信与我联系。
+有的,目前微信群“**Doocs 的技术朋友们**”已经接近 200 号人,如果你希望加入,请通过微信与我联系。
**注意**,群内禁止一切垃圾广告信息,包括小程序助力信息、小游戏、社群推广、公众号推广、支付宝推广码等;交流 GitHub、开发相关,可以自由分享一些开发相关知识,但不提倡整天水群,建议还是多花点时间提升自己。
diff --git a/docs/high-availability/README.md b/docs/high-availability/README.md
index 0c10a2d40..296b2dfec 100644
--- a/docs/high-availability/README.md
+++ b/docs/high-availability/README.md
@@ -1,48 +1,52 @@
# 高可用架构
-- [Hystrix 介绍](./hystrix-introduction.md)
-- [电商网站详情页系统架构](./e-commerce-website-detail-page-architecture.md)
-- [Hystrix 线程池技术实现资源隔离](./hystrix-thread-pool-isolation.md)
-- [Hystrix 信号量机制实现资源隔离](./hystrix-semphore-isolation.md)
-- [Hystrix 隔离策略细粒度控制](./hystrix-execution-isolation.md)
-- [深入 Hystrix 执行时内部原理](./hystrix-process.md)
-- [基于 request cache 请求缓存技术优化批量商品数据查询接口](./hystrix-request-cache.md)
-- [基于本地缓存的 fallback 降级机制](./hystrix-fallback.md)
-- [深入 Hystrix 断路器执行原理](./hystrix-circuit-breaker.md)
-- [深入 Hystrix 线程池隔离与接口限流](./hystrix-thread-pool-current-limiting.md)
-- [基于 timeout 机制为服务接口调用超时提供安全保护](./hystrix-timeout.md)
+
+- [Hystrix 介绍](./hystrix-introduction.md)
+- [电商网站详情页系统架构](./e-commerce-website-detail-page-architecture.md)
+- [Hystrix 线程池技术实现资源隔离](./hystrix-thread-pool-isolation.md)
+- [Hystrix 信号量机制实现资源隔离](./hystrix-semphore-isolation.md)
+- [Hystrix 隔离策略细粒度控制](./hystrix-execution-isolation.md)
+- [深入 Hystrix 执行时内部原理](./hystrix-process.md)
+- [基于 request cache 请求缓存技术优化批量商品数据查询接口](./hystrix-request-cache.md)
+- [基于本地缓存的 fallback 降级机制](./hystrix-fallback.md)
+- [深入 Hystrix 断路器执行原理](./hystrix-circuit-breaker.md)
+- [深入 Hystrix 线程池隔离与接口限流](./hystrix-thread-pool-current-limiting.md)
+- [基于 timeout 机制为服务接口调用超时提供安全保护](./hystrix-timeout.md)
## 高可用系统
-- 如何设计一个高可用系统?
+
+- 如何设计一个高可用系统?
## 限流
-- 如何限流?在工作中是怎么做的?说一下具体的实现?
+
+- 如何限流?在工作中是怎么做的?说一下具体的实现?
## 熔断
-- 如何进行熔断?
-- 熔断框架都有哪些?具体实现原理知道吗?
-- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](/docs/high-availability/sentinel-vs-hystrix.md)
+
+- 如何进行熔断?
+- 熔断框架都有哪些?具体实现原理知道吗?
+- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](./sentinel-vs-hystrix.md)
## 降级
-- 如何进行降级?
+
+- 如何进行降级?
---
## 公众号
-GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
+
+[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
\ No newline at end of file
+
+
+ 
+ |
+
+ 
+ |
+
+
+
+关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
+
+

diff --git a/docs/high-availability/e-commerce-website-detail-page-architecture.md b/docs/high-availability/e-commerce-website-detail-page-architecture.md
index 0978f5f46..b9a27bc8f 100644
--- a/docs/high-availability/e-commerce-website-detail-page-architecture.md
+++ b/docs/high-availability/e-commerce-website-detail-page-architecture.md
@@ -1,6 +1,7 @@
-## 电商网站的商品详情页系统架构
+# 电商网站的商品详情页系统架构
+
+## 小型电商网站的商品详情页系统架构
-### 小型电商网站的商品详情页系统架构
小型电商网站的页面展示采用页面全量静态化的思想。数据库中存放了所有的商品信息,页面静态化系统,将数据填充进静态模板中,形成静态化页面,推入 Nginx 服务器。用户浏览网站页面时,取用一个已经静态化好的 html 页面,直接返回回去,不涉及任何的业务逻辑处理。

@@ -10,8 +11,8 @@
```html
- 商品名称:#{productName}
- 商品价格:#{productPrice}
+ 商品名称:#{productName}
+ 商品价格:#{productPrice}
商品描述:#{productDesc}
@@ -23,18 +24,18 @@
**坏处**在于,仅仅适用于一些小型的网站,比如页面的规模在几十到几万不等。对于一些大型的电商网站,亿级数量的页面,你说你每次页面模板修改了,都需要将这么多页面全量静态化,靠谱吗?每次渲染花个好几天时间,那你整个网站就废掉了。
-### 大型电商网站的商品详情页系统架构
+## 大型电商网站的商品详情页系统架构
+
大型电商网站商品详情页的系统设计中,当商品数据发生变更时,会将变更消息压入 MQ 消息队列中。**缓存服务**从消息队列中消费这条消息时,感知到有数据发生变更,便通过调用数据服务接口,获取变更后的数据,然后将整合好的数据推送至 redis 中。Nginx 本地缓存的数据是有一定的时间期限的,比如说 10 分钟,当数据过期之后,它就会从 redis 获取到最新的缓存数据,并且缓存到自己本地。
用户浏览网页时,动态将 Nginx 本地数据渲染到本地 html 模板并返回给用户。

-
虽然没有直接返回 html 页面那么快,但是因为数据在本地缓存,所以也很快,其实耗费的也就是动态渲染一个 html 页面的性能。如果 html 模板发生了变更,不需要将所有的页面重新静态化,也不需要发送请求,没有网络请求的开销,直接将数据渲染进最新的 html 页面模板后响应即可。
在这种架构下,我们需要**保证系统的高可用性**。
如果系统访问量很高,Nginx 本地缓存过期失效了,redis 中的缓存也被 LRU 算法给清理掉了,那么会有较高的访问量,从缓存服务调用商品服务。但如果此时商品服务的接口发生故障,调用出现了延时,缓存服务全部的线程都被这个调用商品服务接口给耗尽了,每个线程去调用商品服务接口的时候,都会卡住很长时间,后面大量的请求过来都会卡在那儿,此时缓存服务没有足够的线程去调用其它一些服务的接口,从而导致整个大量的商品详情页无法正常显示。
-这其实就是一个商品接口服务故障导致缓存服务资源耗尽的现象。
\ No newline at end of file
+这其实就是一个商品接口服务故障导致缓存服务资源耗尽的现象。
diff --git a/docs/high-availability/hystrix-circuit-breaker.md b/docs/high-availability/hystrix-circuit-breaker.md
index 9ca3f1b07..593b67a66 100644
--- a/docs/high-availability/hystrix-circuit-breaker.md
+++ b/docs/high-availability/hystrix-circuit-breaker.md
@@ -1,4 +1,4 @@
-## 深入 Hystrix 断路器执行原理
+# 深入 Hystrix 断路器执行原理
### 状态机
@@ -7,8 +7,8 @@ Hystrix 断路器有三种状态,分别是关闭(Closed)、打开(Open

1. `Closed` 断路器关闭:调用下游的请求正常通过
-2. `Open` 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑
-3. `Half-Open` 断路器处于半开状态:[SleepWindowInMilliseconds](#circuitBreaker.sleepWindowInMilliseconds)
+1. `Open` 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑
+1. `Half-Open` 断路器处于半开状态:[SleepWindowInMilliseconds](#circuitBreaker.sleepWindowInMilliseconds)
### [Enabled](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerenabled)
@@ -77,11 +77,12 @@ Hystrix 并不是只要有一条请求经过就去统计,而是将整个滑动
## 实例 Demo
### HystrixCommand 配置参数
+
在 GetProductInfoCommand 中配置 Setter 断路器相关参数。
-- 滑动窗口中,最少 20 个请求,才可能触发断路。
-- 异常比例达到 40% 时,才触发断路。
-- 断路后 3000ms 内,所有请求都被 reject,直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。
+- 滑动窗口中,最少 20 个请求,才可能触发断路。
+- 异常比例达到 40% 时,才触发断路。
+- 断路后 3000ms 内,所有请求都被 reject,直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。
run() 方法中,我们判断一下 productId 是否为 -1,是的话,直接抛出异常。这么写,我们之后测试的时候就可以传入 productId=-1,**模拟服务执行异常**了。
@@ -132,6 +133,7 @@ public class GetProductInfoCommand extends HystrixCommand
{
```
### 断路测试类
+
我们在测试类中,前 30 次请求,传入 productId=-1,然后休眠 3s,之后 70 次请求,传入 productId=1。
```java
@@ -163,7 +165,7 @@ public class CircuitBreakerTest {
测试结果,我们可以明显看出系统断路与恢复的整个过程。
-```c
+```java
调用接口查询商品数据,productId=-1
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
// ...
@@ -184,7 +186,7 @@ ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, spe
前 30 次请求,我们传入的 productId 为 -1,所以服务执行过程中会抛出异常。我们设置了最少 20 次请求通过断路器并且异常比例超出 40% 就触发断路。因此执行了 21 次接口调用,每次都抛异常并且走降级,21 次过后,断路器就被打开了。
-之后的 9 次请求,都不会执行 run() 方法,也就不会打印以下信息。
+之后的 9 次请求,都不会执行 `run()` 方法,也就不会打印以下信息。
```c
调用接口查询商品数据,productId=-1
@@ -197,4 +199,4 @@ ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, spe
### 参考内容
1. [Hystrix issue 1459](https://github.com/Netflix/Hystrix/issues/1459)
-2. [Hystrix Metrics](https://github.com/Netflix/Hystrix/wiki/Configuration#metrics)
\ No newline at end of file
+1. [Hystrix Metrics](https://github.com/Netflix/Hystrix/wiki/Configuration#metrics)
diff --git a/docs/high-availability/hystrix-execution-isolation.md b/docs/high-availability/hystrix-execution-isolation.md
index 80b8f1e75..e96da1c02 100644
--- a/docs/high-availability/hystrix-execution-isolation.md
+++ b/docs/high-availability/hystrix-execution-isolation.md
@@ -1,12 +1,14 @@
-## Hystrix 隔离策略细粒度控制
+# Hystrix 隔离策略细粒度控制
+
Hystrix 实现资源隔离,有两种策略:
-- 线程池隔离
-- 信号量隔离
+- 线程池隔离
+- 信号量隔离
对资源隔离这一块东西,其实可以做一定细粒度的一些控制。
-### execution.isolation.strategy
+## execution.isolation.strategy
+
指定了 HystrixCommand.run() 的资源隔离策略:`THREAD` or `SEMAPHORE`,一种基于线程池,一种基于信号量。
```java
@@ -27,13 +29,15 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat
而使用信号量的场景,通常是针对超大并发量的场景下,每个服务实例每秒都几百的 `QPS`,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护。一般用信号量常见于那种基于纯内存的一些业务逻辑服务,而不涉及到任何网络访问请求。
-### command key & command group
+## command key & command group
+
我们使用线程池隔离,要怎么对**依赖服务**、**依赖服务接口**、**线程池**三者做划分呢?
每一个 command,都可以设置一个自己的名称 command key,同时可以设置一个自己的组 command group。
+
```java
private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
- .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
+ .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
public CommandHelloWorld(String name) {
super(cachedSetter);
@@ -43,10 +47,12 @@ public CommandHelloWorld(String name) {
command group 是一个非常重要的概念,默认情况下,就是通过 command group 来定义一个线程池的,而且还会通过 command group 来聚合一些监控和报警信息。同一个 command group 中的请求,都会进入同一个线程池中。
-### command thread pool
+## command thread pool
+
ThreadPoolKey 代表了一个 HystrixThreadPool,用来进行统一监控、统计、缓存。默认的 ThreadPoolKey 就是 command group 的名称。每个 command 都会跟它的 ThreadPoolKey 对应的 ThreadPool 绑定在一起。
如果不想直接用 command group,也可以手动设置 ThreadPool 的名称。
+
```java
private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
@@ -58,7 +64,8 @@ public CommandHelloWorld(String name) {
}
```
-### command key & command group & command thread pool
+## command key & command group & command thread pool
+
**command key** ,代表了一类 command,一般来说,代表了下游依赖服务的某个接口。
**command group** ,代表了某一个下游依赖服务,这是很合理的,一个依赖服务可能会暴露出来多个接口,每个接口就是一个 command key。command group 在逻辑上对一堆 command key 的调用次数、成功次数、timeout 次数、失败次数等进行统计,可以看到某一个服务整体的一些访问情况。**一般来说,推荐根据一个服务区划分出一个线程池,command key 默认都是属于同一个线程池的。**
@@ -73,17 +80,20 @@ command key -> command group
command key -> 自己的 thread pool key
```
-逻辑上来说,多个 command key 属于一个command group,在做统计的时候,会放在一起统计。每个 command key 有自己的线程池,每个接口有自己的线程池,去做资源隔离和限流。
+逻辑上来说,多个 command key 属于一个 command group,在做统计的时候,会放在一起统计。每个 command key 有自己的线程池,每个接口有自己的线程池,去做资源隔离和限流。
说白点,就是说如果你的 command key 要用自己的线程池,可以定义自己的 thread pool key,就 ok 了。
-### coreSize
+## coreSize
+
设置线程池的大小,默认是 10。一般来说,用这个默认的 10 个线程大小就够了。
+
```java
HystrixThreadPoolProperties.Setter().withCoreSize(int value);
```
-### queueSizeRejectionThreshold
+## queueSizeRejectionThreshold
+
如果说线程池中的 10 个线程都在工作中,没有空闲的线程来做其它的事情,此时再有请求过来,会先进入队列积压。如果说队列积压满了,再有请求过来,就直接 reject,拒绝请求,执行 fallback 降级的逻辑,快速返回。

@@ -94,7 +104,8 @@ HystrixThreadPoolProperties.Setter().withCoreSize(int value);
HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value);
```
-### execution.isolation.semaphore.maxConcurrentRequests
+## execution.isolation.semaphore.maxConcurrentRequests
+
设置使用 SEMAPHORE 隔离策略的时候允许访问的最大并发量,超过这个最大并发量,请求直接被 reject。
这个并发量的设置,跟线程池大小的设置,应该是类似的,但是基于信号量的话,性能会好很多,而且 Hystrix 框架本身的开销会小很多。
@@ -103,4 +114,4 @@ HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value);
```java
HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(int value);
-```
\ No newline at end of file
+```
diff --git a/docs/high-availability/hystrix-fallback.md b/docs/high-availability/hystrix-fallback.md
index cb9e06c6c..52bbe1061 100644
--- a/docs/high-availability/hystrix-fallback.md
+++ b/docs/high-availability/hystrix-fallback.md
@@ -1,29 +1,30 @@
-## 基于本地缓存的 fallback 降级机制
+# 基于本地缓存的 fallback 降级机制
+
Hystrix 出现以下四种情况,都会去调用 fallback 降级机制:
-- 断路器处于打开的状态。
-- 资源池已满(线程池+队列 / 信号量)。
-- Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。
-- 访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。
+- 断路器处于打开的状态。
+- 资源池已满(线程池+队列 / 信号量)。
+- Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。
+- 访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。
-### 两种最经典的降级机制
+## 两种最经典的降级机制
-- 纯内存数据
-在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。
+- 纯内存数据
+ 在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。
-- 默认值
-fallback 降级逻辑中,也可以直接返回一个默认值。
+- 默认值
+ fallback 降级逻辑中,也可以直接返回一个默认值。
在 `HystrixCommand`,降级逻辑的书写,是通过实现 getFallback() 接口;而在 `HystrixObservableCommand` 中,则是实现 resumeWithFallback() 方法。
-
现在,我们用一个简单的栗子,来演示 fallback 降级是怎么做的。
比如,有这么个**场景**。我们现在有个包含 brandId 的商品数据,假设正常的逻辑是这样:拿到一个商品数据,根据 brandId 去调用品牌服务的接口,获取品牌的最新名称 brandName。
假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。
-### 步骤一:本地缓存获取数据
+## 步骤一:本地缓存获取数据
+
本地获取品牌名称的代码大致如下。
```java
@@ -31,7 +32,6 @@ fallback 降级逻辑中,也可以直接返回一个默认值。
* 品牌名称本地缓存
*
*/
-
public class BrandCache {
private static Map brandMap = new HashMap<>();
@@ -51,7 +51,8 @@ public class BrandCache {
}
```
-### 步骤二:实现 GetBrandNameCommand
+## 步骤二:实现 GetBrandNameCommand
+
在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。
这里,我们直接**模拟接口调用报错**,给它抛出个异常。
@@ -63,7 +64,6 @@ public class BrandCache {
* 获取品牌名称的command
*
*/
-
public class GetBrandNameCommand extends HystrixCommand {
private Long brandId;
@@ -95,7 +95,8 @@ public class GetBrandNameCommand extends HystrixCommand {
`FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。
-### 步骤三:CacheController 调用接口
+## 步骤三:CacheController 调用接口
+
在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。
```java
@@ -122,4 +123,4 @@ public class CacheController {
}
```
-关于降级逻辑的演示,基本上就结束了。
\ No newline at end of file
+关于降级逻辑的演示,基本上就结束了。
diff --git a/docs/high-availability/hystrix-introduction.md b/docs/high-availability/hystrix-introduction.md
index d7eb44c40..483f4226a 100644
--- a/docs/high-availability/hystrix-introduction.md
+++ b/docs/high-availability/hystrix-introduction.md
@@ -1,7 +1,9 @@
-## 用 Hystrix 构建高可用服务架构
+# 用 Hystrix 构建高可用服务架构
+
参考 [Hystrix Home](https://github.com/Netflix/Hystrix/wiki#what)。
-### Hystrix 是什么?
+## Hystrix 是什么?
+
在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是**依赖服务**,有的时候某些依赖服务出现故障也是很正常的。
Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些**调用延迟**或者**依赖故障**的**容错机制**。
@@ -10,7 +12,8 @@ Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖
**总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。**
-### Hystrix 的历史
+## Hystrix 的历史
+
Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外的优酷或者爱奇艺之类的视频网站)的 API 团队从 2011 年开始做一些提升系统可用性和稳定性的工作,Hystrix 就是从那时候开始发展出来的。
在 2012 年的时候,Hystrix 就变得比较成熟和稳定了,Netflix 中,除了 API 团队以外,很多其他的团队都开始使用 Hystrix。
@@ -19,13 +22,13 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外
[2018 年 11 月,Hystrix 在其 Github 主页宣布,不再开放新功能,推荐开发者使用其他仍然活跃的开源项目](https://github.com/Netflix/Hystrix/blob/master/README.md#hystrix-status)。维护模式的转变绝不意味着 Hystrix 不再有价值。相反,Hystrix 激发了很多伟大的想法和项目,我们高可用的这一块知识还是会针对 Hystrix 进行讲解。
-### Hystrix 的设计原则
-- 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。
-- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。
-- 提供 `fail-fast`(快速失败)和快速恢复的支持。
-- 提供 fallback 优雅降级的支持。
-- 支持近实时的监控、报警以及运维操作。
+## Hystrix 的设计原则
+- 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。
+- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。
+- 提供 `fail-fast`(快速失败)和快速恢复的支持。
+- 提供 fallback 优雅降级的支持。
+- 支持近实时的监控、报警以及运维操作。
举个栗子。
@@ -37,12 +40,12 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外
Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线程调用服务 C。当此 40 个线程被 hang 住时,其它 60 个线程依然能正常调用工作。从而确保整个系统不会被拖垮。
+## Hystrix 更加细节的设计原则
-### Hystrix 更加细节的设计原则
-- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。
-- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。
-- 提供 fallback 降级机制来应对故障。
-- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(断路技术)来限制任何一个依赖服务的故障的影响。
-- 通过近实时的统计/监控/报警功能,来提高故障发现的速度。
-- 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。
-- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。
\ No newline at end of file
+- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。
+- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。
+- 提供 fallback 降级机制来应对故障。
+- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(断路技术)来限制任何一个依赖服务的故障的影响。
+- 通过近实时的统计、监控、报警功能,来提高故障发现的速度。
+- 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。
+- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。
diff --git a/docs/high-availability/hystrix-process.md b/docs/high-availability/hystrix-process.md
index c0ffedfdd..984fbf586 100644
--- a/docs/high-availability/hystrix-process.md
+++ b/docs/high-availability/hystrix-process.md
@@ -1,9 +1,10 @@
-## 深入 Hystrix 执行时内部原理
+# 深入 Hystrix 执行时内部原理
+
前面我们了解了 Hystrix 最基本的支持高可用的技术:**资源隔离** + **限流**。
-- 创建 command;
-- 执行这个 command;
-- 配置这个 command 对应的 group 和线程池。
+- 创建 command;
+- 执行这个 command;
+- 配置这个 command 对应的 group 和线程池。
这里,我们要讲一下,你开始执行这个 command,调用了这个 command 的 execute() 方法之后,Hystrix 底层的执行流程和步骤以及原理是什么。
@@ -13,11 +14,12 @@

-### 步骤一:创建 command
+## 步骤一:创建 command
+
一个 HystrixCommand 或 HystrixObservableCommand 对象,代表了对某个依赖服务发起的一次请求或者调用。创建的时候,可以在构造函数中传入任何需要的参数。
-- HystrixCommand 主要用于仅仅会返回一个结果的调用。
-- HystrixObservableCommand 主要用于可能会返回多条结果的调用。
+- HystrixCommand 主要用于仅仅会返回一个结果的调用。
+- HystrixObservableCommand 主要用于可能会返回多条结果的调用。
```java
// 创建 HystrixCommand
@@ -27,17 +29,18 @@ HystrixCommand hystrixCommand = new HystrixCommand(arg1, arg2);
HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand(arg1, arg2);
```
-### 步骤二:调用 command 执行方法
+## 步骤二:调用 command 执行方法
+
执行 command,就可以发起一次对依赖服务的调用。
要执行 command,可以在 4 个方法中选择其中的一个:execute()、queue()、observe()、toObservable()。
其中 execute() 和 queue() 方法仅仅对 HystrixCommand 适用。
-- execute():调用后直接 block 住,属于同步调用,直到依赖服务返回单条结果,或者抛出异常。
-- queue():返回一个 Future,属于异步调用,后面可以通过 Future 获取单条结果。
-- observe():订阅一个 Observable 对象,Observable 代表的是依赖服务返回的结果,获取到一个那个代表结果的 Observable 对象的拷贝对象。
-- toObservable():返回一个 Observable 对象,如果我们订阅这个对象,就会执行 command 并且获取返回结果。
+- execute():调用后直接 block 住,属于同步调用,直到依赖服务返回单条结果,或者抛出异常。
+- queue():返回一个 Future,属于异步调用,后面可以通过 Future 获取单条结果。
+- observe():订阅一个 Observable 对象,Observable 代表的是依赖服务返回的结果,获取到一个那个代表结果的 Observable 对象的拷贝对象。
+- toObservable():返回一个 Observable 对象,如果我们订阅这个对象,就会执行 command 并且获取返回结果。
```java
K value = hystrixCommand.execute();
@@ -47,6 +50,7 @@ Observable toOValue = hystrixObservableCommand.toObservable();
```
execute() 实际上会调用 queue().get() 方法,可以看一下 Hystrix 源码。
+
```java
public R execute() {
try {
@@ -58,34 +62,39 @@ public R execute() {
```
而在 queue() 方法中,会调用 toObservable().toBlocking().toFuture()。
+
```java
final Future delegate = toObservable().toBlocking().toFuture();
```
也就是说,先通过 toObservable() 获得 Future 对象,然后调用 Future 的 get() 方法。那么,其实无论是哪种方式执行 command,最终都是依赖于 toObservable() 去执行的。
-### 步骤三:检查是否开启缓存(不太常用)
+## 步骤三:检查是否开启缓存(不太常用)
+
从这一步开始,就进入到 Hystrix 底层运行原理啦,看一下 Hystrix 一些更高级的功能和特性。
如果这个 command 开启了请求缓存 Request Cache,而且这个调用的结果在缓存中存在,那么直接从缓存中返回结果。否则,继续往后的步骤。
-### 步骤四:检查是否开启了断路器
+## 步骤四:检查是否开启了断路器
+
检查这个 command 对应的依赖服务是否开启了断路器。如果断路器被打开了,那么 Hystrix 就不会执行这个 command,而是直接去执行 fallback 降级机制,返回降级结果。
-### 步骤五:检查线程池/队列/信号量是否已满
+## 步骤五:检查线程池/队列/信号量是否已满
+
如果这个 command 线程池和队列已满,或者 semaphore 信号量已满,那么也不会执行 command,而是直接去调用 fallback 降级机制,同时发送 reject 信息给断路器统计。
-### 步骤六:执行 command
+## 步骤六:执行 command
+
调用 HystrixObservableCommand 对象的 construct() 方法,或者 HystrixCommand 的 run() 方法来实际执行这个 command。
-- HystrixCommand.run() 返回单条结果,或者抛出异常。
+- HystrixCommand.run() 返回单条结果,或者抛出异常。
```java
// 通过command执行,获取最新一条商品数据
ProductInfo productInfo = getProductInfoCommand.execute();
```
-- HystrixObservableCommand.construct() 返回一个 Observable 对象,可以获取多条结果。
+- HystrixObservableCommand.construct() 返回一个 Observable 对象,可以获取多条结果。
```java
Observable observable = getProductInfosCommand.observe();
@@ -104,7 +113,7 @@ observable.subscribe(new Observer() {
/**
* 获取完一条数据,就回调一次这个方法
- *
+ *
* @param productInfo 商品信息
*/
@Override
@@ -120,37 +129,40 @@ observable.subscribe(new Observer() {
如果没有 timeout,也正常执行的话,那么调用线程就会拿到一些调用依赖服务获取到的结果,然后 Hystrix 也会做一些 logging 记录和 metric 度量统计。
-### 步骤七:断路健康检查
+## 步骤七:断路健康检查
+
Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout 等事件发送给 circuit breaker 断路器。断路器就会对这些事件的次数进行统计,根据异常事件发生的比例来决定是否要进行断路(熔断)。如果打开了断路器,那么在接下来一段时间内,会直接断路,返回降级结果。
如果在之后,断路器尝试执行 command,调用没有出错,返回了正常结果,那么 Hystrix 就会把断路器关闭。
-### 步骤八:调用 fallback 降级机制
+## 步骤八:调用 fallback 降级机制
+
在以下几种情况中,Hystrix 会调用 fallback 降级机制。
-- 断路器处于打开状态;
-- 线程池/队列/semaphore满了;
-- command 执行超时;
-- run() 或者 construct() 抛出异常。
+- 断路器处于打开状态;
+- 线程池/队列/semaphore 满了;
+- command 执行超时;
+- run() 或者 construct() 抛出异常。
一般在降级机制中,都建议给出一些默认的返回值,比如静态的一些代码逻辑,或者从内存中的缓存中提取一些数据,在这里尽量不要再进行网络请求了。
在降级中,如果一定要进行网络调用的话,也应该将那个调用放在一个 HystrixCommand 中进行隔离。
-- HystrixCommand 中,实现 getFallback() 方法,可以提供降级机制。
-- HystrixObservableCommand 中,实现 resumeWithFallback() 方法,返回一个 Observable 对象,可以提供降级结果。
+- HystrixCommand 中,实现 getFallback() 方法,可以提供降级机制。
+- HystrixObservableCommand 中,实现 resumeWithFallback() 方法,返回一个 Observable 对象,可以提供降级结果。
如果没有实现 fallback,或者 fallback 抛出了异常,Hystrix 会返回一个 Observable,但是不会返回任何数据。
不同的 command 执行方式,其 fallback 为空或者异常时的返回结果不同。
-- 对于 execute(),直接抛出异常。
-- 对于 queue(),返回一个 Future,调用 get() 时抛出异常。
-- 对于 observe(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
-- 对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
+- 对于 execute(),直接抛出异常。
+- 对于 queue(),返回一个 Future,调用 get() 时抛出异常。
+- 对于 observe(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
+- 对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
+
+## 不同的执行方式
-### 不同的执行方式
-- execute(),获取一个 Future.get(),然后拿到单个结果。
-- queue(),返回一个 Future。
-- observe(),立即订阅 Observable,然后启动 8 大执行步骤,返回一个拷贝的 Observable,订阅时立即回调给你结果。
-- toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。
\ No newline at end of file
+- execute(),获取一个 Future.get(),然后拿到单个结果。
+- queue(),返回一个 Future。
+- observe(),立即订阅 Observable,然后启动 8 大执行步骤,返回一个拷贝的 Observable,订阅时立即回调给你结果。
+- toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。
diff --git a/docs/high-availability/hystrix-request-cache.md b/docs/high-availability/hystrix-request-cache.md
index 1222af3e6..2c196022f 100644
--- a/docs/high-availability/hystrix-request-cache.md
+++ b/docs/high-availability/hystrix-request-cache.md
@@ -1,4 +1,5 @@
-## 基于 request cache 请求缓存技术优化批量商品数据查询接口
+# 基于 request cache 请求缓存技术优化批量商品数据查询接口
+
Hystrix command 执行时 8 大步骤第三步,就是检查 Request cache 是否有缓存。
首先,有一个概念,叫做 Request Context 请求上下文,一般来说,在一个 web 应用中,如果我们用到了 Hystrix,我们会在一个 filter 里面,对每一个请求都施加一个请求上下文。就是说,每一次请求,就是一次请求上下文。然后在这次请求上下文中,我们会去执行 N 多代码,调用 N 多依赖服务,有的依赖服务可能还会调用好几次。
@@ -19,7 +20,8 @@ HystrixCommand 和 HystrixObservableCommand 都可以指定一个缓存 key,
我们对批量查询商品数据的接口,可以用 request cache 做一个优化,就是说一次请求,就是一次 request context,对相同的商品查询只执行一次,其余重复的都走 request cache。
-### 实现 Hystrix 请求上下文过滤器并注册
+## 实现 Hystrix 请求上下文过滤器并注册
+
定义 HystrixRequestContextFilter 类,实现 Filter 接口。
```java
@@ -71,7 +73,8 @@ public class EshopApplication {
}
```
-### command 重写 getCacheKey() 方法
+## command 重写 getCacheKey() 方法
+
在 GetProductInfoCommand 中,重写 getCacheKey() 方法,这样的话,每一次请求的结果,都会放在 Hystrix 请求上下文中。下一次同一个 productId 的数据请求,直接取缓存,无须再调用 run() 方法。
```java
@@ -119,7 +122,8 @@ public class GetProductInfoCommand extends HystrixCommand {
这里写了一个 flushCache() 方法,用于我们开发手动删除缓存。
-### controller 调用 command 查询商品信息
+## controller 调用 command 查询商品信息
+
在一次 web 请求上下文中,传入商品 id 列表,查询多条商品数据信息。对于每个 productId,都创建一个 command。
如果 id 列表没有去重,那么重复的 id,第二次查询的时候就会直接走缓存。
@@ -149,7 +153,8 @@ public class CacheController {
}
```
-### 发起请求
+## 发起请求
+
调用接口,查询多个商品的信息。
```
@@ -172,7 +177,8 @@ http://localhost:8080/getProductInfos?productIds=1,1,1,2,2,5
第一次查询 productId=1 的数据,会调用接口进行查询,不是从缓存中取结果。而随后再出现查询 productId=1 的请求,就直接取缓存了,这样的话,效率明显高很多。
-### 删除缓存
+## 删除缓存
+
我们写一个 UpdateProductInfoCommand,在更新商品信息之后,手动调用之前写的 flushCache(),手动将缓存删除。
```java
@@ -197,4 +203,4 @@ public class UpdateProductInfoCommand extends HystrixCommand {
}
```
-这样,以后查询该商品的请求,第一次就会走接口调用去查询最新的商品信息。
\ No newline at end of file
+这样,以后查询该商品的请求,第一次就会走接口调用去查询最新的商品信息。
diff --git a/docs/high-availability/hystrix-semphore-isolation.md b/docs/high-availability/hystrix-semphore-isolation.md
index cd0e1d3ae..13d5cfb0a 100644
--- a/docs/high-availability/hystrix-semphore-isolation.md
+++ b/docs/high-availability/hystrix-semphore-isolation.md
@@ -1,21 +1,24 @@
-## 基于 Hystrix 信号量机制实现资源隔离
+# 基于 Hystrix 信号量机制实现资源隔离
+
Hystrix 里面核心的一项功能,其实就是所谓的**资源隔离**,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话,就可能导致服务崩溃,甚至说这种故障会不断蔓延。
Hystrix 实现资源隔离,主要有两种技术:
-- 线程池
-- 信号量
+- 线程池
+- 信号量
默认情况下,Hystrix 使用线程池模式。
前面已经说过线程池技术了,这一小节就来说说信号量机制实现资源隔离,以及这两种技术的区别与具体应用场景。
-### 信号量机制
+## 信号量机制
+
信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。

-### 线程池与信号量区别
+## 线程池与信号量区别
+
线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。
线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。
@@ -23,10 +26,12 @@ Hystrix 实现资源隔离,主要有两种技术:

**适用场景**:
-- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。
-- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。
-### 信号量简单 Demo
+- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。
+- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。
+
+## 信号量简单 Demo
+
业务背景里,比较适合信号量的是什么场景呢?
比如说,我们一般来说,缓存服务,可能会将一些量特别少、访问又特别频繁的数据,放在自己的纯内存中。
@@ -36,6 +41,7 @@ Hystrix 实现资源隔离,主要有两种技术:
优点在于,不用自己管理线程池啦,不用 care timeout 超时啦,也不需要进行线程的上下文切换啦。信号量做隔离的话,性能相对来说会高一些。
假如这是本地缓存,我们可以通过 cityId,拿到 cityName。
+
```java
public class LocationCache {
private static Map cityMap = new HashMap<>();
@@ -57,6 +63,7 @@ public class LocationCache {
```
写一个 GetCityNameCommand,策略设置为**信号量**。run() 方法中获取本地缓存。我们目的就是对获取本地缓存的代码进行资源隔离。
+
```java
public class GetCityNameCommand extends HystrixCommand {
@@ -80,6 +87,7 @@ public class GetCityNameCommand extends HystrixCommand {
```
在接口层,通过创建 GetCityNameCommand,传入 cityId,执行 execute() 方法,那么获取本地 cityName 缓存的代码将会进行信号量的资源隔离。
+
```java
@RequestMapping("/getProductInfo")
@ResponseBody
@@ -100,4 +108,4 @@ public String getProductInfo(Long productId) {
System.out.println(productInfo);
return "success";
}
-```
\ No newline at end of file
+```
diff --git a/docs/high-availability/hystrix-thread-pool-current-limiting.md b/docs/high-availability/hystrix-thread-pool-current-limiting.md
index a99eb1e22..116320f94 100644
--- a/docs/high-availability/hystrix-thread-pool-current-limiting.md
+++ b/docs/high-availability/hystrix-thread-pool-current-limiting.md
@@ -1,4 +1,5 @@
-## 深入 Hystrix 线程池隔离与接口限流
+# 深入 Hystrix 线程池隔离与接口限流
+
前面讲了 Hystrix 的 request cache 请求缓存、fallback 优雅降级、circuit breaker 断路器快速熔断,这一讲,我们来详细说说 Hystrix 的线程池隔离与接口限流。

@@ -7,7 +8,8 @@ Hystrix 通过判断线程池或者信号量是否已满,超出容量的请求
限流是限制对后端的服务的访问量,比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制,其实是为了避免过大的流量直接打死后端的服务。
-### 线程池隔离技术的设计
+## 线程池隔离技术的设计
+
Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃。
**舱壁隔离**,是说将船体内部空间区隔划分成若干个隔舱,一旦某几个隔舱发生破损进水,水流不会在其间相互流动,如此一来船舶在受损时,依然能具有足够的浮力和稳定性,进而减低立即沉船的危险。
@@ -16,36 +18,39 @@ Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进
Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。
-### Hystrix 应用线程池机制的场景
-- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。
-- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。
-- client 调用库随时会变更。
-- client 调用库随时可能会增加新的网络请求的逻辑。
-- client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。
-- client 调用库一般都对调用者来说是个黑盒,包括实现细节、网络访问、默认配置等等。
-- 在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client 调用库发生了某些变化。
-- 即使 client 调用库没有改变,依赖服务本身可能有会发生逻辑上的变化。
-- 有些依赖的 client 调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确。
-- 大多数网络请求都是同步调用的。
-- 调用失败和延迟,也有可能会发生在 client 调用库本身的代码中,不一定就是发生在网络请求中。
+## Hystrix 应用线程池机制的场景
+
+- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。
+- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。
+- client 调用库随时会变更。
+- client 调用库随时可能会增加新的网络请求的逻辑。
+- client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。
+- client 调用库一般都对调用者来说是个黑盒,包括实现细节、网络访问、默认配置等等。
+- 在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client 调用库发生了某些变化。
+- 即使 client 调用库没有改变,依赖服务本身可能有会发生逻辑上的变化。
+- 有些依赖的 client 调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确。
+- 大多数网络请求都是同步调用的。
+- 调用失败和延迟,也有可能会发生在 client 调用库本身的代码中,不一定就是发生在网络请求中。
简单来说,就是你必须默认 client 调用库很不靠谱,而且随时可能发生各种变化,所以就要用强制隔离的方式来确保任何服务的故障不会影响当前服务。
-### 线程池机制的优点
-- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。
-- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。
-- 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦。
-- 如果一个 client 调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机。
-- 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层。
+## 线程池机制的优点
+
+- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。
+- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。
+- 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦。
+- 如果一个 client 调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机。
+- 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层。
简单来说,最大的好处,就是资源隔离,确保说任何一个依赖服务故障,不会拖垮当前的这个服务。
-### 线程池机制的缺点
-- 线程池机制最大的缺点就是增加了 CPU 的开销。
-除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。
+## 线程池机制的缺点
+
+- 线程池机制最大的缺点就是增加了 CPU 的开销。
+ 除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。
-- 每个 command 的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换。
-- Hystrix 官方自己做了一个多线程异步带来的额外开销统计,通过对比多线程异步调用+同步调用得出,Netflix API 每天通过 Hystrix 执行 10 亿次调用,每个服务实例有 40 个以上的线程池,每个线程池有 10 个左右的线程。)最后发现说,用 Hystrix 的额外开销,就是给请求带来了 3ms 左右的延时,最多延时在 10ms 以内,相比于可用性和稳定性的提升,这是可以接受的。
+- 每个 command 的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换。
+- Hystrix 官方自己做了一个多线程异步带来的额外开销统计,通过对比多线程异步调用+同步调用得出,Netflix API 每天通过 Hystrix 执行 10 亿次调用,每个服务实例有 40 个以上的线程池,每个线程池有 10 个左右的线程。)最后发现说,用 Hystrix 的额外开销,就是给请求带来了 3ms 左右的延时,最多延时在 10ms 以内,相比于可用性和稳定性的提升,这是可以接受的。
我们可以用 Hystrix semaphore 技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量。
@@ -53,14 +58,15 @@ semaphore 技术可以用来限流和削峰,但是不能用来对调用延迟
`execution.isolation.strategy` 设置为 `SEMAPHORE`,那么 Hystrix 就会用 semaphore 机制来替代线程池机制,来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候,底层的网络调用延迟很严重,那么是无法 timeout 的,只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后,就会立即开启限流。
-### 接口限流 Demo
+## 接口限流 Demo
+
假设一个线程池大小为 8,等待队列的大小为 10。timeout 时长我们设置长一些,20s。
在 command 内部,写死代码,做一个 sleep,比如 sleep 3s。
-- withCoreSize:设置线程池大小。
-- withMaxQueueSize:设置等待队列大小。
-- withQueueSizeRejectionThreshold:这个与 withMaxQueueSize 配合使用,等待队列的大小,取得是这两个参数的较小值。
+- withCoreSize:设置线程池大小。
+- withMaxQueueSize:设置等待队列大小。
+- withQueueSizeRejectionThreshold:这个与 withMaxQueueSize 配合使用,等待队列的大小,取得是这两个参数的较小值。
如果只设置了线程池大小,另外两个 queue 相关参数没有设置的话,等待队列是处于关闭的状态。
@@ -159,5 +165,5 @@ ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specificat
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
{"id": -2, "name": "iphone7手机", "price": 5599, "pictureList":"a.jpg,b.jpg", "specification": "iphone7的规格", "service": "iphone7的售后服务", "color": "红色,白色,黑色", "size": "5.5", "shopId": 1, "modifiedTime": "2017-01-01 12:00:00", "cityId": 1, "brandId": 1}
// 后面都是一些正常的商品信息,就不贴出来了
-//...
+// ...
```
diff --git a/docs/high-availability/hystrix-thread-pool-isolation.md b/docs/high-availability/hystrix-thread-pool-isolation.md
index 8fa440865..735e741f4 100644
--- a/docs/high-availability/hystrix-thread-pool-isolation.md
+++ b/docs/high-availability/hystrix-thread-pool-isolation.md
@@ -1,11 +1,13 @@
-## 基于 Hystrix 线程池技术实现资源隔离
+# 基于 Hystrix 线程池技术实现资源隔离
+
[上一讲](./e-commerce-website-detail-page-architecture.md)提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。
资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是分配给商品服务线程池内就 10 个线程,最多就只会用这 10 个线程去执行。不会因为对商品服务调用的延迟,将 Tomcat 内部所有的线程资源全部耗尽。
Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 Command。这也是 Hystrix 最最基本的资源隔离技术。
-### 利用 HystrixCommand 获取单条数据
+## 利用 HystrixCommand 获取单条数据
+
我们通过将调用商品服务的操作封装在 HystrixCommand 中,限定一个 key,比如下面的 `GetProductInfoCommandGroup`,在这里我们可以简单认为这是一个线程池,每次调用商品服务,就只会用该线程池中的资源,不会再去用其它线程资源了。
```java
@@ -35,7 +37,7 @@ public class GetProductInfoCommand extends HystrixCommand {
@ResponseBody
public String getProductInfo(Long productId) {
HystrixCommand getProductInfoCommand = new GetProductInfoCommand(productId);
-
+
// 通过command执行,获取最新商品数据
ProductInfo productInfo = getProductInfoCommand.execute();
System.out.println(productInfo);
@@ -45,7 +47,8 @@ public String getProductInfo(Long productId) {
上面执行的是 execute() 方法,其实是同步的。也可以对 command 调用 queue() 方法,它仅仅是将 command 放入线程池的一个等待队列,就立即返回,拿到一个 Future 对象,后面可以继续做其它一些事情,然后过一段时间对 Future 调用 get() 方法获取数据。这是异步的。
-### 利用 HystrixObservableCommand 批量获取数据
+## 利用 HystrixObservableCommand 批量获取数据
+
只要是获取商品数据,全部都绑定到同一个线程池里面去,我们通过 HystrixObservableCommand 的一个线程去执行,而在这个线程里面,批量把多个 productId 的 productInfo 拉回来。
```java
@@ -78,6 +81,7 @@ public class GetProductInfosCommand extends HystrixObservableCommand Hystrix is a library that helps you control the interactions between these distributed services by adding latency tolerance and fault tolerance logic. Hystrix does this by isolating points of access between the services, stopping cascading failures across them, and providing fallback options, all of which improve your system’s overall resiliency.
@@ -12,26 +14,29 @@ Sentinel 项目地址:https://github.com/alibaba/Sentinel
而 Sentinel 的侧重点在于:
-- 多样化的流量控制
-- 熔断降级
-- 系统负载保护
-- 实时监控和控制台
+- 多样化的流量控制
+- 熔断降级
+- 系统负载保护
+- 实时监控和控制台
两者解决的问题还是有比较大的不同的,下面我们来具体对比一下。
## 共同特性
+
### 1. 资源模型和执行模型上的对比
+
Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象 `HystrixCommand` 或 `HystrixObservableCommand`,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 `commandKey` 和 `groupKey`(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。
-**注**:关于 Hystrix 的详细介绍及代码演示,可以参考本项目[高可用架构](/docs/high-availability/README.md)-Hystrix 部分的详细说明。
+**注**:关于 Hystrix 的详细介绍及代码演示,可以参考本项目[高可用架构](./README.md)-Hystrix 部分的详细说明。
Sentinel 的设计则更为简单。相比 Hystrix Command 强依赖隔离规则,Sentinel 的资源定义与规则配置的耦合度更低。Hystrix 的 Command 强依赖于隔离规则配置的原因是隔离规则会直接影响 Command 的执行。在执行的时候 Hystrix 会解析 Command 的隔离规则来创建 RxJava Scheduler 并在其上调度执行,若是线程池模式则 Scheduler 底层的线程池为配置的线程池,若是信号量模式则简单包装成当前线程执行的 Scheduler。
-而 Sentinel 则不一样,开发的时候只需要考虑这个方法/代码是否需要保护,置于用什么来保护,可以任何时候动态实时的区修改。
+而 Sentinel 则不一样,开发的时候只需要考虑这个方法/代码是否需要保护,至于用什么来保护,可以任何时候动态实时的去修改。
从 `0.1.1` 版本开始,Sentinel 还支持基于注解的资源定义方式,可以通过注解参数指定异常处理函数和 fallback 函数。Sentinel 提供多样化的规则配置方式。除了直接通过 `loadRules` API 将规则注册到内存态之外,用户还可以注册各种外部数据源来提供动态的规则。用户可以根据系统当前的实时情况去动态地变更规则配置,数据源会将变更推送至 Sentinel 并即时生效。
### 2. 隔离设计上的对比
+
隔离是 Hystrix 的核心功能之一。Hystrix 提供两种隔离策略:线程池隔离 `Bulkhead Pattern` 和信号量隔离,其中最推荐也是最常用的是**线程池隔离**。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。
但是,实际情况下,线程池隔离并没有带来非常多的好处。最直接的影响,就是会让机器资源碎片化。考虑这样一个常见的场景,在 Tomcat 之类的 Servlet 容器使用 Hystrix,本身 Tomcat 自身的线程数目就非常多了(可能到几十或一百多),如果加上 Hystrix 为各个资源创建的线程池,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。另外,线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。
@@ -41,62 +46,70 @@ Hystrix 的信号量隔离限制对某个资源调用的并发数。这样的隔
Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。
### 3. 熔断降级的对比
+
Sentinel 和 Hystrix 的熔断降级功能本质上都是基于熔断器模式 `Circuit Breaker Pattern`。Sentinel 与 Hystrix 都支持基于失败比率(异常比率)的熔断降级,在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断,此时所有对该资源的调用都会被 block,直到过了指定的时间窗口后才启发性地恢复。上面提到过,Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。
### 4. 实时指标统计实现的对比
+
Hystrix 和 Sentinel 的实时指标数据统计实现都是基于滑动窗口的。Hystrix 1.5 之前的版本是通过环形数组实现的滑动窗口,通过锁配合 CAS 的操作对每个桶的统计信息进行更新。Hystrix 1.5 开始对实时指标统计的实现进行了重构,将指标统计数据结构抽象成了响应式流(reactive stream)的形式,方便消费者去利用指标信息。同时底层改造成了基于 RxJava 的事件驱动模式,在服务调用成功/失败/超时的时候发布相应的事件,通过一系列的变换和聚合最终得到实时的指标统计数据流,可以被熔断器或 Dashboard 消费。
Sentinel 目前抽象出了 Metric 指标统计接口,底层可以有不同的实现,目前默认的实现是基于 LeapArray 的滑动窗口,后续根据需要可能会引入 reactive stream 等实现。
## Sentinel 特性
+
除了之前提到的两者的共同特性之外,Sentinel 还提供以下的特色功能:
### 1. 轻量级、高性能
+
Sentinel 作为一个功能完备的高可用流量管控组件,其核心 sentinel-core 没有任何多余依赖,打包后只有不到 200KB,非常轻量级。开发者可以放心地引入 sentinel-core 而不需担心依赖问题。同时,Sentinel 提供了多种扩展点,用户可以很方便地根据需求去进行扩展,并且无缝地切合到 Sentinel 中。
引入 Sentinel 带来的性能损耗非常小。只有在业务单机量级超过 25W QPS 的时候才会有一些显著的影响(5% - 10% 左右),单机 QPS 不太大的时候损耗几乎可以忽略不计。
### 2. 流量控制
+
Sentinel 可以针对不同的调用关系,以不同的运行指标(如 QPS、并发调用数、系统负载等)为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状。
Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自动将流量调整成合适的形状。常用的有:
-- **直接拒绝模式**:即超出的请求直接拒绝。
-- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
-
-
-- **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
-
+- **直接拒绝模式**:即超出的请求直接拒绝。
+- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
+ 
+- **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
+ 
目前 Sentinel 对异步调用链路的支持还不是很好,后续版本会着重改善支持异步调用。
### 3. 系统负载保护
+
Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

### 4. 实时监控和控制面板
-Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。如果用户正在使用 Spring Boot/Spring Cloud 并使用了Sentinel Spring Cloud Starter,还可以方便地通过其暴露的 Actuator Endpoint 来获取运行时的一些信息,如动态规则等。未来 Sentinel 还会支持标准化的指标监控 API,可以方便地整合各种监控系统和可视化系统,如 Prometheus、Grafana 等。
+
+Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。如果用户正在使用 Spring Boot/Spring Cloud 并使用了 Sentinel Spring Cloud Starter,还可以方便地通过其暴露的 Actuator Endpoint 来获取运行时的一些信息,如动态规则等。未来 Sentinel 还会支持标准化的指标监控 API,可以方便地整合各种监控系统和可视化系统,如 Prometheus、Grafana 等。
Sentinel 控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。

### 5. 生态
+
Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。未来 Sentinel 还会对更多常用框架进行适配,并且会为 Service Mesh 提供集群流量防护的能力。
## 总结
-| # | Sentinel | Hystrix |
-|---|---|---|
-| 隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
-| 熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
-| 实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
-| 规则配置 | 支持多种数据源 | 支持多种数据源 |
-| 扩展性 | 多个扩展点 | 插件的形式 |
-| 基于注解的支持 | 支持 | 支持 |
-| 限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 |
-| 流量整形 | 支持慢启动、匀速器模式 | 不支持 |
-| 系统负载保护 | 支持 | 不支持 |
-| 控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
-| 常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC | Servlet、Spring Cloud Netflix |
+
+| # | Sentinel | Hystrix |
+| -------------- | ---------------------------------------------- | ----------------------------- |
+| 隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
+| 熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
+| 实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
+| 规则配置 | 支持多种数据源 | 支持多种数据源 |
+| 扩展性 | 多个扩展点 | 插件的形式 |
+| 基于注解的支持 | 支持 | 支持 |
+| 限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 |
+| 流量整形 | 支持慢启动、匀速器模式 | 不支持 |
+| 系统负载保护 | 支持 | 不支持 |
+| 控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
+| 常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC | Servlet、Spring Cloud Netflix |
diff --git a/docs/high-concurrency/README.md b/docs/high-concurrency/README.md
index 20eb73e4d..b80d8922d 100644
--- a/docs/high-concurrency/README.md
+++ b/docs/high-concurrency/README.md
@@ -1,70 +1,69 @@
# 高并发架构
-## [消息队列](/docs/high-concurrency/mq-interview.md)
+## [消息队列](./mq-interview.md)
-* [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](/docs/high-concurrency/why-mq.md)
-* [如何保证消息队列的高可用?](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
-* [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
-* [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)
-* [如何保证消息的顺序性?](/docs/high-concurrency/how-to-ensure-the-order-of-messages.md)
-* [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](/docs/high-concurrency/mq-time-delay-and-expired-failure.md)
-* [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](/docs/high-concurrency/mq-design.md)
+- [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](./why-mq.md)
+- [如何保证消息队列的高可用?](./how-to-ensure-high-availability-of-message-queues.md)
+- [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](./how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
+- [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](./how-to-ensure-the-reliable-transmission-of-messages.md)
+- [如何保证消息的顺序性?](./how-to-ensure-the-order-of-messages.md)
+- [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](./mq-time-delay-and-expired-failure.md)
+- [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](./mq-design.md)
-## [搜索引擎](/docs/high-concurrency/es-introduction.md)
+## [搜索引擎](./es-introduction.md)
-* [ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?](/docs/high-concurrency/es-architecture.md)
-* [ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?](/docs/high-concurrency/es-write-query-search.md)
-* [ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](/docs/high-concurrency/es-optimizing-query-performance.md)
-* [ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](/docs/high-concurrency/es-production-cluster.md)
+- [ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?](./es-architecture.md)
+- [ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?](./es-write-query-search.md)
+- [ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](./es-optimizing-query-performance.md)
+- [ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](./es-production-cluster.md)
## 缓存
-* [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/docs/high-concurrency/why-cache.md)
-* [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](/docs/high-concurrency/redis-single-thread-model.md)
-* [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](/docs/high-concurrency/redis-data-types.md)
-* [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](/docs/high-concurrency/redis-expiration-policies-and-lru.md)
-* [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
-* [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](/docs/high-concurrency/redis-persistence.md)
-* [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](/docs/high-concurrency/redis-cluster.md)
-* [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
-* [如何保证缓存与数据库的双写一致性?](/docs/high-concurrency/redis-consistence.md)
-* [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](/docs/high-concurrency/redis-cas.md)
-* [生产环境中的 Redis 是怎么部署的?](/docs/high-concurrency/redis-production-environment.md)
+- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](./why-cache.md)
+- [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](./redis-single-thread-model.md)
+- [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](./redis-data-types.md)
+- [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](./redis-expiration-policies-and-lru.md)
+- [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](./how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
+- [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](./redis-persistence.md)
+- [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](./redis-cluster.md)
+- [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](./redis-caching-avalanche-and-caching-penetration.md)
+- [如何保证缓存与数据库的双写一致性?](./redis-consistence.md)
+- [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](./redis-cas.md)
+- [生产环境中的 Redis 是怎么部署的?](./redis-production-environment.md)
+- [有了解过 Redis rehash 的过程吗?](./redis-rehash.md)
## 分库分表
-* [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/docs/high-concurrency/database-shard.md)
-* [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](/docs/high-concurrency/database-shard-method.md)
-* [如何设计可以动态扩容缩容的分库分表方案?](/docs/high-concurrency/database-shard-dynamic-expand.md)
-* [分库分表之后,id 主键如何处理?](/docs/high-concurrency/database-shard-global-id-generate.md)
+- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](./database-shard.md)
+- [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](./database-shard-method.md)
+- [如何设计可以动态扩容缩容的分库分表方案?](./database-shard-dynamic-expand.md)
+- [分库分表之后,id 主键如何处理?](./database-shard-global-id-generate.md)
## 读写分离
-* [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md)
+- [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](./mysql-read-write-separation.md)
## 高并发系统
-* [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md)
+- [如何设计一个高并发系统?](./high-concurrency-design.md)
----
+---
## 公众号
-GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
+GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
+
+关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
+
+
diff --git a/docs/high-concurrency/database-shard-dynamic-expand.md b/docs/high-concurrency/database-shard-dynamic-expand.md
index 67277dc38..51d9bbedc 100644
--- a/docs/high-concurrency/database-shard-dynamic-expand.md
+++ b/docs/high-concurrency/database-shard-dynamic-expand.md
@@ -1,16 +1,19 @@
+# 动态扩缩容方案
+
## 面试题
+
如何设计可以动态扩容缩容的分库分表方案?
## 面试官心理分析
对于分库分表来说,主要是面对以下问题:
-* 选择一个数据库中间件,调研、学习、测试;
-* 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
-* 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
-* 完成单库单表到分库分表的**迁移**,双写方案;
-* 线上系统开始基于分库分表对外提供服务;
-* 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
+- 选择一个数据库中间件,调研、学习、测试;
+- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
+- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
+- 完成单库单表到分库分表的**迁移**,双写方案;
+- 线上系统开始基于分库分表对外提供服务;
+- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
这个是你必须面对的一个事儿,就是你已经弄好分库分表方案了,然后一堆库和表都建好了,基于分库分表中间件的代码开发啥的都好了,测试都 ok 了,数据能均匀分布到各个库和各个表里去,而且接着你还通过双写的方案咔嚓一下上了系统,已经直接基于分库分表方案在搞了。
@@ -21,9 +24,10 @@
## 面试题剖析
### 停机扩容(不推荐)
+
这个方案就跟停机迁移一样,步骤几乎一致,唯一的一点就是那个导数的工具,是把现有库表的数据抽出来慢慢倒入到新的库和表里去。但是最好别这么玩儿,有点不太靠谱,因为既然**分库分表**就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,你这么玩儿,可能会出问题。
-从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1小时数据就导完了。这没有问题。
+从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1 小时数据就导完了。这没有问题。
如果 3 个库 + 12 个表,跑了一段时间了,数据量都 1~2 亿了。光是导 2 亿数据,都要导个几个小时,6 点,刚刚导完数据,还要搞后续的修改配置,重启系统,测试验证,10 点才可以搞完。所以不能这么搞。
@@ -33,7 +37,7 @@
我可以告诉各位同学,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支撑还是数据量支撑都没问题。
-每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载 32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500 = 48000 的写并发,接近 5 万每秒的写入并发,前面再加一个MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。
+每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载 $32 \times 1000 = 32000$ 的写并发,如果每个库承载 1500 的写并发,总共就是 $32 \times 1500 = 48000$ 的写并发,接近 5 万每秒的写入并发,前面再加一个 MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。
有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128 个库,256 个库,512 个库。
@@ -43,18 +47,18 @@
谈分库分表的扩容,**第一次分库分表,就一次性给他分个够**,32 个库,1024 张表,可能对大部分的中小型互联网公司来说,已经可以支撑好几年了。
-一个实践是利用 `32 * 32` 来分库分表,即分为 32 个库,每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模路由到库里的表。
+一个实践是利用 $32 \times 32$ 来分库分表,即分为 32 个库,每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模路由到库里的表。
| orderId | id % 32 (库) | id / 32 % 32 (表) |
-|---|---|---|
-| 259 | 3 | 8 |
-| 1189 | 5 | 5 |
-| 352 | 0 | 11 |
-| 4593 | 17 | 15 |
+| ------- | ------------ | ----------------- |
+| 259 | 3 | 8 |
+| 1189 | 5 | 5 |
+| 352 | 0 | 11 |
+| 4593 | 17 | 15 |
刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 MySQL 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 MySQL 服务器之间做迁移就可以了。然后系统配合改一下配置即可。
-比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是 1024 个表。
+比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表,那么最多是 1024 个表。
这么搞,是不用自己写代码做数据迁移的,都交给 DBA 来搞好了,但是 DBA 确实是需要做一些库表迁移的工作,但是总比你自己写代码,然后抽数据导数据来的效率高得多吧。
@@ -62,7 +66,7 @@
这里对步骤做一个总结:
-1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了。
+1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 $32 库 \times 32 表$,对于大部分公司来说,可能几年都够了。
2. 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表
3. 扩容的时候,申请增加更多的数据库服务器,装好 MySQL,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。
4. 由 DBA 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。
diff --git a/docs/high-concurrency/database-shard-global-id-generate.md b/docs/high-concurrency/database-shard-global-id-generate.md
index d8ef35682..ffba27349 100644
--- a/docs/high-concurrency/database-shard-global-id-generate.md
+++ b/docs/high-concurrency/database-shard-global-id-generate.md
@@ -1,4 +1,7 @@
+# 主键 ID 如何处理?
+
## 面试题
+
分库分表之后,id 主键如何处理?
## 面试官心理分析
@@ -33,7 +36,7 @@
适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的。
-``` java
+```java
UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf
```
@@ -47,16 +50,16 @@ UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf
snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的,用其中的 41 bits 作为毫秒数,用 10 bits 作为工作机器 id,12 bits 作为序列号。
-* 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
-* 41 bits:表示的是时间戳,单位是毫秒。41 bits 可以表示的数字多达 `2^41 - 1` ,也就是可以标识 `2^41 - 1` 个毫秒值,换算成年就是表示69年的时间。
-* 10 bits:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。但是 10 bits 里 5 个 bits 代表机房 id,5 个 bits 代表机器 id。意思就是最多代表 `2^5` 个机房(32 个机房),每个机房里可以代表 `2^5` 个机器(32台机器)。
-* 12 bits:这个是用来记录同一个毫秒内产生的不同 id,12 bits 可以代表的最大正整数是 `2^12 - 1 = 4096` ,也就是说可以用这个 12 bits 代表的数字来区分**同一个毫秒内**的 4096 个不同的 id。
+- 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
+- 41 bits:表示的是时间戳,单位是毫秒。41 bits 可以表示的数字多达 `2^41 - 1` ,也就是可以标识 `2^41 - 1` 个毫秒值,换算成年就是表示 69 年的时间。
+- 10 bits:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。但是 10 bits 里 5 个 bits 代表机房 id,5 个 bits 代表机器 id。意思就是最多代表 `2^5` 个机房(32 个机房),每个机房里可以代表 `2^5` 个机器(32 台机器)。
+- 12 bits:这个是用来记录同一个毫秒内产生的不同 id,12 bits 可以代表的最大数字是 `2^12 - 1 = 4095` ,也就是说可以用这个 12 bits 代表的数字来区分**同一个毫秒内**的 4096 个(数字 0 到数字 4095)不同的 id。
-```
+```sh
0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000
```
-``` java
+```java
public class IdWorker {
private long workerId;
@@ -170,7 +173,7 @@ public class IdWorker {
```
-怎么说呢,大概这个意思吧,就是说 41 bit 是当前毫秒单位的一个时间戳,就这意思;然后 5 bit 是你传递进来的一个**机房** id(但是最大只能是 32 以内),另外 5 bit 是你传递进来的**机器** id(但是最大只能是 32 以内),剩下的那个 12 bit序列号,就是如果跟你上次生成 id 的时间还在一个毫秒内,那么会把顺序给你累加,最多在 4096 个序号以内。
+怎么说呢,大概这个意思吧,就是说 41 bit 是当前毫秒单位的一个时间戳,就这意思;然后 5 bit 是你传递进来的一个**机房** id(但是最大只能是 32 以内),另外 5 bit 是你传递进来的**机器** id(但是最大只能是 32 以内),剩下的那个 12 bit 序列号,就是如果跟你上次生成 id 的时间还在一个毫秒内,那么会把顺序给你累加,最多在 4096 个序号以内。
所以你自己利用这个工具类,自己搞一个服务,然后对每个机房的每个机器都初始化这么一个东西,刚开始这个机房的这个机器的序号就是 0。然后每次接收到一个请求,说这个机房的这个机器要生成一个 id,你就找到对应的 Worker 生成。
diff --git a/docs/high-concurrency/database-shard-method.md b/docs/high-concurrency/database-shard-method.md
index 526420902..1430be928 100644
--- a/docs/high-concurrency/database-shard-method.md
+++ b/docs/high-concurrency/database-shard-method.md
@@ -1,4 +1,7 @@
+# 分库分表如何平滑过渡?
+
## 面试题
+
现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上?
## 面试官心理分析
@@ -19,7 +22,7 @@
导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。
-验证一下,ok了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。
+验证一下,ok 了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。
但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。
diff --git a/docs/high-concurrency/database-shard.md b/docs/high-concurrency/database-shard.md
index eaaaa3a90..94e274764 100644
--- a/docs/high-concurrency/database-shard.md
+++ b/docs/high-concurrency/database-shard.md
@@ -1,4 +1,7 @@
+# 为什么要分库分表?
+
## 面试题
+
为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
## 面试官心理分析
@@ -8,6 +11,7 @@
## 面试题剖析
### 为什么要分库分表?(设计高并发系统的时候,数据库层面该如何设计?)
+
说白了,分库分表是两回事儿,大家可别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。
我先给大家抛出来一个场景。
@@ -36,11 +40,11 @@
这就是所谓的**分库分表**,为啥要分库分表?你明白了吧。
-| # | 分库分表前 | 分库分表后 |
-|---|---|---|
-| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL从单机到多机,能承受的并发增加了多倍 |
-| 磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 |
-| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 |
+| # | 分库分表前 | 分库分表后 |
+| ------------ | ---------------------------- | -------------------------------------------- |
+| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL 从单机到多机,能承受的并发增加了多倍 |
+| 磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 |
+| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 |
### 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?
@@ -48,11 +52,11 @@
比较常见的包括:
-* Cobar
-* TDDL
-* Atlas
-* Sharding-jdbc
-* Mycat
+- Cobar
+- TDDL
+- Atlas
+- Sharding-jdbc
+- Mycat
#### Cobar
@@ -96,7 +100,7 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。
-还有**表层面的拆分**,就是分表,将一个表变成 N 个表,就是**让每个表的数据量控制在一定范围内**,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的SQL越复杂,就最好让单表行数越少。
+还有**表层面的拆分**,就是分表,将一个表变成 N 个表,就是**让每个表的数据量控制在一定范围内**,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的 SQL 越复杂,就最好让单表行数越少。
好了,无论分库还是分表,上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后,**中间件可以根据你指定的某个字段值**,比如说 userid,**自动路由到对应的库上去,然后再自动路由到对应的表里去**。
@@ -104,8 +108,8 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
而且这儿还有两种**分库分表的方式**:
-* 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
-* 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
+- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
+- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。
diff --git a/docs/high-concurrency/es-architecture.md b/docs/high-concurrency/es-architecture.md
index 6542c2a02..60f6c5b5c 100644
--- a/docs/high-concurrency/es-architecture.md
+++ b/docs/high-concurrency/es-architecture.md
@@ -1,4 +1,7 @@
+# ES 的分布式架构原理
+
## 面试题
+
ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?
## 面试官心理分析
@@ -15,15 +18,15 @@ ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊
ElasticSearch 设计的理念就是分布式搜索引擎,底层其实还是基于 lucene 的。核心思想就是在多台机器上启动多个 ES 进程实例,组成了一个 ES 集群。
-ES 中存储数据的**基本单位是索引**,比如说你现在要在 ES 中存储一些订单数据,你就应该在 ES 中创建一个索引 `order_idx` ,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。
+ES 中存储数据的**基本单位是索引**,比如说你现在要在 ES 中存储一些订单数据,你就应该在 ES 中创建一个索引 `order_idx` ,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一个数据库。
-```
-index -> type -> mapping -> document -> field。
+```
+index -> type -> mapping -> document -> field
```
这样吧,为了做个更直白的介绍,我在这里做个类比。但是切记,不要划等号,类比只是为了便于理解。
-index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,一个 index 里可以有多个 type,每个 type 的字段都是差不多的,但是有一些略微的差别。假设有一个 index,是订单 index,里面专门是放订单数据的。就好比说你在 mysql 中建表,有些订单是实物商品的订单,比如一件衣服、一双鞋子;有些订单是虚拟商品的订单,比如游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字段可能有略微的一些差别。
+index 相当于 mysql 数据库。而 type 没法跟 mysql 里去对比,一个 index 里可以有多个 type,每个 type 的字段都是差不多的,但是有一些略微的差别。假设有一个 index,是订单 index,里面专门是放订单数据的。就好比说你在 mysql 中建表,有些订单是实物商品的订单,比如一件衣服、一双鞋子;有些订单是虚拟商品的订单,比如游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字段可能有略微的一些差别。
所以就会在订单 index 里,建两个 type,一个是实物商品订单 type,一个是虚拟商品订单 type,这两个 type 大部分字段是一样的,少部分字段是不一样的。
@@ -41,7 +44,7 @@ index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,
ES 集群多个节点,会自动选举一个节点为 master 节点,这个 master 节点其实就是干一些管理的工作的,比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了,那么会重新选举一个节点为 master 节点。
-如果是非 master节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器,重启了之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。
+如果是非 master 节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器,重启了之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。
说得更简单一点,就是说如果某个非 master 节点宕机了。那么此节点上的 primary shard 不就没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。
diff --git a/docs/high-concurrency/es-introduction.md b/docs/high-concurrency/es-introduction.md
index d9d42a8a7..0fc115864 100644
--- a/docs/high-concurrency/es-introduction.md
+++ b/docs/high-concurrency/es-introduction.md
@@ -1,19 +1,23 @@
+# 搜索引擎介绍
+
## Lucene 和 ES 的前世今生
+
Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。
ElasticSearch 基于 Lucene,隐藏了 lucene 的复杂性,提供了简单易用的 RESTful api / Java api 接口(另外还有其他语言的 api 接口)。
-* 分布式的文档存储引擎
-* 分布式的搜索引擎和分析引擎
-* 分布式,支持 PB 级数据
+- 分布式的文档存储引擎
+- 分布式的搜索引擎和分析引擎
+- 分布式,支持 PB 级数据
## ES 的核心概念
### Near Realtime
+
近实时,有两层意思:
-* 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
-* 基于 ES 执行搜索和分析可以达到秒级
+- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
+- 基于 ES 执行搜索和分析可以达到秒级
### Cluster 集群
@@ -27,7 +31,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
文档是 ES 中最小的数据单元,一个 document 可以是一条客户数据、一条商品分类数据、一条订单数据,通常用 json 数据结构来表示。每个 index 下的 type,都可以存储多条 document。一个 document 里面有多个 field,每个 field 就是一个数据字段。
-``` json
+```json
{
"product_id": "1",
"product_name": "iPhone X",
@@ -39,7 +43,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
### Index
-索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 ducument。
+索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 document。
### Type
@@ -51,7 +55,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
### replica
-任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5个 replica shard,最小的高可用配置,是 2 台服务器。
+任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5 个 replica shard,最小的高可用配置,是 2 台服务器。
这么说吧,shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard,而 replica shard 一般简称为 replica。
@@ -59,10 +63,10 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
## ES 核心概念 vs. DB 核心概念
-| ES | DB |
-|---|---|
-| index | 数据库 |
-| type | 数据表 |
-| docuemnt | 一行数据 |
+| ES | DB |
+| -------- | -------- |
+| index | 数据库 |
+| type | 数据表 |
+| document | 一行数据 |
以上是一个简单的类比。
diff --git a/docs/high-concurrency/es-optimizing-query-performance.md b/docs/high-concurrency/es-optimizing-query-performance.md
index 91ec7a386..b59a21049 100644
--- a/docs/high-concurrency/es-optimizing-query-performance.md
+++ b/docs/high-concurrency/es-optimizing-query-performance.md
@@ -1,4 +1,7 @@
+# ES 查询性能优化
+
## 面试题
+
ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
## 面试官心理分析
@@ -19,7 +22,7 @@ ES 在数据量很大的情况下(数十亿级别)如何提高查询效率
es 的搜索引擎严重依赖于底层的 `filesystem cache` ,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file ` 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
-性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1秒、5秒、10秒。但如果是走 `filesystem cache` ,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。
+性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1 秒、5 秒、10 秒。但如果是走 `filesystem cache` ,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。
这里有个真实的案例。某个公司 es 节点有 3 台机器,每台机器看起来内存很多,64G,总内存就是 `64 * 3 = 192G` 。每台机器给 es jvm heap 是 `32G` ,那么剩下来留给 `filesystem cache` 的就是每台机器才 `32G` ,总共集群里给 `filesystem cache` 的就是 `32 * 3 = 96G` 内存。而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了 `1T` 的磁盘容量,es 数据量是 `1T` ,那么每台机器的数据量是 `300G` 。这样性能好吗? `filesystem cache` 的内存才 100G,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。
@@ -39,7 +42,7 @@ hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可
其实可以做**数据预热**。
-举个例子,拿微博来说,你可以把一些大V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 `filesystem cache` 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。
+举个例子,拿微博来说,你可以把一些大 V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 `filesystem cache` 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。
或者是电商,你可以将平时查看最多的一些商品,比如说 iphone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 `filesystem cache` 里去。
diff --git a/docs/high-concurrency/es-production-cluster.md b/docs/high-concurrency/es-production-cluster.md
index ea64087f0..0134cc725 100644
--- a/docs/high-concurrency/es-production-cluster.md
+++ b/docs/high-concurrency/es-production-cluster.md
@@ -1,4 +1,7 @@
+# ES 生产集群架构
+
## 面试题
+
ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
## 面试官心理分析
@@ -15,8 +18,8 @@ ES 生产集群的部署架构是什么?每个索引的数据量大概有多
但是如果你确实没干过,也别虚,我给你说一个基本的版本,你到时候就简单说一下就好了。
-* es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。
-* 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。
-* 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。
+- es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。
+- 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。
+- 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。
大概就这么说一下就行了。
diff --git a/docs/high-concurrency/es-write-query-search.md b/docs/high-concurrency/es-write-query-search.md
index 90a06e5dd..600a2a8b5 100644
--- a/docs/high-concurrency/es-write-query-search.md
+++ b/docs/high-concurrency/es-write-query-search.md
@@ -1,4 +1,7 @@
+# ES 写入数据原理
+
## 面试题
+
ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?
## 面试官心理分析
@@ -11,10 +14,10 @@ ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是
### es 写数据过程
-* 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node` (协调节点)。
-* `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。
-* 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node` 。
-* `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。
+- 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node` (协调节点)。
+- `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。
+- 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node` 。
+- `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。

@@ -22,27 +25,27 @@ ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是
可以通过 `doc id` 来查询,会根据 `doc id` 进行 hash,判断出来当时把 `doc id` 分配到了哪个 shard 上面去,从那个 shard 去查询。
-* 客户端发送请求到**任意**一个 node,成为 `coordinate node` 。
-* `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。
-* 接收请求的 node 返回 document 给 `coordinate node` 。
-* `coordinate node` 返回 document 给客户端。
+- 客户端发送请求到**任意**一个 node,成为 `coordinate node` 。
+- `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。
+- 接收请求的 node 返回 document 给 `coordinate node` 。
+- `coordinate node` 返回 document 给客户端。
### es 搜索数据过程
es 最强大的是做全文检索,就是比如你有三条数据:
-```
+```
java真好玩儿啊
java好难学啊
j2ee特别牛
```
-你根据 `java` 关键词来搜索,将包含 `java` 的 `document` 给搜索出来。es 就会给你返回:java真好玩儿啊,java好难学啊。
+你根据 `java` 关键词来搜索,将包含 `java` 的 `document` 给搜索出来。es 就会给你返回:java 真好玩儿啊,java 好难学啊。
-* 客户端发送请求到一个 `coordinate node` 。
-* 协调节点将搜索请求转发到**所有**的 shard 对应的 `primary shard` 或 `replica shard` ,都可以。
-* query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id` )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
-* fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。
+- 客户端发送请求到一个 `coordinate node` 。
+- 协调节点将搜索请求转发到**所有**的 shard 对应的 `primary shard` 或 `replica shard` ,都可以。
+- query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id` )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
+- fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。
> 写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。
@@ -72,6 +75,9 @@ translog 日志文件的作用是什么?你执行 commit 操作之前,数据
translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会**丢失** 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 `fsync` 到磁盘,但是性能会差很多。
+- `index.translog.sync_interval` 控制 translog 多久 fsync 到磁盘,最小为 100ms;
+- `index.translog.durability` translog 是每 5 秒钟刷新一次还是每次请求都 fsync,这个参数有 2 个取值:request(每次请求都执行 fsync,es 要等 translog fsync 到磁盘后才会返回成功)和 async(默认值,translog 每隔 5 秒钟 fsync 一次)。
+
实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的**数据丢失**。
**总结一下**,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。
@@ -102,29 +108,29 @@ buffer 每 refresh 一次,就会产生一个 `segment file` ,所以默认情
有以下文档:
-| DocId | Doc |
-|---|---|
-| 1 | 谷歌地图之父跳槽 Facebook |
-| 2 | 谷歌地图之父加盟 Facebook |
-| 3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook |
-| 4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 |
-| 5 | 谷歌地图之父拉斯加盟社交网站 Facebook |
+| DocId | Doc |
+| ----- | ---------------------------------------------- |
+| 1 | 谷歌地图之父跳槽 Facebook |
+| 2 | 谷歌地图之父加盟 Facebook |
+| 3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook |
+| 4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 |
+| 5 | 谷歌地图之父拉斯加盟社交网站 Facebook |
对文档进行分词之后,得到以下**倒排索引**。
-| WordId | Word | DocIds |
-|---|---|---|
-| 1 | 谷歌 | 1, 2, 3, 4, 5 |
-| 2 | 地图 | 1, 2, 3, 4, 5 |
-| 3 | 之父 | 1, 2, 4, 5 |
-| 4 | 跳槽 | 1, 4 |
-| 5 | Facebook | 1, 2, 3, 4, 5 |
-| 6 | 加盟 | 2, 3, 5 |
-| 7 | 创始人 | 3 |
-| 8 | 拉斯 | 3, 5 |
-| 9 | 离开 | 3 |
-| 10 | 与 | 4 |
-| .. | .. | .. |
+| WordId | Word | DocIds |
+| ------ | -------- | ------------- |
+| 1 | 谷歌 | 1, 2, 3, 4, 5 |
+| 2 | 地图 | 1, 2, 3, 4, 5 |
+| 3 | 之父 | 1, 2, 4, 5 |
+| 4 | 跳槽 | 1, 4 |
+| 5 | Facebook | 1, 2, 3, 4, 5 |
+| 6 | 加盟 | 2, 3, 5 |
+| 7 | 创始人 | 3 |
+| 8 | 拉斯 | 3, 5 |
+| 9 | 离开 | 3 |
+| 10 | 与 | 4 |
+| .. | .. | .. |
另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。
@@ -132,7 +138,7 @@ buffer 每 refresh 一次,就会产生一个 `segment file` ,所以默认情
要注意倒排索引的两个重要细节:
-* 倒排索引中的所有词项对应一个或多个文档;
-* 倒排索引中的词项**根据字典顺序升序排列**
+- 倒排索引中的所有词项对应一个或多个文档;
+- 倒排索引中的词项**根据字典顺序升序排列**
> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。
diff --git a/docs/high-concurrency/high-concurrency-design.md b/docs/high-concurrency/high-concurrency-design.md
index 3e940e24c..9af4256a2 100644
--- a/docs/high-concurrency/high-concurrency-design.md
+++ b/docs/high-concurrency/high-concurrency-design.md
@@ -1,9 +1,12 @@
+# 如何设计一个高并发系统
+
## 面试题
+
如何设计一个高并发系统?
## 面试官心理分析
-说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先。
+说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发经验者优先。
如果你确实有真才实学,在互联网公司里干过高并发系统,那你确实拿 offer 基本如探囊取物,没啥问题。面试官也绝对不会这样来问你,否则他就是蠢。
@@ -31,12 +34,12 @@
可以分为以下 6 点:
-* 系统拆分
-* 缓存
-* MQ
-* 分库分表
-* 读写分离
-* ElasticSearch
+- 系统拆分
+- 缓存
+- MQ
+- 分库分表
+- 读写分离
+- ElasticSearch

diff --git a/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md b/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md
index 4ac2e27bd..7ad6f9921 100644
--- a/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md
+++ b/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md
@@ -1,9 +1,12 @@
+# 如何保证消息队列的高可用?
+
## 面试题
+
如何保证消息队列的高可用?
## 面试官心理分析
-如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](/docs/high-concurrency/why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。
+如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](./why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。
要是你傻乎乎的就干用了一个 MQ,各种问题从来没考虑过,那你就杯具了,面试官对你的感觉就是,只会简单使用一些技术,没任何思考,马上对你的印象就不太好了。这样的同学招进来要是做个 20k 薪资以内的普通小弟还凑合,要是做薪资 20k+ 的高工,那就惨了,让你设计个系统,里面肯定一堆坑,出了事故公司受损失,团队一起背锅。
@@ -21,11 +24,11 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
#### 单机模式
-单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的😄,没人生产用单机模式。
+单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的,没人生产用单机模式。
#### 普通集群模式(无高可用性)
-普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
+普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每台机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。

@@ -37,7 +40,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
#### 镜像集群模式(高可用性)
-这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。
+这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论是元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。

@@ -51,7 +54,7 @@ Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker
这就是**天然的分布式消息队列**,就是说一个 topic 的数据,是**分散放在多个机器上的,每个机器就放一部分数据**。
-实际上 RabbitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。
+实际上 RabbitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群模式下,也是每个节点都放这个 queue 的完整数据。
Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了,那个 broker 上的 partition 就废了,没法写也没法读,没有什么高可用性可言。
@@ -63,7 +66,7 @@ Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机

-这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。
+这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker 上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。
**写数据**的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)
diff --git a/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md b/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md
index 8fefa0c86..bbadee315 100644
--- a/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md
+++ b/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md
@@ -1,4 +1,7 @@
+# 如何保证 redis 的高并发和高可用?
+
## 面试题
+
如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么?
## 面试官心理分析
@@ -13,8 +16,8 @@
由于此节内容较多,因此,会分为两个小节进行讲解。
-* [redis 主从架构](/docs/high-concurrency/redis-master-slave.md)
-* [redis 基于哨兵实现高可用](/docs/high-concurrency/redis-sentinel.md)
+- [redis 主从架构](./redis-master-slave.md)
+- [redis 基于哨兵实现高可用](./redis-sentinel.md)
redis 实现**高并发**主要依靠**主从架构**,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。
diff --git a/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md b/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md
index db42f806b..4e53f36f7 100644
--- a/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md
+++ b/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md
@@ -1,4 +1,7 @@
+# 如何保证消息不被重复消费?
+
## 面试题
+
如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
## 面试官心理分析
@@ -17,7 +20,9 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一
举个栗子。
-有这么个场景。数据 1/2/3 依次进入 kafka,kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,我们就假设分配的 offset 依次是 152/153/154。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功,那么数据 1/2 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。
+有这么个场景。数据 1/2/3 依次进入 Kafka,Kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,我们就假设分配的 offset 依次是 152/153/154。消费者从 Kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 Zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,Kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 Kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功,那么数据 1/2 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。
+
+注意:新版的 Kafka 已经将 offset 的存储从 Zookeeper 转移至 Kafka brokers,并使用内部位移主题 `__consumer_offsets` 进行存储。

@@ -35,11 +40,11 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一
其实还是得结合业务来思考,我这里给几个思路:
-* 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
-* 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
-* 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
-* 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
+- 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
+- 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
+- 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
+- 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。

-当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。
+当然,如何保证 MQ 的消费是幂等性的,在实际应用中需要结合具体的业务来看。
diff --git a/docs/high-concurrency/how-to-ensure-the-order-of-messages.md b/docs/high-concurrency/how-to-ensure-the-order-of-messages.md
index 69847577b..132608cdb 100644
--- a/docs/high-concurrency/how-to-ensure-the-order-of-messages.md
+++ b/docs/high-concurrency/how-to-ensure-the-order-of-messages.md
@@ -1,4 +1,7 @@
+# 如何保证消息的顺序性?
+
## 面试题
+
如何保证消息的顺序性?
## 面试官心理分析
@@ -15,24 +18,29 @@
先看看顺序会错乱的俩场景:
-* **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。
+- **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者 2 先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。

-* **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。
+- **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。

### 解决方案
#### RabbitMQ
-拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
+
+拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点,这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。

+或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
+
+注意,这里消费者不直接消费消息,而是将消息根据关键值(比如:订单 id)进行哈希,哈希值相同的消息保存到相同的内存队列里。也就是说,需要保证顺序的消息存到了相同的内存队列,然后由一个唯一的 worker 去处理。
+
#### Kafka
-* 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
-* 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
+- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
+- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。

diff --git a/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md b/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md
index de901f9e8..14bbd7658 100644
--- a/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md
+++ b/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md
@@ -1,9 +1,12 @@
+# 如何保证消息的可靠性传输?
+
## 面试题
+
如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题?
## 面试官心理分析
-这个是肯定的,用 MQ 有个基本原则,就是**数据不能多一条,也不能少一条**,不能多,就是前面说的[重复消费和幂等性问题](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)。不能少,就是说这数据别搞丢了。那这个问题你必须得考虑一下。
+这个是肯定的,用 MQ 有个基本原则,就是**数据不能多一条,也不能少一条**,不能多,就是前面说的[重复消费和幂等性问题](./how-to-ensure-that-messages-are-not-repeatedly-consumed.md)。不能少,就是说这数据别搞丢了。那这个问题你必须得考虑一下。
如果说你这个是用 MQ 来传递非常核心的消息,比如说计费、扣费的一些消息,那必须确保这个 MQ 传递过程中**绝对不会把计费消息给弄丢**。
@@ -19,21 +22,29 @@
生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。
-此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务 `channel.txSelect` ,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 `channel.txRollback` ,然后重试发送消息;如果收到了消息,那么可以提交事务 `channel.txCommit` 。
+此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务 `channel.txSelect()` ,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 `channel.txRollback()` ,然后重试发送消息;如果收到了消息,那么可以提交事务 `channel.txCommit()` 。
-``` java
-// 开启事务
-channel.txSelect
+```java
try {
+ // 通过工厂创建连接
+ connection = factory.newConnection();
+ // 获取通道
+ channel = connection.createChannel();
+ // 开启事务
+ channel.txSelect();
+
// 这里发送消息
-} catch (Exception e) {
- channel.txRollback
+ channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
- // 这里再次重发这条消息
-}
+ // 模拟出现异常
+ int result = 1 / 0;
-// 提交事务
-channel.txCommit
+ // 提交事务
+ channel.txCommit();
+} catch (IOException | TimeoutException e) {
+ // 捕捉异常,回滚事务
+ channel.txRollback();
+}
```
但是问题是,RabbitMQ 事务机制(同步)一搞,基本上**吞吐量会下来,因为太耗性能**。
@@ -44,19 +55,73 @@ channel.txCommit
所以一般在生产者这块**避免数据丢失**,都是用 `confirm` 机制的。
+> 已经在 transaction 事务模式的 channel 是不能再设置成 confirm 模式的,即这两种模式是不能共存的。
+
+客户端实现生产者 `confirm` 有 3 种方式:
+
+1.**普通 confirm 模式**:每发送一条消息后,调用 `waitForConfirms()` 方法,等待服务器端 confirm,如果服务端返回 false 或者在一段时间内都没返回,客户端可以进行消息重发。
+
+```java
+channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
+if (!channel.waitForConfirms()) {
+ // 消息发送失败
+ // ...
+}
+```
+
+2.**批量 confirm 模式**:每发送一批消息后,调用 `waitForConfirms()` 方法,等待服务端 confirm。
+
+```java
+channel.confirmSelect();
+for (int i = 0; i < batchCount; ++i) {
+ channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
+}
+if (!channel.waitForConfirms()) {
+ // 消息发送失败
+ // ...
+}
+```
+
+3.**异步 confirm 模式**:提供一个回调方法,服务端 confirm 了一条或者多条消息后客户端会回调这个方法。
+
+```java
+SortedSet confirmSet = Collections.synchronizedSortedSet(new TreeSet());
+channel.confirmSelect();
+channel.addConfirmListener(new ConfirmListener() {
+ public void handleAck(long deliveryTag, boolean multiple) throws IOException {
+ if (multiple) {
+ confirmSet.headSet(deliveryTag + 1).clear();
+ } else {
+ confirmSet.remove(deliveryTag);
+ }
+ }
+
+ public void handleNack(long deliveryTag, boolean multiple) throws IOException {
+ System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
+ if (multiple) {
+ confirmSet.headSet(deliveryTag + 1).clear();
+ } else {
+ confirmSet.remove(deliveryTag);
+ }
+ }
+});
+
+while (true) {
+ long nextSeqNo = channel.getNextPublishSeqNo();
+ channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
+ confirmSet.add(nextSeqNo);
+}
+```
+
#### RabbitMQ 弄丢了数据
就是 RabbitMQ 自己弄丢了数据,这个你必须**开启 RabbitMQ 的持久化**,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,**恢复之后会自动读取之前存储的数据**,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,**可能导致少量数据丢失**,但是这个概率较小。
设置持久化有**两个步骤**:
-* 创建 queue 的时候将其设置为持久化
-
-这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
+- 创建 queue 的时候将其设置为持久化。这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
-* 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2
-
-就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
+- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2。就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。
@@ -70,11 +135,14 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
这个时候得用 RabbitMQ 提供的 `ack` 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 `ack` ,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 `ack` 一把。这样的话,如果你还没处理完,不就没有 `ack` 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
+> 为了保证消息从队列中可靠地到达消费者,RabbitMQ 提供了消息确认机制。消费者在声明队列时,可以指定 noAck 参数,当 noAck=false,RabbitMQ 会等待消费者显式发回 ack 信号后,才从内存(和磁盘,如果是持久化消息)中移去消息。否则,一旦消息被消费者消费,RabbitMQ 会在队列中立即删除它。
+

### Kafka
#### 消费端弄丢了数据
+
唯一可能导致消费者弄丢数据的情况,就是说,你消费到了这个消息,然后消费者那边**自动提交了 offset**,让 Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。
这不是跟 RabbitMQ 差不多吗,大家都知道 Kafka 会自动提交 offset,那么只要**关闭自动提交** offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是**可能会有重复消费**,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。
@@ -89,13 +157,54 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
所以此时一般是要求起码设置如下 4 个参数:
-* 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
-* 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。
-* 在 producer 端设置 `acks=all` :这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。
-* 在 producer 端设置 `retries=MAX` (很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。
+- 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
+- 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。
+- 在 producer 端设置 `acks=all` :这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。
+- 在 producer 端设置 `retries=MAX` (很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。
我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。
#### 生产者会不会弄丢数据?
如果按照上述的思路设置了 `acks=all` ,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
+
+### RocketMQ
+
+#### 消息丢失的场景
+
+1. 生产者发送消息到 MQ 有可能丢失消息
+2. MQ 收到消息后写入硬盘可能丢失消息
+3. 消息写入硬盘后,硬盘坏了丢失消息
+4. 消费者消费 MQ 也可能丢失消息
+5. 整个 MQ 节点挂了丢失消息
+
+#### 生产者发送消息时如何保证不丢失?
+
+解决发送时消息丢失的问题可以采用 RocketMQ 自带的**事务消息**机制
+
+事务消息原理:首先生产者会发送一个**half 消息**(对原始消息的封装),该消息对消费者不可见,MQ 通过 ACK 机制返回消息接受状态, 生产者执行本地事务并且返回给 MQ 一个状态(Commit、RollBack 等),如果是 Commit 的话 MQ 就会把消息给到下游, RollBack 的话就会丢弃该消息,状态如果为 UnKnow 的话会过一段时间回查本地事务状态,默认回查 15 次,一直是 UnKnow 状态的话就会丢弃此消息。
+
+为什么先发一个 half 消息,作用就是先判断下 MQ 有没有问题,服务正不正常。
+
+#### MQ 收到消息后写入硬盘如何保证不丢失?
+
+数据存盘绕过缓存,改为同步刷盘,这一步需要修改 Broker 的配置文件,将 flushDiskType 改为 SYNC_FLUSH 同步刷盘策略,默认的是 ASYNC_FLUSH 异步刷盘,一旦同步刷盘返回成功,那么就一定保证消息已经持久化到磁盘中了。
+
+#### 消息写入硬盘后,硬盘坏了如何保证不丢失?
+
+为了保证磁盘损坏导致丢失数据,RocketMQ 采用主从机构,集群部署,Leader 中的数据在多个 Follower 中都存有备份,防止单点故障导致数据丢失。
+
+Master 节点挂了怎么办?Master 节点挂了之后 DLedger 登场
+
+- 接管 MQ 的 commitLog
+- 选举从节点
+- 文件复制 uncommited 状态 多半从节点收到之后改为 commited
+
+#### 消费者消费 MQ 如何保证不丢失?
+
+1. 如果是网络问题导致的消费失败可以进行重试机制,默认每条消息重试 16 次
+2. 多线程异步消费失败,MQ 认为已经消费成功但是实际上对于业务逻辑来说消息是没有落地的,解决方案就是按照 mq 官方推荐的先执行本地事务再返回成功状态。
+
+#### 整个 MQ 节点挂了如何保证不丢失?
+
+这种极端情况可以消息发送失败之后先存入本地,例如放到缓存中,另外启动一个线程扫描缓存的消息去重试发送。
diff --git a/docs/high-concurrency/huifer-how-to-limit-current.md b/docs/high-concurrency/how-to-limit-current.md
similarity index 61%
rename from docs/high-concurrency/huifer-how-to-limit-current.md
rename to docs/high-concurrency/how-to-limit-current.md
index cffc1b0de..d3616aea1 100644
--- a/docs/high-concurrency/huifer-how-to-limit-current.md
+++ b/docs/high-concurrency/how-to-limit-current.md
@@ -1,8 +1,5 @@
# 如何限流?在工作中是怎么做的?说一下具体的实现?
-* Author: [HuiFer](https://github.com/huifer)
-* Description: 该文简单介绍限流相关技术以及实现
-
## 什么是限流
> 限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。
@@ -13,9 +10,9 @@
#### 实现方式
-* 控制单位时间内的请求数量
+控制单位时间内的请求数量。
-``` java
+```java
import java.util.concurrent.atomic.AtomicInteger;
@@ -54,18 +51,19 @@ public class Counter {
```
-* 劣势
- - 假设在 00:01 时发生一个请求,在 00:01-00:58 之间不在发送请求,在 00:59 时发送剩下的所有请求 `n-1` (n为限流请求数量),在下一分钟的 00:01 发送n个请求,这样在2秒钟内请求到达了 `2n - 1` 个.
- - 设每分钟请求数量为60个,每秒可以处理1个请求,用户在 00:59 发送 60 个请求,在 01:00 发送 60 个请求 此时2秒钟有120个请求(每秒60个请求),远远大于了每秒钟处理数量的阈值
+劣势:
+
+假设在 00:01 时发生一个请求,在 00:01-00:58 之间不在发送请求,在 00:59 时发送剩下的所有请求 `n-1` (n 为限流请求数量),在下一分钟的 00:01 发送 n 个请求,这样在 2 秒钟内请求到达了 `2n - 1` 个。
+
+设每分钟请求数量为 60 个,每秒可以处理 1 个请求,用户在 00:59 发送 60 个请求,在 01:00 发送 60 个请求 此时 2 秒钟有 120 个请求(每秒 60 个请求),远远大于了每秒钟处理数量的阈值。
### 滑动窗口
#### 实现方式
-* 滑动窗口是对计数器方式的改进, 增加一个时间粒度的度量单位
- - 把一分钟分成若干等分(6份,每份10秒), 在每一份上设置独立计数器,在 00:00-00:09 之间发生请求计数器累加1.当等分数量越大限流统计就越详细
+滑动窗口是对计数器方式的改进,增加一个时间粒度的度量单位,把一分钟分成若干等分(6 份,每份 10 秒),在每一份上设置独立计数器,在 00:00-00:09 之间发生请求计数器累加 1。当等分数量越大限流统计就越详细。
-``` java
+```java
package com.example.demo1.service;
import java.util.Iterator;
@@ -86,7 +84,7 @@ public class TimeWindow {
*/
private int max;
- public TimeWindow(int max, int seconds) {
+ public TimeWindow(int max, int seconds) {
this.seconds = seconds;
this.max = max;
@@ -109,10 +107,10 @@ public class TimeWindow {
public static void main(String[] args) throws Exception {
- final TimeWindow timeWindow = new TimeWindow(10, 1);
+ final TimeWindow timeWindow = new TimeWindow(10, 1);
// 测试3个线程
- IntStream.range(0, 3).forEach((i) -> {
+ IntStream.range(0, 3).forEach((i) -> {
new Thread(() -> {
while (true) {
@@ -193,9 +191,9 @@ public class TimeWindow {
#### 实现方式
-* 规定固定容量的桶, 有水进入, 有水流出. 对于流进的水我们无法估计进来的数量、速度, 对于流出的水我们可以控制速度.
+规定固定容量的桶,有水进入,有水流出。对于流进的水我们无法估计进来的数量、速度,对于流出的水我们可以控制速度。
-``` java
+```java
public class LeakBucket {
/**
* 时间
@@ -216,7 +214,7 @@ public class LeakBucket {
public boolean limit() {
long now = System.currentTimeMillis();
- nowSize = Math.max(0, (nowSize - (now - time) * rate));
+ nowSize = Math.max(0, (nowSize - (now - time) * rate));
time = now;
if ((nowSize + 1) < total) {
nowSize++;
@@ -233,9 +231,9 @@ public class LeakBucket {
#### 实现方式
-* 规定固定容量的桶, token 以固定速度往桶内填充, 当桶满时 token 不会被继续放入, 每过来一个请求把 token 从桶中移除, 如果桶中没有 token 不能请求
+规定固定容量的桶, token 以固定速度往桶内填充, 当桶满时 token 不会被继续放入, 每过来一个请求把 token 从桶中移除, 如果桶中没有 token 不能请求。
-``` java
+```java
public class TokenBucket {
/**
* 时间
@@ -256,7 +254,7 @@ public class TokenBucket {
public boolean limit() {
long now = System.currentTimeMillis();
- nowSize = Math.min(total, nowSize + (now - time) * rate);
+ nowSize = Math.min(total, nowSize + (now - time) * rate);
time = now;
if (nowSize < 1) {
// 桶里没有token
@@ -275,9 +273,9 @@ public class TokenBucket {
### spring cloud gateway
-* spring cloud gateway 默认使用redis进行限流, 笔者一般只是修改修改参数属于拿来即用. 并没有去从头实现上述那些算法.
+- spring cloud gateway 默认使用 redis 进行限流,笔者一般只是修改修改参数属于拿来即用,并没有去从头实现上述那些算法。
-``` xml
+```xml
org.springframework.cloud
spring-cloud-starter-gateway
@@ -288,34 +286,30 @@ public class TokenBucket {
```
-``` yaml
+```yaml
spring:
- cloud:
- gateway:
- routes:
-
- - id: requestratelimiter_route
-
- uri: lb://pigx-upms
- order: 10000
- predicates:
-
- - Path=/admin/**
-
- filters:
+ cloud:
+ gateway:
+ routes:
+ - id: requestratelimiter_route
- - name: RequestRateLimiter
+ uri: lb://pigx-upms
+ order: 10000
+ predicates:
+ - Path=/admin/**
- args:
- redis-rate-limiter.replenishRate: 1 # 令牌桶的容积
- redis-rate-limiter.burstCapacity: 3 # 流速 每秒
- key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
+ filters:
+ - name: RequestRateLimiter
- - StripPrefix=1
+ args:
+ redis-rate-limiter.replenishRate: 1 # 令牌桶的容积
+ redis-rate-limiter.burstCapacity: 3 # 流速 每秒
+ key-resolver: '#{@remoteAddrKeyResolver}' #SPEL表达式去的对应的bean
+ - StripPrefix=1
```
-``` java
+```java
@Bean
KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
@@ -324,38 +318,38 @@ KeyResolver remoteAddrKeyResolver() {
### sentinel
-* 通过配置来控制每个url的流量
+- 通过配置来控制每个 url 的流量
-``` xml
+```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
```
-``` yaml
+```yaml
spring:
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848
- sentinel:
- transport:
- dashboard: localhost:8080
- port: 8720
- datasource:
- ds:
- nacos:
- server-addr: localhost:8848
- dataId: spring-cloud-sentinel-nacos
- groupId: DEFAULT_GROUP
- rule-type: flow
- namespace: xxxxxxxx
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848
+ sentinel:
+ transport:
+ dashboard: localhost:8080
+ port: 8720
+ datasource:
+ ds:
+ nacos:
+ server-addr: localhost:8848
+ dataId: spring-cloud-sentinel-nacos
+ groupId: DEFAULT_GROUP
+ rule-type: flow
+ namespace: xxxxxxxx
```
-* 配置内容在nacos上进行编辑
+- 配置内容在 nacos 上进行编辑
-``` json
+```json
[
{
"resource": "/hello",
@@ -369,14 +363,14 @@ spring:
]
```
-* resource:资源名,即限流规则的作用对象。
-* limitApp:流控针对的调用来源,若为 default 则不区分调用来源。
-* grade:限流阈值类型,QPS 或线程数模式,0代表根据并发数量来限流,1代表根据QPS来进行流量控制。
-* count:限流阈值
-* strategy:判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口
-* controlBehavior:流控效果(直接拒绝 / 排队等待 / 慢启动模式)
-* clusterMode:是否为集群模式
+- resource:资源名,即限流规则的作用对象。
+- limitApp:流控针对的调用来源,若为 default 则不区分调用来源。
+- grade:限流阈值类型,QPS 或线程数模式,0 代表根据并发数量来限流,1 代表根据 QPS 来进行流量控制。
+- count:限流阈值
+- strategy:判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口
+- controlBehavior:流控效果(直接拒绝 / 排队等待 / 慢启动模式)
+- clusterMode:是否为集群模式
### 总结
-> sentinel和spring cloud gateway两个框架都是很好的限流框架, 但是在我使用中还没有将[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)接入到项目中进行使用, 所以我会选择**spring cloud gateway**, 当接入完整的或者接入Nacos项目使用setinel会有更加好的体验.
+> sentinel 和 spring cloud gateway 两个框架都是很好的限流框架, 但是在我使用中还没有将[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)接入到项目中进行使用, 所以我会选择**spring cloud gateway**, 当接入完整的或者接入 Nacos 项目使用 setinel 会有更加好的体验.
diff --git a/docs/high-concurrency/images/favicon-16x16.png b/docs/high-concurrency/images/favicon-16x16.png
deleted file mode 100644
index f0ee715c7..000000000
Binary files a/docs/high-concurrency/images/favicon-16x16.png and /dev/null differ
diff --git a/docs/high-concurrency/images/favicon-32x32.png b/docs/high-concurrency/images/favicon-32x32.png
deleted file mode 100644
index c90d7f588..000000000
Binary files a/docs/high-concurrency/images/favicon-32x32.png and /dev/null differ
diff --git a/docs/high-concurrency/images/icon.png b/docs/high-concurrency/images/icon.png
deleted file mode 100644
index fdcfeee2d..000000000
Binary files a/docs/high-concurrency/images/icon.png and /dev/null differ
diff --git a/docs/high-concurrency/images/lru-cache.png b/docs/high-concurrency/images/lru-cache.png
new file mode 100644
index 000000000..bf54c10b8
Binary files /dev/null and b/docs/high-concurrency/images/lru-cache.png differ
diff --git a/docs/high-concurrency/images/lru.png b/docs/high-concurrency/images/lru.png
new file mode 100644
index 000000000..bc2f084c0
Binary files /dev/null and b/docs/high-concurrency/images/lru.png differ
diff --git a/docs/high-concurrency/images/qrcode-for-doocs.jpg b/docs/high-concurrency/images/qrcode-for-doocs.jpg
deleted file mode 100644
index bd1db5d11..000000000
Binary files a/docs/high-concurrency/images/qrcode-for-doocs.jpg and /dev/null differ
diff --git a/docs/high-concurrency/images/qrcode-for-yanglbme.jpg b/docs/high-concurrency/images/qrcode-for-yanglbme.jpg
deleted file mode 100644
index 5bd385bca..000000000
Binary files a/docs/high-concurrency/images/qrcode-for-yanglbme.jpg and /dev/null differ
diff --git a/docs/high-concurrency/images/redis-caching-avoid-penetration.png b/docs/high-concurrency/images/redis-caching-avoid-penetration.png
new file mode 100644
index 000000000..b978620aa
Binary files /dev/null and b/docs/high-concurrency/images/redis-caching-avoid-penetration.png differ
diff --git a/docs/high-concurrency/mq-design.md b/docs/high-concurrency/mq-design.md
index dbb746746..c81f85eb1 100644
--- a/docs/high-concurrency/mq-design.md
+++ b/docs/high-concurrency/mq-design.md
@@ -1,12 +1,15 @@
+# 如何设计一个消息队列?
+
## 面试题
+
如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。
## 面试官心理分析
其实聊到这个问题,一般面试官要考察两块:
-* 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
-* 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
+- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
+- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,**大多数人就是平时埋头用,从来不去思考背后的一些东西**。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做?
@@ -16,12 +19,12 @@
比如说这个消息队列系统,我们从以下几个角度来考虑一下:
-* 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
+- 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
-* 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。
+- 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。
-* 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
+- 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
-* 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。
+- 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。
mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。
diff --git a/docs/high-concurrency/mq-interview.md b/docs/high-concurrency/mq-interview.md
index 25abde680..e89b2aa60 100644
--- a/docs/high-concurrency/mq-interview.md
+++ b/docs/high-concurrency/mq-interview.md
@@ -1,4 +1,4 @@
-## 消息队列面试场景
+# 消息队列面试场景
**面试官**:你好。
diff --git a/docs/high-concurrency/mq-time-delay-and-expired-failure.md b/docs/high-concurrency/mq-time-delay-and-expired-failure.md
index 7561864d2..4a0ad9887 100644
--- a/docs/high-concurrency/mq-time-delay-and-expired-failure.md
+++ b/docs/high-concurrency/mq-time-delay-and-expired-failure.md
@@ -1,4 +1,7 @@
+# 如何解决消息队列的延时以及过期失效问题?
+
## 面试题
+
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
## 面试官心理分析
@@ -19,20 +22,68 @@
一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:
-* 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。
-* 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
-* 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
-* 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
-* 等快速消费完积压数据之后,**得恢复原先部署的架构**,**重新**用原先的 consumer 机器来消费消息。
+- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。
+- 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
+- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
+- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
+- 等快速消费完积压数据之后,**得恢复原先部署的架构**,**重新**用原先的 consumer 机器来消费消息。
### mq 中的消息过期失效了
假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是**大量的数据会直接搞丢**。
-这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是**批量重导**,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。
+这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是**批量重导**,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上 12 点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。
假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
### mq 都快写满了
如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。
+
+---
+
+对于 RocketMQ,官方针对消息积压问题,提供了解决方案。
+
+### 1. 提高消费并行度
+
+绝大部分消息消费行为都属于 IO 密集型,即可能是操作数据库,或者调用 RPC,这类消费行为的消费速度在于后端数据库或者外系统的吞吐量,通过增加消费并行度,可以提高总的消费吞吐量,但是并行度增加到一定程度,反而会下降。所以,应用必须要设置合理的并行度。 如下有几种修改消费并行度的方法:
+
+同一个 ConsumerGroup 下,通过增加 Consumer 实例数量来提高并行度(需要注意的是超过订阅队列数的 Consumer 实例无效)。可以通过加机器,或者在已有机器启动多个进程的方式。
+提高单个 Consumer 的消费并行线程,通过修改参数 consumeThreadMin、consumeThreadMax 实现。
+
+### 2. 批量方式消费
+
+某些业务流程如果支持批量方式消费,则可以很大程度上提高消费吞吐量,例如订单扣款类应用,一次处理一个订单耗时 1 s,一次处理 10 个订单可能也只耗时 2 s,这样即可大幅度提高消费的吞吐量,通过设置 consumer 的 consumeMessageBatchMaxSize 返个参数,默认是 1,即一次只消费一条消息,例如设置为 N,那么每次消费的消息数小于等于 N。
+
+### 3. 跳过非重要消息
+
+发生消息堆积时,如果消费速度一直追不上发送速度,如果业务对数据要求不高的话,可以选择丢弃不重要的消息。例如,当某个队列的消息数堆积到 100000 条以上,则尝试丢弃部分或全部消息,这样就可以快速追上发送消息的速度。示例代码如下:
+
+```java
+public ConsumeConcurrentlyStatus consumeMessage(
+ List msgs,
+ ConsumeConcurrentlyContext context) {
+ long offset = msgs.get(0).getQueueOffset();
+ String maxOffset =
+ msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET);
+ long diff = Long.parseLong(maxOffset) - offset;
+ if (diff > 100000) {
+ // TODO 消息堆积情况的特殊处理
+ return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
+ }
+ // TODO 正常消费过程
+ return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
+}
+```
+
+### 4. 优化每条消息消费过程
+
+举例如下,某条消息的消费过程如下:
+
+- 根据消息从 DB 查询【数据 1】
+- 根据消息从 DB 查询【数据 2】
+- 复杂的业务计算
+- 向 DB 插入【数据 3】
+- 向 DB 插入【数据 4】
+
+这条消息的消费过程中有 4 次与 DB 的 交互,如果按照每次 5ms 计算,那么总共耗时 20ms,假设业务计算耗时 5ms,那么总过耗时 25ms,所以如果能把 4 次 DB 交互优化为 2 次,那么总耗时就可以优化到 15ms,即总体性能提高了 40%。所以应用如果对时延敏感的话,可以把 DB 部署在 SSD 硬盘,相比于 SCSI 磁盘,前者的 RT 会小很多。
diff --git a/docs/high-concurrency/mysql-read-write-separation.md b/docs/high-concurrency/mysql-read-write-separation.md
index a1358bdcb..b35b083dd 100644
--- a/docs/high-concurrency/mysql-read-write-separation.md
+++ b/docs/high-concurrency/mysql-read-write-separation.md
@@ -1,4 +1,7 @@
+# 如何实现读写分离?
+
## 面试题
+
你们有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决 MySQL 主从同步的延时问题?
## 面试官心理分析
@@ -8,6 +11,7 @@
## 面试题剖析
### 如何实现 MySQL 的读写分离?
+
其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。
### MySQL 主从复制原理的是啥?
@@ -34,15 +38,15 @@
我们通过 MySQL 命令:
-``` sql
-show status
+```sql
+show slave status
```
查看 `Seconds_Behind_Master` ,可以看到从库复制主库的数据落后了几 ms。
一般来说,如果主从延迟较为严重,有以下解决方案:
-* 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
-* 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
-* 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
-* 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。
+- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
+- 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
+- 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
+- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。
diff --git a/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md b/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md
index 30c6a1f4d..05be3b92a 100644
--- a/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md
+++ b/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md
@@ -1,4 +1,7 @@
+# 缓存雪崩、穿透和击穿
+
## 面试题
+
了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?
## 面试官心理分析
@@ -7,7 +10,8 @@
## 面试题剖析
-### 缓存雪崩
+### 缓存雪崩(Cache Avalanche)
+
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
这就是缓存雪崩。
@@ -18,9 +22,9 @@
缓存雪崩的事前事中事后的解决方案如下:
-* 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
-* 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
-* 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
+- 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
+- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
+- 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

@@ -30,13 +34,13 @@
好处:
-* 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
-* 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
-* 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
+- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
+- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
+- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
-### 缓存穿透
+### 缓存穿透(Cache Penetration)
-对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
+对于系统 A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
@@ -46,12 +50,21 @@
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN` 。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
-### 缓存击穿
+当然,如果黑客如果每次使用不同的负数 id 来攻击,写空值的方法可能就不奏效了。更为经常的做法是在缓存之前增加布隆过滤器,将数据库中所有可能的数据哈希映射到布隆过滤器中。然后对每个请求进行如下判断:
+
+- 请求数据的 key 不存在于布隆过滤器中,可以确定数据就一定不会存在于数据库中,系统可以立即返回不存在。
+- 请求数据的 key 存在于布隆过滤器中,则继续再向缓存中查询。
+
+使用布隆过滤器能够对访问的请求起到了一定的初筛作用,避免了因数据不存在引起的查询压力。
+
+
+
+### 缓存击穿(Hotspot Invalid)
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
不同场景下的解决方式可如下:
-* 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
-* 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
-* 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
+- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
+- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
+- 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
diff --git a/docs/high-concurrency/redis-cas.md b/docs/high-concurrency/redis-cas.md
index 2d96ab772..c1852bf8e 100644
--- a/docs/high-concurrency/redis-cas.md
+++ b/docs/high-concurrency/redis-cas.md
@@ -1,4 +1,7 @@
+# Redis 的并发竞争问题
+
## 面试题
+
Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?
## 面试官心理分析
diff --git a/docs/high-concurrency/redis-cluster.md b/docs/high-concurrency/redis-cluster.md
index 54403a51a..69c3d4978 100644
--- a/docs/high-concurrency/redis-cluster.md
+++ b/docs/high-concurrency/redis-cluster.md
@@ -1,4 +1,7 @@
+# Redis 集群模式原理
+
## 面试题
+
Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
## 面试官心理分析
@@ -17,19 +20,20 @@ Redis cluster,主要是针对**海量数据+高并发+高可用**的场景。R
### Redis cluster 介绍
-* 自动将数据进行分片,每个 master 上放一部分数据
-* 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
+- 自动将数据进行分片,每个 master 上放一部分数据
+- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
-在 Redis cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
+在 Redis cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是 加 1w 的端口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议, `gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
### 节点间的内部通信机制
#### 基本通信原理
+
集群元数据的维护有两种方式:集中式、Gossip 协议。Redis cluster 节点间采用 gossip 协议进行通信。
-**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm` 。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
+**集中式**是将集群元数据(节点信息、故障等等)集中存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm` 。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。

@@ -41,25 +45,25 @@ Redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节
gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。
-* 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong` 。
+- 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong` 。
-* 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。
+- 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。
#### gossip 协议
gossip 协议包含多种消息,包含 `ping` , `pong` , `meet` , `fail` 等等。
-* meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。
+- meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。
-``` bash
+```bash
Redis-trib.rb add-node
```
其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。
-* ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
-* pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。
-* fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
+- ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
+- pong:返回 ping 和 meet,包含自己的状态和其它信息,也用于信息广播和更新。
+- fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
#### ping 消息深入
@@ -71,9 +75,9 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担
### 分布式寻址算法
-* hash 算法(大量缓存重建)
-* 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
-* Redis cluster 的 hash slot 算法
+- hash 算法(大量缓存重建)
+- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
+- Redis cluster 的 hash slot 算法
#### hash 算法
diff --git a/docs/high-concurrency/redis-consistence.md b/docs/high-concurrency/redis-consistence.md
index 31f2f411e..150ba6704 100644
--- a/docs/high-concurrency/redis-consistence.md
+++ b/docs/high-concurrency/redis-consistence.md
@@ -1,4 +1,7 @@
+# 缓存与数据库一致性问题
+
## 面试题
+
如何保证缓存与数据库的双写一致性?
## 面试官心理分析
@@ -15,8 +18,8 @@
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
-* 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
-* 更新的时候,**先更新数据库,然后再删除缓存**。
+- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
+- 更新的时候,**先更新数据库,然后再删除缓存**。
**为什么是删除缓存,而不是更新缓存?**
@@ -36,7 +39,21 @@

-解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
+解决思路 1:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
+
+解决思路 2:延时双删。依旧是先更新数据库,再删除缓存,唯一不同的是,我们把这个删除的动作,在不久之后再执行一次,比如 5s 之后。
+
+```java
+public void set(key, value) {
+ putToDb(key, value);
+ deleteFromRedis(key);
+
+ // ... a few seconds later
+ deleteFromRedis(key);
+}
+```
+
+删除的动作,可以有多种选择,比如:1. 使用 `DelayQueue`,会随着 JVM 进程的死亡,丢失更新的风险;2. 放在 `MQ`,但编码复杂度为增加。总之,我们需要综合各种因素去做设计,选择一个最合理的解决方案。
### 比较复杂的数据不一致问题分析
@@ -60,13 +77,13 @@
高并发的场景下,该解决方案要注意的问题:
-* 读请求长时阻塞
+- 读请求长时阻塞
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。
-另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。
+另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 \* 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。
一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。
@@ -80,19 +97,19 @@
经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。
-* 读请求并发量过高
+- 读请求并发量过高
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
-* 多服务实例部署的请求路由
+- 多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。
比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。
-* 热点商品的路由问题,导致请求的倾斜
+- 热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
diff --git a/docs/high-concurrency/redis-data-types.md b/docs/high-concurrency/redis-data-types.md
index abf57e582..1681ea2af 100644
--- a/docs/high-concurrency/redis-data-types.md
+++ b/docs/high-concurrency/redis-data-types.md
@@ -1,4 +1,7 @@
+# Redis 数据类型和使用场景
+
## 面试题
+
Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
## 面试官心理分析
@@ -7,8 +10,8 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
其实问这个问题,主要有两个原因:
-* 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
-* 看看你在实际项目里都怎么玩儿过 Redis。
+- 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
+- 看看你在实际项目里都怎么玩儿过 Redis。
要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。
@@ -16,11 +19,11 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
Redis 主要有以下几种数据类型:
-* Strings
-* Hashes
-* Lists
-* Sets
-* Sorted Sets
+- Strings
+- Hashes
+- Lists
+- Sets
+- Sorted Sets
> Redis 除了这 5 种数据类型之外,还有 Bitmaps、HyperLogLogs、Streams 等。
@@ -28,7 +31,7 @@ Redis 主要有以下几种数据类型:
这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。
-``` bash
+```bash
set college szu
```
@@ -36,19 +39,19 @@ set college szu
这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是**这个对象没嵌套其他的对象**)给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 hash 里的**某个字段**。
-``` bash
+```bash
hset person name bingo
hset person age 20
hset person id 1
hget person name
```
-``` json
-person = {
- "name": "bingo",
- "age": 20,
- "id": 1
-}
+```json
+(person = {
+ "name": "bingo",
+ "age": 20,
+ "id": 1
+})
```
### Lists
@@ -59,14 +62,14 @@ Lists 是有序列表,这个可以玩儿出很多花样。
比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。
-``` bash
+```bash
# 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。
lrange mylist 0 -1
```
比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。
-``` bash
+```bash
lpush mylist 1
lpush mylist 2
lpush mylist 3 4 5
@@ -85,7 +88,7 @@ Sets 是无序集合,自动去重。
把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。
-``` bash
+```bash
#-------操作一个set-------
# 添加元素
sadd mySet 1
@@ -124,7 +127,7 @@ sdiff yourSet mySet
Sorted Sets 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。
-``` bash
+```bash
zadd board 85 zhangsan
zadd board 72 lisi
zadd board 96 wangwu
diff --git a/docs/high-concurrency/redis-expiration-policies-and-lru.md b/docs/high-concurrency/redis-expiration-policies-and-lru.md
index 7fccd942d..54da3fe22 100644
--- a/docs/high-concurrency/redis-expiration-policies-and-lru.md
+++ b/docs/high-concurrency/redis-expiration-policies-and-lru.md
@@ -1,4 +1,7 @@
+# Redis 的过期策略和 LRU 算法
+
## 面试题
+
Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?
## 面试官心理分析
@@ -7,7 +10,7 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
常见的有两个问题:
-* 往 Redis 写入的数据怎么没了?
+- 往 Redis 写入的数据怎么没了?
可能有同学会遇到,在生产环境的 Redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 Redis 你就没用对啊。Redis 是缓存,你给当存储了是吧?
@@ -15,13 +18,14 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
那既然内存是有限的,比如 Redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
-* 数据明明过期了,怎么还占用着内存?
+- 数据明明过期了,怎么还占用着内存?
这是由 Redis 的过期策略来决定。
## 面试题剖析
### Redis 过期策略
+
Redis 过期策略是:**定期删除+惰性删除**。
所谓**定期删除**,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
@@ -40,42 +44,53 @@ Redis 过期策略是:**定期删除+惰性删除**。
Redis 内存淘汰机制有以下几个:
-* noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
-* **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。
-* allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
-* volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。
-* volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。
-* volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。
+- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
+- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。
+- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
+- volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。
+- volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。
+- volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。
### 手写一个 LRU 算法
+LRU 就是 Least Recently Used 的缩写,翻译过来就是“最近最少使用”。也就是说 LRU 算法会将最近最少用的缓存移除,让给最新使用的缓存。而往往最常读取的,也就是读取次数最多的,所以利用好 LRU 算法,我们能够提供对热点数据的缓存效率,能够提高缓存服务的内存使用率。
+
+那么如何实现呢?
+
+其实,实现的思路非常简单,就像下面这张图种描述的一样。
+
+
+
你可以现场手写最原始的 LRU 算法,那个代码量太大了,似乎不太现实。
不求自己纯手工从底层开始打造出自己的 LRU,但是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。
-``` java
-class LRUCache extends LinkedHashMap {
- private final int CACHE_SIZE;
+
+
+```java
+public class LRUCache extends LinkedHashMap {
+ private int capacity;
/**
* 传递进来最多能缓存多少数据
*
- * @param cacheSize 缓存大小
+ * @param capacity 缓存大小
*/
- public LRUCache(int cacheSize) {
- // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
- super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
- CACHE_SIZE = cacheSize;
+ public LRUCache(int capacity) {
+ super(capacity, 0.75f, true);
+ this.capacity = capacity;
}
/**
- * 钩子方法,通过put新增键值对的时候,若该方法返回true
- * 便移除该map中最老的键和值
+ * 如果map中的数据量大于设定的最大容量,返回true,再新加入对象时删除最老的数据
+ *
+ * @param eldest 最老的数据项
+ * @return true则移除最老的数据
*/
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
- // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
- return size() > CACHE_SIZE;
+ // 当 map中的数据量大于指定的缓存个数的时候,自动移除最老的数据
+ return size() > capacity;
}
}
```
diff --git a/docs/high-concurrency/redis-master-slave.md b/docs/high-concurrency/redis-master-slave.md
index 47fe1b44f..bcb451e88 100644
--- a/docs/high-concurrency/redis-master-slave.md
+++ b/docs/high-concurrency/redis-master-slave.md
@@ -8,16 +8,16 @@ Redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并
## Redis replication 的核心机制
-* Redis 采用**异步方式**复制数据到 slave 节点,不过 Redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
-* 一个 master node 是可以配置多个 slave node 的;
-* slave node 也可以连接其他的 slave node;
-* slave node 做复制的时候,不会 block master node 的正常工作;
-* slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
-* slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
+- Redis 采用**异步方式**复制数据到 slave 节点,不过 Redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
+- 一个 master node 是可以配置多个 slave node 的;
+- slave node 也可以连接其他的 slave node;
+- slave node 做复制的时候,不会 block master node 的正常工作;
+- slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
+- slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
-注意,如果采用了主从架构,那么建议必须**开启** master node 的[持久化](/docs/high-concurrency/redis-persistence.md),不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
+注意,如果采用了主从架构,那么建议必须**开启** master node 的[持久化](./redis-persistence.md),不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
-另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能**确保启动的时候,是有数据的**,即使采用了后续讲解的[高可用机制](/docs/high-concurrency/redis-sentinel.md),slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。
+另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能**确保启动的时候,是有数据的**,即使采用了后续讲解的[高可用机制](./redis-sentinel.md),slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。
## Redis 主从复制的核心原理
@@ -39,7 +39,7 @@ master node 会在内存中维护一个 backlog,master 和 slave 都会保存
master 在内存中直接创建 `RDB` ,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 `repl-diskless-sync yes` 即可。
-``` bash
+```bash
repl-diskless-sync yes
# 等待 5s 后再开始复制,因为要等更多 slave 重新连接过来
@@ -60,29 +60,29 @@ slave node 内部有个定时任务,每秒检查是否有新的 master node
### 全量复制
-* master 执行 bgsave ,在本地生成一份 rdb 快照文件。
-* master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
-* master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
-* 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
+- master 执行 bgsave ,在本地生成一份 rdb 快照文件。
+- master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60 秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
+- master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
+- 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
-``` bash
+```bash
client-output-buffer-limit slave 256MB 64MB 60
```
-* slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时**基于旧的数据版本**对外提供服务。
-* 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
+- slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中。注意,在清空旧数据之前,slave node 依然会**基于旧的数据版本**对外提供服务。
+- 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
### 增量复制
-* 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
-* master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
-* master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
+- 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
+- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
+- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
### heartbeat
主从节点互相都会发送 heartbeat 信息。
-master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat。
+master 默认每隔 10 秒发送一次 heartbeat,slave node 每隔 1 秒发送一个 heartbeat。
### 异步复制
@@ -100,4 +100,4 @@ Redis 的高可用架构,叫做 `failover` **故障转移**,也可以叫做
master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 Redis 的主从架构下的高可用。
-后面会详细说明 Redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.md)。
+后面会详细说明 Redis [基于哨兵的高可用性](./redis-sentinel.md)。
diff --git a/docs/high-concurrency/redis-persistence.md b/docs/high-concurrency/redis-persistence.md
index 7dba2cf3e..20605e2df 100644
--- a/docs/high-concurrency/redis-persistence.md
+++ b/docs/high-concurrency/redis-persistence.md
@@ -1,4 +1,7 @@
+# Redis 持久化机制
+
## 面试题
+
Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
## 面试官心理分析
@@ -21,8 +24,8 @@ Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了
### Redis 持久化的两种方式
-* RDB:RDB 持久化机制,是对 Redis 中的数据执行**周期性**的持久化。
-* AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。
+- RDB:RDB 持久化机制,是对 Redis 中的数据执行**周期性**的持久化。
+- AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。
通过 RDB 或 AOF,都可以将 Redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。
@@ -32,25 +35,24 @@ Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了
#### RDB 优缺点
-* RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。
-* RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis **保持高性能**,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
-* 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。
-
-* 如果想要在 Redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 Redis 进程宕机,那么会丢失最近 5 分钟的数据。
-* RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
+- RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。
+- RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis **保持高性能**,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
+- 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。
+- 如果想要在 Redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 Redis 进程宕机,那么会丢失最近 5 分钟(甚至更长时间)的数据。
+- RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
#### AOF 优缺点
-* AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 `fsync` 操作,最多丢失 1 秒钟的数据。
-* AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
-* AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
-* AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。
-* 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
-* AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync` ,性能也还是很高的。(如果实时写入,那么 QPS 会大降,Redis 性能会大大降低)
-* 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。
+- AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 `fsync` 操作,最多丢失 1 秒钟的数据。
+- AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
+- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
+- AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。
+- 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
+- AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync` ,性能也还是很高的。(如果实时写入,那么 QPS 会大降,Redis 性能会大大降低)
+- 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 `merge` 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。
### RDB 和 AOF 到底该如何选择
-* 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
-* 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
-* Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
+- 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
+- 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
+- Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择;用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
diff --git a/docs/high-concurrency/redis-production-environment.md b/docs/high-concurrency/redis-production-environment.md
index f743b7c1d..74aab390e 100644
--- a/docs/high-concurrency/redis-production-environment.md
+++ b/docs/high-concurrency/redis-production-environment.md
@@ -1,4 +1,7 @@
+# Redis 生产部署方案
+
## 面试题
+
生产环境中的 Redis 是怎么部署的?
## 面试官心理分析
@@ -9,9 +12,9 @@
## 面试题剖析
-Redis cluster,10 台机器,5 台机器部署了 Redis 主实例,另外 5 台机器部署了 Redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。
+Redis cluster,10 台机器,5 台机器部署了 Redis 主实例,另外 5 台机器部署了 Redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰 QPS 可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求每秒。
-机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 Redis 进程的是10g内存,一般线上生产环境,Redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
+机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 Redis 进程的是 10g 内存,一般线上生产环境,Redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
5 台机器对外提供读写,一共有 50g 内存。
diff --git a/docs/high-concurrency/redis-rehash.md b/docs/high-concurrency/redis-rehash.md
new file mode 100644
index 000000000..52deb3e6e
--- /dev/null
+++ b/docs/high-concurrency/redis-rehash.md
@@ -0,0 +1,45 @@
+# Redis rehash 过程
+
+## 面试题
+
+有了解过 Redis rehash 的过程吗?
+
+## 面试官心理分析
+
+这个知识点算 redis 中比较低频的面试点,但是当你在介绍 HashMap 的 rehash 或者 ConcurrentHashMap 的 rehash 过程中,可以主动和面试官提及你不仅了解这些,同时还了解 Redis 中的 rehash 过程。
+
+Redis 是以速度快,性能好著称的,我们知道 Redis 一开始的容量是有限的,当容量不足时,需要扩容,那扩容的方式是什么?一次性全部将数据转移吗?那当数据量上千万上亿,这必定会阻塞 Redis 对命令的执行。因此就非常有必要了解一下 Redis 中的 rehash 过程。
+
+## 面试题剖析
+
+众所周知,Redis 主要用于存储键值对(`Key-Value Pair`),而键值对的存储方式是由字典实现,而 Redis 中字典的底层又是通过哈希表来实现的。通过哈希表中的节点保存字典中的键值对。类似 Java 中的 HashMap,将 Key 通过哈希函数映射到哈希表节点位置。
+
+Redis 中字典的数据结构如下:
+
+```c
+// 字典对应的数据结构,有关hash表的结构可以参考redis源码,再次就不进行描述
+typedef struct dict {
+ dictType *type; // 字典类型
+ void *privdata; // 私有数据
+ dictht ht[2]; // 2个哈希表,这也是进行rehash的重要数据结构,从这也看出字典的底层通过哈希表进行实现。
+ long rehashidx; // rehash过程的重要标志,值为-1表示rehash未进行
+ int iterators; // 当前正在迭代的迭代器数
+} dict;
+```
+
+在对哈希表进行扩展或者收缩操作时,程序需要将现有哈希表包含的所有键值对 rehash 到新哈希表里面,具体过程如下:
+
+### 1. 为字典的备用哈希表分配空间。
+
+如果执行的是扩展操作,那么备用哈希表的大小为第一个大于等于需要扩容的哈希表的键值对数量*2 的 2"(2 的 n 次方幂);【`5*2=10,`所以备用哈希表的容量为第一个大于 10 的 2",即 16】
+
+如果执行的是收缩操作,那么备用哈希表的大小为第一个大于等于需要扩容的哈希表的键值对数量(`ht[0] .used`)的 2"。
+
+### 2. 渐进式 rehash
+
+rehash 过程在数据量非常大(几千万、亿)的情况下并不是一次性地完成的,而是**渐进式地**完成的。**渐进式 rehash**的好处在于避免对服务器造成影响。
+
+渐进式 rehash 的本质:
+
+1. 借助 rehashidx,将 rehash 键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式 rehash 而带来的庞大计算量。
+2. 在 rehash 进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将原哈希表在 rehashidx 索引上的所有键值对 rehash 到备用哈希表,当 rehash 工作完成之后,程序将 rehashidx 属性的值加 1。
diff --git a/docs/high-concurrency/redis-sentinel.md b/docs/high-concurrency/redis-sentinel.md
index 0564368b3..d6aa8dff9 100644
--- a/docs/high-concurrency/redis-sentinel.md
+++ b/docs/high-concurrency/redis-sentinel.md
@@ -4,25 +4,25 @@
sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的一个组件,主要有以下功能:
-* 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
-* 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
-* 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
-* 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
+- 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
+- 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
+- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
+- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 Redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
-* 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
-* 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
+- 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
+- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
## 哨兵的核心知识
-* 哨兵至少需要 3 个实例,来保证自己的健壮性。
-* 哨兵 + Redis 主从的部署架构,是**不保证数据零丢失**的,只能保证 Redis 集群的高可用性。
-* 对于哨兵 + Redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
+- 哨兵至少需要 3 个实例,来保证自己的健壮性。
+- 哨兵 + Redis 主从的部署架构,是**不保证数据零丢失**的,只能保证 Redis 集群的高可用性。
+- 对于哨兵 + Redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
哨兵集群必须部署 2 个以上节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1。
-```
+```
+----+ +----+
| M1 |---------| R1 |
| S1 | | S2 |
@@ -31,7 +31,7 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的
配置 `quorum=1` ,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候,需要 majority,也就是大多数哨兵都是运行的。
-```
+```
2 个哨兵,majority=2
3 个哨兵,majority=2
4 个哨兵,majority=2
@@ -43,7 +43,7 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的
经典的 3 节点哨兵集群是这样的:
-```
+```
+----+
| M1 |
| S1 |
@@ -60,15 +60,16 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的
## Redis 哨兵主备切换的数据丢失问题
### 导致数据丢失的两种情况
+
主备切换的过程,可能会导致数据丢失:
-* 异步复制导致的数据丢失
+- 异步复制导致的数据丢失
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。

-* 脑裂导致的数据丢失
+- 脑裂导致的数据丢失
脑裂,也就是说,某个 master 所在机器突然**脱离了正常的网络**,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会**认为** master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的**脑裂**。
@@ -80,7 +81,7 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的
进行如下配置:
-``` bash
+```bash
min-slaves-to-write 1
min-slaves-max-lag 10
```
@@ -89,18 +90,18 @@ min-slaves-max-lag 10
如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。
-* 减少异步复制数据的丢失
+- 减少异步复制数据的丢失
有了 `min-slaves-max-lag` 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。
-* 减少脑裂的数据丢失
+- 减少脑裂的数据丢失
如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失 10 秒的数据。
## sdown 和 odown 转换机制
-* sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
-* odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机
+- sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
+- odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机
sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 `is-master-down-after-milliseconds` 指定的毫秒数之后,就主观认为 master 宕机了;如果一个哨兵在指定时间内,收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。
@@ -122,22 +123,22 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过
如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息:
-* 跟 master 断开连接的时长
-* slave 优先级
-* 复制 offset
-* run id
+- 跟 master 断开连接的时长
+- slave 优先级
+- 复制 offset
+- run id
如果一个 slave 跟 master 断开连接的时间已经超过了 `down-after-milliseconds` 的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。
-```
+```
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
```
接下来会对 slave 进行排序:
-* 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。
-* 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。
-* 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。
+- 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。
+- 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。
+- 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。
## quorum 和 majority
diff --git a/docs/high-concurrency/redis-single-thread-model.md b/docs/high-concurrency/redis-single-thread-model.md
index dedbf630b..17a427406 100644
--- a/docs/high-concurrency/redis-single-thread-model.md
+++ b/docs/high-concurrency/redis-single-thread-model.md
@@ -1,4 +1,7 @@
+# Redis 和 Memcached 的区别
+
## 面试题
+
Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么 Redis 单线程却能支撑高并发?
## 面试官心理分析
@@ -13,7 +16,7 @@ Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么
#### Redis 支持复杂的数据结构
-Redis 相比 Memcached 来说,拥有[更多的数据结构](/docs/high-concurrency/redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, Redis 会是不错的选择。
+Redis 相比 Memcached 来说,拥有[更多的数据结构](./redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, Redis 会是不错的选择。
#### Redis 原生支持集群模式
@@ -29,10 +32,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事
文件事件处理器的结构包含 4 个部分:
-* 多个 socket
-* IO 多路复用程序
-* 文件事件分派器
-* 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
+- 多个 socket
+- IO 多路复用程序
+- 文件事件分派器
+- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。
@@ -54,10 +57,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事
### 为啥 Redis 单线程模型也能效率这么高?
-* 纯内存操作。
-* 核心是基于非阻塞的 IO 多路复用机制。
-* C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
-* 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
+- 纯内存操作。
+- 核心是基于非阻塞的 IO 多路复用机制。
+- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
+- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
### Redis 6.0 开始引入多线程
@@ -69,4 +72,4 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事
### 总结
-Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。
+Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间(释放操作不会阻塞网络 IO 读写,因为网络 IO 读写与释放的命令执行不是同一个线程)也能减少对 Redis 主线程阻塞的时间,提高执行的效率。
diff --git a/docs/high-concurrency/why-cache.md b/docs/high-concurrency/why-cache.md
index 7a9365f0d..27eb0f94d 100644
--- a/docs/high-concurrency/why-cache.md
+++ b/docs/high-concurrency/why-cache.md
@@ -1,4 +1,7 @@
+# 缓存的使用方式
+
## 面试题
+
项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?
## 面试官心理分析
@@ -31,7 +34,7 @@
mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql 单机支撑到 `2000QPS` 也开始容易报警了。
-所以要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。
+所以要是你有个系统,高峰期一秒钟过来的请求有 1 万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。
> 缓存是走内存的,内存天然就支撑高并发。
@@ -39,8 +42,8 @@ mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,
常见的缓存问题有以下几个:
-* [缓存与数据库双写不一致](/docs/high-concurrency/redis-consistence.md)
-* [缓存雪崩、缓存穿透、缓存击穿](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
-* [缓存并发竞争](/docs/high-concurrency/redis-cas.md)
+- [缓存与数据库双写不一致](./redis-consistence.md)
+- [缓存雪崩、缓存穿透、缓存击穿](./redis-caching-avalanche-and-caching-penetration.md)
+- [缓存并发竞争](./redis-cas.md)
点击超链接,可直接查看缓存相关问题及解决方案。
diff --git a/docs/high-concurrency/why-mq.md b/docs/high-concurrency/why-mq.md
index cb8fac066..47e54b2f1 100644
--- a/docs/high-concurrency/why-mq.md
+++ b/docs/high-concurrency/why-mq.md
@@ -1,30 +1,35 @@
+# 为什么使用消息队列?
+
## 面试题
-* 为什么使用消息队列?
-* 消息队列有什么优点和缺点?
-* Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
+- 为什么使用消息队列?
+- 消息队列有什么优点和缺点?
+- Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
## 面试官心理分析
其实面试官主要是想看看:
-* **第一**,你知不知道你们系统里为什么要用消息队列这个东西?
+- **第一**,你知不知道你们系统里为什么要用消息队列这个东西?
+
+ 不少候选人,说自己项目里用了 Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人设计的架构,他从头到尾都没思考过。
-不少候选人,说自己项目里用了 Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人设计的架构,他从头到尾都没思考过。
-没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。
+ 没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。
-* **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?
+- **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?
-你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。
+ 你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。
-* **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?
+- **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?
-你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。
-如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。
+ 你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。
+
+ 如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。
## 面试题剖析
### 为什么使用消息队列
+
其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?
面试官问你这个问题,**期望的一个回答**是说,你们公司有个什么**业务场景**,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处。
@@ -33,7 +38,7 @@
#### 解耦
-看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃......
+看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 D 系统现在不需要了呢?A 系统负责人几乎崩溃......

@@ -81,36 +86,36 @@
缺点有以下几个:
-* 系统可用性降低
+- 系统可用性降低
-系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)。
+ 系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](./how-to-ensure-high-availability-of-message-queues.md)。
-* 系统复杂度提高
+- 系统复杂度提高
-硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)?怎么[处理消息丢失的情况](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。
+ 硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](./how-to-ensure-that-messages-are-not-repeatedly-consumed.md)?怎么[处理消息丢失的情况](./how-to-ensure-the-reliable-transmission-of-messages.md)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。
-* 一致性问题
+- 一致性问题
-A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
+ A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
-所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
+ 所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
### Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?
-| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
-|---|---|---|---|---|
-| 单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
-| topic 数量对吞吐量的影响 | | | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 |
-| 时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 |
-| 可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
-| 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ |
-| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
+| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
+| ------------------------ | ------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
+| 单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
+| topic 数量对吞吐量的影响 | | | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 |
+| 时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 |
+| 可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
+| 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ |
+| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
综上,各种对比之后,有如下建议:
-一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了;
+一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了。
-后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;
+后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高。
不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 [Apache](https://github.com/apache/rocketmq),但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 000000000..99b7329ed
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,35 @@
+---
+# https://vitepress.dev/reference/default-theme-home-page
+layout: home
+
+hero:
+ name: "advanced-java"
+ text: "互联网 Java 工程师进阶知识完全扫盲"
+ tagline: Doocs 技术社区出品
+ actions:
+ - theme: alt
+ text: GitHub 首页
+ link: https://github.com/doocs/advanced-java
+ - theme: brand
+ text: 开始学习
+ link: /high-concurrency/mq-interview
+
+
+features:
+ - title: "⚡ 高并发架构"
+ details: 包含消息队列、搜索引擎、缓存、分库分表、读写分离等核心知识点
+ link: /high-concurrency/mq-interview
+ - title: "🌐 分布式系统"
+ details: 涉及 Dubbo、分布式锁、事务、会话等设计与实现原理
+ link: /distributed-system/distributed-system-interview
+ - title: "🛡️ 高可用架构"
+ details: 涵盖限流、熔断、降级、Hystrix 实现等高可用保障手段
+ link: /high-availability/hystrix-introduction
+ - title: "🧩 微服务架构"
+ details: 深入了解微服务基础、Spring Cloud、服务治理与通信机制
+ link: /micro-services/microservices-introduction
+ - title: "📊 海量数据处理"
+ details: 探索应对大数据场景下的经典算法与设计题
+ link: /big-data/find-common-urls
+---
+
diff --git a/docs/micro-services/README.md b/docs/micro-services/README.md
index 6c5d0962d..ebbbcb3ab 100644
--- a/docs/micro-services/README.md
+++ b/docs/micro-services/README.md
@@ -1,38 +1,42 @@
-# 微服务架构
-
-* [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
-* [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
-
-## Spring Cloud 微服务架构
-
-* 什么是微服务?微服务之间是如何独立通讯的?
-* Spring Cloud 和 Dubbo 有哪些区别?
-* Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
-* 什么是服务熔断?什么是服务降级?
-* 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
-* 你所知道的微服务技术栈都有哪些?
-* Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
-* ......
-
----
-
-## 公众号
-
-GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
-
-
+# 微服务架构
+
+- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
+- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
+- [从单体式架构迁移到微服务架构](/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md)
+- [微服务的事件驱动数据管理](/docs/micro-services/event-driven-data-management-for-microservices.md)
+- [选择微服务部署策略](/docs/micro-services/choose-microservice-deployment-strategy.md)
+- [微服务架构的优势与不足](/docs/micro-services/advantages-and-disadvantages-of-microservice.md)
+
+## Spring Cloud 微服务架构
+
+- [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/what's-microservice-how-to-communicate.md)
+- Spring Cloud 和 Dubbo 有哪些区别?
+- Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
+- 什么是服务熔断?什么是服务降级?
+- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
+- [你所知道的微服务技术栈都有哪些?](/docs/micro-services/micro-services-technology-stack.md)
+- [微服务治理策略](/docs/micro-services/micro-service-governance.md)
+- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
+- [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md)
+- ......
+
+---
+
+## 公众号
+
+[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
+
+
+
+
+ 
+ |
+
+ 
+ |
+
+
+
+关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
+
+
diff --git a/docs/micro-services/advantages-and-disadvantages-of-microservice.md b/docs/micro-services/advantages-and-disadvantages-of-microservice.md
index a5d975d99..f3e359740 100644
--- a/docs/micro-services/advantages-and-disadvantages-of-microservice.md
+++ b/docs/micro-services/advantages-and-disadvantages-of-microservice.md
@@ -1,4 +1,6 @@
-# 开发单体式应用
+# 微服务架构的优势与不足
+
+## 开发单体式应用
假设你正准备开发一款与 Uber 和 Hailo 竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于 Rails、Spring Boot、Play 或者 Maven 的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下:
@@ -10,7 +12,7 @@
这种应用开发风格很常见,因为 IDE 和其它工具都擅长开发一个简单应用,这类应用也很易于调试,只需要简单运行此应用,用 Selenium 链接 UI 就可以完成端到端测试。单体式应用也易于部署,只需要把打包应用拷贝到服务器端,通过在负载均衡器后端运行多个拷贝就可以轻松实现应用扩展。在早期这 类应用运行的很好。
-# 单体式应用的不足
+## 单体式应用的不足
不幸的是,这种简单方法却有很大的局限性。一个简单的应用会随着时间推移逐渐变大。在每次的 sprint 中, 开发团队都会面对新“故事”,然后开发许多新代码。几年后,这个小而简单的应用会变成了一个巨大的怪物。这儿有一个例子,我最近和一个开发者讨论,他正在 写一个工具,用来分析他们一个拥有数百万行代码的应用中 JAR 文件之间的依赖关系。我很确信这个代码正是很多开发者经过多年努力开发出来的一个怪物。
@@ -30,7 +32,7 @@
那么如何应对呢?
-# 微处理架构——处理复杂事物
+## 微处理架构——处理复杂事物
许多公司,比如 Amazon、eBay 和 NetFlix,通过采用微处理结构模式解决了上述问题。其思路不是开发一个巨大的单体式的应用,而是将应用分解为小的、互相连接的微服务。
@@ -64,7 +66,7 @@
表面上看来,微服务架构模式有点像 SOA,他们都由多个服务构成。但是,可以从另外一个角度看此问题,微服务架构模式是一个不包含 Web 服务 (WS-)和 ESB 服务的 SOA。微服务应用乐于采用简单轻量级协议,比如 REST,而不是 WS-,在微服务内部避免使用 ESB 以及 ESB 类似功能。微服 务架构模式也拒绝使用 canonical schema 等 SOA 概念。
-# 微服务架构的好处
+## 微服务架构的好处
微服务架构模式有很多好处。首先,通过分解巨大单体式应用为多个服务方法解决了复杂性问题。在功能不变的情况下,应用 被分解为多个可管理的分支或服务。每个服务都有一个用 RPC-或者消息驱动 API 定义清楚的边界。微服务架构模式给采用单体式编码方式很难实现的功能提供 了模块化的解决方案,由此,单个服务很容易开发、理解和维护。
@@ -74,7 +76,7 @@
最后,微服务架构模式使得每个服务独立扩展。你可以根据每个服务的规模来部署满足需求的规模。甚至于,你可以使用更适合于服务资源需求的硬件。比 如,你可以在 EC2 Compute Optimized instances 上部署 CPU 敏感的服务,而在 EC2 memory-optimized instances 上部署内存数据库。
-# 微服务架构的不足
+## 微服务架构的不足
Fred Brooks 在 30 年前写道,“there are no silver bullets”,像任何其它科技一样,微服务架构也有不足。其中一个跟他的名字类似,『微服务』强调了服务大小,实际上,有一些开发者鼓吹建立稍微大一 些的,10-100 LOC 服务组。尽管小服务更乐于被采用,但是不要忘了这只是终端的选择而不是最终的目的。微服务的目的是有效的拆分应用,实现敏捷开发和部署。
@@ -90,6 +92,6 @@ Fred Brooks 在 30 年前写道,“there are no silver bullets”,像任何
一种自动化方法是使用 PaaS 服务,例如 Cloud Foundry。 PaaS 给开发者提供一个部署和管理微服务的简单方法,它把所有这些问题都打包内置解决了。同时,配置 PaaS 的系统和网络专家可以采用最佳实践和策略来 简化这些问题。另外一个自动部署微服务应用的方法是开发对于你来说最基础的 PaaS 系统。一个典型的开始点是使用一个集群化方案,比如配合 Docker 使 用 Mesos 或者 Kubernetes。后面的系列我们会看看如何基于软件部署方法例如 NGINX,可以方便的在微服务层面提供缓存、权限控制、API 统 计和监控。
-# 总结
+## 总结
-构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。
\ No newline at end of file
+构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。
diff --git a/docs/micro-services/choose-microservice-deployment-strategy.md b/docs/micro-services/choose-microservice-deployment-strategy.md
index 037843b83..0795d5d0e 100644
--- a/docs/micro-services/choose-microservice-deployment-strategy.md
+++ b/docs/micro-services/choose-microservice-deployment-strategy.md
@@ -1,4 +1,6 @@
-# 前言
+# 选择微服务部署策略
+
+## 前言
部署一个单体式应用意味运行大型应用的多个副本,典型的提供若干个(N)服务器(物理或者虚拟),运行若干个(M)个应用实例。部署单体式应用不会很直接,但是肯定比部署微服务应用简单些。
@@ -6,7 +8,7 @@
有一些微服务部署的模式,先讨论一下每个主机多服务实例的模式。
-# 单主机多服务实例模式
+## 单主机多服务实例模式
部署微服务的一种方法就是单主机多服务实例模式,使用这种模式,需要提供若干台物理或者虚拟机,每台机器上运行多个服务实例。很多情况下,这是传统的应用部署方法。每个服务实例运行一个或者多个主机的 well-known 端口,主机可以看做宠物。
@@ -32,11 +34,11 @@
可以看到,尽管熟悉,但是单主机多服务实例有很多严重缺陷。下面看看是否有其他部署微服务方式能够避免这些问题。
-# 单主机单服务实例模式
+## 单主机单服务实例模式
另外一种部署微服务方式是单主机单实例模式。当使用这种模式,每个主机上服务实例都是各自独立的。有两种不同实现模式:单虚拟机单实例和单容器单实例。
-## 单虚拟机单实例模式
+### 单虚拟机单实例模式
但是用单虚拟机单实例模式,一般将服务打包成虚拟机映像(image),例如一个 Amazon EC2 AMI。每个服务实例是一个使用此映像启动的 VM(例如,EC2 实例)。下图展示了此架构:
@@ -66,7 +68,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用
那么我们来看看另外一种仍然具有虚机特性,但是比较轻量的微服务部署方法。
-# 单容器单服务实例模式
+## 单容器单服务实例模式
当使用这种模式时,每个服务实例都运行在各自容器中。容器是运行在操作系统层面的虚拟化机制。一个容器包含若干运行在沙箱中的进程。从进程角度来看,他们有各自的命名空间和根文件系统;可以限制容器的内存和 CPU 资源。某些容器还具有 I/O 限制,这类容器技术包括 Docker 和 Solaris Zones。
@@ -74,7 +76,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用

-使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。
+使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统 。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。
一旦将服务打包成容器映像,就需要启动若干容器。一般在一个物理机或者虚拟机上运行多个容器,可能需要集群管理系统,例如 k8s 或者 Marathon,来管理容器。集群管理系统将主机作为资源池,根据每个容器对资源的需求,决定将容器调度到那个主机上。
@@ -92,7 +94,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用
除了这些之外,server-less 部署技术,避免了前述容器和 VM 技术的缺陷,吸引了越来越多的注意。下面我们来看看。
-# Serverless 部署
+## Serverless 部署
AWS Lambda 是 serverless 部署技术的例子,支持 Java,Node.js 和 Python 服务;需要将服务打包成 ZIP 文件上载到 AWS Lambda 就可以部署。可以提供元数据,提供处理服务请求函数的名字(一个事件)。AWS Lambda 自动运行处理请求足够多的微服务,然而只根据运行时间和消耗内存量来计费。当然细节决定成败,AWS Lambda 也有限制。但是大家都不需要担心服务器,虚拟机或者容器内的任何方面绝对吸引人。
@@ -100,10 +102,10 @@ Lambda 函数 是无状态服务。一般通过激活 AWS 服务处理请求。
有四种方法激活 Lambda 函数:
-* 直接方式,使用 web 服务请求
-* 自动方式,回应例如 AWS S3,DynamoDB,Kinesis 或者 Simple Email Service 等产生的事件
-* 自动方式,通过 AWS API 网关来处理应用客户端发出的 HTTP 请求
-* 定时方式,通过 cron 响应--很像定时器方式
+- 直接方式,使用 web 服务请求
+- 自动方式,回应例如 AWS S3,DynamoDB,Kinesis 或者 Simple Email Service 等产生的事件
+- 自动方式,通过 AWS API 网关来处理应用客户端发出的 HTTP 请求
+- 定时方式,通过 cron 响应 --很像定时器方式
可以看出,AWS Lambda 是一种很方便部署微服务的方式。基于请求计费方式意味着用户只需要承担处理自己业务那部分的负载;另外,因为不需要了解基础架构,用户只需要开发自己的应用。
diff --git a/docs/micro-services/event-driven-data-management-for-microservices.md b/docs/micro-services/event-driven-data-management-for-microservices.md
index 43fbcd06c..e4edbcdaf 100644
--- a/docs/micro-services/event-driven-data-management-for-microservices.md
+++ b/docs/micro-services/event-driven-data-management-for-microservices.md
@@ -1,13 +1,15 @@
-# 1.1 微服务和分布式数据管理问题
+# 微服务的事件驱动数据管理
+
+## 1.1 微服务和分布式数据管理问题
单体式应用一般都会有一个关系型数据库,由此带来的好处是应用可以使用 ACID transactions,可以带来一些重要的操作特性:
1. 原子性 – 任何改变都是原子性的
2. 一致性 – 数据库状态一直是一致性的
-3. 隔离性 – 即使交易并发执行,看起来也是串行的
-4. Durable – 一旦交易提交了就不可回滚
+3. 隔离性 – 即使事务并发执行,看起来也是串行的
+4. Durable – 一旦事务提交了就不可回滚
-鉴于以上特性,应用可以简化为:开始一个交易,改变(插入,删除,更新)很多行,然后提交这些交易。
+鉴于以上特性,应用可以简化为:开始一个事务,改变(插入,删除,更新)很多行,然后提交这些事务。
使用关系型数据库带来另外一个优势在于提供 SQL(功能强大,可声明的,表转化的查询语言)支持。用户可以非常容易通过查询将多个表的数据组合起来,RDBMS 查询调度器决定最佳实现方式,用户不需要担心例如如何访问数据库等底层问题。另外,因为所有应用的数据都在一个数据库中,很容易去查询。
@@ -27,9 +29,9 @@
第二个挑战是如何完成从多个服务中搜索数据。例如,设想应用需要显示客户和他的订单。如果订单服务提供 API 来接受用户订单信息,那么用户可以使用类应用型的 join 操作接收数据。应用从用户服务接受用户信息,从订单服务接受此用户订单。假设,订单服务只支持通过私有键(key)来查询订单(也许是在使用只支持基于主键接受的 NoSQL 数据库),此时,没有合适的方法来接收所需数据。
-# 1.2 事件驱动架构
+## 1.2 事件驱动架构
-对许多应用来说,这个解决方案就是使用事件驱动架构(event-driven architecture)。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的时间发布。
+对许多应用来说,这个解决方案就是使用事件驱动架构(event-driven architecture)。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的事件发布。
可以使用事件来实现跨多服务的业务交易。交易一般由一系列步骤构成,每一步骤都由一个更新业务实体的微服务和发布激活下一步骤的事件构成。下图展现如何使用事件驱动方法,在创建订单时检查信用可用度,微服务通过消息代理(Messsage Broker)来交换事件。
@@ -47,7 +49,7 @@
更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。
-考虑到(a)每个服务原子性更新数据库和发布事件,然后,(b)消息代理确保事件传递至少一次,然后可以跨多个服务完成业务交易(此交易不是 ACID 交易)。这种模式提供弱确定性,例如最终一致性 eventual consistency。这种交易类型被称作 BASE model。
+考虑到(a)每个服务原子性更新数据库和发布事件,然后,(b)消息代理确保事件传递至少一次,然后可以跨多个服务完成业务交易(此交易不是 ACID 事务)。这种模式提供弱确定性,例如最终一致性 eventual consistency。这种交易类型被称作 BASE model。
亦可以使用事件来维护不同微服务拥有数据预连接(pre-join)的实现视图。维护此视图的服务订阅相关事件并且更新视图。例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件。
@@ -55,13 +57,13 @@
当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。可以使用文档数据库(例如 MongoDB)来实现客户订单视图,为每个用户存储一个文档。客户订单视图查询服务负责响应对客户以及最近订单(通过查询客户订单视图数据集)的查询。
-事件驱动架构也是既有优点也有缺点,此架构可以使得交易跨多个服务且提供最终一致性,并且可以使应用维护最终视图;而缺点在于编程模式比 ACID 交易模式更加复杂:为了从应用层级失效中恢复,还需要完成补偿性交易,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)交易造成的改变是可见的,另外当应用读取未更新的最终视图时也会遇见数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事件。
+事件驱动架构也是既有优点也有缺点,此架构可以使得事务跨多个服务且提供最终一致性,并且可以使应用维护最终视图;而缺点在于编程模式比 ACID 事务模式更加复杂:为了从应用层级失效中恢复,还需要完成补偿性事务,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)事务造成的改变是可见的,另外当应用读取未更新的最终视图时也会遇见数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事件。
-# 1.3 原子操作 Achieving Atomicity
+## 1.3 原子操作 Achieving Atomicity
-事件驱动架构还会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向 ORDER 表插入一行,然后发布 Order Created event,这两个操作需要原子性。如果更新数据库后,服务瘫了(crashes)造成事件未能发布,系统变成不一致状态。确保原子操作的标准方式是使用一个分布式交易,其中包括数据库和消息代理。然而,基于以上描述的 CAP 理论,这却并不是我们想要的。
+事件驱动架构还会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向 ORDER 表插入一行,然后发布 Order Created event,这两个操作需要原子性。如果更新数据库后,服务瘫了(crashes)造成事件未能发布,系统变成不一致状态。确保原子操作的标准方式是使用一个分布式事务,其中包括数据库和消息代理。然而,基于以上描述的 CAP 理论,这却并不是我们想要的。
-## 1.3.1 使用本地交易发布事件
+### 1.3.1 使用本地事务发布事件
获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions,技巧在于一个 EVENT 表,此表在存储业务实体数据库中起到消息列表功能。应用发起一个(本地)数据库交易,更新业务实体状态,向 EVENT 表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此 EVENT 表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示:
@@ -73,7 +75,7 @@
此方法因为应用采用了本地交易更新状态和发布事件而不需要 2PC,现在再看看另外一种应用简单更新状态获得原子性的方法。
-## 1.3.2 挖掘数据库交易日志
+### 1.3.2 挖掘数据库交易日志
另外一种不需要 2PC 而获得线程或者进程发布事件原子性的方式就是挖掘数据库交易或者提交日志。应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见:
@@ -87,7 +89,7 @@
交易日志挖掘方法通过应用直接更新数据库而不需要 2PC 介入。下面我们再看一种完全不同的方法:不需要更新只依赖事件的方法。
-## 1.3.3 使用事件源
+### 1.3.3 使用事件源
Event sourcing (事件源)通过使用根本不同的事件中心方式来获得不需 2PC 的原子性,保证业务实体的一致性。 这种应用保存业务实体一系列状态改变事件,而不是存储实体现在的状态。应用可以通过重放事件来重建实体现在状态。只要业务实体发生变化,新事件就会添加到时间表中。因为保存事件是单一操作,因此肯定是原子性的。
@@ -103,8 +105,8 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来
事件源方法也有不少缺点,因为采用不同或者不太熟悉的变成模式,使得重新学习不太容易;事件存储只支持主键查询业务实体,必须使用 Command Query Responsibility Segregation (CQRS) 来完成查询业务,因此,应用必须处理最终一致数据。
-# 1.4 总结
+## 1.4 总结
-在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务交易一致性;第二个挑战是如何从多服务环境中获取一致性数据。
+在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务事务一致性;第二个挑战是如何从多服务环境中获取一致性数据。
最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法可以解决此问题,包括将数据库视为消息队列、交易日志挖掘和事件源。
diff --git a/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md b/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md
index d3a9f45a1..fe5b0503a 100644
--- a/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md
+++ b/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md
@@ -1,8 +1,5 @@
# 服务发现组件 Eureka 的几个主要调用过程
-* Author: [mghio](https://www.mghio.cn)
-* Description: 该文主要讲述服务发现组件 Eureka 的几个主要调用过程
-
## 前言
现在流行的微服务体系结构正在改变我们构建应用程序的方式,从单一的单体服务转变为越来越小的可单独部署的服务(称为 `微服务` ),共同构成了我们的应用程序。当进行一个业务时不可避免就会存在多个服务之间调用,假如一个服务 A 要访问在另一台服务器部署的服务 B,那么前提是服务 A 要知道服务 B 所在机器的 IP 地址和服务对应的端口,最简单的方式就是让服务 A 自己去维护一份服务 B 的配置(包含 IP 地址和端口等信息),但是这种方式有几个明显的缺点:随着我们调用服务数量的增加,配置文件该如何维护;缺乏灵活性,如果服务 B 改变 IP 地址或者端口,服务 A 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。
@@ -18,7 +15,7 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
`Eureka` 是由 [Netflix](https://www.netflix.com) 公司开源,采用的是 Client / Server 模式进行设计,基于 http 协议和使用 Restful Api 开发的服务注册与发现组件,提供了完整的服务注册和服务发现,可以和 `Spring Cloud` 无缝集成。其中 Server 端扮演着服务注册中心的角色,主要是为 Client 端提供服务注册和发现等功能,维护着 Client 端的服务注册信息,同时定期心跳检测已注册的服务当不可用时将服务剔除下线,Client 端可以通过 Server 端获取自身所依赖服务的注册信息,从而完成服务间的调用。遗憾的是从其官方的 [github wiki](https://github.com/Netflix/eureka/wik) 可以发现,2.0 版本已经不再开源。但是不影响我们对其进行深入了解,毕竟服务注册、服务发现相对来说还是比较基础和通用的,其它开源实现框架的思想也是想通的。
-## 服务注册中心(Eureka Server)
+## 服务注册中心(Eureka Server)
我们在项目中引入 `Eureka Server` 的相关依赖,然后在启动类加上注解 `@EnableEurekaServer` ,就可以将其作为注册中心,启动服务后访问页面如下:
@@ -28,8 +25,6 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti

-`Demo` 仓库地址:https://github.com/mghio/depth-in-springcloud
-
可以看到 `Eureka` 的使用非常简单,只需要添加几个注解和配置就实现了服务注册和服务发现,接下来我们看看它是如何实现这些功能的。
### 服务注册(Register)
@@ -53,7 +48,7 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
### 服务续约(Renew)
-服务续约会由服务提供者(比如 `Demo` 中的 `service-provider` )定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务时效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。
+服务续约会由服务提供者(比如 `Demo` 中的 `service-provider` )定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务失效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。

@@ -123,7 +118,8 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
工作中项目使用的是 `Spring Cloud` 技术栈,它有一套非常完善的开源代码来整合 `Eureka` ,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对 `Eureka` 服务发现组件有了更近一步的了解。
----
+---
+
参考文章
[Netflix Eureka](https://github.com/Netflix/eureka)
diff --git a/docs/micro-services/huifer-micro-service-governance.md b/docs/micro-services/huifer-micro-service-governance.md
deleted file mode 100644
index 82960667d..000000000
--- a/docs/micro-services/huifer-micro-service-governance.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# 微服务治理策略
-
-* Author: [HuiFer](https://github.com/huifer)
-* Description: 该文简单介绍微服务的治理策略以及应用技术
-
-## 服务的注册和发现
-
-> 解决问题: 集中管理服务
-
-> 解决方法: eureka 、zookeeper
-
-## 负载均衡
-
-> 解决问题: 降低服务器硬件压力
-
-> 解决方法: nginx 、 Ribbon
-
-## 通讯
-
-> 解决问题: 各个服务之间的沟通桥梁
-
-> 解决方法 :
-> - 同步消息
-> 1. rest
-> 1. rpc
-> - 异步消息
-> 1. MQ
-
-## 配置管理
-
-> 解决问题: 随着服务的增加配置也在增加, 如何管理各个服务的配置
-
-> 解决方法: nacos 、 spring cloud config 、 Apollo
-
-## 容错和服务降级
-
-> 解决问题: 在微服务当中,一个请求经常会涉及到调用几个服务,如果其中某个服务不可以,没有做服务容错的话,极有可能会造成一连串的服务不可用,这就是雪崩效应.
-
-> 解决方法: hystrix
-
-## 服务依赖关系
-
-> 解决问题: 多个服务之间来回依赖, 启动关系的不明确
-
-> 解决方法:
-
-> 1. 应用分层: 数据层, 业务层 数据层不需要依赖业务层, 业务层依赖数据, 规定上下依赖关系避免循环圈
-
-## 服务文档
-
-> 解决问题: 降低沟通成本
-
-> 解决方法: swagger 、 java doc
-
-## 服务安全问题
-
-> 解决问题: 敏感数据的安全性
-
-> 解决方法: oauth 、 shiro 、 spring security
-
-## 流量控制
-
-> 解决问题: 避免一个服务上的流量过大拖垮整个服务体系
-
-> 解决方法: Hystrix
-
-## 自动化测试
-
-> 解决问题: 提前预知异常, 确定服务是否可用
-
-> 解决方法: junit
-
-## 服务上线, 下线的流程
-
-> 解决问题: 避免服务随意的上线下线
-
-> 解决方法: 新服务上线需要经过管理人员审核. 服务下线需要告知各个调用方进行修改, 直到没有调用该服务才可以进行下线.
-
-## 兼容性
-
-> 解决问题: 服务开发持续进行如何做到兼容
-
-> 解决方法: 通过版本号的形式进行管理, 修改完成进行回归测试
-
-## 服务编排
-
-> 解决问题: 解决服务依赖问题的一种方式
-
-> 解决方法: docker & k8s
-
-## 资源调度
-
-> 解决问题: 每个服务的资源占用量不同, 如何分配
-
-> 解决方法: JVM 隔离、classload 隔离 ; 硬件隔离
-
-## 容量规划
-
-> 解决问题: 随着时间增长, 调用逐步增加, 什么时候追加机器
-
-> 解决方法: 统计每日调用量和响应时间, 根据机器情况设置阈值, 超过阈值就可以追加机器
diff --git a/docs/micro-services/huifer-micro-services-technology-stack.md b/docs/micro-services/huifer-micro-services-technology-stack.md
deleted file mode 100644
index 36d6c1c09..000000000
--- a/docs/micro-services/huifer-micro-services-technology-stack.md
+++ /dev/null
@@ -1,311 +0,0 @@
-# 微服务技术栈
-
-* Author: [HuiFer](https://github.com/huifer)
-* Description: 该文简单介绍微服务技术栈有哪些分别用来做什么
-
-## 技术栈
-
-### 微服务开发
-
-作用:快速开发服务。
-
-* Spring
-* Spring MVC
-* Spring Boot
-
-[官网](https://spring.io/),Spring 目前是 JavaWeb 开发人员必不可少的一个框架,SpringBoot 简化了 Spring 开发的配置目前也是业内主流开发框架。
-
-### 微服务注册发现
-
-作用:发现服务,注册服务,集中管理服务
-
-#### Eureka
-
-* Eureka Server : 提供服务注册服务, 各个节点启动后,会在 Eureka Server 中进行注册。
-* Eureka Client : 简化与 Eureka Server 的交互操作
-* Spring Cloud Netflix : [GitHub](https://github.com/spring-cloud/spring-cloud-netflix),[文档](https://cloud.spring.io/spring-cloud-netflix/reference/html/)
-
-#### Zookeeper
-
-> ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
-
-[Zookeeper](https://github.com/apache/zookeeper) 是一个集中的服务, 用于维护配置信息、命名、提供分布式同步和提供组服务。
-
-#### Zookeeper 和 Eureka 区别
-
-Zookeeper 保证 CP,Eureka 保证 AP:
-
-* C:数据一致性;
-* A:服务可用性;
-* P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
-
-### 微服务配置管理
-
-作用:统一管理一个或多个服务的配置信息, 集中管理。
-
-#### Disconf
-
-Distributed Configuration Management Platform(分布式配置管理平台) , 它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务, 是一套完整的基于 zookeeper 的分布式配置统一解决方案。
-
-* [GitHub](https://github.com/knightliao/disconf)
-
-#### SpringCloudConfig
-
-* [GitHub](https://github.com/spring-cloud/spring-cloud-config)
-
-#### Apollo
-
-Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,用于微服务配置管理场景。
-
-* [GitHub](https://github.com/ctripcorp/apollo)
-
-### 权限认证
-
-作用:根据系统设置的安全规则或者安全策略, 用户可以访问而且只能访问自己被授权的资源,不多不少。
-
-#### Spring Security
-
-* [官网](https://spring.io/projects/spring-security)
-
-#### apache Shiro
-
-> Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
-
-* [官网](http://shiro.apache.org/)
-
-### 批处理
-
-作用: 批量处理同类型数据或事物
-
-#### Spring Batch
-
-* [官网](官网)
-
-### 定时任务
-
-> 作用: 定时做什么.
-
-#### Quartz
-
-* [官网](http://www.quartz-scheduler.org/)
-
-### 微服务调用 (协议)
-
-> 通讯协议
-
-#### Rest
-
-* 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互
-
-#### RPC
-
-* Remote Procedure Call
-* 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。
-
-#### gRPC
-
-* [官网](https://www.grpc.io/)
-* > A high-performance, open-source universal RPC framework
-
- > 所谓 RPC(remote procedure call 远程过程调用) 框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从 server/client 模型。使用的时候客户端调用 server 端提供的接口就像是调用本地的函数一样。
-
-#### RMI
-
-* Remote Method Invocation
-* 纯 Java 调用
-
-### 服务接口调用
-
-> 作用:多个服务之间的通讯
-
-#### Feign(HTTP)
-
-Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。
-
-* [GitHub](https://github.com/OpenFeign/feign)
-
-### 服务熔断
-
-> 作用: 当请求到达一定阈值时不让请求继续.
-
-#### Hystrix
-
-* > Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.
-
- >
-
-* [GitHub](https://github.com/Netflix/Hystrix)
-
-#### Sentinel
-
-* > A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库)
-* [GitHub](https://github.com/alibaba/Sentinel)
-
-### 服务的负载均衡
-
-> 作用:降低服务压力, 增加吞吐量
-
-#### Ribbon
-
-* >Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具, 它基于 Netflix Ribbon 实现
-
- >
-
-* [GitHub](https://github.com/Netflix/ribbon)
-
-#### Nginx
-
-Nginx (engine x) 是一个高性能的 HTTP 和反向代理 web 服务器, 同时也提供了 IMAP/POP3/SMTP 服务
-
-* [GitHub](https://github.com/nginx/nginx)
-
-#### Nginx 与 Ribbon 区别
-
-1. Nginx 属于服务端负载均衡,Ribbon 属于客户端负载均衡.Nginx 作用与 Tomcat,Ribbon 作用与各个服务之间的调用 (RPC)
-
-### 消息队列
-
-> 作用: 解耦业务, 异步化处理数据
-
-#### Kafka
-
-* [官网](http://kafka.apache.org/)
-
-#### RabbitMQ
-
-* [官网](https://www.rabbitmq.com/)
-
-#### RocketMQ
-
-* [官网](http://rocketmq.apache.org/)
-
-#### activeMQ
-
-* [官网](http://activemq.apache.org/)
-
-### 日志采集 (elk)
-
-> 作用: 收集各服务日志提供日志分析、用户画像等
-
-#### Elasticsearch
-
-* [GitHub](https://github.com/elastic/elasticsearch)
-
-#### Logstash
-
-* [GitHub](https://github.com/elastic/logstash)
-
-#### Kibana
-
-* [GitHub](https://github.com/elastic/kibana)
-
-### API 网关
-
-> 作用: 外部请求通过 API 网关进行拦截处理, 再转发到真正的服务
-
-#### Zuul
-
-> Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more.
->
-
-* [GitHub](https://github.com/Netflix/zuul)
-
-### 服务监控
-
-> 作用: 以可视化或非可视化的形式展示出各个服务的运行情况 (CPU、内存、访问量等)
-
-#### Zabbix
-
-* [GitHub](https://github.com/jjmartres/Zabbix)
-
-#### Nagios
-
-* [官网](https://www.nagios.org/)
-
-#### Metrics
-
-* [官网](https://metrics.dropwizard.io)
-
-### 服务链路追踪
-
-> 作用: 明确服务之间的调用关系
-
-#### Zipkin
-
-* [GitHub](https://github.com/openzipkin/zipkin)
-
-#### Brave
-
-* [GitHub](https://github.com/openzipkin/brave)
-
-### 数据存储
-
-> 作用: 存储数据
-
-#### 关系型数据库
-
-##### MySql
-
-* [官网](https://www.mysql.com/)
-
-##### Oracle
-
-* [官网](https://www.oracle.com/index.html)
-
-##### MsSql
-
-* [官网](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15)
-
-##### PostgreSql
-
-* [官网](https://www.postgresql.org/)
-
-#### 非关系型数据库
-
-##### Mongodb
-
-* [官网](https://www.mongodb.com/)
-
-##### Elasticsearch
-
-* [GitHub](https://github.com/elastic/elasticsearch)
-
-### 缓存
-
-> 作用: 存储数据
-
-#### redis
-
-* [官网](https://redis.io/)
-
-### 分库分表
-
-> 作用: 数据库分库分表方案.
-
-#### shardingsphere
-
-* [官网](http://shardingsphere.apache.org/)
-
-#### Mycat
-
-* [官网](http://www.mycat.io/)
-
-### 服务部署
-
-> 作用: 将项目快速部署、上线、持续集成.
-
-#### Docker
-
-* [官网](http://www.docker.com/)
-
-#### Jenkins
-
-* [官网](https://jenkins.io/zh/)
-
-#### Kubernetes(K8s)
-
-* [官网](https://kubernetes.io/)
-
-#### Mesos
-
-* [官网](http://mesos.apache.org/)
diff --git a/docs/micro-services/images/30103116_ZCcM.png b/docs/micro-services/images/30103116_ZCcM.png
index 474a8a8fc..18dd99933 100644
Binary files a/docs/micro-services/images/30103116_ZCcM.png and b/docs/micro-services/images/30103116_ZCcM.png differ
diff --git a/docs/micro-services/images/Before-and-after-migration.png b/docs/micro-services/images/Before-and-after-migration.png
index 3ec40a886..3e22f0016 100644
Binary files a/docs/micro-services/images/Before-and-after-migration.png and b/docs/micro-services/images/Before-and-after-migration.png differ
diff --git a/docs/micro-services/images/Event-sourcing.png b/docs/micro-services/images/Event-sourcing.png
index 6ea80168b..a509b1156 100644
Binary files a/docs/micro-services/images/Event-sourcing.png and b/docs/micro-services/images/Event-sourcing.png differ
diff --git a/docs/micro-services/images/Law-of-Holes.png b/docs/micro-services/images/Law-of-Holes.png
index 696fc2585..0b138b901 100644
Binary files a/docs/micro-services/images/Law-of-Holes.png and b/docs/micro-services/images/Law-of-Holes.png differ
diff --git a/docs/micro-services/images/No-2PC-required.png b/docs/micro-services/images/No-2PC-required.png
index 7be7d25a0..c3330e2d4 100644
Binary files a/docs/micro-services/images/No-2PC-required.png and b/docs/micro-services/images/No-2PC-required.png differ
diff --git a/docs/micro-services/images/PreferFunctionalStaffOrganization.png b/docs/micro-services/images/PreferFunctionalStaffOrganization.png
index e7f5a6f79..ca1e2c99a 100644
Binary files a/docs/micro-services/images/PreferFunctionalStaffOrganization.png and b/docs/micro-services/images/PreferFunctionalStaffOrganization.png differ
diff --git a/docs/micro-services/images/basic-pipeline.png b/docs/micro-services/images/basic-pipeline.png
index 4d9fa62d2..2e6041da7 100644
Binary files a/docs/micro-services/images/basic-pipeline.png and b/docs/micro-services/images/basic-pipeline.png differ
diff --git a/docs/micro-services/images/conways-law.png b/docs/micro-services/images/conways-law.png
index 595677355..eb85ee189 100644
Binary files a/docs/micro-services/images/conways-law.png and b/docs/micro-services/images/conways-law.png differ
diff --git a/docs/micro-services/images/deal-with-complex-things-3.png b/docs/micro-services/images/deal-with-complex-things-3.png
index 07ed24ccc..ec7eec45f 100644
Binary files a/docs/micro-services/images/deal-with-complex-things-3.png and b/docs/micro-services/images/deal-with-complex-things-3.png differ
diff --git a/docs/micro-services/images/decentralised-data.png b/docs/micro-services/images/decentralised-data.png
index c2b851901..e85ab2752 100644
Binary files a/docs/micro-services/images/decentralised-data.png and b/docs/micro-services/images/decentralised-data.png differ
diff --git a/docs/micro-services/images/deployment-strategy-1.png b/docs/micro-services/images/deployment-strategy-1.png
index f30401d19..728c24cef 100644
Binary files a/docs/micro-services/images/deployment-strategy-1.png and b/docs/micro-services/images/deployment-strategy-1.png differ
diff --git a/docs/micro-services/images/deployment-strategy-2.png b/docs/micro-services/images/deployment-strategy-2.png
index 165ec0a0c..45e8424d3 100644
Binary files a/docs/micro-services/images/deployment-strategy-2.png and b/docs/micro-services/images/deployment-strategy-2.png differ
diff --git a/docs/micro-services/images/deployment-strategy-3.png b/docs/micro-services/images/deployment-strategy-3.png
index beaeec50a..f18eaabaa 100644
Binary files a/docs/micro-services/images/deployment-strategy-3.png and b/docs/micro-services/images/deployment-strategy-3.png differ
diff --git a/docs/micro-services/images/eureka-instance-registered-currently.png b/docs/micro-services/images/eureka-instance-registered-currently.png
index 56404e60e..76d9ccefe 100644
Binary files a/docs/micro-services/images/eureka-instance-registered-currently.png and b/docs/micro-services/images/eureka-instance-registered-currently.png differ
diff --git a/docs/micro-services/images/eureka-server-applicationresource-addinstance.png b/docs/micro-services/images/eureka-server-applicationresource-addinstance.png
index b09c9f04a..e53cf0391 100644
Binary files a/docs/micro-services/images/eureka-server-applicationresource-addinstance.png and b/docs/micro-services/images/eureka-server-applicationresource-addinstance.png differ
diff --git a/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png b/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png
index 2d5704d2e..4886824aa 100644
Binary files a/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png and b/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png differ
diff --git a/docs/micro-services/images/eureka-server-evict-sequence-chart.png b/docs/micro-services/images/eureka-server-evict-sequence-chart.png
index 49af8fb56..2dc853fd6 100644
Binary files a/docs/micro-services/images/eureka-server-evict-sequence-chart.png and b/docs/micro-services/images/eureka-server-evict-sequence-chart.png differ
diff --git a/docs/micro-services/images/eureka-server-homepage.png b/docs/micro-services/images/eureka-server-homepage.png
index 6152d05fa..7b7990f8e 100644
Binary files a/docs/micro-services/images/eureka-server-homepage.png and b/docs/micro-services/images/eureka-server-homepage.png differ
diff --git a/docs/micro-services/images/eureka-server-instanceresource-cancellease.png b/docs/micro-services/images/eureka-server-instanceresource-cancellease.png
index 478984e49..9ef461431 100644
Binary files a/docs/micro-services/images/eureka-server-instanceresource-cancellease.png and b/docs/micro-services/images/eureka-server-instanceresource-cancellease.png differ
diff --git a/docs/micro-services/images/eureka-server-instanceresource-renew.png b/docs/micro-services/images/eureka-server-instanceresource-renew.png
index 02424ed95..7b053417d 100644
Binary files a/docs/micro-services/images/eureka-server-instanceresource-renew.png and b/docs/micro-services/images/eureka-server-instanceresource-renew.png differ
diff --git a/docs/micro-services/images/eureka-server-register-sequence-chart.png b/docs/micro-services/images/eureka-server-register-sequence-chart.png
index 6bc2fd6d1..1229176fb 100644
Binary files a/docs/micro-services/images/eureka-server-register-sequence-chart.png and b/docs/micro-services/images/eureka-server-register-sequence-chart.png differ
diff --git a/docs/micro-services/images/eureka-server-registry-structure.png b/docs/micro-services/images/eureka-server-registry-structure.png
index e3f487016..c70cdb32e 100644
Binary files a/docs/micro-services/images/eureka-server-registry-structure.png and b/docs/micro-services/images/eureka-server-registry-structure.png differ
diff --git a/docs/micro-services/images/eureka-server-renew-sequence-chart.png b/docs/micro-services/images/eureka-server-renew-sequence-chart.png
index 3e8dd1005..c0cbc2dd6 100644
Binary files a/docs/micro-services/images/eureka-server-renew-sequence-chart.png and b/docs/micro-services/images/eureka-server-renew-sequence-chart.png differ
diff --git a/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png b/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png
index b3b89560f..37d4c3cfb 100644
Binary files a/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png and b/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png differ
diff --git a/docs/micro-services/images/eureka-service-consumer-fetchregistry.png b/docs/micro-services/images/eureka-service-consumer-fetchregistry.png
index 0782482a5..e99bdf270 100644
Binary files a/docs/micro-services/images/eureka-service-consumer-fetchregistry.png and b/docs/micro-services/images/eureka-service-consumer-fetchregistry.png differ
diff --git a/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png
index becf2ea5f..2092cf2ff 100644
Binary files a/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png and b/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png differ
diff --git a/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png
index 8132d5cc8..4b1c8afc4 100644
Binary files a/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png and b/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png differ
diff --git a/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png
index e239928bb..804051b01 100644
Binary files a/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png and b/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png differ
diff --git a/docs/micro-services/images/micro-deployment.png b/docs/micro-services/images/micro-deployment.png
index 55c710ec9..191e4cd39 100644
Binary files a/docs/micro-services/images/micro-deployment.png and b/docs/micro-services/images/micro-deployment.png differ
diff --git a/docs/micro-services/images/pre-join.png b/docs/micro-services/images/pre-join.png
index 1971edce1..27ba6d861 100644
Binary files a/docs/micro-services/images/pre-join.png and b/docs/micro-services/images/pre-join.png differ
diff --git a/docs/micro-services/images/qrcode-for-doocs.jpg b/docs/micro-services/images/qrcode-for-doocs.jpg
deleted file mode 100644
index bd1db5d11..000000000
Binary files a/docs/micro-services/images/qrcode-for-doocs.jpg and /dev/null differ
diff --git a/docs/micro-services/images/qrcode-for-yanglbme.jpg b/docs/micro-services/images/qrcode-for-yanglbme.jpg
deleted file mode 100644
index 5bd385bca..000000000
Binary files a/docs/micro-services/images/qrcode-for-yanglbme.jpg and /dev/null differ
diff --git a/docs/micro-services/images/sketch.png b/docs/micro-services/images/sketch.png
index ac8827dd1..f4a540288 100644
Binary files a/docs/micro-services/images/sketch.png and b/docs/micro-services/images/sketch.png differ
diff --git a/docs/micro-services/micro-service-governance.md b/docs/micro-services/micro-service-governance.md
new file mode 100644
index 000000000..ba7363748
--- /dev/null
+++ b/docs/micro-services/micro-service-governance.md
@@ -0,0 +1,125 @@
+# 微服务治理策略
+
+## 服务的注册和发现
+
+解决问题:集中管理服务
+
+解决方法:
+
+- Eureka
+- Zookeeper
+
+## 负载均衡
+
+解决问题:降低服务器硬件压力
+
+解决方法:
+
+- Nginx
+- Ribbon
+
+## 通讯
+
+解决问题:各个服务之间的沟通桥梁
+
+解决方法:
+
+- REST(同步)
+- RPC(同步)
+- MQ(异步)
+
+## 配置管理
+
+解决问题:随着服务的增加配置也在增加,如何管理各个服务的配置。
+
+解决方法:
+
+- Nacos
+- Spring Cloud Config
+- Apollo
+
+## 容错和服务降级
+
+解决问题:在微服务当中,一个请求经常会涉及到调用几个服务,如果其中某个服务不可以,没有做服务容错的话,极有可能会造成一连串的服务不可用,这就是雪崩效应。
+
+解决方法:
+
+- Hystrix
+
+## 服务依赖关系
+
+解决问题:多个服务之间来回依赖,启动关系的不明确。
+
+解决方法:应用分层。
+
+## 服务文档
+
+解决问题:降低沟通成本
+
+解决方法:
+
+- Swagger
+- Java doc
+
+## 服务安全问题
+
+解决问题:敏感数据的安全性
+
+解决方法:
+
+- Oauth
+- Shiro
+- Spring Security
+
+## 流量控制
+
+解决问题:避免一个服务上的流量过大拖垮整个服务体系
+
+解决方法:
+
+- Hystrix
+
+## 自动化测试
+
+解决问题:提前预知异常,确定服务是否可用
+
+解决方法:
+
+- junit
+
+## 服务上线,下线的流程
+
+解决问题:避免服务随意的上线下线
+
+解决方法:新服务上线需要经过管理人员审核,服务下线需要告知各个调用方进行修改,直到没有调用该服务才可以进行下线。
+
+## 兼容性
+
+解决问题:服务开发持续进行如何做到兼容。
+
+解决方法:通过版本号的形式进行管理,修改完成进行回归测试。
+
+## 服务编排
+
+解决问题:解决服务依赖问题的一种方式
+
+解决方法:
+
+- Docker
+- K8s
+
+## 资源调度
+
+解决问题:每个服务的资源占用量不同,如何分配
+
+解决方法:
+
+- JVM 隔离
+- Classload 隔离
+- 硬件隔离
+
+## 容量规划
+
+解决问题:随着时间增长,调用逐步增加,什么时候追加机器。
+
+解决方法:统计每日调用量和响应时间,根据机器情况设置阈值,超过阈值就可以追加机器。
diff --git a/docs/micro-services/micro-services-technology-stack.md b/docs/micro-services/micro-services-technology-stack.md
new file mode 100644
index 000000000..c91a3177f
--- /dev/null
+++ b/docs/micro-services/micro-services-technology-stack.md
@@ -0,0 +1,227 @@
+# 微服务技术栈
+
+## 技术栈
+
+### 微服务开发
+
+作用:快速开发服务。
+
+- Spring
+- Spring MVC
+- Spring Boot
+
+[Spring](https://spring.io/) 目前是 JavaWeb 开发人员必不可少的一个框架,SpringBoot 简化了 Spring 开发的配置目前也是业内主流开发框架。
+
+### 微服务注册发现
+
+作用:发现服务,注册服务,集中管理服务。
+
+#### Eureka
+
+- Eureka Server : 提供服务注册服务, 各个节点启动后,会在 Eureka Server 中进行注册。
+- Eureka Client : 简化与 Eureka Server 的交互操作。
+- Spring Cloud Netflix : [GitHub](https://github.com/spring-cloud/spring-cloud-netflix),[文档](https://cloud.spring.io/spring-cloud-netflix/reference/html/)
+
+#### Zookeeper
+
+> ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
+
+[Zookeeper](https://github.com/apache/zookeeper) 是一个集中的服务, 用于维护配置信息、命名、提供分布式同步和提供组服务。
+
+#### Zookeeper 和 Eureka 区别
+
+Zookeeper 保证 CP,Eureka 保证 AP:
+
+- C:数据一致性;
+- A:服务可用性;
+- P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
+
+### 微服务配置管理
+
+作用:统一管理一个或多个服务的配置信息, 集中管理。
+
+#### [Disconf](https://github.com/knightliao/disconf)
+
+Distributed Configuration Management Platform(分布式配置管理平台) , 它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务, 是一套完整的基于 zookeeper 的分布式配置统一解决方案。
+
+#### [SpringCloudConfig](https://github.com/spring-cloud/spring-cloud-config)
+
+#### [Apollo](https://github.com/ctripcorp/apollo)
+
+Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,用于微服务配置管理场景。
+
+### 权限认证
+
+作用:根据系统设置的安全规则或者安全策略, 用户可以访问而且只能访问自己被授权的资源,不多不少。
+
+#### [Spring Security](https://spring.io/projects/spring-security)
+
+#### [Apache Shiro](http://shiro.apache.org/)
+
+> Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
+
+### 批处理
+
+作用: 批量处理同类型数据或事物
+
+#### [Spring Batch](https://spring.io/projects/spring-batch)
+
+### 定时任务
+
+> 作用: 定时做什么。
+
+#### [Quartz](http://www.quartz-scheduler.org/)
+
+### 微服务调用 (协议)
+
+> 通讯协议
+
+#### Rest
+
+- 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互
+
+#### RPC
+
+- Remote Procedure Call
+- 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。
+
+#### [gRPC](https://www.grpc.io/)
+
+> A high-performance, open-source universal RPC framework
+
+所谓 RPC(remote procedure call 远程过程调用) 框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从 server/client 模型。使用的时候客户端调用 server 端提供的接口就像是调用本地的函数一样。
+
+#### RMI
+
+- Remote Method Invocation
+- 纯 Java 调用
+
+### 服务接口调用
+
+> 作用:多个服务之间的通讯
+
+#### [Feign(HTTP)](https://github.com/OpenFeign/feign)
+
+Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。
+
+### 服务熔断
+
+> 作用: 当请求到达一定阈值时不让请求继续.
+
+#### [Hystrix](https://github.com/Netflix/Hystrix)
+
+> Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.
+
+#### [Sentinel](https://github.com/alibaba/Sentinel)
+
+> A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库)
+
+### 服务的负载均衡
+
+> 作用:降低服务压力, 增加吞吐量
+
+#### [Ribbon](https://github.com/Netflix/ribbon)
+
+> Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具, 它基于 Netflix Ribbon 实现
+
+#### [Nginx](https://github.com/nginx/nginx)
+
+Nginx (engine x) 是一个高性能的 HTTP 和反向代理 web 服务器, 同时也提供了 IMAP/POP3/SMTP 服务
+
+#### Nginx 与 Ribbon 区别
+
+Nginx 属于服务端负载均衡,Ribbon 属于客户端负载均衡。Nginx 作用与 Tomcat,Ribbon 作用与各个服务之间的调用 (RPC)。
+
+### 消息队列
+
+> 作用: 解耦业务, 异步化处理数据
+
+#### [Kafka](http://kafka.apache.org/)
+
+#### [RabbitMQ](https://www.rabbitmq.com/)
+
+#### [RocketMQ](http://rocketmq.apache.org/)
+
+#### [activeMQ](http://activemq.apache.org/)
+
+### 日志采集 (elk)
+
+> 作用: 收集各服务日志提供日志分析、用户画像等
+
+#### [Elasticsearch](https://github.com/elastic/elasticsearch)
+
+#### [Logstash](https://github.com/elastic/logstash)
+
+#### [Kibana](https://github.com/elastic/kibana)
+
+### API 网关
+
+> 作用: 外部请求通过 API 网关进行拦截处理, 再转发到真正的服务
+
+#### [Zuul](https://github.com/Netflix/zuul)
+
+> Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more.
+
+### 服务监控
+
+> 作用: 以可视化或非可视化的形式展示出各个服务的运行情况 (CPU、内存、访问量等)
+
+#### [Zabbix](https://github.com/jjmartres/Zabbix)
+
+#### [Nagios](https://www.nagios.org/)
+
+#### [Metrics](https://metrics.dropwizard.io)
+
+### 服务链路追踪
+
+> 作用: 明确服务之间的调用关系
+
+#### [Zipkin](https://github.com/openzipkin/zipkin)
+
+#### [Brave](https://github.com/openzipkin/brave)
+
+### 数据存储
+
+> 作用: 存储数据
+
+#### 关系型数据库
+
+##### [MySql](https://www.mysql.com/)
+
+##### [Oracle](https://www.oracle.com/index.html)
+
+##### [MsSQL](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15)
+
+##### [PostgreSql](https://www.postgresql.org/)
+
+#### 非关系型数据库
+
+##### [Mongodb](https://www.mongodb.com/)
+
+##### [Elasticsearch](https://github.com/elastic/elasticsearch)
+
+### 缓存
+
+> 作用: 存储数据
+
+#### [redis](https://redis.io/)
+
+### 分库分表
+
+> 作用: 数据库分库分表方案.
+
+#### [ShardingSphere](http://shardingsphere.apache.org/)
+
+#### [Mycat](http://www.mycat.io/)
+
+### 服务部署
+
+> 作用: 将项目快速部署、上线、持续集成.
+
+#### [Docker](http://www.docker.com/)
+
+#### [Jenkins](https://jenkins.io/zh/)
+
+#### [Kubernetes(K8s)](https://kubernetes.io/)
+
+#### [Mesos](http://mesos.apache.org/)
diff --git a/docs/micro-services/microservices-introduction.md b/docs/micro-services/microservices-introduction.md
index 93d66eb5a..27c488b11 100644
--- a/docs/micro-services/microservices-introduction.md
+++ b/docs/micro-services/microservices-introduction.md
@@ -1,6 +1,6 @@
-# 微服务
+# 关于微服务架构的描述
-> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。
本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。
+> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。
本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。
过去几年中出现了“微服务架构”这一术语,它描述了将软件应用程序设计为若干个可独立部署的服务套件的特定方法。尽管这种架构风格尚未有精确的定义,但围绕业务能力、自动部署、端点智能以及语言和数据的分散控制等组织来说,它们还是存在着某些共同特征。
@@ -44,7 +44,7 @@
在将大型应用程序拆分为多个部分时,管理层往往侧重于技术层面,从而导致 UI 团队、服务器端逻辑团队、数据库团队的划分。当团队按照这些方式分开时,即便是简单的更改也可能导致跨团队项目的时间和预算批准。一个聪明的团队将围绕这个进行优化,“两害相权取其轻”——只需将逻辑强制应用到他们可以访问的任何应用程序中。换句话说,逻辑无处不在。这是康威定律[5]的一个例子。
-> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。
—— 梅尔文•康威,1967年
+> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。
—— 梅尔文•康威,1967 年

@@ -96,7 +96,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
对于微服务社区来说,开销特别缺乏吸引力。这并不是说社区不重视服务合约。恰恰相反,因为他们有更多的合约。只是他们正在寻找不同的方式来管理这些合约。像 [Tolerant Reader](https://martinfowler.com/bliki/TolerantReader.html) 和 [Consumer-Driven Contracts](https://martinfowler.com/articles/consumerDrivenContracts.html) 这样的模式通常被用于微服务。这些援助服务合约在独立进化。执行消费者驱动的合约作为构建的一部分,增加了信心并对服务是否在运作提供了更快的反馈。事实上,我们知道澳大利亚的一个团队用消费者驱动的合约这种模式来驱动新业务的构建。他们使用简单的工具定义服务的合约。这已变成自动构建的一部分,即使新服务的代码还没写。服务仅在满足合约的时候才被创建出来 - 这是在构建新软件时避免 "YAGNI"[9] 困境的一个优雅的方法。围绕这些成长起来的技术和工具,通过减少服务间的临时耦合,限制了中心合约管理的需要。
-也许去中心化治理的最高境界就是亚马逊广为流传的 build it/run it 理念。团队要对他们构建的软件的各方面负责,包括 7*24 小时的运营。这一级别的责任下放绝对是不规范的,但我们看到越来越多的公司让开发团队负起更多责任。Netflix 是采用这一理念的另一家公司[11]。每天凌晨 3 点被传呼机叫醒无疑是一个强有力的激励,使你在写代码时关注质量。这是关于尽可能远离传统的集中治理模式的一些想法。
+也许去中心化治理的最高境界就是亚马逊广为流传的 build it/run it 理念。团队要对他们构建的软件的各方面负责,包括 7\*24 小时的运营。这一级别的责任下放绝对是不规范的,但我们看到越来越多的公司让开发团队负起更多责任。Netflix 是采用这一理念的另一家公司[11]。每天凌晨 3 点被传呼机叫醒无疑是一个强有力的激励,使你在写代码时关注质量。这是关于尽可能远离传统的集中治理模式的一些想法。
### 分散数据管理
@@ -202,7 +202,7 @@ Guardian 网站是设计和构建成单体应用程序的一个很好的例子
12: We are being a little disengenuous here. Obviously deploying more services, in more complex topologies is more difficult than deploying a single monolith. Fortunately, patterns reduce this complexity - investment in tooling is still a must though.
-13: In fact, Dan North refers to this style as *Replaceable Component Architecture* rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter.
+13: In fact, Dan North refers to this style as _Replaceable Component Architecture_ rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter.
14: Kent Beck highlights this as one his design principles in [Implementation Patterns](https://www.amazon.com/gp/product/0321413091?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321413091).
diff --git a/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md b/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md
index b11ad562a..9484ebfea 100644
--- a/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md
+++ b/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md
@@ -12,7 +12,7 @@ Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字
我们来看看其他可行策略。
-# 策略 1——停止挖掘
+## 策略 1——停止挖掘
Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用不可管理时这是最佳建议。换句话说,应该停止让单体式应用继续变大,也就是说当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。如下图所示:
@@ -24,17 +24,17 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
微服务有三种方式访问单体应用数据:
-* 换气单体应用提供的远程 API
-* 直接访问单体应用数据库
-* 自己维护一份从单体应用中同步的数据
+- 换气单体应用提供的远程 API
+- 直接访问单体应用数据库
+- 自己维护一份从单体应用中同步的数据
-胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 *Domain Driven Design*,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。
+胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 _Domain Driven Design_,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。
将新功能以轻量级微服务方式实现由很多优点,例如可以阻止单体应用变的更加无法管理。微服务本身可以开发、部署和独立扩展。采用微服务架构会给开发者带来不同的切身感受。
-然而,这方法并不解决任何单体式本身问题,为了解决单体式本身问题必须深入单体应用做出改变。我们来看看这么做的策略。
+然而,这方法并不解决任何单体式本身问题,为了解决单体式本身问题必须深入单体应用 做出改变。我们来看看这么做的策略。
-# 策略 2——将前端和后端分离
+## 策略 2——将前端和后端分离
减小单体式应用复杂度的策略是讲表现层和业务逻辑、数据访问层分开。典型的企业应用至少有三个不同元素构成:
@@ -52,11 +52,11 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
然而,这种策略只是部分的解决方案。很可能应用的两部分之一或者全部都是不可管理的,因此需要使用第三种策略来消除剩余的单体架构。
-# 策略 3——抽出服务
+## 策略 3——抽出服务
第三种迁移策略就是从单体应用中抽取出某些模块成为独立微服务。每当抽取一个模块变成微服务,单体应用就变简单一些;一旦转换足够多的模块,单体应用本身已经不成为问题了,要么消失了,要么简单到成为一个服务。
-## 排序那个模块应该被转成微服务
+### 排序那个模块应该被转成微服务
一个巨大的复杂单体应用由成十上百个模块构成,每个都是被抽取对象。决定第一个被抽取模块一般都是挑战,一般最好是从最容易抽取的模块开始,这会让开发者积累足够经验,这些经验可以为后续模块化工作带来巨大好处。
@@ -66,7 +66,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
查找现有粗粒度边界来决定哪个模块应该被抽取,也是很有益的,这使得移植工作更容易和简单。例如,只与其他应用异步同步消息的模块就是一个明显边界,可以很简单容易地将其转换为微服务。
-## 如何抽取模块
+### 如何抽取模块
抽取模块第一步就是定义好模块和单体应用之间粗粒度接口,由于单体应用需要微服务的数据,反之亦然,因此更像是一个双向 API。因为必须在负责依赖关系和细粒度接口模式之间做好平衡,因此开发这种 API 很有挑战性,尤其对使用域模型模式的业务逻辑层来说更具有挑战,因此经常需要改变代码来解决依赖性问题,如图所示:
diff --git a/docs/micro-services/huifer-what's-microservice-how-to-communicate.md b/docs/micro-services/what's-microservice-how-to-communicate.md
similarity index 78%
rename from docs/micro-services/huifer-what's-microservice-how-to-communicate.md
rename to docs/micro-services/what's-microservice-how-to-communicate.md
index 24442707d..7367a81c1 100644
--- a/docs/micro-services/huifer-what's-microservice-how-to-communicate.md
+++ b/docs/micro-services/what's-microservice-how-to-communicate.md
@@ -1,14 +1,11 @@
# 什么是微服务?微服务之间是如何独立通讯的?
-* Author: [HuiFer](https://github.com/huifer)
-* Description: 介绍微服务的定义以及服务间的通信。
-
## 什么是微服务
-* 微服务架构是一个分布式系统, 按照业务进行划分成为不同的服务单元, 解决单体系统性能等不足。
-* 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。
+- 微服务架构是一个分布式系统,按照业务进行划分成为不同的服务单元,解决单体系统性能等不足。
+- 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。
-> 微服务概念起源: [Microservices](https://martinfowler.com/articles/microservices.html)
+> 微服务概念起源:[Microservices](https://martinfowler.com/articles/microservices.html)
## 微服务之间是如何独立通讯的
@@ -16,17 +13,17 @@
#### REST HTTP 协议
-REST 请求在微服务中是最为常用的一种通讯方式, 它依赖于 HTTP\HTTPS 协议。RESTFUL 的特点是:
+REST 请求在微服务中是最为常用的一种通讯方式,它依赖于 HTTP\HTTPS 协议。RESTFUL 的特点是:
-1\. 每一个 URI 代表 1 种资源
-2\. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作: GET 用来获取资源, POST 用来新建资源\(也可以用于更新资源\), PUT 用来更新资源, DELETE 用来删除资源
-3\. 通过操作资源的表现形式来操作资源
-4\. 资源的表现形式是 XML 或者 HTML
-5\. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
+1. 每一个 URI 代表 1 种资源
+2. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源
+3. 通过操作资源的表现形式来操作资源
+4. 资源的表现形式是 XML 或者 HTML
+5. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
举个例子,有一个服务方提供了如下接口:
-``` java
+```java
@RestController
@RequestMapping("/communication")
public class RestControllerDemo {
@@ -39,7 +36,7 @@ public class RestControllerDemo {
另外一个服务需要去调用该接口,调用方只需要根据 API 文档发送请求即可获取返回结果。
-``` java
+```java
@RestController
@RequestMapping("/demo")
public class RestDemo{
@@ -56,27 +53,26 @@ public class RestDemo{
通过这样的方式可以实现服务之间的通讯。
-#### RPC TCP协议
+#### RPC TCP 协议
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。它的工作流程是这样的:
-1\. 执行客户端调用语句,传送参数
-2\. 调用本地系统发送网络消息
-3\. 消息传送到远程主机
-4\. 服务器得到消息并取得参数
-5\. 根据调用请求以及参数执行远程过程(服务)
-6\. 执行过程完毕,将结果返回服务器句柄
-7\. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果
-8\. 消息传回本地主机
-9\. 客户端句柄由本地主机的网络服务接收消息
+1. 执行客户端调用语句,传送参数
+2. 调用本地系统发送网络消息
+3. 消息传送到远程主机
+4. 服务器得到消息并取得参数
+5. 根据调用请求以及参数执行远程过程(服务)
+6. 执行过程完毕,将结果返回服务器句柄
+7. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果
+8. 消息传回本地主机
+9. 客户端句柄由本地主机的网络服务接收消息
10. 客户端接收到调用语句返回的结果数据
举个例子。
首先需要一个服务端:
-``` java
-
+```java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@@ -90,7 +86,6 @@ import java.util.concurrent.Executors;
/**
* RPC 服务端用来注册远程方法的接口和实现类
- * @Date: 2019-11-04
*/
public class RPCServer {
private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
@@ -183,8 +178,7 @@ public class RPCServer {
其次需要一个客户端:
-``` java
-
+```java
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
@@ -195,7 +189,6 @@ import java.net.Socket;
/**
* RPC 客户端
- * @Date: 2019-11-04
*/
public class RPCclient {
/**
@@ -242,7 +235,7 @@ public class RPCclient {
再来一个测试的远程方法。
-``` java
+```java
public interface Tinterface {
String send(String msg);
}
@@ -258,16 +251,10 @@ public class TinterfaceImpl implements Tinterface {
测试代码如下:
-``` java
-
-import com.huifer.admin.rpc.Tinterface;
-import com.huifer.admin.rpc.TinterfaceImpl;
-
+```java
import java.net.InetSocketAddress;
-/**
- * @Date: 2019-11-04
- */
+
public class RunTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@@ -292,5 +279,4 @@ public class RunTest {
#### 消息中间件
-> 常见的消息中间件有 Kafka、ActiveMQ、RabbitMQ、RocketMQ , 常见的协议有AMQP、MQTTP、STOMP、XMPP. 这里不对消息队列进行拓展了, 具体如何使用还是请移步官网.
->
+常见的消息中间件有 Kafka、ActiveMQ、RabbitMQ、RocketMQ ,常见的协议有 AMQP、MQTTP、STOMP、XMPP。这里不对消息队列进行拓展了,具体如何使用还是请移步官网。
diff --git a/docs/public/favicon-16x16.png b/docs/public/favicon-16x16.png
new file mode 100644
index 000000000..d0010b987
Binary files /dev/null and b/docs/public/favicon-16x16.png differ
diff --git a/docs/public/favicon-32x32.png b/docs/public/favicon-32x32.png
new file mode 100644
index 000000000..292ae8597
Binary files /dev/null and b/docs/public/favicon-32x32.png differ
diff --git a/docs/public/icon.png b/docs/public/icon.png
new file mode 100644
index 000000000..1de9f42e0
Binary files /dev/null and b/docs/public/icon.png differ
diff --git a/images/favicon-16x16.png b/images/favicon-16x16.png
index 3c1b762d6..d0010b987 100644
Binary files a/images/favicon-16x16.png and b/images/favicon-16x16.png differ
diff --git a/images/favicon-32x32.png b/images/favicon-32x32.png
index 7c069d2ef..292ae8597 100644
Binary files a/images/favicon-32x32.png and b/images/favicon-32x32.png differ
diff --git a/images/icon.png b/images/icon.png
index a39d36b6f..1de9f42e0 100644
Binary files a/images/icon.png and b/images/icon.png differ
diff --git a/images/pdf.png b/images/pdf.png
new file mode 100644
index 000000000..1d4f2926e
Binary files /dev/null and b/images/pdf.png differ
diff --git a/images/qrcode-for-doocs.jpg b/images/qrcode-for-doocs.jpg
index bd1db5d11..487ce6b28 100644
Binary files a/images/qrcode-for-doocs.jpg and b/images/qrcode-for-doocs.jpg differ
diff --git a/images/qrcode-for-yanglbme.jpg b/images/qrcode-for-yanglbme.jpg
index 5bd385bca..cfbb34448 100644
Binary files a/images/qrcode-for-yanglbme.jpg and b/images/qrcode-for-yanglbme.jpg differ
diff --git a/images/starcharts.svg b/images/starcharts.svg
new file mode 100644
index 000000000..40f7b1f19
--- /dev/null
+++ b/images/starcharts.svg
@@ -0,0 +1,77494 @@
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
deleted file mode 100644
index b3fe9e8f8..000000000
--- a/index.html
+++ /dev/null
@@ -1,149 +0,0 @@
-
-
-
-
-
- 互联网 Java 工程师进阶知识完全扫盲
-
-
-
-
-
-
-
-
-
-
-
-
- 本系列知识由 Doocs 开源社区总结发布
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..c1789e796
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2304 @@
+{
+ "name": "advanced-java",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "vitepress-plugin-comment-with-giscus": "^1.1.15"
+ },
+ "devDependencies": {
+ "vitepress": "^1.6.3"
+ }
+ },
+ "node_modules/@algolia/autocomplete-core": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz",
+ "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/autocomplete-plugin-algolia-insights": "1.17.7",
+ "@algolia/autocomplete-shared": "1.17.7"
+ }
+ },
+ "node_modules/@algolia/autocomplete-plugin-algolia-insights": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz",
+ "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/autocomplete-shared": "1.17.7"
+ },
+ "peerDependencies": {
+ "search-insights": ">= 1 < 3"
+ }
+ },
+ "node_modules/@algolia/autocomplete-preset-algolia": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz",
+ "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/autocomplete-shared": "1.17.7"
+ },
+ "peerDependencies": {
+ "@algolia/client-search": ">= 4.9.1 < 6",
+ "algoliasearch": ">= 4.9.1 < 6"
+ }
+ },
+ "node_modules/@algolia/autocomplete-shared": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz",
+ "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==",
+ "dev": true,
+ "peerDependencies": {
+ "@algolia/client-search": ">= 4.9.1 < 6",
+ "algoliasearch": ">= 4.9.1 < 6"
+ }
+ },
+ "node_modules/@algolia/client-abtesting": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/client-abtesting/-/client-abtesting-5.23.3.tgz",
+ "integrity": "sha512-yHI0hBwYcNPc+nJoHPTmmlP8pG6nstCEhpHaZQCDwLZhdMtNhd1hliZMCtLgNnvd1yKEgTt/ZDnTSdZLehfKdA==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-analytics": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/client-analytics/-/client-analytics-5.23.3.tgz",
+ "integrity": "sha512-/70Ey+nZm4bRr2DcNrGU251YIn9lDu0g8xeP4jTCyunGRNFZ/d8hQAw9El34pcTpO1QDojJWAi6ywKIrUaks9w==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-common": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/client-common/-/client-common-5.23.3.tgz",
+ "integrity": "sha512-fkpbPclIvaiyw3ADKRBCxMZhrNx/8//6DClfWGxeEiTJ0HEEYtHlqE6GjAkEJubz4v1ioCQkhZwMoFfFct2/vQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-insights": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/client-insights/-/client-insights-5.23.3.tgz",
+ "integrity": "sha512-TXc5Ve6QOCihWCTWY9N56CZxF1iovzpBWBUhQhy6JSiUfX3MXceV3saV+sXHQ1NVt2NKkyUfEspYHBsTrYzIDg==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-personalization": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/client-personalization/-/client-personalization-5.23.3.tgz",
+ "integrity": "sha512-JlReruxxiw9LB53jF/BmvVV+c0thiWQUHRdgtbVIEusvRaiX1IdpWJSPQExEtBQ7VFg89nP8niCzWtA34ktKSA==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-query-suggestions": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.23.3.tgz",
+ "integrity": "sha512-GDEExFMXwx0ScE0AZUA4F6ssztdJvGcXUkdWmWyt2hbYz43ukqmlVJqPaYgGmWdjJjvTx+dNF/hcinwWuXbCug==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-search": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/client-search/-/client-search-5.23.3.tgz",
+ "integrity": "sha512-mwofV6tGo0oHt4BPi+S5eLC3wnhOa4A1OVgPxetTxZuetod+2W4cxKavUW2v/Ma5CABXPLooXX+g9E67umELZw==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/ingestion": {
+ "version": "1.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/ingestion/-/ingestion-1.23.3.tgz",
+ "integrity": "sha512-Zxgmi7Hk4lI52YFphzzJekUqWxYxVjY2GrCpOxV+QiojvUi8Ru+knq6REcwLHFSwpwaDh2Th5pOefMpn4EkQCw==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/monitoring": {
+ "version": "1.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/monitoring/-/monitoring-1.23.3.tgz",
+ "integrity": "sha512-zi/IqvsmFW4E5gMaovAE4KRbXQ+LDYpPGG1nHtfuD5u3SSuQ31fT1vX2zqb6PbPTlgJMEmMk91Mbb7fIKmbQUw==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/recommend": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/recommend/-/recommend-5.23.3.tgz",
+ "integrity": "sha512-C9TwbT1zGwULLXGSUSB+G7o/30djacPmQcsTHepvT47PVfPr2ISK/5QVtUnjMU84LEP8uNjuPUeM4ZeWVJ2iuQ==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-browser-xhr": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.23.3.tgz",
+ "integrity": "sha512-/7oYeUhYzY0lls7WtkAURM6wy21/Wwmq9GdujW1MpoYVC0ATXXxwCiAfOpYL9xdWxLV0R3wjyD+yZEni+nboKg==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-fetch": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/requester-fetch/-/requester-fetch-5.23.3.tgz",
+ "integrity": "sha512-r/4fKz4t+bSU1KdjRq+swdNvuGfJ0spV8aFTHPtcsF+1ZaN/VqmdXrTe5NkaZLSztFeMqKwZlJIVvE7VuGlFtw==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-node-http": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/@algolia/requester-node-http/-/requester-node-http-5.23.3.tgz",
+ "integrity": "sha512-UZiTNmUBQFPl3tUKuXaDd8BxEC0t0ny86wwW6XgwfM9IQf4PrzuMpvuOGIJMcCGlrNolZDEI0mcbz/tqRdKW7A==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-common": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz",
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "dependencies": {
+ "@babel/types": "^7.27.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.0.tgz",
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@docsearch/css": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmmirror.com/@docsearch/css/-/css-3.8.2.tgz",
+ "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==",
+ "dev": true
+ },
+ "node_modules/@docsearch/js": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmmirror.com/@docsearch/js/-/js-3.8.2.tgz",
+ "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==",
+ "dev": true,
+ "dependencies": {
+ "@docsearch/react": "3.8.2",
+ "preact": "^10.0.0"
+ }
+ },
+ "node_modules/@docsearch/react": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmmirror.com/@docsearch/react/-/react-3.8.2.tgz",
+ "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/autocomplete-core": "1.17.7",
+ "@algolia/autocomplete-preset-algolia": "1.17.7",
+ "@docsearch/css": "3.8.2",
+ "algoliasearch": "^5.14.2"
+ },
+ "peerDependencies": {
+ "@types/react": ">= 16.8.0 < 19.0.0",
+ "react": ">= 16.8.0 < 19.0.0",
+ "react-dom": ">= 16.8.0 < 19.0.0",
+ "search-insights": ">= 1 < 3"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "search-insights": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@giscus/vue": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmmirror.com/@giscus/vue/-/vue-2.4.0.tgz",
+ "integrity": "sha512-QOxKHgsMT91myyQagP2v20YYAei1ByZuc3qcaYxbHx4AwOeyVrybDIuRFwG9YDv6OraC86jYnU4Ixd37ddC/0A==",
+ "dependencies": {
+ "giscus": "^1.4.0"
+ },
+ "peerDependencies": {
+ "vue": ">=3.2.0"
+ }
+ },
+ "node_modules/@iconify-json/simple-icons": {
+ "version": "1.2.31",
+ "resolved": "https://registry.npmmirror.com/@iconify-json/simple-icons/-/simple-icons-1.2.31.tgz",
+ "integrity": "sha512-xBUPtvkcSAiXs9DfVtudhLddQtQYin3I3Ph/W5FNYA0oE6r2hmLB8TgOog9OjOt1Sxn3IB5+4n5+64DMf2xNmQ==",
+ "dev": true,
+ "dependencies": {
+ "@iconify/types": "*"
+ }
+ },
+ "node_modules/@iconify/types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz",
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ },
+ "node_modules/@lit-labs/ssr-dom-shim": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
+ "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ=="
+ },
+ "node_modules/@lit/reactive-element": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/@lit/reactive-element/-/reactive-element-2.1.0.tgz",
+ "integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
+ "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz",
+ "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz",
+ "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz",
+ "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz",
+ "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz",
+ "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz",
+ "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz",
+ "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz",
+ "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz",
+ "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz",
+ "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz",
+ "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz",
+ "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz",
+ "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz",
+ "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz",
+ "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz",
+ "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz",
+ "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz",
+ "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz",
+ "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@shikijs/core": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-2.5.0.tgz",
+ "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/engine-javascript": "2.5.0",
+ "@shikijs/engine-oniguruma": "2.5.0",
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4",
+ "hast-util-to-html": "^9.0.4"
+ }
+ },
+ "node_modules/@shikijs/engine-javascript": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz",
+ "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "oniguruma-to-es": "^3.1.0"
+ }
+ },
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz",
+ "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ }
+ },
+ "node_modules/@shikijs/langs": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-2.5.0.tgz",
+ "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/types": "2.5.0"
+ }
+ },
+ "node_modules/@shikijs/themes": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-2.5.0.tgz",
+ "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/types": "2.5.0"
+ }
+ },
+ "node_modules/@shikijs/transformers": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/transformers/-/transformers-2.5.0.tgz",
+ "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/core": "2.5.0",
+ "@shikijs/types": "2.5.0"
+ }
+ },
+ "node_modules/@shikijs/types": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-2.5.0.tgz",
+ "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmmirror.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
+ "dev": true
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+ "dev": true
+ },
+ "node_modules/@types/markdown-it": {
+ "version": "14.1.2",
+ "resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+ "dev": true,
+ "dependencies": {
+ "@types/linkify-it": "^5",
+ "@types/mdurl": "^2"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+ "dev": true
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
+ "dev": true
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz",
+ "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==",
+ "dev": true,
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+ "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/shared": "3.5.13",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+ "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+ "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/compiler-core": "3.5.13",
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.11",
+ "postcss": "^8.4.48",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+ "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.2.tgz",
+ "integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==",
+ "dev": true,
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.2"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz",
+ "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==",
+ "dev": true,
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.2",
+ "birpc": "^0.2.19",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.1"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz",
+ "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==",
+ "dev": true,
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
+ "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+ "dependencies": {
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+ "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+ "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/runtime-core": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+ "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "vue": "3.5.13"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
+ "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
+ },
+ "node_modules/@vueuse/core": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.8.2.tgz",
+ "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "12.8.2",
+ "@vueuse/shared": "12.8.2",
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/integrations": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmmirror.com/@vueuse/integrations/-/integrations-12.8.2.tgz",
+ "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==",
+ "dev": true,
+ "dependencies": {
+ "@vueuse/core": "12.8.2",
+ "@vueuse/shared": "12.8.2",
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "async-validator": "^4",
+ "axios": "^1",
+ "change-case": "^5",
+ "drauu": "^0.4",
+ "focus-trap": "^7",
+ "fuse.js": "^7",
+ "idb-keyval": "^6",
+ "jwt-decode": "^4",
+ "nprogress": "^0.2",
+ "qrcode": "^1.5",
+ "sortablejs": "^1",
+ "universal-cookie": "^7"
+ },
+ "peerDependenciesMeta": {
+ "async-validator": {
+ "optional": true
+ },
+ "axios": {
+ "optional": true
+ },
+ "change-case": {
+ "optional": true
+ },
+ "drauu": {
+ "optional": true
+ },
+ "focus-trap": {
+ "optional": true
+ },
+ "fuse.js": {
+ "optional": true
+ },
+ "idb-keyval": {
+ "optional": true
+ },
+ "jwt-decode": {
+ "optional": true
+ },
+ "nprogress": {
+ "optional": true
+ },
+ "qrcode": {
+ "optional": true
+ },
+ "sortablejs": {
+ "optional": true
+ },
+ "universal-cookie": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-12.8.2.tgz",
+ "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-12.8.2.tgz",
+ "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==",
+ "dev": true,
+ "dependencies": {
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/algoliasearch": {
+ "version": "5.23.3",
+ "resolved": "https://registry.npmmirror.com/algoliasearch/-/algoliasearch-5.23.3.tgz",
+ "integrity": "sha512-0JlUaY/hl3LrKvbidI5FysEi2ggAlcTHM8AHV2UsrJUXnNo8/lWBfhzc1b7o8bK3YZNiU26JtLyT9exoj5VBgA==",
+ "dev": true,
+ "dependencies": {
+ "@algolia/client-abtesting": "5.23.3",
+ "@algolia/client-analytics": "5.23.3",
+ "@algolia/client-common": "5.23.3",
+ "@algolia/client-insights": "5.23.3",
+ "@algolia/client-personalization": "5.23.3",
+ "@algolia/client-query-suggestions": "5.23.3",
+ "@algolia/client-search": "5.23.3",
+ "@algolia/ingestion": "1.23.3",
+ "@algolia/monitoring": "1.23.3",
+ "@algolia/recommend": "5.23.3",
+ "@algolia/requester-browser-xhr": "5.23.3",
+ "@algolia/requester-fetch": "5.23.3",
+ "@algolia/requester-node-http": "5.23.3"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/birpc": {
+ "version": "0.2.19",
+ "resolved": "https://registry.npmmirror.com/birpc/-/birpc-0.2.19.tgz",
+ "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "dev": true,
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dev": true,
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/emoji-regex-xs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
+ "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==",
+ "dev": true
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/focus-trap": {
+ "version": "7.6.4",
+ "resolved": "https://registry.npmmirror.com/focus-trap/-/focus-trap-7.6.4.tgz",
+ "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==",
+ "dev": true,
+ "dependencies": {
+ "tabbable": "^6.2.0"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/giscus": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/giscus/-/giscus-1.6.0.tgz",
+ "integrity": "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==",
+ "dependencies": {
+ "lit": "^3.2.1"
+ }
+ },
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmmirror.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
+ "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
+ "dev": true,
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "dev": true,
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "dev": true
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/lit": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmmirror.com/lit/-/lit-3.3.0.tgz",
+ "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==",
+ "dependencies": {
+ "@lit/reactive-element": "^2.1.0",
+ "lit-element": "^4.2.0",
+ "lit-html": "^3.3.0"
+ }
+ },
+ "node_modules/lit-element": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/lit-element/-/lit-element-4.2.0.tgz",
+ "integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0",
+ "@lit/reactive-element": "^2.1.0",
+ "lit-html": "^3.3.0"
+ }
+ },
+ "node_modules/lit-html": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmmirror.com/lit-html/-/lit-html-3.3.0.tgz",
+ "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==",
+ "dependencies": {
+ "@types/trusted-types": "^2.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/mark.js": {
+ "version": "8.11.1",
+ "resolved": "https://registry.npmmirror.com/mark.js/-/mark.js-8.11.1.tgz",
+ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==",
+ "dev": true
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "dev": true,
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/minisearch": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmmirror.com/minisearch/-/minisearch-7.1.2.tgz",
+ "integrity": "sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==",
+ "dev": true
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/oniguruma-to-es": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz",
+ "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex-xs": "^1.0.0",
+ "regex": "^6.0.1",
+ "regex-recursion": "^6.0.2"
+ }
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/preact": {
+ "version": "10.26.5",
+ "resolved": "https://registry.npmmirror.com/preact/-/preact-10.26.5.tgz",
+ "integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.0.0.tgz",
+ "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/regex/-/regex-6.0.1.tgz",
+ "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==",
+ "dev": true,
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-recursion": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-6.0.2.tgz",
+ "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
+ "dev": true,
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-utilities": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/regex-utilities/-/regex-utilities-2.3.0.tgz",
+ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
+ "dev": true
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "dev": true
+ },
+ "node_modules/rollup": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.0.tgz",
+ "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.7"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.40.0",
+ "@rollup/rollup-android-arm64": "4.40.0",
+ "@rollup/rollup-darwin-arm64": "4.40.0",
+ "@rollup/rollup-darwin-x64": "4.40.0",
+ "@rollup/rollup-freebsd-arm64": "4.40.0",
+ "@rollup/rollup-freebsd-x64": "4.40.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.40.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.40.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.40.0",
+ "@rollup/rollup-linux-arm64-musl": "4.40.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.40.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.40.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.40.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.40.0",
+ "@rollup/rollup-linux-x64-gnu": "4.40.0",
+ "@rollup/rollup-linux-x64-musl": "4.40.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.40.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.40.0",
+ "@rollup/rollup-win32-x64-msvc": "4.40.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/search-insights": {
+ "version": "2.17.3",
+ "resolved": "https://registry.npmmirror.com/search-insights/-/search-insights-2.17.3.tgz",
+ "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/shiki": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/shiki/-/shiki-2.5.0.tgz",
+ "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==",
+ "dev": true,
+ "dependencies": {
+ "@shikijs/core": "2.5.0",
+ "@shikijs/engine-javascript": "2.5.0",
+ "@shikijs/engine-oniguruma": "2.5.0",
+ "@shikijs/langs": "2.5.0",
+ "@shikijs/themes": "2.5.0",
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "dev": true,
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/superjson": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz",
+ "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
+ "dev": true,
+ "dependencies": {
+ "copy-anything": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmmirror.com/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "dev": true
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.0.tgz",
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.2.tgz",
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+ "dev": true,
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.18",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.18.tgz",
+ "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitepress": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmmirror.com/vitepress/-/vitepress-1.6.3.tgz",
+ "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==",
+ "dev": true,
+ "dependencies": {
+ "@docsearch/css": "3.8.2",
+ "@docsearch/js": "3.8.2",
+ "@iconify-json/simple-icons": "^1.2.21",
+ "@shikijs/core": "^2.1.0",
+ "@shikijs/transformers": "^2.1.0",
+ "@shikijs/types": "^2.1.0",
+ "@types/markdown-it": "^14.1.2",
+ "@vitejs/plugin-vue": "^5.2.1",
+ "@vue/devtools-api": "^7.7.0",
+ "@vue/shared": "^3.5.13",
+ "@vueuse/core": "^12.4.0",
+ "@vueuse/integrations": "^12.4.0",
+ "focus-trap": "^7.6.4",
+ "mark.js": "8.11.1",
+ "minisearch": "^7.1.1",
+ "shiki": "^2.1.0",
+ "vite": "^5.4.14",
+ "vue": "^3.5.13"
+ },
+ "bin": {
+ "vitepress": "bin/vitepress.js"
+ },
+ "peerDependencies": {
+ "markdown-it-mathjax3": "^4",
+ "postcss": "^8"
+ },
+ "peerDependenciesMeta": {
+ "markdown-it-mathjax3": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitepress-plugin-comment-with-giscus": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmmirror.com/vitepress-plugin-comment-with-giscus/-/vitepress-plugin-comment-with-giscus-1.1.15.tgz",
+ "integrity": "sha512-1DJjgN+7SYvn5ZkjuSXPmz7nlqfcrh4qCGGviiZghA2ELXnaO2m9WY7m+RisPSaqCn90xqe0JbO2T4NMq8iUBg==",
+ "dependencies": {
+ "@giscus/vue": "^2.2.8"
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
+ "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-sfc": "3.5.13",
+ "@vue/runtime-dom": "3.5.13",
+ "@vue/server-renderer": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "dev": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..71733933c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+ "devDependencies": {
+ "vitepress": "^1.6.3"
+ },
+ "scripts": {
+ "docs:dev": "vitepress dev docs",
+ "docs:build": "vitepress build docs",
+ "docs:preview": "vitepress preview docs"
+ },
+ "dependencies": {
+ "vitepress-plugin-comment-with-giscus": "^1.1.15"
+ }
+}
diff --git a/summary.md b/summary.md
deleted file mode 100644
index 8087ba649..000000000
--- a/summary.md
+++ /dev/null
@@ -1,126 +0,0 @@
-- 高并发架构
-
- - [消息队列](./docs/high-concurrency/mq-interview.md)
- - [为什么使用消息队列?](./docs/high-concurrency/why-mq.md)
- - [如何保证消息队列的高可用?](./docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
- - [如何保证消息不被重复消费?](./docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
- - [如何保证消息的可靠性传输?](./docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)
- - [如何保证消息的顺序性?](./docs/high-concurrency/how-to-ensure-the-order-of-messages.md)
- - [如何解决消息队列的延时以及过期失效问题?](./docs/high-concurrency/mq-time-delay-and-expired-failure.md)
- - [如何设计一个消息队列?](./docs/high-concurrency/mq-design.md)
-
- - [搜索引擎](./docs/high-concurrency/es-introduction.md)
- - [ES 的分布式架构原理是什么?](./docs/high-concurrency/es-architecture.md)
- - [ES 写入数据的工作原理是什么?](./docs/high-concurrency/es-write-query-search.md)
- - [ES 在数十亿级别数量下如何提高查询效率?](./docs/high-concurrency/es-optimizing-query-performance.md)
- - [ES 生产集群的部署架构是什么?](./docs/high-concurrency/es-production-cluster.md)
-
- - 缓存
- - [在项目中缓存是如何使用的?](./docs/high-concurrency/why-cache.md)
- - [Redis 和 Memcached 有什么区别?](./docs/high-concurrency/redis-single-thread-model.md)
- - [Redis 都有哪些数据类型以及适用场景?](./docs/high-concurrency/redis-data-types.md)
- - [Redis 的过期策略都有哪些?](./docs/high-concurrency/redis-expiration-policies-and-lru.md)
- - [如何保证 Redis 高并发、高可用?](./docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
- - [Redis 的持久化有哪几种方式?](./docs/high-concurrency/redis-persistence.md)
- - [Redis 集群模式的工作原理能说一下么?](./docs/high-concurrency/redis-cluster.md)
- - [Redis 的雪崩、穿透和击穿,如何应对?](./docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
- - [如何保证缓存与数据库双写一致性?](./docs/high-concurrency/redis-consistence.md)
- - [如何解决 Redis 的并发竞争问题?](./docs/high-concurrency/redis-cas.md)
- - [生产环境中的 Redis 是怎么部署的?](./docs/high-concurrency/redis-production-environment.md)
-
- - 分库分表
- - [为什么要分库分表?](./docs/high-concurrency/database-shard.md)
- - [分库分表如何平滑过渡?](./docs/high-concurrency/database-shard-method.md)
- - [设计一个动态扩容缩容的分库分表方案?](./docs/high-concurrency/database-shard-dynamic-expand.md)
- - [分库分表之后,id 主键如何处理?](./docs/high-concurrency/database-shard-global-id-generate.md)
-
- - 读写分离
- - [如何实现 MySQL 的读写分离?](./docs/high-concurrency/mysql-read-write-separation.md)
-
- - 高并发系统
- - [如何设计一个高并发系统?](./docs/high-concurrency/high-concurrency-design.md)
-
-* 分布式系统
- - [面试连环炮](./docs/distributed-system/distributed-system-interview.md)
- - 系统拆分
- - [为什么要进行系统拆分?](./docs/distributed-system/why-dubbo.md)
-
- - 分布式服务框架
- - [说一下 Dubbo 的工作原理?](./docs/distributed-system/dubbo-operating-principle.md)
- - [Dubbo 支持哪些序列化协议?](./docs/distributed-system/dubbo-serialization-protocol.md)
- - [Dubbo 负载均衡策略和集群容错策略?](./docs/distributed-system/dubbo-load-balancing.md)
- - [Dubbo 的 SPI 思想是什么?](./docs/distributed-system/dubbo-spi.md)
- - [如何基于 Dubbo 进行服务治理?](./docs/distributed-system/dubbo-service-management.md)
- - [分布式服务接口的幂等性如何设计?](./docs/distributed-system/distributed-system-idempotency.md)
- - [分布式服务接口请求的顺序性如何保证?](./docs/distributed-system/distributed-system-request-sequence.md)
- - [如何自己设计一个类似 Dubbo 的 RPC 框架?](./docs/distributed-system/dubbo-rpc-design.md)
- - [CAP 定理的 P 是什么?](./docs/distributed-system/distributed-system-cap.md)
-
- - 分布式锁
- - [Zookeeper 都有哪些应用场景?](./docs/distributed-system/zookeeper-application-scenarios.md)
- - [分布式锁如何设计?](./docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
-
- - 分布式事务
- - [分布式事务了解吗?](./docs/distributed-system/distributed-transaction.md)
-
- - 分布式会话
- - [集群分布式 Session 如何实现?](./docs/distributed-system/distributed-session.md)
-
-* 高可用架构
- - 基于 Hystrix 实现高可用
- - [Hystrix 介绍](./docs/high-availability/hystrix-introduction.md)
- - [电商网站详情页系统架构](./docs/high-availability/e-commerce-website-detail-page-architecture.md)
- - [Hystrix 线程池技术实现资源隔离](./docs/high-availability/hystrix-thread-pool-isolation.md)
- - [Hystrix 信号量机制实现资源隔离](./docs/high-availability/hystrix-semphore-isolation.md)
- - [Hystrix 隔离策略细粒度控制](./docs/high-availability/hystrix-execution-isolation.md)
- - [深入 Hystrix 执行时内部原理](./docs/high-availability/hystrix-process.md)
- - [基于 request cache 请求缓存技术优化批量商品数据查询接口](./docs/high-availability/hystrix-request-cache.md)
- - [基于本地缓存的 fallback 降级机制](./docs/high-availability/hystrix-fallback.md)
- - [深入 Hystrix 断路器执行原理](./docs/high-availability/hystrix-circuit-breaker.md)
- - [深入 Hystrix 线程池隔离与接口限流](./docs/high-availability/hystrix-thread-pool-current-limiting.md)
- - [基于 timeout 机制为服务接口调用超时提供安全保护](./docs/high-availability/hystrix-timeout.md)
-
- - 高可用系统
- - 如何设计一个高可用系统?
-
- - 限流
- - [如何限流?说一下具体的实现?](/docs/high-concurrency/huifer-how-to-limit-current.md)
-
- - 熔断
- - 如何进行熔断?
- - 熔断框架都有哪些?具体实现原理知道吗?
- - [熔断框架,选用 Sentinel 还是 Hystrix?](./docs/high-availability/sentinel-vs-hystrix.md)
-
- - 降级
- - 如何进行降级?
-
-* 微服务架构
- - 微服务的一些概念
- - [关于微服务架构的描述](./docs/micro-services/microservices-introduction.md)
- - [从单体式架构迁移到微服务架构](./docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md)
- - [微服务的事件驱动数据管理](./docs/micro-services/event-driven-data-management-for-microservices.md)
- - [选择微服务部署策略](./docs/micro-services/choose-microservice-deployment-strategy.md)
-
- - Spring Cloud 微服务架构
- - [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/huifer-what's-microservice-how-to-communicate.md)
- - Spring Cloud 和 Dubbo 有哪些区别?
- - Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
- - 什么是服务熔断?什么是服务降级?
- - 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
- - [你所知道的微服务技术栈都有哪些?](/docs/micro-services/huifer-micro-services-technology-stack.md)
- - [微服务治理策略](/docs/micro-services/huifer-micro-service-governance.md)
- - Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
- - [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md)
-
-* 海量数据处理
- - 10 道经典的海量数据处理面试题
- - [如何从大量的 URL 中找出相同的 URL?](./docs/big-data/find-common-urls.md)
- - [如何从大量数据中找出高频词?](./docs/big-data/find-top-100-words.md)
- - [如何找出某一天访问百度网站最多的 IP?](./docs/big-data/find-top-1-ip.md)
- - [如何在大量的数据中找出不重复的整数?](./docs/big-data/find-no-repeat-number.md)
- - [如何在大量的数据中判断一个数是否存在?](./docs/big-data/find-a-number-if-exists.md)
- - [如何查询最热门的查询串?](./docs/big-data/find-hotest-query-string.md)
- - [如何统计不同电话号码的个数?](./docs/big-data/count-different-phone-numbers.md)
- - [如何从 5 亿个数中找出中位数?](./docs/big-data/find-mid-value-in-500-millions.md)
- - [如何按照 query 的频度排序?](./docs/big-data/sort-the-query-strings-by-counts.md)
- - [如何找出排名前 500 的数?](./docs/big-data/find-rank-top-500-numbers.md)