走进SpringCloud微服务

微服务概述

    • 一、注册中心:Eureka ⭐⭐⭐
      • 1.1 原理
      • 1.2 代码
    • 二、负载均衡:Ribbon ⭐
    • 三、远程调用:Feigh ⭐⭐⭐
      • 3.1 原理
      • 3.2 代码
    • 四、熔断限流:Hystrix ⭐⭐⭐
      • 4.1线程池策略
      • 4.2 信号量隔离策略
      • 4.3 方法降级
      • 4.4 断路器、熔断器
    • 五、网关:Getway ⭐⭐⭐
      • 5.1 动态路由
      • 5.2 限流
    • 六、链路追踪:Flueth、Zipkin(docker配置组件) ⭐⭐

1,什么是微服务?

简而言之,微服务架构风格是一种将单个应用程序开发为一组小服务的方法,每个小服务都在自己的进程中运行,并与轻量级机制(通常是 HTTP 资源 API)进行通信。这些服务是围绕业务能力构建的,并且可以通过完全自动化的部署机制独立部署。这些服务的集中管理极少,可以用不同的编程语言编写并使用不同的数据存储技术。

2,微服务架构思想,没有一个落地的技术栈去做支持,此时,SpringCloud就是一个微服务架构落地技术栈,注意:Springcloud是解决接服务问题的系列技术栈。
.

3,SpringCloud家族常用组件:[已忽略]不计

注册中心:Eureka
配置中心: Config (忽略)
远程调用:Feigh
负载均衡:Ribbon
熔断降级:Hystrix
网关:Zuul(忽略)、Getway
消息总线、链路追踪:Slueth、Zipkin

接下来,将要介绍的是Eureka、Feigh、Ribbon、Hystrix、Getway、Slueth
注意:Config作为统一管理项目的配置文件,简单了解一下即可,本文忽略、实际企业开发中采用的springcloud alibaba的nacos统一注册中心和配置中心。
官网中Zuul网关由于停止维护,采用springcloud自家开发的新的网关技术Getway,实际企业应用中,getway基本替代zuul、所以,这里zuul就不做了解,直接学最新技术(注意,springcloud-getway 只能用在springcloud家族,其他微服务下目前集成不进去,而Zuul可可以用到其他微服务下,getway的使用范围限制大于zuul)

.

赋gitee源码练习:https://gitee.com/xzq25_com/springcloud-tets

一、注册中心:Eureka ⭐⭐⭐

1.1 原理

简单概括就是服务注册与发现提供了一个服务注册中心、服务发现的客户端,还有一个方便查看所有注册的服务的界面。所有的服务使用Eureka的服务发现客户端来将自己注册到Eureka的服务器上。

玩的就是心跳!!!!如图所示:
走进SpringCloud微服务_第1张图片
Eureka的架构图:
走进SpringCloud微服务_第2张图片
走进SpringCloud微服务_第3张图片

Eureka由两个组件组成: Eureka服务器和Eureka客户端。

Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

EurekaClient通过注册中心进行访问 它是一个Java客户端,用于简化Eureka
Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka
Server发送心跳(默认周期为30秒)。如果Eureka
Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。

在微服务项目中,不使用注册中心的现存问题?

举个例子,服务A调用服务B,此时不使用注册中心,则存在需要在服务A中写死服务B的地址信息,如果服务B地址信息发生的改变,则所有调用服务B的服务都需要修改调用B的代码,这种无疑是不可行的。
注册中心Eureka争对上述问题,提供了解决方案::让服务的提供者将信息统一注册到Eureka上(服务名-ip-port),调用者直接从eureka上拉取注册服务列表,直接进行访问,这样效率大大提升

关于Eureka:

1.服务注册端核心启动类上导入@EurekaClient 注解 ok
2.Eureka 作为注册,本身不安全,需要在导入 security 依赖并且配置 yml 文件, 登录时候带上账号、密码
3.EurekaClient 默认 30s 向 service 发送心跳请求,说明自身的存活状态,同时同步注册表中信息。
4.EurekaService 发现 90 秒 client 没有发送心跳请求,则认为 client 宕机
5.EurekaClient 拉取 service 信息到本地缓存,提高访问其他服务的效率
6.EurekaService 在 15min 内,如果超过 85%心跳不正常,依旧对外服务,但是会不删除、添加服务,等到心跳正常,在去其他 service 同步。
7.Eureka 基于 AP 原则,而 Nacos 基于 AP 和 CP 之间横跳

