跳到主要内容

微服务篇

微服务面试相关问题整理

什么是单体架构?

将业务的所有功能集中在一个项目中开发,打成一个包部署。

单体架构的优缺点如下:

优点:

  • 架构简单
  • 部署成本低

缺点:

  • 耦合度高(维护困难、升级困难)

什么是分布式架构?

分布式架构就是根据业务功能做拆分,每个业务功能模块作为独立项目开发,称为一个服务,将不同的业务提取出来部署在不同的服务器上。

① 分布式架构的出现是为了用廉价的,普通的及其完成单个计算机无法完成的计算,存储任务, 其目的就是利用更多的机器,处理更多的数据。

② 单个服务器无法承受大量的服务,对于大量的用户进行访问是无法处理这种流量,此时就需要利用 更多的机器来分担这种流量访问的压力,因此此时就利用到了"分布式架构"将 每一个不同的服务部 署在不同机器上,来处理不同的请求,缓解单个服务器被访问压力。

③ 只有单个节点的处理能力无法满足需求(内存+磁盘+cpu),才需要用到分布式,因为分布式系统要解决 的问题本身适合单机系统一样的,但是由于分布式系统多节点,在网络通信中,会出现很多单机系统没有的问题。

  1. 将一个大的系统划分为多个业务模块,业务模块分别部署到不同的机器上,各个业务模块之间通过接口进行数据交互。 区别分布式的方式是根据不同机器不同业务。

分布式架构的优缺点:

优点:

  • 降低服务耦合
  • 有利于服务升级和拓展

缺点:

  • 服务调用关系错综复杂

分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:

  • 服务拆分的粒度如何界定?
  • 服务之间如何调用?
  • 服务的调用关系如何管理?

人们需要制定一套行之有效的标准来约束分布式架构。

微服务架构特点是什么?

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务, 服务之间互相协调,互相配合,为用户提供最终价值。

特点:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
  • 自治:团队独立、技术独立、数据独立,独立部署和交付
  • 面向服务:服务提供统一标准的接口,与语言和技术无关
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。

因此,可以认为微服务是一种经过良好架构设计的分布式架构方案

服务怎么拆分?

选择合适的拆分粒度:一般基于业务功能进行拆分。

微服务拆分时的几个原则:

  • 单一职责:不同微服务,不要重复开发相同业务
  • 数据独立:微服务数据独立,不要访问其它微服务的数据库
  • 面向服务:微服务可以将自己的业务暴露为接口,供其它微服务调用

SpringCloud 常见组件有哪些?

问题说明:这个题目主要考察对 SpringCloud 的组件基本了解

难易程度:简单

参考话术

SpringCloud 包含的组件很多,有很多功能是重复的。其中最常用组件包括:

•注册中心组件:Eureka、Nacos 等

•负载均衡组件:Ribbon

•远程调用组件:OpenFeign

•网关组件:Zuul、Gateway

•服务保护组件:Hystrix、Sentinel

•服务配置管理组件:SpringCloudConfig、Nacos

服务间是怎么远程调用的?

RestTemplate 到 Open Feign,只需要知道服务 ID 和接口路径就可以调用服务。

这里要引入服务的注册中心了,可以使用 Eureka 或 Nacos,见 问题 1.5.Nacos 与 Eureka 的区别有哪些?

调用时结合负载均衡组件,发起远程调用时会被 LoadBalancerInterceptor 拦截,从注册中心拉取服务列表,根据负载均衡算法得到真实的服务地址信息,替换服务 ID,请求到服务对应实例。

相关负载均衡算法:轮询、权重、Zone 机房轮询、随机、选择并发低的服务器等等。

Nacos 的健康检测有两种模式

  • ==临时实例==:
    • 采用客户端心跳检测模式,心跳周期 5 秒
    • 心跳间隔超过 15 秒则标记为不健康
    • 心跳间隔超过 30 秒则从服务列表删除
  • ==永久实例==:
    • 采用服务端主动健康检测方式
    • 周期为 2000 + 5000 毫秒内的随机数
    • 检测异常只会标记为不健康,不会删除

Nacos 的服务发现分为两种模式

  • 模式一:==主动拉取模式==,消费者定期主动从 Nacos 拉取服务列表并缓存起来,再服务调用时优先读取本地缓存中的服务列表。
  • 模式二:==订阅模式==,消费者订阅 Nacos 中的服务列表,并基于 UDP 协议来接收服务变更通知。当 Nacos 中的服务列表更新时,会发送 UDP 广播给所有订阅者。

与 Eureka 相比,Nacos 的订阅模式服务状态更新更及时,消费者更容易及时发现服务列表的变化,剔除故障服务。

