docs: update articles

This commit is contained in:
yanglbme
2022-09-26 10:34:20 +08:00
parent 94e3510357
commit e0ead3e559
63 changed files with 821 additions and 811 deletions

View File

@@ -2,50 +2,50 @@
## [消息队列](/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)
- [有了解过 Redis rehash 的过程吗?](/docs/high-concurrency/redis-rehash.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)
- [有了解过 Redis rehash 的过程吗?](/docs/high-concurrency/redis-rehash.md)
## 分库分表
- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/docs/high-concurrency/database-shard.md)
- [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](/docs/high-concurrency/database-shard-method.md)
- [如何设计可以动态扩容缩容的分库分表方案?](/docs/high-concurrency/database-shard-dynamic-expand.md)
- [分库分表之后id 主键如何处理?](/docs/high-concurrency/database-shard-global-id-generate.md)
- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/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)
---

View File

@@ -6,12 +6,12 @@
对于分库分表来说,主要是面对以下问题:
- 选择一个数据库中间件,调研、学习、测试;
- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
- 完成单库单表到分库分表的**迁移**,双写方案;
- 线上系统开始基于分库分表对外提供服务;
- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
- 选择一个数据库中间件,调研、学习、测试;
- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
- 完成单库单表到分库分表的**迁移**,双写方案;
- 线上系统开始基于分库分表对外提供服务;
- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
这个是你必须面对的一个事儿,就是你已经弄好分库分表方案了,然后一堆库和表都建好了,基于分库分表中间件的代码开发啥的都好了,测试都 ok 了,数据能均匀分布到各个库和各个表里去,而且接着你还通过双写的方案咔嚓一下上了系统,已经直接基于分库分表方案在搞了。

View File

@@ -48,10 +48,10 @@ UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf
snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id1 个 bit 是不用的,用其中的 41 bits 作为毫秒数,用 10 bits 作为工作机器 id12 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 代表机房 id5 个 bits 代表机器 id。意思就是最多代表 `2^5` 个机房32 个机房),每个机房里可以代表 `2^5` 个机器32 台机器)。
- 12 bits这个是用来记录同一个毫秒内产生的不同 id12 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 代表机房 id5 个 bits 代表机器 id。意思就是最多代表 `2^5` 个机房32 个机房),每个机房里可以代表 `2^5` 个机器32 台机器)。
- 12 bits这个是用来记录同一个毫秒内产生的不同 id12 bits 可以代表的最大正整数是 `2^12 - 1 = 4096` ,也就是说可以用这个 12 bits 代表的数字来区分**同一个毫秒内**的 4096 个不同的 id。
```sh
0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000

View File

@@ -50,11 +50,11 @@
比较常见的包括:
- Cobar
- TDDL
- Atlas
- Sharding-jdbc
- Mycat
- Cobar
- TDDL
- Atlas
- Sharding-jdbc
- Mycat
#### Cobar
@@ -106,8 +106,8 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
而且这儿还有两种**分库分表的方式**
- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range要看场景。

View File

@@ -4,9 +4,9 @@ Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene
ElasticSearch 基于 Lucene隐藏了 lucene 的复杂性,提供了简单易用的 RESTful api / Java api 接口(另外还有其他语言的 api 接口)。
- 分布式的文档存储引擎
- 分布式的搜索引擎和分析引擎
- 分布式,支持 PB 级数据
- 分布式的文档存储引擎
- 分布式的搜索引擎和分析引擎
- 分布式,支持 PB 级数据
## ES 的核心概念
@@ -14,8 +14,8 @@ ElasticSearch 基于 Lucene隐藏了 lucene 的复杂性,提供了简单易
近实时,有两层意思:
- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s
- 基于 ES 执行搜索和分析可以达到秒级
- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s
- 基于 ES 执行搜索和分析可以达到秒级
### Cluster 集群
@@ -31,11 +31,11 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
```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": "电子产品"
}
```

View File

@@ -16,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。
大概就这么说一下就行了。

View File

@@ -12,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)
@@ -23,10 +23,10 @@ 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 搜索数据过程
@@ -40,10 +40,10 @@ j2ee特别牛
你根据 `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 读取,采用的是随机轮询算法。
@@ -73,8 +73,8 @@ translog 日志文件的作用是什么?你执行 commit 操作之前,数据
translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会**丢失** 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 `fsync` 到磁盘,但是性能会差很多。
- `index.translog.sync_interval` 控制 translog 多久 fsync 到磁盘,最小为 100ms
- `index.translog.durability` translog 是每 5 秒钟刷新一次还是每次请求都 fsync这个参数有 2 个取值request(每次请求都执行 fsync,es 要等 translog fsync 到磁盘后才会返回成功)和 async(默认值translog 每隔 5 秒钟 fsync 一次)。
- `index.translog.sync_interval` 控制 translog 多久 fsync 到磁盘,最小为 100ms
- `index.translog.durability` translog 是每 5 秒钟刷新一次还是每次请求都 fsync这个参数有 2 个取值request(每次请求都执行 fsync,es 要等 translog fsync 到磁盘后才会返回成功)和 async(默认值translog 每隔 5 秒钟 fsync 一次)。
实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的**数据丢失**。
@@ -136,7 +136,7 @@ buffer 每 refresh 一次,就会产生一个 `segment file` ,所以默认情
要注意倒排索引的两个重要细节:
- 倒排索引中的所有词项对应一个或多个文档;
- 倒排索引中的词项**根据字典顺序升序排列**
- 倒排索引中的所有词项对应一个或多个文档;
- 倒排索引中的词项**根据字典顺序升序排列**
> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。

