【面试题】Java 高级工程师面试刷题100题(五)

redis 缓存如何回收

回收策略

noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键

如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和 noeviction 差不多了。

选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过 RedisINFO 命令输出以便调优。

一般的经验规则:

  • 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
  • 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
  • 使用volatile-ttl:如果你想要通过创建缓存对象时设置 TTL 值,来决定哪些对象应该被过期。

allkeys-lruvolatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。

为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。

回收进程如何工作

理解回收进程如何工作是非常重要的:

  • 一个客户端运行了新的命令,添加了新的数据。
  • Redi 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。
  • 一个新的命令被执行,等等。
  • 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

RabbitMQ 的架构设计是什么样的

是 AMQP 的实现,相关概念语义

Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输

Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。

Queue:消息的载体,每个消息都会被投到一个或多个队列。

Binding:绑定,它的作用就是把 exchange 和 queue 按照路由规则绑定起来.

Routing Key:路由关键字,exchange 根据这个关键字进行消息投递。

vhost:虚拟主机,一个 broker 里可以有多个 vhost,用作不同用户的权限分离。

Producer:消息生产者,就是投递消息的程序.

Consumer:消息消费者,就是接受消息的程序.

Channel:消息通道,在客户端的每个连接里,可建立多个 channel.

核心概念

在 mq 领域中,producer 将 msg 发送到 queue,然后 consumer 通过消费 queue 完成 P.C 解耦

kafka 是由 producer 决定 msg 发送到那个 queue

rabbitmq 是由 Exchange 决定 msg 应该怎么样发送到目标 queue,这就是 binding 及对应的策略

Exchange

Direct Exchange:直接匹配,通过 Exchange 名称+RountingKey 来发送与接收消息.
Fanout Exchange:广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能收到消息,忽略 Routing Key.
Topic Exchange:主题匹配订阅,这里的主题指的是 RoutingKey,RoutingKey 可以采用通配符,如:*或#,RoutingKey 命名采用.来分隔多个词,只有消息这将队列绑定到该路由器且指定 RoutingKey 符合匹配规则时才能收到消息;
Headers Exchange:消息头订阅,消息发布前,为消息定义一个或多个键值对的消息头,然后消费者接收消息同时需要定义类似的键值对请求头:(如:x-mactch=all 或者 x_match=any),只有请求头与消息头匹配,才能接收消息,忽略 RoutingKey.
默认的 exchange:如果用空字符串去声明一个 exchange,那么系统就会使用”amq.direct”这个 exchange,我们创建一个 queue 时,默认的都会有一个和新建 queue 同名的 routingKey 绑定到这个默认的 exchange 上去

复杂与精简

在众多的 MQ 中间件中,首先学习 Rabbitmq 的时候,就理解他是一个单机的 mq 组件,为了系统的解耦,可以自己在业务层面做 AKF

其在内卷能力做的非常出色,这得益于 AMQP,也就是消息的传递形式、复杂度有 exchange 和 queue 的 binding 实现,这,对于 P.C 有很大的帮助


RabbitMQ 如何确保消息发送和消息接收

消息发送确认

1 ConfirmCallback 方法

ConfirmCallback 是一个回调接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中。

2 ReturnCallback 方法

通过实现 ReturnCallback 接口,启动消息失败返回,此接口是在交换器路由不到队列时触发回调,该方法可以不使用,因为交换器和队列是在代码里绑定的,如果消息成功投递到 Broker 后几乎不存在绑定队列失败,除非你代码写错了。

消息接收确认

RabbitMQ 消息确认机制(ACK)默认是自动确认的,自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,假如你用回滚了也只是保证了数据的一致性,但是消息还是丢了,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。

消息确认模式有:

AcknowledgeMode.NONE:自动确认。
AcknowledgeMode.AUTO:根据情况确认。
AcknowledgeMode.MANUAL:手动确认。
消费者收到消息后,手动调用 Basic.Ack 或 Basic.Nack 或 Basic.Reject 后,RabbitMQ 收到这些消息后,才认为本次投递完成。

Basic.Ack 命令:用于确认当前消息。
Basic.Nack 命令:用于否定当前消息(注意:这是 AMQP 0-9-1 的 RabbitMQ 扩展) 。
Basic.Reject 命令:用于拒绝当前消息。
Nack,Reject 后都有能力要求是否 requeue 消息或者进入死信队列


RabbitMQ 事务消息原理是什么

事务 V.S 确认

确认是对一件事的确认

事务是对批量的确认

增删改查中,事务是对于增删改的保证

发送方事务