8.Eureka的集群模式中注册中心信息都是互相拉取,是平等的关系,不存在主从关系

1.2 代码

Eureka搭建server服务器,其他服务需要用到Eureka,则导入EurekaClient依赖+注解就可

搭建Eureka-server服务
1,导入依赖

       <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
       dependency>

2,启动类上添加注解

@EnableEurekaServer

3, application.yml配置文件配置Eureka

eureka:
  client:
    service-url:
      defaultZone: http://eureka8762.com:8762/eureka/    # 将自己配置到其它所有eureka服务上
    register-with-eureka: false  #不向注册中心注册自己
    fetch-registry: false  #自己是注册中心,不用获取注册表
  server:
    enable-self-preservation: true  # 开启自我保护机制
    eviction-interval-timer-in-ms: 60000
  instance:
    hostname: eureka8761.com

server:
  port: 8761    # 指定端口号

spring:
  application:
    name: eureka-server    # 指定EurekaServer的服务名
 

同理创建另外Eureka服务,相同配置,端口号不同即可,此在,这里创建两个客户端服务serve1、serve2注册到注册中心
注意:由于配置了集群,这里需要ip和域名映射,如果使用域名,两台eurekaserve服务同ip部署集群下,配置文件中使用ip:port配置可以会失效,这里需要到本地C盘下:C:\Windows\System32\drivers\etc 的host文件中加上如下所示:

127.0.0.1 eureka8761.com
127.0.0.1 eureka8762.com

走进SpringCloud微服务_第4张图片

配置客户端Serve1服务
1,导入依赖

   <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>

2,添加注解

@EnableEurekaClient

3, application.yml配置文件配置Eureka

eureka:
  client:
    service-url:
      defaultZone:  http://eureka8761.com:8761/eureka/ , http://eureka8762.com:8762/eureka/       # 注册的Eureka服务地址
    registry-fetch-interval-seconds: 30
  instance:
    appname: serve1

同理创建另客户端serve2服务,相同配置,端口不同即可

微服务项目搭建完场之后,启动Eureka集群,启动两个客户端Serve1,Serve2
走进SpringCloud微服务_第5张图片
去往第一个注册中心:8761端口
走进SpringCloud微服务_第6张图片

去往第二个注册中心:8761端口
走进SpringCloud微服务_第7张图片

Eureka的自我保护机制:EurekaService 在 15min 内,如果超过 85%心跳不正常,依旧对外服务,但是会不删除、添加服务,等到心跳正常,在去其他 service 同步。
走进SpringCloud微服务_第8张图片
至此,注册中心就算搭建起来了!!!
.

二、负载均衡:Ribbon ⭐

Ribbon简单来说:就是当前微服务模块集群的时候,需要负载均衡,就用Ribbon,使用也简单,导入依赖,远程调用方法上加注解@LoadBalanced。
Ribbon也可以不导入依赖, spring-cloud-starter-netflix-eureka-client已经引入了Ribbon,注册中心轮询服务就是用的Ribbon的负载均衡的轮询策略,可以不手动导入依赖
在后续要学习的远程调用Feigh中也自动集成了Ribbon,所以这个可以不用,后续的远程调用在配置文件配置一下即可,注解也自动加上了

三、远程调用:Feigh ⭐⭐⭐

3.1 原理

在开发中,我们常用httpClient去远程调用其他系统的接口,需要我们指定调用的url,Feign 是一个声明式的 Web Service 客户端,它实现了一套远程调用的方法,调用的方式也更为优雅。

使用前提:被调用的模块注册到eureka中,能正常运行!!!

关于Feign

