百味皆苦 java后端开发攻城狮

八股文-中间件篇

2019-04-08
百味皆苦

消息中间件

消息中间件怎样保证不丢失消息的?

什么事消息队列?为什么使用mq?使用mq会带来什么问题?使用场景有哪些?

我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。

使用消息队列能为我们的系统带来下面三点好处:
1:通过异步处理提高系统性能(减少响应所需时间)。
2:削峰/限流
3:降低系统耦合性。

带来的问题:
1:系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!
2:系统复杂性提高: 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
3:一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!

最常用的场景就是下单
介绍下JMS和AMQP?

JMS(JAVA Message Service,java 消息服务)是 java 的消息服务,JMS 的客户端之间可以通过 JMS 服务进行异步的消息传输。JMS API 是一个消息服务的标准或者说是规范,允许应用程序组件基于 JavaEE 平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。

JMS 两种消息模型
1:点到点(P2P)模型
使用队列(Queue)作为消息通信载体;满足生产者与消费者模式,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送 100 条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
2:发布/订阅(Pub/Sub)模型
发布订阅模型(Pub/Sub) 使用主题(Topic)作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。

JMS 五种不同的消息正文格式:
StreamMessage -- Java 原始值的数据流
MapMessage--一套名称-值对
TextMessage--一个字符串对象
ObjectMessage--一个序列化的 Java 对象
BytesMessage--一个字节的数据流


