1、分布式系统

1.1 为什么要进行系统拆分?

(1)为什么要进行系统拆分?如何进行系统拆分?拆分后不用dubbo可以吗?dubbo和thrift有什么区别呢?

1.2 分布式服务框架

(1)说一下的dubbo的工作原理?注册中心挂了可以继续通信吗?


(2)dubbo支持哪些序列化协议?说一下hessian的数据结构?PB知道吗?为什么PB的效率是最高的?


(3)dubbo负载均衡策略和高可用策略都有哪些?动态代理策略呢?


(4)dubbo的spi思想是什么?


(5)如何基于dubbo进行服务治理、服务降级、失败重试以及超时重试?


(6)分布式服务接口的幂等性如何设计(比如不能重复扣款)?


(7)分布式服务接口请求的顺序性如何保证?


(8)如何自己设计一个类似dubbo的rpc框架?

1.3 分布式锁

(1)使用redis如何设计分布式锁?使用zk来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

1.4 分布式事务

(1)分布式事务了解吗?你们如何解决分布式事务问题的?TCC如果出现网络连不通怎么办?XA的一致性如何保证?

1.5 分布式会话

(1)集群部署时的分布式session如何实现?

2、高并发架构

2.1 如何设计一个高并发系统?

2.2 消息队列

(1)为什么使用消息队列啊?消息队列有什么优点和缺点啊?kafka、activemq、rabbitmq、rocketmq都有什么优点和缺点啊?

消息队列对系统之间的通信进行管理,减少系统之间的耦合度,为系统通信提供统一的平台处理。

消息队列的优点:解耦、异步、削峰

解耦: 系统之间通信解耦合,因为通信只和消息队列对接。

异步:可以把通信交给消息队列处理后,直接返回,减少等待返回时间,提升用户的体验。

削峰:由消息队列控制通信的阈值

消息队列的缺点:

系统可用性降低

系统严重依赖消息队列,如果消息队列不可用,系统全部不可用。

系统复杂度提高

需要处理各个系统与消息队列的对接

一致性问题

由于系统间的松耦合,所以在处理一致性问题需要更多的处理。

Active MQ、RabbitMQ、RocketMQ、Kafka对比

特性

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 功能,在大数据领域的实时计算以及日志采集被大规模使用

(2)如何保证消息队列的高可用啊?

使用消息队列的集群

(3)如何保证消息不被重复消费啊(如何进行消息队列的幂等性问题)?

1.消息队列提供的幂等性机制   

常见的消息队列如 Kafka、RocketMQ等提供了幂等性机制,能够确保同一条消息被消费多次时只会产生一次影响。在Kafka中,可以通过设置消息的key来实现幂等性。

2.消费者自己维护消费记录   

消费者可以在消费一条消息后,将其在数据库中或者内存中记录下来。在消费下一条消息时,先查询是否已经消费过该消息,如果已经消费过,则不再处理。

3.使用分布式锁   

在消费消息时,可以使用分布式锁来保证同一条消息只会被一个消费者处理。常见的分布式锁实现有 ZooKeeper、Redis 等。

(4)如何保证消息的可靠性传输(如何处理消息丢失的问题)?

持久化存储

在消息发送之前,消息队列需要将消息进行持久化存储,确保消息在遭遇意外情况时也不会丢失。消息队列通常有两种持久化方式:内存存储和磁盘存储。内存存储相对来说速度较快,但是在断电等情况下会导致数据全部丢失;磁盘存储则可以使用文件或数据库等方式,比较稳定可靠,但是速度相对较慢。

消息确认机制

在消息发送完成后,发送方需要接收到接收方的确认消息,才能认为消息发送成功。如果发送方没有接收到确认消息,则需要对消息进行重发,以保证消息的可靠传输。

重试机制

在消息发送过程中,可能会出现网络错误、消息队列服务宕机等问题,导致消息无法及时到达目标。为了解决这些问题,消息队列引入了重试机制,即在一定时间内重复发送消息,直到消息传送成功为止。

幂等性处理

由于消息队列处理消息是异步的,可能会造成消息被消费多次的问题。为此,需要进行幂等性处理,即使同样的消息重复消费也不会影响数据的正确性。

(5)如何保证消息的顺序性?

