百题面试训练营总结
2020-03-07/分布式高可用
- .高并发下容错策略如何选择(B)
a failover
b failfast
c failcache
d failback
Dubbo中的容错策略:
Failover
故障转移策略。当消费者调用提供者集群中的某个服务器失败时,
其会自动尝试着调用其它服务器。
而重试的次数是通过retries属性指定的。读操作建议使用 Failover 失败自动切换,
默认重试两次其他服务器。
Failfast
快速失败策略。消费者端只发起一次调用,若失败则立即报错。
通常用于非幂等性的写操作,比如新增记录。
Failsafe
失败安全策略。当消费者调用提供者出现异常时,直接忽略本次消费操作。
该策略通常用于执行相对不太重要的服务。
Failback
失败自动恢复策略。消费者调用提供者失败后,Dubbo会记录下该失败请求,
然后会定时发起重试请求,而定时任务执行的次数仍是通过配置文件中的retries指定的。
该策略通常用于实时性要求不太高的服务。
Forking
并行策略。消费者对于同一服务并行调用多个提供者服务器,
只要一个成功即调用结束并返回结果。通常用于实时性要求较高的读操作,但其会浪费较多服务器资源。
Broadcast
广播策略。广播调用所有提供者,逐个调用,任意一台报错则报错。
通常用于通知所有提供者更新缓存或日志等本地资源信息。
简单介绍下分布式事务
对于分布式事务,通俗地说就是,一次操作由若干分支操作组成,这些分支操作分属不同应用,
分布在不同服务器上。
分布式事务需要保证这些分支操作要么全部成功,要么全部 失败。
分布式事务与普通事务一样,就是为了保证操作结果的一致性。
对于分布式事务 我们需要知道分布式事务协议,比如2PC/3PC/TCC等;
2PC是非常经典的强一致、中心化的原子提交协议,
协议中定义了两类节点:一个中心化协调者节点和多个参与者节点。
2PC分为两个阶段:
1. 准备阶段:
协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待所有参与者答复。
各参与者执行事务操作,将Undo和Redo信息记入事务日志中(但不提交事务)。
如参与者执行成功,给协调者反馈YES,即可以提交;如执行失败,给协调者反馈NO,即不可提交。
2. 提交阶段:
(所有参与者均反馈YES)
1、协调者向所有参与者发出正式提交事务的请求(即Commit请求)。
2、参与者执行Commit请求,并释放整个事务期间占用的资源
3、各参与者向协调者反馈Ack完成的消息。
4、协调者收到所有参与者反馈的Ack消息后,即完成事务提交
(任何一个参与者反馈NO)
1、协调者向所有参与者发出回滚请求(即Rollback请求)。
2、参与者使用阶段1中的Undo信息执行回滚操作,并释放整个事务期间占用的资源。
3、各参与者向协调者反馈Ack完成的消息。
4、协调者收到所有参与者反馈的Ack消息后,即完成事务中断。
3PC三阶段提交协议
2PC的改进版本,其在两阶段提交的基础上增加了CanCommit阶段,并引入了超时机制。
一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,
这样相对有效地解决了协调者单点故障的问题。
阶段1:CanCommit
1、协调者向所有参与者发出包含事务内容的CanCommit请求,询问是否可以提交事务并等待所有参与者答复
2、参与者收到CanCommit请求后,如果认为可以执行事务操作,则反馈YES并进入预备状态,否则反馈NO。
阶段2:PreCommit
事务预提交:(所有参与者均反馈YES时)
1、协调者向所有参与者发出PreCommit请求,进入准备阶段。
2、参与者收到PreCommit请求后,执行事务操作,将Undo和Redo信息记入事务日志中(但不提交事务)。
3、各参与者向协调者反馈Ack响应或No响应,并等待最终指令。
中断事务:(任何一个参与者反馈NO,或者等待超时后协调者尚无法收到所有参与者的反馈时)
1、协调者向所有参与者发出abort请求。
2、无论收到协调者发出的abort请求,或者在等待协调者请求过程中出现超时,参与者均会中断事务
阶段3:do Commit
提交事务:(所有参与者均反馈Ack响应时)
1、如果协调者处于工作状态,则向所有参与者发出do Commit请求。
2、参与者收到do Commit请求后,会正式执行事务提交,并释放整个事务期间占用的资源。
3、各参与者向协调者反馈Ack完成的消息。
4、协调者收到所有参与者反馈的Ack消息后,即完成事务提交。
中断事务:(任何一个参与者反馈NO,或者等待超时后协调者尚无法收到所有参与者的反馈时)
1、如果协调者处于工作状态,向所有参与者发出abort请求。
2、参与者使用阶段1中的Undo信息执行回滚操作,并释放整个事务期间占用的资源。
3、各参与者向协调者反馈Ack完成的消息。
4、协调者收到所有参与者反馈的Ack消息后,即完成事务中断。
2020-03-07/分布式高可用
- 以下关于分布式锁及其设计的描述正确的是(B、C、D)
A、在CAP理论中分布式锁本质上是AP模型
B、在CAP理论中Redis集群属于AP模型,主从切换可能丢失锁信息,不适合做为分布式锁的解决方案
C、有较多的客户端频繁的申请加锁、释放锁,对于 Zookeeper集群的压力会非常大,所以Zookeeper 不适合用来实现分布式锁
D、在CAP理论中支持CP模型的Etcd,在性能和易用性上均超过Zookeeper,在数据一致性保证方面强于Redis集群,并且Etcd还有很多适合实现分布式锁的特性,适合做分布式锁解决方案
分布式锁一般会选用CP模型。现在基于Redisson和Etcd实现分布式锁是比较受欢迎方式。
- 通常情况下,导致系统可用性降低的原因有哪些?
1. 网络故障
2. 系统配置错误
3. 中间件出现故障,比如缓存不可用
4. 高访问量,服务响应慢,没有设置超时时间,导致主线程堵死
5. 大流量下,流量负载不均,引起个别服务负载过高,出现 雪崩效应
这道题就比较开放了,主考是想考察面试者有没有高并发、分布式、微服务这些方面的实战经验
遇到问题能否有排查或者调优思路。我这边就列举了五条比较常见的,欢迎大家补充。
2020-03-08/高可用
- 高可用架构如何量化(多选)
A、一段时间(比如一年)的停机时间占比
B、一段时间(比如一年)的停机影响请求量占比(停机影响请求量/总请求量)
C、某功能或者服务的失败次数与总的请求次数占比
D、SLA p99 或者 p995
答案:ABCD
SLA 服务等级协议,p99 1.403 表示过去的10秒内最慢的1%请求的平均延时为1.403秒。
p95 过 去的10秒内最慢的5%的请求平均延时。
1年 = 365天 = 8760小时
99.9 = 8760 * (100-99.9)% = 8760 * 0.001 = 8.76小时
99.99 = 8760 * 0.0001 = 0.876小时 = 0.876 * 60 = 52.6分钟
99.999 = 8760 * 0.00001 = 0.0876小时 = 0.0876 * 60 = 5.26分钟
- 高性能架构设计手段有(多选)
A、⾼效的⽹络 IO 与线程模型运⽤
B、服务间调用设置尽可能长的超时时间
C、对于热点访问数据合理使用缓存
D、使用MQ将同步架构转换为异步架构
答案:ACD
- 对高可用9999是什么?(多选)
A、具有故障自动恢复能力的可用性
B、具有较高的可用性,一年出问题时间不超过52.56分钟
C、系统出问题的时间在99.99分钟以内
D、系统能够在99.99%的时间正常工作不出问题
答案:ABD
- 客服端负载均衡的几种算法(多选)
A、本地优先
B、并发数均衡
C、延迟优先
D、一致性哈希
E、权重优先
答案:ABCED
2020-03-10/SpringBoot,Reactor模型
- 符合七层负载均衡设计的有哪些(CD)
A、A10
B、F5
C、LVS
D、Nginx
lvs和nginx可用于7层负载均衡。
- 请简单描述一下 Reactor模型在 Netty 中的应用?
在 Netty-Server 中一般使用的是 Reactor的多线程池模型,
而Netty-Client中一般使用的是 Reactor单线程池模型。
NioEventLoopGroup 中都包含了多个NioEventLoop,
而每个 NioEventLoop 又绑定着一个线程。
一个 NioEventLoop可以处理多个 Channel中的 IO操作,而其只有一个线程。
所以对于这个线程资源的使用,就存在了竞争。
此时为每一个 NioEventLoop都绑定了一个多跑复用器Selector,由 Selector来决定当
NioEventLoop的线程为哪些 Channel服务。
这就是 Reactor模型在 Netty中的应用。
- 请简单谈一下你对 Spring Boot 自动配置的理解?
Spring Boot 与 SSM 传统开发相比,其最大的特点是自动配置,不用再在 xml 文件中做大量的配置。
自动配置的实现主要是通过自动配置类来完成的,
自动配置类存在于两类位置:
一个是 Spring Boot 框架中内置的,
一个是从外部引入的 Starter 中。
具体来说,自动配置类的作用就是根据条件创建核心业务类的实例到 Spring 容器中,
以备该 Starter 的引用工程类中注入。
当然,自动配置类还有一个作用:若创建核心业务类时需要获取配置文件中的相关属性值,
其也会将这些属性值封装为一个属性实例,以备核心业务类使用。
当然,自动配置类需要在 META-INF/spring.factories 中注册。
所以自动配置其实就是能够自动获取配置文件中的属性并创建相应核心业务类实例。
2020-03-12/SpringCloud各组件原理
1、在Nacos作为注册中心时,服务A调用服务B,如果此时服务B挂了会出现什么情况?(A、C)
A. 服务A感知延迟
B. 服务A能够立马感知到服务B已下线
C. 服务B仍然可以被服务A调用
D. 服务B不能被服务A调用
这道题考察的是Nacos的心跳检测机制。当服务B不健康时,
对于Nacos和服务A来说是在B服务出现不健康后15s才知道。
服务A还是会去调服务B,但是发现调不通,此时会对服务B做降级处理。
2、请说说Hystrix 的设计原则?(简答)
解析:
A、对有依赖关系的应用服务在调⽤时出现的调⽤延迟和调⽤失败进⾏控制和容错保护。
B、在复杂的分布式系统中,阻⽌服务故障而带来雪崩效应。
C、提供快速失败和快速恢复的⽀持。
D、提供 fallback(兜底方案) 优雅降级的⽀持。
E、⽀持监控、报警等操作。
3、Hystrix是如何实现资源隔离的?(简答)
资源隔离:把对某⼀个依赖服务的所有调⽤请求,全部隔离在同⼀份资源池内,不会去⽤其它资源了。
哪怕对这个依赖服务,⽐如说订单服务,现在订单的请求为100,但是线程池内就 10 个线程,
最多就只会⽤这 10个线程去执⾏,不会存在,对订单服务的请求,
因为接⼝调⽤延时,将 tomcat 内部所有的线程资源全部耗尽。
Hystrix 实现资源隔离,主要有两种技术:
A、线程池
B、信号量
线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。
更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执⾏。
Hystrix 线程池满后,会确保tomcat 的线程不会因为依赖服务的接⼝调⽤延迟或故障⽽被 hang 住,
tomcat 其它的线程不会卡死,可以快速返回,然后⽀撑其它的事情。
线程池隔离技术,是⽤ Hystrix ⾃⼰的线程去执⾏调⽤;
⽽信号量隔离技术,是直接让 tomcat线程去调⽤依赖服务。
信号量隔离,只是⼀道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执⾏。
适⽤场景:
线程池技术:适合绝⼤多数场景,⽐如说我们对依赖服务的⽹络请求的调⽤和访问、
需要对调⽤的 timeout 进⾏控制(捕捉 timeout 超时异常)。
信号量技术:适合说你的访问不是对外部依赖的访问,
⽽是对内部的⼀些⽐较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的⽹络请求,
那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。
- Eureka 源码中 InstanceInfo 类中具有两个状态属性,是哪两个,你了解过它们吗?(简答)
分别是 status 与 overriddenStatus。下面我依次谈一下我对它们的了解。
status 就是当前 Instance 的工作状态,其初值为 UP,表示可以正常提供服务。
overriddenStatus 用于记录外部对当前 instance 修改的状态,初值为 UNKNOWN。
这个状态仅在 Server 端是有意义的。其意义就是通过修改 Server 端 instanceInfo
overridden Status的值来达到修改 Server 端对应instanceInfo 的 status 的值的目的。
- Spring Cloud 中 Eureka Client 需要定时从 Eureka Server 中获取注册表信息,这个过程称为服务发现。请谈一下你对这个获取过程的认识?(简答)
Eureka Client 从 Eureka Server 中获取注册表分为两种情况,
一种是将 Server 中所有注册信息全部下载到当前客户端本地并进行缓存,这种称为全量获取;
一种是仅获取在 Server中发生变更的注册信息到本地,
然后根据变更修改本地缓存中的注册信息,这种称为增量获取。
当 Client 在启动时第一次下载就属于全量获取,
而后期每 30 秒从 Server 下载一次的定时下载属于增量下载。
无论是哪种情况,Client 都是通过 Jersey 框架向 Server 发送了一个GET 请求。
只不过是,不同的获取方式,提交请求时携带的参数是不同的。
2020-03-14/Dubbo专题
- Dubbo和SpringCloud中的 OpenFeign组件的区别?
最大的区别:Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Dubbo,
Hession序列化完成RPC通信。
而SpringCloud OpenFeign是基于Http协议+Rest接口调用远程过程的通信,
相对来说,Http请求会有更大的报文,占的带宽也会更多。
但是REST相比RPC更为灵活。
- Dubbo框架源码最重要的设计原则是什么?从架构设计角度谈一下你对这个设计原则的理解。
Dubbo在设计时具有两大设计原则:
“微内核+插件”的设计模式。
内核只负责组装插件(扩展点),Dubbo的功能都是由插件实现的,
也就是 Dubbo 的所有功能点都可被用户自定义扩展类所替换。
Dubbo的高扩展性、开放性在这里被充分体现。
采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
简单来说就是,在 Dubbo中,所有重要资源都是以URL的形式来描述的。
- 为什么Dubbo使用URL,而不使用JSON,使用URL的好处是什么?
首先,Dubbo是将URL作为公共契约出现的,即希望所有扩展点都要遵守的约定。
既然是约定,那么可以这样约定,也可以那样约定。只要统一就行。
所以,在Dubbo创建之初,也许当时若采用了JSON作为这个约定也是未偿不可的。
其次,单从JSON与URL相比而言,都是一种简洁的数据存储格式。但在简洁的同时,
URL与Dubbo应用场景的契合度更高些。
因为Dubbo中URL的所有应用场景都与通信有关,都会涉及到通信协议、通信主机、端口号、业务接口等信息。
其语义性要强于JSON,且对于这些数据就无需再给出相应的key了,会使传输的数据量更小。
- JDK的SPI机制存在什么问题?
JDK的SPI机制将所有配置文件中的实现类全部实例化,无论是否能够用到,浪费了宝贵的系统资源。
2020-03-15/并发编程专题
- 简述HashMap的底层原理
(1)hash算法:为什么要高位和低位做异或运算?
答:让高位也参与hash寻址运算,降低hash冲突
(2)hash寻址:为什么是hash值和数组.length - 1进行与运算?
答:因为取余算法效率很低,按位与运算效率高
(3)hash冲突的机制:链表,超过8个以后,红黑树(数组的容量大于等于64)
(4)扩容机制:数组2倍扩容,重新寻址(rehash),hash & n - 1,
判断二进制结果中是否多出一个bit的1,如果没多,那么就是原来的index,
如果多了出来,那么就是index + oldCap,通过这个方式。
就避免了rehash的时候,用每个hash对新数组.length取模,取模性能不高,位运算的性能比较高
JDK 1.8以后,优化了一下,如果一个链表的长度超过了8,就会自动将链表转换为红黑树,
查找的性能,是O(logn),这个性能是比O(n)要高的
(5)红黑树是二叉查找树,左小右大,根据这个规则可以快速查找某个值
(6)但是普通的二叉查找树,是有可能出现瘸子的情况,
只有一条腿,不平衡了,导致查询性能变成O(n),线性查询了
(7)红黑树,红色和黑色两种节点,有一大堆的条件限制,尽可能保证树是平衡的,不会出现瘸腿的情况
(8)如果插入节点的时候破坏了红黑树的规则和平衡,
会自动重新平衡,变色(红 <-> 黑),旋转,左旋转,右旋转
- 解决map的并发问题方案?
HashMap不是线程安全的;Hashtable线程安全,但效率低,
因为是Hashtable是使用synchronized的,所有线程竞争同一把锁;
而ConcurrentHashMap不仅线程安全而且效率高,因为它包含一个segment数组,
将数据分段存储,给每一段数据配一把锁,也就是所谓的锁分段技术。(1.7版本)
- jvm内存结构,各个部分的特点?
- PC寄存器:
a. 每个线程拥有一个pc寄存器;
b. 指向下一条指令的地址。 - 方法区:
a. 保存装载的类的元信息:类型的常量池,字段、方法信息,方法字节码;
jdk6时,String等常量信息置于方法区,jdk7移到了堆中;
b. 通常和永久区(Perm)关联在一起; - 堆:a. 应用系统对象都保存在java堆中;b. 所有线程共享java堆;c. 对分代GC来说,堆也是分代的;
- 栈:
a. 线程私有;
b. 栈由一系列帧组成(因此java栈也叫做帧栈);
c. 帧保存一个方法的局部变量(局部变量表)、操作数栈、常量池指针;
d. 每一次方法调⽤用创建一个帧,并压栈。
- 什么是 CAS?
CAS(Compare And Swap/Set)比较并交换,CAS 算法的过程是这样:它包含 3 个参数CAS(V,E,N)。
V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值。
当且仅当 V 值等于 E 值时,才会将 V 的值设为 N,
如果 V 值和 E 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。
最后,CAS 返回当前 V 的真实值。
CAS 操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。
当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。
失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。
基于这样的原理,CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
- 什么是 Java 的内 存 模 型 ,Java中各个线 程是怎么彼此看到对方的变量的 ?
Java的内存模型定义了程序中各个变量的访问规则,
即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节。
此处的变量包括实例字段、静态字段和构成数组对象的元素,
但是不包括局部变量和方法参数,因为这些是线程私有的,不会被共享,所以不存在竞争问题
Java中各个线程是怎么彼此看到对方的变量的呢?
Java中定义了主内存与工作内存的概念:
所有的变量都存储在主内存,每条线程还有自己的工作内存,保存了被该线程使用到的变量的主内存副本拷贝
线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,
不能直接读写主内存的变量。不同的线程之间也无法直接访问对方工作内存的变量,
线程间变量值的传递需要通过主内存。
2020-03-16/并发编程专题
- 请谈谈volatile有什么特点,为什么它能保证变量对所有线程的可见性?
关键字volatile是Java虚拟机提供的最轻量级的同步机制。
当一个变量被定义成volatile之后,具备两种特性:
保证此变量对所有线程的可见性——当一条线程修改了这个变量的值新值对于其他线程是可以立即得知的。
而普通变量做不到这一点。
禁止指令重排序优化——普通变量仅仅能保证在该方法执行过程中,得到正确结果,
但是不保证程序代码的执行顺序。
- 既 然 volatile 能够保证线程间的变量可见性,是不是就意味着基于volatile变量的运算就是并发安全的?
显然不是的。基于 volatile 变量的运算在并发下不一定是安全的。
volatile变量在各个线程的工作内存,不存在一致性问题(
各个线程的工作内存中volatile变量,每次使用前都要刷新到主内存)。
但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。
- 请对比下volatile对比Synchronized 的异同?
Synchronized既能保证可见性,又能保证原子性,
而volatile只能保证可见性,无法保证原子性。
ThreadLocal和Synchonized 都用于解决多线程并发访问,防止任务在共享资源上产生冲突。
但是ThreadLocal 与Synchronized 有本质区别。
Synchronized 用于实现同步机制,是利用锁的机制使变量或代码块在某一时刻只能被一个线程访问,是一种 “以时间换空间”的方式。
而ThreadLocal 为每一个线程都提供了变量的副本,
使得每个线程在某一时间访问到的并不是同一个对象,根除了对变量的共享,
是一种 “以空间换时间”的方式。
- 请谈谈ThreadLocal是怎么解决并发安全的?
ThreadLocal这是Java提供的一种保存线程私有信息的机制,因为其在整个线程生命周期内有效,
所以可以方便地在一个线程关联的不同业务模块之间传递信息,比如事务ID、Cookie等上下文相关信息。
为每一个线程维护变量的副本,把共享数据的可见范围限制在同一个线程之内,
其实现原理是,在类中有一个,用于存储每一个线程的变量的副本。
- 什么是ThreadLocal?
ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,
所以这些变量不是线程安全的,我们可以使用同步技术。
但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
每个线程都会拥有他们自己的Thread变量,
它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。
ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。
- ThreadLocal内存泄漏
关于ThreadLocal的内存泄露 和预防方法你可以看看这篇文件 :https://zhuanlan.zhihu.com/p/102571059
2020-03-17/Zookeeper专题一
- 对于 zk 的节点类型,谈一下你的认识?
每个 znode 根据节点类型的不同,具有不同的生命周期与特性。
持久节点:节点被创建后会一直保存在 zk 中,直到将其删除。
持久顺序节点:一个父节点可以为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。
其在创建子节点时,会在子节点名称后添加数字后辍,作为该子节点的完整节点名。
序号由 10 位数字组成,由这些子节点的父节点维护,从 0 开始计数。
临时节点:临时节点的生命周期与客户端的会话绑定在一起,会话消失则该节点就会被自动清理。
临时顺序节点:添加了创建序号的临时节点。
- 对于 zk 来说 watcher 机制非常重要,watcher 机制的工作原理是怎样的?谈一下你的认识?
当客户端想要监听 zk 中某节点的状态变化时,需要向该节点注册 watcher 监听。
其首先会在客户端创建一个 watcher 对象,并为其添加相应的回调。
当 zk 中对应的节点发生了相应的 watcher 事件后,zk 会向客户端发送事件通知,
触发该 watcher 回调的执行。执行完毕,该 watcher 对象销毁。若要再次监听,则需再次注册。
- 对于 zk 官方给出了四种最典型的应用场景,配置维护就是之一。什么是配置维护?请谈一下你的看法?
分布式系统中,很多服务都是部署在集群中的,即多台服务器中部署着完全相同的应用,起着完全相同的作用
当然,集群中的这些服务器的配置文件是完全相同的。
若集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改这些服务器中的配置文件。
如果我们集群服务器比较少,那么这些修改还不是太麻烦,
但如果集群服务器特别多,那么纯手工,短时间内完成更改这些配置文件几乎就是一件不可能完成的任务。
即使使用大量人力进行修改,但过多的人员参与,出错的概率大大提升,对于集群所形成的危险是很大的。
zk 可以通过一种发布订阅模式对集群中的配置文件进行统一管理,这就是配置维护。
其作用与 Spring Cloud Config 的作用相同。
- 使用 zk 可以实现 Master 选举,实现原理是什么?请谈一下你的看法?
使用 zk 实现 Master 选举的原理是,
集群中所有主机都向 zk 中创建相同路径下的某持久节点注册子节点列表变更 watcher 监听,
并在该节点下持久相同名称的临时节点,谁创建成功谁就是 Master。
当 Master 宕机,该临时节点消失,
此时会触发其他主机 watcher 回调的执行。
watcher回调会重新抢注该节点下的临时节点,谁注册成功谁就是 Master。
即可以实现 Master 宕机后的自动重新选举。
5、zk 可以实现分布式锁,Redis 也可以实现。由它们实现的分布式锁有什么区别?请谈一下你的认识?
使用 zk 实现的分布式锁是 CP 的分布式锁。
因为 zk 是 CP 的。在某客户端向 zk 集群中的某节点写入数据后,
会等待超过半数的其它节点完成同步后,才会响应该客户端。
使用 Redis 实现的分布式锁是 AP 的分布式锁。
因为 Redis 是 AP 的。在某客户端向 Redis集群中的某节点写入数据后,
会立即响应该客户端,之后在 Redis 集群中会以异步的方式来同步数据。
对于 AP 的分布式锁,需要注意可能会出现的问题:
一个客户端 a 在 Redis 集群的某节点 A 写入数据后,
另一个节点 B 在还未同步时,另一个客户端 b 从 B 节点读取数据,没有发现 a 写入的数据。
此时可能会出现问题。
所以,如果某共享资源要求必须严格按照锁机制进行访问,那么就使用 zk 实现的 CP 锁。
2020-03-18/Zookeeper专题二
- zk客户端维护着会话超时管理,请谈一下你对此的认识?
zk 客户端维护着会话超时管理,主要管理的超时有两类:读超时与连接超时。
当客户端长时间没有收到服务端的请求响应或心跳响应时,会发生读超时;
当客户端发出连接请求后,长时间没有收到服务端的连接成功 ACK,此时发生连接超时。
无论哪类超时,都会抛出 SessionTimeoutException异常。
但若是读超时,则会发出 Ping连接请求,Ping失败则会关闭连接;
若是连接超时,则会再次连接,直到重连策略不满足,关闭连接。
- zk是 CP的,zk集群在数据同步或 leader选举时是不对外提供服务的,那岂不是用户体验非常不好?请谈一下你对此的看法?
对于 zk 的 CP,其实对于客户端来说,一般是感知不到的。
因为当客户端连接 zk 集群时,若集群恰好由于数据同步或 leader 选举而不对外提供服务,
那么,客户端的此次连接是失败的。
所以,其就会尝试着按照重试策略再连接。只要不超时就会一直连。
而 zk 集群中的数据同步与leader 选举是很快的,在客户端重试连接过程中已经完成。
此时客户端再连就会连接成功。
所以,对于客户端来说,zk的 CP是感知不到的。
- zk Client在连接 zk时会发生连接丢失事件,什么是连接丢失?请谈一下你的认识?
因为网络抖动等原因导致客户端长时间收不到服务端的心跳回复,
客户端就会引发连接丢失。
连接丢失会引发客户端自动从 zk地址列表中逐个尝试重新连接,直到重连成功,
或按照指定的重试策略终止。
- zk Client在连接 zk时会发生会话转移事件,什么是会话转移?请谈一下你的认识?
当发生连接丢失后,客户端又以原来的 sessionId重新连接上了 zk服务器。
若重连上的服务器不是原来的服务器,那么客户端就需要更新本地 zk 对象中的相关信息。
这就是会话转移,即回话从一个服务端转移到了另一个服务器。
服务端会由于长时间没有收到某客户端的心跳,即该客户端会话在服务端出现长时间的空闲状态时,
服务器会认为该客户端已经挂了,然后会将该会话从服务器中删除。
不过,在空闲超时时间范围内,该客户端又重新连接上了服务器,
此时服务器并不会删除该会话,且sessionId仍是原来的。
- zk Client在连接 zk时会发生会话失效事件,什么是会话失效?请谈一下你的认识?
若客户端连接丢失后,在会话空闲超时范围内没有连接上服务器,则服务器会将该会话从服务器中删除。
由于客户端的重连与服务端的会话删除是两个独立运行于不同主机的进程,
所以客户端的重连与服务端的会话删除没有关系。
若在服务端将某客户端的会话删除后,而该客户端仍使用原来的 sessionId又重新连接上了服务器。
那么这个会话是失效的,因为服务端根本就没有该会话的信息。
此时服务端会向客户端发送关闭该连接的响应。
这也是客户端知道其与服务端连接失效的途径。
2020-03-19/Zookeeper专题三
- zk中的会话空闲超时管理采用的是分桶策略。请谈一下你对分桶策略的认识?
分桶策略是一种查找空闲超时会话的方式,是将空闲超时时间相近的会话放到同一个会话桶中来进行管理,
以减少管理的复杂度。在检查超时时,只需要检查桶中剩下的会话即可,
因为在该桶的时间范围内没有超时的会话已经被移出了桶,而桶中存在的会话就是超时的会话。
- zk 中的会话空闲超时管理采用的是分桶策略。会话桶中的会话会发生换桶,什么时候会进行换桶?如何换桶呢?从源码角度请谈一下你的认识?
zk 中的会话空闲超时管理中分桶策略中存在会话桶的概念,从源码角度来说,
会话发生换桶说明会话没有发生空闲过期,只有当会话与服务器发生了交互时才不会过期。
所以,当某会话与服务器发生了一次交互时,就会马上判断这个会话是否需要换桶。
如何判断是否需要换桶呢?首先会重新计算会话的空闲超时时间点,
然后根据这个时间点再计算出其应该对应的会话桶标识 expireTime。
再然后比较 expireTime与当前会话所在的会话桶标识 tickTime(包含在会话实例中)的大小关系。
若 expireTime等于 tickTime,则说明当前会话不需要换桶;若expireTime大于tickTime,
则说明该换桶了;不可能出现expireTime小于 tickTime的情况,因为时间值只会增大。
如何进行换桶呢?
从会话当前所在桶中删除当前会话,然后更新当前会话所在的会话桶标识 tickTime,
然后再根据这个 tickTime从会话 Map 中查找响应的会话桶。
若存在,则直接将会话放入桶中。若没有,则创建一个,并放入到会话 Map 中,
然后再将会话放入桶中。
- zk 中的会话空闲超时管理采用的是分桶策略。该分桶策略中会话空闲超时判断发生在哪里?超时发生后的处理发生在哪里?都做了哪些处理呢?请谈一下你的认识?
zk 中的会话空闲超时管理中分桶策略中存在会话桶的概念。
当服务器启动时会创建并启动一个不会停止的线程,专门用于查找过期的会话桶。
然后将过期的会话桶从会话桶Map 中删除,并关闭该会话桶中的所有会话。
- 简述Netty中 Client端 bootstrap 中定义的 ChannelInitializer处理器的创建、添加时机,及添加到哪个 channel的 pipeline中了?
在 Client端 bootstrap中定义的 ChannelInitializer处理器的创建、添加时机,及添加位置如下:
创建时机:在 Client启动时被创建
添加位置:被添加到 Channel的 pipeline中,Client端没有parentChannel与childChannel的区分
添加时机:在 Client启动时被添加