AMQP,即 Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 高级消息队列协议(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。RabbitMQ 就是基于 AMQP 协议实现的。

AMQP 为消息定义了线路层(wire-level protocol)的协议,而 JMS 所定义的是 API 规范。在 Java 体系中,多个 client 均可以通过 JMS 进行交互,不需要应用修改代码,但是其对跨平台的支持较差。而 AMQP 天然具有跨平台、跨语言特性。
JMS 支持 TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。
由于 Exchange 提供的路由算法,AMQP 可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。
对比一下常用的消息队列?

吞吐量:万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。

可用性:都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用

时效性:RabbitMQ 基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。

功能支持:除了 Kafka,其他三个功能都较为完备。 Kafka 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准

消息丢失:ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。

ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。

RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做 erlang 源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ 挺好的

Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。
什么是幂等性?

所谓幂等性就是无论多少次操作和第一次的操作结果一样。如果消息被多次消费,很有可能造成数据的不一致。而如果消息不可避免地被消费多次,如果我们开发人员能通过技术手段保证数据的前后一致性,那也是可以接受的
常见的幂等性场景有哪些?

微信支付结果通知场景
微信官方文档上提到微信支付通知结果可能会推送多次,需要开发者自行保证幂等性。第一次我们可以直接修改订单状态(如支付中 -> 支付成功),第二次就根据订单状态来判断,如果不是支付中,则不进行订单处理逻辑。

插入数据库场景
每次插入数据时,先检查下数据库中是否有这条数据的主键 id,如果有,则进行更新操作。

写 Redis 场景
Redis 的 Set 操作天然幂等性,所以不用考虑 Redis 写数据的问题。

其他场景方案
生产者发送每条数据时,增加一个全局唯一 id,类似订单 id。每次消费时,先去 Redis 查下是否有这个 id,如果没有,则进行正常处理消息,且将 id 存到 Redis。如果查到有这个 id,说明之前消费过,则不要进行重复处理这条消息。
不同业务场景,可能会有不同的幂等性方案,大家选择合适的即可,上面的几种方案只是提供常见的解决思路。

消息丢失的解决方案

rabbitMQ消息乱序场景

rabbitMQ消息乱序解决方案

消息的重发,补充策略有哪些?

第一种是立即重试,实现简单适用于瞬时故障,比如网络中断问题;缺点是在高峰期可能会导致消息堆积,可能导致消息丢失和重复处理

第二种是指数退避,在每次重试失败后增加重试的时间间隔,一般是指数增长;优点是避免了高并发,适合处理暂时性故障;缺点是消息可能会延迟处理,需要管理重试次数和时间,复杂度高;

第三种是定时重试,在特定的时间间隔后进行重试;优点是可以控制重试时间,避免高峰,适合处理恢复时间长的场景;缺点是消息延迟

补偿策略一是事务补偿,在执行失败时执行一个补偿操作撤销之前的操作;保证最终一致性;但是会增加系统复杂性

补偿策略二是幂等性补偿,相同的消息可以多次处理而不会影响最终结果;

kafka

介绍下kafka?

Kafka 是一个分布式流式处理平台。

流平台具有三个关键功能:
消息队列:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
容错的持久方式存储记录消息流: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
流式处理平台: 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。

Kafka 主要有两大应用场景:
消息队列 :建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
数据处理: 构建实时的流数据处理程序来转换或处理数据流。
kafka有什么优势?

极致的性能 :基于 Scala 和 Java 语言开发,设计中大量使用了批量处理和异步的思想,最高可以每秒处理千万级别的消息。
生态系统兼容性无可匹敌 :Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域。
kafka的消息模型是什么?

Kafka 采用的就是发布 - 订阅模型。
发布订阅模型(Pub-Sub) 使用主题(Topic) 作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。

在发布 - 订阅模型中,如果只有一个订阅者,那它和队列模型就基本是一样的了。所以说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。
什么是Producer、Consumer、Broker、Topic、Partition?

Kafka 将生产者发布的消息发送到 Topic(主题) 中,需要这些消息的消费者可以订阅这些 Topic(主题)
Kafka 比较重要的几个概念:

Producer(生产者) : 产生消息的一方。
Consumer(消费者) : 消费消息的一方。
Broker(代理) : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。
同时,每个 Broker 中又包含了 Topic 以及 Partition 这两个重要的概念:

Topic(主题) : Producer 将消息发送到特定的主题,Consumer 通过订阅特定的 Topic(主题) 来消费消息。
Partition(分区) : Partition 属于 Topic 的一部分。一个 Topic 可以有多个 Partition ,并且同一 Topic 下的 Partition 可以分布在不同的 Broker 上,这也就表明一个 Topic 可以横跨多个 Broker 。

什么是kafka分区的多副本机制?

分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。

生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。当 leader 副本发生故障时会从 follower 中选举出一个 leader,但是 follower 中如果有和 leader 同步程度达不到要求的参加不了 leader 的竞选。

Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力(负载均衡)。
Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能力,不过也相应的增加了所需要的存储空间。
zookeeper再kafka中的作用是什么?

roker 注册 :在 Zookeeper 上会有一个专门用来进行 Broker 服务器列表记录的节点。每个 Broker 在启动时,都会到 Zookeeper 上进行注册,即到 /brokers/ids 下创建属于自己的节点。每个 Broker 就会将自己的 IP 地址和端口等信息记录到该节点中去

Topic 注册 : 在 Kafka 中,同一个Topic 的消息会被分成多个分区并将其分布在多个 Broker 上,这些分区信息及与 Broker 的对应关系也都是由 Zookeeper 在维护。比如我创建了一个名字为 my-topic 的主题并且它有两个分区,对应到 zookeeper 中会创建这些文件夹:/brokers/topics/my-topic/Partitions/0、/brokers/topics/my-topic/Partitions/1

负载均衡 :上面也说过了 Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力。 对于同一个 Topic 的不同 Partition,Kafka 会尽力将这些 Partition 分布到不同的 Broker 服务器上。当生产者产生消息后也会尽量投递到不同 Broker 的 Partition 里面。当 Consumer 消费的时候,Zookeeper 可以根据当前的 Partition 数量以及 Consumer 数量来实现动态负载均衡。
Kafka 如何保证消息的消费顺序?

Kafka 中 Partition(分区)是真正保存消息的地方,我们发送的消息都被放在了这里。而我们的 Partition(分区) 又存在于 Topic(主题) 这个概念中,并且我们可以给特定 Topic 指定多个 Partition。
每次添加消息到 Partition(分区) 的时候都会采用尾加法。 Kafka 只能为我们保证 Partition(分区) 中的消息有序。
消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。Kafka 通过偏移量(offset)来保证消息在分区内的顺序性。
一种很简单的保证消息消费顺序的方法:1 个 Topic 只对应一个 Partition。这样当然可以解决问题,但是破坏了 Kafka 的设计初衷。
另一种是 Kafka 中发送 1 条消息的时候,可以指定 topic, partition, key,data(数据) 4 个参数。如果你发送消息的时候指定了 Partition 的话,所有消息都会被发送到指定的 Partition。并且,同一个 key 的消息可以保证只发送到同一个 partition,这个我们可以采用表/对象的 id 来作为 key 。
Kafka 如何保证消息不丢失?

生产者丢失消息的情况
生产者(Producer) 调用send方法发送消息之后,消息可能因为网络问题并没有发送过去。所以,我们不能默认在调用send方法发送消息之后消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 send 方法发送消息实际上是异步的操作,我们可以通过 get()方法获取调用结果,但是这样也让它变为了同步操作,但是一般不推荐这么做!可以采用为其添加回调函数的形式
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);
        future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
                ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));