一、单线程消费来保证消息的顺序性;二、对消息进行编号,消费者处理时根据编号判断顺序。

(6)如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?

一、如何解决消息队列的延时以及过期失效问题?

MQ 中消息失效问题原因:

在 RabbitMQ 中可以设置过期时间,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。RabbitMQ 的死信队列、延迟队列

解决消息失效的方法:

我们可以采取一个方案,就是批量重导,当消息大量积压的时候,我们可以先丢弃掉数据,然后等过了高峰期之后,我们就开始写个临时程序,将丢失的那批数据一点一点的查出来,然后重新放入 MQ 中进行处理,相当于手动发到 MQ 里去再补一次。

二、消息队列满了以后怎么处理?

如果消息积压在 mq 里,很长时间没有处理掉,导致 mq 快写满了,那只能写个临时程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。

然后走上面 “批量重导” 的方案,过了高峰期再补数据,重新放到 MQ 中进行处理。

(7)如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路

用Java语言简单实现MQ消息队列服务_虾王之五的技术博客_51CTO博客


2.3 搜索引擎

(1)es的分布式架构原理能说一下么(es是如何实现分布式的啊)?

elasticsearch 是分布式搜索引擎,底层其实基于 lucene 的。核心思想就是在多台机器上启动多个 es 进程实例,组成了一个 es 集群。
es 中存储数据的基本单位是索引,比如说你现在要在 es 中存储一些订单数据,你就应该在es 中创建一个索引,order_idx,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。index -> type -> mapping -> document -> field。index:mysql 里的一张表。type:没法跟 mysql 里去对比,一个 index 里可以有多个 type,每个 type 的字段都是差不多的,但是有一些略微的差别。
好比说,有一个 index,是订单 index,里面专门是放订单数据的。就好比说你在 mysql 中建表,有些订单是实物商品的订单,就好比说一件衣服,一双鞋子;有些订单是虚拟商品

的订单,就好比说游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字
段可能有略微的一些差别。
所以就会在订单 index 里,建两个 type,一个是实物商品订单 type,一个是虚拟商品订单
type,这两个 type 大部分字段是一样的,少部分字段是不一样的。
很多情况下,一个 index 里可能就一个 type,但是确实如果说是一个 index 里有多个 type 的情况,你可以认为 index 是一个类别的表,具体的每个 type 代表了具体的一个 mysql 中的表,每个 type 有一个 mapping,如果你认为一个 type 是一个具体的一个表,index 代表了多个type 的同属于的一个类型,mapping 就是这个 type 的表结构定义,你在 mysql 中创建一个
表,肯定是要定义表结构的,里面有哪些字段,每个字段是什么类型。。。
mapping 就代表了这个 type 的表结构的定义,定义了这个 type 中每个字段名称,字段是什么类型的,然后还有这个字段的各种配置,实际上你往 index 里的一个 type 里面写的一条数据,叫做一条 document,一条 document就代表了 mysql 中某个表里的一行给,每个 document 有多个 field,每个 field 就代表了这个document 中的一个字段的值
接着你搞一个索引,这个索引可以拆分成多个 shard,每个 shard 存储部分数据。
接着就是这个 shard 的数据实际是有多个备份,就是说每个 shard 都有一个 primary shard,
负责写入数据,但是还有几个 replica shard。primary shard 写入数据之后,会将数据同步到
其他几个 replica shard 上去。通过这个 replica 的方案,每个 shard 的数据都有多个备份,如果某个机器宕机了,没关系啊,还有别的数据副本在别的机器上呢。高可用了吧。
es 集群多个节点,会自动选举一个节点为 master 节点,这个 master 节点其实就是干一些管
理的工作的,比如维护索引元数据拉,负责切换 primary shard 和 replica shard 身份拉,之
类的。
要是 master 节点宕机了,那么会重新选举一个节点为 master 节点。如果是非 master 节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身份转移到其他机器上的 replica shard。急着你要是修复了那个宕机机器,重启了之后,
master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群
恢复正常。
其实上述就是 elasticsearch 作为一个分布式搜索引擎最基本的一个架构设计

(2)es写入数据的工作原理是什么啊?es查询数据的工作原理是什么啊?底层的lucene介绍一下呗?倒排索引了解吗?

