详谈微服务架构 【服务注册与发现】【熔断】【网关】【限流】【链路跟踪】【分布式事务】

404 阅读21分钟

1. 微服务架构介绍

微服务架构是一种基于服务拆分的分布式架构模式,通过将应用程序拆分成一组更小、更独立的服务单元来实现。每个服务单元可独立开发、测试、部署,并使用相应的技术栈。这些服务通过轻量级通信机制进行相互协作,从而构建出一个灵活、可扩展的系统。

image.png

1.1. 微服务优点

微服务优点和缺点的介绍由文心一言生成。

  1. 服务拆分:微服务架构将复杂的单体应用拆分成一组独立的服务单元,每个服务单元都有明确定义的边界和职责。
  2. 高度解耦:每个微服务都是独立的代码库和部署单元,可以独立开发、测试和部署。微服务之间通过轻量级通信机制进行通信,如HTTP/REST、消息队列等。
  3. 可独立部署和扩展:每个微服务都可以独立部署,可以根据需求对其中一个或多个服务进行水平扩展,而无需影响整个系统。
  4. 技术异构性:微服务架构允许使用多种编程语言和技术栈来开发不同的微服务。这样可以根据需求选择最适合的技术来解决特定问题。
  5. 易于维护和理解:由于每个微服务都是独立的,代码库较小,职责清晰,因此易于理解和维护。同时,每个微服务都有自己的团队负责开发和维护,可以更快地响应问题和需求变更。
  6. 高度可伸缩性:微服务架构可以根据需求对每个微服务进行独立的扩展。这样可以更好地利用资源,提高系统的性能和可伸缩性。
  7. 容错性和可恢复性:由于微服务之间是松耦合的,一个微服务的故障不会影响整个系统的正常运行。微服务架构可以更好地处理故障隔离和容错恢复,提高系统的可靠性。

1.2. 微服务缺点

  1. 系统复杂性:微服务架构会增加系统的复杂性,需要管理大量的微服务实例和服务之间的通信。
  2. 分布式系统的挑战:微服务架构中的服务分布在不同的节点上,需要解决分布式系统的一致性、通信、事务管理等问题。
  3. 服务发现和治理:需要实现服务注册与发现、负载均衡、熔断、限流等功能,确保服务之间的通信和协调。
  4. 数据一致性:在微服务架构中,需要解决跨服务的数据一致性和事务管理问题。
  5. 监控和调试:微服务架构中的服务数量庞大,需要建立完善的监控系统和日志系统,便于故障排查和性能优化。

2. 微服务架构问题

虽然微服务架构也有它的缺点,但是与它的优点相比,还是可以接受的。下面讨论一下微服务架构的缺点及其解决方案。

  • 服务发现:当我们将不同的模块拆分之后并将模块部署在不同的服务器上时,服务如何调用另一个服务、用户如何调用服务如何解决?难道通过 ip:port 的形式来调用吗?那万一被调用的服务 ip 发生了改变怎么办?这可以通过 服务注册中心 来解决,后面再来介绍。

  • 服务熔断:当一个服务调用另一个服务时,被调用的服务由于发生了错误导致一直在处理,也不给调用者响应信息,导致调用者一直等待响应。在这个等待过程中,如果还有大量的请求到调用者上时,就可能导致请求堆积,严重可能会导致服务器宕机。像这种由于被调用的服务处理时间非常长导致调用者因长时间等待而导致的请求堆积,最终导致服务器宕机的情况就叫做 服务雪崩。当被调用者长时间未返回结果时,我们主动就中断请求,这个过程就叫做 服务熔断

  • 服务降级:在服务熔断的基础上,当我们一个功能的实现可能需要多个服务共同返回的数据用于展示,比如我们在实现一个搜索功能时,需要展示商品的评论数量和商品的信息,而评论信息和商品信息的获取需要访问两个不同的服务。这时如果评论服务出现故障,比如服务器宕机了,这时我们就可以只展示商品的信息,不展示评论信息,这种做法就叫做 服务降级