另外这里推荐为 Producer 的retries (重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了


消费者丢失消息的情况
当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。
解决办法也比较粗暴,我们手动关闭自动提交 offset,每次在真正消费完消息之后再自己手动提交 offset 。 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。

Kafka 弄丢了消息
假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失
设置 acks = all
解决办法就是我们设置 acks = all。acks 是 Kafka 生产者(Producer) 很重要的一个参数。
acks 的默认值即为1,代表我们的消息被leader副本接收之后就算被成功发送。当我们配置 acks = all 代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。

设置 replication.factor >= 3
为了保证 leader 副本能有 follower 副本能同步消息,我们一般会为 topic 设置 replication.factor >= 3。这样就可以保证每个 分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。

设置 min.insync.replicas > 1
一般情况下我们还需要设置 min.insync.replicas> 1 ,这样配置代表消息至少要被写入到 2 个副本才算是被成功发送。min.insync.replicas 的默认值为 1 ,在实际生产中应尽量避免默认值 1。

但是,为了保证整个 Kafka 服务的高可用性,你需要确保 replication.factor > min.insync.replicas 。为什么呢?设想一下假如两者相等的话,只要是有一个副本挂掉,整个分区就无法正常工作了。这明显违反高可用性!一般推荐设置成 replication.factor = min.insync.replicas + 1。

设置 unclean.leader.election.enable = false
我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。多个 follower 副本之间的消息同步情况不一样,当我们配置了 unclean.leader.election.enable = false 的话,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。
Kafka 如何保证消息不重复消费?

kafka出现消息重复消费的原因:
服务端侧已经消费的数据没有成功提交 offset(根本原因)。
Kafka 侧 由于服务端处理业务时间长或者网络链接等等原因让 Kafka 认为服务假死,触发了分区 rebalance。

解决方案:
消费消息服务做幂等校验,比如 Redis 的set、MySQL 的主键等天然的幂等功能。这种方法最有效。
将 enable.auto.commit 参数设置为 false,关闭自动提交,开发者在代码中手动提交 offset。那么这里会有个问题:什么时候提交offset合适?
处理完消息再提交:依旧有消息重复消费的风险,和自动提交一样
拉取到消息即提交:会有消息丢失的风险。允许消息延时的场景,一般会采用这种方式。然后,通过定时任务在业务不繁忙(比如凌晨)的时候做数据兜底。

activeMQ

rocketMQ

介绍下rocketMQ

RocketMQ 是一个 队列模型 的消息中间件,具有高性能、高可靠、高实时、分布式 的特点。它是一个采用 Java 语言开发的分布式的消息系统
队列模型
消息中间件的队列模型就真的只是一个队列;“广播” 的概念,也就是说如果我们此时我们需要将一个消息发送给多个消费者(比如此时我需要将信息发送给短信系统和邮件系统),这个时候单个队列即不能满足需求了。

主题模型
在主题模型中,消息的生产者称为 发布者(Publisher) ,消息的消费者称为 订阅者(Subscriber) ,存放消息的容器称为 主题(Topic) 。
其中,发布者将消息发送到指定主题中,订阅者需要 提前订阅主题 才能接受特定主题的消息。

zookeeper

介绍下zookeeper?

ZooKeeper 是一个开源的分布式协调服务。它是一个为分布式应用提供一致性 服务的软件,分布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、 负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和 分布式队列等功能。
ZooKeeper 的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性 能高效、功能稳定的系统提供给用户。
Zookeeper 保证了如下分布式一致性特性:
(1)顺序一致性
(2)原子性
(3)单一视图
(4)可靠性
(5)实时性(最终一致性)
客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了 监听器,这个监听器也是由所连接的 zookeeper 机器来处理。对于写请求,这 些请求会同时发给其他 zookeeper 机器并且达成一致后,请求才会返回成功。因此,随着 zookeeper 的集群机器增多,读请求的吞吐会提高但是写请求的吞 吐会下降。
有序性是 zookeeper 中非常重要的一个特性,所有的更新都是全局有序的,每 个更新都有一个唯一的时间戳,这个时间戳称为 zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中 会带有这个zookeeper 最新的 zxid。
zookeeper提供了什么?文件系统是什么?

提供了文件系统和通知机制

Zookeeper 提供一个多层级的节点命名空间(节点称为 znode)。与文件系统 不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存 放数据而目录节点不行。
Zookeeper 为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构, 这种特性使得 Zookeeper 不能用于存放大量的数据,每个节点的存放数据上限 为1M。
Zookeeper 怎么保证主从节点的状态同步?

Zookeeper 的核心是原子广播机制,这个机制保证了各个 server 之间的同步。 实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模 式和广播模式。
恢复模式
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出 来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状 态同步保证了leader 和 server 具有相同的系统状态。
广播模式
一旦 leader 已经和多数的 follower 进行了状态同步后,它就可以开始广播消息 了,即进入广播状态。这时候当一个 server 加入 ZooKeeper 服务中,它会在 恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。ZooKeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的followers 支持。
zookeeper有哪几种数据节点?

(1)PERSISTENT-持久节点
除非手动删除,否则节点一直存在于 Zookeeper 上 
(2)EPHEMERAL-临时节点
临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与 zookeeper 连接断开不一定会话失效),那么这个客户端创建的所有临时节点 都会被移除。
(3)PERSISTENT_SEQUENTIAL-持久顺序节点
基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维 护的自增整型数字。
(4)EPHEMERAL_SEQUENTIAL-临时顺序节点
基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的 自增整型数字。
zookeeper 是如何保证事务的顺序一致性的?

zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了 zxid,zxid 实际上是一个 64 位的数字,高 32 位是epoch( 时期; 纪元; 世; 新时代)用来标识 leader 周期,如果有新的 leader 产生出来,epoch会自增,低 32 位用来递增计数。当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。
zookeeper的选举机制是怎样的?

当 leader 崩溃或者 leader 失去大多数的 follower,这时 zk 进入恢复模式,恢复模式需要重新选举出一个新的 leader,让所有的 Server 都恢复到一个正确的状态。
Zk 的选举算法有两种:一种是基于 basic paxos 实现的,另外一种是基于 fast paxos 算法实现的。系统默认的选举算法为fast paxos。 1、Zookeeper 选主流程(basic paxos)
(1) 选举线程由当前 Server 发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server; 
(2) 选举线程首先向所有 Server 发起一次询问(包括自己); 
(3) 选举线程收到回复后,验证是否是自己发起的询问(验证 zxid 是否一致),然后获取对方的 id(myid),并存储到当前询问对象列表中,最后获取对方提议的 leader 相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
(4) 收到所有 Server 回复以后,就计算出 zxid 最大的那个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server; (5) 线程将当前 zxid 最大的 Server 设置为当前 Server 要推荐的 Leader,如果此时获胜的 Server 获得 n/2 + 1 的 Server 票数,设置当前推荐的 leader 为获胜的 Server,将根据获胜的 Server 相关信息设置自己的状态,否则,继续这个过程,直到 leader 被选举出来。 通过流程分析我们可以得出:要使 Leader 获得多数Server 的支持,则 Server总数必须是奇数 2n+1,且存活的 Server 的数目不得少于 n+1. 每个 Server 启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的 server 还会从磁盘快照中恢复数据和会话信息,zk 会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。

dubbo

dubbo的分层是什么?
从大的范围来说,dubbo分为三层,business业务逻辑层由我们自己来提供接口和实现还有一些配置信息,RPC层就是真正的RPC调用的核心层,封装整个RPC的调用过程、负载均衡、集群容错、代理,remoting则是对网络传输协议和数据转换的封装。
划分到更细的层面,就是图中的10层模式,整个分层依赖由上至下,除开business业务逻辑之外,其他的几层都是SPI机制。

dubbo的工作原理是什么?工作流程是什么?


1:服务启动的时候,provider和consumer根据配置信息,连接到注册中心register注册自己,包括自己的地址(ip+port),分别向注册中心注册和订阅服务

2:register根据服务订阅关系,返回provider列表信息到consumer,同时consumer会把provider信息缓存到本地。如果信息有变更,consumer会收到来自register的推送

3:consumer生成代理对象,通过动态代理来拦截方法的执行,同时根据配置选择负载均衡策略,通讯协议策略,请求封装序列化,网络通讯框架策略;选择一台provider,同时定时向monitor记录接口的调用次数和时间信息

