微服务常见八股(分布式seat, 网关,服务注册与发现、负载均衡、断路器、API 网关、分布式配置中心)

Spring Cloud

常规八股

关于微服务你是怎么理解的

微服务的核心思想是 "单一职责原则",即每个服务专注于完成一个特定的任务,确保服务的高内聚性和低耦合性。可以针对不同服务可以进行不同技术或者语言选型,这会使得开发、部署、维护更加灵活和高效。服务之间的通信一般使用 RPC(远程调用),相比单体应用会带来网络的开销。它的特点是:独立部署,减少了系统整体部署的复杂度,不同的微服务可以使用不同的技术栈,可以灵活扩展并且容错性高。

如何对微服务集群做监控和报警的

1)Prometheus: Prometheus 是一个开源的监控系统,其数据模型较为灵活,且内置了数据查询语言 PromQL(Prometheus Query Language)提供了对时间序列数据丰富的查询,聚合以及逻辑运算能力的支持。使用时可以实时选择和汇总时间序列数据,然后它还可以通过 Http 协议定期拉取微服务的指标数据,并且提供了可扩展的存储以及查询功能。

2)Grafana: Gfafana 是一个开源的可视化仪表板工具,可以与 Prometheus 结合使用,创建实时和历史数据的仪表盘。并且 Grafana 提供了丰富的图表和可视化选择,可以帮助用户更好地查看当前微服务集群的性能和状态。

也可以利用 ELFK 套件来实现监控:Elasticsearch:用于存储和检索日志信息。Logstash/Fluentd:采集日志数据,发往 es。Kibana:可视化分析工具,展示查询数据。

ElasticSearch:ElasticSearch 是一个分布式搜索和分析引擎,可以存储和索引大量的数据日志,他提供了快速的搜索和聚合功能,实现大规模的日志数据高效处理。

Logstash:Logstash 是一个用于采集、过滤和转发日志数据的工具。它可以从文件、消息队列、网络等多种渠道实现日志数据的采集,并对数据进行处理以及转换,最后发到 ElasticSearch 进行存储和索引。

Kibana:Kibana 是一个日志数据可视化以及分析的工具,它提供了丰富的图表以及仪表盘工具,可以帮助用户实现日志数据的实时监控和分析。

SOA、单体应用、微服务架构有什么区别

单体应用:其对外是一个整体,所有的功能都打包在同一个应用上,这种架构风格方便测试和容易部署,容易发生单体故障,所以灵活性和可维护性并不是很高。

SOA:它是一种面向服务的架构风格,将系统划分为多个独立的服务。这些服务可以通过网络调用,并且可以实现跨平台、跨语言进行交互。

微服务:它是在 SOA 的基础上演变而来的,微服务架构进一步将系统为多个小型、独立的服务,每个服务都是一个单独的应用程序,可以采用独立部署的方式运行和扩展。缺点是:开发与运维成本增加,基础设施成本增加。相较于单体应用复杂性增加,同时性能监控与故障排除都会更加困难,因为涉及到很多个服务。

微服务的通讯方式有哪些

最常见的就是远程调用,即 RPC 调用,它通过将方法封装成通用网络数据包实现不同进程间的传输,从而实现不同服务之间的调用。RPC 调用其实又包含了基于 TCP 自定义协议的调用和 HTTP 调用,比如 Spring Cloud OpenFegin 用的就是 HTTP 调用,而 Dubbo 默认用的就是自定义的 dubbo 协议调用。

其他的通讯方式常见的还有消息队列,一个服务往 Broker 发送消息,另一个服务只要订阅这个主题就能从 Broker 获取这个消息。还有就是直接通过 HTTP 调用了,直接利用 HttpClient 填入对方的网址进行调用。

分布式和微服务有什么区别

分布式系统:它是由多台计算机或多节点组成的系统,各阶段之间通过网络进行通信和协作,共同完成一个或多个共享的任务,分布式的各个节点其实目标是一致的。

微服务架构:它是一种服务的架构风格,主要是为了把一个大而全的服务,拆分成多个独立、松耦合的服务单元,为了让这些服务单元可以独立部署、运行和管理。

微服务架构是如何运行的

客户端 :这个主要是来自不同设备的不同用户请求。身份提供商 :这个是验证用户身份的工具,即鉴权,一般客户端在访问 API 网关之前都需要实现这一步,通过用户提供商获取安全令牌。API 网关 :主要用于处理客户端的请求,并把请求路由转发到对应的微服务。静态服务 :指容纳系统的所有内容。管理 :指微服务的配置、监控、运维等服务治理。服务发现 :准确来说是服务的注册与发现,用于服务间的通信及查找。远程服务:即驻留在 IT 设备网络上的远程访问信息。

Spring Cloud Config 是什么

Spring Cloud Config 是 Spring Cloud 生态系统中的一个关键组件,它的主要作用是为分布式系统提供集中化的外部配置管理,通过使用它,开发者能够在不同的环境中对应用程序的配置进行集中管理和动态更新,而不需要在每个服务实例中单独维护配置文件。

集中化配置:将所有微服务的配置文件统一存放在一个中心位置(如 Git 仓库),方便进行管理和维护。

环境隔离:支持为不同的环境(如开发、测试、生产)提供不同的配置,并且能够根据环境自动切换配置。

动态刷新:在运行时可以动态刷新配置,无需重启服务,保证系统的高可用性。

版本控制:由于配置文件存储在版本控制系统(如 git) 中,因此可以跟踪配置的变更历史,便于进行回滚操作。

Config Server : 这是配置中心的核心,它负责从配置仓库(如 Git、SVN 或本地文件系统)中获取配置信息,并以 HTTP 接口的形式将这些配置提供给客户端。它是一个独立的 Spring Boot 应用,需要添加 spring-cloud-config-server 依赖,支持多种后端存储,包括 Git、SVN、本地文件系统等,可以通过配置 spring.cloud.config.server.uri 指定 git 仓库的地址。

Config Client :这是各个微服务客户端,它们在启动时会从 Config Server 获取配置信息,并将其注入到应用程序的环境中。微服务客户端需要添加 spring-cloud-config-client 依赖,通过配置 spring.cloud.config.uri 指定 Config Server 的地址,客户端会根据应用名称(spring.application.name) 和环境(spring.profiles.active) 来获取对应的配置。

配置规则

在 Git 仓库中,配置文件遵循特定的命名规则。

  • {application}-{profile}.properties (或 .yml): 例如 user-service-dev.properties,表示 user-service 服务在开发环境下的配置。

  • {application}.properties (或 .yml): 这是默认配置,会被特定环境的配置覆盖。

  • application-{profile}.properties (或 .yml):这是全局配置,会应用到所有服务。

动态刷新配置

要实现配置的动态刷新,需要配合 Spring Cloud Bus 或 Actuator;

  1. 添加 spring-boot-starter-actuator 依赖,并启用 refresh 端点。

  2. 在需要动态刷新的 Bean 上添加 @RefreshScope 注解,当配置发生变更并且客户端接收到刷新事件时,标注了 @RefreshScope 的 Bean 会重新加载最新的配置。

  3. 当配置发生变更时,通过 POST 请求触发 /actuator/refresh 端点,客户端会重新加载配置。

高可用性

部署多个 Config Server 实例,并使用负载均衡器(如 Nginx、Spring Cloud Gateway)进行负载均衡。对于 Git 仓库,可以使用高可用的 Git 服务(如 Github、GitLab 或自建 Git 服务器集群)。

复制代码
// Config Server 配置
<dependency>
    <groupId>org.springframework.cloud></groupId>
    <artifactId>spring-cloud-config-server></artifactId>
</dependency>
    
spring:
    cloud:
        config:
            server:
                git:    
                    uri: https//github.com/example/config-repo
                    search-paths: config
                    username: your-username
                    password: your-password
server:
    port: 8888
        
​
// 主应用类
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    SpringApplication.run(ConfigServerApplication.class, args);
}
​
​
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>
    
// bootstrap.yml(注意是 bootstrap.yml, 不是 application.yml)
spring:
    application:
        name: user-service
    cloud:
        config:
            uri: http://localhost:8888
            profile: dev
            label: master

Spring Cloud 有哪些注册中心

CAP 原则

在分布式系统中,CAP 原则(Consistency, Availablity,Partition tolerance)指出:一个系统不可能同时完全满足一致性、可用性和分区容错性,通常需要在三者之间进行权衡。

  • 一致性(Consistency):所有节点在同一时间看到的数据一致。

  • 可用性(Avaliability):系统在任意时间都能提供服务。

  • 分区容错性(Partition tolerance):系统能够应对网络分区的发生。

CP(一致性、分区容忍性):Consul、Zookeeper、Etcd

AP(可用性、分区容忍性):Eureka

CP/AP 都有:Nacos 两者都有,默认情况下是 AP

Eureka

通过 @EnableEurekaServer 和 @EnableEurekaClient 注解可以轻松实现服务注册中心或服务客户端。

  • 开源的服务注册与发现框架,属于 Spring Cloud 生态的一部分。

  • 支持服务注册、心跳检测、服务下线服务剔除 ,拥有自我保护机制,在网络出现短暂问题时,不立即剔除服务,可以提升系统的容错性。

  • 采用 AP 原则,即强调系统的可用性和分区容忍性,允许短暂的一致性延迟。

  • 使用的内存较少,适合中小规模的服务注册中心部署。

  • 基于客户端的心跳续约机制,服务实例需要定期向 Eureka Server 发送心跳(心跳的默认时间间隔是 30 s,如果 Server 在 90 s 内未收到心跳),Eureka Server 根据心跳的情况决定是否剔除服务实例。

自我保护机制

当 Eureka Server 在短时间内检测到大量服务实例不可用时,它会启动自我保护机制,暂时停止剔除失效的实例,以防止因为网络问题或短暂的系统波动导致大量服务下线。当网络恢复正常,Eureka Server 接收到的心跳数恢复到期望值后,自我保护机制会自动关闭,恢复正常的服务剔除逻辑。

当 Eureka Server 接收到的心跳数低于期望值(心跳率的 85%)时,自我保护机制会自动开启,在此期间,Eureka Server 仍然会接收新的服务注册和查询,但不会移除失效实例。