那么为什么 Nacos 有临时和永久两种实例呢?

以淘宝为例,双十一大促期间,流量会比平常高出很多,此时服务肯定需要增加更多实例来应对高并发,而这些实例在双十一之后就无需继续使用了,采用临时实例比较合适。而对于服务的一些常备实例,则使用永久实例更合适。

Nacos 与 Eureka 的区别有哪些?

参考话术

Nacos 与 Eureka 有相同点,也有不同之处,可以从以下几点来描述:

  • 接口方式:Nacos 与 Eureka 都对外暴露了 Rest 风格的 API 接口,用来实现服务注册、发现等功能
  • 实例类型:Nacos 的实例有永久和临时实例之分;而 Eureka 只支持临时实例
  • 健康检测:Nacos 对临时实例采用心跳模式检测,对永久实例采用主动请求来检测,eureka 是==30==秒,Nacos 是==5==秒;Eureka 只支持心跳模式
  • 服务发现:Nacos 支持定时拉取和订阅推送两种模式;Eureka 只支持定时拉取模式

另外,nacos 还支持做服务的配置中心

Nacos 的服务注册表结构是怎样的?

Nacos 采用了数据的分级存储模型,最外层是 Namespace,用来隔离环境。然后是 Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此 Service 下有多个集群(Cluster),Cluster 下是不同的实例(Instance)。

对应到 Java 代码中,Nacos 采用了一个多层的 Map 来表示。结构为 Map<String, Map<String, Service>>,其中最外层 Map 的 key 就是 namespaceId,值是一个 Map。内层 Map 的 key 是 group 拼接 serviceName,值是 Service 对象。Service 对象内部又是一个 Map,key 是集群名称,值是 Cluster 对象。而 Cluster 对象内部维护了 Instance 的集合。

Nacos 如何支撑阿里内部数十万服务注册压力?

Nacos 内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。

Nacos 如何保证并发写的安全性?

首先,在注册实例时,会对 service 加锁,不同 service 之间本身就不存在并发写问题,互不影响。相同 service 时通过锁来互斥。并且,在更新实例列表时,是基于异步的线程池来完成,而线程池的线程数量为 1.

Nacos 如何避免并发读写冲突问题?

Nacos 在更新实例列表时,会采用 CopyOnWrite 写时复制技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的实例列表来覆盖旧的实例列表。

这样在更新的过程中,就不会对读实例列表的请求产生影响,也不会出现脏读问题了。

分布式事务 Seata

Seata 事务管理中有三个重要的角色:

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

image-20210724172326452

Seata 基于上述架构提供了四种不同的分布式事务解决方案:

  • XA 模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • AT 模式:最终一致的分阶段事务模式,无业务侵入,也是 Seata 的默认模式
  • TCC 模式:最终一致的分阶段事务模式,有业务侵入
  • SAGA 模式:长事务模式,有业务侵入

无论哪种方案,都离不开 TC,也就是事务的协调者。

XA 模式

两阶段提交:

  • 一阶段:事务协调组通知每个事务参与者执行本地事务,本地事务执行完成后报告事务执行状态给事务协调者,此时事务是不提交的,继续持有数据库锁
  • 二阶段:如果一阶段都成功,通知所有事务参与者提交事务;否则,通知所有事务参与者回滚

优点:

  • 事务强一致性,满足 ACID 原则。
  • 常用数据库都支持,实现简单,没有代码侵入。

缺点:

  • 性能差,阶段一锁定资源,阶段二才释放
  • 依赖关系型数据库实现事务

AT 模式

AT 模式同样是分阶段提交的事务模型,不过缺弥补了 XA 模型中资源锁定周期过长的缺陷。

两阶段提交:

  • 一阶段:类似 XA 模式,但是执行事务前会先对数据库做一个快照 undo-log,然后分支事务直接执行并提交。
  • 二阶段:如果一阶段都成功,删除 undo log;否则,根据 undo log 回滚数据

AT 模式的优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能比较好
  • 利用全局锁实现读写隔离
  • 没有代码侵入,框架自动完成回滚和提交

AT 模式的缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比 XA 模式要好很多

简述 AT 模式与 XA 模式最大的区别是什么?

  • XA 模式一阶段不提交事务,锁定资源;AT 模式一阶段直接提交,不锁定资源。
  • XA 模式依赖 数据库机制 实现回滚;AT 模式利用 数据快照 实现数据回滚。
  • XA 模式强一致;AT 模式最终一致。

TCC 模式

