SpringCloud组件(1)-简单解析及测试

156 阅读23分钟

1 概述

1.1 架构图

img

  • 网关gateway、配置中心config、断路器breaker、服务注册registery、链路追踪、服务发现、一次性令牌,全局锁定,领导选举,分布式会话,集群状态

1.2 版本关系

image-20201124112523103

1.3 编码问题

  • 项目的yaml文件中如果增加了注释,一定要把file encoding更改为utf-8,默认为系统编码gbk

    image-20201124123955456

2 Eureka(服务注册与发现)

2.1 概述

  • 服务治理

  • 在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,需要使用服务治理管理服务之间依赖关系,可以实现服务调用、负载均衡、容错和服务注册和发现

  • 服务注册发现

    • 服务注册和发现中存在一个注册中心,当服务器启动时就会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上,而另一方(作为消费者)以改别名去注册中心上获取到实际的服务通信地址,然后再实现本地RPC调用
  • Euerka架构和Dubbo架构

    image-20201124114002432

2.2 单机Euerka

2.2.1 服务端

  • 依赖

    • 注意添加的依赖为spring-cloud-starter-netflix-eureka-server,表明为客户端
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  • 开启Euerka注册中心功能

    @EnableEurekaServer
    
  • 配置文件

    • eureka.instance.hostname指定主机地址的作用为客户端的注册地址(不再通过ip指定)

    • 服务名称spring.application.name的作用是当创建集群时,集群中的所有服务器可以使用同一个服务名用来做负载均衡

      image-20201124142849723

    server:
      port: 8001 #指定运行端口
    spring:
      application:
        name: eureka-server #指定服务名称
    eureka:
      instance:
        hostname: localhost #指定主机地址
      client:
        fetch-registry: false #指定是否要从注册中心获取服务(注册中心不需要开启)
        register-with-eureka: false #指定是否要注册到注册中心(注册中心不需要开启)
      server:
        enable-self-preservation: false #关闭保护模式
    
    • enable-self-preservation当关闭保护模式后,访问服务器主页时就会有对应的提示

      image-20201124133059008

2.2.2 客户端

  • 依赖

    • 注意添加的依赖为spring-cloud-starter-netflix-eureka-client,表明为服务端
    • 注意一定要引入spring-boot-starter-web,不然就会报客户端注册失败,错误码为204
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 表明为Eureka客户端

    @EnableDiscoveryClient
    
  • 配置文件

    • defaultZone不会提示,需要手动填写,注意yaml格式值前面需要添加空格
    • localhost代表的是服务端的eureka.instance.hostname
    server:
      port: 8101 #运行端口号
    spring:
      application:
        name: eureka-client #服务名称
    eureka:
      client:
        register-with-eureka: true #注册到Eureka的注册中心
        fetch-registry: true #获取注册实例列表
        service-url:
          defaultZone: http://localhost:8001/eureka/ #配置注册中心地址
    

2.3 集群版

2.3.1 服务端

  • 原有的eureka-server服务器添加两个配置文件,分别为application-replica1.yml和application-replica2.yml用来配置两个注册中心

  • application-replica1.yml

    • 相对于单机版,首先eureka.client增加了serviceUrl.defaultZone,并且对应的值为另一个注册中心,显然这里用的同样不是ip,而是eureka.instance.hostname
    • 修改了fetch-registry和register-with-eureka为true,因为现在服务端同样需要互相注册
    server:
      port: 8002
    spring:
      application:
        name: eureka-server
    eureka:
      instance:
        hostname: replica1
      client:
        serviceUrl:
          defaultZone: http://replica2:8003/eureka/ #注册到另一个Eureka注册中心
        fetch-registry: true
        register-with-eureka: true
    
    server:
      port: 8003
    spring:
      application:
        name: eureka-server
    eureka:
      instance:
        hostname: replica2
      client:
        serviceUrl:
          defaultZone: http://replica1:8002/eureka/ #注册到另一个Eureka注册中心
        fetch-registry: true
        register-with-eureka: true
    
  • 修改host文件

    • 用于对服务名进行ip地址映射
    127.0.0.1 replica1
    127.0.0.1 replica2
    
  • 运行集群

    • 注意:不需要再重新添加两个服务,可以通过使用不同的配置文件来启动同一个springboot应用

    • 从原启动配置中复制两个以不同配置文件启动的配置

      image-20201124142528030

      image-20201124142615657