开启事务,发送多条数据,事务提交或回滚是原子的,要么都提交,要么都回滚

消费方事务

消费方是读取行为,那么事务体现在哪里呢

rabbitmq 的消费行为会触发 queue 中 msg 的是否删除、是否重新放回队列等行为,类增删改

所以,消费方的 ack 是要手动提交的,且最终确定以事务的提交和回滚决定


RabbitMQ 死信队列、延时队列分别是什么

死信队列

DLX(Dead Letter Exchange),死信交换器

当队列中的消息被拒绝、或者过期会变成死信,死信可以被重新发布到另一个交换器,这个交换器就是 DLX,与 DLX 绑定的队列称为死信队列。
造成死信的原因:

  • 信息被拒绝
  • 信息超时
  • 超过了队列的最大长度

过期消息:

在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。

队列设置:在队列申明的时候使用 x-message-ttl 参数,单位为 毫秒

单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒

延迟队列

延迟队列存储的是延迟消息

延迟消息指的是,当消息被发发布出去之后,并不立即投递给消费者,而是在指定时间之后投递。如:

在订单系统中,订单有 30 秒的付款时间,在订单超时之后在投递给消费者处理超时订单。

rabbitMq 没有直接支持延迟队列,可以通过死信队列实现。

在死信队列中,可以为普通交换器绑定多个消息队列,假设绑定过期时间为 5 分钟,10 分钟和 30 分钟,3 个消息队列,然后为每个消息队列设置 DLX,为每个 DLX 关联一个死信队列。

当消息过期之后,被转存到对应的死信队列中,然后投递给指定的消费者消费。


简述 kafka 架构设计是什么样

语义概念

1 broker
Kafka 集群包含一个或多个服务器,服务器节点称为broker。

broker存储topic的数据。如果某topic有N个partition,集群有N个broker,那么每个broker存储该topic的一个partition。

如果某topic有N个partition,集群有(N+M)个broker,那么其中有N个broker存储该topic的一个partition,剩下的M个broker不存储该topic的partition数据。

如果某topic有N个partition,集群中broker数目少于N个,那么一个broker存储该topic的一个或多个partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致Kafka集群数据不均衡。

2 Topic
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)

类似于数据库的表名

3 Partition
topic中的数据分割为一个或多个partition。每个topic至少有一个partition。每个partition中的数据使用多个segment文件存储。partition中的数据是有序的,不同partition间的数据丢失了数据的顺序。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1。

4 Producer
生产者即数据的发布者,该角色将消息发布到Kafka的topic中。broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中。生产者发送的消息,存储到一个partition中,生产者也可以指定数据存储的partition。

5 Consumer
消费者可以从broker中读取数据。消费者可以消费多个topic中的数据。

6 Consumer Group
每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个CG。topic的消息会复制-给consumer。如果需要实现广播,只要每个consumer有一个独立的CG就可以了。要实现单播只要所有的consumer在同一个CG。用CG还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。

7 Leader
每个partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责数据的读写的partition。

8 Follower
Follower跟随Leader,所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。当Follower与Leader挂掉、卡住或者同步太慢,leader会把这个follower从“in sync replicas”(ISR)列表中删除,重新创建一个Follower。

9 Offset
kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka

KAFKA 天生是分布式的,满足 AKF 的 XYZ 轴特点,扩展性,可靠性,高性能是没得说

而且,kafka 具备自己的特色,比如动态 ISR 集合,是在强一致性,过半一致性之外的另一个实现手段



Kafka 消息丢失的场景有哪些

生产者在生产过程中的消息丢失

broker 在故障后的消息丢失

消费者在消费过程中的消息丢失

ACK 机制

ack 有 3 个可选值,分别是 1,0,-1。

ack=0:生产者在生产过程中的消息丢失

简单来说就是,producer 发送一次就不再发送了,不管是否发送成功。

ack=1:broker 在故障后的消息丢失

简单来说就是,producer 只要收到一个分区副本成功写入的通知就认为推送消息成功了。这里有一个地方需要注意,这个副本必须是 leader 副本。只有 leader 副本成功写入了,producer 才会认为消息发送成功。

注意,ack 的默认值就是 1。这个默认值其实就是吞吐量与可靠性的一个折中方案。生产上我们可以根据实际情况进行调整,比如如果你要追求高吞吐量,那么就要放弃可靠性。

ack=-1:生产侧和存储侧不会丢失数据

简单来说就是,producer 只有收到分区内所有副本的成功写入的通知才认为推送消息成功了。

Offset 机制