优点是:提高系统容错能力,避免数据丢失。

缺点是:短时间内可能提供错误的服务列表。自我保护机制在网络恢复正常后会自动关闭,但这个过程中可能会延长不可用实例。

Consul

它支持 多数据中心 ,并且自带 KV 存储健康检查 功能。可以定期检测服务的状态,并自动剔除失效实例,包括 HTTP、TCP、脚本检测等。

  • 拥有 内置的健康检查 功能,可以定期检查服务状态并剔除失败的实例。

  • 提供 DNSHTTP API 两种服务发现方式,便于服务的注册和查询。

  • 支持 CP 模型 ,即更注重数据一致性,使用 Raft 协议 来保证注册数据的一致性。

Consul 的 Server 节点使用 Raft 共识算法 来确保集群中的数据一致性,并使用 Gossip 协议 进行节点间的状态传播和故障检测,Gossip 协议是一种去中心化的通信协议,可以在节点之间的传播健康检查状态、服务列表等信息。

LAN Gossip:用于同一个数据节点中心内的节点间通信,确保各个节点状态的同步。

WAN Gossip: 用于不同数据中心之间的节点状态传播,实现跨数据中心的服务状态同步。

Agent

Client Agent: 部署在每个服务节点上,负责向 Consul Server 发送服务注册和健康检查信息,Client Agent 是无状态的,所有请求会被转发给 Server 节点。

Server Agent: 是 Consul 集群的核心,负责存储集群的状态信息,并对外提供服务注册,健康检查和配置管理等功能。它们之间 使用 Raft 协议 来保证数据一致性。

Zookeeper
  • 适用于需要元数据管理一致性协调的系统,如 Hadoop 集群、HBase 集群中的元数据管理。

  • 由于其强一致特点,适合对一致性要求严格的场景,如分布式消息队列(Kafka)、分布式锁、分布式数据库 等。

  • 它的一致性保障机制使得其性能在高并发场景下可能受到影响,特别是在进行大量写操作时。建议使用奇数个节点来保证数据的可靠性。

  • 采用**临时节点(ephemeral nodes)**来实现健康检查,当客户端与 Zookeeper 的连接断开时,临时节点会被删除,从而标记服务失效。

Nacos
  • 支持 AP 和 CP模型 的切换,用户可以根据业务场景选择不同的模型。

  • 支持 DNS-F 和 HTTP 的服务发现方式,适合不同类型的微服务架构。

  • 提供强大的配置管理能力,可以与服务发现功能配合使用,实现配置的动态更新。

  • 通过 Nacos 集群部署来应对高并发的服务注册需求,支持多种注册数据的持久化方式(如嵌入数据库、MySQL)。

  • 支持主动健康检查被动检测两种方式,通过 API 可以灵活配置服务的健康检测策略。

Etcd

它是 Kubernetes 的默认注册中心,并且是一个高可用的键值存储数据库 ,最初是由 CoreOS 开发的,主要用于 分布式系统的协调和服务发现 ,它是Kubernetes 的默认注册中心。

  • 基于 CP 模型,使用 Raft 协议,保证数据的一致性。

  • 适合对一致性要求高的分布式系统和容器编排系统。

  • 提供强大的键值存储功能,可以用于配置管理、分布式锁、服务注册等多种场景。

Spring Cloud 如何实现服务注册

SpringCloud 服务注册和发现需要依赖其他注册中心。项目在启动时,会自动将本身服务的服务名、IP、端口信息通过引入的注册中心依赖注册到服务注册中心的服务端。

服务自动注册

SpringCloud 提供的服务自动注册规范,其实就是三个接口,只要注册中心实现这些接口,就能够在服务启动时自动注册到注册中心。

Registration 是 SpringCloud 提供的一个接口,继承了 ServiceInstance 接口:

复制代码
public interface Registration extends ServiceInstance {
    
}
​
public interface ServiceInstance {
    
    default String getInstanceId() {
        return null;
    }
    
    String getServiceId();
    
    String getHost();
    
    int getPort();
}

从 ServiceInstance 的接口定义可以看出,这是一个服务实例数据的封装,比如这个服务的 ip 是多少,端口号是多少。所以 Registration 就是当前服务实例数据封装,封装了当前服务的所在的机器 ip 和端口号等信息。

复制代码
public class NacosRegistration implements Registration, ServiceInstance {
    
    public static final String MANAGEMENT_PORT = "management.port"
    
    public static final String MANAGEMENT_CONTEXT_PATH  = "management.context-path"
}
服务注册--ServiceRegistry

这个接口的作用就是把上面封装的当前服务的数据 Registration 注册通过 register 方法注册到中心中。

复制代码
public interface ServiceRegistry<R extends Registration> {
    
    void registere(R registration);
    
    void deregister(R registration);
}
​
public class NacosServiceRegistry implements ServiceRegistry<Registration> {
    
    private static final String STAUS_UP = "UP";
    
    private static final String STATUS_DOWN = "DOWN";
    
    private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class);
    
    private final NacosDiscoveryProperties nacosDiscoveryProperties;
}
​
@Override
public void register(Registration registration) {
    if (StringUtils.isEmpty(registration.getServiceId())) {...}
    
    NamingService namingService = namingService();
    String serviceId = registrartion.getServiceId();
    String group = nacosDiscoveryProperties.getGroup();
    
    Instance instance = getNacosInstanceFromRegistration(registration);
    
    try {
        namingService.registerInstance(serviceId, group, instance);
        log.info("nacos registry, {} {} {}:{} register finished", group, serviceId, instance.getIp(), instance.getPort());
    } catch (Exception e) {
        log.error("nacos registry, {} register failed...{},", serviceId,
                 registration.toString(), e);
        
        // rethrow a RuntimeException if the registration is failed
        rethrowRuntimeException(e);
    }
}
服务自动注册--AutoServiceRegistration
复制代码
public interface AutoServiceRegistration {
    
}

除了 Nacos 是这么实现的,常见的比如 Eureka、Zookeeper 等注册中心在整合 SpringCloud 都是实现上面的三板斧。

应用架构演进流程
单体多模块模型架构
多服务器单应用模型架构

我们将原先的 1 个 server 的服务进行了拆分,拆出了 用户服务订单服务商品服务 等等。每个服务里用 4 台机器来实例化,并且每个服务还相互关联和调用,使得维护成本比之前困难了很多。

每个实例服务在启动时都根据自身的名称向服务注册中心写入自己的实例信息如名称、IP、端口。

用户服务可以根据订单服务注册的名称从注册中心获取订单服务的详细信息,然后根据负载均衡策略选择一个 IP 调用即可,都不用自己做选择。

微服务网关你了解多少呢?

  1. 服务网关可以通过路由转发的方式,将请求分发到不同的服务实例上,也可以结合注册中心实现负载均衡与流量控制。

  2. 在之前单体架构时,我们实现安全鉴权这个操作放在拦截器中。但是在微服务中,我们可以考虑将鉴权功能放在网关进行,比如身份认证、授权、加密等功能。

  3. 网关其实是可以通过缓存一些信息的方式来加快访问速度以及提高性能的,即通过缓存一些静态资源来降低响应时间,提高访问速度。

  4. 在网关层可以通过 HTTP 协议转换为 RPC 协议或者 HTTPS 协议

  5. 因为网关可以统一管理服务的出口以及入口调用,因此可以很方便的进行日志监控以及请求统计,方便开发者了解服务的使用以及性能状况。

常见的 API 网关

1)Spring Cloud Gateway: 其基于非阻塞的 WebFlux 响应式编程框架,充分发挥了响应式编程的优势及特性并且提供了流量染色、路由转发、流量控制、服务熔断等,并且与其他微服务组件结合的较好,实现服务的注册发现、远程调用等功能。

2)Nginx:它是前后端分离的常用网关,其提供了负载均衡、路由转发的功能,性能较高,不过其修改配置之后需要 Reload 才能使得配置生效,跟不上云原生架构的发展。

3)Zuul: 这个是 Spring Cloud 早期默认的 API 网关,它可以实现路由、过滤、负载均衡等功能,不过自 2020 年 12 月起,Zuul 就已经停止维护了,在逐渐被 Spring Cloud Gateway 所取代。

4)Envoy:其底层使用的是 C++ 语言,二次开发的难度大,其是 CNCF 云原生基金会的毕业项目,比较适合服务网格场景以及多语言架构的部署。

Zuul

主要用于提供动态路由、监控、弹性和安全等边缘服务功能,它本质上是一个基于 JVM 的路由器和服务器端负载均衡器。

为什么选择 Gateway 作为网关

以前用的比较多的是官方开源的 Zuul,后面哪些都差不多停止维护了,而官方自己又研发了 Spring Cloud Gateway ,所以相比 Zuul,Gateway 是一个更好的选择。

  • 与 Spirng 生态兼容好,集成了 Spring Cloud 相关组件,如 Eureka、Nacos、Consul 等实现服务的注册与发现。

  • 内置限流模块,集成 Hystrix 断路器功能,实现服务熔断

  • 可以对路由指定断言(Predicate)和过滤器,实现动态路由。它可以根据配置条件,如 URL、请求方法等实现动态配置。

Dubbo 和 Spring Cloud Gateway 的区别是什么呢

SpringCloud 的优缺点
  • SpringCloud 是 spring 家族中的一员能够得到更多的原生支持。对一些常见的微服务模式做了抽象如服务发现、动态配置、一步消息等,同时包含一些批处理任务、定时任务、持久化数据访问等领域也有涉猎。

  • 基于 HTTP 的通信模式,加上相对比较完善的入门文档和演示 demo 和 starters。

  • 很多微服务实践场景的问题需要用户独自解决,比如优雅停机、启动预热、服务测试、再比如双注册、双订阅、延迟注册、服务按分组隔离、集群容错等。

  • 当集群规模增长后就会遇到地址推送效率、内存占用等各种瓶颈问题,但此时迁移到其他体系却很难实现。欠缺服务治理能力,尤其是流量管控方面如负载均衡、流量路由方面能力都比较弱。

Dubbo 的优点

