提交 dd274075 编写于 作者: Y yanglbme

docs: format document with prettier

全面整理项目内容,可读性更佳
上级 ffb727bb
此差异已折叠。
# 海量数据处理
* [如何从大量的 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)
- [如何从大量的 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)
---
## 公众号
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
关注「**Doocs开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
![](./images/pdf.png)
......
......@@ -7,6 +7,7 @@
### 解答思路
#### 方法一:分治法
依然可以用分治法解决,方法与前面类似,就不再次赘述了。
#### 方法二:位图法
......
......@@ -6,9 +6,9 @@
### 解答思路
每个 URL 占 64B,那么 50 亿个 URL占用的空间大小约为 320GB。
每个 URL 占 64B,那么 50 亿个 URL 占用的空间大小约为 320GB。
> 5, 000, 000, 000 * 64B ≈ 5GB * 64 = 320GB
> 5, 000, 000, 000 _ 64B ≈ 5GB _ 64 = 320GB
由于内存大小只有 4G,因此,我们不可能一次性把所有 URL 加载到内存中处理。对于这种类型的题目,一般采用**分治策略**,即:把一个文件中的 URL 按照某个特征划分为多个小文件,使得每个小文件大小不超过 4G,这样就可以把这个小文件读到内存中进行处理了。
......@@ -16,7 +16,7 @@
首先遍历文件 a,对遍历到的 URL 求 `hash(URL) % 1000` ,根据计算结果把遍历到的 URL 存储到 a<sub>0</sub>, a<sub>1</sub>, a<sub>2</sub>, ..., a<sub>999</sub>,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b<sub>0</sub>, b<sub>1</sub>, b<sub>2</sub>, ..., b<sub>999</sub> 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a<sub>0</sub> 对应 b<sub>0</sub>, ..., a<sub>999</sub> 对应 b<sub>999</sub>,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。
接着遍历 a<sub>i</sub>( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
接着遍历 a<sub>i</sub>( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
### 方法总结
......
......@@ -20,7 +20,7 @@
#### 方法二:HashMap 法
虽然字符串总数比较多,但去重后不超过 300w,因此,可以考虑把所有字符串及出现次数保存在一个 HashMap 中,所占用的空间为 300w*(255+4)≈777M(其中,4表示整数占用的4个字节)。由此可见,1G 的内存空间完全够用。
虽然字符串总数比较多,但去重后不超过 300w,因此,可以考虑把所有字符串及出现次数保存在一个 HashMap 中,所占用的空间为 300w\*(255+4)≈777M(其中,4 表示整数占用的 4 个字节)。由此可见,1G 的内存空间完全够用。
**思路如下**
......
......@@ -14,9 +14,9 @@
若数据总数为**偶数**,当这两个堆建好之后,**中位数就是这两个堆顶元素的平均值**。当数据总数为**奇数**时,根据两个堆的大小,**中位数一定在数据多的堆的堆顶**
``` java
```java
class MedianFinder {
private PriorityQueue<Integer> maxHeap;
private PriorityQueue<Integer> minHeap;
......@@ -25,14 +25,14 @@ class MedianFinder {
maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
minHeap = new PriorityQueue<>(Integer::compareTo);
}
public void addNum(int num) {
if (maxHeap.isEmpty() || maxHeap.peek() > num) {
maxHeap.offer(num);
} else {
minHeap.offer(num);
}
int size1 = maxHeap.size();
int size2 = minHeap.size();
if (size1 - size2 > 1) {
......@@ -41,12 +41,12 @@ class MedianFinder {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
int size1 = maxHeap.size();
int size2 = minHeap.size();
return size1 == size2
return size1 == size2
? (maxHeap.peek() + minHeap.peek()) * 1.0 / 2
: (size1 > size2 ? maxHeap.peek() : minHeap.peek());
}
......
......@@ -7,6 +7,7 @@
### 解答思路
#### 方法一:分治法
与前面的题目方法类似,先将 2.5 亿个数划分到多个小文件,用 HashSet/HashMap 找出每个小文件中不重复的整数,再合并每个子结果,即为最终结果。
#### 方法二:位图法
......@@ -17,25 +18,25 @@
假设我们要对 `[0,7]` 中的 5 个元素 (6, 4, 2, 1, 5) 进行排序,可以采用位图法。0~7 范围总共有 8 个数,只需要 8bit,即 1 个字节。首先将每个位都置 0:
```
```
0 0 0 0 0 0 0 0
```
然后遍历 5 个元素,首先遇到 6,那么将下标为 6 的位的 0 置为 1;接着遇到 4,把下标为 4 的位 的 0 置为 1:
```
```
0 0 0 0 1 0 1 0
```
依次遍历,结束后,位数组是这样的:
```
```
0 1 1 0 1 1 1 0
```
每个为 1 的位,它的下标都表示了一个数:
```
```
for i in range(8):
if bits[i] == 1:
print(i)
......@@ -47,11 +48,11 @@ for i in range(8):
**那么对于这道题**,我们用 2 个 bit 来表示各个数字的状态:
* 00 表示这个数字没出现过;
* 01 表示这个数字出现过一次(即为题目所找的不重复整数);
* 10 表示这个数字出现了多次。
- 00 表示这个数字没出现过;
- 01 表示这个数字出现过一次(即为题目所找的不重复整数);
- 10 表示这个数字出现了多次。
那么这 2<sup>32</sup> 个整数,总共所需内存为 2<sup>32</sup>*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作:
那么这 2<sup>32</sup> 个整数,总共所需内存为 2<sup>32</sup>\*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作:
遍历 2.5 亿个整数,查看位图中对应的位,如果是 00,则变为 01,如果是 01 则变为 10,如果是 10 则保持不变。遍历结束后,查看位图,把对应位是 01 的整数输出即可。
......
......@@ -2,7 +2,7 @@
### 题目描述
有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20*500 个数中找出前 500 的数?
有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20\*500 个数中找出前 500 的数?
### 解答思路
......@@ -16,7 +16,7 @@
> 为了在堆中取出一个数据后,能知道它是从哪个数组中取出的,从而可以从这个数组中取下一个值,可以把数组的指针存放到堆中,对这个指针提供比较大小的方法。
``` java
```java
import lombok.Data;
import java.util.Arrays;
......
......@@ -10,7 +10,7 @@
**思路如下**
首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000` ,将结果为 i 的词存放到文件 a<sub>i</sub> 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。
首先遍历大文件,对遍历到的每个词 x,执行 `hash(x) % 5000` ,将结果为 i 的词存放到文件 a<sub>i</sub> 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。
接着统计每个小文件中出现频数最高的 100 个词。最简单的方式是使用 HashMap 来实现。其中 key 为词,value 为该词出现的频率。具体方法是:对于遍历到的词 x,如果在 map 中不存在,则执行 `map.put(x, 1)` ;若存在,则执行 `map.put(x, map.get(x)+1)` ,将该词频数加 1。
......
......@@ -20,5 +20,5 @@
### 方法总结
* 内存若够,直接读入进行排序;
* 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。
- 内存若够,直接读入进行排序;
- 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。
......@@ -4,40 +4,40 @@
## 系统拆分
* [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md)
- [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md)
## 分布式服务框架
* [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](/docs/distributed-system/dubbo-operating-principle.md)
* [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](/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)
- [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](/docs/distributed-system/dubbo-operating-principle.md)
- [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](/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)
* [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
- [Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md)
- [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
## 分布式事务
* [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md)
- [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md)
## 分布式会话
* [集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.md)
- [集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.md)
---
## 公众号
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
关注「**Doocs开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
![](./images/pdf.png)
......
## 面试题
一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
## 面试官心理分析
......@@ -13,9 +14,9 @@
这个分布式锁有 3 个重要的考量点:
* 互斥(只能有一个客户端获取锁)
* 不能死锁
* 容错(只要大部分 Redis 节点创建了这把锁就可以)
- 互斥(只能有一个客户端获取锁)
- 不能死锁
- 容错(只要大部分 Redis 节点创建了这把锁就可以)
#### Redis 最普通的分布式锁
......@@ -33,7 +34,7 @@ SET resource_name my_random_value PX 30000 NX
释放锁就是删除 key ,但是一般可以用 `lua` 脚本删除,判断 value 一样才删除:
``` lua
```lua
-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
......@@ -65,7 +66,7 @@ end
zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能**注册个监听器**监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。
``` java
```java
/**
* ZooKeeperSession
*/
......@@ -93,7 +94,7 @@ public class ZooKeeperSession {
/**
* 获取分布式锁
*
*
* @param productId
*/
public Boolean acquireDistributedLock(Long productId) {
......@@ -126,7 +127,7 @@ public class ZooKeeperSession {
/**
* 释放掉一个分布式锁
*
*
* @param productId
*/
public void releaseDistributedLock(Long productId) {
......@@ -177,7 +178,7 @@ public class ZooKeeperSession {
/**
* 获取单例
*
*
* @return
*/
public static ZooKeeperSession getInstance() {
......@@ -198,7 +199,7 @@ public class ZooKeeperSession {
如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 ZooKeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。
``` java
```java
public class ZooKeeperDistributedLock implements Watcher {
private ZooKeeper zk;
......@@ -257,17 +258,17 @@ public class ZooKeeperDistributedLock implements Watcher {
// locksRoot = locks
// /locks/10000000000,/locks/10000000001,/locks/10000000002
lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 看看刚创建的节点是不是最小的节点
// locks:10000000000,10000000001,10000000002
List<String> locks = zk.getChildren(locksRoot, false);
Collections.sort(locks);
if(lockNode.equals(locksRoot+"/"+ locks.get(0))){
//如果是最小的节点,则表示取得锁
return true;
}
//如果不是最小的节点,找到比自己小1的节点
int previousLockIndex = -1;
for(int i = 0; i < locks.size(); i++) {
......@@ -276,7 +277,7 @@ public class ZooKeeperDistributedLock implements Watcher {
break;
}
}
this.waitNode = locks.get(previousLockIndex);
} catch (KeeperException e) {
throw new LockException(e);
......@@ -327,8 +328,8 @@ public class ZooKeeperDistributedLock implements Watcher {
### redis 分布式锁和 zk 分布式锁的对比
* redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。
* zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。
- redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。
- zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。
另外一点就是,如果是 Redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。
......
## 面试题
集群部署时的分布式 Session 如何实现?
## 面试官心理分析
......@@ -25,11 +26,11 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存
### Tomcat + Redis
这个其实还挺方便的,就是使用 Session 的代码,跟以前一样,还是基于 Tomcat 原生的 Session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 Tomcat 都将 Session 数据存储到 Redis 即可。
这个其实还挺方便的,就是使用 Session 的代码,跟以前一样,还是基于 Tomcat 原生的 Session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 Tomcat 都将 Session 数据存储到 Redis 即可。
在 Tomcat 的配置文件中配置:
``` xml
```xml
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
......@@ -37,7 +38,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存
port="{redis.port}"
database="{redis.dbnum}"
maxInactiveInterval="60"/>
```
```
然后指定 Redis 的 host 和 port 就 ok 了。
......@@ -61,7 +62,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存
在 pom.xml 中配置:
``` xml
```xml
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
......@@ -76,7 +77,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存
在 Spring 配置文件中配置:
``` xml
```xml
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600"/>
......@@ -100,7 +101,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存
在 web.xml 中配置:
``` xml
```xml
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
......@@ -113,7 +114,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存
示例代码:
``` java
```java
@RestController
@RequestMapping("/test")
public class TestController {
......
## 面试题
分布式服务接口的幂等性如何设计(比如不能重复扣款)?
## 面试官心理分析
......@@ -21,9 +22,9 @@
其实保证幂等性主要是三点:
* 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。
* 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。
* 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
- 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。
- 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。
- 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
实际运作过程中,你要结合自己的业务来,比如说利用 Redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。
......
## 分布式系统面试连环炮
有一些同学,之前呢主要是做传统行业,或者外包项目,一直是在那种小的公司,技术一直都搞的比较简单。他们有共同的一个问题,就是都没怎么搞过分布式系统,现在互联网公司,一般都是做分布式的系统,大家都不是做底层的分布式系统、分布式存储系统 Hadoop HDFS、分布式计算系统 Hadoop MapReduce / Spark、分布式流式计算系统 Storm。
分布式业务系统,就是把原来用 Java 开发的一个大块系统,给拆分成**多个子系统**,多个子系统之间互相调用,形成一个大系统的整体。假设原来你做了一个 OA 系统,里面包含了权限模块、员工模块、请假模块、财务模块,一个工程,里面包含了一堆模块,模块与模块之间会互相去调用,1 台机器部署。现在如果你把这个系统给拆开,权限系统、员工系统、请假系统、财务系统 4 个系统,4 个工程,分别在 4 台机器上部署。一个请求过来,完成这个请求,这个员工系统,调用权限系统,调用请假系统,调用财务系统,4 个系统分别完成了一部分的事情,最后 4 个系统都干完了以后,才认为是这个请求已经完成了。
......@@ -11,27 +12,27 @@
### 为什么要进行系统拆分?
* 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?Dubbo 和 thrift 有什么区别呢?
- 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?Dubbo 和 thrift 有什么区别呢?
### 分布式服务框架
* 说一下的 Dubbo 的工作原理?注册中心挂了可以继续通信吗?
* Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
* Dubbo 负载均衡策略和高可用策略都有哪些?动态代理策略呢?
* Dubbo 的 SPI 思想是什么?
* 如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?
* 分布式服务接口的幂等性如何设计(比如不能重复扣款)?
* 分布式服务接口请求的顺序性如何保证?
* 如何自己设计一个类似 Dubbo 的 RPC 框架?
- 说一下的 Dubbo 的工作原理?注册中心挂了可以继续通信吗?
- Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
- Dubbo 负载均衡策略和高可用策略都有哪些?动态代理策略呢?
- Dubbo 的 SPI 思想是什么?
- 如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?
- 分布式服务接口的幂等性如何设计(比如不能重复扣款)?
- 分布式服务接口请求的顺序性如何保证?
- 如何自己设计一个类似 Dubbo 的 RPC 框架?
### 分布式锁
* 使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
- 使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
### 分布式事务
* 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?
- 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?
### 分布式会话
* 集群部署时的分布式 Session 如何实现?
- 集群部署时的分布式 Session 如何实现?
## 面试题
分布式服务接口请求的顺序性如何保证?
## 面试官心理分析
......
......@@ -37,9 +37,9 @@
TCC 的全称是: `Try``Confirm``Cancel`
* Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**
* Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**
* Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**
- Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**
- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
这种方案说实话几乎很少人使用,我们用的也比较少,但是也有使用的场景。因为这个**事务回滚**实际上是**严重依赖于你自己写代码来回滚和补偿**了,会造成补偿代码巨大,非常之恶心。
......
## 面试题
说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程?
## 面试官心理分析
......@@ -9,29 +10,29 @@ MQ、ES、Redis、Dubbo,上来先问你一些**思考性的问题**、**原理
当然去年开始 spring cloud 非常火,现在大量的公司开始转向 spring cloud 了,spring cloud 人家毕竟是微服务架构的全家桶式的这么一个东西。但是因为很多公司还在用 dubbo,所以 dubbo 肯定会是目前面试的重点,何况人家 dubbo 现在重启开源社区维护了,捐献给了 apache,未来应该也还是有一定市场和地位的。
既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc 分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。
既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc 分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。
## 面试题剖析
### dubbo 工作原理
* 第一层:service 层,接口层,给服务提供者和消费者来实现的
* 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的
* 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信
* 第四层:registry 层,服务注册层,负责服务的注册与发现
* 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
* 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
* 第七层:protocal 层,远程调用层,封装 rpc 调用
* 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步
* 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口
* 第十层:serialize 层,数据序列化层
- 第一层:service 层,接口层,给服务提供者和消费者来实现的
- 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的
- 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信
- 第四层:registry 层,服务注册层,负责服务的注册与发现
- 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
- 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
- 第七层:protocal 层,远程调用层,封装 rpc 调用
- 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步
- 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口
- 第十层:serialize 层,数据序列化层
### 工作流程
* 第一步:provider 向注册中心去注册
* 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
* 第三步:consumer 调用 provider
* 第四步:consumer 和 provider 都异步通知监控中心
- 第一步:provider 向注册中心去注册
- 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
- 第三步:consumer 调用 provider
- 第四步:consumer 和 provider 都异步通知监控中心
![dubbo-operating-principle](./images/dubbo-operating-principle.png)
......
## 面试题
如何自己设计一个类似 Dubbo 的 RPC 框架?
## 面试官心理分析
说实话,就这问题,其实就跟问你如何自己设计一个 MQ 一样的道理,就考两个:
* 你有没有对某个 rpc 框架原理有非常深入的理解。
* 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。
- 你有没有对某个 rpc 框架原理有非常深入的理解。
- 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。
## 面试题剖析
......@@ -16,11 +17,11 @@
举个栗子,我给大家说个最简单的回答思路:
* 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。
* 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。
* 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
* 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。
* 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。
* 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。
- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。
- 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。
- 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
- 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。
- 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。
- 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。
这就是一个最最基本的 rpc 框架的思路,先不说你有多牛逼的技术功底,哪怕这个最简单的思路你先给出来行不行?
## 面试题
dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
## 面试官心理分析
......@@ -15,7 +16,7 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian
### dubbo 支持不同的通信协议
* dubbo 协议
- dubbo 协议
**默认**就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高。
......@@ -29,19 +30,19 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian
![dubbo-not-keep-connection](./images/dubbo-not-keep-connection.png)
* rmi 协议
- rmi 协议
走 Java 二进制序列化,多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用。
* hessian 协议
- hessian 协议
走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用。
* http 协议
- http 协议
走表单序列化。
* webservice
- webservice
走 SOAP 文本序列化。
......@@ -53,24 +54,24 @@ dubbo 支持 hession、Java 二进制序列化、json、SOAP 文本序列化多
Hessian 的对象序列化机制有 8 种原始类型:
* 原始二进制数据
* boolean
* 64-bit date(64 位毫秒值的日期)
* 64-bit double
* 32-bit int
* 64-bit long
* null
* UTF-8 编码的 string
- 原始二进制数据
- boolean
- 64-bit date(64 位毫秒值的日期)
- 64-bit double
- 32-bit int
- 64-bit long
- null
- UTF-8 编码的 string
另外还包括 3 种递归类型:
* list for lists and arrays
* map for maps and dictionaries
* object for objects
- list for lists and arrays
- map for maps and dictionaries
- object for objects
还有一种特殊的类型:
* ref:用来表示对共享对象的引用。
- ref:用来表示对共享对象的引用。
### 为什么 PB 的效率是最高的?
......
## 面试题
如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
## 面试官心理分析
......@@ -27,17 +28,17 @@
需要自动统计**各个接口和服务之间的调用次数以及访问延时**,而且要分成两个级别。
* 一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少;
* 第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的 TP50/TP90/TP99,分别是多少。
- 一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少;
- 第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的 TP50/TP90/TP99,分别是多少。
这些东西都搞定了之后,后面才可以来看当前系统的压力主要在哪里,如何来扩容和优化啊。
#### 3. 其它
* 服务分层(避免循环依赖)
* 调用链路失败监控和报警
* 服务鉴权
* 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%)
- 服务分层(避免循环依赖)
- 调用链路失败监控和报警
- 服务鉴权
- 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%)
### 服务降级
......@@ -45,7 +46,7 @@
举个栗子,我们有接口 `HelloService``HelloServiceImpl` 有该接口的具体实现。
``` java
```java
public interface HelloService {
void sayHello();
}
......@@ -57,7 +58,7 @@ public class HelloServiceImpl implements HelloService {
}
```
``` xml
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
......@@ -92,7 +93,7 @@ public class HelloServiceImpl implements HelloService {
mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+ `Mock` ” 后缀。然后在 Mock 类里实现自己的降级逻辑。
``` java
```java
public class HelloServiceMock implements HelloService {
public void sayHello() {
// 降级逻辑
......@@ -104,7 +105,7 @@ public class HelloServiceMock implements HelloService {
所谓失败重试,就是 consumer 调用 provider 要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。配置如下:
``` xml
```xml
<dubbo:reference id="xxxx" interface="xx" check="true" async="false" retries="3" timeout="2000"/>
```
......@@ -114,5 +115,5 @@ public class HelloServiceMock implements HelloService {
可以结合你们公司具体的场景来说说你是怎么设置这些参数的:
* `timeout` :一般设置为 `200ms` ,我们认为不能超过 `200ms` 还没返回。
* `retries` :设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。
- `timeout` :一般设置为 `200ms` ,我们认为不能超过 `200ms` 还没返回。
- `retries` :设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。
## 面试题
dubbo 的 spi 思想是什么?
## 面试官心理分析
......@@ -10,11 +11,12 @@ dubbo 的 spi 思想是什么?
## 面试题剖析
### spi 是啥?
spi,简单来说,就是 `service provider interface` ,说白了是什么意思呢,比如你有个接口,现在这个接口有 3 个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要 spi 了,需要**根据指定的配置**或者是**默认的配置**,去**找到对应的实现类**加载进来,然后用这个实现类的实例对象。
举个栗子。
你有一个接口 A。A1/A2/A3 分别是接口A的不同实现。你通过配置 `接口 A = 实现 A2` ,那么在系统实际运行的时候,会加载你的配置,用实现 A2 实例化一个对象来提供服务。
你有一个接口 A。A1/A2/A3 分别是接口 A 的不同实现。你通过配置 `接口 A = 实现 A2` ,那么在系统实际运行的时候,会加载你的配置,用实现 A2 实例化一个对象来提供服务。
spi 机制一般用在哪儿?**插件扩展的场景**,比如说你开发了一个给别人使用的开源框架,如果你想让别人自己写个插件,插到你的开源框架里面,从而扩展某个功能,这个时候 spi 思想就用上了。
......@@ -32,7 +34,7 @@ Java 定义了一套 jdbc 的接口,但是 Java 并没有提供 jdbc 的实现
dubbo 也用了 spi 思想,不过没有用 jdk 的 spi 机制,是自己实现的一套 spi 机制。
``` java
```java
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
```
......@@ -42,26 +44,26 @@ Protocol 接口,在系统运行的时候,,dubbo 会判断一下应该选
上面那行代码就是 dubbo 里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现好了,没问题。
``` java
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
```java
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
```
在 dubbo 自己的 jar 里,在 `/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol` 文件中:
``` xml
```xml
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
......@@ -83,7 +85,7 @@ hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
然后自己搞一个 `dubbo provider` 工程,在这个工程里面依赖你自己搞的那个 jar,然后在 spring 配置文件里给个配置:
``` xml
```xml
<dubbo:protocol name=”my” port=”20000” />
```
......@@ -93,4 +95,4 @@ provider 启动的时候,就会加载到我们 jar 包里的 `my=com.bingo.MyP
dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类`
然后对于对应的组件,类似 `<dubbo:protocol>` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。
然后对于对应的组件,类似 `<dubbo:protocol>` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。
## 面试题
为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗?
## 面试官心理分析
......@@ -20,6 +21,7 @@
## 面试题剖析
### 为什么要将系统进行拆分?
网上查查,答案极度零散和复杂,很琐碎,原因一大坨。但是我这里给大家直观的感受:
要是**不拆分**,一个大系统几十万行代码,20 个人维护一份代码,简直是悲剧啊。代码经常改着改着就冲突了,各种代码冲突和合并要处理,非常耗费时间;经常我改动了我的代码,你调用了我的,导致你的代码也得重新测试,麻烦的要死;然后每次发布都是几十万行代码的系统一起发布,大家得一起提心吊胆准备上线,几十万行代码的上线,可能每次上线都要做很多的检查,很多异常问题的处理,简直是又麻烦又痛苦;而且如果我现在打算把技术升级到最新的 spring 版本,还不行,因为这可能导致你的代码报错,我不敢随意乱改技术。
......@@ -42,7 +44,7 @@ A 就检查了自己负责的 1 万行代码对应的功能,确保 ok 就闪
系统拆分为分布式系统,拆成多个服务,拆成微服务的架构,是需要拆很多轮的。并不是说上来一个架构师一次就给拆好了,而以后都不用拆。
第一轮;团队继续扩大,拆好的某个服务,刚开始是 1 个人维护 1 万行代码,后来业务系统越来越复杂,这个服务是 10 万行代码,5 个人;第二轮,1个服务 -> 5个服务,每个服务 2 万行代码,每人负责一个服务。
第一轮;团队继续扩大,拆好的某个服务,刚开始是 1 个人维护 1 万行代码,后来业务系统越来越复杂,这个服务是 10 万行代码,5 个人;第二轮,1 个服务 -> 5 个服务,每个服务 2 万行代码,每人负责一个服务。
如果是多人维护一个服务,最理想的情况下,几十个人,1 个人负责 1 个或 2~3 个服务;某个服务工作量变大了,代码量越来越多,某个同学,负责一个服务,代码量变成了 10 万行了,他自己不堪重负,他现在一个人拆开,5 个服务,1 个人顶着,负责 5 个人,接着招人,2 个人,给那个同学带着,3 个人负责 5 个服务,其中 2 个人每个人负责 2 个服务,1 个人负责 1 个服务。
......
## 面试题
zookeeper 都有哪些使用场景?
## 面试官心理分析
现在聊的 topic 是分布式系统,面试官跟你聊完了 dubbo 相关的一些问题之后,已经确认你对分布式服务框架/RPC框架基本都有一些认知了。那么他可能开始要跟你聊分布式相关的其它问题了。
现在聊的 topic 是分布式系统,面试官跟你聊完了 dubbo 相关的一些问题之后,已经确认你对分布式服务框架/RPC 框架基本都有一些认知了。那么他可能开始要跟你聊分布式相关的其它问题了。
分布式锁这个东西,很常用的,你做 Java 系统开发,分布式系统,可能会有一些场景会用到。最常用的分布式锁就是基于 zookeeper 来实现的。
......@@ -13,10 +14,10 @@ zookeeper 都有哪些使用场景?
大致来说,zookeeper 的使用场景如下,我就举几个简单的,大家能说几个就好了:
* 分布式协调
* 分布式锁
* 元数据/配置信息管理
* HA高可用性
- 分布式协调
- 分布式锁
- 元数据/配置信息管理
- HA 高可用性
### 分布式协调
......@@ -36,7 +37,7 @@ zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、stor
![zookeeper-meta-data-manage](./images/zookeeper-meta-data-manage.png)
### HA高可用性
### HA 高可用性
这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。
......
......@@ -2,21 +2,21 @@
## Offer 与进阶
* [我的 Offer 在哪里?](https://doocs.gitee.io/advanced-java/#/docs/extra-page/offer)
* [让我们同步进阶!](https://doocs.gitee.io/advanced-java/#/docs/extra-page/advanced)
- [我的 Offer 在哪里?](https://doocs.gitee.io/advanced-java/#/docs/extra-page/offer)
- [让我们同步进阶!](https://doocs.gitee.io/advanced-java/#/docs/extra-page/advanced)
## 项目 Page 页
* [GitHub Page](https://doocs.github.io/advanced-java/#/)
* [Gitee Page](https://doocs.gitee.io/advanced-java/#/)
- [GitHub Page](https://doocs.github.io/advanced-java/#/)
- [Gitee Page](https://doocs.gitee.io/advanced-java/#/)
---
## 公众号
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
关注「**Doocs开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
![](./images/pdf.png)
......
<p align="center"><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=1334849028&auto=1&height=66"></iframe></p>
<p align="center">本单曲受版权保护,可<a href="https://music.163.com/#/mv?id=10859500">点击观看 MV</a></p>
> 受伤的得到疗愈,挣扎的得到出口<br>
*Let those who hurt heal, let those who struggle find hope*.
> _Let those who hurt heal, let those who struggle find hope_.
```
告别的时刻已到了
......@@ -69,4 +68,4 @@
跨越了浩劫
曙光渐亮 oh~
```
\ No newline at end of file
```
......@@ -119,7 +119,7 @@ HR
: / |"- '
' | /
` |
```
[![get-up-and-study](./images/get-up-and-study.png)](https://doocs.github.io/advanced-java)
\ No newline at end of file
[![get-up-and-study](./images/get-up-and-study.png)](https://doocs.github.io/advanced-java)
......@@ -16,34 +16,37 @@
### 博客
| # | 文章 | 抄袭者 |
|---|---|---|
| 1 | [如何保证缓存与数据库的双写一致性](https://blog.51cto.com/14230003/2363051) | Java_老男孩-51CTO |
| 2 | [了解什么是 redis 的雪崩和穿透?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?](https://blog.csdn.net/chang384915878/article/details/86756536) | 你是我的海啸-CSDN |
| 3 | [深入 Hystrix 线程池隔离与接口限流](https://blog.csdn.net/u014513171/article/details/93461724) | 塔寨村村主任-CSDN |
| 4 | [MySQL 面试题](https://jsbintask.cn/2019/02/17/interview/interview-high-concurrency-design/) | jsbintask |
| 5 | [消息队列面试题](https://blog.51cto.com/13904503/2351522) | Java邵先生 |
| 6 | [高并发架构消息队列面试题解析](https://www.cnblogs.com/yuxiang1/p/10542569.html) | 手留余香-博客园 |
| 7 | [消息中间件面试题:消息中间件的高可用](https://www.jianshu.com/p/92862edc7c51) | jsbintask-简书 |
| 8 | [深入 Hystrix 执行时内部原理](https://www.jianshu.com/p/1a14401e219f) | kevin0016-简书 |
| # | 文章 | 抄袭者 |
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| 1 | [如何保证缓存与数据库的双写一致性](https://blog.51cto.com/14230003/2363051) | Java\_老男孩-51CTO |
| 2 | [了解什么是 redis 的雪崩和穿透?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?](https://blog.csdn.net/chang384915878/article/details/86756536) | 你是我的海啸-CSDN |
| 3 | [深入 Hystrix 线程池隔离与接口限流](https://blog.csdn.net/u014513171/article/details/93461724) | 塔寨村村主任-CSDN |
| 4 | [MySQL 面试题](https://jsbintask.cn/2019/02/17/interview/interview-high-concurrency-design/) | jsbintask |
| 5 | [消息队列面试题](https://blog.51cto.com/13904503/2351522) | Java 邵先生 |
| 6 | [高并发架构消息队列面试题解析](https://www.cnblogs.com/yuxiang1/p/10542569.html) | 手留余香-博客园 |
| 7 | [消息中间件面试题:消息中间件的高可用](https://www.jianshu.com/p/92862edc7c51) | jsbintask-简书 |
| 8 | [深入 Hystrix 执行时内部原理](https://www.jianshu.com/p/1a14401e219f) | kevin0016-简书 |
### 公众号
| # | 文章 | 抄袭者 |
|---|---|---|
| # | 文章 | 抄袭者 |
| --- | ---- | ------ |
### 头条号
| # | 文章 | 抄袭者 |
|---|---|---|
| # | 文章 | 抄袭者 |
| --- | ---- | ------ |
### 掘金
| # | 文章 | 抄袭者 |
|---|---|---|
| # | 文章 | 抄袭者 |
| --- | ---- | ------ |
### 知乎
| # | 文章 | 抄袭者 | 备注 |
|---|---|---|---|
| 1 | [Java消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java高级架构解析 | 严重抄袭 |
| # | 文章 | 抄袭者 | 备注 |
| --- | ---------------------------------------------------------------------- | ----------------- | -------- |
| 1 | [Java 消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java 高级架构解析 | 严重抄袭 |
# Doocs 开源社区的公众号来了
GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专注于挖掘 IT 技术知识,助力开发者成长。
GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs 开源社区**”,专注于挖掘 IT 技术知识,助力开发者成长。
<div style="text-align:center; ">
<img src="./images/qrcode_for_doocs.jpg" width="200px; "/>
......@@ -11,12 +12,12 @@ GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专
微信公众号的**原创保护功能**做得比较好,内容阅读也比较方便。
后续我的所有原创文章,将第一时间通过微信公众号“Doocs开源社区”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。
后续我的所有原创文章,将第一时间通过微信公众号“Doocs 开源社区”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。
## 公众号的定位是怎样的?
* **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。
* **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。
- **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。
- **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。
<div style="text-align:center; ">
<img src="./images/article-demo.png" width="300px; "/>
......@@ -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、开发相关,可以自由分享一些开发相关知识,但不提倡整天水群,建议还是多花点时间提升自己。
......
# 高可用架构
- [Hystrix 介绍](./hystrix-introduction.md)
- [电商网站详情页系统架构](./e-commerce-website-detail-page-architecture.md)
- [Hystrix 线程池技术实现资源隔离](./hystrix-thread-pool-isolation.md)
......@@ -12,25 +13,30 @@
- [基于 timeout 机制为服务接口调用超时提供安全保护](./hystrix-timeout.md)
## 高可用系统
- 如何设计一个高可用系统?
## 限流
- 如何限流?在工作中是怎么做的?说一下具体的实现?
## 熔断
- 如何进行熔断?
- 熔断框架都有哪些?具体实现原理知道吗?
- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](/docs/high-availability/sentinel-vs-hystrix.md)
## 降级
- 如何进行降级?
---
## 公众号
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
关注「**Doocs开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
![](./images/pdf.png)
......@@ -49,4 +55,4 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
</a><br>
</td>
</tr>
</table>
\ No newline at end of file
</table>
......@@ -77,6 +77,7 @@ Hystrix 并不是只要有一条请求经过就去统计,而是将整个滑动
## 实例 Demo
### HystrixCommand 配置参数
在 GetProductInfoCommand 中配置 Setter 断路器相关参数。
- 滑动窗口中,最少 20 个请求,才可能触发断路。
......@@ -132,6 +133,7 @@ public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
```
### 断路测试类
我们在测试类中,前 30 次请求,传入 productId=-1,然后休眠 3s,之后 70 次请求,传入 productId=1。
```java
......@@ -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
2. [Hystrix Metrics](https://github.com/Netflix/Hystrix/wiki/Configuration#metrics)
## Hystrix 隔离策略细粒度控制
Hystrix 实现资源隔离,有两种策略:
- 线程池隔离
......@@ -7,6 +8,7 @@ Hystrix 实现资源隔离,有两种策略:
对资源隔离这一块东西,其实可以做一定细粒度的一些控制。
### execution.isolation.strategy
指定了 HystrixCommand.run() 的资源隔离策略:`THREAD` or `SEMAPHORE`,一种基于线程池,一种基于信号量。
```java
......@@ -28,12 +30,14 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat
而使用信号量的场景,通常是针对超大并发量的场景下,每个服务实例每秒都几百的 `QPS`,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护。一般用信号量常见于那种基于纯内存的一些业务逻辑服务,而不涉及到任何网络访问请求。
### 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);
......@@ -44,9 +48,11 @@ public CommandHelloWorld(String name) {
command group 是一个非常重要的概念,默认情况下,就是通过 command group 来定义一个线程池的,而且还会通过 command group 来聚合一些监控和报警信息。同一个 command group 中的请求,都会进入同一个线程池中。
### 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"))
......@@ -59,6 +65,7 @@ public CommandHelloWorld(String name) {
```
### 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
设置线程池的大小,默认是 10。一般来说,用这个默认的 10 个线程大小就够了。
```java
HystrixThreadPoolProperties.Setter().withCoreSize(int value);
```
### queueSizeRejectionThreshold
如果说线程池中的 10 个线程都在工作中,没有空闲的线程来做其它的事情,此时再有请求过来,会先进入队列积压。如果说队列积压满了,再有请求过来,就直接 reject,拒绝请求,执行 fallback 降级的逻辑,快速返回。
![hystrix-thread-pool-queue](./images/hystrix-thread-pool-queue.png)
......@@ -95,6 +105,7 @@ HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value);
```
### 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
```
## 基于本地缓存的 fallback 降级机制
Hystrix 出现以下四种情况,都会去调用 fallback 降级机制:
- 断路器处于打开的状态。
......@@ -9,14 +10,13 @@ Hystrix 出现以下四种情况,都会去调用 fallback 降级机制:
### 两种最经典的降级机制
- 纯内存数据<br>
在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。
在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。
- 默认值<br>
fallback 降级逻辑中,也可以直接返回一个默认值。
fallback 降级逻辑中,也可以直接返回一个默认值。
`HystrixCommand`,降级逻辑的书写,是通过实现 getFallback() 接口;而在 `HystrixObservableCommand` 中,则是实现 resumeWithFallback() 方法。
现在,我们用一个简单的栗子,来演示 fallback 降级是怎么做的。
比如,有这么个**场景**。我们现在有个包含 brandId 的商品数据,假设正常的逻辑是这样:拿到一个商品数据,根据 brandId 去调用品牌服务的接口,获取品牌的最新名称 brandName。
......@@ -24,6 +24,7 @@ fallback 降级逻辑中,也可以直接返回一个默认值。
假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。
### 步骤一:本地缓存获取数据
本地获取品牌名称的代码大致如下。
```java
......@@ -52,6 +53,7 @@ public class BrandCache {
```
### 步骤二:实现 GetBrandNameCommand
在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。
这里,我们直接**模拟接口调用报错**,给它抛出个异常。
......@@ -96,6 +98,7 @@ public class GetBrandNameCommand extends HystrixCommand<String> {
`FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。
### 步骤三:CacheController 调用接口
在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。
```java
......@@ -122,4 +125,4 @@ public class CacheController {
}
```
关于降级逻辑的演示,基本上就结束了。
\ No newline at end of file
关于降级逻辑的演示,基本上就结束了。
## 用 Hystrix 构建高可用服务架构
参考 [Hystrix Home](https://github.com/Netflix/Hystrix/wiki#what)
### Hystrix 是什么?
在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是**依赖服务**,有的时候某些依赖服务出现故障也是很正常的。
Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些**调用延迟**或者**依赖故障****容错机制**
......@@ -11,6 +13,7 @@ Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖
**总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。**
### Hystrix 的历史
Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外的优酷或者爱奇艺之类的视频网站)的 API 团队从 2011 年开始做一些提升系统可用性和稳定性的工作,Hystrix 就是从那时候开始发展出来的。
在 2012 年的时候,Hystrix 就变得比较成熟和稳定了,Netflix 中,除了 API 团队以外,很多其他的团队都开始使用 Hystrix。
......@@ -20,13 +23,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 优雅降级的支持。
- 支持近实时的监控、报警以及运维操作。
举个栗子。
有这样一个分布式系统,服务 A 依赖于服务 B,服务 B 依赖于服务 C/D/E。在这样一个成熟的系统内,比如说最多可能只有 100 个线程资源。正常情况下,40 个线程并发调用服务 C,各 30 个线程并发调用 D/E。
......@@ -37,12 +40,12 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外
Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线程调用服务 C。当此 40 个线程被 hang 住时,其它 60 个线程依然能正常调用工作。从而确保整个系统不会被拖垮。
### Hystrix 更加细节的设计原则
- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。
- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。
- 提供 fallback 降级机制来应对故障。
- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(断路技术)来限制任何一个依赖服务的故障的影响。
- 通过近实时的统计/监控/报警功能,来提高故障发现的速度。
- 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。
- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。
\ No newline at end of file
- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。
## 深入 Hystrix 执行时内部原理
前面我们了解了 Hystrix 最基本的支持高可用的技术:**资源隔离** + **限流**
- 创建 command;
......@@ -14,6 +15,7 @@
![hystrix-process](./images/new-hystrix-process.jpg)
### 步骤一:创建 command
一个 HystrixCommand 或 HystrixObservableCommand 对象,代表了对某个依赖服务发起的一次请求或者调用。创建的时候,可以在构造函数中传入任何需要的参数。
- HystrixCommand 主要用于仅仅会返回一个结果的调用。
......@@ -28,6 +30,7 @@ HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand
```
### 步骤二:调用 command 执行方法
执行 command,就可以发起一次对依赖服务的调用。
要执行 command,可以在 4 个方法中选择其中的一个:execute()、queue()、observe()、toObservable()。
......@@ -47,6 +50,7 @@ Observable<K> toOValue = hystrixObservableCommand.toObservable();
```
execute() 实际上会调用 queue().get() 方法,可以看一下 Hystrix 源码。
```java
public R execute() {
try {
......@@ -58,6 +62,7 @@ public R execute() {
```
而在 queue() 方法中,会调用 toObservable().toBlocking().toFuture()。
```java
final Future<R> delegate = toObservable().toBlocking().toFuture();
```
......@@ -65,17 +70,21 @@ final Future<R> 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
调用 HystrixObservableCommand 对象的 construct() 方法,或者 HystrixCommand 的 run() 方法来实际执行这个 command。
- HystrixCommand.run() 返回单条结果,或者抛出异常。
......@@ -104,7 +113,7 @@ observable.subscribe(new Observer<ProductInfo>() {
/**
* 获取完一条数据,就回调一次这个方法
*
*
* @param productInfo 商品信息
*/
@Override
......@@ -121,15 +130,17 @@ observable.subscribe(new Observer<ProductInfo>() {
如果没有 timeout,也正常执行的话,那么调用线程就会拿到一些调用依赖服务获取到的结果,然后 Hystrix 也会做一些 logging 记录和 metric 度量统计。
### 步骤七:断路健康检查
Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout 等事件发送给 circuit breaker 断路器。断路器就会对这些事件的次数进行统计,根据异常事件发生的比例来决定是否要进行断路(熔断)。如果打开了断路器,那么在接下来一段时间内,会直接断路,返回降级结果。
如果在之后,断路器尝试执行 command,调用没有出错,返回了正常结果,那么 Hystrix 就会把断路器关闭。
### 步骤八:调用 fallback 降级机制
在以下几种情况中,Hystrix 会调用 fallback 降级机制。
- 断路器处于打开状态;
- 线程池/队列/semaphore满了;
- 线程池/队列/semaphore 满了;
- command 执行超时;
- run() 或者 construct() 抛出异常。
......@@ -150,7 +161,8 @@ Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout
- 对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
### 不同的执行方式
- execute(),获取一个 Future.get(),然后拿到单个结果。
- queue(),返回一个 Future。
- observe(),立即订阅 Observable,然后启动 8 大执行步骤,返回一个拷贝的 Observable,订阅时立即回调给你结果。
- toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。
\ No newline at end of file
- toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。
## 基于 request cache 请求缓存技术优化批量商品数据查询接口
Hystrix command 执行时 8 大步骤第三步,就是检查 Request cache 是否有缓存。
首先,有一个概念,叫做 Request Context 请求上下文,一般来说,在一个 web 应用中,如果我们用到了 Hystrix,我们会在一个 filter 里面,对每一个请求都施加一个请求上下文。就是说,每一次请求,就是一次请求上下文。然后在这次请求上下文中,我们会去执行 N 多代码,调用 N 多依赖服务,有的依赖服务可能还会调用好几次。
......@@ -20,6 +21,7 @@ HystrixCommand 和 HystrixObservableCommand 都可以指定一个缓存 key,
我们对批量查询商品数据的接口,可以用 request cache 做一个优化,就是说一次请求,就是一次 request context,对相同的商品查询只执行一次,其余重复的都走 request cache。
### 实现 Hystrix 请求上下文过滤器并注册
定义 HystrixRequestContextFilter 类,实现 Filter 接口。
```java
......@@ -72,6 +74,7 @@ public class EshopApplication {
```
### command 重写 getCacheKey() 方法
在 GetProductInfoCommand 中,重写 getCacheKey() 方法,这样的话,每一次请求的结果,都会放在 Hystrix 请求上下文中。下一次同一个 productId 的数据请求,直接取缓存,无须再调用 run() 方法。
```java
......@@ -120,6 +123,7 @@ public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
这里写了一个 flushCache() 方法,用于我们开发手动删除缓存。
### controller 调用 command 查询商品信息
在一次 web 请求上下文中,传入商品 id 列表,查询多条商品数据信息。对于每个 productId,都创建一个 command。
如果 id 列表没有去重,那么重复的 id,第二次查询的时候就会直接走缓存。
......@@ -150,6 +154,7 @@ public class CacheController {
```
### 发起请求
调用接口,查询多个商品的信息。
```
......@@ -173,6 +178,7 @@ 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<Boolean> {
}
```
这样,以后查询该商品的请求,第一次就会走接口调用去查询最新的商品信息。
\ No newline at end of file
这样,以后查询该商品的请求,第一次就会走接口调用去查询最新的商品信息。
## 基于 Hystrix 信号量机制实现资源隔离
Hystrix 里面核心的一项功能,其实就是所谓的**资源隔离**,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话,就可能导致服务崩溃,甚至说这种故障会不断蔓延。
Hystrix 实现资源隔离,主要有两种技术:
......@@ -11,11 +12,13 @@ Hystrix 实现资源隔离,主要有两种技术:
前面已经说过线程池技术了,这一小节就来说说信号量机制实现资源隔离,以及这两种技术的区别与具体应用场景。
### 信号量机制
信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。
![hystrix-semphore](./images/hystrix-semphore.png)
### 线程池与信号量区别
线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。
线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。
......@@ -23,10 +26,12 @@ Hystrix 实现资源隔离,主要有两种技术:
![hystrix-semphore-thread-pool](./images/hystrix-semphore-thread-pool.png)
**适用场景**
- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。
- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。
### 信号量简单 Demo
业务背景里,比较适合信号量的是什么场景呢?
比如说,我们一般来说,缓存服务,可能会将一些量特别少、访问又特别频繁的数据,放在自己的纯内存中。
......@@ -36,6 +41,7 @@ Hystrix 实现资源隔离,主要有两种技术:
优点在于,不用自己管理线程池啦,不用 care timeout 超时啦,也不需要进行线程的上下文切换啦。信号量做隔离的话,性能相对来说会高一些。
假如这是本地缓存,我们可以通过 cityId,拿到 cityName。
```java
public class LocationCache {
private static Map<Long, String> cityMap = new HashMap<>();
......@@ -57,6 +63,7 @@ public class LocationCache {
```
写一个 GetCityNameCommand,策略设置为**信号量**。run() 方法中获取本地缓存。我们目的就是对获取本地缓存的代码进行资源隔离。
```java
public class GetCityNameCommand extends HystrixCommand<String> {
......@@ -80,6 +87,7 @@ public class GetCityNameCommand extends HystrixCommand<String> {
```
在接口层,通过创建 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
```
## 深入 Hystrix 线程池隔离与接口限流
前面讲了 Hystrix 的 request cache 请求缓存、fallback 优雅降级、circuit breaker 断路器快速熔断,这一讲,我们来详细说说 Hystrix 的线程池隔离与接口限流。
![hystrix-process](./images/hystrix-process.png)
......@@ -8,6 +9,7 @@ Hystrix 通过判断线程池或者信号量是否已满,超出容量的请求
限流是限制对后端的服务的访问量,比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制,其实是为了避免过大的流量直接打死后端的服务。
### 线程池隔离技术的设计
Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃。
**舱壁隔离**,是说将船体内部空间区隔划分成若干个隔舱,一旦某几个隔舱发生破损进水,水流不会在其间相互流动,如此一来船舶在受损时,依然能具有足够的浮力和稳定性,进而减低立即沉船的危险。
......@@ -17,6 +19,7 @@ Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进
Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。
### Hystrix 应用线程池机制的场景
- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。
- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。
- client 调用库随时会变更。
......@@ -32,6 +35,7 @@ Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果
简单来说,就是你必须默认 client 调用库很不靠谱,而且随时可能发生各种变化,所以就要用强制隔离的方式来确保任何服务的故障不会影响当前服务。
### 线程池机制的优点
- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。
- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。
- 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦。
......@@ -41,8 +45,9 @@ Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果
简单来说,最大的好处,就是资源隔离,确保说任何一个依赖服务故障,不会拖垮当前的这个服务。
### 线程池机制的缺点
- 线程池机制最大的缺点就是增加了 CPU 的开销。<br>
除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。
除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。
- 每个 command 的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换。
- Hystrix 官方自己做了一个多线程异步带来的额外开销统计,通过对比多线程异步调用+同步调用得出,Netflix API 每天通过 Hystrix 执行 10 亿次调用,每个服务实例有 40 个以上的线程池,每个线程池有 10 个左右的线程。)最后发现说,用 Hystrix 的额外开销,就是给请求带来了 3ms 左右的延时,最多延时在 10ms 以内,相比于可用性和稳定性的提升,这是可以接受的。
......@@ -54,6 +59,7 @@ semaphore 技术可以用来限流和削峰,但是不能用来对调用延迟
`execution.isolation.strategy` 设置为 `SEMAPHORE`,那么 Hystrix 就会用 semaphore 机制来替代线程池机制,来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候,底层的网络调用延迟很严重,那么是无法 timeout 的,只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后,就会立即开启限流。
### 接口限流 Demo
假设一个线程池大小为 8,等待队列的大小为 10。timeout 时长我们设置长一些,20s。
在 command 内部,写死代码,做一个 sleep,比如 sleep 3s。
......
## 基于 Hystrix 线程池技术实现资源隔离
[上一讲](./e-commerce-website-detail-page-architecture.md)提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。
资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是分配给商品服务线程池内就 10 个线程,最多就只会用这 10 个线程去执行。不会因为对商品服务调用的延迟,将 Tomcat 内部所有的线程资源全部耗尽。
......@@ -6,6 +7,7 @@
Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 Command。这也是 Hystrix 最最基本的资源隔离技术。
### 利用 HystrixCommand 获取单条数据
我们通过将调用商品服务的操作封装在 HystrixCommand 中,限定一个 key,比如下面的 `GetProductInfoCommandGroup`,在这里我们可以简单认为这是一个线程池,每次调用商品服务,就只会用该线程池中的资源,不会再去用其它线程资源了。
```java
......@@ -35,7 +37,7 @@ public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
@ResponseBody
public String getProductInfo(Long productId) {
HystrixCommand<ProductInfo> getProductInfoCommand = new GetProductInfoCommand(productId);
// 通过command执行,获取最新商品数据
ProductInfo productInfo = getProductInfoCommand.execute();
System.out.println(productInfo);
......@@ -46,6 +48,7 @@ public String getProductInfo(Long productId) {
上面执行的是 execute() 方法,其实是同步的。也可以对 command 调用 queue() 方法,它仅仅是将 command 放入线程池的一个等待队列,就立即返回,拿到一个 Future 对象,后面可以继续做其它一些事情,然后过一段时间对 Future 调用 get() 方法获取数据。这是异步的。
### 利用 HystrixObservableCommand 批量获取数据
只要是获取商品数据,全部都绑定到同一个线程池里面去,我们通过 HystrixObservableCommand 的一个线程去执行,而在这个线程里面,批量把多个 productId 的 productInfo 拉回来。
```java
......@@ -78,6 +81,7 @@ public class GetProductInfosCommand extends HystrixObservableCommand<ProductInfo
```
在缓存服务接口中,根据传来的 id 列表,比如是以 `,` 分隔的 id 串,通过上面的 HystrixObservableCommand,执行 Hystrix 的一些 API 方法,获取到所有商品数据。
```java
public String getProductInfos(String productIds) {
String[] productIdArray = productIds.split(",");
......@@ -112,4 +116,4 @@ public String getProductInfos(String productIds) {
![hystrix-thread-pool-isolation](./images/hystrix-thread-pool-isolation.png)
从 Nginx 开始,缓存都失效了,那么 Nginx 通过缓存服务去调用商品服务。缓存服务默认的线程大小是 10 个,最多就只有 10 个线程去调用商品服务的接口。即使商品服务接口故障了,最多就只有 10 个线程会 hang 死在调用商品服务接口的路上,缓存服务的 Tomcat 内其它的线程还是可以用来调用其它的服务,干其它的事情。
\ No newline at end of file
从 Nginx 开始,缓存都失效了,那么 Nginx 通过缓存服务去调用商品服务。缓存服务默认的线程大小是 10 个,最多就只有 10 个线程去调用商品服务的接口。即使商品服务接口故障了,最多就只有 10 个线程会 hang 死在调用商品服务接口的路上,缓存服务的 Tomcat 内其它的线程还是可以用来调用其它的服务,干其它的事情。
## 基于 timeout 机制为服务接口调用超时提供安全保护
一般来说,在调用依赖服务的接口的时候,比较常见的一个问题就是**超时**。超时是在一个复杂的分布式系统中,导致系统不稳定,或者系统抖动。出现大量超时,线程资源会被 hang 死,从而导致吞吐量大幅度下降,甚至服务崩溃。
你去调用各种各样的依赖服务,特别是在大公司,你甚至都不认识开发一个服务的人,你都不知道那个人的技术水平怎么样,对那个人根本不了解。
......@@ -12,6 +13,7 @@ Peter Steiner 说过,"[On the Internet, nobody knows you're a dog](https://en.
如果你不对各种依赖服务接口的调用做超时控制,来给你的服务提供安全保护措施,那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了。大量的接口调用很慢,大量的线程被卡死。如果你做了资源的隔离,那么也就是线程池的线程被卡死,但其实我们可以做超时控制,没必要让它们全卡死。
### TimeoutMilliseconds
在 Hystrix 中,我们可以手动设置 timeout 时长,如果一个 command 运行时间超过了设定的时长,那么就被认为是 timeout,然后 Hystrix command 标识为 timeout,同时执行 fallback 降级逻辑。
`TimeoutMilliseconds` 默认值是 1000,也就是 1000ms。
......@@ -22,6 +24,7 @@ HystrixCommandProperties.Setter()
```
### TimeoutEnabled
这个参数用于控制是否要打开 timeout 机制,默认值是 true。
```java
......@@ -30,6 +33,7 @@ HystrixCommandProperties.Setter()
```
## 实例 Demo
我们在 command 中,将超时时间设置为 500ms,然后在 run() 方法中,设置休眠时间 1s,这样一个请求过来,直接休眠 1s,结果就会因为超时而执行降级逻辑。
```java
......@@ -100,4 +104,4 @@ public class TimeoutTest {
```c
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": 1, "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}
```
\ No newline at end of file
```
# 如何做技术选型?Sentinel 还是 Hystrix?
Sentinel 是阿里中间件团队研发的面向分布式服务架构的轻量级高可用流量控制组件,于 2018 年 7 月正式开源。Sentinel 主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户提升服务的稳定性。大家可能会问:Sentinel 和之前经常用到的熔断降级库 Netflix Hystrix 有什么异同呢?本文将从资源模型和执行模型、隔离设计、熔断降级、实时指标统计设计等角度将 Sentinel 和 Hystrix 进行对比,希望在面临技术选型的时候,对各位开发者能有所帮助。
Sentinel 项目地址:https://github.com/alibaba/Sentinel
Sentinel 项目地址:https://github.com/alibaba/Sentinel
## 总体说明
先来看一下 Hystrix 的官方介绍:
> 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.
......@@ -20,7 +22,9 @@ Sentinel 项目地址:https://github.com/alibaba/Sentinel
两者解决的问题还是有比较大的不同的,下面我们来具体对比一下。
## 共同特性
### 1. 资源模型和执行模型上的对比
Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象 `HystrixCommand``HystrixObservableCommand`,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 `commandKey``groupKey`(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。
**注**:关于 Hystrix 的详细介绍及代码演示,可以参考本项目[高可用架构](/docs/high-availability/README.md)-Hystrix 部分的详细说明。
......@@ -32,6 +36,7 @@ Sentinel 的设计则更为简单。相比 Hystrix Command 强依赖隔离规则
`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 过高的时候可以自动将流量调整成合适的形状。常用的有:
- **直接拒绝模式**:即超出的请求直接拒绝。
- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
![Slow-Start-Preheating-Mode](./images/Slow-Start-Preheating-Mode.jpg)
![Slow-Start-Preheating-Mode](./images/Slow-Start-Preheating-Mode.jpg)
- **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
![Homogenizer-mode](./images/Homogenizer-mode.jpg)
![Homogenizer-mode](./images/Homogenizer-mode.jpg)
目前 Sentinel 对异步调用链路的支持还不是很好,后续版本会着重改善支持异步调用。
### 3. 系统负载保护
Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
![BRP](./images/BRP.jpg)
### 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)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。
![Sentinel-Dashboard](./images/Sentinel-Dashboard.jpg)
### 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 |
......@@ -2,57 +2,57 @@
## [消息队列](/docs/high-concurrency/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 都有什么优点和缺点?](/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 的分布式架构原理能说一下么(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 是如何实现分布式的啊)?](/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)
## 缓存
* [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/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)
- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/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)
## 分库分表
* [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/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)
- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/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 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md)
- [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md)
## 高并发系统
* [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md)
- [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md)
---
---
## 公众号
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
关注「**Doocs开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
![](./images/pdf.png)
......
## 面试题
如何设计可以动态扩容缩容的分库分表方案?
## 面试官心理分析
对于分库分表来说,主要是面对以下问题:
* 选择一个数据库中间件,调研、学习、测试;
* 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
* 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
* 完成单库单表到分库分表的**迁移**,双写方案;
* 线上系统开始基于分库分表对外提供服务;
* 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
- 选择一个数据库中间件,调研、学习、测试;
- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
- 完成单库单表到分库分表的**迁移**,双写方案;
- 线上系统开始基于分库分表对外提供服务;
- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
这个是你必须面对的一个事儿,就是你已经弄好分库分表方案了,然后一堆库和表都建好了,基于分库分表中间件的代码开发啥的都好了,测试都 ok 了,数据能均匀分布到各个库和各个表里去,而且接着你还通过双写的方案咔嚓一下上了系统,已经直接基于分库分表方案在搞了。
......@@ -21,9 +22,10 @@
## 面试题剖析
### 停机扩容(不推荐)
这个方案就跟停机迁移一样,步骤几乎一致,唯一的一点就是那个导数的工具,是把现有库表的数据抽出来慢慢倒入到新的库和表里去。但是最好别这么玩儿,有点不太靠谱,因为既然**分库分表**就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,你这么玩儿,可能会出问题。
从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1小时数据就导完了。这没有问题。
从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1 小时数据就导完了。这没有问题。
如果 3 个库 + 12 个表,跑了一段时间了,数据量都 1~2 亿了。光是导 2 亿数据,都要导个几个小时,6 点,刚刚导完数据,还要搞后续的修改配置,重启系统,测试验证,10 点才可以搞完。所以不能这么搞。
......@@ -33,7 +35,7 @@
我可以告诉各位同学,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支撑还是数据量支撑都没问题。
每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载 32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500 = 48000 的写并发,接近 5 万每秒的写入并发,前面再加一个MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。
每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载 32 _ 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 _ 1500 = 48000 的写并发,接近 5 万每秒的写入并发,前面再加一个 MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。
有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128 个库,256 个库,512 个库。
......@@ -46,11 +48,11 @@
一个实践是利用 `32 * 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 服务器之间做迁移就可以了。然后系统配合改一下配置即可。
......@@ -62,7 +64,7 @@
这里对步骤做一个总结:
1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了。
1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 \* 32 表,对于大部分公司来说,可能几年都够了。
2. 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表
3. 扩容的时候,申请增加更多的数据库服务器,装好 MySQL,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。
4. 由 DBA 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。
......
## 面试题
分库分表之后,id 主键如何处理?
## 面试官心理分析
......@@ -33,7 +34,7 @@
适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的。
``` java
```java
UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf
```
......@@ -47,16 +48,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 = 4096` ,也就是说可以用这个 12 bits 代表的数字来区分**同一个毫秒内**的 4096 个不同的 id。
```
```
0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000
```
``` java
```java
public class IdWorker {
private long workerId;
......@@ -170,7 +171,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 生成。
......
## 面试题
现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上?
## 面试官心理分析
......@@ -19,7 +20,7 @@
导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。
验证一下,ok了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。
验证一下,ok 了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。
但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。
......
## 面试题
为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
## 面试官心理分析
......@@ -8,6 +9,7 @@
## 面试题剖析
### 为什么要分库分表?(设计高并发系统的时候,数据库层面该如何设计?)
说白了,分库分表是两回事儿,大家可别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。
我先给大家抛出来一个场景。
......@@ -36,11 +38,11 @@
这就是所谓的**分库分表**,为啥要分库分表?你明白了吧。
| # | 分库分表前 | 分库分表后 |
|---|---|---|
| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL从单机到多机,能承受的并发增加了多倍 |
| 磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 |
| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 |
| # | 分库分表前 | 分库分表后 |
| ------------ | ---------------------------- | -------------------------------------------- |
| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL 从单机到多机,能承受的并发增加了多倍 |
| 磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 |
| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 |
### 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?
......@@ -48,11 +50,11 @@
比较常见的包括:
* Cobar
* TDDL
* Atlas
* Sharding-jdbc
* Mycat
- Cobar
- TDDL
- Atlas
- Sharding-jdbc
- Mycat
#### Cobar
......@@ -96,7 +98,7 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。
还有**表层面的拆分**,就是分表,将一个表变成 N 个表,就是**让每个表的数据量控制在一定范围内**,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的SQL越复杂,就最好让单表行数越少。
还有**表层面的拆分**,就是分表,将一个表变成 N 个表,就是**让每个表的数据量控制在一定范围内**,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的 SQL 越复杂,就最好让单表行数越少。
好了,无论分库还是分表,上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后,**中间件可以根据你指定的某个字段值**,比如说 userid,**自动路由到对应的库上去,然后再自动路由到对应的表里去**
......@@ -104,8 +106,8 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
而且这儿还有两种**分库分表的方式**
* 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
* 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。
......
## 面试题
ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?
## 面试官心理分析
......@@ -17,7 +18,7 @@ ElasticSearch 设计的理念就是分布式搜索引擎,底层其实还是基
ES 中存储数据的**基本单位是索引**,比如说你现在要在 ES 中存储一些订单数据,你就应该在 ES 中创建一个索引 `order_idx` ,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。
```
```
index -> type -> mapping -> document -> field。
```
......@@ -41,7 +42,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。
......
## Lucene 和 ES 的前世今生
Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。
ElasticSearch 基于 Lucene,隐藏了 lucene 的复杂性,提供了简单易用的 RESTful api / Java api 接口(另外还有其他语言的 api 接口)。
* 分布式的文档存储引擎
* 分布式的搜索引擎和分析引擎
* 分布式,支持 PB 级数据
- 分布式的文档存储引擎
- 分布式的搜索引擎和分析引擎
- 分布式,支持 PB 级数据
## ES 的核心概念
### Near Realtime
近实时,有两层意思:
* 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
* 基于 ES 执行搜索和分析可以达到秒级
- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
- 基于 ES 执行搜索和分析可以达到秒级
### Cluster 集群
......@@ -27,13 +29,13 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
文档是 ES 中最小的数据单元,一个 document 可以是一条客户数据、一条商品分类数据、一条订单数据,通常用 json 数据结构来表示。每个 index 下的 type,都可以存储多条 document。一个 document 里面有多个 field,每个 field 就是一个数据字段。
``` json
```json
{
"product_id": "1",
"product_name": "iPhone X",
"product_desc": "苹果手机",
"category_id": "2",
"category_name": "电子产品"
"product_id": "1",
"product_name": "iPhone X",
"product_desc": "苹果手机",
"category_id": "2",
"category_name": "电子产品"
}
```
......@@ -51,7 +53,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 +61,10 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
## ES 核心概念 vs. DB 核心概念
| ES | DB |
|---|---|
| index | 数据库 |
| type | 数据表 |
| ES | DB |
| -------- | -------- |
| index | 数据库 |
| type | 数据表 |
| document | 一行数据 |
以上是一个简单的类比。
## 面试题
ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
## 面试官心理分析
......@@ -19,7 +20,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 +40,7 @@ hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可
其实可以做**数据预热**
举个例子,拿微博来说,你可以把一些大V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 `filesystem cache` 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。
举个例子,拿微博来说,你可以把一些大 V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 `filesystem cache` 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。
或者是电商,你可以将平时查看最多的一些商品,比如说 iphone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 `filesystem cache` 里去。
......
## 面试题
ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
## 面试官心理分析
......@@ -15,8 +16,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。
大概就这么说一下就行了。
## 面试题
ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?
## 面试官心理分析
......@@ -11,10 +12,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` 都搞定之后,就返回响应结果给客户端。
![es-write](./images/es-write.png)
......@@ -22,27 +23,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 读取,采用的是随机轮询算法。
......@@ -102,29 +103,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 +133,7 @@ buffer 每 refresh 一次,就会产生一个 `segment file` ,所以默认情
要注意倒排索引的两个重要细节:
* 倒排索引中的所有词项对应一个或多个文档;
* 倒排索引中的词项**根据字典顺序升序排列**
- 倒排索引中的所有词项对应一个或多个文档;
- 倒排索引中的词项**根据字典顺序升序排列**
> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。
## 面试题
如何设计一个高并发系统?
## 面试官心理分析
......@@ -31,12 +32,12 @@
可以分为以下 6 点:
* 系统拆分
* 缓存
* MQ
* 分库分表
* 读写分离
* ElasticSearch
- 系统拆分
- 缓存
- MQ
- 分库分表
- 读写分离
- ElasticSearch
![high-concurrency-system-design](./images/high-concurrency-system-design.png)
......
## 面试题
如何保证消息队列的高可用?
## 面试官心理分析
......@@ -21,7 +22,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
#### 单机模式
单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的😄,没人生产用单机模式。
单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的 😄,没人生产用单机模式。
#### 普通集群模式(无高可用性)
......@@ -63,7 +64,7 @@ Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机
![kafka-after](./images/kafka-after.png)
这么搞,就有所谓的**高可用性**了,因为如果某个 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 之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)
......
## 面试题
如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么?
## 面试官心理分析
......@@ -13,8 +14,8 @@
由于此节内容较多,因此,会分为两个小节进行讲解。
* [redis 主从架构](/docs/high-concurrency/redis-master-slave.md)
* [redis 基于哨兵实现高可用](/docs/high-concurrency/redis-sentinel.md)
- [redis 主从架构](/docs/high-concurrency/redis-master-slave.md)
- [redis 基于哨兵实现高可用](/docs/high-concurrency/redis-sentinel.md)
redis 实现**高并发**主要依靠**主从架构**,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。
......
## 面试题
如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
## 面试官心理分析
......@@ -35,10 +36,10 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一
其实还是得结合业务来思考,我这里给几个思路:
* 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
* 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
* 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
* 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
- 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
- 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
- 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
- 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
![mq-11](./images/mq-11.png)
......
## 面试题
如何保证消息的顺序性?
## 面试官心理分析
......@@ -15,24 +16,25 @@
先看看顺序会错乱的俩场景:
* **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。这不明显乱了。
![rabbitmq-order-01](./images/rabbitmq-order-01.png)
* **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。<br>消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。
- **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。<br>消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。
![kafka-order-01](./images/kafka-order-01.png)
### 解决方案
#### RabbitMQ
拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
![rabbitmq-order-02](./images/rabbitmq-order-02.png)
#### Kafka
* 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
* 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
![kafka-order-02](./images/kafka-order-02.png)
## 面试题
如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题?
## 面试官心理分析
......@@ -21,7 +22,7 @@
此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务 `channel.txSelect` ,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 `channel.txRollback` ,然后重试发送消息;如果收到了消息,那么可以提交事务 `channel.txCommit`
``` java
```java
// 开启事务
channel.txSelect
try {
......@@ -50,11 +51,11 @@ channel.txCommit
设置持久化有**两个步骤**
* 创建 queue 的时候将其设置为持久化<br>
- 创建 queue 的时候将其设置为持久化<br>
这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
* 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2<br>
- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2<br>
就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
......@@ -75,6 +76,7 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
### Kafka
#### 消费端弄丢了数据
唯一可能导致消费者弄丢数据的情况,就是说,你消费到了这个消息,然后消费者那边**自动提交了 offset**,让 Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。
这不是跟 RabbitMQ 差不多吗,大家都知道 Kafka 会自动提交 offset,那么只要**关闭自动提交** offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是**可能会有重复消费**,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。
......@@ -89,10 +91,10 @@ 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 切换时,数据不会丢失。
......
# 如何限流?在工作中是怎么做的?说一下具体的实现?
* Author: [HuiFer](https://github.com/huifer)
* Description: 该文简单介绍限流相关技术以及实现
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文简单介绍限流相关技术以及实现
## 什么是限流
......@@ -13,9 +13,9 @@
#### 实现方式
* 控制单位时间内的请求数量
- 控制单位时间内的请求数量
``` java
```java
import java.util.concurrent.atomic.AtomicInteger;
......@@ -54,18 +54,18 @@ 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;
......@@ -193,9 +193,9 @@ public class TimeWindow {
#### 实现方式
* 规定固定容量的桶, 有水进入, 有水流出. 对于流进的水我们无法估计进来的数量、速度, 对于流出的水我们可以控制速度.
- 规定固定容量的桶, 有水进入, 有水流出. 对于流进的水我们无法估计进来的数量、速度, 对于流出的水我们可以控制速度.
``` java
```java
public class LeakBucket {
/**
* 时间
......@@ -233,9 +233,9 @@ public class LeakBucket {
#### 实现方式
* 规定固定容量的桶, token 以固定速度往桶内填充, 当桶满时 token 不会被继续放入, 每过来一个请求把 token 从桶中移除, 如果桶中没有 token 不能请求
- 规定固定容量的桶, token 以固定速度往桶内填充, 当桶满时 token 不会被继续放入, 每过来一个请求把 token 从桶中移除, 如果桶中没有 token 不能请求
``` java
```java
public class TokenBucket {
/**
* 时间
......@@ -275,9 +275,9 @@ public class TokenBucket {
### spring cloud gateway
* spring cloud gateway 默认使用redis进行限流, 笔者一般只是修改修改参数属于拿来即用. 并没有去从头实现上述那些算法.
- spring cloud gateway 默认使用 redis 进行限流, 笔者一般只是修改修改参数属于拿来即用. 并没有去从头实现上述那些算法.
``` xml
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
......@@ -288,34 +288,30 @@ public class TokenBucket {
</dependency>
```
``` yaml
```yaml
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
- id: requestratelimiter_route
uri: lb://pigx-upms
order: 10000
predicates:
- Path=/admin/**
uri: lb://pigx-upms
order: 10000
predicates:
filters:
- name: RequestRateLimiter
- Path=/admin/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶的容积
redis-rate-limiter.burstCapacity: 3 # 流速 每秒
key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
- 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,16 +320,16 @@ KeyResolver remoteAddrKeyResolver() {
### sentinel
* 通过配置来控制每个url的流量
- 通过配置来控制每个 url 的流量
``` xml
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
```
``` yaml
```yaml
spring:
cloud:
nacos:
......@@ -353,30 +349,30 @@ spring:
namespace: xxxxxxxx
```
* 配置内容在nacos上进行编辑
- 配置内容在 nacos 上进行编辑
``` json
```json
[
{
"resource": "/hello",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
{
"resource": "/hello",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
```
* 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 会有更加好的体验.
## 面试题
如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。
## 面试官心理分析
其实聊到这个问题,一般面试官要考察两块:
* 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
* 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,**大多数人就是平时埋头用,从来不去思考背后的一些东西**。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做?
......@@ -16,12 +17,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 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。
## 面试题
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
## 面试官心理分析
......@@ -19,17 +20,17 @@
一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:
* 先修复 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 里去再补一次。
......
## 面试题
你们有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决 MySQL 主从同步的延时问题?
## 面试官心理分析
......@@ -8,6 +9,7 @@
## 面试题剖析
### 如何实现 MySQL 的读写分离?
其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。
### MySQL 主从复制原理的是啥?
......@@ -34,7 +36,7 @@
我们通过 MySQL 命令:
``` sql
```sql
show status
```
......@@ -42,7 +44,7 @@ show status
一般来说,如果主从延迟较为严重,有以下解决方案:
* 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
* 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
* 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
* 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库****不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。
- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
- 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
- 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库****不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。
## 面试题
了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?
## 面试官心理分析
......@@ -8,6 +9,7 @@
## 面试题剖析
### 缓存雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
这就是缓存雪崩。
......@@ -18,9 +20,9 @@
缓存雪崩的事前事中事后的解决方案如下:
* 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
* 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
* 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
- 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
- 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
![redis-caching-avalanche-solution](./images/redis-caching-avalanche-solution.png)
......@@ -30,13 +32,13 @@
好处:
* 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
* 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
* 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
### 缓存穿透
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
对于系统 A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
......@@ -52,6 +54,6 @@
不同场景下的解决方式可如下:
* 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
* 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
* 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
- 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
## 面试题
Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?
## 面试官心理分析
......
## 面试题
Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
## 面试官心理分析
......@@ -17,16 +18,17 @@ 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(分布式协调的中间件)对所有元数据进行存储维护。
......@@ -41,25 +43,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 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。
- fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
#### ping 消息深入
......@@ -71,9 +73,9 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担
### 分布式寻址算法
* hash 算法(大量缓存重建)
* 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
* Redis cluster 的 hash slot 算法
- hash 算法(大量缓存重建)
- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
- Redis cluster 的 hash slot 算法
#### hash 算法
......
## 面试题
如何保证缓存与数据库的双写一致性?
## 面试官心理分析
......@@ -15,8 +16,8 @@
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
* 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
* 更新的时候,**先更新数据库,然后再删除缓存**
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,**先更新数据库,然后再删除缓存**
**为什么是删除缓存,而不是更新缓存?**
......@@ -60,13 +61,13 @@
高并发的场景下,该解决方案要注意的问题:
* 读请求长时阻塞
- 读请求长时阻塞
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 \* 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**
一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。
......@@ -80,19 +81,19 @@
经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。
* 读请求并发量过高
- 读请求并发量过高
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
* 多服务实例部署的请求路由
- 多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**
比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。
* 热点商品的路由问题,导致请求的倾斜
- 热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
......
## 面试题
Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
## 面试官心理分析
......@@ -7,8 +8,8 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
其实问这个问题,主要有两个原因:
* 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
* 看看你在实际项目里都怎么玩儿过 Redis。
- 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
- 看看你在实际项目里都怎么玩儿过 Redis。
要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。
......@@ -16,11 +17,11 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
Redis 主要有以下几种数据类型:
* Strings
* Hashes
* Lists
* Sets
* Sorted Sets
- Strings
- Hashes
- Lists
- Sets
- Sorted Sets
> Redis 除了这 5 种数据类型之外,还有 Bitmaps、HyperLogLogs、Streams 等。
......@@ -28,7 +29,7 @@ Redis 主要有以下几种数据类型:
这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。
``` bash
```bash
set college szu
```
......@@ -36,19 +37,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 +60,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 +86,7 @@ Sets 是无序集合,自动去重。
把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。
``` bash
```bash
#-------操作一个set-------
# 添加元素
sadd mySet 1
......@@ -124,7 +125,7 @@ sdiff yourSet mySet
Sorted Sets 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。
``` bash
```bash
zadd board 85 zhangsan
zadd board 72 lisi
zadd board 96 wangwu
......
## 面试题
Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?
## 面试官心理分析
......@@ -7,7 +8,7 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
常见的有两个问题:
* 往 Redis 写入的数据怎么没了?
- 往 Redis 写入的数据怎么没了?
可能有同学会遇到,在生产环境的 Redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 Redis 你就没用对啊。Redis 是缓存,你给当存储了是吧?
......@@ -15,13 +16,14 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
那既然内存是有限的,比如 Redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
* 数据明明过期了,怎么还占用着内存?
- 数据明明过期了,怎么还占用着内存?
这是由 Redis 的过期策略来决定。
## 面试题剖析
### Redis 过期策略
Redis 过期策略是:**定期删除+惰性删除**
所谓**定期删除**,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
......@@ -40,12 +42,12 @@ 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 算法
......@@ -53,7 +55,7 @@ Redis 内存淘汰机制有以下几个:
不求自己纯手工从底层开始打造出自己的 LRU,但是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。
``` java
```java
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
......
......@@ -8,12 +8,12 @@ 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 的数据也丢了。
......@@ -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 开启了 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。
### 异步复制
......
## 面试题
Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
## 面试官心理分析
......@@ -21,8 +22,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 +33,25 @@ Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了
#### RDB 优缺点
* RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。
* RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis **保持高性能**,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
* 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。
- 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 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
- 如果想要在 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 来进行快速的数据恢复。
## 面试题
生产环境中的 Redis 是怎么部署的?
## 面试官心理分析
......
......@@ -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 |
......@@ -63,13 +63,13 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的
主备切换的过程,可能会导致数据丢失:
* 异步复制导致的数据丢失
- 异步复制导致的数据丢失
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。
![async-replication-data-lose-case](./images/async-replication-data-lose-case.png)
* 脑裂导致的数据丢失
- 脑裂导致的数据丢失
脑裂,也就是说,某个 master 所在机器突然**脱离了正常的网络**,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会**认为** master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的**脑裂**
......@@ -81,7 +81,7 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的
进行如下配置:
``` bash
```bash
min-slaves-to-write 1
min-slaves-max-lag 10
```
......@@ -90,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 了。
......@@ -123,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
......@@ -160,4 +160,4 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过
哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 `pub/sub` 消息机制。
这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。
\ No newline at end of file
这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。
## 面试题
Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么 Redis 单线程却能支撑高并发?
## 面试官心理分析
......@@ -29,10 +30,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事
文件事件处理器的结构包含 4 个部分:
* 多个 socket
* IO 多路复用程序
* 文件事件分派器
* 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
- 多个 socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。
......@@ -54,10 +55,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事
### 为啥 Redis 单线程模型也能效率这么高?
* 纯内存操作。
* 核心是基于非阻塞的 IO 多路复用机制。
* C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
* 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
- 纯内存操作。
- 核心是基于非阻塞的 IO 多路复用机制。
- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
### Redis 6.0 开始引入多线程
......
## 面试题
项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?
## 面试官心理分析
......@@ -31,7 +32,7 @@
mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql 单机支撑到 `2000QPS` 也开始容易报警了。
所以要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。
所以要是你有个系统,高峰期一秒钟过来的请求有 1 万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。
> 缓存是走内存的,内存天然就支撑高并发。
......@@ -39,8 +40,8 @@ mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,
常见的缓存问题有以下几个:
* [缓存与数据库双写不一致](/docs/high-concurrency/redis-consistence.md)
* [缓存雪崩、缓存穿透、缓存击穿](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
* [缓存并发竞争](/docs/high-concurrency/redis-cas.md)
- [缓存与数据库双写不一致](/docs/high-concurrency/redis-consistence.md)
- [缓存雪崩、缓存穿透、缓存击穿](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
- [缓存并发竞争](/docs/high-concurrency/redis-cas.md)
点击超链接,可直接查看缓存相关问题及解决方案。
## 面试题
* 为什么使用消息队列?
* 消息队列有什么优点和缺点?
* Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
- 为什么使用消息队列?
- 消息队列有什么优点和缺点?
- Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
## 面试官心理分析
其实面试官主要是想看看:
* **第一**,你知不知道你们系统里为什么要用消息队列这个东西?<br>
- **第一**,你知不知道你们系统里为什么要用消息队列这个东西?<br>
不少候选人,说自己项目里用了 Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人设计的架构,他从头到尾都没思考过。<br>
没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。
* **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?<br>
- **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?<br>
你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。
* **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?<br>
- **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?<br>
你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**<br>
如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。
......@@ -25,6 +25,7 @@
## 面试题剖析
### 为什么使用消息队列
其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?
面试官问你这个问题,**期望的一个回答**是说,你们公司有个什么**业务场景**,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处。
......@@ -81,15 +82,15 @@
缺点有以下几个:
* 系统可用性降低<br>
- 系统可用性降低<br>
系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
* 系统复杂度提高<br>
- 系统复杂度提高<br>
硬生生加个 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)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。
* 一致性问题<br>
- 一致性问题<br>
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
......@@ -97,14 +98,14 @@ A 系统处理完了直接返回成功了,人都以为你这个请求就成功
### 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 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
综上,各种对比之后,有如下建议:
......
# 微服务架构
* [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
* [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
## Spring Cloud 微服务架构
* 什么是微服务?微服务之间是如何独立通讯的?
* Spring Cloud 和 Dubbo 有哪些区别?
* Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
* 什么是服务熔断?什么是服务降级?
* 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
* 你所知道的微服务技术栈都有哪些?
* Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
* ......
- 什么是微服务?微服务之间是如何独立通讯的?
- Spring Cloud 和 Dubbo 有哪些区别?
- Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
- 什么是服务熔断?什么是服务降级?
- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
- 你所知道的微服务技术栈都有哪些?
- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
- ......
---
## 公众号
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
关注「**Doocs开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便!
![](./images/pdf.png)
......
......@@ -92,4 +92,4 @@ Fred Brooks 在 30 年前写道,“there are no silver bullets”,像任何
# 总结
构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。
\ No newline at end of file
构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。
......@@ -74,7 +74,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用
![deployment-strategy-3](./images/deployment-strategy-3.png)
使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统​。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。
使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统 ​。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。
一旦将服务打包成容器映像,就需要启动若干容器。一般在一个物理机或者虚拟机上运行多个容器,可能需要集群管理系统,例如 k8s 或者 Marathon,来管理容器。集群管理系统将主机作为资源池,根据每个容器对资源的需求,决定将容器调度到那个主机上。
......@@ -100,10 +100,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 是一种很方便部署微服务的方式。基于请求计费方式意味着用户只需要承担处理自己业务那部分的负载;另外,因为不需要了解基础架构,用户只需要开发自己的应用。
......
# 服务发现组件 Eureka 的几个主要调用过程
* Author: [mghio](https://www.mghio.cn)
* Description: 该文主要讲述服务发现组件 Eureka 的几个主要调用过程
- Author: [mghio](https://www.mghio.cn)
- Description: 该文主要讲述服务发现组件 Eureka 的几个主要调用过程
## 前言
......@@ -18,7 +18,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` ,就可以将其作为注册中心,启动服务后访问页面如下:
......@@ -123,7 +123,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)
......
# 微服务治理策略
* Author: [HuiFer](https://github.com/huifer)
* Description: 该文简单介绍微服务的治理策略以及应用技术
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文简单介绍微服务的治理策略以及应用技术
## 服务的注册和发现
......@@ -20,10 +20,14 @@
> 解决问题: 各个服务之间的沟通桥梁
> 解决方法 :
>
> - 同步消息
>
> 1. rest
> 1. rpc
>
> - 异步消息
>
> 1. MQ
## 配置管理
......
# 微服务技术栈
* Author: [HuiFer](https://github.com/huifer)
* Description: 该文简单介绍微服务技术栈有哪些分别用来做什么。
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文简单介绍微服务技术栈有哪些分别用来做什么。
## 技术栈
......@@ -9,9 +9,9 @@
作用:快速开发服务。
* Spring
* Spring MVC
* Spring Boot
- Spring
- Spring MVC
- Spring Boot
[Spring](https://spring.io/) 目前是 JavaWeb 开发人员必不可少的一个框架,SpringBoot 简化了 Spring 开发的配置目前也是业内主流开发框架。
......@@ -21,9 +21,9 @@
#### 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/)
- 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
......@@ -35,9 +35,9 @@
Zookeeper 保证 CP,Eureka 保证 AP:
* C:数据一致性;
* A:服务可用性;
* P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
- C:数据一致性;
- A:服务可用性;
- P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
### 微服务配置管理
......@@ -81,12 +81,12 @@ Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能
#### Rest
* 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互
- 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互
#### RPC
* Remote Procedure Call
* 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。
- Remote Procedure Call
- 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。
#### [gRPC](https://www.grpc.io/)
......@@ -96,8 +96,8 @@ Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能
#### RMI
* Remote Method Invocation
* 纯 Java 调用
- Remote Method Invocation
- 纯 Java 调用
### 服务接口调用
......@@ -227,4 +227,4 @@ Nginx 属于服务端负载均衡,Ribbon 属于客户端负载均衡。Nginx
#### [Kubernetes(K8s)](https://kubernetes.io/)
#### [Mesos](http://mesos.apache.org/)
\ No newline at end of file
#### [Mesos](http://mesos.apache.org/)
# 什么是微服务?微服务之间是如何独立通讯的?
* Author: [HuiFer](https://github.com/huifer)
* Description: 介绍微服务的定义以及服务间的通信。
- Author: [HuiFer](https://github.com/huifer)
- Description: 介绍微服务的定义以及服务间的通信。
## 什么是微服务
* 微服务架构是一个分布式系统, 按照业务进行划分成为不同的服务单元, 解决单体系统性能等不足。
* 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。
- 微服务架构是一个分布式系统, 按照业务进行划分成为不同的服务单元, 解决单体系统性能等不足。
- 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。
> 微服务概念起源: [Microservices](https://martinfowler.com/articles/microservices.html)
> 微服务概念起源: [Microservices](https://martinfowler.com/articles/microservices.html)
## 微服务之间是如何独立通讯的
......@@ -19,14 +19,14 @@
REST 请求在微服务中是最为常用的一种通讯方式, 它依赖于 HTTP\HTTPS 协议。RESTFUL 的特点是:
1. 每一个 URI 代表 1 种资源
2. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作: GET 用来获取资源, POST 用来新建资源(也可以用于更新资源), PUT 用来更新资源, DELETE 用来删除资源
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 +39,7 @@ public class RestControllerDemo {
另外一个服务需要去调用该接口,调用方只需要根据 API 文档发送请求即可获取返回结果。
``` java
```java
@RestController
@RequestMapping("/demo")
public class RestDemo{
......@@ -56,7 +56,7 @@ public class RestDemo{
通过这样的方式可以实现服务之间的通讯。
#### RPC TCP协议
#### RPC TCP 协议
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。它的工作流程是这样的:
......@@ -68,14 +68,13 @@ RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请
6\. 执行过程完毕,将结果返回服务器句柄
7\. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果
8\. 消息传回本地主机
9\. 客户端句柄由本地主机的网络服务接收消息
10. 客户端接收到调用语句返回的结果数据
9\. 客户端句柄由本地主机的网络服务接收消息 10. 客户端接收到调用语句返回的结果数据
举个例子。
首先需要一个服务端:
``` java
```java
import java.io.IOException;
import java.io.ObjectInputStream;
......@@ -182,7 +181,7 @@ public class RPCServer {
其次需要一个客户端:
``` java
```java
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
......@@ -240,7 +239,7 @@ public class RPCclient<T> {
再来一个测试的远程方法。
``` java
```java
public interface Tinterface {
String send(String msg);
}
......@@ -256,7 +255,7 @@ public class TinterfaceImpl implements Tinterface {
测试代码如下:
``` java
```java
import com.huifer.admin.rpc.Tinterface;
import com.huifer.admin.rpc.TinterfaceImpl;
......
# 微服务
> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。<br> 本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。
> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。<br> 本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。
过去几年中出现了“微服务架构”这一术语,它描述了将软件应用程序设计为若干个可独立部署的服务套件的特定方法。尽管这种架构风格尚未有精确的定义,但围绕业务能力、自动部署、端点智能以及语言和数据的分散控制等组织来说,它们还是存在着某些共同特征。
......@@ -44,7 +44,7 @@
在将大型应用程序拆分为多个部分时,管理层往往侧重于技术层面,从而导致 UI 团队、服务器端逻辑团队、数据库团队的划分。当团队按照这些方式分开时,即便是简单的更改也可能导致跨团队项目的时间和预算批准。一个聪明的团队将围绕这个进行优化,“两害相权取其轻”——只需将逻辑强制应用到他们可以访问的任何应用程序中。换句话说,逻辑无处不在。这是康威定律[5]的一个例子。
> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。<br> —— 梅尔文•康威,1967年
> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。<br> —— 梅尔文•康威,1967
![conways-law](./images/conways-law.png)
......@@ -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).
......
......@@ -24,15 +24,15 @@ 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——将前端和后端分离
......
......@@ -22,4 +22,4 @@
"devDependencies": {
"docsify-pdf-converter": "^2.0.7"
}
}
}
\ No newline at end of file
- 高并发架构
- [消息队列](./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-master-slave.md)
- [Redis 的持久化有哪几种方式?](./docs/high-concurrency/redis-persistence.md)
- [Redis 如何基于哨兵集群实现高可用?](./docs/high-concurrency/redis-sentinel.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/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-master-slave.md)
- [Redis 的持久化有哪几种方式?](./docs/high-concurrency/redis-persistence.md)
- [Redis 如何基于哨兵集群实现高可用?](./docs/high-concurrency/redis-sentinel.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)
- [面试连环炮](./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)
- 降级
- 如何进行降级?
- 基于 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)
- 微服务的一些概念
- [关于微服务架构的描述](./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)
- 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)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册