kafka 消费者的三种消费语义

at-most-once:最多一次,可能丢数据

at-least-once:最少一次,可能重复消费数据

exact-once message:精确一次


Kafka 是 pull?push?以及优劣势分析

Kafka 最初考虑的问题是,customer 应该从 brokes 拉取消息还是 brokers 将消息推送到 consumer,也就是 pull 还 push。

Kafka 遵循了一种大部分消息系统共同的传统的设计:producer 将消息推送到 broker,consumer 从 broker 拉取消息。

一些消息系统比如 Scribe 和 Apache Flume 采用了 push 模式,将消息推送到下游的 consumer。

这样做有好处也有坏处:由 broker 决定消息推送的速率,对于不同消费速率的 consumer 就不太好处理了。

消息系统都致力于让 consumer 以最大的速率最快速的消费消息,但不幸的是,push 模式下,当 broker 推送的速率远大于 consumer 消费的速率时,consumer 恐怕就要崩溃了。

最终 Kafka 还是选取了传统的 pull 模式。

Pull 模式的另外一个好处是 consumer 可以自主决定是否批量的从 broker 拉取数据。

Push 模式必须在不知道下游 consumer 消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。

如果为了避免 consumer 崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。

Pull 模式下,consumer 就可以根据自己的消费能力去决定这些策略。

Pull 有个缺点是,如果 broker 没有可供消费的消息,将导致 consumer 不断在循环中轮询,直到新消息到达。

为了避免这点,Kafka 有个参数可以让 consumer 阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发


Kafka 中 zk 的作用是什么

Zookeeper 是分布式协调,注意它不是数据库

kafka 中使用了 zookeeper 的分布式锁和分布式配置及统一命名的分布式协调解决方案

在 kafka 的 broker 集群中的 controller 的选择,是通过 zk 的临时节点争抢获得的

brokerID 等如果自增的话也是通过 zk 的节点 version 实现的全局唯一

kafka 中 broker 中的状态数据也是存储在 zk 中,不过这里要注意,zk 不是数据库,所以存储的属于元数据

而,新旧版本变化中,就把曾经的 offset 从 zk 中迁移出了 zk


Kafka 中高性能如何保障

首先,性能的最大瓶颈依然是 IO,这个是不能逾越的鸿沟

虽然,broker 在持久化数据的时候已经最大努力的使用了磁盘的顺序读写

更进一步的性能优化是零拷贝的使用,也就是从磁盘日志到消费者客户端的数据传递,因为 kafka 是 mq,对于 msg 不具备加工处理,所以得以实现

然后就是大多数分布式系统一样,总要做 tradeoff,在速度与可用性/可靠性中挣扎

ACK 的 0,1,-1 级别就是在性能和可靠中权衡


kafka 的 rebalance 机制是什么

消费者分区分配策略

Range 范围分区(默认的)

RoundRobin 轮询分区

Sticky 策略

触发 Rebalance 的时机

Rebalance 的触发条件有 3 个。

  • 组成员个数发生变化。例如有新的 consumer 实例加入该消费组或者离开组。
  • 订阅的 Topic 个数发生变化。
  • 订阅 Topic 的分区数发生变化。

Coordinator 协调过程

消费者如何发现协调者

消费者如何确定分配策略

如果需要再均衡分配策略的影响


zk 的数据模型和节点类型有哪些

ZooKeeper 数据模型

ZooKeeper 的数据模型,在结构上和标准文件系统的非常相似,拥有一个层次的命名空间,都是采用树形层次结构,ZooKeeper 树中的每个节点被称为—Znode。

和文件系统的目录树一样,ZooKeeper 树中的每个节点可以拥有子节点。但也有不同之处:

	Znode兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子Znode。用户对Znode具有增、删、改、查等操作(权限允许的情况下)

	Znode具有原子性操作,读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作

	Znode存储数据大小有限制。ZooKeeper虽然可以关联一些数据,但并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个Znode的数据大小至多1M,当时常规使用中应该远小于此值

	Znode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在ZooKeeper中,路径由Unicode字符串组成,并且有一些限制。字符串"/zookeeper"用以保存管理信息,比如关键配额信息。

节点类型

Znode 有两种,分别为临时节点和永久节点。
节点的类型在创建时即被确定,并且不能改变。
临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然可以也可以手动删除。临时节点不允许拥有子节点。

永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。

Znode 还有一个序列化的特性,如果创建的时候指定的话,该 Znode 的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。它的格式为“%10d”(10 位数字,没有数值的数位用 0 补充,例如“0000000001”)