1.Feigin 默认支持 ribbon 做负载均衡
2.在调用方服务启动类上加入注解@EnableFeignClients
3.声明接口,接口映射被调用方 Controller 层的方法,这里时本质采用 jdk 动态代理生成代理对象,调用远程方法
4.在声明的接口上加入注解@FeignClient(value=指定服务名)
5.feign 的 fallback 实际是整合 hystrix,需要在 yml 文件中配置 hystrix
6.单独使用 feign 的 fallback 无法获取异常,需要实现 fallbackfactory接口
7.默认情况下,feign 的超时时间是 1s,hystrix 的线程池隔离的超时时间也是1S

关于feigh负载均衡:配置一下即可

feign:
  hystrix:
    enabled: true

3.2 代码

在调用者服务中使用,创建一个接口,映射服务提供者的controller中的方法
例如:这里Serve2服务,调用Serve1服务,则:接口中的方法则是serve1中controller层的方法,去掉了方法体
走进SpringCloud微服务_第9张图片
1,服务调用方导入依赖:

 <dependency>
       <groupId>org.springframework.cloudgroupId>
       <artifactId>spring-cloud-starter-openfeignartifactId>
 dependency>

2,创建接口映射,添加注解:@FeignClient

3,Feigh支持降级方法

Feigh还支持方法降级:当服务提供方崩了、资源紧张或者响应速度特备慢的时候,可以走服务调用方设置的降级方法,快速失败,返回托底数据
举例:在淘宝中双11中访问某一件爆款商品,加入购入车的时候,出现了访问时间过久,重新访问一次的页面,此时就是设置了降级方法,因为服务访问人数过多,直接返回另外一个结果。
Feigh的降级方法本质上是整合了后续要学习的Hystrix,这里了解一下即可,后续学习中在说。

注意,fallback不能拿到降级的异常,需要通过FallbackFactory来实现
走进SpringCloud微服务_第10张图片
走进SpringCloud微服务_第11张图片

四、熔断限流:Hystrix ⭐⭐⭐

Hystrix总结是实现三个方向:服务降级、熔断、限流

当微服务中某一个服务出现资源紧张、服务崩了,此时由于微服务中是互相调用关系居多,此时可能出现下面一种情况,整个服务都资源紧张了,这中情况是不可取的。
走进SpringCloud微服务_第12张图片
Hystrix提供了三种策略还解决上述的服务雪崩问题

  • Hystrix提供了线程隔离策略
  • Hystrix提供了方法的降级,可以实现快速失败,返回托底数据
  • Hystrix提供了断路器、熔断器。(摸电门)
  • Hystrix还提供了一个近乎实时的图形化界面,可以查看近期服务的执行情况。

接下来就是分别学习了解一下这三种策略

4.1线程池策略

  • 客户端发送请求到服务时,由容器的线程池中的线程处理请求,执行业务逻辑代码时,采用Hystrix提供的线程池中的线程处理
  • 涉及到了线程的切换时:
    – 由于切换了线程,支持超时时间的设置
    – 由于切换了线程,导致这个请求的处理速度相对更慢
    – 一般比较复杂的业务推荐使用线程池隔离策略

总结:请求交给spring容器线程,处理业务逻辑交给Hystrix线程,这样可以设置超时时间,快速失败!
.

4.2 信号量隔离策略

  • 信号量就是一个计数器,客户端发送请求和业务的处理都使用容器的线程池中的线程处理
  • 通过这个计数器,确认线程池中的情况,给予快速的反馈
  • 没有涉及到线程的切换:
    • 不支持超时时间,无法实现快速失败,返回托底数据
    • 更适合应用到一些处理速度较快的业务上

总结:请求和业务线程都交给spring容器线程处理,无法设置超时时间,适合用在处理速度快的业务上。

示例:
1,导入依赖:

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>

2,启动类上加注解:

@EnableCircuitBreaker

3,方法上@HystrixCommand()进行系列配置!

具体配置方案,查看Hystrix在github上的Wiki百科