它是一个 RPC(远程过程调用)框架,主要用于服务之间的通信。它提供高性能的 RPC 调用、负载均衡、服务发现、服务注册、服务治理等功能。

  • 提供 Java 外的多语言实现,使得构建多语言异构的微服务体系成为可能。

  • 在通信协议和编码上选择更加灵活,包括 RPC 通信层协议如 HTTP、HTTP/2(Triple、gRPC)、TCP 二进制协议、rest 等,序列化编码协议 Protobuf、JSON、Hessian2 等,支持单端口多协议。

  • 是在超大规模微服务集群实践场景下开发的框架,可以做到百万实例规模的集群水平。从设计上突出服务治理能力,如权重动态调整、标签路由、条件路由等,支持 Proxyless 等多种模式接入 Service Mesh 体系,高性能的 RPC 协议编码与实现。

  • 完全支持 Spring & Spring Boot 开发模式,同时在服务发现、动态配置等基础模式上提供与 Spring Cloud 对等的能力。Dubbo 考虑到了企业微服务实践中会遇到的各种问题如优雅上下线、多注册中心、流量管理等。

关于令牌桶算法你了解多少呢?

它是一种流量控制算法,用于限制系统的访问频率。该算法允许以固定的速率向 "桶" 中加入令牌,处理请求时消耗桶中的令牌,当桶中的令牌耗尽时,后续请求会被拒绝或延迟处理。在 Java 中可以使用基于 Guava 的 RateLimiter 实现令牌算法,可以有效控制单用户的访问频率。

复制代码
import com.google.common.util.concurrent.RateLimiter;
​
​
public class RateLimitExample {
    
    // 创建一个 RateLimiter,设置每秒生成 5 个令牌
    private static final RateLimiter rateLimiter = RateLimiter.create(5.0);
    
    public stataic void main(String[] args) {
        // 模拟请求处理
        for (int i = 0; i < 10; i++) {
            // 请求获取令牌
            rateLimiter.acquire();
            // 处理请求
            System.out.println("Request " + i + " processed");
        }
    }
}
令牌桶算法工作原理

1)令牌生成:系统以固定的速率生成令牌,令牌被放入桶中。生成的速率可以根据需求进行配置,例如每秒生成一定数量的令牌。

2)令牌存储:桶中可以存储一定数量的令牌,这个数量被称为 "桶容量" 或 "最大容量"。当桶满时,多余的令牌将会被丢弃。

3)请求处理:每当一个请求到达系统时,需要从桶中取出一个令牌。如果桶中有足够的令牌,允许请求通过;如果没有足够的令牌,请求会被拒绝或者等待令牌的生成。

4)速率控制:由于令牌是以固定速率生成的,因此系统能够控制请求的速率。例如,如果每秒生成 10 个令牌并且桶的容量为 100,那么系统每秒最多允许处理 10 个请求,但如果有更多的请求到达,可以在桶中缓存令牌。

通过调整令牌的生成速率和桶的大小,可以灵活地控制流量速率和突发流量的处理能力。

注意事项

如果桶的容量设置过小,可能会导致无法处理正常的突发流量;如果设置过大,则可能会积累过多的流量,超出系统的处理能力。 令牌生成速率直接影响系统的处理能力,如果速率设置过低,可能无法满足用户的请求;如果速率设置过高,可能会导致系统负担过重。 在分布式系统中,时间同步问题可能影响令牌的精确生成,导致限流效果不稳定。

对于配置中心你了解过吗?介绍一下

它实现了配置的统一管理和动态刷新,当配置信息发生变化时,配置中心可以自动通知服务实例进行配置更新,这样就可以实例无需重启即可应用最新的配置。

常见的配置中心

Spring Cloud Config: Spring 提供的分布式配置管理工具,支持从 Git、SVN 等版本控制系统加载配置。没有原生的推送机制,需要借助消息队列等外部系统实现。

Nacos: 阿里巴巴的配置管理和服务发现工具,既支持配置中心功能,又能作为注册中心使用。1.x 版本为长轮询,2.x 版本基于 gRPC (HTTP/2) 实现的长连接。

Zookeeper: Zookeeper 是一个开源的分布式协调服务,和 Nacos 一样,其既可以作为注册中心,又可以作为配置中心。它是基于 TCP 协议的长连接。Zookeeper 和 Etcd 更偏向于分布式协调服务,支持配置管理,但在易用性和界面操作上不如 Nacos 友好。Nacos 提供了更好的 API 和管理页面,易于上手。

配置中心如何实现动态更新
长连接

它是一种基于 TCP 或 WebSocket 协议的连接方式,在建立连接后,客户端和服务器之间可以持续进行数据的双向传输,而无需每次都重新建立连接。长连接的典型实现是 WebSocket 或 gRPC,能够实现实时的推送。

当客户端与服务器建立一个持久的连接,该连接保持打开状态。当服务器检测到配置变化时,立即通过这个连接向客户端推送变更信息,客户端即时接收和处理更新。连接会一直保持直到手动关闭或由于网络中断等因素断开。

优点是:实时性强,服务器可以即时推送更新;无需频繁建立连接,减少了连接开销。缺点:长连接需要消耗更多的系统资源,并且对网络环境的要求较高,断线重连和连接管理需要额外的处理。

长轮询

它是一种模拟服务器推送的机制,客户端主动向服务器发起请求,并保持连接(比如保持 30s),直到服务器有更新或超时为止。如果有更新,服务器会返回新的数据,客户端在接收到数据后,再次发起新一轮的请求(如果等待超时,也再次发起新的请求)。

客户端发送 HTTP 请求给服务器,询问是否有配置更新。服务器保持这个请求打开,直到检测到配置发生变更,或者达到超时时间。如果配置有更新,服务器返回更新的配置数据,客户端处理并再次发起新请求;如果没有更新,连接会超时,客户端也会重新发起请求。模拟推送的效果,但本质上是客户端的连续请求

优点:实现相对简单,兼容性好,适用于大多数网络环境。缺点:即便没有配置变化,也需要不断发起请求,造成资源浪费;响应数据取决于轮询频率,不够实时。

Nacos 配置中心的实现原理你了解多少呢?

企业内 Nacos 一般会使用数据库(如 MySQL)来存储配置数据库,配置数据库是以键值对的形式存储的,每个配置项对应一个数据 ID(Data Id),并且支持通过 Namespace(命名空间)、Group(组)来进行配置的隔离与分类。通过多副本的数据持久化保证配置的高可用性和可靠性。

Nacos 客户端维护一个本地缓存,客户端接收到配置更新后,会自动刷新本地缓存的配置。当与 Nacos 服务器的连接出现异常或服务器不可用时,可以从本地缓存中读取配置,保证系统的基本运行不受影响。

Nacos 配置中心使用 gRPC 长连接 来实现配置的实时推送,Nacos 服务器可以通过这个长连接实时向客户端推送配置更新。客户端在启动时,会向 Nacos 注册监听器(Listener)并维持与服务器的长连接,当 Nacos 检测到配置变更时,会通知所有相关客户端进行配置刷新。

Nacos 的配置管理模型

1)Namespace:用于环境隔离,如 dev、test、prod..

2)Group:在同一 Namespace 下进一步对配置进行分组管理,方便针对业务模块进行分类。

3)Data ID:每个具体的配置项的唯一标识,通过这个 ID 可以精确定位到某个配置(可以认为是文件名)。

Nacos 配置中心的集群高可用

Nacos 集群至少需要 3 个节点,以确保在某些节点失效时,系统仍然可以正常工作。Nacos 采用了 Raft 协议 来实现节点之间的数据一致性,确保配置数据在集群中的一致性。

多副本存储:Nacos 支持配置的多副本存储,通过在集群内多节点存储配置数据的方式,避免单节点故障问题,提升了服务的可用性。

断线重连机制:客户端在与 Nacos 服务器的连接断开时,会自动尝试重新连接,并在连接恢复后同步最新的配置数据,保证配置更新不会因网络问题而丢失。

Nacos 2.x 的改进

gRPC 协议 :Nacos 2.x 版本相较于 1.x,做出了显著的优化,将通信机制从 HTTP 长轮询切换为 gRPC。gRPC 基于 HTTP/2 协议,支持更高效的双向流通信,带来了以下优势。

RPC 能够实现真正的长连接,支持实时推送,减少了 HTTP 轮询带来的延迟和带宽消耗。HTTP/2 支持多路复用,客户端与服务器之间可以通过一个 gRPC 连接传输多路数据流,降低了连接开销。gRPC 支持数据压缩和流式传输,使得在规模配置更新时的传输更为高效。

配置灰度发布与版本管理

Nacos 提供了灰度发布的能力,可以将配置的更新仅应用到部分客户端,以降低风险。通常情况下,可以先发布到某个 Group 或使用标签(Tag)指定的客户端,确保新配置在小范围内运行稳定后,再逐步扩展到所有客户端。

当配置发生变更时,会生成新的版本,旧版本也会保。版本管理可以确保在配置更新出现问题时,能够快速回退到之前的版本,减少对生产环境的影响。

配置的权限管理

RBAC(基于角色的访问控制):Nacos 支持基于角色的权限控制,管理员可以为不同的用户分配相应的权限,例如读取、写入、删除配置的权限。每个用户只能访问自己权限范围内的配置数据。

身份认证:Nacos 通过 Token 或 OAuth2 等方式进行用户身份认证,确保只有经过认证的用户才能访问配置数据,增强了配置的安全性。

审计日志:Nacos 记录了所有配置操作的审计日志,包括配置的创建、修改和删除操作。管理员可以通过日志监控配置变更,保证系统的合规性。

关于分布式事务你了解多少呢?

防悬挂

防悬挂 是指在分布式事务的第一阶段,防止在没有对应的 Try 操作的情况下出现 Confirm 或 Cancel 操作,这是为了保证事务的正确性和一致性。

TCC(Try-Confirm-Cancel)模式,在 TCC 模型中,事务分为三个步骤。Try : 资源的预留操作,Confirm : 确认操作,完成业务逻辑。Cancel:取消操作,回滚预留资源。

如果 Confirm 操作在没有执行过 Try 操作的情况才被调用,可能会导致数据不一致。类似地,如果 Cancel 操作在没有 Try 操作的情况下被调用,也会破坏数据的一致性。