在 ZooKeeper 中,每个数据节点都是有生命周期的,其生命周期的长短取决于数据节点的节点类型。

1、持久节点(PERSISTENT)

该数据节点别创建后,就会一直存在于 ZooKeeper 服务器上,直到有删除操作来主动删除该节点。

2、持久顺序节点(PERSISTENT_SEQUENTIAL)

持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在 ZooKeeper 中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。

3、临时节点(EPHEMERAL)

临时节点的生命周期和客户端的回话绑定在一起,如果客户端会话失效,那么这个节点就会被自动地清理掉。

ZooKeeper 规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。

4、临时顺序节点(EPHEMERAL_SEQUENTIAL)


Zookeeper watch 机制是什么

ZooKeeper 是用来协调(同步)分布式进程的服务,提供了一个简单高性能的协调内核,用户可以在此之上构建更多复杂的分布式协调功能。

多个分布式进程通过 ZooKeeper 提供的 API 来操作共享的 ZooKeeper 内存数据对象 ZNode 来达成某种一致的行为或结果,这种模式本质上是基于状态共享的并发模型,与 Java 的多线程并发模型一致,他们的线程或进程都是”共享式内存通信“。

Java 没有直接提供某种响应式通知接口来监控某个对象状态的变化,只能要么浪费 CPU 时间毫无响应式的轮询重试,或基于 Java 提供的某种主动通知(Notif)机制(内置队列)来响应状态变化,但这种机制是需要循环阻塞调用。

而 ZooKeeper 实现这些分布式进程的状态(ZNode 的 Data、Children)共享时,基于性能的考虑采用了类似的异步非阻塞的主动通知模式即 Watch 机制,使得分布式进程之间的“共享状态通信”更加实时高效,其实这也是 ZooKeeper 的主要任务决定的—协调。Consul 虽然也实现了 Watch 机制,但它是阻塞的长轮询。

ZooKeeper 的 Watch 特性

  1. Watch 是一次性的,每次都需要重新注册,并且客户端在会话异常结束时不会收到任何通知,而快速重连接时仍不影响接收通知。
  2. Watch 的回调执行都是顺序执行的,并且客户端在没有收到关注数据的变化事件通知之前是不会看到最新的数据,另外需要注意不要在 Watch 回调逻辑中阻塞整个客户端的 Watch 回调
  3. Watch 是轻量级的,WatchEvent 是最小的通信单元,结构上只包含通知状态、事件类型和节点路径。ZooKeeper 服务端只会通知客户端发生了什么,并不会告诉具体内容。

Zookeeper 状态

Disconnected:客户端是断开连接的状态,不能连接服务集合中的任意一个
SyncConnected:客户端是连接状态,连接其中的一个服务
AuthFailed:鉴权失败
ConnectedReadOnly:客户端连接只读的服务器
SaslAuthenticated:SASL 认证
Expired:服务器已经过期了该客户端的 Session

Zookeeper 事件类型

None:无
NodeCreated:节点创建
NodeDeleted:节点删除
NodeDataChanged:节点数据改变
NodeChildrenChanged:子节点改变(添加/删除)

Watcher 使用的注意事项

Watcher 是一次触发器,假如需要持续监听数据变更,需要在每次获取时设置 Watcher
会话过期:当客户端会话过期时,该客户端注册的 Watcher 会失效
事件丢失:在接收通知和注册监视点之间,可能会丢失事件,但 Zookeeper 的状态变更和数据变化,都会记录在状态元数据信息和 ZK 数据节点上,所以能够获取最终一致的 ZK 信息状态
避免 Watcher 过多:服务器会对每一个注册 Watcher 事件的客户端发送通知,通知通过 Socket 连接的方式发送,当 Watcher 过多时,会产生一个尖峰的通知

zk 的命名服务、配置管理、集群管理分别是什么

分布式协调

大于等于一的情况下,才会有协调,在协调的事务进行分类得到一些名词,语义能够接受就可以

命名服务

通过使用有序节点的特性做到协调命名规则

通过 zk 的事务 ID 递增,做到有序行命名规则

通过使用自己点做 map 映射,做到 1:N 的命名映射,比如 DNS

顺序关系、映射关系

配置管理

配置、元数据、状态等语义可以通过 ZK 的节点 1MB 存储,或者通过 zk 的节点目录结构特性存储

并且通过 watch 机制,满足配置变化的全局通知能力

集群管理

通过 zk 的排他性,有序性

满足分布式锁、分布式选主、队列锁

串行化回调调度

分布式调度等

微信公众号

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tellsea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值