4:拿到代理对象之后,consumer通过代理对象发起接口调用

5:provider收到请求后对数据进行反序列化,然后通过代理调用具体的接口实现

为什么要通过代理对象通信?

主要是为了实现接口的透明代理,封装调用细节,让用户可以像调用本地方法一样调用远程方法,同时还可以通过代理实现一些其他的策略,比如:

1、调用的负载均衡策略

2、调用失败、超时、降级和容错机制

3、做一些过滤操作,比如加入缓存、mock数据

4、接口调用数据统计
讲一下Dubbo的底层网络通讯机制原理?

Dubbo 底层网络通信框架是Netty nio,核心优势是异步非阻塞:通过事件驱动模型处理高并发请求。高性能:零拷贝、内存池优化减少 GC 压力。可扩展性:ChannelHandler 链式处理支持自定义协议。

netty的线程模型是主从 Reactor 多线程模型
BossGroup(主 Reactor):线程数:通常为 1;职责:监听并接收客户端连接,将新连接注册到 WorkerGroup。
WorkerGroup(从 Reactor):线程数:默认 CPU 核数 * 2;职责:处理 I/O 读写(Channel.read())、编解码等网络操作。
业务线程池(Dubbo Dispatcher):职责:执行 Dubbo 服务接口的业务逻辑,避免阻塞 I/O 线程。

事件处理流程是:
1:连接建立:BossGroup 处理 OP_ACCEPT 事件,分配 Channel 到 WorkerGroup。
2:I/O 就绪:WorkerGroup 处理 OP_READ/OP_WRITE 事件。
3:协议解析:解码器(如 LengthFieldBasedFrameDecoder)处理粘包/拆包。
4:业务逻辑:将请求派发至 Dubbo 业务线程池执行。
5:响应返回:业务线程处理完成后,通过 I/O 线程写回响应。

netty怎样进行网络通讯
阐述一下服务暴露的流程?

1:在容器启动的时候,通过ServiceConfig解析标签,创建dubbo标签解析器来解析dubbo的标签,容器创建完成之后,触发ContextRefreshEvent事件回调开始暴露服务
2:通过ProxyFactory获取到invoker,invoker包含了需要执行的方法的对象信息和具体的URL地址
3:再通过DubboProtocol的实现把包装后的invoker转换成exporter,然后启动服务器server,监听端口
4:最后RegistryProtocol保存URL地址和invoker的映射关系,同时注册到服务中心

阐述一下服务引用的流程?

服务暴露之后,客户端就要引用服务,然后才是调用的过程。

1:首先客户端根据配置文件信息从注册中心订阅服务

2:之后DubboProtocol根据订阅的得到provider地址和接口信息连接到服务端server,开启客户端client,然后创建invoker

3:invoker创建完成之后,通过invoker为服务接口生成代理对象,这个代理对象用于远程调用provider,服务的引用就完成了


负载均衡策略有哪些?

1:加权随机:假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上就可以了。

2:最小活跃数:每个服务提供者对应一个活跃数 active,初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求。

3:一致性hash:通过hash算法,把provider的invoke和随机节点生成hash,并将这个 hash 投射到 [0, 2^32 - 1] 的圆环上,查询的时候根据key进行md5然后进行hash,得到第一个节点的值大于等于当前hash的invoker。

4:加权轮询:比如服务器 A、B、C 权重比为 5:2:1,那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。
集群容错方式有哪些?

1:Failover Cluster失败自动切换:dubbo的默认容错方案,当调用失败时自动切换到其他可用的节点,具体的重试次数和间隔时间可用通过引用服务的时候配置,默认重试次数为1也就是只调用一次。

2:Failback Cluster快速失败:在调用失败,记录日志和调用信息,然后返回空结果给consumer,并且通过定时任务每隔5秒对失败的调用进行重试

3:Failfast Cluster失败自动恢复:只会调用一次,失败后立刻抛出异常

4:Failsafe Cluster失败安全:调用出现异常,记录日志不抛出,返回空结果

5:Forking Cluster并行调用多个服务提供者:通过线程池创建多个线程,并发调用多个provider,结果保存到阻塞队列,只要有一个provider成功返回了结果,就会立刻返回结果

6:Broadcast Cluster广播模式:逐个调用每个provider,如果其中一台报错,在循环调用结束后,抛出异常。
自定义一个RPC框架怎样设计?