为了防止这种情况,需要通过某些机制来检测和防止悬挂。Confirm 和 Cancel 操作应该具备幂等性,避免多次调用引发问题。在执行 Confirm 或 Cancel 之前,可以先检查是否有对应的 Try 操作成功过,如果没有,则拒绝执行 Confrim 或 Cancel 操作。

空回滚

它是指在没有执行成功的 Try 操作时,Cancel 操作仍然被调用了。Cancel 操作实际上就是 Try 操作的回滚操作,如果 Try 操作根本没有成功,则 Cancel 操作实际不会对任何资源产生影响,这就是空回滚了。

它一般发生在分布式系统中的异常情况下,比如:Try 操作还没执行成功,网络就超时了,分布式事务协调器可能误认为 Try 操作失败,因此会调用 Cancle。Try 操作请求未到达相应的服务端,客户端误认为 Try 操作已经失败并发起 Cancel。

为了支持空回滚,即使 Cancel 操作被多次调用,它的效果也只能是执行一次,不会对系统产生额外影响。Cancel 操作在发现没有 Try 操作执行成功时,不做任何修改,直接返回成功状态。

防悬挂 的目标是防止 Confirm 或 Cancel 操作在没有执行 Try 操作的情况下被调用,防止系统出现异常。空回滚则是在没有 Try 操作成功的情况下,Cancel 操作被调用了,而 Cancel 操作正确处理这种情况并不产生副作用。

XA 模式的回滚机制

XA 模式基于标准的 XA 协议实现回滚,在第一阶段中,事务协调器(TC)向各个资源管理器(RM)发送预备指令。所有参与的资源在执行操作后暂时不提交,而是等待 TC 的最终决策。如果 TC 选择回滚事务,它会通知各个 RM 执行 ROLLBACK 操作。RM 根据数据库内置的 XA 支持来回滚数据,确保所有资源恢复到未提交之前的状态。

AT 模式的回滚机制

在 AT 模式中,Seata 的回滚机制依赖于数据库中的 undo_log 表,每当执行数据操作(如 INSERTUPDATEDELETE) 时,Seata 的 RM 会在数据库中记录操作前的数据快照,并存储在 undo_log 中,这个过程在业务操作的本地事务提交前完成。

如果全局事务中任一分支事务失败,或者 TM 向 TC 发起了全局回滚请求,TC 会通知所有相关的 RM 执行回滚操作。在接收到回滚请求后,RM 从 undo_log 中读取数据的原始状态,并将数据恢复到操作前的状态。即使数据库中已经进行了 UPDATEDELETE 操作,RM 也能根据 undo_log 中的快照还原原始数据。

TCC 模式的回滚机制

在 TCC 模式下,事务回滚是通过业务实现的 Cancel 方法来完成的。开发者需要在业务代码中定义 Try(预留资源)、Confirm (确认操作)、Cancel(取消操作)三个阶段。当全局事务需要回滚时,Seata 会调用对应的 Cancel 方法来撤销 Try 阶段中预留的资源。Cancel 操作的逻辑由业务代码实现,通常是对 Try 操作的逆过程。

SAGA 模式的回滚机制

SAGA 模式通过定义每个业务操作的补偿操作来实现回滚,每个子事务都有一个对应的补偿操作,用于在子事务失败时撤销之前的操作。如果某个子事务执行失败,SAGA 模式会按相反的顺序调用之前已完成的子事务的补偿算法,从而将整个业务流程回滚到初始状态

Seata 分布式事务详解

AT模式

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。

第一阶段 Seata 会拦截 SQL,在数据更新前,记录修改之前的数据。第二阶段负责业务的回滚或提交,如果一阶段本地事务提交失败,则全局回滚,反之全局提交。回滚时用到的就是一阶段的修改之前的数据,生成反向 SQL 回滚数据。

复制代码
@GlobalTransactional
public void purchase(String userId, String commdityCode, int count, int money) {
    
    jdbcTemplateA.update("update stock_tb1 set count = count - ? where commodity_code =?",     new Object[] {count, commodityCode});
    jdbcTemplateB.update("update account_tb1 set money = money - ? where user_id = ",
     new Object[] {money, userId});
}
写隔离

一阶段本地事务提交前,需要确保先拿到全局锁 ,拿不到全局锁 ,不能提交本地事务。拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁,本地提交释放本地锁。

tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿到该记录的 全局锁 , tx1 全局提交前,该记录的全局锁被 tx1 持有, tx2 需要重试等待 全局锁

tx1 二阶段全局提交,释放全局锁 ,tx2 拿到全局锁 提交本地事务。

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍然等待该数据的 全局锁 ,同时持有本地锁,则 tx1 的分支回滚会失败,分支的回滚一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。如果应用在特定场景下,必须要全局的 读已提交,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的处理。

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果全局锁被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅仅针对 FOR UPDATE 的 SELECT 语句

AT 工作机制示例
Field Type Key
id bigint(20) PRI
name varchar(100)
since varchar(100)
复制代码
update product set name = 'GTS' where name = 'TXC';
一阶段

1)解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(Where name = 'TXC')等相关的信息。

2)查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。

复制代码
select id, name, since from product where name = 'TXC';

3)执行业务 SQL:更新这条记录的 name 为 'GTS'。

4)查询后镜像:根据前镜像的结果,通过 主键定位数据。

复制代码
select id, name, since from product where id = 1;

5)插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。

复制代码
{
	"branchId": 641789253,
	"undoItems": [{
		"afterImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "GTS"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"beforeImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "TXC"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"sqlType": "UPDATE"
	}],
	"xid": "xid:xxx"
}

6)提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁

7)本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。

8)将本地事务提交的结果上报给 TC。

二阶段
二阶段-回滚

1)收到 TC 的分子回滚请求,开启一个本地事务,执行如下操作。

2)通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。

3)数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理。

4)根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句

复制代码
update product set name = 'TXC' where id = ;

5)提交本地事务,并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

二阶段-提交

1)收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。

2)异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

TCC模式

它是一种侵入式地分布式事务解决方案,需要业务系统自行实现 Try,Confirm,Cancel 三个操作,对业务系统有着非常大的入侵性,设计相对复杂。整体是 两阶段提交 模型。

全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

  • 一阶段 prepare 行为

  • 二阶段 commit 或 rollback 行为

在两阶段提交协议中,资源管理器(RM,Resource Manager)需要提供 "准备"、"提交" 和 "回滚" 3 个操作;而事务管理器(TM,Transaction Manager)分 2 阶段协调所有资源管理器,在第一阶段询问所有资源管理器 "准备" 是否成功,如果所有资源均 "准备" 成功则在第二阶段执行所有资源的 "提交" 操作,否则在第二阶段执行所有资源的 "回滚" 操作,保证所有资源的最终状态是一致的,要么全部提交要么全部回滚。

资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现;TCC 是一种比较成熟的分布式解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题;TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。

TCC 的 Try 操作作为第一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。

基本使用

区别在于 AT 模式直接使用数据源代理来屏蔽分布式事务细节,业务方需要自行定义 TCC 资源的 "准备"、"提交"、和 "回滚"。

复制代码
public interface TccActionOne {
    @TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback")
    public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);
    public boolean commit(BusinessActionContext actionContext);
    public boolean rollback(BusinessActionContext actionContext);
}

Seata 会把一个 TCC 接口当成一个 Resource,也叫 TCC Resource。在业务接口中核心的注解是 @TwoPhaseBusinessAction, 表示当前方法使用 TCC 模式管理事务提交,并标明了 Try,Confirm,Cancel 三个阶段。name 属性,给当前事务注册了一个全局唯一的 TCC bean name,同时 TCC 模式的三个执行阶段分别是。

  • Try 阶段,预定操作资源(Prepare)这一阶段所以执行的方法便是被 @TwoPhaseBusinessAction 所修饰的方法,如示例代码中的 prepare 方法。

  • Confirm 阶段,执行主要业务逻辑(Commit)这一阶段使用 commitMethod 属性所指向的方法,来执行 Confirm 的工作。

  • Cancel 阶段,事务回滚(Rollback)这一阶段使用 rollbackMethod 属性所指向的方法,来执行 Cancel 的工作。

其次,可以在 TCC 模式下使用 BusinessActionContext 在事务上下文中传递查询参数。

xid 全局事务 id,branchId 分支事务 id,actionName 分支资源 id, actionContext 业务传递的参数,可以通过 @BusinessActionContextParameter 来标注需要传递的参数。

注意,如果 TCC 参与者是本地 bean(非远程 RPC 服务),本地 TCC bean 还需要在接口中添加 @LocalTCC 注解,比如。

复制代码
@LocalTCC
public interface TccActionTwo {
    @TwoPhaseBusinessAction(name = "TccActionTwo", commitMethod = "commit", rollbackMethod = "rollback")
    public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);
    public boolean commit(BusinessActionContext actionContext);
    public boolean rollback(BusinessActionContext actionContext);
}
SAGA模式

它是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

Saga 的实现(状态机引擎)

SEATA 提供的 Saga 模式是基于状态机引擎来实现的,机制是:

  1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件。

  2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点

  3. 状态图 JSON 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚(注意:异常发生时是否进行补偿也可以由用户自定义决定)

  4. 可以实现服务编码需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能。

状态机引擎原理

1)图中的状态图是先执行 stateA,再执行 stateB,然后执行 stateC

2)"状态" 的执行是基于事件驱动的模型,stateA 执行完成后,会产生路由消息放入 EventQueue,事务消费端从 EventQueue 取出消息,执行 stateB

3)在整个状态机启动时会调用 Seata Server 开启分布式事务,并生产 xid,然后记录 "状态机实例" 启动事务到本地数据库。

4)当执行到一个 "状态" 时会调用 Seata Server 注册分支事务,并生产 branchld,然后记录 "状态实例" 开始执行事件到本地数据库。

5)当一个 "状态" 执行完成后会记录 "状态实例" 执行结束事件到本地数据库,然后调用 Seata Server 上报分支事务的状态。

6)当整个状态执行完成,会记录 "状态机实例" 执行完成事件到本地数据库,然后调用 Seata Server 提交或回滚分布式事务。

状态机引擎设计
Eventing 层次:

实现事件驱动架构,可以压入事件,并由消费者端消费事件,本层不关心事件是什么消费端执行什么,由上层实现。

ProcessController 层次:

