《Java 后端面试经》专栏文章索引:
《Java 后端面试经》Java 基础篇
《Java 后端面试经》Java EE 篇
《Java 后端面试经》数据库篇
《Java 后端面试经》多线程与并发编程篇
《Java 后端面试经》JVM 篇
《Java 后端面试经》操作系统篇
《Java 后端面试经》Linux 篇
《Java 后端面试经》设计模式篇
《Java 后端面试经》计算机网络篇
《Java 后端面试经》微服务篇
《Java 后端面试经》微服务篇
- SpringCloud
-
- SpringCloud 是什么?它有哪些组件?
- SpringCloud 和 Dubbo 区别?
- SpringBoot 和 SpringCloud 的区别?
- 分布式
-
- 什么是 CAP 定理
-
- 谈谈对分布式事务的了解
- 分布式锁有哪几种实现方案?
-
- 追问:Redis 和 Zookeeper 实现分布式锁的区别?
- 分布式的幂等性?
- 微服务
-
- 单片,SOA 和微服务架构有什么区别?
- SOA 与微服务的主要区别?
- 谈谈对微服务的理解,微服务有哪些优缺点?
- 微服务中的相关概念?
- 微服务中注册中心有什么作用?
- Eureka
-
- 谈谈 Eureka 的基本架构
- 什么是 Eureka 的自我保护模式
- Eureka 中的自我保护是怎样实现的?
- Eureka 中的元数据有哪几种?
- 在 Eureka 中为什么注册一个服务这么慢?
- Eureka 和 ZooKeeper 都可以提供服务注册与发现的功能,它们有什么区别呢?
- Dubbo
-
- 什么是 RPC? 为什么需要 RPC?
- 简单谈谈 RPC 的工作原理?
- HTTP 和 RPC 区别?
- 什么是 Dubbo?
- Dubbo 架构中的核心角色有哪些?
- 谈谈一次 Dubbo 服务请求流程?
- 谈谈 Dubbo 的工作原理?
- 服务调用是阻塞的吗?
- 注册中心挂了,consumer 还能不能调用 provider?
- Dubbo 的负载均衡策略有哪些?
- Dubbo 容错策略有哪些?
- Dubbo 是如何做到动态感知服务下线的?
- Zookeeper
-
- Zookeeper 的工作机制?
- 简单介绍一下 Zookeeper 的文件系统
- Zookeeper 的特点有哪些?
- Zookeeper 有哪些应用场景?
- ZooKeeper 有哪些节点类型?
- 谈谈 Zookeeper 中的权限控制?
- 请描述一下 Zookeeper 的通知机制是什么?
- ZooKeeper 对节点的 watch 监听通知是永久的吗?
- ZooKeeper 中使用 watch 的注意事项有哪些?
- ZooKeeper 集群中有哪些角色?
- Zookeeper 集群中 leader 的选举机制?
- ZooKeeper 集群中 Server 有哪些工作状态?
- ZooKeeper 集群为啥最好奇数台(2n-1,n>=2)?
- ZooKeeper 怎么保证主从节点的状态同步?
- ZooKeeper 是如何保证事务的顺序一致性的呢?
- ZooKeeper 集群中各服务器之间是怎样通信的?
- ZooKeeper 分布式锁怎么实现的?
- 谈谈 ZAB 协议
SpringCloud
SpringCloud 是什么?它有哪些组件?
SpringCloud 的本质是在 SpringBoot 的基础上,增加了一堆微服务相关的规范,并对应用上下文 (ApplicationContext) 进行了功能增强。既然 SpringCloud 是规范,那么就需要去实现,目前SpringCloud 规范已有 Spring 官方、Spring Cloud Netflix、Spring Cloud Alibaba 等实现。通过组件化的方式,Spring Cloud 将这些实现整合到一起构成全家桶式的微服务技术栈。
(1) SpringCloud Netflix 组件
组件名称 |
|
Eureka |
服务注册中心 |
Ribbon |
客户端负载均衡 |
Feign |
声明式服务调用 |
Hystrix |
客户端容错保护 |
Zuul |
API 服务网关 |
(2) SpringCloud Alibaba 组件
组件名称 |
作用 |
Nacos |
服务注册中心 |
Sentinel |
客户端容错保护 |
(3) SpringCloud 原生及其他组件
组件名称 |
作用 |
Consul |
服务注册中心 |
Config |
分布式配置中心 |
Gateway |
API 服务网关 |
Sleuth/Zipkin |
分布式链路追踪 |
SpringCloud 和 Dubbo 区别?
- 服务调用方式:Dubbo 是 RPC 调用,SpringCloud 是 REST API 调用。
- 注册中心:Dubbo 是 Zookeeper 作为注册中心,SpringCloud 是 Eureka 或者 Zookeeper、Nacos.
- 服务网关:Dubbo 本身没有实现服务网关,只能通过第三方技术整合,SpringCloud 有 Zuul 网关。
SpringBoot 和 SpringCloud 的区别?
- SpringBoot 专注于快速、方便的开发单个微服务个体,SpringCloud 关注全局的服务治理框架。
- SpringCloud 关注全局的微服务协调整理治理框架,它将 SpringBoot 开发的一个个单体服务整合并管理起来,为各个微服务之间提供配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。
- SpringBoot 可以离开 SpringCloud 独立使用开发项目, 但是 SpringCloud 离不开SpringBoot ,属于依赖的关系。
分布式
什么是 CAP 定理
CAP 定理指出对于一个分布式系统来说,当网络出现分区的时候,进行设计读写操作时,需要同时满足以下三点中的两点:
- 一致性(Consistency):所有的节点在同一时间都能看到相同的数据,数据的更新是一致的,所有数据的变化都是同步的。
- 可用性(Availability):即使集群中的部分节点出现故障,服务依然一直可用,能够保证在正常的响应时间内对客户端的读写请求进行响应。
- 分区容错性(Partition tolerance):当分布式系统出现网络分区的时候,仍然能够对外提供服务。
CAP 理论中分区容错性(Partition tolerance) 是一定要满足的,在此基础上,只能满足一致性(Consistency) 或者可用性(Availability)。
追问:什么叫网络分区?
在分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区。
追问:CAP 应用案例?
- ZooKeeper 保证的是 CP: 任何时刻对 ZooKeeper 的读请求都能得到一致性的结果,但是,ZooKeeper 不保证每次请求的可用性。例如,在 Leader 选举过程中或者半数以上的机器不可用的时候服务就是不可用的。
- Eureka 保证的则是 AP: Eureka 在设计的时候就是优先保证 A (可用性)。在 Eureka 中不存在什么 Leader 节点,每个节点都是一样的、平等的。因此 Eureka 不会像 ZooKeeper 那样出现选举过程中或者半数以上的机器不可用的时候,服务就是不可用的情况。 Eureka 保证即使大部分节点挂掉也不会影响正常提供服务,只要有一个节点是可用的就行,只不过这个节点上的数据可能并不是最新的。
- Nacos 不仅支持 CP 也支持 AP。
谈谈对分布式事务的了解
- 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
- 简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式锁有哪几种实现方案?
定义:分布式锁其实就是控制分布式系统不同进程共同访问共享资源的一种锁的实现,如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证数据一致性。
分布式锁应该具备的条件:
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
- 高可用、高性能的获取锁与释放锁
- 具备可重入特性,具备锁失效机制(防止死锁),具备非阻塞锁特性(即没有获取到锁将直接返回获取锁失败)
分布式锁的实现方案:
(1)基于数据库实现分布式锁:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
(2)基于 Redis 实现分布式锁:
- 获取锁的时候,使用
setnx
加锁,并使用 expire
命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的 value 值为一个随机生成的 UUID,通过此在释放锁的时候进行判断。
- 获取锁的时候再设置一个获取的超时时间,若超过这个时间则放弃获取锁。
- 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。
(3)基于 Zookeeper 实现分布式锁:
ZooKeeper 是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名,基于 ZooKeeper 实现分布式锁的步骤如下:
- 创建一个临时顺序节点,称为 mylock
- 线程 A 想获取锁就在 mylock 节点下创建临时顺序节点。获取 mylock 节点下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
- 线程 B 获取所有节点,如果判断自己不是最小节点,设置监听比自己次小的节点
- 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁
追问:Redis 和 Zookeeper 实现分布式锁的区别?
- redis 实现的分布式锁性能高,适合高并发场景;zookeeper 实现分布式锁性能较低
- redis 实现的分布式锁轻量级,zookeeper 实现的分布式锁属于重量级锁
- zookeeper 实现的分布式锁比 redis 实现的分布式锁更可靠
分布式的幂等性?
微服务
单片,SOA 和微服务架构有什么区别?
- 单片架构类似于大容器,其中应用程序的所有软件组件组装在一起并紧密封装。
- SOA(面向服务的架构)是一种相互通信服务的集合,通信可以涉及简单的数据传递,也可以涉及两个或多个协调某些活动的服务。
- 微服务架构是一种架构风格,它将应用程序构建为以业务域为模型的小型自治服务集合。
SOA 与微服务的主要区别?
功能 |
SOA |
微服务 |
组件大小 |
大块业务逻辑 |
单独任务或小块业务逻辑 |
耦合 |
通常松耦合 |
总是松耦合 |
公司架构 |
任何类型 |
小型、专注于功能交叉团队 |
管理 |
着重中央管理 |
着重分散管理 |
目标 |
确保应用能够交互操作 |
执行新功能、快速拓展开发团队 |
谈谈对微服务的理解,微服务有哪些优缺点?
微服务是一种架构风格,通过将大型单体应用划分为较小的服务单元,从而降低整个系统的复杂度。
微服务架构的优点:
- 服务部署更灵活:每个应用都可以是一个独立的项目,可以独立部署,不依赖于其他服务,耦合度低。
- 技术更新灵活:在大型单体应用中,技术要进行更新,往往是非常困难的,而微服务可以根据业务特点,灵活选择技术栈。
- 应用的性能得到提高:在大型单体应用中,往往启动就会成为一个很大的难关,而采用微服务后,整个系统启动速度得到提升。
- 更容易组合专门的团队:在单体应用中,团队成员往往需要对系统的各个部分都要有深入的了解,门槛很高,而采用微服务之后,可以给每个微服务组建专门的团队。
- 代码复用:很多底层服务可以使用 REST API 的方式对外提供统一的服务,所有基础服务可以在整个微服务系统中通用。
微服务架构的缺点:
- 微服务的复杂性提高:网络问题、容错问题、负载均衡问题、高并发问题
- 分布式事务:尽量不要使用微服务事务。
- 运维的难度提升: 单体架构只要维护一个环境,而微服务有多个环境,并且运维方式还都不一样,所以对部署、监控、告警等要求就会变得非常困难。
微服务中的相关概念?
- 服务注册:服务实例将自身服务信息注册到注册中心,这部分服务信息包括服务所在主机 IP 地址和提供服务的端口号,以及暴露服务自身状态和访问协议等信息。
- 服务发现:服务实例请求注册中心获取所依赖服务信息。服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。
- 负载均衡:负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性。
- 服务熔断:熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用,这种牺牲局部,保全整体的措施就叫做熔断。
- 链路追踪:随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要对一次请求涉及的多个服务链路进行日志记录,性能监控即链路追踪。
- API 网关:API 网关字面意思是将所有 API 调用统一接入到 API 网关层,由网关层统一接入和输出。一个网关的基本功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后,各个 API 服务提供团队可以专注于自己的的业务逻辑处理,而API网关更专注于安全、流量、路由等问题。
微服务中注册中心有什么作用?
服务注册中心是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用,注册中心一般包含如下几个功能:
- 服务配置:
1.1 配置订阅:服务提供者和服务调用者订阅微服务相关的配置。
1.2 配置下发:主动将配置推送给服务提供者和服务调用者。
- 服务发现:
2.1 服务注册/反注册:保存服务提供者和服务调用者的信息。
2.2 服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能。
2.3 服务路由(可选):具有筛选整合服务提供者的能力。
- 健康检测:
3.1 检测服务提供者的健康情况。
Eureka
谈谈 Eureka 的基本架构
Eureka 包含两个组件:Eureka Server 和 Eureka Client,它们的作用如下:
- Eureka Client 是一个 Java 客户端,用于简化与 Eureka Server 的交互
- 微服务启动后,会周期性地向 Eureka Server 发送心跳(默认周期为 30 秒)以续约自己的信息。如果 Eureka Server 在一定时间内没有接收到某个微服务节点的心跳,Eureka Server 将会注销该微服务节点(默认 90 秒)
- 每个 Eureka Server 同时也是 Eureka Client,多个 Eureka Server 之间通过复制的方式完成服务注册表的同步
- Eureka Client 会缓存 Eureka Server 中的信息,即使所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者
- Eureka 通过心跳检测、健康检查和客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用性
什么是 Eureka 的自我保护模式
- 默认情况下,如果 Eureka Service 在一定时间内没有接收到某个微服务的心跳,Eureka Service 会进入自我保护模式。
- 在该模式下 Eureka Service 会保护服务注册表中的信息,不再删除注册表中的数据,当网络故障恢复后,Eureka Service 会自动推出自我保护模式。
Eureka 中的自我保护是怎样实现的?
- 微服务第一次注册成功之后,每 30 秒会发送一次心跳将服务的实例信息注册到注册中心,通知 Eureka Server 该实例仍然存在。如果超过 90 秒没有发送更新,则服务器将从注册信息中将此服务移除。
- Eureka Server在运行期间,会统计心跳失败的比例在 15 分钟之内是否低于 85%,如果出现低于的情况(在单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),Eureka Server 会将当前的实例注册信息保护起来,同时提示这个警告。
- 保护模式主要用于一组客户端和 Eureka Server 之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server 将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。
- 可以通过设置
eureka.enableSelfPreservation=false
来关闭自我保护功能。
Eureka 中的元数据有哪几种?
Eureka 的元数据有两种:标准元数据和自定义元数据:
- 标准元数据:主机名、IP地址、端口号、状态页和健康检查等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
- 自定义元数据:可以使用
eureka.instance.metadata-map
配置,符合 KEY/VALUE 的存储格式,这些元数据可以在远程客户端中访问。
在 Eureka 中为什么注册一个服务这么慢?
- 这是因为在 Eureka 中,服务的注册涉及到心跳连接,默认为每 30 秒一次。
- 只有当 Eureka 服务端和客户端本地缓存中的服务元数据相同时这个服务才能被其它客户端发现,这需要 3 个心跳周期。
- 可以通过参数
eureka.instance.leaseRenewalIntervalInSeconds
调整这个时间间隔来加快这个过程。
Eureka 和 ZooKeeper 都可以提供服务注册与发现的功能,它们有什么区别呢?
- ZooKeeper 中的节点服务挂了就要重新选举,在选举期间注册服务瘫痪(虽然服务最终会恢复,但是选举期间不可用的),选举就是改微服务做了集群,必须有一台主其他的都是从。
- Eureka 各个节点是平等关系,服务器挂了没关系,只要有一台 Eureka 就可以保证服务可用,数据都是最新的。 如果查询到的数据并不是最新的,就是因为 Eureka 的自我保护模式导致的。
- Eureka 本质上是一个工程,而 ZooKeeper 只是一个进程。
- Eureka 可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像 ZooKeeper 一样使得整个注册系统瘫痪。
- ZooKeeper 保证的是 CP,Eureka 保证的是 AP.
Dubbo
什么是 RPC? 为什么需要 RPC?
RPC(Remote Procedure Call) 即远程过程调用,它是一种概念。它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。简言之,RPC 使得程序能够像访问本地系统资源一样,去访问远端系统资源。
当两个不同的服务器上的服务提供的方法不在一个内存空间,就需要通过网络编程才能传递方法调用所需要的参数。并且,方法调用的结果也需要通过网络编程来接收。但是,如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的,因为,我们需要考虑底层传输方式(TCP还是UDP)、序列化方式等等方面。RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。
简单谈谈 RPC 的工作原理?
RPC 功能的实现依赖于它的组件,其中几个核心的组件有:
- 客户端(服务消费端):调用远程方法的一端。
- 客户端 Stub(桩) : 这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
- 网络传输 :网络传输就是你要把你调用的方法的信息(参数)传输到服务端,然后服务端执行完之后再把返回结果通过网络传输回来。网络传输的实现方式有很多种比如最近基本的 Socket 或者性能以及封装更加优秀的 Netty(推荐)。
- 服务端 Stub(桩) :这个桩就不是代理类。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去指定对应的方法然后返回结果给客户端的类。
- 服务端(服务提供端):提供远程方法的一端。
RPC 的工作过程:
- 服务消费端(client) 以本地调用的方式调用远程服务。
- 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):RpcRequest.
- 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端。
- 服务端 Stub(桩) 收到消息将消息反序列化为 Java 对象: RpcRequest.
- 服务端 Stub(桩) 根据 RpcRequest 中的类、方法、方法参数等信息调用本地的方法。
- 服务端 Stub(桩) 得到方法执行结果并将组装成能够进行网络传输的消息体:RpcResponse(序列化) 发送至消费方。
- 客户端 Stub(client stub) 接收到消息并将消息反序列化为 Java 对象: RpcResponse ,得到返回结果。
HTTP 和 RPC 区别?
- 传输协议:RPC 可以基于 TCP 协议,也可以基于 HTTP 协议。HTTP 基于 HTTP 协议。
- 负载均衡:RPC 基本都自带了负载均衡策略,HTTP 需要配置 Nginx 来实现。
- 服务治理:RPC 能做到自动通知,不影响上游,HTTP 需要事先通知,修改 Nginx 配置。
- 性能消耗:RPC 可以基于 thrift 实现高效的二进制传输,HTTP 大部分是通过 json 来实现的,字节大小和序列化耗时都比 thrift 要更消耗性能。
- 传输效率:RPC 使用自定义的 TCP 协议,可以让请求报文体积更小,提高传输效率 HTTP 请求中会包含很多无用的内容。
- 调用环境:RPC 通常用于调用公司内部服务调用,HTTP 主要用于对外的异构环境、浏览器接口调用、APP 接口调用。
什么是 Dubbo?
Dubbo 是一款高性能、轻量级的开源 Java RPC 框架。Dubbo 提供了六大核心能力:
- 面向接口代理的高性能 RPC 调用
- 智能容错和负载均衡
- 服务自动注册和发现
- 高度可扩展能力
- 运行期流量调度
- 可视化的服务治理与运维
Dubbo 架构中的核心角色有哪些?
- Container: 服务运行容器,负责加载、运行服务提供者。
- Provider: 暴露服务的服务提供方,会向注册中心注册自己提供的服务。
- Consumer: 调用远程服务的服务消费方,会向注册中心订阅自己所需的服务。
- Registry: 服务注册与发现的注册中心。注册中心会返回服务提供者地址列表给消费者。
- Monitor: 统计服务的调用次数和调用时间的监控中心。服务消费者和提供者会定时发送统计数据到监控中心。
- 其中,Registry 和 Monitor 是可选的,不是一定需要。
谈谈一次 Dubbo 服务请求流程?
Dubbo 的架构如下图所示:
Dubbo 请求服务过程中的调用链如下图所示:
生产者的调用过程:
- 首先,生产者启动,初始化服务实例,通过 Proxy 组件调用具体协议,把服务端需要暴露的接口封装成 Invoker,然后转换成 Exporter。
- 之后,框架会打开服务端口等并记录服务实例到内存中,最后通过 Registry 把服务元数据注册到注册中心,这就是服务端整个接口暴露的过程。
消费者的调用过程:
- 首先,调用过程也是从一个 Proxy 开始的,Proxy 持有了一个 Invoker 对象,然后触发 invoke 调用。
- 其次,在 invoke 调用过程中,需要使用 Cluster,Cluster 负责容错,如调用失败的重试,Cluster 在调用之前会通过 Directory 获取所有可以调用的远程服务 invoker 列表(一个接口可能有多个节点提供服务)。由于可以调用的远程服务很多,此时如果用户配置了路由规则,那么还会根据路由规则将 Invoker 列表过滤一遍。
- 之后,再通过 LoadBalance 方法做负载均衡,最终选出一个可以调用的 Invoker。这个 Invoker 在调用之前又会经过一个过滤器链,这个过滤器链通常是处理上下文,限流,计数等。
- 然后,会使用 Client 做数据传输,如我们常见的 Netty Client 等,对数据包进行序列化,然后传输到服务提供者。服务提供者收到数据包,再对完整的数据报文做反序列化的处理。
- 最后,request 请求会被分配到线程池中进行处理,Server 处理这些 Request,根据请求查找对应的 Exporter,再经过一个服务提供者的过滤器链我们最终就得到了一个具体接口的真实实现并调用。
谈谈 Dubbo 的工作原理?
Dubbo 的整体设计如下图所示:
图例说明:
图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法
- config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
- proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
- monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
- protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
- exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
- serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
服务调用是阻塞的吗?
- 默认是阻塞的,可以异步调用,没有返回值的可以这么做。
- Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
注册中心挂了,consumer 还能不能调用 provider?
可以调用。因为刚开始初始化的时候,consumer 会将需要的所有提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信。但是 provider 挂了,那就没法调用了。
Dubbo 的负载均衡策略有哪些?
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。具体实现上,Dubbo 提供的是客户端负载均衡,即由 Consumer 通过负载均衡算法得出需要将请求提交到哪个 Provider 实例。
目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:
算法 |
特性 |
说明 |
RandomLoadBalance |
加权随机 |
默认算法,默认权重相同 |
RoundRobinLoadBalance |
加权轮询 |
借鉴于 Nginx 的平滑加权轮询算法,默认权重相同 |
LeastActiveLoadBalance |
最少活跃优先 + 加权随机 |
背后是能者多劳的思想 |
ShortestResponseLoadBalance |
最短响应优先 + 加权随机 |
更加关注响应速度 |
ConsistentHashLoadBalance |
一致性 Hash |
确定的入参,确定的提供者,适用于有状态请求 |
Dubbo 容错策略有哪些?
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 Failover 重试。
- Failover:失败自动切换,当出现失败时,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过
retries="2"
来设置重试次数(不含第一次)
- Failfast:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
- Failsafe :失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
- Failback:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
- Forking :并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过
forks="2"
来设置最大并行数
Dubbo 是如何做到动态感知服务下线的?
服务订阅通常有 pull 和 push 两种方式:
- pull 模式需要客户端定时向注册中心拉取配置
- push 模式采用注册中心主动推送数据给客户端
DubboZookeeper 注册中心采用是事件通知与客户端拉取方式。服务第一次订阅的时候将会拉取对应目录下全量数据,然后在订阅的节点注册一个 watcher. 一旦目录节点下发生任何数据变化,Zookeeper 将会通过 watcher 通知客户端。客户端接到通知,将会重新拉取该目录下全量数据,并重新注册 watcher。利用这个模式,Dubbo 服务就可以就做到服务的动态发现。
注意:Zookeeper 提供了“心跳检测”功能,它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除。
Zookeeper
Zookeeper 的工作机制?
Zookeeper 是一个开源的分布式的,为分布式框架提供协调服务的 Apache 项目。
Zookeeper 从设计模式角度来理解:
- 是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应。
- Zookeeper = 文件系统+通知机制
简单介绍一下 Zookeeper 的文件系统
- Zookeeper 提供一个多层级的节点命名空间(节点称为 znode),类似于 Linux 系统的文件目录,但是这些节点都可以设置关联的数据,而 Linux 文件系统中只有文件节点可以存放数据而目录节点不行。
- Zookeeper 为了保证高吞吐和低延迟,在内存中维护了一个树状的目录结构,这种特性使得 Zookeeper 不能用于存放大量的数据,每个节点的存放数据上限为 1M.
Zookeeper 的特点有哪些?
- Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。
- 集群中只要有半数以上节点存活,Zookeeper 集群就能正常服务,所以 Zookeeper 适合安装奇数台服务器。
- 全局数据一致:每个 Server 保存一份相同的数据副本,Client 无论连接到哪个 Server,数据都是一致的。
- 更新请求顺序执行:来自同一个 Client 的更新请求按其发送顺序依次执行。
- 数据更新原子性:一次数据更新要么成功,要么失败。
- 实时性:在一定时间范围内,Client 能读到最新数据。
Zookeeper 有哪些应用场景?
- 分布式锁:通过创建全局唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。
- 统一命名服务:在分布式环境下,经常需要对应用/服务进行统一命名,便于识别,可以通过 Zookeeper 的顺序节点生成全局唯一 ID.
- 统一配置管理:可将配置信息写入 ZooKeeper 上的一个 znode,各个客户端服务器监听这个 znode,一旦 znode 中的数据被修改,ZooKeeper 将通知各个客户端服务器。
- 数据发布/订阅:通过 watcher 机制 可以很方便地实现数据发布/订阅,当你将数据发布到 Zookeeper 被监听的节点上,其他机器可通过监听 Zookeeper 上节点的变化来实现配置的动态更新。
- 负载均衡:在 Zookeeper 中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求。
ZooKeeper 有哪些节点类型?
Znode 有两种类型:
- 短暂(ephemeral):客户端和服务器端断开连接后,创建的节点自己删除。
- 持久(persistent):客户端和服务器端断开连接后,创建的节点不删除。
Znode 有四种形式的目录节点(默认是 persistent)
- 持久节点:一旦创建就一直存在即使 Zookeeper 集群宕机,直到将其删除。
- 临时节点 :临时节点的生命周期是与客户端会话 (Session) 绑定的,会话消失则节点消失。并且,临时节点只能做叶子节点 ,不能创建子节点。
- 持久顺序节点 :除了具有持久节点的特性之外, 子节点的名称还具有顺序性。比如
/node1/app0000000001 、/node1/app0000000002
- 临时顺序节点 :除了具备临时节点的特性之外,子节点的名称还具有顺序性。
谈谈 Zookeeper 中的权限控制?
ZooKeeper 采用 ACL(Access Control List) 策略来进行权限控制,类似于 UNIX 文件系统的权限控制。
对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
- CREATE : 能创建子节点
- READ :能获取节点数据和列出其子节点
- WRITE : 能设置/更新节点数据
- DELETE : 能删除子节点
- ADMIN : 能设置节点 ACL 的权限
CREATE 和 DELETE 这两种权限都是针对子节点的权限控制。
对于身份认证,提供了以下几种方式:
- world:默认方式,所有用户都可无条件访问。
- auth:不使用任何 id,代表任何已认证的用户。
- digest:用户名:密码认证方式:username:password.
- ip : 对指定 ip 进行限制。
请描述一下 Zookeeper 的通知机制是什么?
客户端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些客户端会收到 zk 的通知,然后客户端可以根据 znode 变化来做出业务上的改变等。
ZooKeeper 对节点的 watch 监听通知是永久的吗?
- 不是永久的。一个 watch 事件是一个一次性的触发器,当被设置了 watch 的数据发生了改变的时候,则服务器将这个改变发送给设置了 watch 的客户端,以便通知它们。
- 为什么不是永久的呢?举个例子,如果服务端变动频繁,而监听的客户端很多情况下,每次变动都要通知到所有的客户端,给网络和服务器造成很大的压力。
- 一般是客户端执行
getData(“/节点 A”, true)
,如果节点 A 发生了变更或删除,客户端会得到它的 watch 事件,但是在之后节点 A 又发生了变更,而客户端又没有设置 watch 事件,就不再给客户端发送。
- 在实际应用中,很多情况下,我们的客户端不需要知道服务端的每一次变动,只要获取到最新的数据即可。
ZooKeeper 中使用 watch 的注意事项有哪些?
- watch 通知是一次性的,必须重复注册。
- 对某个节点注册了 watch,但是节点被删除了,那么注册在这个节点上的 watch 都会被移除。
- 同一个 zk 客户端对某一个节点注册相同的 watch,只会收到一次通知。
- watch 对象只会保存在客户端,不会传递到服务端。
- 发生
CONNECTIONLOSS
之后,只要在 session_timeout
之内再次连接上(即不发生SESSIONEXPIRED
),那么这个连接注册的 watch 依然在。
ZooKeeper 集群中有哪些角色?
在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色,如下图所示:
ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
角色 |
说明 |
Leader |
为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态 |
Follower |
为客户端提供读服务,如果是写服务则转发给 Leader,参与选举过程中的投票 |
Observer |
为客户端提供读服务,如果是写服务则转发给 Leader,不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能,此角色于 ZooKeeper 3.3 系列新增的角色 |
Zookeeper 集群中 leader 的选举机制?
Zookeeper 集群中 leader 的选举发生在两个阶段,分别为服务器启动时选举和服务器运行期间选举。
- SID:服务器 ID,用来唯一标识一台 ZooKeeper 集群中的机器,每台机器不能重复,和 myid 一致。
- ZXID:事务 ID,ZXID 是一个事务 ID,用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的 ZXID 值不一定完全一致,这和 ZooKeeper 服务器对于客户端“更新请求”的处理逻辑有关。
- Epoch:每个 Leader 任期的代号。没有 Leader 时同一轮投票过程中的逻辑时钟值是相同的,每投完一次票这个数据就会增加。
服务器启动时的选举:
- 首先,Server1 启动,发起一次选举。Server1 投自己一票,此时 Server1 票数一票,不够半数以上(3票),选举无法完成,Server1 状态保持为
LOOKING
.
- 其次,Server2 启动,再发起一次选举。Server1 和 Server2 分别投自己一票并交换选票信息:此时 Server1 发现 Server2 的 myid 比自己目前投票推举的(Server1) 大,更改选票为推举 Server2. 此时 Server1 票数 0 票,Server2 票数 2 票,没有半数以上结果,选举无法完成,Server1 ,Server2 状态保持
LOOKING
.
- 之后,Server3 启动,发起一次选举。此时 Server1 和 Server2 都会更改选票为 Server3. 此次投票结果:Server1 为 0 票,Server2 为 0 票,Server1 为 3 票,此时 Server3 的票数已经超过半数,Server3 当选 Leader. Server1,Server2 更改状态为
FOLLOWING
,Server3 更改状态为 LEADING
.
- 然后,Server4 启动,发起一次选举。此时 Server1,Server2,Server3 已经不是
LOOKING
状态,不会更改选票信息。交换选票信息结果:Server3 为 3 票,Server4 为 1 票。此时 Server4 服从多数,更改选票信息为 Server3,并更改状态为 FOLLOWING
.
- 最后,Server5 启动,状态为
FOLLOWING
.
在 ZooKeeper 集群正常运行过程中,一旦选出一个 leader,那么所有服务器的集群角色一般不会再发生变化,当 leader 节点的服务器宕机之后,就需要重新选举新的服务器作为 leader 节点,服务器运行期间的选举过程如下:
- 假设 ZooKeeper 由 5 台服务器组成,SID 分别为 1、2、3、4、5,ZXID 分别为 8、8、8、7、7,并且此时 SID 为 3 的服务器是 leader,某一时刻,3 和 5 服务器出现故障,因此开始进行 leader 选举。
- 首先,就是变更节点的状态。当 leader 挂了之后,余下的非 observer 服务器都会将自己的服务器状态变更为
LOOKING
,然后再进入 Leader 选举流程。
- 之后,开始进行投票。SID 为1、2、4 的机器投票情况 (EPOCH, ZXID, SID) 为 (1,8,1),(1,8,2),(1,7,4)
- 最后,根据选举 leader 规则: ①EPOCH 大的直接胜出 ②EPOCH 相同,事务 id 大的胜出 ③事务 id 相同,服务器 id 大的胜出。
ZooKeeper 集群中 Server 有哪些工作状态?
服务器具有四种工作状态:
- LOOKING:寻找 Leader 状态,当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。
- FOLLOWING:跟随者状态,表明当前服务器角色是 Follower。
- LEADING:领导者状态,表明当前服务器角色是 Leader。
- OBSERVING:观察者状态,表明当前服务器角色是 Observer。
ZooKeeper 集群为啥最好奇数台(2n-1,n>=2)?
- ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。
- 假如集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2.
- 2n 和 2n-1 的容忍度是一样的。 假如有 3 台服务器,那么最大允许宕掉 1 台 ZooKeeper 服务器,当有 4 台服务器的的时候也同样只允许宕掉 1 台。 假如有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果有 6 台的的时候也同样只允许宕掉 2 台。
- 因此,没有必要再增加 1 台服务器。
ZooKeeper 怎么保证主从节点的状态同步?
- Zookeeper 的核心是原子广播机制,这个机制保证了各个服务器之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式:
- 恢复模式:当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态
- 广播模式:一旦 leader 已经和多数的 follower 进行了状态同步后,它就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 ZooKeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。ZooKeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持。
ZooKeeper 是如何保证事务的顺序一致性的呢?
- zookeeper 采用了全局递增的事务 id 来标识,所有的 proposal(提议) 都在被提出的时候加上了 zxid
- zxid 实际上是一个 64 位的数字,高 32 位是 epoch 用来标识 leader 周期,如果有新的 leader 产生出来,epoch 会自增,低 32 位用来递增计数
- 当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行
ZooKeeper 集群中各服务器之间是怎样通信的?
- leader 服务器会和每一个 follower/observer 服务器都建立 TCP 连接,同时为每个 follower/observer 都创建一个叫做
LearnerHandler
的实体。
- LearnerHandler 主要负责 leader 和 follower/observer 之间的网络通讯,包括数据同步,请求转发和 Proposal 提议的投票等,leader 服务器保存了所有 follower/observer 的 LearnerHandler.
ZooKeeper 分布式锁怎么实现的?
- 创建一个临时顺序节点,称为 mylock
- 线程 A 想获取锁就在 mylock 节点下创建临时顺序节点。获取 mylock 节点下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
- 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点
- 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁
谈谈 ZAB 协议
ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
ZAB 协议的两种基本模式:
- 崩溃恢复 :当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。
- 消息广播 :当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。