(1)es 写数据过程
1)客户端选择一个 node 发送请求过去,这个 node 就是 coordinating node(协调节点)
2 ) coordinating node ,对 document 进行 路由 ,将 请求 转发 给对 应的 node (有 primary
shard)
3)实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node
4)coordinating node,如果发现 primary node 和所有 replica node 都搞定之后,就返回响应
结果给客户端
(2)es 读数据过程
查询,GET 某一条数据,写入了某个 document,这个 document 会自动给你分配一个全局
唯一的 id,doc id,同时也是根据 doc id 进行 hash 路由到对应的 primary shard 上面去。也
可以手动指定 doc id,比如用订单 id,用户 id。
你可以通过 doc id 来查询,会根据 doc id 进行 hash,判断出来当时把 doc id 分配到了哪个
shard 上面去,从那个 shard 去查询
1)客户端发送请求到任意一个 node,成为 coordinate node
2)coordinate node 对 document 进行路由,将请求转发到对应的 node,此时会使用 round-
robin 随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均

3)接收请求的 node 返回 document 给 coordinate node
4)coordinate node 返回 document 给客户端
(3)es 搜索数据过程
es 最强大的是做全文检索,就是比如你有三条数据


java 真好玩儿啊
java 好难学啊
j2ee 特别牛
你根据 java 关键词来搜索,将包含 java 的 document 给搜索出来
es 就会给你返回:java 真好玩儿啊,java 好难学啊
1)客户端发送请求到一个 coordinate node
2)协调节点将搜索请求转发到所有的 shard 对应的 primary shard 或 replica shard 也可以
3)query phase:每个 shard 将自己的搜索结果(其实就是一些 doc id),返回给协调节点,
由协调节点进行数据的合并、排序、分页等操作,产出最终结果
4)fetch phase:接着由协调节点,根据 doc id 去各个节点上拉取实际的 document 数据,
最终返回给客户端

(5)写数据底层原理
1)先写入 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文

2)如果 buffer 快满了,或者到一定时间,就会将 buffer 数据 refresh 到一个新的 segment
file 中,但是此时数据不是直接进入 segment file 的磁盘文件的,而是先进入 os cache 的。
这个过程就是 refresh。
每隔 1 秒钟,es 将 buffer 中的数据写入一个新的 segment file,每秒钟会产生一个新的磁盘
文件,segment file,这个 segment file 中就存储最近 1 秒内 buffer 中写入的数据
但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作咯,每秒创建换一个空的
segment file,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的
segment file 中
操作系统里面,磁盘文件其实都有一个东西,叫做 os cache,操作系统缓存,就是说数据
写入磁盘文件之前,会先进入 os cache,先进入操作系统级别的一个内存缓存中去
只要 buffer 中的数据被 refresh 操作,刷入 os cache 中,就代表这个数据就可以被搜索到了
为什么叫 es 是准实时的?NRT,near real-time,准实时。默认是每隔 1 秒 refresh 一次的,
所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。
可以通过 es 的 restful api 或者 java api,手动执行一次 refresh 操作,就是手动将 buffer 中的
数据刷入 os cache 中,让数据立马就可以被搜索到。


只要数据被输入 os cache 中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在
translog 里面已经持久化到磁盘去一份了
3)只要数据进入 os cache,此时就可以让这个 segment file 的数据对外提供搜索了
4)重复 1~3 步骤,新的数据不断进入 buffer 和 translog,不断将 buffer 数据写入一个又一
个新的 segment file 中去,每次 refresh 完 buffer 清空,translog 保留。随着这个过程推进,
translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 commit 操作。
buffer 中的数据,倒是好,每隔 1 秒就被刷到 os cache 中去,然后这个 buffer 就被清空了。
所以说这个 buffer 的数据始终是可以保持住不会填满 es 进程的内存的。
每 次一 条数 据写 入 buffer ,同 时会 写入 一条 日志 到 translog 日志 文件 中去 ,所 以这 个
translog 日志文件是不断变大的,当 translog 日志文件大到一定程度的时候,就会执行
commit 操作。
5)commit 操作发生第一步,就是将 buffer 中现有数据 refresh 到 os cache 中去,清空 buffer
6)将一个 commit point 写入磁盘文件,里面标识着这个 commit point 对应的所有 segment
file
7)强行将 os cache 中目前所有的数据都 fsync 到磁盘文件中去
translog 日志文件的作用是什么?就是在你执行 commit 操作之前,数据要么是停留在
buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器
死了,内存中的数据就全丢了。
所以需要将数据对应的操作写入一个专门的日志文件,translog 日志文件中,一旦此时机器
宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和
os cache 中去。
commit 操作:1、写 commit point;2、将 os cache 数据 fsync 强刷到磁盘上去;3、清空
translog 日志文件
8)将现有的 translog 清空,然后再次重启启用一个 translog,此时 commit 操作完成。默认
每隔 30 分钟会自动执行一次 commit,但是如果 translog 过大,也会触发 commit。整个
commit 的过程,叫做 flush 操作。我们可以手动执行 flush 操作,就是将所有 os cache 数据
刷到磁盘文件中去。
不叫做 commit 操作,flush 操作。es 中的 flush 操作,就对应着 commit 的全过程。我们也
可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去,记
录一个 commit point,清空 translog 日志文件。