由于上层的 Eventing 驱动一个 "空" 流程引擎的执行,"state" 的行为和路由都未实现,由上层实现。

StateMachineEngine 层次

实现状态机引擎每种 state 的行为和路由逻辑,提供 API、状态机语言仓库。

状态机的高可用设计

状态机引擎是无状态的,它是内嵌在应用中。当应用正常运行时(图中上半部分):状态机执行日志存储在业务的数据库中,状态机引擎会上报状态到 Seata Server;

当一台应用实例宕机时(图中下半部分):状态机引擎收到事务恢复请求后,从数据库里装载日志,并恢复状态机上下文继续执行。Seata Server 会感知到,并发送事务恢复请求到还存活的应用实例。

XA模式

利用事务资源(数据库、消息服务)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式

  • 可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 回滚

  • 持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)

  • 分支提交:执行 XA 分支的 commit

  • 分支回滚:执行 XA 分支的 rollback

整体运行机制
  • XA start/XA end/XA prepare + SQL + 注册分支

  • XA commit/XA rollback

数据源代理

XA 模式需要 XAConnection,获取 XAConnection 两种方式:

方式一:要求开发者配置 XADataSource

方式二:根据开发者的普通 DataSource 来创建

第一种方式,给开发者增加了认知负担,需要为 XA 模式专门去学习和使用 XA 数据源,与 透明化 XA 编程模型的设计目标相违背。

第二种方式,对开发者比较友好,和 AT 模式使用一样,开发者完成不必关心 XA 层面的任何问题,保持本地编程模型即可。

我们优先设计实现第二种方式:数据源代理根据普通数据源中获取的普通 JDBC 连接创建出相应的 XAConnection。

但是,第二种方法有局限:无法保证兼容的正确性。

实际上,这种方法是在数据库驱动程序要做的事情,不同的厂商、版本的数据库驱动实现机制是厂商私有的。

综合考虑,XA 模式的数据源代理设计需要同时支持第一种方式:基于 XA 数据源进行代理。

分支注册

XA start 需要 Xid 参数,这个 Xid 需要和 Seata 全局事务的 XID 和 Branchld 关联起来,以便由 TC 驱动 XA 分支的提交或回滚。目前 Seata 的 Branchld 是在分支注册过程,由 TC 统一生成的,所以 XA 模式分支注册的时机需要在 XA start 之前。

把分支注册尽量延后,类似 AT 模式在本地事务提交之前才注册分支,避免分支执行失败情况下,没有意义的分支注册。

XA 模式的使用

样例场景是 Seata 经典的,涉及库存、订单、账户 3 个微服务的商品订购业务。上层编程模型与 AT 模式完全相同,只需要修改数据源代理,即可实现 XA 模式与 AT 模式之间的切换。

复制代码
@Bean("dataSource")
public DataSource dataSource(DruidDataSource druidDataSource) {
    
    
    // DataSourceProxy for AT mode
    // return new DataSourceProxy(druidDataSource);
    
    
   // DataSourceProxyXA for XA mode
    return new DataSourceProxyXA(druidDataSource);
}

什么是 Seata

Seata (Simple Extensible Autonomous Transaction Architecture) 是阿里巴巴开源的一款分布式事务解决方案,主要是为了解决分布式系统中全局事务的一致性问题。

事务协调器(Transaction Coordinator, TC):TC 负责管理全局事务的生命周期,记录全局事务和分支事务的状态,并协调全局事务的提交和回滚。TC 是 Seata 的中心控制器,所有的分布式事务请求都会通过 TC 进行管理。

作用:TC 保证各个分支事务在全局事务中的状态一致性,它记录每个事务分支的状态信息,并在发送异常时向相关分支发送回滚命令。

事务管理器(Transaction Manager, TM): TM 负责定义全局事务的边界,即启动、提交、回滚全局事务。TM 通常嵌入在业务服务中,用于向 TC 发起全局事务的创建和提交请求。

通过 @GlobalTransactional 注解或 API,TM 标记某个业务操作为全局事务。TM 在事务的注册、提交、回滚。RM 的核心作用是对本地数据库进行事务操作,并将分支事务的状态通知给 TC。

资源管理器(Resource Manage, RM):RM 负责管理本地资源(如数据库),以及分支事务的注册、提交、回滚。RM 的核心作用是对本地数据数据库进行事务操作,并将分支事务的状态通知给 TC。

在分支事务执行时,RM 通过数据库代理层来记录数据记录,以便在事务回滚时能够还原数据。同时,RM 负责向 TC 注册分支事务,并在接受到 TC 的命令时执行数据的提交或回滚操作。

  1. TM 向 TC 发起全局事务创建,TC 返回 XID。

  2. TM 调用业务方法 , 业务方法执行过程中,RM 向 TC 注册分支事务

  3. RM 执行数据库操作 并生成回滚日志,同时提交本地事务

  4. 业务方法执行结束后,TM 向 TC发送全局提交或回滚操作。

  5. TC 收到请求后,通知 RM 执行提交或回滚操作

  6. RM 执行提交或回滚,并将结果通知 TC。

  7. TC 记录事务状态为提交完成或回滚完成。

Seata 的事务执行流程
全局事务的创建(Transaction Start)

事务管理器通过在业务方法上使用 @GlobalTransactional 注解,标识该方法为全局事务。业务方法调用时,TM 会向 事务协调器(TC) 发起全局事务的创建请求

TC 负责为该全局事务生成一个唯一的 全局事务 ID(XID),并返回给 TM。XID 是全局事务的唯一标识,用于后续跟踪和管理这个事务的所有操作。

分支事务的注册(Branch Register)

分支事务是全局事务的一部分,通常对应业务方法中具体的数据库操作(如新增、更新删除)。

业务方法 执行时,会调用数据库操作(ru INSERTUPDATE)。Seata 的资源管理器(RM) 通过代理数据库操作来记录数据的前后状态 并生成回滚日志

在执行数据库操作之前,RM 会将该数据库操作注册为分支事务 ,并向 TC 发起分支注册请求。注册信息包括:全局事务ID(XID),资源 ID (数据库表名),分支事务的类型(如 AT 模式的 SQL 操作),数据的锁定信息(用于行锁管理)。TC 接收到分支事务注册请求后,会记录该分支事务的信息,并将注册成功的结果返回给 RM。

业务方法的执行与本地事务提交(Local Transaction Commit)

分支事务注册成功后,RM 执行数据库操作,更新数据并将修改的状态保存到数据库中。

在完成操作后,本地事务提交 ,此时本地事务的数据已经写入数据库,但全局事务尚未提交。为了支持回滚,回滚日志(undo_log) 也会存储在数据库中,以记录数据修改前的状态。

全局提交流程
  • 如果业务逻辑执行过程中发生异常,TM 向 TC 发起全局回滚请求

  • TC 根据 XID 确认所有分支事务状态正常后,向所有参与的RM 发送分支提交请求

  • RM 接收到分支提交请求后,执行数据库操作的最终提交(实际上,AT 模式下的数据已经在本地事务阶段提交,分支提交请求主要是确认无误)。

  • TC 收到所有分支提交成功的响应后,标记全局事务为提交完成,并释放全局锁。

全局回滚流程
  • 如果业务逻辑执行过程中发生异常,TM 向 TC 发起全局回滚请求

  • TC 向所有参与的 RM 发送分支回滚请求

  • RM 在接收到分支回滚请求后,根据 undo_log 中记录的数据快照,将数据库恢复到执行操作之前的状态,确保数据的一致性。

  • TC 收到所有分支回滚完成的响应后,标记全局事务为回滚完成,并释放相关的全局锁。

全局事务的结束(Transaction End)

无论是全局事务的提交 还是回滚 ,当 TC 确认所有分支事务的操作完成后,会标记该全局事务的状态为 COMMITEEDROLLED BACK,并在事务日志中记录事务的结束状态。

事务结束后,Seata 会清理 undo_log 表中的数据,防止日志占用过多的数据库存储空间。

Seate 的实现原理

Seata 的数据一致性保证机制

回滚日志机制(Undo Log) :在 AT 模式下,Seata 会在本地事务执行前后记录 undo_log,即数据操作前的快照信息。回滚时,Seata 读取 undo_log 并恢复数据,确保事务回滚操作的正确性。

全局锁机制:Seata 使用全局锁来管理分支事务对数据库行的并发访问,确保同一行数据在同一时刻只能被一个全局事务修改,通过 TC 管理全局锁,可是防止多事务并发修改同一数据行时冲突。

两阶段提交(2PC) :Seata 的事务提交过程遵循两阶段提交协议:第一阶段 :RM 提交本地事务,同时记录 undo_log,注册分支事务到 TC。第二阶段:TC 根据全局事务的状态决定提交或回滚,并通知所有 RM 执行相应

什么是服务雪崩

服务雪崩是指在微服务架构分布式系统 中,由于某个服务不可用或性能下降,导致依赖它的其他服务也出现连锁故障,最终整个系统或大部分服务不可用的现象。

一个服务的不可用或性能下降可能会导致依赖它的多个上游服务响应变慢,甚至出现请求堆积。在服务故障或超时情况下,重试机制可能会产生更多的请求,进一步加剧下游服务的压力,导致故障范围扩大。

解决办法

熔断器(Hystrix、Resilience4j)能够在检测到某个服务请求的失败率达到一定阈值时,自动中断对该服务的进一步调用,从而防止服务继续被拖垮。也可以通过限流(令牌桶、漏桶算法)和隔离(如线程池隔离、信号量隔离),限制单个服务的请求数量,防止流量过大。还可以提供降级方案,确保系统在部分功能不可用时仍能为用户提供基本服务。

什么是服务降级

服务降级是一种在分布式系统微服务架构 中常用的容错机制 ,用于在系统压力过大或部分服务出现故障时,暂时减少或关闭某些不必要的功能,从而确保核心功能的正常运行,避免系统崩溃。

当某个服务的调用时间超过了设定的阈值,或者服务多次调用失败时,可以触发降级机制,返回预设的降级响应。当系统的负载过高(如 CPU 使用率、内存占用率等)时,可以主动降级某些非核心功能,释放系统资源,确保核心业务的正常运行。如果下游依赖服务不可用或者响应时间过长,可以通过降级机制,返回缓存数据或默认数据,避免请求继续传播。