TCC 模式与 AT 模式非常相似,每阶段都是独立事务,不同的是 TCC 通过人工编码来实现数据恢复。需要实现三个方法:

  • Try:资源的检测和预留;

  • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。

  • Cancel:预留资源释放,可以理解为 try 的反向操作。

TCC 模式的每个阶段是做什么的?

  • Try:资源检查和预留
  • Confirm:业务执行和提交
  • Cancel:预留资源的释放

TCC 的优点是什么?

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比 AT 模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC 的缺点是什么?

  • 有代码侵入,需要人为编写 try、Confirm 和 Cancel 接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑 Confirm 和 Cancel 的失败情况,做好幂等处理

SAGA 模式

在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

Saga 也分为两个阶段:

  • 一阶段:直接提交本地事务
  • 二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚

优点:

  • 事务参与者可以基于事件驱动实现异步调用,吞吐高
  • 一阶段直接提交事务,无锁,性能好
  • 不用编写 TCC 中的三个阶段,实现简单

缺点:

  • 软状态持续时间不确定,时效性差
  • 没有锁,没有事务隔离,会有脏写

四种模式对比

image-20210724185021819

微服务保护 Sentinel

簇点链路:当请求进入微服务时,首先会访问 DispatcherServlet,然后进入 Controller、Service、Mapper,这样一个调用链就叫做簇点链路。簇点链路中被监控的每一个接口就是一个资源。

四大功能:流量控制、隔离和降级、热点参数、授权规则。

  • 流控:流量控制
  • 降级:降级熔断
  • 热点:热点参数限流,是限流的一种
  • 授权:请求的权限控制

流量控制

流控模式:==直接==、==关联== 和 ==链路==。

  1. 直接:统计当前资源的请求,触发阈值时对当前资源限流。默认。
  2. 关联:统计与当前资源相关联的另一个资源的请求,触发阈值时对当前资源限流。
    • 场景:修改和查询订单,会导致争抢数据库锁。业务需求是优先更新订单,因此更新订单触发阈值时,对查询订单业务限流。
  3. 链路:统计从指定链路访问到当前资源的请求,触发阈值时对指定链路限流。

流控效果:==快速失败==、==warm up== 和 ==排队等待==。

  1. 快速失败:到达阈值后,新的请求会被立即拒绝并抛出 FlowException 异常。默认。
  2. warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常,但这种模式阈值会动态变化,从一个较小值逐渐增加到最大值。
  3. 排队等待:让所有请求按照先后次序排队进行,两个请求的间隔不能小于指定时长。

热点参数限流:分别统计 参数值 相同的请求,判断是否超过阈值。

隔离和降级

都是通过调用方发起远程调用来实现的,所以需要配合 Feign 来实现。编写失败降级逻辑:实现 FallbackFactory 接口,重写对应 FeignClient 的方法。

线程隔离(舱壁模式),调用者在调用服务提供者时,给每个调用的请求分配独立的线程池,出现故障时,最多消耗这个线程池内的资源,避免把调用者的所有资源耗尽。

限流时选择阈值类型为线程数,是采用线程数隔离,信号量隔离(Sentinel 默认采用),计数器模式记录业务的线程数量。

熔断降级:是在调用方这边加入断路器,统计对服务提供者的调用,如果调用的失败比例过高,则熔断该业务,不允许访问该服务的提供者了。

断路器熔断策略有三种:==慢调用==、==异常比例==、==异常数==。

  • 慢调用:RT 大于指定时长的请求就是一次慢调用请求。在指定时间内,大于设定的阈值,触发熔断。
  • 异常比例和异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

授权规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。配合网关做授权。

规则持久化

  • 原始模式:保存在内存
  • pull 模式:客户端的内存和本地文件存储规则
  • push 模式:规则推送到配置中心,客户端监听 nacos 配置变更,完成本地配置更新

Sntinel 的限流与 Gateway 的限流有什么差别?

限流算法常见的有三种实现:滑动时间窗口、令牌桶算法、漏桶算法。

Gateway 则采用了基于 Redis 实现的令牌桶算法。

而 Sentinel 内部却比较复杂:

  • 默认限流模式是基于滑动时间窗口算法
  • 排队等待的限流模式则基于漏桶算法
  • 而热点参数限流则是基于令牌桶算法

Sentinel 的线程隔离与 Hystix 的线程隔离有什么差别?

问题说明:考察对线程隔离方案的掌握情况

难易程度:一般

参考话术

Hystix 默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的 CPU 开销,性能一般,但是隔离性更强。

Sentinel 是基于信号量(计数器)实现的线程隔离,不用创建线程池,性能较好,但是隔离性一般。