下面我们将会使用 服务注册中心(Eureka)服务调用器(Ribbon)服务网关(SpringCloud GateWay) 的使用。

3. 微服务搭建

3.1. 服务注册中心搭建

Eureka 是一个由 Netflix 开发的服务发现框架,基于 RESTful API 的服务,主要用于微服务架构中实现服务的注册与发现。此外还有 NacosZookeeperConsulEtcd 等。

下面我们将基于 SpringBoot 来搭建注册中心。

首先就是创建一个普通的 SpringBoot 项目,当然你也可以在创建 SpringBoot Initialer 勾选上 Eureka 这个依赖,在添加时,你或许会看到 Eureka Server 和 Eureka Client,如果当前服务器是作为注册中,你就选择 Eureka Server,如果是服务提供者,你就选择 Eureka Client 这个依赖(如果你不使用 Eureka,那就得自己去看看其他教程了)。或者自己添加下面的依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

添加依赖之后,后面的步骤也非常简单,所以这里基本就是做个记录,没什么技术含量这里。在启动类上加上 @EnableEurekaServer 注解。

@SpringBootApplication
@EnableEurekaServer
public class ServiceRegisterApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceRegisterApplication.class, args);
    }
}

最后在 application.yml 文件中输入下面的配置:

server:
  port: 8761

spring:
  application:
    name: eureka-server

至此注册中心就搭建完毕了。

3.2. 服务提供者搭建

再创建一个 SpringBoot 项目,这会我们要选择 Eureka Client 这个依赖,你也可以自己添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

好,下面在启动类上加上 @EnableEurekaClient 这个注解,或者你也可以选择 @EnableDiscoveryClient 注解。这两个注解的区别就是前者只适用于 Eureka 注册中心,后者适用于所有的注册中心,并且官方也推荐使用后者。

下面再添加配置:

server:
  port: 9004

spring:
  application:
    name: service-provider

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

服务提供者的搭建到这也结束了,但是我们没有搭建 SpringMVC,这里就不演示了,这里就简单地记录搭建步骤。

3.3. 服务通信

好,微服务搭建好了,我们如何进行服务之间的通信呢?通过使用 RestTemplate 这个类来实现的。

首先创建个配置类,在里面使用 @Bean 导出一个该类的实例即可。

@Configuration
public class AppConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

@LoadBalanced 注解的作用是将一个 RestTemplate 对象标记为支持负载均衡的,从而允许根据服务名称进行 REST 调用时,使用负载均衡算法来选择目标服务的实例。在微服务架构中,服务间通信经常通过 HTTP 调用进行。@LoadBalanced 注解使得客户端在调用服务时,不再需要关心服务的具体实例地址(如 IP 地址和端口号),而是直接通过服务名称进行调用。Spring Cloud 会自动为这些调用添加负载均衡支持,确保请求能够均匀地分布到服务的各个实例上。

下面是我的一段测试代码,服务之间的通信需要借助注册中心来完成,并且它的通信方式也与 HTTP 有点类似。具体代码如下:

@RestController
@RequestMapping("/test")
public class TestController {

    private final RestTemplate restTemplate;

    @Autowired
    public TestController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @RequestMapping("/hello")
    public Object hello() {
        return restTemplate.getForObject("http://service-provider/love/birthday", String.class);
    }
}

可以看到,服务之间的通信地址不是使用 IP:PORT 的形式表示的,是使用服务名来表示的,当我们试图请求其他的服务时,首先拿服务名从注册中心获取对应服务的所有的服务器的地址列表,再通过负载均衡从服务器列表中选择一个服务器进行请求。

3.4. 简化服务调用