解决办法

基于 HystrixResilience4j 系统,可以在 @HystrixCommand 注解中指定 fallbackMethod,当服务调用失败或超时时,直接执行降低方法,返回预设的响应。或者当服务不可用时,直接返回最近一次的缓存数据,保证用户体验的稳定性。也可以当系统流量超过某个阈值时,对部分非核心请求进行限流或直接拒绝,用来保护核心服务的可用性。

  • Sentinel:它是阿里巴巴开源的服务限流降级框架,提供流量控制、熔断降级、系统自适应保护等功能。与 Spring Cloud Alibaba 生态集成良好,适用于国内市场,功能丰富易于配置。

  • Hystrix:它是 Netflix 开源的熔断器和降级框架,通过 @HystrixCommand 主机可以方便地实现服务地降级逻辑,适用于 Spring Cloud 的应用中,用来服务降级和熔断处理。同时提供实时的仪表盘和可视化的监控界面。

  • Resilience4j:它是一个轻量级的容错框架、支持熔断、限流、降级等功能,采用函数式编程风格,适合与 Spring Boot 2.x 集成,它更适合在新的微服务项目中使用。

Sentinel 与 Hystrix 的区别是什么呢
  • Sentinel 支持多种流量策略,比如基于 QPS、线程数、并发连接数等多种指标。

  • 支持通过 API 动态配置流量规则以及熔断规则,并且可以根据服务的实际情况进行动态调整。

  • 支持粒度更小,支持对任意资源,比如代码段、url、接口等实现自定义流量控制规则,比如熔断、限流、降级等操作。

Sentinel 限流的核心组件

它的核心限流逻辑是通过 Slot Chain 来实现的,当请求进入时,Slot Chain 会根据限流规则检查当前请求是否可以通过,如果某个槽位(如流控槽)检查到请求超过了设定的限流阈值,则会触发限流操作。

Sentinel 使用 Statistic Node 来记录每个资源的请求统计信息,包括通过数、阻塞数、QPS、响应时间等,为限流决策提供了数据支持,帮助 Flow Slot 在限流时做出精确的判断。

Flow Slot 是 Slot Chain 中负责流控的部分,它会根据限流规则中的 QPS 阈值或并发数限制,判断是否允许请求通过。

Sentinel 是怎么实现集群限流的

Sentinel 支持对多个 Token Server 进行配置,实现多 Token Server 模式,从而避免单点故障。多个 Token Server 可以对令牌请求进行负载均衡,提高系统的可靠性。

当 Token Server 出现故障时,可以自动切换到其他可用的 Token Server,以保证集群限流的持续性。

流程如下:当一个服务实例(Token Client)有请求进来时,它会首先向 Token Server 发送一个令牌申求。Token Server 根据预设的限流规则,判断当前请求是否可以通过,并返回结果给 Token Client,如果请求获得了令牌,Token Client 就允许继续处理该请求。如果未获得令牌,则根据设定的限流策略(如直接拒绝、排队等待等)进行相对应处理。

关于限流你了解多少呢

它是指的是 限制到达系统的并发请求数 ,使得系统能够正常的处理 部分 用户的请求,来保证系统的稳定性。它需要在用户体验和系统稳定之间做平衡,即我们常说的 trade off

计数限流

优点:简单粗暴,单机在 Java 中可用 Atomic 等原子类、分布式就 Redis incr。

缺点:假设我们允许的阈值是 1 万,此时计数器的值为 0,当 1 万个请求在前 1 秒内一股脑儿的都涌进来,这突发的流量可是顶不住的。

固定窗口限流
  • 请求次数小于阈值,允许访问并且计数器 + 1;

  • 请求次数大于阈值,拒绝访问;

  • 这个时间窗口过了之后,计数器清零。

固定窗口临界问题

虽然窗口内的计数没有超过阈值,但是全局来看来 0.55s - 1.05s 这 0.5 s 内涌入了 200 个请求,这其实对于阈值是 100/s 的系统来说是无法接收的。

滑动窗口限流

它除了需要引入计数器之外还需要记录时间窗口内每个请求到达的时间点,解决了固定窗口临界值的问题,可以保证在任意时间窗口内都不会超过阈值,但是对内存的占用会比较多

  • 记录每次请求的时间

  • 统计每次请求的时间 至 往前推 1 秒这个时间窗口内请求数,并且 1 秒前的数据可以删除。

  • 统计的请求数小于阈值就记录这个请求的时间,并允许通过,反之拒绝。

滑动窗口和固定窗口都无法解决短时间之内集中流量的突击,我们可以设置多条限流规则,不仅限制每秒 100 个请求,再设置每 10 ms 不超过 2 个。

TCP 的滑动窗口是接收方告知发送方自己能接收多少 "货",然后发送方控制发送速率。

漏桶算法
  • 请求来了放入桶中

  • 桶内请求量满了拒绝请求

  • 服务定速从桶内拿请求处理

优点:宽进严出,无论请求多少,请求的速率有多大,都按照固定的速率流出,对应的就是服务按照固定的速率处理请求。

缺点:在面对突发流量我们希望在系统平稳的同时,提升用户体验即能更快的处理请求,而不是和正常流量一样,循规蹈矩的处理

令牌桶算法

什么是服务熔断

服务熔断 指的是当某个服务的调用失败率持续升高时,通过中断对该服务的请求,防止系统资源被不断消耗,进而保护整个系统不受影响。

当一个服务在设定的时间窗口内,连续多次请求失败(如超时、异常、HTTP 5xx 错误等),并且失败率超过设定阈值时,熔断器会自动触发。或者当服务完全不可访问(如网络故障或服务宕机),熔断器可以直接切断请求,快速返回错误。最后如果一个服务的响应时间长,导致调用超时,并且这种情况在一定时间内多次发生,熔断器也会触发熔断。

熔断器的三种状态

Closed(关闭状态) :熔断器在正常情况下处于关闭状态,所有请求都会正常发往目标服务。当服务调用失败次数或失败率达到阈值时,熔断器会从 Closed 状态变为 open 状态。

Open (打开状态) :当熔断器处于打开状态时,Hystrix 会阻断所有对目标服务的请求,直接返回降级结果。经过一段时间后,熔断器会自动进入 Half-Open 状态,尝试发送部分请求,以判断目标服务是否恢复。

Half-Open(半开状态):在半开状态下,部分请求可以尝试发往目标服务。如果这些请求成功率达到设定阈值,熔断器会关闭,恢复正常调用,如果半开状态下的请求失败率仍然很高,则熔断器会重新打开。

Hystrix 的常见配置项
  • circuitBreaker.requestVolumeThreshold: 熔断器触发的最小请求数量,当达到该数量且失败率超过阈值时,熔断器才会打开。

  • circuitBreaker.errorThresholdPercentage: 请求失败率的阈值,超过该百分比时,熔断器打开。

  • circuitBreaker.sleepWindowInMilliseconds: 熔断器打开后的休眠时间,休眠结束后进入半开状态。

  • coreSize: 线程池的核心线程数,控制并发线程数。

  • maxQueueSize: 线程池的最大队列长度,用于控制请求排队等待的数量。

  • queueSizeRejectionThreshold: 队列拒绝阈值,当队列中的请求超过该阈值,后续请求将被拒绝。

  • execution.isolation.semaphore.maxConcurrentRequests: 信号量隔离的最大并发请求数,超出该数量的请求将被拒绝。

负载均衡

主要用于将网络请求和流量 分发到多台服务器或服务实例上,从而提高系统的高可用性性能 ,同它还能确保系统在高并发访问下保持稳定,避免单个服务器因负载过高而导致性能下降或宕机,同时能够方便地进行横向扩展(Scale OUt)

负载均衡算法

哈希算法(Hash):根据请求的某个特定关键字来实现负载均衡,比如通过请求的 URL 路径、请求的 IP 地址等来计算哈希值,然后根据哈希值选择相应的后端服务器。

最小连接算法(Least Connection):根据后端服务器当前的连接数来决定请求的分配,负载均衡器会选择当前连接最少的服务器进行请求分配,保证后端服务器的负载均衡。

加权随机算法(Weight Random):在随机的基础上添加权值的概念,可以根据服务器的性能设置权值。

随机算法(Random):将请求随机分配后给后端服务器,每个后端服务器被选中的概念是一致的,不考虑服务器的负载情况。

加权轮询算法(Weighted Round Robin):在轮询算法的基础上添加权重的概念,可以从后端服务器的性能设置对应的权重和分配比例。

轮询算法(Round Robin):最简单的负载均衡算法,主要就是按照顺序将请求分配给后端服务器。

负载均衡的实现方式

硬件负载均衡

就是用一个硬件一个基础网络设备,类似于我们的交换机啊这样的硬件,来实现负载均衡,常见的硬件有:F5、A10;

优点:支持全局负载均衡提供全面的复杂均衡算法,支持百万以上并发,提供安全功能。例如:防火墙,防 DDos 攻击等。

缺点:扩展能力差,硬件成本高。

软件负载均衡

常见的软件有:LVS、Nginx、HAProxy

  • 四层负载均衡:在网络层利用 IP 地址端口进行请求的转发,基本上就是起个转发分配的作用。

  • 七层负载均衡:可以根据访问用户的 HTTP 请求头、URL 信息将请求转发到特定的主机。

缺点:和硬件负载均衡比性能一般,流量很大的企业就用软件负载均衡顶不住,没有防火墙或者防 DDos 攻击等安全性功能。

DNS 负载均衡

这个负载均衡是通过 DNS 来的,因为 DNS 解析同一个域名可以返回不同的 IP,所以主要是用来实现地理级别的负载均衡

优点:就近访问可以减少响应的时间,提升访问速度。

缺点:因为是运营商管理控制的所以扩展能力差。DNS 有缓存而且缓存时间较长,当机房迁移等需要修改 DNS 配置的时候,用户可能还会访问之前的 IP。

什么是 Feign

本身并没有负载均衡的能力,它负载均衡的能力需要依赖其他框架。比如 Ribbon 或者 loadbalancer。

特点:

声明式的服务客户端,通过 Java 接口和注解构建服务客户端,简化了 Http 调用的使用过程,无需手动构建 HTTP 请求。