1:首先需要一个服务注册中心,这样consumer和provider才能去注册和订阅服务
2:需要负载均衡的机制来决定consumer如何调用客户端,这其中还当然要包含容错和重试的机制
3:需要通信协议和工具框架,比如通过http或者rmi的协议通信,然后再根据协议选择使用什么框架和工具来进行通信,当然,数据的传输序列化要考虑
4:除了基本的要素之外,像一些监控、配置管理页面、日志是额外的优化考虑因素。

ES

什么是ELK?
E:Elasticsearch


L:Logstash
Logstash 是 Elastic Stack 的核心产品之一,可用来对数据进行聚合和处理,并将数据发送到 Elasticsearch。Logstash 是一个开源的服务器端数据处理管道,允许您在将数据索引到 Elasticsearch 之前同时从多个来源采集数据,并对数据进行充实和转换。

K:Kibana
Kibana 是一款适用于 Elasticsearch 的数据可视化和管理工具,可以提供实时的直方图、线性图等。
ES索引是什么?

Elasticsearch 索引指相互关联的文档集合。Elasticsearch 会以 JSON 文档的形式存储数据。每个文档都会在一组键 ( 字段或属性的名称 ) 和它们对应的值 ( 字符串、数字、布尔值、日期、数值组、地理位置或其他类型的数据 ) 之间建立联系。

Elasticsearch 使用的是一种名为倒排索引的数据结构,这一结构的设计可以允许十分快速地进行全文本搜索。倒排索引会列出在所有文档中出现的每个特有词汇,并且可以找到包含每个词汇的全部文档。

在索引过程中,Elasticsearch 会存储文档并构建倒排索引,这样用户便可以近实时地对文档数据进行搜索。索引过程是在索引 API 中启动的,通过此 API 您既可向特定索引中添加 JSON 文档,也可更改特定索引中的 JSON 文档。
什么是分词?倒排索引的原理是什么?

分词是将输入的文本数据切分成一个个独立的词语或词项的过程。Elasticsearch 使用分词器(Analyzer)来处理文本数据,以便更好地进行索引和搜索

分词器可以切分文本,去除停顿词,小写化,词干提取
分词器有标准分词器,中文分词器,自定义分词器

倒排索引是 Elasticsearch 中用于快速检索文档的主要数据结构。它的工作原理可以简单理解为将文档中的词项映射到包含该词项的文档列表

倒排索引的构建过程
1:文档分词处理
2:构建索引,为每个词创建一个列表,记录包含该词项的所有文档ID,例如
文档 1:内容 "Elasticsearch 是一个搜索引擎"
文档 2:内容 "搜索引擎的工作原理"
文档 3:内容 "Elasticsearch 提供了强大的搜索功能"
经过分词后,倒排索引可能是:
"Elasticsearch" -> [1, 3]
"是" -> [1]
"一个" -> [1]
"搜索" -> [1, 2]
"引擎" -> [1, 2]
"提供" -> [3]
"了" -> [3]
"强大" -> [3]
"的" -> [2]
"工作" -> [2]
"原理" -> [2]

查询过程:
当用户发起搜索请求时,Elasticsearch 会首先对查询的文本进行分词。
然后,它会查找倒排索引,找到包含查询词项的所有文档 ID。
最后,系统会根据相关性评分等因素返回匹配的文档。

什么是ES的分段存储思想?什么是段合并策略?

Lucene 是著名的搜索开源软件,ElasticSearch 和 Solr 底层用的都是它。
分段存储是 Lucene 的思想。
文档有个很小的改动,整个索引需要重新建立,速度慢,成本高,为了提高速度,定期更新那么时
效性就差。
现在一个索引文件,拆分为多个子文件,每个子文件是段。修改的数据不影响的段不必做处理。
每次新增数据就会新增加一个段,时间久了,一个文档对应的段非常多。段多了,也就影响检索性
能了。
检索过程:
1. 查询所有短中满足条件的数据
2. 对每个段的结果集合并
所以,定期的对段进行合理是很必要的。
策略:将段按大小排列分组,大到一定程度的不参与合并。小的组内合并。整体维持在一个合理的
大小范围。
什么是文本相似度TF-IDF?