@HystrixCommand(commandProperties = {
    // 设置隔离策略,THREAD代表线程池隔离,SEMAPHORE代表信号量隔离
    @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD、SEMAPHORE"),
    // 设置超时时间,默认为1s
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
    // 超时时间开关,默认开启
    @HystrixProperty(name = "execution.timeout.enabled", value = "false"),
    // 超时后,是否中断线程,默认为true
    @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
    // 取消任务后,是否中断线程,默认为false
    @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "false"),
    // 信号量最大请求并发数,默认值为10,10理论上可以处理25000的每秒请求数
    @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
})

.

4.3 方法降级

针对目标方法设置降级方法,当目标方法出现问题时,会直接快速失败,执行降级方法,返回托底数据 降级方法的描述要和原方法一模一样

示例:

@HystrixCommand(fallbackMethod = "coreFallback"})
public String core() throws InterruptedException {
    System.out.println("当前线程名为:" + Thread.currentThread().getName());
    return "ok!";
}

public String coreFallback(){
    return "服务器挤爆了,请稍后再试!";
}

4.4 断路器、熔断器

断路器是马丁福勒提出的一个思想Hystrix断路器的执行原理、流程 如下图所示:
走进SpringCloud微服务_第13张图片断路器默认开启,为了更好的查看效果,配置图形化界面

示例:图形化界面
1,导入依赖

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

2,启动类上添加注解: @EnableHystrixDashboard

3,配置ServeletConfig:这里:这里/hystrix.stream路径时固定了,只能时这个路径,不能改变,否则看不到可视化图形界面

/**
 * @Author xiaozq
 * @Date 2022/11/30 10:04
 * 

@Description:图形化界面

*/
@Configuration public class HystrixConfig{ @Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; } }

访问:http://host:port/hystrix
走进SpringCloud微服务_第14张图片
点击下面的monitor按钮,则进入下面界面:标识进入监控范围了
走进SpringCloud微服务_第15张图片
下面来试着对配置了hystrix的方法进行测试,注意,这里没有配置隔离策略,则默认的时线程池隔离策略
走进SpringCloud微服务_第16张图片
注意上面监控,上图配置中可以看出,10s以内(这是默认的,无法修改),来5个请求,请求失败率达到20%(也就是连续5个请求是失败1个,断路器就会开启),则开启断路器10s,10s后进入半开启状态,此时来一个请求,成功,则断路器关闭,业务正常访问,否则,继续开启断路器10s
.
接下来进行测试:
访问http://localhost:8081/prac2/2,掏出postman发请求访问来连续5次,访问正常,则monitor监控上显示如图所示:
走进SpringCloud微服务_第17张图片

走进SpringCloud微服务_第18张图片
.

接下来http://localhost:8081/prac2/1, 访问请求异常的情况,则monitor监控上显示如图所示:
走进SpringCloud微服务_第19张图片

.

五、网关:Getway ⭐⭐⭐

GetWay作为统一配置的网关,可以实现负载均衡,动态路由以及限流
GetWay作为SpringCloud家族中新一代网关,在性能上比Zuul高上1.5倍
Getway内部实现了负载均衡,不需要手动配置,这点了解一下即可,此在,GetWay天然适配Eureka,能够在夫妻集群中负载均衡,默认采用轮询的方式进行。
GetWay的动态路由也是很大一特点,在下面的学习中会清楚的应用起来 GetWay限流内部默认采用的时redis lua脚本+令牌桶算法进行限流,限流方式很多,例如:ip限流,路径限流等,此在限流默认系统时无法捕捉到异常原因,只会返回code码429

5.1 动态路由

1,导入依赖:

    <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
 <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
            <version>2.2.5.RELEASEversion>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redis-reactiveartifactId>
            <version>2.0.5.RELEASEversion>
        dependency>

2,添加注解,将getway服务注册到注册中心上,getway服务端口为8083
走进SpringCloud微服务_第20张图片
3,配置application,yml文件

静态路由和动态路由的区别在于配置中关于uri的配置,lb://服务名 (标识动态路由,默认采用的负载均衡),如果是写死的ip:port则是静态路路由

需要了解配置文件中三个关键属性