很好地融入了 SpringCloud 生态,可以使用 SpringCloud 负载均衡、服务熔断等能力。

我们经常需要调用第三方提供的 Http 接口,此时我们就可以使用一些 Http 框架来实现,比如 HttpClient;

复制代码
public class HttpClientDemo {
    
    public static void main(String[] args) throws Exception {
        // 创建一个 HttpClient
        HttpClient httpClient = HttpClientBuilder.create().build();
        
        // 构建一个 get 请求
        HttpGet httpGet = new HttpGet("http://192.168.100.1:8080/order/1");
        
        // 发送请求,获取响应
        HttpResponse httpResponse = httpClient.execute(httpGet);
        HttpEntity httpEntity = httpResponse.getEntity();
        
        // 读出响应值
        String response = EntityUtils.toString(httpEntity);
        
        System.out.println("Response:" + response);
    }
}

Feign 是一个声明式的 Http 框架,当你需要调用 Http 接口时,你需要声明一个接口,加一些注解就可以了。而像组装参数、发送 Http 请求等重复性的工作都交给 Feign 来完成。

为什么 Feign 第一次调用耗时很长

主要是由于 Feign 内置的负载均衡组件 Ribbon 的懒加载机制。

  • 主动预热:只需要在应用启动的时候进行服务预热,先自动执行一次随便的调用,提前加载 Ribbon 以及其他相关的服务调用组件。

  • 开启 Ribbon 的饥饿加载

复制代码
# application.yml 配置
ribbon:
	eager-load:
		enabled: true

ribbon.eager-load.enabled: 将其设置为 true,表示启用 Ribbon 的饥饿加载机制。

ribbon.eager-load.clients: 指定需要进行饥饿加载的 Feign 客户端列表,用逗号分割。这些客户端的 Ribbon 配置和服务实例信息将在应用启动时预先加载。

Ribbon 的客户但缓存机制

默认情况下,Ribbon 会隔取 30s 刷新一次缓存中的服务实例列表,意味着在此期间内,客户端获取到的服务实例信息不会变化,通过调整 ribbon.ServerListRefreshInterval 的值,可以更频繁或更少地刷新缓存。

Spring Cloud LoadBalancer 是 Spring 官方提供的新一代负载均衡,旨在替代 Ribbon,提供更加简洁和现代化的配置方式。

Ribbon 如何得知服务实例的数据

需要注册中心主动去适配 Ribbon,只要注册中心去适配了 Ribbon,那么 Ribbon 自然而然就知道服务实例的数据了。

当 Ribbon 通过 ServerList 获取到服务实例数据之后,会基于这些数据来做负载均衡的。Nacos 自然而然也实现了 ServerList 接口,为 Ribbon 提供 Nacos 注册中心的服务。

同样的,除了 Nacos 之外,Eureka、Zookeeper 等注册中心也都实现了这个接口。

对于 loadbalancer 来说,其实也是这种模式,需要不同的注册中心去适配,这样也就可以获取到服务注册中心的服务实例信息了。

Feign 和 Dubbo 的区别

核心架构协议
Feign

基于 HTTP 协议(如 REST/JSON),适用声明式注解(如 FeignClient)定义客户端接口,适用于微服务间的轻量级通信。

本质上是将 HTTP 请求封装 Java 接口调用,依赖外部服务发现(如 Eureka)和负载均衡(如 Ribbon)。

Dubbo

基于自定义二级制协议(如 Dubbo 协议),适用 RPC 模式,支持高效序列化(如 Hessian、Kryo)。

内置服务注册中心、负载均衡、容错等机制,提供完整的服务治理能力。

优点:高性能、低延迟,适用于高并发分布式系统,但配置相对复杂。

通信模型

Feign:采用同步 HTTP 请求/响应模型,每次调用可能涉及 TCP 连接建立和断开,开销较大,但兼容性强。

Dubbo:基于长连接和 NIO(非阻塞 I/O),支持异步调用,减少了网络开销,吞吐量更高。

服务发现与负载均衡

Feign:依赖外部组件(如 Spring Cloud Eureka)实现服务发现,负载均衡通过客户端库(如 Ribbon)完成。

Dubbo:基于长连接和 NIO(非阻塞 I/O),支持异步调用,减少网络开销,吞吐量更高。

适用场景

Feign:适合 RESTful 微服务架构,尤其是 Spring Boot/Cloud 项目,需要快速开发 HTTP API 客户端。

Dubbo: 适合企业级分布式系统,对性能要求高,需要服务治理(如熔断、监控)的场景。

性能与扩展

Fegin:HTTP 协议协议通用性强,但序列化(JSON)和网络开销较大,适合中小规模微服务。

Dubbo:二进制协议效率高,支持自定义扩展(如过滤器、路由),更适合大规模高可用系统。

Feign 和 OpenFeign 的区别

OpenFeign 是 Spring Cloud 在 Feign 的基础上进一步封装的,它整合了 Spring Cloud 的特性,使得我们可以更加简单地使用 Feign:

自动配置:OpenFeign 利用 Spring Boot 的自动配置机制,通过 @FeignClient 和 @EnableFeignClients

Feign 的本质:动态代理 + 七大核心组件
contract

这个 Contract 接口会去解析方法上的注解和参数,获取 Http 请求需要用到基本参数。

SpringCloud 在整合 Feign 的时候,为了让 Feign 能够识别 Spring MVC 的注解,所以就自己实现了 Contract 接口。

Encoder

Feign 默认的 Encoder 实现只支持请求体对应的方法参数类型为 String 和字节数组。具体作用就是将请求体对应的方法参数序列化成字节数组。

Spring 就实现了 Encoder 接口,可以将任意请求体对应的方法参数类型对象序列化成字节数组。

Decoder

Decoder 其实就是将响应体由字节流反序列化成方法返回值类型的对象,和 Encode 的默认情况是一样的,只支持反序列化成字节数组或者是 String。

所以 Spring 同样实现了 Decoder,用来扩展它的功能。

Client

这其实就是动态代理对象最终用来执行 Http 请求的组件,默认实现就是通过 JDK 提供的 HttpURLConnection 来的。

如下是 OpenFeign 用来整合 Ribbon 实现复杂均衡的核心实现。

InvocationHandlerFactory

对于 JDK 动态代理来说,InvocationHandler 的 invoke 实现方法就是动态代理走的核心逻辑,而 InvocationHandlerFactory 顾明思义其实就是创建 InvocationHandler 的工厂。

去看看 InvocationHandler 的实现类 FeignInvocationHandler

除了 Object 类的一些方法,最终会调用方法对应的 MethodHandler 的 invoke 方法,虽然说默认情况下 SpringCloud 使用是默认实现,最终使用 FeignInvocationHandler,但是当其他框架整合 SpringCloud 生态的时候,为了适配 OpenFeign,有时会自己实现 InvocationHandler。

RequestInterceptor

RequestInterceptor 它其实是一个在发送请求前的一个拦截接口,通过这个接口,在发送 Http 请求之前再对 Http 请求的内容进行修改。比如我们可以设置一些接口需要的公共参数,如鉴权 token 之类的。

复制代码
@Component
public class TokenRequestInterceptor implements RequestInterceptor {
    
    @Override
    public void apply(RequestTemplate template) {
        template.header("token", "token值");
    }
}
Retryer

默认实现如下

默认情况下,最大重试 5 次,在 SpringCloud 下,并没有使用上面那个实现而是使用的是下面这个实现。所以 SpringCloud 下默认是不会进行重试。

Feign 核心运行原理分析

动态代理生成原理
复制代码
public class FeignDemo {
    
    public static void main(String[] args) {
        OrderApiClient orderApiClient = Feign.builder().target(OrderApiClient.class, "http://localhost:8088");
        
        orderApiClient.queryOrder(9527L);
    }
}

Feign.builder().target(xx) 获取到的动态代理如下:

最终会调用 ReflectiveFegin 的 newInstance 方法来创建动态代理对象,而 ReflectiveFeign 内部设置了前面提到的一些核心组件。接下来我们可以看看 newInstance 方法。

首先解析接口,构建每个方法对应的 MethodHandler,MethodHandler 在前面讲 InvocationHandlerFactory 特别地提醒过动态代理(FeignInvocationHandler)最终会调用 MethodHandler 来处理 Feign 地一次 HTTP 调用。然后通过 InvocationHandlerFactory 创建 InvocationHandler 然后再构建出接口的动态代理对象。

一次 Feign 的 HTTP 调用执行过程

调用接口动态代理的方式时,通过 InvocationHandler (FeignInvocationHandler),最终交给 MethodHandler 的 invoke 方法来执行。MethodHandler 是一个接口,最终会走到它的实现类 SynchronousMethodHandler 的 invoke 方法实现。

SynchronousMethodHandler 中的属性就是我们前面提到的一些组件。

  • 首先说进入 FeignInvocationHandler,找到方法对应的 SynchronousMethodHandler,调用 invoke 方法实现。

  • 之后根据 MethodMetadata 和方法的入参,构造出一个 RequestTemplate,RequestTemplate 封装了 Http 请求的参数,在这个过程中,如果有请求体,那么会通过 Encoder 序列化

  • 然后调用 RequestInterceptor,通过 RequestInterceptor 对 RequestTemplate 进行拦截扩展,可以对请求数据再进行修改

  • 再然后讲 RequestTemplate 转换成 Request,Request 其实跟 RequestTemplate 差不多,也是封装了 Http 请求的参数

  • 接下来通过 Client 去根据 Request 中封装的 Http 请求参数,发送 Http 请求,得到响应 Response

  • 最后根据 Decoder,将响应体反序列化成方法返回值类型对象,返回。

扫盲八股

Spring Cloud Alibaba 与 Spring Cloud 有什么区别,你了解多少呢?

它是基于 Spring Cloud 构建的一套微服务开发一战式解决方案,它额外增加了一些特定于阿里云的解决方案和工具,它集成度高、生态丰富、可扩展性能强、功能全面。它包括:服务注册与发现(Eureka/Nacos)、负载均衡(Ribbon)、断路器(Hystrix/Resilience4j)、API 网关(Zuul/Spring Cloud Gateway)、链路追踪(Sleuth/Zipkin)、Spring Cloud Config 配置管理:提供分布式系统中的外部配置支持,配置可以动态刷新,无需重启应用。

