拼多多面经
一:看你在项目中用了 Redis,我们先聊聊 Redis 吧,常用的数据结构有哪几
种,在你的项目中用过哪几种,以及在业务中使用的场景。Redis 的 hash 怎
么实现的,rehash 过程讲一下和 JavaHashMap 的 rehash 有什么区别?
Redis cluster 有没有了解过,怎么做到高可用的?
1. 常用的数据结构:
字符串(String),散列/哈希(hash),列表(list),无序集合类型(set),有序集合类型
(sorted set)
2. Redis 的 Hash 实现:
Redis 散列/哈希是键值对的集合。Redis 散列/哈希是字符串字段和字符串值之间的映射,
但字段值只能是字符串,不支持其他类型。因此,他们用于表示对象。应用场景:存储用
户的基本信息,等等。
3. Redis cluster:
Redis 最开始使用主从模式做集群,若 master 宕机需要手动配置 slave 转为 master;后
来为了高可用提出来哨兵模式,该模式下有一个哨兵监视 master 和 slave,若 master 宕
机可自动将 slave 转为 master,但它也有一个问题,就是不能动态扩充;所以在 3.x 提出
cluster 集群模式。
Redis-Cluster 采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他
所有节点连接。
4. 其结构特点:
l 所有的 Redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和
带宽。
l 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。
l 客户端与 Redis 节点直连,不需要中间 proxy 层.客户端不需要连接集群所有节点,连接
集群中任何一个可用节点即可。
l Redis-cluster 把所有的物理节点映射到[0-16383]slot 上(不一定是平均分
配),cluster 负责维护 node<->slot<->value。
l Redis 集群预分好 16384 个桶,当需要在 Redis 集群中放置一个 key-value 时,根
据 CRC16(key) mod 16384 的值,决定将一个 key 放到哪个桶中。
二:Redis 集群和哨兵机制有什么区别?Redis 的持久化机制了解吗?你们在
项目中是怎么做持久化的?遇到过 Redis 的 hotkey 吗?怎么处理的?
1. Redis 集群和哨兵机制有什么区别?
谈到 Redis 服务器的高可用,如何保证备份的机器是原始服务器的完整备份呢?这时候就
需要哨兵和复制。
l 哨兵(Sentinel):可以管理多个 Redis 服务器,它提供了监控,提醒以及自动的故障
转移的功能。
l 复制(Replication):则是负责让一个 Redis 服务器可以配备多个备份的服务器。
l Redis 正是利用这两个功能来保证 Redis 的高可用。
2. Redis 哨兵主要功能
l 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
l 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理
员。
l 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
l 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
3. Redis 哨兵的高可用
原理:当主节点出现故障时,由 Redis Sentinel 自动完成故障发现和转移,并通知应用
方,实现高可用性。这个就是哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下
线。
l 主观下线:一个哨兵节点判定主节点 down 掉是主观下线。
l 客观下线:只有半数哨兵节点都主观判定主节点 down 掉,此时多个哨兵节点交换主
观判定结果,才会判定主节点客观下线。
原理:基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起
投票机制 Raft 算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过
程。
4. Redis 主从复制、哨兵和集群这三个有什么区别?
主从复制是为了数据备份,哨兵是为了高可用,Redis 主服务器挂了哨兵可以切换,集群
则是因为单实例能力有限,搞多个分散压力,简短总结如下:
l 主从模式:备份数据、负载均衡,一个 Master 可以有多个 Slaves。
l sentinel 发现 master 挂了后,就会从 slave 中重新选举一个 master。l cluster 是为了解决单机 Redis 容量有限的问题,将数据按一定的规则分配到多台机
器。
l sentinel 着眼于高可用,Cluster 提高并发量。
1. 主从模式:读写分离,备份,一个 Master 可以有多个 Slaves。
2. 哨兵 sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从 slave 中重新选举
一个主服务器。
3. 集群:为了解决单机 Redis 容量有限的问题,将数据按一定的规则分配到多台机器,内
存/QPS 不受限于单机,可受益于分布式集群高扩展性。
三:Redis 是单线程的吗?单线程为什么还这么快?讲一讲 Redis 的内存模
型?
Redis 内存划分:
Redis 作为内存数据库,在内存中存储的内容主要是数据(键值对);通过前面的叙述可以
知道,除了数据以外,Redis 的其他部分也会占用内存。
Redis 的内存占用主要可以划分为以下几个部分:
1. 数据
作为数据库,数据是最主要的部分;这部分占用的内存会统计在 used_memory 中。
Redis 使用键值对存储数据,其中的值(对象)包括 5 种类型,即字符串、哈希、列表、
集合、有序集合。这 5 种类型是 Redis 对外提供的,实际上,在 Redis 内部,每种类型可
能有 2 种或更多的内部编码实现;此外,Redis 在存储对象时,并不是直接将数据扔进内
存,而是会对对象进行各种包装:如 redisObject、SDS 等;这篇文章后面将重点介绍Redis 中数据存储的细节。
2. 进程本身运行需要的内存
Redis 主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,
在大多数生产环境中与 Redis 数据占用的内存相比可以忽略。这部分内存不是由 jemalloc
分配,因此不会统计在 used_memory 中。
补充说明:除了主进程外,Redis 创建的子进程运行也会占用内存,如 Redis 执行 AOF、
RDB 重写时创建的子进程。当然,这部分内存不属于 Redis 进程,也不会统计在
used_memory 和 used_memory_rss 中。
3. 缓冲内存
缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF 缓冲区等;其中,客户端缓冲存储客
户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF 缓冲区用于在进行
AOF 重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;
这部分内存由 jemalloc 分配,因此会统计在 used_memory 中。
4. 内存碎片
内存碎片是 Redis 在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,
而且数据之间的大小相差很大,可能导致 Redis 释放的空间在物理内存中并没有释放,但
Redis 又无法有效利用,这就形成了内存碎片。内存碎片不会统计在 used_memory 中。
内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器
也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。后面将要说到的 jemalloc 便在控制内存碎片方面做的很好。
如果 Redis 服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为
重启之后,Redis 重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择
合适的内存单元,减小内存碎片。
四:我看你还用了 RabbitMQ,简单说一下 RabbitMQ 的工作原理?如何保
证消息的顺序执行?Kafka 了解吗?和 RabbitMQ 有什么区别?你为啥不用
kafka 来做,当时怎么考虑的?
组成部分说明如下:
l Broker:消息队列服务进程,此进程包括两个部分:Exchange 和 Queue。
l Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行
过虑。
l Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
l Producer:消息生产者,即生产方客户端,生产方客户端将消息发送到 MQ。
l Consumer:消息消费者,即消费方客户端,接收 MQ 转发的消息。
消息发布接收流程:
-----发送消息-----
1. 生产者和 Broker 建立 TCP 连接。
2. 生产者和 Broker 建立通道。
3. 生产者通过通道消息发送给 Broker,由 Exchange 将消息进行转发。
4. Exchange 将消息转发到指定的 Queue(队列)----接收消息-----
1. 消费者和 Broker 建立 TCP 连接。
2. 消费者和 Broker 建立通道。
3. 消费者监听指定的 Queue(队列)。
4. 当有消息到达 Queue 时 Broker 默认将消息推送给消费者。
5. 消费者接收到消息。
五:我看你简历里说熟悉计算机网络,来聊一聊计算机网络吧。了不了解
tcp/udp,简单说下两者的区别?
1. 区别:
l TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不
需要建立连接。
l TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错、不丢失、不重
复,且按序到达,UDP 尽最大努力交付,即不保证可靠交付。
l TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文
的
l UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很
有用,如 IP 电话,实时视频会议等)。
l 每一条 TCP 连接只能是点到点的,UDP 支持一对一、一对多、多对一和多对多的交
互通信。
l TCP 首部开销 20 字节;UDP 的首部开销小,只有 8 个字节。
l TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道。六:两次握手为什么不可以?为什么要四次挥手?
采用三次握手是为了防止失效的连接请求报文段突然又传送到主机 B,因而产生错误。失
效的连接请求报文段是指:主机 A 发出的连接请求没有收到主机 B 的确认,于是经过一段
时间后,主机 A 又重新向主机 B 发送连接请求,且建立成功,顺序完成数据传输。考虑这
样一种特殊情况,主机 A 第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟
达到主机 B,主机 B 以为是主机 A 又发起的新连接,于是主机 B 同意连接,并向主机 A 发
回确认,但是此时主机 A 根本不会理会,主机 B 就一直在等待主机 A 发送数据,导致主机
B 的资源浪费。
TCP 关闭链接四次握手原因在于 TCP 链接是全双工通道,需要双向关闭。client 向 server
发送关闭请求,表示 client 不再发送数据,server 响应。此时 server 端仍然可以向 client
发送数据,待 server 端发送数据结束后,就向 client 发送关闭请求,然后 client 确认。
七:TCP 怎么保证有序传输的?
l 应用数据被分割成 TCP 认为最适合发送的数据块。
l 超时重传:当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文
段。如果不能及时收到一个确认,将重发这个报文段。
l TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用
层
l 校验和:TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测
数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段
和不确认收到此报文段。l TCP 的接收端会丢弃重复的数据。
l 流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端
发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据,能提示发送
方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协
议。
l 拥塞控制:当网络拥塞时,减少数据的发送。
八:time_wait 状态,这个状态出现在什么地方,有什么用?
1. 为实现 TCP 全双工连接的可靠释放
当服务器先关闭连接,如果不在一定时间内维护一个这样的 TIME_WAIT 状态,那么当被动关
闭的一方的 FIN 到达时,服务器的 TCP 传输层会用 RST 包响应对方,这样被对方认为是
有错误发生,事实上这只是正常的关闭连接工程,并没有异常。
2. 为使过期的数据包在网络因过期而消失
在这条连接上,客户端发送了数据给服务器,但是在服务器没有收到数据的时候服务器就
断开了连接。
现在数据到了,服务器无法识别这是新连接还是上一条连接要传输的数据,一个处理不当
就会导致诡异的情况发生。
九:HTTP 与 HTTPS 有啥区别?HTTPS 是怎么做到安全的?
HTTPS 安全的原因:
HTTPS 把 HTTP 消息进行加密之后再传送,这样就算坏人拦截到了,得到消息之后也看不懂,这样就做到了安全,具体来说,HTTPS 是通过对称加密和非对称加密和 hash 算法共
同作用,来在性能和安全性上达到一个平衡,加密是会影响性能的,尤其是非对称加密,
因为它的算法比较复杂,那么加密了就安全了吗?不是的,HTTPS 除了对消息进行了加密
以外还会对通信的对象进行身份验证。
HTTPS 并不是一种新的协议,而是使用了一种叫做 TLS(Transport layer secure)的安
全层,这个安全层提供了数据加密的支持,让 HTTP 消息运行在这个安全层上,就达到了
安全,而运行在这个安全层上的 HTTP 就叫做 HTTPS。
十、还有点时间,写个题吧
leetcode406. 根据身高重建队列
假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中 h 是这个
人的身高,k 是排在这个人前面且身高大于或等于 h 的人数。 编写一个算法来重建这个队
列。
注意:
总人数少于 1100 人。
示例
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]思路:
第一步排序:
people:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
排序后:
[[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]
然后从数组 people 第一个元素开始,放入到数组 result 中,放入的位置就是离 result 开
始位置偏移了元素第二个数字后的位置。如下:
1. people: [7,0]
插入到离开始位置偏移了 0 个距离的位置。
result: [[7,0]]
2. people: [7,1]
插入到离开始位置偏移了 1 个距离的位置,即插入到[7,0]的后面。
result: [[7,0], [7,1]]
3. people: [6,1]
插入到离开始位置偏移了 1 个距离的位置,即插入到[7,0]的后面。
result: [[7,0], [6,1], [7,1]]
4. people: [5,0]
插入到离开始位置偏移了 0 个距离的位置,即插入到[7,0]的前面。result: [[5,0], [7,0], [6,1], [7,1]]
5. people: [5,2]
插入到离开始位置偏移了 2 个距离的位置,即插入到[7,0]的后面。
result: [[5,0], [7,0], [5,2], [6,1], [7,1]]
6. people: [4,4]
插入到离开始位置偏移了 4 个距离的位置,即插入到[6,1]的后面。
result: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
这种算法体现了元素第二个数字与其插入位置的关系,所以通过简单的一个 for 循环就可
快手面经
1. Spring 原理,Spring IOC、AOP
这个问题最好可以多说一点,比如对于 IOC,不妨把 Bean 如何加载、如何初始化以及如
何注册到 IOC 容器中的详细过程说一下,涉及 BeanDefinition、BeanFactory 也深入细
节聊一下。
2. 一个请求过来在 Spring 中发生了哪些事情?
这个问题不妨把一个请求过来 在 TCP 层面上建立连接、操作系统如何处理连接、Web 容
器器接收到连接对象后做了哪些事情、Spring 如何对接收到的请求进⾏处理都说一下,
当然最终还是 落在 Spring 容器内部如何处理一个请求,这个过程一定要说清楚,需要体
现细节。在说前面的内容的时候,可以放心面试官不会打断你。
3. 手写一个单例
这个基本上大多数公司都会考察的。要写一个 基于懒汉式的 双重检测的单例。单例有三
个比较关键的点:
l 私有构造方法,避免外部 new 出对象;
l 保证唯一性;
l 提供一个全局访问点。
另外懒汉式双重检测的实现方式有三点需要注意的地方:
l 全局访问点必须是静态的,外界使用可以通过类直接调用;l 在进入锁之后还需要校验;
l 保存单例例对象的私有变量一定要用 volatile 修饰,这个地方 可以多说一些,比如
volatile 防止指令重排序,保证内存可见性(JVM 层面 和 CPU 层面 可以分别说)。
volatile 这个地方能说的东西还是很多的,基本上可以与面试官再聊二十分钟了。
4. HashMap
对于 HashMap 其实一般高级岗位及以上不再会问这个东西了,一旦问了,肯定不是让
你只说一下数组+链表的。对于它的实现,不同版本实现方式不一样。
在 jdk1.8 之后,HashMap 除了数组+链表之外,引入了红黑树。那么好了,你需要说明
对于引入了红黑树的 HashMap 如何 put 一个元素,以及链表是在何时转化为红黑树
的。比如首先需要知道这个元素落在哪一个数组⾥,获取 hashcode 后并不是对数组长度
取余来确定的,而是高低位异或求与来得到的。这个地方首先得知道 异或、与是做什么样
的运算的,然后说一下在 HashMap 中的实现,比如 hashcode 无符号右移 16 位后和原
hashcode 做异或运算,这相当于把 hashcode 的高 16 位拿过来 和 hashcode 的 低
16 位 做异或运算,因为无符号右移后 前面高 16 位都补零,这就是前面说的 "高低位异
或“,进而是 ”求与“,和谁求与呢,和 数组长度减 1 求与。
说到这⾥起码能够证明你是看过源码的,接下来说说你的思考。
比如 我们知道对于 HashMap 初始化容量决定了数组大小,一般我们对于数组这个初始
容量的设置是有规律的,它应该是 2^n 。这个初始容量的设置影响了 HashMap 的效
率,那又涉及到影响 HashMap 效率的主要因素,比如初始容量和负载因子。当已用数组达到容量与负载因子的乘积之后会进行一个 rehash 的过程,这个地方涉及到的 如何
rehash 及各种算法如果有时间也是可以说的,没有时间不说也没有关系。回到刚才说的
2^n, 可以说说它为什么是 2^n。当我们说什么东西为什么是这样的时候,我们一般从两
个角度考虑,一个是这样做有什么好处,另一个是不这样做有什么坏处。我们刚才说到”
求与“这个过程,如果不是 2^n, 会导致较多的哈希碰撞(具体原因 可以自己分析一下 或
者百度一下),这个会影响 HashMap 的效率。
说完上面这些,既表明你看过源码,又表明你有自己的思考了,当然也可以进一步说说它
是在什么条件下以及 如何进行扩容的(如果时间允许,并且面试官也有耐心继续听下
去)。对于 put 操作,这才只是第一步,找到数组的位置,接下来 要看这个位置也没有元
素,如果没有,直接放进去就可以,如果有,要看怎么放进去,jdk1.8 中 对于 HashMap
的实现中,是基于 Node(链表节点) 和 TreeNode(红黑树节点) 的,当然它们继承了
Entry。那么 如果数组当前位置已经有了元素,就得知道这个元素 是 链表的节点还是红
黑树的节点,以便便 进一步确认接下来要 put 的元素 是以链表的方式插入还是以红黑树
的方式插入,这个地方 在源码中 进⾏了一个类型的判断,如果是链表的节点,就以链表
的方式把要 put 的节点插入到 next 为 null 的节点上,如果是红黑树的节点,就要以红黑
树的方式插入一个节点。
5. Java1.8 为什么要引入红黑树?如何在红黑树中插入一个节点?
对于这两个问题,首先,引入红黑树的好处是为了提高查询效率,要说出 O(log2(n)),但
是 在提高查找效率的同时也在插入的时候更加耗时,那可以说一下为什么更加耗时,自然
带出第二个问题,如何在红黑树中插入一个节点,比如当插入一个节点的时候我们会默认它是红色的(这个地方可以结合红黑树特点说一下我们为什么默认它是红色的,从黑色高度
以及相邻两节点不同为红色入手),插入后如果父节点是黑色的 就不需要动了了,但假如
是红色的,就需要进⾏左旋和右旋操作,如果很了解,可以细说左旋右旋如何实现,如果
不不是很了了解,到此为止也 ok。
说到这⾥,我们忽略略了一个重要的点,就是链表转换为红黑树的条件,说出链表长度到
8(相当于红黑树开始第四层) 以及数组大小达到 64 就已经够了了,也可以进一步说一下
链表是如何转换为红黑树的。说完也可以说一下 ConcurrentHashMap 中也是一样的,
然后接下来就引入对 ConcurrentHashMap 的理解,比如在什么地方会涉及到线程安全问
题以及 ConcurrentHashMap 是如何解决的,说说 CAS,说完 CAS 再说说 AQS,自由发
挥吧。
6. JVM 四种引用类型
这个问题比较简单,强引用、弱引用、软引用、虚引用,说一下它们各自的特点和 GC 对
它们的不同处理方式,再说一下常见的应用场景 或者 jdk 的实现中对它们的使用,比如
ThreadLocal 的静态内部类 ThreadLocalMap,它的 Key 是弱引用的,也可以说一下 在
你的理解中为什么它是弱引用的,假如不是会怎么样。
7. SpringBoot 启动过程
这个主要是从它基于 Spring 的事件发布和监听机制开始说起 就没什么问题。
8. 类加载过程加载、链接、初始化,链接又分为验证准备和解析,每一个阶段是做了什么要说清楚。
Object a = new Object();
这⾏代码做了了哪些事情,需要从类加载开始说起,这个相
当于上面问题的延续,所以 一定要清楚 每一个环节 做了哪些事情的,否则这个问题不可
能说清楚。说完类加载的过程 再说一下 开辟内存空间、初始化内存空间以及把内存地址
赋值给变量 a,接下来可以进一步说一下 JVM 或者 CPU 层面对指令的优化,以及在某些
时刻我们需要避免它做这样的优化,比如在单例中我们的实例需要用 volatile 修饰 避免指
令重排序(可以说一下 在 new 一个对象的过程中如果指令重排序了会导致什么结果)。
9. 说说 Netty
从 NIO 开始说肯定是没错的,再说说 Netty 的实现方式,以及它除了 IO 之外还干了哪些
事情。
10. 消息队列的熟练程度,比如问问 Kafka 分区,如何分区等
在 Kafka 实际生产过程中,每个 topic 都会有 多个 partitions。
1. 多个 Partitions 有什么好处?
l 多个 partition ,能够对 broker 上的数据进行分片,通过减少消息容量
来提升 IO 性能;
l 为了提高消费端的消费能力,一般情况下会通过多个 conusmer 去消费
同一个 topic 中的消息,即实现消费端的负载均衡。
2. 针对多个 Partition,消费者该消费哪个分区的消息?Kafka 存在 消费者组 group.id 的概念,组内的所有消费者协调在一起来消
费订阅的 topic 中的消息(消息可能存在于多个分区中)。那么同一个
group.id 组中的 consumer 该如何去分配它消费哪个分区里的数据。
针对下图中情况,3 个分区(test-0 ~ test-3),3 个消费者(ConsumerA ~
C),哪个消费者应该消费哪个分区的消息呢?
对于如上这种情况,3 个分区,3 个消费者。这 3 个消费者都会分别去消费
test 中 topic 的 3 个分区,也就是每个 Consumer 会消费一个分区中的消
息。
如果 4 个消费者消费 3 个分区,则会有 1 个消费者无法消费到消息;如果
2 个消费者消费 3 个分区,则会有 1 个消费者消费 2 个分区的消息。针对
这种情况,分区数 和 消费者数 之间,该如何选择?此处就涉及到 Kafka 消
费端的分区分配策略了。1. 什么是分区分配策略
通过如上实例,我们能够了解到,同一个 group.id 中的消费者,对于一个
topic 中的多个 partition 中的消息消费,存在着一定的分区分配策略。
在 Kafka 中,存在着两种分区分配策略。一种是 RangeAssignor 分配策略
(范围分区),另一种是 RoundRobinAssignor 分配策略(轮询分区)。默认采用
Range 范围分区。 Kafka 提供了消费者客户端参数
partition.assignment.strategy 用来设置消费者与订阅主题之间的分区分配策
略。默认情况下,此参数的值为:
org.apache.kafka.clients.consumer.RangeAssignor,即采用
RangeAssignor 分配策略
RangeAssignor 范围分区
Range 范围分区策略是对每个 topic 而言的。首先对同一个 topic 里面的分
区按照序号进行排序,并对消费者按照字母顺序进行排序。假如现在有 10 个
分区,3 个消费者,排序后的分区将会是 0,1,2,3,4,5,6,7,8,9;消费者排序完之
后将会是 C1-0,C2-0,C3-0。通过 partitions 数/consumer 数 来决定每个消
费者应该消费几个分区。如果除不尽,那么前面几个消费者将会多消费 1 个分
区。
例如,10/3 = 3 余 1 ,除不尽,那么 消费者 C1-0 便会多消费 1 个分区,
最终分区分配结果如下:C1-0
消费 0,1,2,3 分区
C2-0
消费 4,5,6 分区
C3-0
消费 7,8,9 分区(如果有 11 个分区的话,C1-0 将消费 0,1,2,3 分区,C2-0 将消费 4,5,6,7 分
区 C3-0 将消费 8,9,10 分区)
Range 范围分区的弊端:
如上,只是针对 1 个 topic 而言,C1-0 消费者多消费 1 个分区影响不是很
大。如果有 N 多个 topic,那么针对每个 topic,消费者 C1-0 都将多消费
1 个分区,topic 越多,C1-0 消费的分区会比其他消费者明显多消费 N 个分
区。这就是 Range 范围分区的一个很明显的弊端了
由于 Range 范围分区存在的弊端,于是有了 RoundRobin 轮询分区策略,
如下介绍↓↓↓
RoundRobinAssignor 轮询分区
RoundRobin 轮询分区策略,是把所有的 partition 和所有的 consumer 都
列出来,然后按照 hascode 进行排序,最后通过轮询算法来分配 partition
给到各个消费者。
轮询分区分为如下两种情况:l 同一消费组内所有消费者订阅的消息都是相同的。
l 同一消费者组内的消费者锁定月的消息不相同。
如果同一消费组内,所有的消费者订阅的消息都是相同的,那么 RoundRobin
策略的分区分配会是均匀的。
例如:同一消费者组中,有 3 个消费者 C0、C1 和 C2,都订阅了 2 个主题
t0 和 t1,并且每个主题都有 3 个分区(p0、p1、p2),那么所订阅的所以分
区可以标识为 t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终分区分配结果如
下:
消费者 C0
消费 t0p0 、t1p0 分区
消费者 C1
消费 t0p1 、t1p1 分区
消费者 C2
消费 t0p2 、t1p2 分区
如果同一消费者组内,所订阅的消息是不相同的,那么在执行分区分配的时
候,就不是完全的轮询分配,有可能会导致分区分配的不均匀。如果某个消费
者没有订阅消费组内的某个 topic,那么在分配分区的时候,此消费者将不会
分配到这个 topic 的任何分区。
例如:同一消费者组中,有 3 个消费者 C0、C1 和 C2,他们共订阅了 3 个主题:t0、t1 和 t2,这 3 个主题分别有 1、2、3 个分区(即:t0 有 1 个分区
(p0),t1 有 2 个分区(p0、p1),t2 有 3 个分区(p0、p1、p2)),即整个消费者
所订阅的所有分区可以标识为 t0p0、t1p0、t1p1、t2p0、t2p1、t2p2。具体
而言,消费者 C0 订阅的是主题 t0,消费者 C1 订阅的是主题 t0 和 t1,消费者
C2 订阅的是主题 t0、t1 和 t2,最终分区分配结果如下:
消费者 C0
消费 t0p0
消费者 C1
消费 t1p0 分区
消费者 C2
消费 t1p1、t2p0、t2p1、t2p2 分区
RoundRobin 轮询分区的弊端
从如上实例,可以看到 RoundRobin 策略也并不是十分完美,这样分配其实并
不是最优解,因为完全可以将分区 t1p1 分配给消费者 C1。
所以,如果想要使用 RoundRobin 轮询分区策略,必须满足如下两个条件:
l 每个消费者订阅的主题,必须是相同的。
l 每个主题的消费者实例都是相同的。(即:上面的第一种情况,才优先使用
RoundRobin 轮询分区策略)。
什么时候触发分区分配策略
当出现以下几种情况时,Kafka 会进行一次分区分配操作,即 Kafka 消费者端的 Rebalance 操作
l 同一个 consumer 消费者组 group.id 中,新增了消费者进来,会执行
Rebalance 操作。
l 消费者离开当期所属的 consumer group 组。比如主动停机或者宕机。
l 分区数量发生变化时(即 topic 的分区数量发生变化时)。
l 消费者主动取消订阅。
Kafka 消费端的 Rebalance 机制,规定了一个 Consumer group 下的所有
consumer 如何达成一致来分配订阅 topic 的每一个分区。而具体如何执行
分区策略,就是上面提到的 Range 范围分区 和 RoundRobin 轮询分区 两
种内置的分区策略。
Kafka 对于分区分配策略这块,也提供了可插拔式的实现方式,除了上面两种
分区分配策略外,我们也可以创建满足自己使用的分区分配策略,即:自定义
分区策略。
面经资料来自网络
以搞定。
腾讯面经
1. 看你项目介绍中大量使用了 Redis,那能不能介绍下 Redis 的主从同步机
制呢?
关于这道题,因为我在之前的文章也分析过 Redis 主从同步的机制,所以我从 完整重同
步 和 部分重同步 两个阶段去分析的,结果也得到了面试官的认可。详细的完整重同步和
部分重同步机制原理是什么样的,在这里就不展开介绍了。
Redis 主从复制
和 Mysql 主从复制的原因一样,Redis 虽然读取写入的速度都特别快,但是也会产生读压
力特别大的情况。为了分担读压力,Redis 支持主从复制,Redis 的主从结构可以采用一
主多从或者级联结构,Redis 主从复制可以根据是否是全量分为全量同步和增量同步。
全量同步Redis 全量复制一般发生在 Slave 初始化阶段,这时 Slave 需要将 Master 上的所有数据
都复制一份。具体步骤如下:
l 从服务器连接主服务器,发送 SYNC 命令;
l 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区
记录此后执行的所有写命令;
l 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录
被执行的写命令;
l 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
l 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
l 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命
令;完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来
自用户的读请求。
增量同步
Redis 增量复制是指 Slave 初始化后开始正常工作时主服务器发生的写操作同步到从服务
器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从
服务器接收并执行收到的写命令。
Redis 主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需
要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行
增量同步,如不成功,要求从机进行全量同步
2. 你们项目中 Redis 的内存使用和过期数据是怎么做的?
这是两道题,先来说说内存是怎么设计的,首先我们是按照业务维度去评估资源内存的使
用量和 QPS 两个维度去评估 Redis 该部署多少资源,然后提到了业务中 Redis 的部署
方式及原因。然后在聊到过期数据的剔除策略,这些在之前的文章也有介绍过,附上链接
朋友们自行查看吧。缓存过期剔除策略
1.内存使用:(增加内存;使用内存淘汰策略;Redis 集群。) 看到
https://blog.csdn.net/u014590757/article/details/797880762.过期数据:Redis 中有个设置时间过期的功能。
https://www.cnblogs.com/xuliangxing/p/7151812.html
3. 看你在实际项目中使用 Streaming 做了一些实时推荐的项目,能介绍下
吗?
我们的部分实时推荐项目使用的是 Steaming,其通过对 Kafka 的 Consumer 进行了二
次开发封装,屏蔽了 Kafka 不同版本对于业务的影响,同时也可以用 Steaming 去对接
不同的数据源,比如现在也支持 xx 数据源的消费;
SparkStreaming 的 Receiver 方式和直连 direct 方式:
https://www.cnblogs.com/hdfs/p/9971761.html
4. 既然使用了 Kafka,你知道为什么 Kafka 那么快吗?
这道题之前在 Kafka 常见面试题中有提到过,这里需要补充一点是,Kafka 的“零拷
贝”机制需要朋友在深入研究下,在之前的文章里面没有深入解释过这个点,剩余的其他
方面附上链接朋友们自行查看吧:Kafka 为什么那么快
5. 使用 Kafka 有遇到过重复消费的情况吗?你们是怎么解决的?
有遇到过,这里补充一点,因为业务形态所决定消息重复消费产生的影响不是特别严重,
所以在一定程度上可以暂时接受,后续通过一些手段去解决了,解决办法之前介绍过如何
处理,懒得在这里继续重复了,接看链接吧:Kafka 重复消费怎么办6. 关于项目咱们先聊这么多,出个题吧,怎么实现一个阻塞队列?
消费线程等到非空了消费,生产线程等到非满了生产;阻塞队列(BlockingQueue)是一
个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等
待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者
和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。
阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
7. MySQL 熟悉吗?MySQL 事务是什么?
ACID 准备下,时间不多了,直接略过,准备面试的朋友们记得准备一下哈。简单聊了下
读未提交、读已提交、重复读、序列化几种情况。需要额外再准备下业务中使用的事务隔
离级别是怎么用的(我在项目中使用的 MySQL 不多,面试官也没有多问,主要是一个小
时的面试时间快到了,哈哈哈)
Transaction
l 事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行
账户转账业务,该业务就是一个最小的工作单元)
l 一个完整的业务需要批量的 DML(insert、update、delete)语句共同联合完成
l 事务只和 DML 语句有关,或者说 DML 语句才有事务。这个和业务逻辑有关,业务逻
辑不同,DML 语句的个数不同。
https://blog.csdn.net/w_linux/article/details/796660868. 知道 MySQL 的 MVCC 机制吗?
平时基本不用 MySQL,这个没答出来,之后了解了下 InnoDB 大概是通过 undo log
和版本号机制来实现的(怪我没有充分准备就面试了,哈哈,自己活该)
多版本控制(Multiversion Concurrency Control)
: 指的是一种提高并发的技术。最早
的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,
只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了 InnoDB 的并发
度。在内部实现中,InnoDB 通过 undo log 保存每条数据的多个版本,并且能够找回数
据历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。在同一个事务中,
用户只能看到该事务创建快照之前已经提交的修改和该事务本身做的修改。
9. JVM 的垃圾回收机制了解吗?
标记-清楚算法、复制算法、标记-压缩算法、分代收集算法的实现原理。
一、 技术背景
二、 哪些内存需要回收?
2.1 引用计数算法
2.1.1 算法分析
2.1.2 优缺点
2.1.3 是不是很无趣,来段代码压压惊
2.2 可达性分析算法
2.3 Java 中的引用你了解多少2.4 对象死亡(被回收)前的最后一次挣扎
2.5 方法区如何判断是否需要回收
三、常用的垃圾收集算法
3.1 标记-清除算法
3.2 复制算法
3.3 标记-整理算法
3.4 分代收集算法
3.4.1 年轻代(Young Generation)的回收算法
3.4.2 年老代(Old Generation)的回收算法
3.4.3 持久代(Permanent Generation)的回收算法
四、常见的垃圾收集器
五、GC 是什么时候触发的(面试最常见的问题之一)
5.1 Scavenge GC
5.2 Full GC
https://www.cnblogs.com/1024Community/p/honery.html
10. 项目中用的是 CMS 垃圾回收器吗?为什么要分为四个阶段?
初始标记,并发标记,重新标记,并发清理四阶段,系统要求快速响应低延迟,对于多核
美团面经
1、Spring:有没有用过 Spring,Spring IOC、AOP 机制与实现,Spring
MVC
其实我挺不想被问到 Spring 的细节的,框架这些我都没有复习不太记得了。所以我对面
试官说 Spring 里面的一些比较重要的机制我理解的还不错,然后我用一个实际的例子把
我对 IOC、AOP 理解讲了一下,他听了说对,理解的不错(难得遇到一个边面试边能给反
馈的面试官,好开心)。
Spring MVC 其实我用过,我就对面试官讲了我的项目中用到的 Servlet,jsp 和
javabean 实现的 MVC,以及 MVC 各个模块职责以及每个模块是怎么联系到一起的,最
后我补充了一句我想 SpringMVC 的思想其实跟这个是一样的(他说对的,嘿嘿有反馈真
好)
。
2、多线程:怎么实现线程安全,各个实现方法有什么区别,volatile 关键字的
使用,可重入锁的理解,Synchronized 是不是可重入锁
这里我就主要讲了 Synchronized 关键字,还有并发包下面的一些锁,以及各自的优缺点
和区别。volatile 关键字我主要从可见性、原子性和禁止 JVM 指令重排序三个方面讲的,
再讲了一下我在多线程的单例模式 double-check 中用到 volatile 关键字禁止 JVM 指令重
排优化。
3、集合:HashMap 底层实现,怎么实现 HashMap 线程安全我讲了一下 HashMap 底层是数组加单链表实现,Node 内部类,add 的过程,Hash 冲
突解决办法,扩容,三种集合视图。HashMap 线程安全的实现方式主要讲了
HashTable、ConcurrentHashMap 以及 Collections 中的静态方法 SynchronizedMap
可以对 HashMap 进行封装。以及这三种方式的区别,效率表现。
4、JVM 内存管理,GC 算法,HotSpot 里面的垃圾回收器、类加载
JVM 内存主要分为五个区,哪些是线程共享的,哪些是线程独享的,每个区存放什么。
GC 方面:怎么判断哪些对象需要被 GC,GC 的方法,Minor GC 与 Full GC。HotSpot
GC 算法以及 7 种垃圾回收期,主要讲了 CMS 和 G1 收集器。类加载:类加载的过程,
Bootstrap classloader-ExtClassloader-AppClassloader,父类委托机制。
5、进程和线程的区别
从调度、并发性、拥有的资源和系统开销四个方面回答的。
6、HTTP 有没有状态,我说无状态,怎么解决 HTTP 无状态
怎么解决 HTTP 无状态其实就是怎么进行会话跟踪,有四种方法:URL 重写、隐藏表单
域、Cookie、Session。
7、Java IO,NIO,Java 中有没有实现异步 IO
Java IO 实现的是同步阻塞,它是怎么实现同步阻塞的。我拿了 read()方法举例来讲的。
NIO 实现的是同步非阻塞,我详细讲了一下 Selector 中的 select()方法轮询说明它是如何
实现多路复用 IO 的。然后对比了一下他们的效率。面试官可能看我对这一块比较了解,又继续问我 Java 中有没有实现异步 IO,我感觉好像没有,但面试官说有,让我想想,其
实这里我并不清楚啦,所以我就对面试官讲了一下我对 Unix 中异步 IO 模型的理解,然后
说至于 Java 里面有没有我真的不太清楚。(他居然笑了!说你理解是对的,Java 里面有没
有不重要!哈哈)
8、前端会不会,Ajax 是什么,Ajax 实现原理
前端我只是会用一些 js 而已,用过 jquery 框架,问我 Ajax 全称是啥,我猜是异步的 js 和
xml。Ajax 实现原理其实我也不懂,我就只简单讲了一下它通过 XMLHttpRequest 对象
进行异步查询,Ajax 引擎在客户端运行,减少了服务器工作量。
9、让我设计一个线程池
因为我简历中有写到我对多线程、并发这一块理解比较好。所以他老问这方面的题。这个
问题因为我之前看过 ThreadPoolExecutor 的源代码,所以我就仿照那个类的设计思路来
想的,详细讲了一下核心池、创建线程可以用工厂方法模式来进行设计、线程池状态、阻
塞队列、拒绝策略这几个方面。设计的还算比较周全。
10、讲几个设计模式,哪些地方用到了,为什么要用
单例模,jdk 中的 getRuntime();工厂方法模式,ThreadPoolExcutor 用到
ThreadFactory;观察者模式:java.util 包下面的 Observable 和 Observer。最后主要讲
了一下工厂方法模式的使用场景。
面经资料来自网络