View File

@@ -32,12 +32,12 @@
可以分为以下 6 点:
- 系统拆分
- 缓存
- MQ
- 分库分表
- 读写分离
- ElasticSearch
- 系统拆分
- 缓存
- MQ
- 分库分表
- 读写分离
- ElasticSearch
![high-concurrency-system-design](./images/high-concurrency-system-design.png)

View File

@@ -14,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。

View File

@@ -38,10 +38,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)

View File

@@ -16,11 +16,11 @@
先看看顺序会错乱的俩场景:
- **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)
@@ -38,7 +38,7 @@
#### 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)

View File

@@ -117,9 +117,9 @@ while (true) {
设置持久化有**两个步骤**
- 创建 queue 的时候将其设置为持久化。这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
- 创建 queue 的时候将其设置为持久化。这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2。就是将消息设置为持久化的此时 RabbitMQ 就会将消息持久化到磁盘上去。
- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2。就是将消息设置为持久化的此时 RabbitMQ 就会将消息持久化到磁盘上去。
必须要同时设置这两个持久化才行RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue恢复这个 queue 里的数据。
@@ -155,10 +155,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 切换时,数据不会丢失。

View File

@@ -273,7 +273,7 @@ public class TokenBucket {
### spring cloud gateway
- spring cloud gateway 默认使用 redis 进行限流,笔者一般只是修改修改参数属于拿来即用,并没有去从头实现上述那些算法。
- spring cloud gateway 默认使用 redis 进行限流,笔者一般只是修改修改参数属于拿来即用,并没有去从头实现上述那些算法。
```xml
<dependency>
@@ -288,25 +288,25 @@ public class TokenBucket {
```yaml
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: lb://pigx-upms
order: 10000
predicates:
- Path=/admin/**
uri: lb://pigx-upms
order: 10000
predicates:
- Path=/admin/**
filters:
- name: RequestRateLimiter
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶的容积
redis-rate-limiter.burstCapacity: 3 # 流速 每秒
key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶的容积
redis-rate-limiter.burstCapacity: 3 # 流速 每秒
key-resolver: '#{@remoteAddrKeyResolver}' #SPEL表达式去的对应的bean
- StripPrefix=1
- StripPrefix=1
```
```java
@@ -318,7 +318,7 @@ KeyResolver remoteAddrKeyResolver() {
### sentinel
- 通过配置来控制每个 url 的流量
- 通过配置来控制每个 url 的流量
```xml
<dependency>
@@ -329,25 +329,25 @@ KeyResolver remoteAddrKeyResolver() {
```yaml
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8720
datasource:
ds:
nacos:
server-addr: localhost:8848
dataId: spring-cloud-sentinel-nacos
groupId: DEFAULT_GROUP
rule-type: flow
namespace: xxxxxxxx
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8720
datasource:
ds:
nacos:
server-addr: localhost:8848
dataId: spring-cloud-sentinel-nacos
groupId: DEFAULT_GROUP
rule-type: flow
namespace: xxxxxxxx
```
- 配置内容在 nacos 上进行编辑
- 配置内容在 nacos 上进行编辑
```json
[
@@ -363,13 +363,13 @@ spring:
]
```
- resource资源名即限流规则的作用对象。
- limitApp流控针对的调用来源若为 default 则不区分调用来源。
- grade限流阈值类型QPS 或线程数模式0 代表根据并发数量来限流1 代表根据 QPS 来进行流量控制。
- count限流阈值
- strategy判断的根据是资源自身还是根据其它关联资源 (refResource),还是根据链路入口
- controlBehavior流控效果直接拒绝 / 排队等待 / 慢启动模式)
- clusterMode是否为集群模式
- resource资源名即限流规则的作用对象。
- limitApp流控针对的调用来源若为 default 则不区分调用来源。
- grade限流阈值类型QPS 或线程数模式0 代表根据并发数量来限流1 代表根据 QPS 来进行流量控制。
- count限流阈值
- strategy判断的根据是资源自身还是根据其它关联资源 (refResource),还是根据链路入口
- controlBehavior流控效果直接拒绝 / 排队等待 / 慢启动模式)
- clusterMode是否为集群模式
### 总结

View File

@@ -6,8 +6,8 @@
其实聊到这个问题,一般面试官要考察两块:
- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,**大多数人就是平时埋头用,从来不去思考背后的一些东西**。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做?
@@ -17,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 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。

View File

@@ -20,11 +20,11 @@
一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:
- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。
- 新建一个 topicpartition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
- 接着临时征用 10 倍的机器来部署 consumer每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
- 等快速消费完积压数据之后,**得恢复原先部署的架构****重新**用原先的 consumer 机器来消费消息。
- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。
- 新建一个 topicpartition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
- 接着临时征用 10 倍的机器来部署 consumer每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
- 等快速消费完积压数据之后,**得恢复原先部署的架构****重新**用原先的 consumer 机器来消费消息。
### mq 中的消息过期失效了
@@ -78,10 +78,10 @@ public ConsumeConcurrentlyStatus consumeMessage(
举例如下,某条消息的消费过程如下:
- 根据消息从 DB 查询【数据 1】
- 根据消息从 DB 查询【数据 2】
- 复杂的业务计算
- 向 DB 插入【数据 3】
- 向 DB 插入【数据 4】
- 根据消息从 DB 查询【数据 1】
- 根据消息从 DB 查询【数据 2】
- 复杂的业务计算
- 向 DB 插入【数据 3】
- 向 DB 插入【数据 4】
这条消息的消费过程中有 4 次与 DB 的 交互,如果按照每次 5ms 计算,那么总共耗时 20ms假设业务计算耗时 5ms那么总过耗时 25ms所以如果能把 4 次 DB 交互优化为 2 次,那么总耗时就可以优化到 15ms即总体性能提高了 40%。所以应用如果对时延敏感的话,可以把 DB 部署在 SSD 硬盘,相比于 SCSI 磁盘,前者的 RT 会小很多。

View File

@@ -44,7 +44,7 @@ show slave status
一般来说,如果主从延迟较为严重,有以下解决方案:
- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
- 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s并行复制还是没意义。
- 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。
- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
- 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s并行复制还是没意义。
- 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。

View File

@@ -20,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)
@@ -32,9 +32,9 @@
好处:
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
- 只要数据库不死就是说对用户来说2/5 的请求都是可以被处理的。
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
- 只要数据库不死就是说对用户来说2/5 的请求都是可以被处理的。
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
### 缓存穿透(Cache Penetration)
@@ -50,8 +50,8 @@
当然,如果黑客如果每次使用不同的负数 id 来攻击,写空值的方法可能就不奏效了。更为经常的做法是在缓存之前增加布隆过滤器,将数据库中所有可能的数据哈希映射到布隆过滤器中。然后对每个请求进行如下判断:
- 请求数据的 key 不存在于布隆过滤器中,可以确定数据就一定不会存在于数据库中,系统可以立即返回不存在。
- 请求数据的 key 存在于布隆过滤器中,则继续再向缓存中查询。
- 请求数据的 key 不存在于布隆过滤器中,可以确定数据就一定不会存在于数据库中,系统可以立即返回不存在。
- 请求数据的 key 存在于布隆过滤器中,则继续再向缓存中查询。
使用布隆过滤器能够对访问的请求起到了一定的初筛作用,避免了因数据不存在引起的查询压力。
@@ -63,6 +63,6 @@
不同场景下的解决方式可如下:
- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
- 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
- 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。

View File

@@ -18,8 +18,8 @@ Redis cluster主要是针对**海量数据+高并发+高可用**的场景。R
### Redis cluster 介绍
- 自动将数据进行分片,每个 master 上放一部分数据
- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
- 自动将数据进行分片,每个 master 上放一部分数据
- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
在 Redis cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379另外一个就是 加 1w 的端口号,比如 16379。
@@ -43,15 +43,15 @@ 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
Redis-trib.rb add-node
@@ -59,9 +59,9 @@ Redis-trib.rb add-node
其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。
- ping每个节点都会频繁给其它节点发送 ping其中包含自己的状态还有自己维护的集群元数据互相通过 ping 交换元数据。
- pong返回 ping 和 meet包含自己的状态和其它信息也用于信息广播和更新。
- fail某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
- ping每个节点都会频繁给其它节点发送 ping其中包含自己的状态还有自己维护的集群元数据互相通过 ping 交换元数据。
- pong返回 ping 和 meet包含自己的状态和其它信息也用于信息广播和更新。
- fail某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
#### ping 消息深入
@@ -73,9 +73,9 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担
### 分布式寻址算法
- hash 算法(大量缓存重建)
- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
- Redis cluster 的 hash slot 算法
- hash 算法(大量缓存重建)
- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
- Redis cluster 的 hash slot 算法
#### hash 算法

View File

@@ -16,8 +16,8 @@
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,**先更新数据库,然后再删除缓存**。
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,**先更新数据库,然后再删除缓存**。
**为什么是删除缓存,而不是更新缓存?**
@@ -75,7 +75,7 @@ public void set(key, value) {
高并发的场景下,该解决方案要注意的问题:
- 读请求长时阻塞
- 读请求长时阻塞
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
@@ -95,19 +95,19 @@ public void set(key, value) {
经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。
- 读请求并发量过高
- 读请求并发量过高
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
- 多服务实例部署的请求路由
- 多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。
比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。
- 热点商品的路由问题,导致请求的倾斜
- 热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。

View File

@@ -8,8 +8,8 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
其实问这个问题,主要有两个原因:
- 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
- 看看你在实际项目里都怎么玩儿过 Redis。
- 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
- 看看你在实际项目里都怎么玩儿过 Redis。
要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。
@@ -17,11 +17,11 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
Redis 主要有以下几种数据类型:
- Strings
- Hashes
- Lists
- Sets
- Sorted Sets
- Strings
- Hashes
- Lists
- Sets
- Sorted Sets
> Redis 除了这 5 种数据类型之外,还有 Bitmaps、HyperLogLogs、Streams 等。

View File

@@ -8,7 +8,7 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
常见的有两个问题:
- 往 Redis 写入的数据怎么没了?
- 往 Redis 写入的数据怎么没了?
可能有同学会遇到,在生产环境的 Redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 Redis 你就没用对啊。Redis 是缓存,你给当存储了是吧?
@@ -16,7 +16,7 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
那既然内存是有限的,比如 Redis 就只能用 10G你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
- 数据明明过期了,怎么还占用着内存?
- 数据明明过期了,怎么还占用着内存?
这是由 Redis 的过期策略来决定。
@@ -42,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 算法

View File

@@ -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 的数据也丢了。
@@ -60,23 +60,23 @@ slave node 内部有个定时任务,每秒检查是否有新的 master node
### 全量复制
- master 执行 bgsave ,在本地生成一份 rdb 快照文件。
- master node 将 rdb 快照文件发送给 slave node如果 rdb 复制时间超过 60 秒repl-timeout那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB6G 文件,很可能超过 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 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB6G 文件,很可能超过 60s)
- master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
- 如果在复制期间,内存缓冲区持续消耗超过 64MB或者一次性超过 256MB那么停止复制复制失败。
```bash
client-output-buffer-limit slave 256MB 64MB 60
```
- slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中。注意在清空旧数据之前slave node 依然会**基于旧的数据版本**对外提供服务。
- 如果 slave node 开启了 AOF那么会立即执行 BGREWRITEAOF重写 AOF。
- slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中。注意在清空旧数据之前slave node 依然会**基于旧的数据版本**对外提供服务。
- 如果 slave node 开启了 AOF那么会立即执行 BGREWRITEAOF重写 AOF。
### 增量复制
- 如果全量复制过程中master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node默认 backlog 就是 1MB。
- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
- 如果全量复制过程中master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node默认 backlog 就是 1MB。
- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
### heartbeat

View File

@@ -22,8 +22,8 @@ Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了
### Redis 持久化的两种方式
- RDBRDB 持久化机制,是对 Redis 中的数据执行**周期性**的持久化。
- AOFAOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。
- RDBRDB 持久化机制,是对 Redis 中的数据执行**周期性**的持久化。
- AOFAOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。
通过 RDB 或 AOF都可以将 Redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。
@@ -33,24 +33,24 @@ Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了
#### RDB 优缺点
- RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。
- RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis **保持高性能**,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
- 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。
- 如果想要在 Redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 Redis 进程宕机,那么会丢失最近 5 分钟(甚至更长时间)的数据。
- RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
- RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。
- RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis **保持高性能**,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
- 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。
- 如果想要在 Redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 Redis 进程宕机,那么会丢失最近 5 分钟(甚至更长时间)的数据。
- RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
#### AOF 优缺点
- AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 `fsync` 操作,最多丢失 1 秒钟的数据。
- AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
- AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。
- 对于同一份数据来说AOF 日志文件通常比 RDB 数据快照文件更大。
- AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync` ,性能也还是很高的。(如果实时写入,那么 QPS 会大降Redis 性能会大大降低)
- 以前 AOF 发生过 bug就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 `merge` 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。
- AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 `fsync` 操作,最多丢失 1 秒钟的数据。
- AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
- AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。
- 对于同一份数据来说AOF 日志文件通常比 RDB 数据快照文件更大。
- AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync` ,性能也还是很高的。(如果实时写入,那么 QPS 会大降Redis 性能会大大降低)
- 以前 AOF 发生过 bug就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 `merge` 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。
### RDB 和 AOF 到底该如何选择
- 不要仅仅使用 RDB因为那样会导致你丢失很多数据
- 也不要仅仅使用 AOF因为那样有两个问题第一你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快第二RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug
- Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择;用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
- 不要仅仅使用 RDB因为那样会导致你丢失很多数据
- 也不要仅仅使用 AOF因为那样有两个问题第一你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快第二RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug
- Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择;用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

View File

@@ -4,21 +4,21 @@
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。
@@ -63,13 +63,13 @@ sentinel中文名是哨兵。哨兵是 Redis 集群架构中非常重要的
主备切换的过程,可能会导致数据丢失:
- 异步复制导致的数据丢失
- 异步复制导致的数据丢失
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slavemaster 就宕机了,此时这部分数据就丢失了。
![async-replication-data-lose-case](./images/async-replication-data-lose-case.png)
- 脑裂导致的数据丢失
- 脑裂导致的数据丢失
脑裂,也就是说,某个 master 所在机器突然**脱离了正常的网络**,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会**认为** master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候集群里就会有两个 master ,也就是所谓的**脑裂**。
@@ -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,10 +123,10 @@ 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。
@@ -136,9 +136,9 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master超过
接下来会对 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

View File

@@ -30,10 +30,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事
文件事件处理器的结构包含 4 个部分:
- 多个 socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
- 多个 socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket根据 socket 的事件类型交给对应的事件处理器进行处理。
@@ -55,10 +55,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事
### 为啥 Redis 单线程模型也能效率这么高?
- 纯内存操作。
- 核心是基于非阻塞的 IO 多路复用机制。
- C 语言实现一般来说C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
- 纯内存操作。
- 核心是基于非阻塞的 IO 多路复用机制。
- C 语言实现一般来说C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
### Redis 6.0 开始引入多线程

View File

@@ -40,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)
点击超链接,可直接查看缓存相关问题及解决方案。

View File

@@ -1,28 +1,28 @@
## 面试题
- 为什么使用消息队列?
- 消息队列有什么优点和缺点?
- Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
- 为什么使用消息队列?
- 消息队列有什么优点和缺点?
- Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
## 面试官心理分析
其实面试官主要是想看看:
- **第一**,你知不知道你们系统里为什么要用消息队列这个东西?
- **第一**,你知不知道你们系统里为什么要用消息队列这个东西?
不少候选人,说自己项目里用了 Redis、MQ但是其实他并不知道自己为什么要用这个东西。其实说白了就是为了用而用或者是别人设计的架构他从头到尾都没思考过。
不少候选人,说自己项目里用了 Redis、MQ但是其实他并不知道自己为什么要用这个东西。其实说白了就是为了用而用或者是别人设计的架构他从头到尾都没思考过。
没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。
没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。
- **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?
- **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?
你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。
你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。
- **第三**,既然你用了 MQ可能是某一种 MQ那么你当时做没做过调研
- **第三**,既然你用了 MQ可能是某一种 MQ那么你当时做没做过调研
你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ比如 Kafka甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。
你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ比如 Kafka甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。
如果是一个不考虑技术选型的候选人招进了团队leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。
如果是一个不考虑技术选型的候选人招进了团队leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。
## 面试题剖析
@@ -84,19 +84,19 @@
缺点有以下几个:
- 系统可用性降低
- 系统可用性降低
系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)。
系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)。
- 系统复杂度提高
- 系统复杂度提高
硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)?怎么[处理消息丢失的情况](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。
硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](/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)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。
- 一致性问题
- 一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
### Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?