之前我们是使用 RestTemplate 对象来调用其他的服务的,下面我们会使用 Feign 来取代它,与 RestTemplate 相比,Feign 有以下优点:

  • 声明式接口:Feign 允许你使用 Java 接口和注解来定义 HTTP 请求,这使得代码更加简洁和易读。
  • 内置负载均衡:Feign 可以与 Ribbon 或其他负载均衡器集成,提供内置的负载均衡功能。这意味着你可以更容易地在多个服务实例之间进行调用,而无需在代码中显式处理负载均衡逻辑。
  • 服务发现:Feign 可以与 Eureka、Consul 或其他服务发现工具集成,自动从服务注册中心获取服务列表。这使得服务之间的调用更加灵活和可靠,因为你不需要手动配置服务地址。
  • 错误处理和重试:Feign 提供了更高级的错误处理和重试机制,可以帮助你更好地处理网络故障和服务不可用的情况。你可以配置 Feign 以在请求失败时自动重试,或者实现自定义的错误处理逻辑。

下面就来用 Feign 简化调用。由于 Feign 是简化调用其他的服务,所以这个 Feign 依赖应该加在调用者上。

在 SpringBoot 启动类上加上 @EnableFeignClients,默认情况下,它会扫描与主应用程序类在同一包或子包下的所有 @FeignClient 注解的接口,并将它们注册为 Spring Beans。你也可以通过指定 basePackages 或 clients 属性来更改扫描的包或指定要加载的特定客户端。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.example.serviceconsumer.feign")
public class ServiceConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerApplication.class, args);
    }
}

然后创建一个接口,在这个接口上加上 @FeignClient 注解,其中 value 是被调用的服务名,其中的方法要写上 @RequestMapping 注解,表示方式映射的请求路径,不过这里请求路径最前面是没有斜杆的,需要注意一下,关于这个 Feign 的传参还有一些需要注意的地方,后面会说明。

@FeignClient("service-provider")
public interface LoveRemoteCaller {

    @GetMapping("love/birthday")
    String getBirthdayLove();
}
3.4.1. Feign 传参注意事项

假设被调用者有如下的 Controller:

@RestController
@RequestMapping("/love")
public class LoveController {

    @GetMapping(value = "/birthday")
    public String getBirthdayLove(String name) {
        return name + ", Happy Birthday to You!";
    }
}

如上,我们会在调用时使用 GetMapping 来映射请求路径,但是你去写时可能会发生错误,具体的错误是由于 Feign 默认会将请求参数作为请求体来发送,另外没有使用 @RequestParam 注解的话,Feign 会将 Get 请求方法改为 Post。

下面的代码在调用上面的控制器方法时会发生错误。

@FeignClient("service-provider")
public interface LoveRemoteCaller {

    @GetMapping("love/birthday")
    String getBirthdayLove(String name);
}

应像下面这样添加一个 @Requestaram 注解,它的 value 也是必须给定的:

@FeignClient("service-provider")
public interface LoveRemoteCaller {

    @GetMapping("love/birthday")
    String getBirthdayLove(@RequestParam("name") String name);
}

OK,以上就是使用 Feign 传参需要注意的事项,POST 倒是没有需要特别注意的。注意在 Feign 中,GET 请求不能有请求体,POST 请求不能有请求参数,上面的错误是因为给了 GET 请求设置了请求体,另外给 POST 请求设置请求参数也会报错。

3.5. Eureka 集群搭建

我们就创建两个 Eureka 服务器来演示集群搭建,分别为 87618762 端口。然后这两个节点会相互注册到对方的注册表中。

这里就只写 8761 的配置,8762 与 8761 配置相似

server:
  port: 8761

spring:
  application:
    name: eureka-server

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      # 这里填除自己以外的其他注册中心节点
      defaultZone: http://localhost:8762/eureka/

然后在服务提供者的配置中书写下面的代码

server:
  port: 9004

spring:
  application:
    name: service-provider

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      # 这里填所有的注册中心的节点
      defaultZone: http://localhost:8761/eureka/, http://localhost:8762/eureka/

然后启动所有的节点即完成 Eureka 集群的搭建。

3.6. 熔断器