9)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 秒的数据
不在磁盘上,此时如果宕机,会导致 5 秒的数据丢失。
如果你希望一定不能丢失数据的话,你可以设置个参数,官方文档,百度一下。每次写入
一条数据,都是写入 buffer,同时写入磁盘上的 translog,但是这会导致写性能、写入吞吐
量会下降一个数量级。本来一秒钟可以写 2000 条,现在你一秒钟只能写 200 条,都有可能。
10)如果是删除操作,commit 的时候会生成一个.del 文件,里面将某个 doc 标识为 deleted
状态,那么搜索的时候根据.del 文件就知道这个 doc 被删除了
11)如果是更新操作,就是将原来的 doc 标识为 deleted 状态,然后新写入一条数据
12)buffer 每次 refresh 一次,就会产生一个 segment file,所以默认情况下是 1 秒钟一个
segment file,segment file 会越来越多,此时会定期执行 merge
13)每次 merge 的时候,会将多个 segment file 合并成一个,同时这里会将标识为 deleted
的 doc 给物理删除掉,然后将新的 segment file 写入磁盘,这里会写一个 commit point,标
识所有新的 segment file,然后打开 segment file 供搜索使用,同时删除旧的 segment file。
es 里的写流程,有 4 个底层的核心概念,refresh、flush、translog、merge
当 segment file 多到一定程度的时候,es 就会自动触发 merge 操作,将多个 segment file 给
merge 成一个 segment file。

(3)es在数据量很大的情况下(数十亿级别)如何提高查询效率啊?


(4)es生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?

2.4 缓存

(1)在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?


(2)redis和memcached有什么区别?redis的线程模型是什么?为什么单线程的redis比多线程的memcached效率要高得多?


(3)redis都有哪些数据类型?分别在哪些场景下使用比较合适?


(5)redis的过期策略都有哪些?手写一下LRU代码实现?


(6)如何保证Redis高并发、高可用、持久化?redis的主从复制原理能介绍一下么?redis的哨兵原理能介绍一下么?


(7)redis的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?


(8)redis集群模式的工作原理能说一下么?在集群模式下,redis的key是如何寻址的?分布式寻址都有哪些算法?了解一致性hash算法吗?如何动态增加和删除一个节点?


(9)了解什么是redis的雪崩和穿透?redis崩溃之后会怎么样?系统该如何应对这种情况?如何处理redis的穿透?


(10)如何保证缓存与数据库的双写一致性?


(11)redis的并发竞争问题是什么?如何解决这个问题?了解Redis事务的CAS方案吗?


(12)生产环境中的redis是怎么部署的?

2.5 分库分表


(2)为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?


(3)现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?


(4)如何设计可以动态扩容缩容的分库分表方案?


(5)分库分表之后,id主键如何处理?


2.6 读写分离

(1)如何实现mysql的读写分离?MySQL主从复制原理的是啥?如何解决mysql主从同步的延时问题?

3、高可用架构

3.1 如何设计一个高可用系统?

3.2 限流

(1)如何限流?在工作中是怎么做的?说一下具体的实现?

3.3 熔断

(1)如何进行熔断?熔断框架都有哪些?具体实现原理知道吗?

3.4 降级

(1)如何进行降级?