Spring Cloud Gateway 作为统一网关,接收用户请求。在网关层可能会配合 Sentinel 做限流和熔断,避免突发流量打爆后端。网关会去 Eureka / Nacos / Consul 查询可用的服务地址。通过 Ribbon / LoadBalancer 实现负载均衡,应用之间调用用 openFeign,代码更简洁。所有微服务的配置都统一放在 Spring Cloud Config / Apollo / Nacos Config ,配置变化后可以通过 Spring Cloud Bus 通知集群里的服务动态刷新。如果某个下游服务挂了,Sentinel / Resilience4j 会做熔断降级,返回兜底结果。一些非核心逻辑会丢到Spring Cloud Stream(Kafaka、RabbitMQ) ,实现解耦和削峰。用 Sleuth + Zipkin 给调用链打日志和 traceId, 方便排查性能问题,Spring Boot Admin 可以看到各个微服务的运行情况。

现在有哪些流行的微服务框架
  • Spring Cloud Netflix,它是 Spring Cloud 下面的一个子项目,结合了 Netflix 开源的多款组件,包括我们所熟知的 Eureka、Hystrix、Zuul 等组件,并逐渐进入了维护状态。

  • Spring Cloud Alibaba 这个是目前主流用的比较多的方案,它是阿里巴巴开源的一套微服务下的解决方案,主要包括了 Nacos、Sentinel、RocketMQ 等多款开源组件。

  • Dubbo 很多人认为其只是一个 RPC 框架,其一些服务治理的功能还是依赖于第三方组件实现,如 Zookeeper、Apollo 等,现在支持 (TCP、HTTP、Redis 等) 和多种序列化方式(如 Json、Hessian、Protobuf 等)、可以实现按需分配。

  • 主要是作为一个 RPC 框架,具有较高的性能和可扩展性,提供了服务注册与发现、负载均衡、容错、分布式调用等功能。不过近两年其为了适应云原生时代的发展,又开始了迭代,并推出了 Dubbo 3。

在微服务中是怎么使用链路追踪的,它可以选择哪些微服务链路追踪方案

Spring Cloud Sleuth

它是 Spring Cloud 提供的分布式链路追踪库,它会在每个请求中自动生成 Trace ID 和 Span ID,并将这些 ID 传递到调用链中的所有服务中。Zipkin 是一种分布式追踪系统,支持收集和展示 Spring Cloud Sleuth 生成的追踪数据,它能够将每个请求的详细路径进行可视化展示。

Jaeger

它是 CNCF 托管的分布式追踪系统,支持高性能的追踪数据收集和存储,与 Zipkin 相比,它更适合大规模分布式系统。Spring Cloud Sleuth 可以与 Jaeger 集成,利用 Jaeger 的强大功能进行请求追踪、分析和展示,支持对历史追踪数据的搜索和分析。

SkyWalking

它是 Apache 开源的应用性能监控和分布式追踪平台,支持多种语言和框架。它不仅可以提供链路追踪功能,还支持全链路的 APM(应用性能管理),包括指标监控、告警和服务依赖分析。它可以直接与 Spring Cloud 微服务集成,自动采集微服务的调用数据,生成调用链路图和依赖关系视图,适合对系统性能有较高要求的场景。

OpenTelemetry

它是一个统一的分布式追踪和指标收集的框架,由 OpenTracing 和 OpenCensus 合并而来,它提供了跨平台和跨语言的支持,是未来分布式追踪领域的重要方向。在 Spring Cloud 中,开发者可以使用 OpenTelemetry 与 Spring 集成,将追踪数据发送到支持 OpenTelemetry 的后端系统(如 Jaeger、Zipkin、Prometheus 等)。

基本概念
  • Trace ID: 表示整个调用链的唯一标识,当一个请求发起时,系统会生成一个 Trace ID,用于标识该请求在整个系统中的流转路径。

  • Span ID: 每个服务处理请求的一个单元称为一个 Span,每个 Span 都有一个唯一的 Span ID。Span 记录了每个微服务在调用链中的处理时间、日志和元数据。

  • Span 关系:Span 之间可以有父子关系,一个 Trace 可以包含多个 Span,用于描述微服务之间的调用关系,这些关系构成了调用链树。

工作原理

数据采集:链路追踪工具通过拦截 HTTP 请求、数据库查询、RPC 调用等操作来记录请求的流转过程。它会在每个请求中注入 TraceID 和 SpanID,并将这些信息传递给下一个服务。

数据传递:链路追踪信息(Trace ID 和 Span ID)通过 HTTP Header 或 RPC 调用头部进行传递,确保所有服务都能共享同一个 Trace ID,从而实现对同一请求的全程跟踪。

数据上报:各个微服务将追踪信息发送到集中式的追踪存储系统(如 Zipkin、Jaeger)。存储系统会汇总和分析这些数据,并通过可视化界面展示给开发者。

微服务链路追踪的优势

首先它能够记录每个请求在各个服务中的处理时间和顺序,帮助开发者了解请求在系统中的全貌,提升系统的可观测性。然后当系统出现延迟或错误时,可以通过追踪请求的调用路径,精确定位到性能瓶颈或异常服务,帮助开发者分析系统的性能瓶颈,优化服务的调用链和接口性能。最后它可以持续监控请求的延迟和错误率,生成调用链的拓扑图和统计数据,使运维人员可以更好地管理和优化微服务架构。

什么是服务网格

它是一个专门用于处理服务间通信的基础设施层 ,它通过部署在每个服务旁边的Sidecar 代理 ,拦截服务间的网络流量,实现诸如流量管理、故障恢复、服务发现 等功能。常见的服务网格解决方案包括 Istio、Linked 等。

它的核心就是通过 Sidecar 模式,将复杂的网络通信逻辑从微服务中分离出来,应用开发者只需关注业务逻辑,无需处理服务间的通信管理。

它是一种新型的架构模式,主要的思想就是将服务通信从服务代码中抽象出来,并且在应用中实现统一的管理和控制,这里可以再看下腾讯云的服务网格架构图。

服务网格提供了强大的可观测性工具,开发者可以轻松监控服务间的调用情况、响应时间等,从而快速定位问题。

服务网格的核心组件

控制平面(Control Plane) :控制平面负责管理和下发服务网格的配置,协调各个代理的行为。它负责流量策略的配置、策略规则的下发、身份认证的管理等。它是服务网格的"大脑",通过配置管理来实现流量控制、安全策略和可观测性功能。常见的比如:Pilot、Citadel、Galley 等组件。

数据平面(Data Plane) :它由Sidecar 代理组成,这些代理部署在每个微服务旁边,负责处理服务之间的所有网络流量。它和微服务应用在同一宿主机上运行,能够透明地管理进出服务地流量,无需修改应用程序代码。

什么是灰度发布、金丝雀部署以及蓝绿部署

灰度发布

它是一种渐进式的发布方式,通过将新版本逐步推送给部分用户进行试用,逐步扩大使用范围,直到新版本完全替换旧版本。目的是通过小范围的用户测试,验证新版本的稳定性,降低发布新版本的风险。

适用于业务逻辑变动较大的场景使用,并在影响较小的情况下进行回滚。

金丝雀部署

它是一种特殊的灰度发布策略,通常是指将新版本部署到少量的实例上,并仅将部分流量引导至这些实例,以验证新版本在实际生产环境中的表现。它可以通过负载均衡器或服务网格,将一定比例的流量引导到运行新版本的实例上,若新版本稳定,再逐步增加其流量占比。

它适用于需要在生产环境中验证新功能或配置变更的场景,通过金丝雀部署,可以提前发现新版本在生产环境中的潜在问题。同时也适用于在实际生产环境下进行性能测试或新版本兼容性测试的场景。

蓝绿部署

它是一种无缝切换的部署策略,当绿色版本(新版本)准备就绪时,通过负载均衡或路由切换,将所有流量从蓝色版本(当前的生产版本)切换到绿色版本。它通常是通过负载均衡器、DNS 切换或 API 网关,来切换蓝色版本和绿色版本之间的流量指向。

适用于需要快速切换和回滚的场景,特别是在对系统稳定性要求高的应用中,通过蓝绿部署,可以实现无停机升级。

HTTP 与 RPC 之间的区别

HTTP 是一种应用层的协议,主要强调的是网络通信,然后 RPC 其实是一种分布式系统之间通信的方式,强调的是服务之间的远程调用。有一些 RPC 框架底层使用的是 HTTP 协议实现通信,比如 gRPC,其底层使用的就是 HTTP/2,然后还有 Dubbo,其现在也兼容了 gRPC 了,并且使用了 HTTP/2 作为传输协议。

HTTP 的传输格式:啊通常使用文本格式(如 JSON、XML)传输数据,容易被人类阅读和理解,由于 HTTP 是基于 TCP 的应用层协议,因此它的消息体较大,适用于 Web 页面、API 等场景。

RPC 的传输格式:RPC 传输的数据通常是二进制格式(如 Protocol Buffers、Thrift),数据量较少,效率较高。通常是基于 TCP 进行通信,同时也可以直接使用 HTTP。

说明:IDL 是指接口定义语言,如二进制、JSON、Protocol Buffers 等。

相关推荐
爱笑的眼睛117 小时前
MLflow Tracking API:超越实验记录,构建可复现的机器学习工作流
java·人工智能·python·ai
好学且牛逼的马7 小时前
Apache Commons DbUtils
java·设计模式·apache
榮十一7 小时前
100道Java面试SQL题及答案
java·sql·面试
专注于大数据技术栈7 小时前
java学习--String
java·开发语言·学习
胡玉洋7 小时前
Spring Boot 项目配置文件密码加密解决方案 —— Jasypt 实战指南
java·spring boot·后端·安全·加密·配置文件·jasypt
苹果醋37 小时前
JAVA设计模式之观察者模式
java·运维·spring boot·mysql·nginx
明洞日记7 小时前
【设计模式手册019】状态模式 - 管理对象状态转换
java·设计模式·状态模式
guslegend7 小时前
SpringSecurity认证原理与实战
java
JIngJaneIL7 小时前
基于java+ vue畅游游戏销售管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·游戏