id: 这个代表路由一个服务的标识,一般与服务名一致
uri: 路由地址
predicates: 断言,需要匹配到这个路径,才会路由到uri
filters:限流策略(默认采用令牌桶的限流策略,所以基本配置都是配置令牌桶相关的参数)
eureka:
  client:
    service-url:
      defaultZone: http://eureka8761.com:8761/eureka/ , http://eureka8762.com:8762/eureka/ # 注册的Eureka服务地址
    registry-fetch-interval-seconds: 30
  instance:
    appname: getway-serve

spring:
  application:
    name: getway-serve

  redis:
    host: 120.76.159.196
    port: 6379
    database: 2

  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: serve1
          uri: lb://SERVE1
          predicates:
            - Path=/ser1/**
          filters:
            - name: SelfRateLimiter # 自定义限流过滤器
              args:
              # 每秒允许处理的请求数量  #  令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 1
              # 每秒最大处理的请求数量# 令牌桶总容量
                redis-rate-limiter.burstCapacity: 1
              # 每次请求获取的令牌数
                redis-rate-limiter.requestedTokens: 1
              #限流策略,对应策略的Bean
                key-resolver: "#{@pathKeyResolver}"

        - id: serve2
          uri: lb://SERVE2
          predicates:
            - Path=/ser2/**
          filters:
#            - name: RequestRateLimiter  # 官方自带
             - name: SelfRateLimiter
               args:
                # 每秒允许处理的请求数量  #  令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 1
                # 每秒最大处理的请求数量# 令牌桶总容量
                redis-rate-limiter.burstCapacity: 1
                # 每次请求获取的令牌数
                redis-rate-limiter.requestedTokens: 1
                #限流策略,对应策略的Bean
                key-resolver: "#{@ipKeyResolver}"

这样通过路由8083端口去访问服务,则效果如图所示:
网关访问服务2,服务2调用服务1 的test1方法,成功!!!
走进SpringCloud微服务_第21张图片

5.2 限流

在配置yml文件中filter属性配置就是配置限流的策略,限流默认采用令牌桶限流策略
注意,还需要了解一个类: KeyResolver :指定具体的限流对象,例如ip限流,url限流等

在项目代码中,配置了两中限流方式:IP、URL
走进SpringCloud微服务_第22张图片
注意,在下面配置文件中,filter属性默认的限流name = RequestRateLimiter 这个类,下面配置我采用自定义限流继承RequestRateLimiterGatewayFilterFactory这个类

  routes:
        - id: serve1
          uri: lb://SERVE1
          predicates:
            - Path=/ser1/**
          filters:
            - name: SelfRateLimiter # 自定义限流过滤器
              args:
              # 每秒允许处理的请求数量  #  令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 1
              # 每秒最大处理的请求数量# 令牌桶总容量
                redis-rate-limiter.burstCapacity: 1
              # 每次请求获取的令牌数
                redis-rate-limiter.requestedTokens: 1
              #限流策略,对应策略的Bean
                key-resolver: "#{@pathKeyResolver}"

        - id: serve2
          uri: lb://SERVE2
          predicates:
            - Path=/ser2/**
          filters:
#            - name: RequestRateLimiter  # 官方自带
             - name: SelfRateLimiter
               args:
                # 每秒允许处理的请求数量  #  令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 1
                # 每秒最大处理的请求数量# 令牌桶总容量
                redis-rate-limiter.burstCapacity: 1
                # 每次请求获取的令牌数
                redis-rate-limiter.requestedTokens: 1
                #限流策略,对应策略的Bean
                key-resolver: "#{@ipKeyResolver}"

继承RequestRateLimiterGatewayFilterFactory,限流策略没变,基本是源码复制过来,稍微重写一下,重写的目的是末尾能够将异常原因设置响应给前端

/**
 * 自定义geway限流策略
 */
@Component
public class SelfRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {

    private final RateLimiter defaultRateLimiter;

    private final KeyResolver defaultKeyResolver;

