mirror of
https://github.com/doocs/advanced-java.git
synced 2025-01-29 06:40:29 +08:00
docs(project): update config, fix typo
Add .gitignore and .gitattribute Fix typo Update offer page
This commit is contained in:
parent
488cb742ed
commit
a3ab26dca2
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
* text=auto
|
||||
*.js linguist-language=java
|
||||
*.css linguist-language=java
|
||||
*.html linguist-language=java
|
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/gradle/wrapper/gradle-wrapper.properties
|
||||
##----------Android----------
|
||||
# build
|
||||
*.apk
|
||||
*.ap_
|
||||
*.dex
|
||||
*.class
|
||||
bin/
|
||||
gen/
|
||||
build/
|
||||
|
||||
# gradle
|
||||
.gradle/
|
||||
gradle-app.setting
|
||||
!gradle-wrapper.jar
|
||||
build/
|
||||
|
||||
local.properties
|
||||
|
||||
##----------idea----------
|
||||
*.iml
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
##----------Other----------
|
||||
# osx
|
||||
*~
|
||||
.DS_Store
|
||||
gradle.properties
|
||||
|
||||
.vscode
|
@ -31,7 +31,7 @@
|
||||
- 服务分层(避免循环依赖)
|
||||
- 调用链路失败监控和报警
|
||||
- 服务鉴权
|
||||
- 每个服务的可用性的监控(接口调用成功率?几个9?99.99%,99.9%,99%。
|
||||
- 每个服务的可用性的监控(接口调用成功率?几个9?99.99%,99.9%,99%。)
|
||||
|
||||
### 服务降级
|
||||
比如说服务 A调用服务 B,结果服务 B 挂掉了,服务 A 重试几次调用服务 B,还是不行,那么直接降级,走一个备用的逻辑,给用户返回响应。
|
||||
|
@ -14,7 +14,7 @@ es 在数据量很大的情况下(数十亿级别)如何提高查询效率
|
||||
|
||||
![es-search-process](/img/es-search-process.png)
|
||||
|
||||
es 的搜索引擎严重依赖于底层的 `filesystem cache`,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
|
||||
es 的搜索引擎严重依赖于底层的 `filesystem cache`,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file ` 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
|
||||
|
||||
性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1秒、5秒、10秒。但如果是走 `filesystem cache`,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。
|
||||
|
||||
@ -24,7 +24,7 @@ es 的搜索引擎严重依赖于底层的 `filesystem cache`,你如果给 `fi
|
||||
|
||||
根据我们自己的生产环境实践经验,最佳的情况下,是仅仅在 es 中就存少量的数据,就是你要**用来搜索的那些索引**,如果内存留给 `filesystem cache` 的是 100G,那么你就将索引数据控制在 `100G` 以内,这样的话,你的数据几乎全部走内存来搜索,性能非常之高,一般可以在 1 秒以内。
|
||||
|
||||
比如说你现在有一行数据。`id name age ....` 30 个字段。但是你现在搜索,只需要根据 `id name age` 三个字段来搜索。如果你傻乎乎往 es 里写入一行数据所有的字段,就会导致说 `90%` 的数据是不用来搜索的,结果硬是占据了 es 机器上的 `filesystem cache` 的空间,单条数据的数据量越大,就会导致 `filesystem cahce` 能缓存的数据就越少。其实,仅仅写入 es 中要用来检索的**少数几个字段**就可以了,比如说就写入es `id name age` 三个字段,然后你可以把其他的字段数据存在 mysql/hbase 里,我们一般是建议用 `es + hbase` 这么一个架构。
|
||||
比如说你现在有一行数据。`id,name,age ....` 30 个字段。但是你现在搜索,只需要根据 `id,name,age` 三个字段来搜索。如果你傻乎乎往 es 里写入一行数据所有的字段,就会导致说 `90%` 的数据是不用来搜索的,结果硬是占据了 es 机器上的 `filesystem cache` 的空间,单条数据的数据量越大,就会导致 `filesystem cahce` 能缓存的数据就越少。其实,仅仅写入 es 中要用来检索的**少数几个字段**就可以了,比如说就写入es `id,name,age` 三个字段,然后你可以把其他的字段数据存在 mysql/hbase 里,我们一般是建议用 `es + hbase` 这么一个架构。
|
||||
|
||||
hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可以写入海量数据,但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 `doc id`,然后根据 `doc id` 到 hbase 里去查询每个 `doc id` 对应的**完整的数据**,给查出来,再返回给前端。
|
||||
|
||||
|
@ -63,7 +63,7 @@ commit 操作发生第一步,就是将 buffer 中现有数据 `refresh` 到 `o
|
||||
|
||||
translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 `translog` 中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。
|
||||
|
||||
translog 其实也是先写入 os cache 的,**默认每隔5秒**刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会**丢失** 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 `fsync` 到磁盘,但是性能会差很多。
|
||||
translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会**丢失** 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 `fsync` 到磁盘,但是性能会差很多。
|
||||
|
||||
实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的**数据丢失**。
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
## 面试官心理分析
|
||||
如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](/docs/high-concurrency/why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。
|
||||
|
||||
要是你傻乎乎的就干用了一个 MQ,各种问题从来没考虑过,那你就杯具了,面试官对你的印象就是,只会简单使用一些技术,没任何思考,马上对你的印象就不太好了。这样的同学招进来要是做个 20k 薪资以内的普通小弟还凑合,要是做薪资 20k+ 的高工,那就惨了,让你设计个系统,里面肯定一堆坑,出了事故公司受损失,团队一起背锅。
|
||||
要是你傻乎乎的就干用了一个 MQ,各种问题从来没考虑过,那你就杯具了,面试官对你的感觉就是,只会简单使用一些技术,没任何思考,马上对你的印象就不太好了。这样的同学招进来要是做个 20k 薪资以内的普通小弟还凑合,要是做薪资 20k+ 的高工,那就惨了,让你设计个系统,里面肯定一堆坑,出了事故公司受损失,团队一起背锅。
|
||||
|
||||
## 面试题剖析
|
||||
这个问题这么问是很好的,因为不能问你 Kafka 的高可用性怎么保证?ActiveMQ 的高可用性怎么保证?一个面试官要是这么问就显得很没水平,人家可能用的就是 RabbitMQ,没用过 Kafka,你上来问人家 Kafka 干什么?这不是摆明了刁难人么。
|
||||
@ -20,7 +20,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
|
||||
单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的😄,没人生产用单机模式。
|
||||
|
||||
#### 普通集群模式(无高可用性)
|
||||
普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。但是你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
|
||||
普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
|
||||
|
||||
![mq-7](/img/mq-7.png)
|
||||
|
||||
@ -37,18 +37,18 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
|
||||
|
||||
这样的话,好处在于,你任何一个机器宕机了,没事儿,别的机器都可以用。坏处在于,第一,这个性能开销也太大了吧,消息同步所有机器,导致网络带宽压力和消耗很重!第二,这么玩儿,就**没有扩展性可言**了,如果某个 queue 负载很重,你加机器,新增的机器也包含了这个 queue 的所有数据,并没有办法线性扩展你的 queue。
|
||||
|
||||
那么**如何开启这个镜像集群模式**呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是**镜像集群模式的策略**,指定的时候可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
|
||||
那么**如何开启这个镜像集群模式**呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是**镜像集群模式的策略**,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
|
||||
|
||||
### Kafka 的高可用性
|
||||
Kafka 一个最基本的架构认识:多个 broker 组成,每个 broker 是一个节点;你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据。
|
||||
Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker 是一个节点;你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据。
|
||||
|
||||
这就是**天然的分布式消息队列**,就是说一个 topic 的数据,是**分散放在多个机器上的,每个机器就放一部分数据**。
|
||||
|
||||
实际上 RabbmitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性)的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。
|
||||
实际上 RabbmitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。
|
||||
|
||||
Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了,那个 broker 上的 partition 就废了,没法写也没法读,没有什么高可用性可言。
|
||||
|
||||
Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。然后所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。只能读写 leader?很简单,**要是你可以随意读写每个 follower,那么就要 care 数据一致性的问题**,系统复杂度太高,很容易出问题。Kafka 会均匀的将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。
|
||||
Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。只能读写 leader?很简单,**要是你可以随意读写每个 follower,那么就要 care 数据一致性的问题**,系统复杂度太高,很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。
|
||||
|
||||
![mq-9](/img/mq-9.png)
|
||||
|
||||
|
@ -19,18 +19,18 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一
|
||||
|
||||
有这么个场景。数据 1/2/3 依次进入 kafka,kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,分配的 offset 依次是 152/153/154。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。数据 1/2 再次被消费。
|
||||
|
||||
如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说说,你可能就把数据 1/2 在数据库里插入了 2 次,那么数据就错啦。
|
||||
如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说,你可能就把数据 1/2 在数据库里插入了 2 次,那么数据就错啦。
|
||||
|
||||
|
||||
其实重复消费不可怕,可怕的是你没考虑到重复消费之后,**怎么保证幂等性**。
|
||||
|
||||
举个例子吧。假设你有个系统,消费一条往数据库里插入一条,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下已经消费过了,直接扔了,不就保留了一条数据?
|
||||
举个例子吧。假设你有个系统,消费一条消息就往数据库里插入一条数据,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下是否已经消费过了,若是就直接扔了,这样不就保留了一条数据,从而保证了数据的正确性。
|
||||
|
||||
一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性。
|
||||
|
||||
幂等性,通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,**不能出错**。
|
||||
|
||||
那所以第二个问题来了,怎么保证消息队列消费的幂等性?
|
||||
所以第二个问题来了,怎么保证消息队列消费的幂等性?
|
||||
|
||||
其实还是得结合业务来思考,我这里给几个思路:
|
||||
|
||||
|
@ -66,7 +66,7 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
|
||||
### Kafka
|
||||
|
||||
#### 消费端弄丢了数据
|
||||
唯一可能导致消费者弄丢数据的情况,就是说,你那个消费到了这个消息,然后消费者那边**自动提交了 offset**,让 Kafka 以为你已经消费好了这个消息,其实你刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。
|
||||
唯一可能导致消费者弄丢数据的情况,就是说,你消费到了这个消息,然后消费者那边**自动提交了 offset**,让 Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。
|
||||
|
||||
这不是跟 RabbitMQ 差不多吗,大家都知道 Kafka 会自动提交 offset,那么只要**关闭自动提交** offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是**可能会有重复消费**,比如你刚处理完,还没提交offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。
|
||||
|
||||
@ -74,13 +74,13 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
|
||||
|
||||
#### Kafka 弄丢了数据
|
||||
|
||||
这块比较常见的一个场景,就是 Kafka 某个 broker 宕机,然后重新选举 partiton 的 leader。大家想想,要是此时其他的 follower 刚好还有些数据没有同步,结果此时 leader 挂了,然后选举某个 follower 成 leader 之后,他不就少了一些数据?这就丢了一些数据啊。
|
||||
这块比较常见的一个场景,就是 Kafka 某个 broker 宕机,然后重新选举 partition 的 leader。大家想想,要是此时其他的 follower 刚好还有些数据没有同步,结果此时 leader 挂了,然后选举某个 follower 成 leader 之后,不就少了一些数据?这就丢了一些数据啊。
|
||||
|
||||
生产环境也遇到过,我们也是,之前 Kafka 的 leader 机器宕机了,将 follower 切换为 leader 之后,就会发现说这个数据就丢了。
|
||||
|
||||
所以此时一般是要求起码设置如下 4 个参数:
|
||||
|
||||
- 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少2个副本。
|
||||
- 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
|
||||
- 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。
|
||||
- 在 producer 端设置 `acks=all`:这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。
|
||||
- 在 producer 端设置 `retries=MAX`(很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。
|
||||
@ -88,4 +88,4 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
|
||||
我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。
|
||||
|
||||
#### 生产者会不会弄丢数据?
|
||||
如果按照上述的思路设置了 `ack=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
|
||||
如果按照上述的思路设置了 `acks=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
|
@ -12,7 +12,7 @@
|
||||
|
||||
**面试官**:那你说一下你们在项目里是怎么用消息队列的?
|
||||
|
||||
**候选人**:巴拉巴拉,“我们啥啥系统发送个啥啥消息到队列,别的系统来消费啥啥的。比如我们有个订单系统,订单系统会每次下一个新的订单的时候,就会发送时一条消息到`ActiveMQ`里面去,后台有个库存系统负责获取了消息然后更新库存。”
|
||||
**候选人**:巴拉巴拉,“我们啥啥系统发送个啥啥消息到队列,别的系统来消费啥啥的。比如我们有个订单系统,订单系统会每次下一个新的订单的时候,就会发送一条消息到`ActiveMQ`里面去,后台有个库存系统负责获取消息然后更新库存。”
|
||||
|
||||
(部分同学在这里会进入一个误区,就是你仅仅就是知道以及回答你们是怎么用这个消息队列的,用这个消息队列来干了个什么事情?)
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
|
||||
|
||||
## 面试官心理分析
|
||||
你看这问法,其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了;或者消费的极其极其慢。接着就坑爹了,可能你的消息队列集群的磁盘都快写满了,都没人消费,这个时候怎么办?或者是整个这就积压了几个小时,你这个时候怎么办?或者是你积压的时间太长了,导致比如 rabbitmq 设置了消息过期时间后就没了怎么办?
|
||||
你看这问法,其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了;或者消费的速度极其慢。接着就坑爹了,可能你的消息队列集群的磁盘都快写满了,都没人消费,这个时候怎么办?或者是这整个就积压了几个小时,你这个时候怎么办?或者是你积压的时间太长了,导致比如 RabbitMQ 设置了消息过期时间后就没了怎么办?
|
||||
|
||||
所以就这事儿,其实线上挺常见的,一般不出,一出就是大 case。一般常见于,举个例子,消费端每次消费之后要写 mysql,结果 mysql 挂了,消费端 hang 那儿了,不动了。或者是消费端出了个什么岔子,导致消费速度极其慢。
|
||||
所以就这事儿,其实线上挺常见的,一般不出,一出就是大 case。一般常见于,举个例子,消费端每次消费之后要写 mysql,结果 mysql 挂了,消费端 hang 那儿了,不动了;或者是消费端出了个什么岔子,导致消费速度极其慢。
|
||||
|
||||
## 面试题剖析
|
||||
关于这个事儿,我们一个一个来梳理吧,先假设一个场景,我们现在消费端出故障了,然后大量消息在 mq 里积压,现在出事故了,慌了。
|
||||
@ -29,4 +29,4 @@
|
||||
假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
|
||||
|
||||
### mq 都快写满了
|
||||
如果走的方式是消息积压在 mq 里,那么如果你很长时间都没处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。
|
||||
如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。
|
@ -2,7 +2,7 @@
|
||||
了解什么是 redis 的雪崩和穿透?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?
|
||||
|
||||
## 面试官心理分析
|
||||
其实这是问到缓存必问的,因为缓存雪崩和穿透,是缓存最大的两个问题,要么不出现,一旦出现就是致命性的问题。所以面试官一定会问你。
|
||||
其实这是问到缓存必问的,因为缓存雪崩和穿透,是缓存最大的两个问题,要么不出现,一旦出现就是致命性的问题,所以面试官一定会问你。
|
||||
|
||||
## 面试题剖析
|
||||
### 缓存雪崩
|
||||
@ -15,7 +15,7 @@
|
||||
大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。
|
||||
|
||||
缓存雪崩的事前事中事后的解决方案如下。
|
||||
- 事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃。
|
||||
- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
|
||||
- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
|
||||
- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
好处:
|
||||
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
|
||||
- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
|
||||
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,就是可能点击几次刷不出来页面,但是可能多点几次,就可以刷出来一次。
|
||||
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
|
||||
|
||||
### 缓存穿透
|
||||
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
|
||||
|
@ -2,7 +2,7 @@
|
||||
redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗?
|
||||
|
||||
## 面试官心理分析
|
||||
这个也是线上非常常见的一个问题,就是**多客户端同时并发写**一个 key,可能本来应该先到的数据后到了,导致数据版本错了。或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。
|
||||
这个也是线上非常常见的一个问题,就是**多客户端同时并发写**一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。
|
||||
|
||||
而且 redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
|
||||
|
||||
## 面试题剖析
|
||||
一般来说,就是如果你的系统**不是严格要求**“缓存+数据库”必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,即:**读请求和写请求串行化**,串到一个**内存队列**里去,这样就可以保证一定不会出现不一致的情况。
|
||||
一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统**不是严格要求** “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:**读请求和写请求串行化**,串到一个**内存队列**里去。
|
||||
|
||||
串行化之后,就会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
|
||||
串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
|
||||
|
||||
### Cache Aside Pattern
|
||||
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
**为什么是删除缓存,而不是更新缓存?**
|
||||
|
||||
原因很简单,很多时候,复杂点的缓存的场景,缓存不单单是数据库中直接取出来的值。
|
||||
原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。
|
||||
|
||||
比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
|
||||
|
||||
@ -50,22 +50,22 @@
|
||||
|
||||
待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。
|
||||
|
||||
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值
|
||||
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。
|
||||
|
||||
高并发的场景下,该解决方案要注意的问题:
|
||||
- 读请求长时阻塞
|
||||
|
||||
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回
|
||||
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
|
||||
|
||||
该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频繁是怎样的。
|
||||
该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。
|
||||
|
||||
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压100个商品的库存修改操作,每隔库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待10 * 100 = 1000ms = 1s后,才能得到数据,这个时候就导致**读请求的长时阻塞**。
|
||||
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每隔库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。
|
||||
|
||||
一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。
|
||||
|
||||
- 读请求并发量过高
|
||||
|
||||
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值
|
||||
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值。
|
||||
|
||||
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
|
||||
|
||||
|
@ -5,8 +5,8 @@ redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
|
||||
除非是面试官感觉看你简历,是工作 3 年以内的比较初级的同学,可能对技术没有很深入的研究,面试官才会问这类问题。否则,在宝贵的面试时间里,面试官实在不想多问。
|
||||
|
||||
其实问这个问题,主要有两个原因:
|
||||
- 看看你到底有没有全面的了解 redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 kv 操作
|
||||
- 看看你在实际项目里都怎么玩儿过 redis
|
||||
- 看看你到底有没有全面的了解 redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
|
||||
- 看看你在实际项目里都怎么玩儿过 redis。
|
||||
|
||||
要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。
|
||||
|
||||
@ -70,7 +70,7 @@ set 是无序集合,自动去重。
|
||||
|
||||
可以基于 set 玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧。
|
||||
|
||||
把两个大v的粉丝都放在两个 set 中,对两个 set 做交集。
|
||||
把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。
|
||||
```bash
|
||||
#-------操作一个set-------
|
||||
# 添加元素
|
||||
|
@ -7,7 +7,7 @@ redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
|
||||
常见的有两个问题:
|
||||
- 往 redis 写入的数据怎么没了?
|
||||
|
||||
可能有同学会遇到,在生产环境的 redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 redis 你就没用对啊。redis是缓存,你给当存储了是吧?
|
||||
可能有同学会遇到,在生产环境的 redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧?
|
||||
|
||||
啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个 G 的内存,但是可以有几个 T 的硬盘空间。redis 主要是基于内存来进行高性能、高并发的读写操作的。
|
||||
|
||||
@ -36,8 +36,8 @@ redis 过期策略是:**定期删除+惰性删除**。
|
||||
### 内存淘汰机制
|
||||
redis 内存淘汰机制有以下几个:
|
||||
- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
|
||||
- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的key(这个是**最常用**的)
|
||||
- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊。
|
||||
- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)
|
||||
- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
|
||||
- volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)
|
||||
- volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。
|
||||
- volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。
|
||||
|
@ -34,7 +34,7 @@ redis 内部使用文件事件处理器 `file event handler`,这个文件事
|
||||
|
||||
![redis-single-thread-model](/img/redis-single-thread-model.png)
|
||||
|
||||
客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 `AE_READABLE` 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给`连接应答处理器`。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 `AE_READABLE` 事件与命令请求处理器关联。
|
||||
客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 `AE_READABLE` 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给**连接应答处理器**。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 `AE_READABLE` 事件与命令请求处理器关联。
|
||||
|
||||
假设此时客户端发送了一个 `set key value` 请求,此时 redis 中的 socket01 会产生 `AE_READABLE` 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket01 的 `AE_READABLE` 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 `key value` 并在自己内存中完成 `key value` 的设置。操作完成后,它会将 socket01 的 `AE_WRITABLE` 事件与命令回复处理器关联。
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
只要问到缓存,上来第一个问题,肯定是先问问你项目哪里用了缓存?为啥要用?不用行不行?如果用了以后可能会有什么不良的后果?
|
||||
|
||||
这就是看看你对你用缓存这个东西背后有没有思考,如果你就是傻乎乎的瞎用,没法给面试官一个合理的解答。那面试官对你印象肯定不太好,觉得你平时思考太少,就知道干活儿。
|
||||
这就是看看你对缓存这个东西背后有没有思考,如果你就是傻乎乎的瞎用,没法给面试官一个合理的解答,那面试官对你印象肯定不太好,觉得你平时思考太少,就知道干活儿。
|
||||
|
||||
|
||||
## 面试题剖析
|
||||
@ -19,7 +19,7 @@
|
||||
#### 高性能
|
||||
假设这么个场景,你有个操作,一个请求过来,吭哧吭哧你各种乱七八糟操作 mysql,半天查出来一个结果,耗时 600ms。但是这个结果可能接下来几个小时都不会变了,或者变了也可以不用立即反馈给用户。那么此时咋办?
|
||||
|
||||
缓存啊,折腾 600ms 查出来的结果,扔缓存里,一个 key 对应一个 value,下次再有人查,别走 mysql 折腾 600ms 了。直接从缓存里,通过一个 key 查出来一个 value,2ms 搞定。性能提升300倍。
|
||||
缓存啊,折腾 600ms 查出来的结果,扔缓存里,一个 key 对应一个 value,下次再有人查,别走 mysql 折腾 600ms 了,直接从缓存里,通过一个 key 查出来一个 value,2ms 搞定。性能提升 300 倍。
|
||||
|
||||
就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么结果直接放在缓存,后面直接读缓存就好。
|
||||
|
||||
|
BIN
img/get-up-and-study.png
Normal file
BIN
img/get-up-and-study.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 846 KiB |
BIN
img/where-is-my-offer.png
Normal file
BIN
img/where-is-my-offer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 729 KiB |
@ -53,6 +53,7 @@
|
||||
<script src="//unpkg.com/prismjs/components/prism-c.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-cpp.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-json.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-java.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-python.min.js"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/search.js"></script>
|
||||
|
6
offer.md
6
offer.md
@ -1,3 +1,5 @@
|
||||
[![where-is-my-offer](/img/where-is-my-offer.png)](https://doocs.github.io/advanced-java)
|
||||
|
||||
<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=1321616516&auto=1&height=66"></iframe></p>
|
||||
|
||||
```
|
||||
@ -89,4 +91,6 @@ HR
|
||||
燃烧我的卡路里
|
||||
|
||||
我要变成收割机
|
||||
```
|
||||
```
|
||||
|
||||
[![get-up-and-study](/img/get-up-and-study.png)](https://doocs.github.io/advanced-java)
|
Loading…
Reference in New Issue
Block a user