3.6.1. 介绍熔断器
  • 服务雪崩 在微服务架构中,服务与服务之间通过RPC或HTTP相互调用。由于网络原因或自身原因,服务并不能保证100%可用。如果一个服务出现故障,调用该服务的请求会出现线程阻塞。若此时有大量请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。这种故障会传播,对整个微服务系统造成灾难性的后果,即 服务雪崩。熔断器的作用就是防止这种级联失败的发生。
  1. 快速失败与恢复
    • 当对特定服务的调用失败次数达到一个阈值(如Hystrix默认的5秒内20次失败),熔断器会被打开。此时,对该服务的调用会被直接拒绝,避免连锁故障的发生。同时,熔断器会进入半开状态,允许部分请求通过以检测服务是否恢复。如果请求成功,熔断器会关闭;如果请求失败,熔断器会继续保持打开状态。
  2. 提供回退策略
    • 当熔断器打开时,为了避免直接返回失败结果给调用方,熔断器会提供一个回退(fallback)方法。这个方法可以返回一个默认值、错误信息或执行一些备用逻辑,以保证服务的可用性。
3.6.2. 熔断器的实现原理
  1. 统计与监控
    • 熔断器会对依赖服务的调用情况进行统计,包括成功次数、失败次数、拒绝次数、超时次数等。同时,熔断器还会对这些指标进行实时监控,以便在需要时作出相应的调整。
  2. 阈值判断
    • 熔断器会根据配置的阈值(如请求失败率、失败次数等)来判断是否打开熔断器。当达到阈值时,熔断器会进入打开状态,拒绝对该服务的调用。
  3. 状态转换
    • 熔断器的状态可以在关闭、打开和半开之间转换。关闭状态下,请求被允许通过;打开状态下,请求被直接拒绝;半开状态下,允许部分请求通过以检测服务是否恢复。
  4. 动态配置
    • 熔断器的阈值、超时时间等参数可以动态配置,以便根据系统的运行情况进行调整。
3.6.3. 配置熔断器

熔断器是在服务调用者上添加的,下面是要添加的依赖。另外如果 SpringBoot 没有提供版本依赖,则我们需要自己去找对应的版本。在 Maven 仓库随便找个就行了。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

然后在我们要进行熔断的方法上加上 @HystrixCommand 注解,具体配置如下:

@HystrixCommand(fallbackMethod = "fallback", commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
@RequestMapping
public String testFeign() {
    String res = loveRemoteCaller.getBirthdayLove("flowerwine");
    System.out.println(res);
    return res;
}

关于 @HystrixProperty 注解,请自行查阅官方文档,当然这里是限制命令执行的时间不能超过 3000 毫秒。它还有其他的 name,非常多。GitHub -- Netflix/Hystrix

最后再启动类上加上 @EnableHystrix 注解即可。

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class ServiceConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceConsumerApplication.class, args);
    }
}

关于熔断器的实现原理,可以自行百度。

3.6.4. Hystrix 仪表盘配置

首先创建一个新的项目,这个新的项目是我们 Hystrix 的仪表盘项目,它是一个单独的服务器。我们将这个项目命名为 hystrix-dashboard

然后在这个项目中引入相关的依赖,如下所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

之后在这个项目的启动类上加上 @EnableHystrixDashboard 注解。

最后在它的 yml 配置文件中给出以下配置即可。

server:
  port: 8081

spring:
  application:
    name: hystrix-dashboard

# 允许本机访问
hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"

然后就是在每个使用到熔断器的项目中,向 spring 容器中注入 ServletRegistrationBean 这个类。

@Bean
public ServletRegistrationBean<HystrixMetricsStreamServlet> getServletRegistrationBean(){
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean =
            new ServletRegistrationBean<>(streamServlet);
    registrationBean.setName("HystrixMetricsStreamServlet");
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/hystrix.stream");
    return registrationBean;
}

至此熔断器仪表盘就搭建完成。接下来就来访问一下 http://localhost:8081/hystrix,你将看到如下界面:

image.png

然后在输入框输入具有熔断器的服务地址即可。如:http://localhost:9090/hystrix.stream

image.png

