1 zookeeper原理 / zookeepr分布式一致性原理
- ZooKeeper的核心是原子广播,实现原子广播机制的协议叫ZAB协议
- 选举阶段、恢复阶段、广播阶段
2 讲一下CAP原理?ZooKeeper是什么?Eureka是什么?
C:Consistency,一致性,通常指的是强一致性
A:Availability,可用性
P:Partition tolerance,分区容错性
ZooKeeper是CP,Eureka是AP
zookeeper其实提供的也是弱一致性,读的数据可能不是最新的,如果想要读到最新的数据,要手工调用sync方法从leader同步。
3 zookeeper能做什么?zookeeper应用场景?
类似Linux文件系统的树形结构,轻量级的内存文件系统,只适合存储少量信息,不适合存储大量文件或大文件,每个znode默认存储1MB的数据,主要是状态数据。
zookeeper应用场景
- 注册中心(临时节点、watch)
- 分布式锁(临时节点、节点唯一性、watch)
- 分布式ID(顺序节点)
- 分布式队列(临时有序节点、watch)
- 配置中心(watch机制发布订阅)
- leader选举(临时节点、节点唯一性、watch)
- 统一命名服务:根据指定名字来获取资源或服务的地址,提供者等信息,利用其znode的特点和watcher机制,将其作为动态注册和获取服务信息的配置中心,统一管理服务名称和其对应的服务器列表信息,我们能够近乎实时地感知到后端服务器的状态(上线、下线、宕机)。
- 集群管理
- 负载均衡
-
服务器动态上下线
利用到zookeeper的中间件
- dubbo
- kafka
- solr
- HBASE
- storm
4 zookeeper作为注册中心有什么问题?
- ZooKeeper是CP,leader选举时间过长(30~120s)会出现服务不可用
5 zookeeper与eureka区别
- ZooKeeper是CP,Eureka是AP
- ZooKeeper要n/2+1才能对外服务,Eureka只要一台存活就可以服务,只是信息可能是旧的,还有自我保护机制(15分钟内超过85%的节点没有心跳开启自我保护机制)
- Eureka在自我保护机制开启后会有以下情况出现:
- 不再移除本已经过期的服务
- 还可以接受新服务注册和查询请求,但是不会同步到其它节点
- 网络稳定恢复正常,新注册信息才会同步到其它节点
6 master选举/leader选举
zoo.cfg中electionAlg属性指定1~3,分别对应3中leader选举算法:
- 1对应LeaderElection
- 2对应AuthFastLeaderElection
- 3对应FastLeaderElection,默认的算法,3.4.0以后只保留这一种。
leader选举过程
- (myid, zxid)作为quorum的权重
- 先比较zxid,选zxid大的
- zxid相同,比较myid,选myid大的
- 半数机制,超过半数机器存活,则集群可用
7 zookeeper通信模型
- client ——> follower:java NIO(默认,还有一种基于netty3)
- follower ——> leader
- ElectionLeader / AuthFastElectionLeader:UDP
- FastElectionLeader:TCP/IP
8 zookeeper数据结构(znode默认存储1MB数据)
ZooKeeper跟踪时间的多种方式
Zxid:每个ZooKeeper状态变化将会接收到一个zxid(ZooKeeper Transaction Id)的时间戳。ZooKeeper通过该字段了解所有变化的顺序。每次变化都会有一个唯一的zxid,如果zxid1小于zxid2,说明zxid1发生在zxid2之前。
Version numbers:每个对节点的变化都会对这个节点的版本号加1.有3个版本号他们分别是version\cversion\aversion
Ticks:当在使用多机ZooKeeper服务时,服务器之间会使用ticks票据来定义事件(比如状态上传、会话超时、连接超时等等)的计时。tick时间会通过最小会话超时时间间接显示;如果客户端请求会话超时小于最小的会话时间,服务端会告诉客户端这个会话超时时间实际是最小会话时间;
Real time:ZooKeeper没有使用实时时间或者时钟时间,除了把时间戳放进正在创建或者修改的znode节点的数据结构中。
在ZooKeeper的每个znode节点的数据结构由以下几个字段组成:
- czxid:znode节点创建时间所对应的zxid
- mzxid:znode节点修改时间所对应的zxid
- ctime:以距离时间原点(epoch)的毫秒数表示的znode创建时间
- mtime:以距离时间原点(epoch)的毫秒数表示的znode最近修改时间
- version:znode节点的版本号,记录修改次数
- cversion:znode子节点的版本号,记录子节点修改次数
- aversion:znode节点的权限(ACL)版本号,记录ACL修改次数
- ephemeralOwner:如果znode节点是临时节点,表示所属会话的id,如果不是临时节点则为0
- dataLength:znode节点数据字段的长度
- numChildren:znode节点的子节点数量
9 服务注册到zookeeper,这些数据保存在哪里?
zoo.cfg中dataDir=/tmp/zookeeper
10 zookeeper怎么实现分布式锁
分布式锁的3种实现方案
- 数据库锁
- 乐观锁,version
- 悲观锁,for update
- redis实现分布式锁
- setnx()
- Redission
- Redlock
- ZooKeeper实现分布式锁
- 有序临时节点,最小的有序节点获得锁,使用完就消失,通过watch监控节点变化,继续找出最小的有序节点获得锁,依次继续。
zookeeper分布式锁实现方式
- 排它锁/写锁/独占锁/阻塞锁
- 加锁:创建临时节点
- 基础版本:/exclusive节点下创建临时有序节点,/exclusive/lock,创建成功即加锁成功;其它未获得锁的客户端注册watch监听
- 改进版本:临时节点序号最小的获得锁,其它的客户端注册watch监听
- 释放锁:删除加锁时候创建成功的临时节点
- 正常释放:正常执行完业务逻辑,客户端主动删除临时节点;
- 异常释放:当前获取锁的客户端机器发生宕机,zookeeper上的临时节点会被移除。
- 加锁:创建临时节点
- 共享锁/读锁/非阻塞锁
- 在获取共享锁时,所有客户端都会到/shared_lock节点下创建一个临时顺序节点(shared_lock/[Hostname]-请求类型-序号),若是读请求,则创建/shared_lock/192.168.0.1-R-0000000001;
- 若是写请求,则创建/shared_lock/192.168.0.1-W-0000000001
zookeeper分布式锁开源实现
- Apache curator
zookeeper实现分布式锁用临时节点有什么问题?
使用EPHEMERAL会引出一个风险:在非正常情况下,网络延迟比较大出现session timeout,ZooKeeper就会认为该client已关闭,从而销毁其id标识,竞争资源的下一个id就可以获取锁。这时可能两个process同时拿到锁在跑任务,设置好sessionTimeout很重要,不要跨机房访问。
同样使用PERSISTENT同样会存在一个死锁的风险,进程异常退出后,对应的竞争资源id一直没有删除,下一个id一直无法获取锁对象。
至于是否使用优化后的分布式锁,开发人员可以灵活掌握。在集群规模不大、网络资源丰富的情况下,之前的实现方案比较简单实用;若集群规模达到一定程度,希望可以精细化地控制分布式锁机制,可以尝试改进后的分布式锁实现。
11 ZooKeeper三种角色
- Leader
- 一个ZooKeeper集群同一时间只会有一个leader,它发起和维护与Follower和Observer间的心跳。
- 所有写操作都是Leader完成的,同时会将写操作通过原子广播给其他服务器,只有超过半数的节点(不包括Observer节点)写入成功,该写请求就会被提交。
- Follower
- Follower可以直接处理并响应客户端的读请求,将写请求转发给Leader
- Follower有投票权
- Observer
- Observer无投票权
- Observer接受客户端请求,并将写请求转发给Leader
- 加入Observer提高伸缩性,不影响吞吐率
12 ZooKeeper四种节点
- 持久节点
- 临时节点
- 持久顺序节点
- 临时顺序节点
13 ZAB协议
13.1 事务编号Zxid
- 低32位:单调递增的计数器,每次事务加1
- 高32位:epoch,每选举一个leader,都会有一个新的epoch,在之前基础上加1
13.2 ZAB协议的两种模式
- 恢复模式
leader选举的过程 - 广播模式
Leader和各节点同步的过程
13.3 ZAB协议的4个阶段
- 选举阶段
- 一个节点得票数超过半数就可以成为准leader,此时的leader只有进入到广播阶段才算是真正的leader
- 发现阶段
- 准leader生成epoch,follower接受epoch
- 同步阶段
- leader获得最新的信息同步集群所有副本
- 广播阶段
- 此时的leader才是真正的leader,leader进行消息广播,正式对外提供服务。
13.4 ZAB协议的java实现
将原生的ZAB协议的发现阶段和同步阶段合并为恢复阶段。
- 选举阶段
- 恢复阶段
- 广播阶段
13.5 投票机制
- 每个节点首先给自己投票
- zxid最大的节点
- 节点获得超半数得票
14 判断长连接是否存活?怎么实现心跳检测机制?
当长连接没有流量时,无法判断是通信异常还是通信正常但无业务流量引起的,需要心跳包来实现。
- 方式一:应用层实现心跳机制(推荐)
- 优点:实现策略灵活,能及时检测到连接状态;
- 缺点:每个应用都需要有一套,无法底层共用;
- 方式二:利用TCP的KeepAlive机制
- TCP协议本身提供了心跳机制,需要通过SO_KEEPALIVE开启。默认情况下,当连接空闲2小时后,每隔75s发送一次心跳包,如果连续9次没有收到响应则关闭连接。
- 优点:不需要二次开发,简单配置参数即可;
- 缺点:网络环境的复杂性使得KeepAlive机制容易失效;
15 zookeeper写数据流程
16 paxos & Raft & ZAB
相同点
- 都是分布式一致性协议,都是采用quorum确定少数服从多数的一致性
- ZooKeeper还实现了带权重的quorum(myid越大权重越大)
- 核心都是leader选举,都是先到先得的投票方式
- 写操作都是leader发起
- 都采用心跳检测探活
异同点
- Raft和ZAB是paxos基础上改性的,Raft目标是简单易懂
- Raft的心跳是leader到follower,ZAB是follower到leader
- ZAB的follower在投票给leader前必须跟leader日志达成一致,Raft的follower简单的谁的term高投给谁
- paxos中角色是proposor、acceptor、learner,ZAB是leader、follower、observer
- ZAB中用epoch,Raft用term
使用场景
- paxos
- Google chubby
- Raft
- Redis Cluster
- etcd
- ZAB
- ZooKeeper
17 什么是quorum?什么是NWR?
- quorum又叫做NWR协议,分布式存储中控制一致性的策略。
- N,分布式存储系统中,有多少份备份数据
- W,至少有W份写入才代表写成功
- R,至少有R份读取才代表读成功
- W+R>N,强一致性
- W+R<=N,最终一致性
18 Dubbo原理
Dubbo架构主要分为五个模块:容器、服务提供者、服务消费者、注册中心、监控中心。
- 容器启动,运行服务提供者,其实就是一个main程序
- 服务提供者启动后,发布服务到注册中心
- 消费者启动后,向注册中心订阅服务列表;当服务列表有变化时,注册中心基于长连接推送变更给消费者
- 消费者调用服务,服务端根据软负载均衡算法选择一台提供者给消费者调用,如果调用失败,再选择另一台提供调用服务
- 服务提供者和消费者在内存中累积调用次数和时间,每隔一分钟发送统计数据给监控中心
- 注册中心和监控中心是可选的,即使两个都宕机,也不影响服务提供者和消费者,因为消费者本地缓存了服务列表,可以直连服务提供者
- Registry、Provider、Consumer三者之间都是基于长连接的,Monitor不是
- Provider全部宕机,服务不可使用,Consumer会无限次重连等待Provider恢复
19 Dubbo服务发现与注册流程
1、调用JavassistProxyFactory 生成一个Invoker。 对应Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));这一段
2、用com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#export方法 进行一个服务的暴露
3、DubboProtocol#export 方法最终会调用com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory#createRegistry
进行一个服务注册 这时zookeepr相应的目录下面就会有对应的内容.这时服务注册就算完成了。
20 Dubbo序列化方式?效率上各有什么区别?
hessian2(duubo默认)
Hessian是一种跨语言的高效的二进制序列化方式,hessian2是阿里基于hessian修改的,是dubbo默认的序列化方式。Kryo+FST(dubbox默认)
基于Java语言的高效的二进制序列化方式,dubbox中默认使用的序列化方式。thrift/protobuf
跨语言高效序列化方式dubbo
阿里开发尚未成熟的序列化方式,不建议在生产环境使用。json/fastjson
json是常用的文本序列化方式,性能低于二进制序列化方式。Java序列化
Java自带的序列化方式,性能不理想。
21 Dubbo的consumer怎么调provider?Dubbo负载均衡策略?
Random(缺省默认)
随机。缺省默认,按照权重设置随机概率。RoundRobin
轮询。按照公约后的权重设置轮询比率。LeastActive
最少活跃调用数。使慢的机器接手到更少的请求。ConsistentHash
一致hash。相同参数的请求总是发送到同一台机器。
22 Dubbo的容错机制
- failover(缺省默认)
Dubbo缺省默认值,失败自动重试,通常用于读操作,但重试会带来更长延迟,可通过retries="2"来设置重试次数(不含第一次) - failfast
快速失败,通常用于非幂等性的写操作,如新增记录 - failsafe
失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作 - failback
失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知等操作 - forking
并行调用多个服务器,只有一个成功即返回,通常用于实时性较高的读操作,但需要浪费更多的服务资源,可通过forks="2"来设置最大并行数 - broadcast
从2.1.0版本开支支持,广播告诉所有提供者,逐个调用,任意一台报错则报错,通常用于通知所有提供者更新缓存或日志等本地资源信息。
23 Dubbo的SPI机制跟Java的SPI有什么区别?
SPI定义
SPI的全名为Service Provider Interface。JDK的SPI实现了为接口自动寻找实现类的功能。SPI的使用方式
当服务提供者提供一个接口的多个实现的时候,一般会在META-INF/services/ 下面建立一个与接口全路径同名的文件,在文件里配置接口具体的实现类。当外部调用模块的时候的时候就能实例化对应的实现类。-
JDK和Dubbo实现SPI的异同
- JDK的SPI用ServiceLoader实现,Dubbo的SPI用ExtensionLoader实现
- JDK的SPI会在一次实例化所有实现,可能会比较耗时,而且有些可能用不到的实现类也会实例化,浪费资源而且没有选择;Dubbo的SPI是延迟加载的。
- dubbo的spi文件定义在META-INF/dubbo/internal/路径下面和jdk的spi类似。但是dubbo的spi文件里面格式和jdkSPI有点不一样,是key-value形式,格式是扩展名=全路径的类名。
- dubbo的spi增加了对扩展点IOC和AOP的支持,一个扩展点可以直接setter注入其他扩展点。这是jdk spi不支持的。
24 Dubbo的group和version
24.1 Dubbo的group属性
- 当一个接口有多种实现时,可以用group区分。
<!-- dubbo group 使用示例 -->
<bean id="demoA" class="com.xxx.IndexServiceImpl1" />
<dubbo:service group="feedback" interface="com.xxx.IndexService" ref="demoA" />
<bean id="demoB" class="com.xxx.IndexServiceImpl2" />
<dubbo:service group="member" interface="com.xxx.IndexService" ref="demoB" />
- Dubbo消费者也可以设置为:消费任意一个group的服务。
<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />
24.2 Dubbo的version属性
- 当一个接口的实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
<!-- 机器A提供1.0.0版本服务 -->
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
<!-- 机器B提供2.0.0版本服务 -->
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
<!-- 机器C消费1.0.0版本服务 -->
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
<!-- 机器D消费2.0.0版本服务 -->
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
- 消费者消费服任意版本的服务时:
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
- 接口升级时,要注意方法:
- 在低压力时间段,先升级一半的提供者为新版本;
- 再将所有的消费者升级为新版本;
- 然后将剩下的一半提供者升级为新版本。
25 Dubbo怎么实现灰度发布?
客户端在发起RPC服务调用之前,在客户端首先从服务器列表中选择一个服务调用者,包含如下关键角色:
1、Directory
服务的动态发现,通常基于注册中心进行服务的动态注册与发现,其具体实现类为RegistryDirectory。
2、Router
路由实现,其含义是根据Directory发现的所有服务提供者列表中,进行路由选择,也就是根据一定的路由规则选择合适的服务提供者,为Directory发现的服务提供者列表子集,可以基于Condition或脚本(默认为JS脚本,其实现类为ScriptRouter)。
3、LoadBalance
负载均衡机制,其作用主要是根据负载均衡算法(随机、轮询)
等算法,从(Directory–>Router)中返回的服务提供者列表中选择一个服务提供者,进行本次的RPC服务调用。
4、Cluster
集群(容错机制),就是当从服务提供者列表中按照负载均衡算法选择一个服务提供者,进行RPC服务调用后,发送了异常后的策略,例如failover(重试)、failfast(快速失败)等。
服务的灰度发布,其目标是希望根据请求,某些请求走新版本服务器,某些请求走旧版本服务器,其本质就是路由机制,即通过一定的条件来缩小服务的服务提供者列表,正好与Dubbo的Router相吻合。