2.3.2 客户端

  • 配置文件

    • 同样添加一个额外的配置文件application-replica.yml,让springboot应用以复制配置文件启动

    • defaultZone同时添加了两个注册中心,中间用逗号隔开

      server:
        port: 8102
      spring:
        application:
          name: eureka-client
      eureka:
        client:
          register-with-eureka: true
          fetch-registry: true
          service-url:
            defaultZone: http://replica1:8002/eureka/,http://replica2:8003/eureka/ #同时注册到两个注册中心
      

2.4 Eureka注册中心添加认证

2.4.1 新建模块,添加依赖

  • eureka-security-server

    • 这里引入的是spring-boot-starter-security,而不是spring-cloud-security
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

2.4.2 配置文件

  • spring.security.user来配置登录的用户名和密码,而不是直接从数据库中读取(缺点就是账号密码都是固定的)
server:
  port: 8004
spring:
  application:
    name: eureka-security-server
  security: #配置SpringSecurity登录用户名和密码
    user:
      name: macro
      password: 123456
eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: false
    register-with-eureka: false

2.4.3 对SpringSecurity进行配置

  • 默认情况下添加SpringSecurity依赖的应用每个请求都需要添加CSRF token才能访问,Eureka客户端注册时并不会添加,所以需要配置/eureka/**路径不需要CSRF token

  • CSRF
    • 跨站请求伪造-- 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法
    • 攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品),由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行
    • 利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}
  • 重新请求Eureka客户端时需要登录

    image-20201124153036253

2.4.4 eureka-client注册到有登录认证的注册中心

  • eureka-client新建一个配置文件application-security.yml并复制一份启动配置以该文件启动

    • defaultZone的格式变更为http://username:{username}:{password}@hostname:{hostname}:{port}/eureka/
    server:
      port: 8103
    spring:
      application:
        name: eureka-client
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://macro:123456@localhost:8004/eureka/
    

2.5 Eureka常用配置

eureka:
  client: #eureka客户端配置
    register-with-eureka: true #是否将自己注册到eureka服务端上去
    fetch-registry: true #是否获取eureka服务端上注册的服务列表
    service-url:
      defaultZone: http://localhost:8001/eureka/ # 指定注册中心地址
    enabled: true # 启用eureka客户端
    registry-fetch-interval-seconds: 30 #定义去eureka服务端获取服务列表的时间间隔
  instance: #eureka客户端实例配置
    lease-renewal-interval-in-seconds: 30 #定义服务多久去注册中心续约
    lease-expiration-duration-in-seconds: 90 #定义服务多久不去续约认为服务失效
    metadata-map:
      zone: jiangsu #所在区域
    hostname: localhost #服务主机名称
    prefer-ip-address: false #是否优先使用ip来作为主机名
  server: #eureka服务端配置
    enable-self-preservation: false #关闭eureka服务端的保护机制

3 Ribbon(负载均衡的服务调用)

3.1 概述

  • Ribbon主要给服务间调用及API网关转发提供负载均衡的功能,基于某种规则(简单轮询、随机连接)进行服务间的连接

3.2 使用RestTemplate实现服务调用

  • RestTemplate是一个HTTP客户端,可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法

3.2.1 Get请求

  • 从请求的地址、请求的返回值类型以及请求参数中获取响应的结果
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables);

<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);

3.2.2 Post请求

  • 从请求的地址、post提交的表单(封装的对象)、返回值类型以及请求参数中获取响应的结果
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);

<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);

3.3 Ribbon测试

3.3.1 被调用模块(user-service)

  • 依赖

    • 注册到Eureka注册中心
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 配置文件

    server:
      port: 8201
    spring:
      application:
        name: user-service
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 定义一些接口用于被调用

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        private UserService userService;
    
        @PostMapping("/create")
        public CommonResult create(@RequestBody User user) {
            userService.create(user);
            return new CommonResult("操作成功", 200);
        }
    
        @GetMapping("/{id}")
        public CommonResult<User> getUser(@PathVariable Long id) {
            User user = userService.getUser(id);
            LOGGER.info("根据id获取用户信息,用户名称为:{}",user.getUsername());
            return new CommonResult<>(user);
        }
    }
    

3.3.2 调用模块(ribbon-service)

  • 依赖

    • 这里加入了spring-cloud-starter-netflix-ribbon用于调用其他服务
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    
  • 配置文件

    • service-url.user-service是user-service服务在Eureka上的调用路径,便于后边通过@value来取出路径
    server:
      port: 8301
    spring:
      application:
        name: ribbon-service
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    service-url:
      user-service: http://user-service
    
  • 给调用模块添加RestTemplate组件通过@LoadBalanced并赋予负载均衡的能力

    @Configuration
    public class RibbonConfig {
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
  • 调用模块只添加controller用于调用user-service模块的service功能(通过restTemplate)

    @RestController
    @RequestMapping("/user")
    public class UserRibbonController {
        @Autowired
        private RestTemplate restTemplate;
        @Value("${service-url.user-service}")
        private String userServiceUrl;
    
        @GetMapping("/{id}")
        public CommonResult getUser(@PathVariable Long id) {
            return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
        }
    
        @GetMapping("/getByUsername")
        public CommonResult getByUsername(@RequestParam String username) {
            return restTemplate.getForObject(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
        }
    }
    

3.4 Ribbon常用配置

3.4.1 全局配置

ribbon:
  ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
  ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
  OkToRetryOnAllOperations: true #对超时请求启用重试机制
  MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
  MaxAutoRetries: 1 # 切换实例后重试最大次数
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

3.4.2 指定服务进行配置

user-service:
  ribbon:
    ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
    ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
    OkToRetryOnAllOperations: true #对超时请求启用重试机制
    MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
    MaxAutoRetries: 1 # 切换实例后重试最大次数
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

3.4.3 Ribbon负载均衡策略

  • com.netflix.loadbalancer.RandomRule:从提供服务的实例中以随机的方式
  • com.netflix.loadbalancer.RoundRobinRule:以线性轮询的方式,就是维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,以此类推,到最后一个以后再从头来过
  • com.netflix.loadbalancer.RetryRule:在RoundRobinRule的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例
  • com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
  • com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例
  • com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
  • com.netflix.loadbalancer.ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例

4 Hystrix(服务容错保护)

4.1 概述

4.1.1 分布式系统面临的问题

  • 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败

  • 服务雪崩
    • 效应
      • 服务雪崩效应是一种因“服务提供者的不可用”(原因)导致“服务调用者不可用”(结果),并将不可用逐渐放大的现象
      • A为服务提供者, B为A的服务调用者, C和D是B的服务调用者. 当A的不可用,引起B的不可用,并将不可用逐渐放大C和D时, 服务雪崩就形成

    img

    • 形成原因

    img

    • 应对策略

    img

4.1.2 Hystrix

  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免出现级联故障,提高分布式系统的弹性
  • "断路器"本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常

4.2 服务熔断、降级

4.2.1 创建模块演示hystrix功能(hystrix-service)

  • 依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 配置文件

    • service-url.user-service同样是为了获取到user-service的服务地址
    server:
      port: 8401
    spring:
      application:
        name: hystrix-service
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    service-url:
      user-service: http://user-service
    
  • 开启断路器功能

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableCircuitBreaker
    public class HystrixServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(HystrixServiceApplication.class, args);
        }
    
    }
    
  • 创建controller用于调用user-service服务

    @RestController
    @RequestMapping("/user")
    public class UserHystrixController {
        @Autowired
        private UserService userService;
    
        @GetMapping("/testFallback/{id}")
        public CommonResult testFallback(@PathVariable Long id) {
            return userService.getUser(id);
        }
    }
    
  • 实际调用user-service服务在service层

    • restTemplate是自定义负载均衡之后注入到IOC容器中的,故直接获取
    • userServiceUrl是预定义在配置文件中user-service的服务地址
    • restTemplate.getForObject调用user-service中的服务路径来获取结果
    @Service
    public class UserService {
    
        private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Value("${service-url.user-service}")
        private String userServiceUrl;
    
        @HystrixCommand(fallbackMethod = "getDefaultUser")
        public CommonResult getUser(Long id) {
            return restTemplate.getForObject(userServiceUrl + "/user/{1}",CommonResult.class,id);
        }
    
        public CommonResult getDefaultUser(@PathVariable Long id) {
            User defaultUser = new User(-1L, "defaultUser", "123456");
            return new CommonResult<>(defaultUser);
        }
    }
    
  • 结果

    • 可以通过hystrix-service模块调用user-service模块

      image-20201124211417960

    • 当停掉user-service服务之后,可以获取到断路器的默认结果

      image-20201124211543687

4.2 服务熔断解读

4.2.1 @HystrixCommand

  • @HystrixCommand注解出现在UserService类中,可以看出getUser方法通过restTemplate远程调用user-service,而@HystrixCommand注解用于处理当服务出现故障时默认的解决方案

  • 常用参数

    • fallbackMethod:指定服务降级处理方法
    • ignoreExceptions:忽略某些异常,不发生服务降级
    • commandKey:命令名称,用于区分不同的命令
    • groupKey:分组名称,Hystrix会根据不同的分组来统计命令的告警及仪表盘信息
    • threadPoolKey:线程池名称,用于划分线程池

4.2.2 Hystrix的请求缓存

4.2.3 Hystrix请求合并

4.3 Hystrix常用配置

4.3.1 全局配置

hystrix:
  command: #用于控制HystrixCommand的行为
    default:
      execution:
        isolation:
          strategy: THREAD #控制HystrixCommand的隔离策略,THREAD->线程池隔离策略(默认),SEMAPHORE->信号量隔离策略
          thread:
            timeoutInMilliseconds: 1000 #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
            interruptOnTimeout: true #配置HystrixCommand执行超时的时候是否要中断
            interruptOnCancel: true #配置HystrixCommand执行被取消的时候是否要中断
          timeout:
            enabled: true #配置HystrixCommand的执行是否启用超时时间
          semaphore:
            maxConcurrentRequests: 10 #当使用信号量隔离策略时,用来控制并发量的大小,超过该并发量的请求会被拒绝
      fallback:
        enabled: true #用于控制是否启用服务降级
      circuitBreaker: #用于控制HystrixCircuitBreaker的行为
        enabled: true #用于控制断路器是否跟踪健康状况以及熔断请求
        requestVolumeThreshold: 20 #超过该请求数的请求会被拒绝
        forceOpen: false #强制打开断路器,拒绝所有请求
        forceClosed: false #强制关闭断路器,接收所有请求
      requestCache:
        enabled: true #用于控制是否开启请求缓存
  collapser: #用于控制HystrixCollapser的执行行为
    default:
      maxRequestsInBatch: 100 #控制一次合并请求合并的最大请求数
      timerDelayinMilliseconds: 10 #控制多少毫秒内的请求会被合并成一个
      requestCache:
        enabled: true #控制合并请求是否开启缓存
  threadpool: #用于控制HystrixCommand执行所在线程池的行为
    default:
      coreSize: 10 #线程池的核心线程数
      maximumSize: 10 #线程池的最大线程数,超过该线程数的请求会被拒绝
      maxQueueSize: -1 #用于设置线程池的最大队列大小,-1采用SynchronousQueue,其他正数采用LinkedBlockingQueue
      queueSizeRejectionThreshold: 5 #用于设置线程池队列的拒绝阀值,由于LinkedBlockingQueue不能动态改版大小,使用时需要用该参数来控制线程数

4.3.2 实例配置

实例配置只需要将全局配置中的default换成与之对应的key即可

hystrix:
  command:
    HystrixComandKey: #将default换成HystrixComrnandKey
      execution:
        isolation:
          strategy: THREAD
  collapser:
    HystrixCollapserKey: #将default换成HystrixCollapserKey
      maxRequestsInBatch: 100
  threadpool:
    HystrixThreadPoolKey: #将default换成HystrixThreadPoolKey
      coreSize: 10
  • 配置文件中相关key的说明
    • HystrixComandKey对应@HystrixCommand中的commandKey属性
    • HystrixCollapserKey对应@HystrixCollapser注解中的collapserKey属性
    • HystrixThreadPoolKey对应@HystrixCommand中的threadPoolKey属性

5 Hystrix Dashboard(断路器执行监控)

5.1 概述

  • Hystrix Dashboard 是Spring Cloud中查看Hystrix实例执行情况的一种仪表盘组件,支持查看单个实例和查看集群实例
  • Hystrix提供了Hystrix Dashboard来实时监控HystrixCommand方法的执行情况,Hystrix Dashboard可以有效地反映出每个Hystrix实例的运行情况,帮助快速发现系统中的问题

5.2 断路器执行监控

5.2.1 创建模块执行监控(hystrix-dashboard)

  • 依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 配置文件

    server:
      port: 8501
    spring:
      application:
        name: hystrix-dashboard
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 开启监控功能

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableHystrixDashboard
    public class HystrixDashboardApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardApplication.class, args);
        }
    
    }
    
  • 查看监控信息

    img

    img

    • 注意:被监控的服务必须开启Actuator的hystrix.stream端点,也就是8401端口配置文件添加:

      management:
        endpoints:
          web:
            exposure:
              include: 'hystrix.stream' #暴露hystrix监控端点
      
  • 监控结果

    image-20201124215140259

5.3 Hystrix集群实例监控

  • 使用Turbine来聚合hystrix-service服务的监控信息,然后hystrix-dashboard服务就可以从Turbine获取聚合好的监控信息

5.3.1 创建模块聚合hystrix-service的监控信息(turbine-service)

6 OpenFeign(基于Ribbon和Hystrix的声明式服务调用)

6.1 概述

  • Feign是一个声明式WebService客户端
  • 在之前使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法,实际开发中,往往一个接口被多出调用,Feign在此基础上做了进一步封装,帮助定义和实现依赖服务接口的定义
  • Feign集成了Ribbon通过轮询实现客户端的负载均衡

6.2 Feign测试

6.2.1 创建模块演示Feign服务调用功能(feign-service)

  • 依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 配置文件

    server:
      port: 8701
    spring:
      application:
        name: feign-service
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 开启Feign客户端功能

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class FeignServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(FeignServiceApplication.class, args);
        }
    
    }
    
  • 添加UserService接口实现对user-service服务Controller接口绑定

    • 通过@FeignClient注解实现了一个Feign客户端,其中的value为user-service表示是对user-service服务的接口调用客户端

    • UserService接口的方法本质上就是对user-service服务中的controller对应的方法删掉方法内容,只保留接口

      @FeignClient(value = "user-service")
      public interface UserService {
          @PostMapping("/user/create")
          CommonResult create(@RequestBody User user);
      
          @GetMapping("/user/{id}")
          CommonResult<User> getUser(@PathVariable Long id);
      
          @GetMapping("/user/getByUsername")
          CommonResult<User> getByUsername(@RequestParam String username);
      
          @PostMapping("/user/update")
          CommonResult update(@RequestBody User user);
      
          @PostMapping("/user/delete/{id}")
          CommonResult delete(@PathVariable Long id);
      }
      
  • 添加controller对UserService中的方法进行调用(省略)

  • user-service重新改造一个配置文件,在启动配置上指定Active Profile启动另一个端口的user-service检测Feign的负载均衡能力

    image-20201124223930404

  • 测试结果(运行在8201和8202的user-service服务交替调用)

    image-20201124224207246

6.2.2 测试Feign中的服务降级

  • 添加UserService接口的实现类作为服务降级类

    public class UserServiceImpl implements UserService {
        @Override
        public CommonResult create(User user) {
            User defaultUser = new User(-1L, "defaultUser", "123456");
            return new CommonResult<>(defaultUser);
        }
    
        @Override
        public CommonResult<User> getUser(Long id) {
            User defaultUser = new User(-1L, "defaultUser", "123456");
            return new CommonResult<>(defaultUser);
        }
    
        @Override
        public CommonResult<User> getByUsername(String username) {
            User defaultUser = new User(-1L, "defaultUser", "123456");
            return new CommonResult<>(defaultUser);
        }
    
        @Override
        public CommonResult update(User user) {
            return new CommonResult("调用失败,服务被降级",500);
        }
    
        @Override
        public CommonResult delete(Long id) {
            return new CommonResult("调用失败,服务被降级",500);
        }
    }
    
  • 指定服务降级类为UserServiceImpl

    image-20201124224719012

  • 在配置文件中开启Hystrix功能

    image-20201124224856305

6.2.3 Feign日志打印功能

  • Feign提供了日志打印功能,可以通过配置来调整日志级别,从而了解Feign中Http请求的细节

  • 日志级别

    • NONE:默认的,不显示任何日志
    • BASIC:仅记录请求方法、URL、响应状态码及执行时间
    • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
    • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
  • 配置Logger.Level(Logger来源于feign.Logger)

    @Configuration
    public class FeignConfig {
    
        @Bean
        public Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    }
    
  • 在配置文件中开启日志的Feign客户端

    • 配置UserService的日志级别为debug

      logging:
        level:
          org.jiang.service.UserService: debug
      
  • 查看日志

    image-20201124225909761

6.3 Feign常用配置

6.3.1 Feign自己的配置

feign:
  hystrix:
    enabled: true #在Feign中开启Hystrix
  compression:
    request:
      enabled: false #是否对请求进行GZIP压缩
      mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型
      min-request-size: 2048 #超过该大小的请求会被压缩
    response:
      enabled: false #是否对响应进行GZIP压缩
logging:
  level: #修改日志级别
    com.macro.cloud.service.UserService: debug

6.3.2 Feign中Ribbon配置

  • 在Feign中配置Ribbon可以直接使用Ribbon的配置

6.3.3 Feign中的Hystrix配置

  • 在Feign中配置Hystrix可以直接使用Hystrix的配置

7 Config(外部集中化配置管理)

7.1 概述

7.1.1 分布式系统面临的配置问题

  • 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,系统中会出现大量的配置信息用于运行,故将公共部分抽取出来成为一套集中式、动态的配置管理设施必不可少

7.1.2 Spring Cloud Config

  • Spring Cloud Config为微服务提供集中化的外部配置支持,配置服务器为不同微服务应用提供了一个中心化的外部配置

  • Spring Cloud Config 分为服务端和客户端两个部分

    • 服务端被称为分布式配置中心,它是个独立的应用,可以从配置仓库获取配置信息并提供给客户端使用
    • 客户端可以通过配置中心来获取配置信息,在启动时加载配置
    • Spring Cloud Config 的配置中心默认采用Git来存储配置信息,所以天然就支持配置信息的版本管理,并且可以使用Git客户端来方便地管理和访问配置信息

    img

7.2 搭建配置中心实现中心配置

7.2.1 远程仓库准备配置信息

  • Spring Cloud Config 需要一个存储配置信息的Git仓库,故提前在Git仓库中添加好配置文件

  • git仓库地址:gitee.com/macrozheng/…

  • 配置仓库目录结构

    • 提供了不同的分支,不同的分支配置了不同的环境,生产、开发、测试

    image-20201125005040369

    image-20201125005114547

7.2.2 创建配置中心模块演示Config配置中心功能(config-server)

  • 依赖

    • 这里添加了spring-cloud-config-server依赖用来作为配置中心
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 配置文件

    • 增加了spring.cloud.config.server用于配置git仓库的信息
    server:
      port: 8901
    spring:
      application:
        name: config-server
      cloud:
        config:
          server:
            git: #配置存储配置信息的Git仓库
              uri: https://gitee.com/macrozheng/springcloud-config.git
              username: macro
              password: 123456
              clone-on-start: true #开启启动时直接从git获取配置
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 开启配置中心功能

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableConfigServer
    public class ConfigServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigServerApplication.class, args);
        }
    
    }
    
  • 通过config-server获取配置信息

    • 获取配置文件信息的访问格式

      # 获取配置信息
      /{label}/{application}-{profile}
      # 获取配置文件信息
      /{label}/{application}-{profile}.yml
      
    • 占位符相关解释

      • application:代表应用名称,默认为配置文件中的spring.application.name,如果配置了spring.cloud.config.name,则为该名称
      • label:代表分支名称,对应配置文件中的spring.cloud.config.label
      • profile:代表环境名称,对应配置文件中的spring.cloud.config.profile
    • 获取配置信息

      • 读取配置信息地址:http://localhost:8901/master/config-dev

      • 之所以是这个请求地址,因为git仓库中的配置文件默认格式就是{application}-{profile}.yml,也就是config就代表application

        image-20201125012247025

  • 通过config-server获取配置文件信息

7.3 搭建客户端用于获取配置中心配置信息

7.3.1 创建模块用于访问(config-client)

  • 依赖

    • 相对于配置中心增加了spring-boot-starter-web模块,说明要进行网络请求
    • 这里使用的是spring-cloud-starter-config,而配置中心使用的是spring-cloud-config-server
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 配置文件bootstrap.yml

    • bootstrap.yml

      • Spring Cloud会创建一个Bootstrap Context作为Spring应用的Application Context的父上下文
      • Bootstrap Context负责从外部源加载配置属性并解析配置,和Application Context共享一个从外部获取的环境
      • Bootstrap属性具有高优先级,默认情况下不会被本地配置覆盖,Bootstrap Context和Application Context有着不同的约定,故新增bootstrap.yml用于和Application Context配置分离
      • bootstrap.yml优先于application.yml加载
    • 配置文件内容

      • 这里没有spring.cloud.config.server,而是spring.cloud.config,因为这是客户端
      • 分别获取了配置中心的地址、配置文件名称和分支名称,也就对应了application、label和profile
      server:
        port: 9001
      spring:
        application:
          name: config-client
        cloud:
          config: #Config客户端配置
            profile: dev #启用配置后缀名称
            label: dev #分支名称
            uri: http://localhost:8901 #配置中心地址
            name: config #配置文件名称
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:8001/eureka/
      
      
  • 创建controller测试获取配置文件信息

    • 为什么是@Value("${config.info}")

      image-20201125092321590

    • 从配置中心读取到的配置文件信息就是这样,所以可以使用config.info读取

    @RestController
    public class ConfigClientController {
    
        @Value("${config.info}")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() {
            return configInfo;
        }
    }
    
  • 测试

  • 获取子目录下的配置

    • 不仅可以把每个项目的配置放在不同的Git仓库存储,也可以在一个Git仓库中存储多个项目的配置,此时就会用到在子目录中搜索配置信息的配置

    • config文件夹就代表dev分支下config应用的配置文件

      image-20201125094047248

    • 获取方式

      • 在config-server的配置文件中添加配置,用于搜索子目录的位置,使用application占位符,表示对于不同的应用,我们从对应应用名称的子目录中搜索配置,比如config子目录中的配置对应config应用

        image-20201125094357836

    • 获取结果

      • 可以发现获取的是config子目录下的配置文件信息

        image-20201125094433889

7.3.2 配置刷新

  • 当Git仓库中的配置信息更改后,我们可以通过SpringBoot Actuator的refresh端点来刷新客户端配置信息
对config-client进行改进
  • 添加依赖

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 修改配置文件添加actuator配置开启refresh端点

    management:
      endpoints:
        web:
          exposure:
            include: 'refresh'
    
  • 在controller类上添加@RefreshScope注解用于刷新配置

    @RestController
    @RefreshScope
    public class ConfigClientController {
    
        @Value("${config.info}")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() {
            return configInfo;
        }
    }
    
  • 发送一次post请求,调用refresh端点进行配置刷新(监控版本变化)

7.4 配置中心添加安全验证

7.4.1 添加模块整合SpringSecurity(config-security-server)

  • 依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  • 配置文件

    • 增加了对security账户的配置
    server:
      port: 8905
    spring:
      application:
        name: config-security-server
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/macrozheng/springcloud-config.git
              username: macro
              password: 123456
              clone-on-start: true #开启启动时直接从git获取配置
      security: #配置用户名和密码
        user:
          name: macro
          password: 123456
    

7.4.2 修改config-client用于匹配添加了安全认证的配置中心

  • 添加一个bootstrap-security.yml配置文件,用于匹配配置了用户名和密码的配置中心,使用bootstrap-security.yml启动config-client服务

    server:
      port: 9002
    spring:
      application:
        name: config-client
      cloud:
        config:
          profile: dev #启用配置后缀名称
          label: dev #分支名称
          uri: http://localhost:8905 #配置中心地址
          name: config #配置文件名称
          username: macro
          password: 123456
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
    management:
      endpoints:
        web:
          exposure:
            include: 'refresh'
    

7.5 config-sever集群

  • 在微服务架构中,所有服务都从配置中心获取配置,配置中心一旦宕机,会发生很严重的问题;故需要搭建配置中心集群

7.5.1搭建配置中心集群

  • 启动两个config-server分别运行在8902和8903端口上

  • 添加config-client的配置文件bootstrap-cluster.yml,主要是添加了从注册中心获取配置中心地址的配置并去除了配置中心uri的配置

    • bootstrap-cluster.yml

      spring:
        cloud:
          config:
            profile: dev #启用环境名称
            label: dev #分支名称
            name: config #配置文件名称
            discovery:
              enabled: true
              service-id: config-server
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:8001/eureka/
      
  • 以bootstrap-cluster.yml启动config-client服务,注册中心显示信息

    img

8 Bus(消息总线)

8.1 概述

  • Spring Cloud Bus 使用轻量级的消息代理来连接微服务架构中的各个服务,可以将其用于广播状态更改(例如配置中心配置更改)或其他管理指令
  • 使用消息代理来构建一个主题,然后把微服务架构中的所有服务都连接到这个主题上去,当我们向该主题发送消息时,所有订阅该主题的服务都会收到消息并进行消费
  • Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新

image-20201125114431226

8.2 安装RabbitMQ

8.3 增加动态刷新配置功能

  • 使用 Spring Cloud Bus 动态刷新配置需要配合 Spring Cloud Config 一起使用,通过config-server、config-client模块演示该功能

8.3.1 config-server模块修改

  • 添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 配置文件修改

    • 添加配置文件application-amqp.yml,主要是添加了RabbitMQ的配置及暴露了刷新配置的Actuator端点

      server:
        port: 8904
      spring:
        application:
          name: config-server
        cloud:
          config:
            server:
              git:
                uri: https://gitee.com/macrozheng/springcloud-config.git
                username: macro
                password: 123456
                clone-on-start: true # 开启启动时直接从git获取配置
        rabbitmq: #rabbitmq相关配置
          host: localhost
          port: 5672
          username: guest
          password: guest
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:8001/eureka/
      management:
        endpoints: #暴露bus刷新配置的端点
          web:
            exposure:
              include: 'bus-refresh'
      

8.3.2 config-client模块修改

  • 添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
  • 修改配置文件

    • 添加配置文件bootstrap-amqp1.yml及bootstrap-amqp2.yml用于启动两个不同的config-client,两个配置文件只有端口号不同

      server:
        port: 9004
      spring:
        application:
          name: config-client
        cloud:
          config:
            profile: dev #启用环境名称
            label: dev #分支名称
            name: config #配置文件名称
            discovery:
              enabled: true
              service-id: config-server
        rabbitmq: #rabbitmq相关配置
          host: localhost
          port: 5672
          username: guest
          password: guest
      eureka:
        client:
          service-url:
            defaultZone: http://localhost:8001/eureka/
      management:
        endpoints:
          web:
            exposure:
              include: 'refresh'
      

8.3.3 动态刷新配置演示

  • 先启动相关服务,启动eureka-server,以application-amqp.yml为配置启动config-server,以bootstrap-amqp1.yml为配置启动config-client,以bootstrap-amqp2.yml为配置再启动一个config-client,启动后注册中心显示

    img

  • 启动所有服务后,登录RabbitMQ的控制台可以发现Spring Cloud Bus 创建了一个叫springCloudBus的交换机及三个以 springCloudBus.anonymous开头的队列

    img

img

8.4 配合WebHooks实现自动刷新服务配置

  • WebHooks相当于是一个钩子函数,通过配置当向Git仓库push代码时触发这个钩子函数,当向配置仓库push代码时就会自动刷新服务配置

9 Sleuth(分布式请求链路跟踪)

9.1 概述

9.1.1 遇到的问题

  • 在微服务框架中,一个由客户端发起的请求在后端系统中会经过很多个不同的服务节点调用来协同产生最后的请求结果,每一个前端请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时和错误都会引起整个请求最后的失败

9.1.2 解决方案

  • Spring Cloud Sleuth 是分布式系统中跟踪服务间调用的工具,它可以直观地展示出一次请求的调用过程

  • 在分布式系统中提供追踪解决方案并且兼容支持了zipkin

  • SpringCloud从F版起已不需要自己构建Zipkin server了,只需要调用jar包即可

    image-20201125210902611

9.2 服务添加请求链路跟踪功能

  • 通过user-service和ribbon-service之间的服务调用演示该功能,将调用ribbon-service的接口时,ribbon-service会通过RestTemplate来调用user-service提供的接口

9.2.1 下载搭建zipkin-server

  • zipkin-server概述

    • Zipkin 是一个开放源代码分布式的跟踪系统,每个服务向zipkin报告计时数据,zipkin会根据调用关系通过Zipkin UI生成依赖关系图

    • Zipkin提供了可插拔数据存储方式:In-Memory、MySql、Cassandra以及Elasticsearch,为了方便在开发环境可以采用了In-Memory方式进行存储,生产数据量大的情况则推荐使用Elasticsearch

    • 表示一条请求链路,一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各Span通过parent id关联起来

      • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
      • span:表示调用链路来源,通俗的理解span就是一次请求信息

      img

  • 构建zipkin服务端

    • zipkin服务端jar包下载:dl.bintray.com/openzipkin/…

    • 启动服务端,默认INFO级别可以不设置logging

      • 命令:java -jar zipkin-server-2.10.4-exec.jar --logging.level.zipkin2=INFO
    • 启动成功

      image-20201125213205835

    • 访问服务端

9.2.1 user-service和ribbon-service添加请求链路跟踪功能的支持

  • user-service和ribbon-service添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    
  • user-service和ribbon-service修改配置文件

    • 添加了zipkin的请求地址以及sleuth抽样收集概率
    server:
      port: 8201
    spring:
      application:
        name: user-service
    
      zipkin:
        base-url: http://localhost:9411
      sleuth:
        sampler:
          probability: 0.1 #设置Sleuth的抽样收集概率
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8001/eureka/
    
  • 多次调用(Sleuth为抽样收集)ribbon-service的接口http://localhost:8301/user/1 ,调用完后查看Zipkin请求链路跟踪信息

    image-20201125214944314

9.3 整合Elasticsearch存储跟踪信息

9.3.1 Elasticsearch安装及配置

  • 下载zip安装包并解压

  • 运行bin目录下的elasticsearch.bat启动Elasticsearch

  • 启动界面

    image-20201125215415141

  • 重启zipkin并指定启动参数

    • # STORAGE_TYPE:表示存储类型 ES_HOSTS:表示ES的访问地址
      java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=elasticsearch --ES_HOSTS=localhost:9200 
      
  • 重启user-service和ribbon-service服务(只有重启存储才能生效)

  • zipkin启动参数参考:github.com/openzipkin/…

9.3.2 配合Elasticsearch的可视化工具Kibana


个人公众号目前正初步建设中,如果喜欢可以关注我的公众号,谢谢!

二维码