有位朋友面试了字节,后端方向。整理了这几道面试真题以及答案,如有错误,欢迎大家留言区讨论哈。金九银十冲刺,面试的小伙伴加油呀。
public、private、no-cache
等值。Https工作流程
了解过Https原理的小伙伴,都知道数字证书这玩意。为了避免公钥被篡改,引入了数字证书,如下:
数字证书构成
tcp连接时,客户端client 有SYN_SEND
、ESTABLISHED
状态,服务端server有SYN_RCVD
、ESTABLISHED
状态。
tcp三次握手
开始客户端和服务器都处于CLOSED状态,然后服务端开始监听某个端口,进入LISTEN状态
可以先回忆下TCP的四次挥手哈,
TCP四次挥手
TIME-WAIT 状态为什么需要等待 2MSL
2MSL,2 Maximum Segment Lifetime,即两个最大段生命周期
- 1个 MSL 保证四次挥手中主动关闭方最后的ACK 报文能最终到达对端
- 1个 MSL 保证对端没有收到 ACK 那么进行重传的FIN报文能够到达
虚拟内存,是虚拟出来的内存,它的核心思想就是确保每个程序拥有自己的地址空间,地址空间被分成多个块,每一块都有连续的地址空间。同时物理空间也分成多个块,块大小和虚拟地址空间的块大小一致,操作系统会自动将虚拟地址空间映射到物理地址空间,程序只需关注虚拟内存,请求的也是虚拟内存,真正使用却是物理内存。
物理内存,指通过物理内存条而获得的内存空间,而虚拟内存则是指将硬盘的一块区域划分来作为内存。
我们常说的物理内存大小,其实是指内存条的大小。一般买电脑时,我们都会看下内存条是多大容量的,话说如果内存条大小是100G,那这100G就都能够被使用吗?不一定的,更多的还是要看CPU地址总线的位数,如果地址总线只有20位,那么它的寻址空间就是1MB,即使可以安装100G的内存条也没有意义,也只能视物理内存大小为1MB。
如下图,CPU里有一个内存管理单元(Memory Management Unit),简称为MMU,虚拟内存不是直接送到内存总线,而是先给到MMU,由MMU来把虚拟地址映射到物理地址,程序只需要管理虚拟内存就好,映射的逻辑自然有其它模块自动处理。
- TCP连接的客户端机:每一个ip可建立的TCP连接理论受限于ip_local_port_range参数,也受限于65535。但可以通过配置多ip的方式来加大自己的建立连接的能力。
- TCP连接的服务器机:每一个监听的端口虽然理论值很大,但这个数字没有实际意义。最大并发数取决你的内存大小,每一条静止状态的TCP连接大约需要吃3 .3K的内存。
注册中心是分布式开发的核心组件之一,而eureka是spring cloud推荐的注册中心实现。
架构图如下:
Eureka server可以集群部署,多个节点之间会通过Replicate(异步方式)进行数据同步,保证数据最终一致性。Eureka Server作为一个开箱即用的服务注册中心,提供的功能包括:服务注册、接收服务心跳、服务剔除、服务下线等。
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
Eureka 挂了,微服务是可以调通的,不过有个前提:provider的地址没变!如果 provider换了一个 IP 地址或者端口,这个时候,consumer 就无法及时感知到这种变化,就会调不通。
Hystrix 工作流程图如下:
Hystrix 提供了两个Command, HystrixCommand 和 HystrixObservableCommand,可以使用这两个对象来包裹待执行的任务。
2 . 执行命令
有四种方式执行command。分别是:
3 . 检查缓存
如果启用了 Hystrix Cache,任务执行前将先判断是否有相同命令执行的缓存。如果有则直接返回缓存的结果;如果没有缓存的结果,但启动了缓存,将缓存本次执行结果以供后续使用。
4. 检查断路器是否打开 断路器(circuit-breaker)和保险丝类似,保险丝在发生危险时将会烧断以保护电路,而断路器可以在达到我们设定的阀值时触发短路(比如请求失败率达到50%),拒绝执行任何请求。
如果断路器被打开,Hystrix 将不会执行命令,直接进入Fallback处理逻辑。
5. 检查线程池/信号量情况 Hystrix 隔离方式有线程池隔离和信号量隔离。当使用Hystrix线程池时,Hystrix 默认为每个依赖服务分配10个线程,当10个线程都繁忙时,将拒绝执行命令。信号量同理。
6. 执行具体的任务 通过HystrixObservableCommand.construct() 或者 HystrixCommand.run() 来运行用户真正的任务。
7. 计算链路健康情况 每次开始执行command、结束执行command以及发生异常等情况时,都会记录执行情况,例如:成功、失败、拒绝以及超时等情况,会定期处理这些数据,再根据设定的条件来判断是否开启断路器。
8. 命令失败时执行 Fallback 逻辑 在命令失败时执行用户指定的 Fallback 逻辑。上图中的断路、线程池拒绝、信号量拒绝、执行执行、执行超时都会进入 Fallback 处理。
9. 返回执行结果 原始结果将以Observable形式返回,在返回给用户之前,会根据调用方式的不同做一些处理。
Zab协议,英文全称是Zookeeper Atomic Broadcast(Zookeeper原子广播)。Zookeeper是通过Zab协议来保证分布式事务的最终一致性。
Zab协议是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议 ,是Zookeeper保证数据一致性的核心算法。Zab借鉴了Paxos算法,是一种通用的分布式一致性算法。
基于Zab协议,Zookeeper实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性。就是指只有一台Leader节点负责处理外部的写事务请求,然后它(Leader)将数据同步到其他Follower节点。
Zookeeper 客户端会随机的链接到 zookeeper 集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向Leader提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。
Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。
服务器启动或者服务器运行期间(Leader挂了),都会进入Leader选举,我们来看一下~假设现在ZooKeeper集群有五台服务器,它们myid分别是服务器1、2、3、4、5,如图:
zookeeper集群初始化阶段,服务器(myid=1-5)依次启动,开始zookeeper选举Leader~
服务器1和服务器2都将自己作为Leader服务器进行投票,投票的基本元素包括:服务器的myid和ZXID,我们以(myid,ZXID)形式表示。初始阶段,服务器1和服务器2都会投给自己,即服务器1的投票为(1,0),服务器2的投票为(2,0),然后各自将这个投票发给集群中的其他所有机器。
每个服务器都会接受来自其他服务器的投票。同时,服务器会校验投票的有效性,是否本轮投票、是否来自LOOKING状态的服务器。
收到其他服务器的投票,会将被人的投票跟自己的投票PK,PK规则如下:
- 优先检查ZXID。ZXID比较大的服务器优先作为leader。
- 如果ZXID相同的话,就比较myid,myid比较大的服务器作为leader。
服务器1的投票是(1,0),它收到投票是(2,0),两者zxid都是0,因为收到的myid=2,大于自己的myid=1,所以它更新自己的投票为(2,0),然后重新将投票发出去。对于服务器2呢,即不再需要更新自己的投票,把上一次的投票信息发出即可。
每次投票后,服务器会统计所有投票,判断是否有过半的机器接受到相同的投票信息。服务器2收到两票,少于3(n/2+1,n为总服务器),所以继续保持LOOKING状态
3 . 服务器3(myid=3)启动,继续进入Leader选举阶段
4 . 服务器4启动,发起一次选举。
5 . 服务器5启动,发起一次选举。
6 . 投票结束,服务器3当选为Leader
zookeeper集群的五台服务器(myid=1-5)正在运行中,突然某个瞬间,Leader服务器3挂了,这时候便开始Leader选举~
1 . 变更状态
Leader 服务器挂了之后,余下的非Observer服务器都会把自己的服务器状态更改为LOOKING,然后开始进入Leader选举流程。
2 . 每个服务器发起投票
每个服务器都把票投给自己,因为是运行期间,所以每台服务器的ZXID可能不相同。假设服务1,2,4,5的zxid分别为333,666,999,888,则分别产生投票(1,333),(2,666),(4,999)和(5,888),然后各自将这个投票发给集群中的其他所有机器。
3 . 接受来自各个服务器的投票
4 . 处理投票
投票规则是跟Zookeeper集群启动期间一致的,优先检查ZXID,大的优先作为Leader,所以显然服务器zxid=999具有优先权。
5 . 统计投票
6 . 改变服务器状态
可以使用滑动窗口实现,代码如下:
public int lengthOfLongestSubstring2(String s) {
int n = s.length();
if (n <= 1) return n;
int maxLen = 1;
//左、右指针
int left = 0, right = 0;
Set<Character> window = new HashSet<>();
while (right < n) {
char rightChar = s.charAt(right);
while (window.contains(rightChar)) {
window.remove(s.charAt(left));
left++;
}
//最大长度对比
maxLen = Math.max(maxLen, right - left + 1);
window.add(rightChar);
right++;
}
return maxLen;
}
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8