    public SelfRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
        super(defaultRateLimiter, defaultKeyResolver);
        this.defaultRateLimiter = defaultRateLimiter;
        this.defaultKeyResolver = defaultKeyResolver;
    }

    @Override
    public GatewayFilter apply(Config config) {

        KeyResolver resolver = getOrDefault(config.getKeyResolver(), defaultKeyResolver);

        RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), defaultRateLimiter);

        return (exchange, chain) -> {

            // exchange : DefaultServerWebExchange
            // 示例:对路径,ip限流,
            // resolve: value:"/ser1/test1"、"0:0:0:0:0:0:0:1"
            // 这里获取到路径
            // mono类型都是要采用反应式调用的方式,就好比steam流了,接下载就是对应的api调用,所以这里用到了flatmap
            Mono<String> resolve = resolver.resolve(exchange);
            return resolve.flatMap(key -> {
                        // 获取路由id(服务名)
                        String routeId = config.getRouteId();

                        if (routeId == null) {
                            // yml文件中没配置,则从当前请求上下文exchange中获取key为gatewayRoute的路由信息
                            Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                            // 获取具体的服务名
                            routeId = route.getId();
                        }

                        String finalRouteId = routeId;

                 // key = "ser1/test"
                // 限流核心方法isAllowed-->redis+lua实现令牌桶算法
                // 返回结果:1,是否允许、 2,剩余令牌有无,没有则-1、 3,流速、生产数、剩余数的map集合
                /** 打印示例如下:
                    transfer-encoding--------[chunked]:分块传输编码
                    X-RateLimit-Remaining--------[0] :剩余量
                    X-RateLimit-Burst-Capacity--------[1]:桶容量
                    X-RateLimit-Replenish-Rate--------[1]:生产令牌速度
                 */
                Mono<RateLimiter.Response> allowed = limiter.isAllowed(routeId, key);
                return allowed.flatMap(response -> {

                    response.getHeaders().entrySet();

                           // response中设置响应信息,响应出去
                            for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
                                exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
                            }

                            // 放行
                            if (response.isAllowed()) {
                                return chain.filter(exchange);
                            }

                            // 不放行
                            System.out.println("已限流: " + finalRouteId);

                            ServerHttpResponse httpResponse = exchange.getResponse();

                            //修改code为500
                            httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);

                            if (!httpResponse.getHeaders().containsKey("Content-Type")) {
                                httpResponse.getHeaders().add("Content-Type", "application/json");
                            }

                            //此处无法触发全局异常处理,(默认返回429,无其它信息),手动返回response,设置code对应mes信息
                            HashMap<Object, Object> resultMap = new HashMap<>();
                            resultMap.put("code", "429");
                            resultMap.put("mes", finalRouteId + "服务访问人数过多,已限流");
                            resultMap.put("data", "Server throttling");
                            resultMap.put("success", "false");

                            //将map格式转为json
                            String resultJson = JSONObject.toJSONString(resultMap);

                            DataBuffer buffer = httpResponse.bufferFactory().wrap(resultJson.getBytes(StandardCharsets.UTF_8));
                            return httpResponse.writeWith(Mono.just(buffer));
                        });

                    });
        };
    }

    private <T> T getOrDefault(T configValue, T defaultValue) {
        return (configValue != null) ? configValue : defaultValue;
    }
}

此在,在微服务的入口这里,还可以实现令牌的校验,检查登录,做授权服务
本质配置去全局过滤器(后续会采用其他的方式,比如springsecurity+jwt+oauth2做认证授权服务器校验token等),这里大致知道有这么一个过滤器GlobalFilter,Ordered