TF-IDF = TF / IDF
简单地说,就是你检索一个词,匹配出来的文章,网页太多了。比如 1000 个,这些内容再该怎么呈现,哪些在前面哪些在后面。这需要也有个对匹配度的评分。
TF = Term Frequency 词频,一个词在这个文档中出现的频率。值越大,说明这文档越匹配,正向指标。
IDF = Inverse Document Frequency 反向文档频率,简单点说就是一个词在所有文档中都出现,那么这个词不重要。比如“的、了、我、好”这些词所有文档都出现,对检索毫无帮助。反向指标
讲一下写索引逻辑

集群 = 主分片 + 副本分片
写索引只能写主分片,然后主分片同步到副本分片上。但主分片不是固定的,可能网络原因,之前还是 Node1 是主分片,后来就变成了 Node2 经过选举成了主分片了。

客户端如何知道哪个是主分片呢? 看下面过程。
1. 客户端向某个节点 NodeX 发送写请求
2. NodeX 通过文档信息,请求会转发到主分片的节点上
3. 主分片处理完,通知到副本分片同步数据,向 Nodex 发送成功信息。
4. Nodex 将处理结果返回给客户端。
在集群中搜索数据的过程?

1. 客户端向集群发送请求,集群随机选择一个 NodeX 处理这次请求。
2. Nodex 先计算文档在哪个主分片上,比如是主分片 A,它有三个副本 A1,A2,A3。那么请求会轮询三个副本中的一个完成请求。
3. 如果无法确认分片,比如检索的不是一个文档,就遍历所有分片。

一个节点的存储量是有限的,于是有了分片的概念。但是分片可能有丢失,于是有了副本的概念。
比如:
ES 集群有 3 个分片,分片 A、分片 B、分片 C,那么分片 A + 分片 B + 分片 C = 所有数据,每个分片只有大概 1/3。分片 A 又有副本 A1 A2 A3,数据都是一样的。
讲一下深翻页问题和解决方案?

深翻页(Deep Pagination)问题指的是在执行分页查询时,当页数较大时,性能会显著下降。这是因为 Elasticsearch 在处理深层分页时需要遍历大量的文档,导致查询速度变慢,资源消耗增加。
性能开销:当请求较高页码(例如第 1000 页)时,Elasticsearch 需要从索引中读取并跳过之前的文档。这意味着它必须扫描和排序所有之前的文档,直到到达请求的页码。
内存消耗:大量的文档需要被加载到内存中以进行排序和跳过,这可能导致内存使用过高,影响集群的整体性能。
响应时间:随着页码的增加,查询的响应时间可能会显著延长,给用户体验带来负面影响。

1. Search After
使用 search_after 参数进行基于游标的分页。用户需要在每次请求中提供上一页最后一个文档的排序值,而不是直接请求页码。避免了深层分页的性能开销,适用于需要频繁翻页的场景。

滚动搜索(Scroll API)
使用 Scroll API 进行大数据集的遍历,适合需要处理大量数据的场景。提供了一种高效的方式来获取大量文档,而不会受到深翻页的性能影响。
查询优化方式有哪些?

设计阶段调优
(1)根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引;举例:设计阶段定义:blog 索引的模板格式为:blog_index_时间戳的形式,每天递增数据。
(2)使用别名进行索引管理;
(3)每天凌晨定时对索引做 force_merge 操作,以释放空间;
(4)采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储;
(5)采取 curator 进行索引的生命周期管理;
(6)仅针对需要分词的字段,合理的设置分词器;
(7)Mapping 阶段充分结合各个字段的属性,是否需要检索、是否需要存储等

写入调优
(1)写入前副本数设置为 0;
(2)写入前关闭 refresh_interval 设置为-1,禁用刷新机制;
(3)写入过程中:采取 bulk 批量写入;
(4)写入后恢复副本数和刷新间隔;
(5)尽量使用自动生成的 id。

查询调优
(1)禁用 wildcard;
(2)禁用批量 terms(成百上千的场景);
(3)充分利用倒排索引机制,能 keyword 类型尽量 keyword;
(4)数据量大时候,可以先基于时间敲定索引再检索;
(5)设置合理的路由机制。
如何实现master选举的?

(1)只有候选主节点(master:true)的节点才能成为主节点。
(2)最小主节点数(min_master_nodes)的目的是防止脑裂。

第一步:确认候选主节点数达标
第二步:比较:先判定是否具备 master 资格,具备候选主节点资格的优先返回;若两节点都为候选主节点,则 id 小的值会主节点。注意这里的 id 为 string 类型。

Similar Posts

上一篇 八股文-web

Comments