然后点击 Monitor Stream 按钮即可。下面是熔断器运行状态。如果一直处于加载状态,那么你可以访问一下对应的服务,这样可能会解决。

image.png

3.7. Spring Security

Spring Security 是一个功能强大且高度可定制的安全框架,为基于 Spring 的应用程序提供全面的安全性解决方案。

下面我们就对其进行简单的配置使用。

首先安装它的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

然后在注册中心的 spring 配置文件中书写下面的代码,下面的代码定义了访问该服务器需要提供的用户名和密码,需在请求时提供。

server:
  port: 8761

spring:
  application:
    name: eureka-server
  security:
    user:
      name: username
      password: password

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      # 这里填除自己以外的其他的注册中心节点
      defaultZone: http://username:password@localhost:8762/eureka/

然后在服务提供者的配置文件中书写下面的代码

server:
  port: 9004

spring:
  application:
    name: service-provider

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      # 服务提供者需要填写所有的注册中心节点
      defaultZone: http://username:password@localhost:8761/eureka/, http://username:password@localhost:8762/eureka/

最后在要设置密码访问的服务器上创建一个配置类,在这个配置类中,我们详细地定义了请求的安全程度。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        // 所有的请求都要验证
        http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
    }
}

好了,Spring Security 的配置大概就是上面这个样子,当然 Spring Security 还提供了其他的安全配置,不仅上面这一点。

3.8. SpringCloud Gateway

Spring Cloud Gateway 的设计目标是提供一个统一的 API 入口,为微服务应用程序提供基于路由的访问,同时还支持常见的负载均衡、安全、监控等功能。Spring Cloud Gateway 支持多种路由策略,包括基于路径、基于服务、基于请求参数等。它还支持动态路由,可以根据运行时的情况动态地添加、删除或更新路由规则。

Spring Cloud Gateway 的核心组件是 Route Predicate(路由谓词)和 Filter(过滤器)。路由谓词用于定义请求的匹配条件,包括请求路径、请求方法、请求头等;过滤器用于在请求和响应之间进行处理,包括修改请求和响应、添加请求头和响应头等。Spring Cloud Gateway 预置了许多常用的过滤器,例如 Hystrix、Swagger、RequestRateLimiter 等,也支持自定义过滤器。

3.8.1. 搭建

你只要引入下面这个依赖就算是搭建了一个 Springcloud Gateway 服务器。只不过离搭好一个 Gateway 服务器还差了一些。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
3.8.2. 断言

断言(Predicate)是用来做请求匹配的一个东西。Springcloud Gateway 提供了许多种类的断言供我们选择。断言相当于一个 if 判断,当断言成立时,就会请求对应的服务器。