@Component
public class LoginFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();

        String accessToken = request.getHeaders().getFirst("token");

        if("xzq".equals(accessToken)){
            return chain.filter(exchange);
        }else{
            // 给响应,跳到登录页面
            return login(exchange);
        }
    }

    private Mono<Void> login(ServerWebExchange exchange) {

        HashMap<Object, Object> resultMap = new HashMap<>();
        resultMap.put("code",401);
        resultMap.put("mes","请重新登录授权");
        resultMap.put("status", "401");

        ServerHttpResponse response = exchange.getResponse();
        byte[] bytes = resultMap.toString().getBytes(StandardCharsets.UTF_8);
        response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
        // 封装响应数据
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return  response.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

接下啦,用postman测试一下限流效果:
走进SpringCloud微服务_第23张图片
.

六、链路追踪:Flueth、Zipkin(docker配置组件) ⭐⭐

简单来说: 可以采用Sleuth收集所有服务的日志信息,Sleuth会将收集到的日志发送到Zipkin图形化界面中,更快速的定位到为题发生的服务中
在各个服务中配置,统一发送到图形化界面上

1,导入依赖

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-sleuthartifactId>
dependency>
 
// 直接导入下面,因为zipkin依赖包含了slueth依赖
<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-zipkinartifactId>
dependency>

2,指定日志级别: 将前端控制器的日志级别设置为DEBUG(因为所有请求都是从前端控制器开始进入的)

logging:
  level: 
    org.springframework.web.servlet.DispatcherServlet: DEBUG

3,配置application.yml文件

zipkin:
    sender:
      type: web
    base-url: http://120.76.159.196:9411
  sleuth:
    sampler:
      probability: 1

这里再serve1和serve2以及网关中加入配置和日志,接下来,启动服务,看控制台!!!!
在这里插入图片描述
注意,这里可以看到,出现了DEBUG [serve2,2393be0d0bcda80b,79db3bf31e734485,true]:解释当前服务,总链路id,子链路id,日志是否发送到其他服务
.

4, 配置slueth搭配zipkin可视化:注意看,可视化的方式,可以直接通过web请求收集,也可以通过irabbitmq收集,也可以通过kafka收集,再实际开发过程中,基本都是通过队列实现,因为通过web请求是同步处理的方式,耗时,这样采用异步的方式将日志发送到队列上在可视化到zipkin上,性能好很多!!!
.

关于收集和是持久化日志:

1,收集日志方式Rabiit、Kafka的方式的话,需要配置rabbit,例如采用docker配置,然后配置文件加入配置即可,这里就简单采用web当时看效果啦
.
2,Sleuth异步传输数据到Zipkin,避免Sleuth以Web的方式发送日志信息给Zipkin会造成额外的性能损耗可以通过RabbitMQ实现异步的方式,让Sleuth将日志发送到消息队列,让Zipkin收集即可
.
3, Zipkin默认采用内存存储数据,导致Zipkin重启后,数据全部丢失。
Zipkin也提供了响应的持久化方案,比较熟悉的是ES和MySQL,其中ES更适合存储海量数据,采用ES作为持久化方案只需要Zipkin设置好ES的地址信息,Zipkin会自动创建索引,存储日志信息

方式一:web
走进SpringCloud微服务_第24张图片

5,接下来就是演示效果截图
访问链接:http://120.76.159.196:9411/zipkin/
走进SpringCloud微服务_第25张图片

接下来从getway网关服务开始,网关路由serve2,serve2远程调用serve1的方法,postman测一下看效果
postman测一下,出现一条日志
走进SpringCloud微服务_第26张图片
走进SpringCloud微服务_第27张图片
在下图中可以清晰的看到服务之间的调用,以及调用的那个方法吗,时间长久相对长久,根据时间判断在调用过程中,那个服务请求的时间长,然后可以做具体的优化!!!!,真的一目了然
走进SpringCloud微服务_第28张图片

在下面两张图中,都可以很清晰的看到请求调用浏览器开始时间,结束时间,经过服务器开始结束时间,以及指定看一个服务器可以看到请求经过的的服务的方法、具体的请求方式Get、路径,controller类,方法名等,真的详细,另外在途中最后中可以看到服务serve1调用出问题,红色代表出现异常

http.method
GET
http.path
/ser2/test1
mvc.controller.class
Serve2Controller
mvc.controller.method
getServe1Info1

走进SpringCloud微服务_第29张图片
走进SpringCloud微服务_第30张图片
.
.

最后,赋springcloud简单总结资料:getway换成zuul也简单了解一下吧
走进SpringCloud微服务_第31张图片走进SpringCloud微服务_第32张图片

.
至此,微服务的基本组件的了解和应用就到此结束了…

你可能感兴趣的:(SpringCloud,spring,cloud,微服务)