下面是 Springcloud Gateway 断言的用例。

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
        - id: serviceA
          uri: http://localhost:8001
          predicates:
            - Path=/testA/**

上面的代码会检查请求路径是否为 testA 开头的,如果是则将请求转发至 url 对应的地址。此外还有对 header、cookie、method、query 等的断言。详细使用请看官网,戳这里 --> Route Predicate Factories :: Spring Cloud Gateway

3.8.3. 过滤器

Gateway 的过滤器类似于 Filter,能对请求和响应都进行处理。

  • 内置过滤器
spring:
  cloud:
    gateway:
      routes:
        - id: add_request_header_route
          uri: https://example.org
          filters:
            - AddRequestHeader=X-Request-red, blue

这个过滤器能在请求进入时给其添加请求头。官网戳这里 --> GatewayFilter Factories :: Spring Cloud Gateway

  • 全局过滤器

全局过滤器的自定义在官网有比较详细的说明,官网戳这里 --> Global Filters :: Spring Cloud Gateway

3.8.4. 自定义断言或过滤器

除了 Spring 自带的断言和过滤器,我们还可以自定义局部过滤器或者是全局过滤器以及断言,官网戳这里 --> Developer Guide :: Spring Cloud Gateway

3.8.5. 动态路由配置

虽然 Gateway 提供了网关的功能,但是它并不知道后端的服务是否可用,将服务节点直接写在 Gateway 的配置文件中,当对应的服务崩溃时,Gateway 并不能感知,所以这里我们需要借助 Eureka 注册中心来解决这个问题。

首先添加 Eureka server 依赖。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

相关的配置如下。其中 lb:// 是为调用服务提供负载均衡,后面接的是要调用的服务名。

server:
  port: 9999

spring:
  cloud:
    gateway:
      routes:
        - id: serviceA
          uri: lb://serviceA
          predicates:
            - Path=/testA/**
            
        - id: serviceB
          uri: lb://serviceB
          predicates:
            - Path=/testB/**
  
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

最后在启动类上加上 @EnableDiscoveryClient 注解即可。

3.8.6. 限流配置

首先导入依赖,Springcloud Gateway 的限流是基于 Redis 来实现的,采用的算法是令牌桶算法。关于令牌桶算法可以看看这篇文章:高并发限流之令牌桶-CSDN博客

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.9.0</version>
</dependency>

然后创建一个配置类,在这个类声明以什么作为 key。这个 key 将作为 redis 中的 key 进行存储。

@Configuration
public class AppConfig {
    @Bean
    public KeyResolver keyResolver() {
        return exchange ->
                Mono.just(exchange.getRequest().getURI().getPath());
    }
}

然后填写一下配置即可,当然以下代码是在前面代码的基础上改造的。

server:
  port: 9999

spring:
  application:
    name: gateway
  main:
    web-application-type: reactive
  cloud:
    gateway:
      routes:
        - id: a
          uri: lb://service-consumer
          predicates:
            - Path=/feign/**
          filters:
            - name: RequestRateLimiter
              args:
                # 令牌生成速率
                redis-rate-limiter.replenishRate: 1
                # 令牌桶容量
                redis-rate-limiter.burstCapacity: 2
                redis-rate-limiter.requestedTokens: 1
                key-resolver: "#{@keyResolver}"
  redis:
    host: localhost
    port: 6379
    lettuce:
      pool:
        max-active: 10
        max-wait: 1000
        max-idle: 5
        min-idle: 3

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

然后就可以去尝试访问对应的接口来看看限流是否起作用了。replenishRate 参数设置的越小,限流越明显。

3.9. 服务链路跟踪

微服务中的服务一多起来,调用关系变得复杂起来就让人摸不清服务的调用过程,出现问题时排查问题就变得十分困难。在这个场景下服务链路跟踪就诞生了。zipkin 就是一个常用的服务链路跟踪工具。下面我们会介绍如何配置它。

首先是安装依赖,它的依赖比较多,下面要留心一些,别装错了依赖。

zipkin 它是一个服务器,所以我们单独创建一个项目,并添加下面的依赖:

<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-server</artifactId>
    <version>2.11.10</version>
</dependency>
<!--zipkin界⾯-->
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-ui</artifactId>
    <version>2.11.10</version>
</dependency>

因为 zipkin 更新较慢,所以 SpringBoot 有些版本就与 zipkin 某些版本不兼容,需要注意,上面的 zipkin 版本在 springboot 2.1.9.RELEASE 版本下是能跑起来的。如果出现了问题就用这个版本。

然后再为该项目添加下面的配置。

spring:
  application:
    name: zipkin
server:
  port: 9411
management:
  endpoints.web.exposure.include: '*'
  metrics.web.server.auto-time-requests: false

最后在启动类上加上 @EnableZipkinServer 注解即可。

为了记录每个微服务的调用过程,我们要在每个微服务中添加下面的依赖,如果报错了,应该是版本的问题,可以去 Maven 仓库找找可以运行的版本。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

然后在每个服务的配置文件中添加下面的配置即可

server:
  port: 9004

spring:
  application:
    name: service-provider
  zipkin:
    enabled: true
    base-url: http://localhost:9411
  sleuth:
    sampler:
      probability: 0.1

最后访问 localhost:9411 即可看到可视化界面。

3.10 分布式事务