SpringCloud-Netflix第一代微服务快速入门

1.springCloud常用组件

Netflix Eureka

当我们的微服务过多的时候,管理服务的通信地址是一个非常麻烦的事情,Eureka就是用来管理微服务的通信地址清单的,有了Eureka之后我们通过服务的名字就能实现服务的调用。

Netflix Ribbon\Feign : 客户端负载均衡

Ribbon和Feign都是客户端负载均衡器,它的作用是在服务发生调用的时候帮我们将请求按照某种规则分发到多个目标服务器上,简单理解就是用来解决微服务之间的通信问题。

Netflix Hystrix :断路器/熔断器(保护我们微服务的)

微服务的调用是非常复杂的,有的时候一个请求需要很多的微服务共同完成,那么一旦某个服务发生故障,导致整个调用链上的微服务全都出现异常,甚至导致整个微服务架构瘫痪。Hystrix就是用来解决微服务故障,保护微服务安全的组件。

Netflix Zuul : 服务网关

zuul作为服务网关,我们可以把它看作是微服务的大门,所有的请求都需要经过zuul之后才能到达目标服务,根据这一特性,我们可以把微服务公共的是事情交给zuul统一处理,如:用户鉴权,请求监控等。

Spring Cloud Config :分布式配置

微服务架构中的服务实例非常的多,服务的配置文件分散在每个服务中,每次修改服务的配置文件和重新服务实例都是一个很麻烦的工作,Spring Cloud Config作为分布式配置管理中心就是用来统一的管理服务的配置文件。

Spring Cloud Bus : 消息总线

消息总线是在微服务中给各个微服务广播消息的一个组件,我们使用消息总线构建一个消息中心,其他微服务来接入到消息中心,当消息总线发起消息,接入的微服务都可以收到消息从而进行消费。

Spring Cloud sleuth :微服务链路追踪

当我们的应用采用微服务架构之后,后台可能有几十个甚至几百个服务在支撑,一个请求请求可能需要多次的服务调用最后才能完成,链路追踪的作用就是来监控维护之间的调用关系,让程序员方便直观的感受到一个请求经历了哪些微服务,以及服务的请求时间,是否有异常等。

2.Eureka服务注册与发现

1.1. 什么是Eureka

Eureka 是Netflix公司提供的服务注册与发现组件。

微服务的其中一个特点是服务之间需要进行网络通信,服务器之间发起调用时调用服务得知道被调用服务的通信地址,试问当微服务数量成百上千之多,程序员该如何管理众多的服务通信地址,对于随时新增加的微服务和下线的微服务,又应该如何去动态添加和删除这些微服务的通信地址呢?所以手工管理服务的通信地址是一件遥不可及的事情,我们需要借助一个强大的工具帮我们实现这一功能 - Eureka,同类型的组件还有 zookeeper,consul等

1.2. Eureka的工作原理
1.服务注册

Eureka是一个服务注册与发现组件,简单说就是用来统一管理微服务的通信地址的组件,它包含了EurekaServer 服务端(也叫注册中心)和EurekaClient客户端两部分组成,EurekaServer是独立的服务,而EurekaClient需要集成到每个微服务中。

微服务(EurekaClient)在启动的时候会向EurekaServer提交自己的服务信息(通信地址如:服务名,ip,端口等),在 EurekaServer会形成一个微服务的通信地址列表存储起来。 --- 这叫服务注册

2.服务发现

微服务(EurekaClient)会定期(RegistryFetchIntervalSeconds:默认30s)的从EurekaServer拉取一份微服务通信地址列表缓存到本地。当一个微服务在向另一个微服务发起调用的时候会根据目标服务的服务名找到其通信地址,然后基于HTTP协议向目标服务发起请求。---这叫服务发现

3.服务续约

另外,微服务(EurekaClient)采用定时(LeaseRenewalIntervalInSeconds:默认30s)发送"心跳"请求向EurekaServer发请求进行服务续约,其实就是定时向 EurekaServer发请求报告自己的健康状况,告诉EurekaServer自己还活着,不要把自己从服务地址清单中剔除掉,那么当微服务(EurekaClient)宕机未向EurekaServer续约,或者续约请求超时,注册中心机会从服务地址清单中剔除该续约失败的服务。

4.服务下线

微服务(EurekaClient)关闭服务前向注册中心发送下线请求,注册中心(EurekaServer)接受到下线请求负责将该服务实例从注册列表剔除

下面我们用一张图来介绍Eureka的工作流程

1.3快速入门
1.3.1导入springcloud父模块依赖
XML 复制代码
<dependencyManagement>
  <dependencies>
    <!--SpringCloud依赖-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>2021.0.5</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
1.3.2Eureka服务端
1.导包
XML 复制代码
<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  </dependency>
</dependencies>
2.启动类添加注解@EnableEurekaServer
java 复制代码
@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApp.class, args);
    }
}
3.yml配置文件
server:
  port: 1000 #端口号
spring:
  application:
    name: eureka-server #服务名
eureka:
  instance:
    instance-id: ${spring.application.name}-${server.port} #服务实例id
    hostname: localhost #ip地址
  client:
    fetch-registry: false #关闭注册中心
    register-with-eureka: false #不注册到注册中心
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka #注册中心地址
1.3.3Eureka客户端
1.导包(client不包含web包)
XML 复制代码
<!--springWeb包-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka客户端包-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.启动类添加注解(可忽略,会自动添加)
3.yml配置文件
server:
  port: 2000
spring:
  application:
    name: pay-server
eureka:
  instance:
    instance-id: ${spring.application.name}
  client:
    fetch-registry: true
    register-with-eureka: true
    serviceUrl:
      defaultZone: http://localhost:1000/eureka
4.集群配置
spring:
  application:
    name: user-service
eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    serviceUrl:
      defaultZone: http://localhost:1000/eureka
---
server:
  port: 3000
eureka:
  instance:
    instance-id: ${spring.application.name}-${server.port}
spring:
  config:
    activate:
      on-profile: user-server-3000
---
server:
  port: 3001
eureka:
  instance:
    instance-id: ${spring.application.name}-${server.port}
spring:
  config:
    activate:
      on-profile: user-server-3001
1.4服务间调用

1.将RestTemplate交给spring管理

java 复制代码
  @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

2.调用

java 复制代码
@RestController
@RequestMapping("/pay")
public class PayController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/{id}")
    public User toPay(@PathVariable("id") Long id) {
        User user = restTemplate.getForObject("http://user-service/user/"+id, User.class);
        return user;
    }
}

3.OpenFeign负载均衡

3.1概念

Feign是一个声明式的http客户端,使用Feign可以实现声明式REST调用,它的目的就是让服务的调用更加简单。Feign整合了SpringMvc注解,这让Feign的客户端接口看起来就像一个Controller。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。

3.2Feign和rest template区别

OpenFeign整合了Ribbon和Hystrix,屏蔽了Ribbon拼接URL,参数的细节,使用声明式编程,让服务调用变得更加简单,OpenFiegn底层也是走的Ribbon的负载均衡策略。推荐使用OpenFeign;

3.3Feign的工作原理

主配置类通过@EnableFeignClients(value="")注解开启Feign,也可以通过value属性指定了Feign的扫描包。同时我们需要为Feign编写客户端接口,接口上需要注解@FeignClient标签。 当程序启动,注解了@FeignClient的接口将会被扫描到然后交给Spring管理。

当请求发起,会使用jdk的动态代理方式代理接口,生成相应的RequestTemplate,Feign会为每个方法生成一个RequestTemplate同时封装好http信息,如:url,请求参数等等

最终RequestTemplate生成request请求,交给Http客户端(UrlConnection ,HttpClient,OkHttp)。然后Http客户端会交给LoadBalancerClient,使用Ribbon的负载均衡发起调用。

3.4Feign的负载均衡原理

在SpringCloud中通过 ClientHttpRequestInterceptor 请求拦截器来连接Feign的请求,其中提供了一个 LoadBalancerClient 来实现负载均衡,他是一个接口,它集成了 ServiceInstanceChooser 接口 ,ServiceInstanceChooser的作用是做服务实例的选择,里面提供了ServiceInstance choose(String serviceId); 方法来根据服务的ID选择一个服务实例(服务提供者做了集群会有多个实例,而一次调用只会执行一个服务实例)。而LoadBalancerClient提供了execute方法去执行服务的调用。

LoadBalancerClient 有一个实现类 BlockingLoadBalancerClient ,来复写 choose 选择实例方法 和 execute调用实例 方法;

在 BlockingLoadBalancerClient 通过调用 ReactiveLoadBalancer #choose(响应式负载均衡器) 方法来选择服务,他有2个子类 RoundRobinLoadBalancer (轮询算法) ,和 RandomLoadBalancer(随机算法) , 默认采用的是RoundRobinLoadBalancer 轮询算法。

3.5Feign入门实战
3.5.1导包
XML 复制代码
<!--2.导入Feign的包-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3.5.2编写远程调用客户端

@FeignClient("user-server") : user-server是用户服务的服务名字,Feign根据服务名能够在注册中心找到目标服务的通信地址

你会发现接口中的方法跟用户服务中的方法一样,其实Feign就是通过客户端接口里面的方法,来决定目标服务的资源路径url,参数以及返回值,这里我们可以直接把要调用的目标服务的controller方法拷贝过来,然后去掉方法体即可。

Feign可以根据@FeignClient("user-server")找到用户服务,根据方法上的 @RequestMapping(value = "/user/{id}",method = RequestMethod.GET)找到目标服务的controller的方法 ,我们在使用Feign接口时传入的参数就会作为目标服务controller方法的参数,而返回值就是目标服务controller方法的返回值。

即:服务名要一致 , url路径要一致 , 参数要一致 , 返回值类型要一致。建议 :服务名直接去目标服务配置中拷贝 , 方法直接从目标服务controller中拷贝

java 复制代码
@FeignClient(value = "user-service",path = "/user")
public interface UserFeign {

    @GetMapping("/{id}")
    User getUser(@PathVariable("id") Long id);
}
3.5.3启动类扫描FeignClient注解@EnableFeignClients
java 复制代码
@SpringBootApplication
@EnableFeignClients
//自定义负载均衡配置
@LoadBalancerClients(
        @LoadBalancerClient(value = "user-service", configuration = LoadBalancerConfig.class)
)

public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class, args);
    }
}
3.5.4远程调用
java 复制代码
 @Autowired
    private UserFeign userFeign;

    @GetMapping("/{id}")
    public User getUser(@PathVariable("id") Long id) {
        return userFeign.getUser(id);
    }
3.5.5负载均衡配置
1.默认轮询
2.使用随机算法

指定一个负载均衡器算法可以通过如下配置

java 复制代码
@Configuration
public class LoadBalancerConfig {
    //将官方提供的 RandomLoadBalancer 注册为Bean

    @Bean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

启动类上加注解,指定服务的负载均衡算法

java 复制代码
@LoadBalancerClients(
        @LoadBalancerClient(value = "user-service", configuration = LoadBalancerConfig.class)
)
3.5.6Feign调用超时配置与打印日志
feign:
  client:
    config:
      default: #所有服务配置
        connectTimeout: 2000 #连接超时时间
        readTimeout: 2000		#调用时间
        loggerLevel: FULL  #日志

Feign支持如下几种日志模式来决定日志记录内容多少:

  • NONE,不记录(DEFAULT)。

  • BASIC,仅记录请求方法和URL以及响应状态代码和执行时间。

  • HEADERS,记录基本信息以及请求和响应标头。

  • FULL,记录请求和响应的标题,正文和元数据。

    logging:
    level:
    cn.itsource.springboot.feignclient.UserFeignClient: debug

3.5.7Feign开启GZIP

可以通过开启Feign的数据压缩传输来节省网络开销,但是压缩数据会增加CPU的开销,所以太小的数据没必要压缩,可以通过压缩大小阈值来控制,配置如下:

feign:
  compression:
    request:
      enabled: true
      min-request-size: 1024 #最小阈值,小于这个不压缩
      mime-types: text/xml,application/xml,application/json #压缩哪些类型的数据
    response:
      enabled: true

4.Gateway服务网关

4.1.Gateway和zuul区别

Zuul是Netflix的开源项目,Spring Cloud将其收纳成为自己的一个子组件。zuul用的是多线程阻塞模型,它本质上就是一个同步 Servlet,这样的模型比较简单,他都问题是多线程之间上下文切换是有开销的,线程越多开销就越大。线程池数量固定意味着能力接受的请求数固定,当后台请求变慢,面对大量的请求,线程池中的线程容易被耗尽,后续的请求会被拒绝。

在Zuul 2.0中它采用了 Netty 实现异步非阻塞编程模型,异步非阻塞模式对线程的消耗比较少,对线程上线文切换的消耗也比较小,并且可以接受更多的请求。它的问题就是线程模型比较复杂,要求深究底层原理需要花一些功夫。

Spring Cloud Gateway是Spring Cloud自己的产物,基于Spring 5 和Spring Boot 2.0 开发,Spring Cloud Gateway的出现是为了代替zuul,在Spring Cloud 高版本中没有对zuul 2.0进行集成,SpringCloud Gateway使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流

所以说其实Gateway和zuul 2.0差别不是特别大,都是采用Netty高性能通信框架,性能都挺不错。

4.2Gataway的核心概念

Spring Cloud Gataway有几个核心组成:

  • Filter(过滤器):

Spring Cloud Gateway的Filter和Zuul的过滤器类似,可以在请求发出前后进行一些业务上的处理 ,这里分为两种类型的Filter,分别是Gateway Filter网关filter和Global Filter全局Filter, 他们的区别在后续会讲到。

  • Route(路由):

网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。说白了就是把url请求路由到对应的资源(服务),或者说是一个请求过来Gateway应该怎么把这个请求转发给下游的微服务,转发给谁。

  • Predicate(断言):

这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。简单理解就是处理HTTP请求的匹配规则,在什么样的请情况下才能命中资源继续访问。

4.3Gateway的工作原理

客户端向Spring Cloud Gateway发出请求。网关处理程序映射器(GatewayHandlerMapping)确定请求与路由匹配,则将其发送到网关Web处理程序(GatewayWebHanlder)。该处理程序通过特定于请求的过滤器链来运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有"前置"过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行"后"过滤器逻辑。

4.4Gateway入门
4.4.1导包
XML 复制代码
 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!--nacos依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--nacos配置中心依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
4.4.2编写yml网关配置
server:
  port: 5000
spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: false #开放服务名访问方式
          lower-case-service-id: true #服务名小写(忽略大小写)
          #配置路由
      routes:
        - id: application-user #指定服务名
          uri: lb://user-service #去注册中心找这个服务名
          predicates: #断言,匹配访问的路径
            - Path=/user/**    #服务访问路径
 #           - Query=test #查询参数,访问请求参数必须携带test参数
          filters: #内置拦截器
            - StripPrefix=1    #请求转发的时候会去掉 /user访问路径
        - id: application-order #指定服务名
          uri: lb://order-service #去注册中心找这个服务名
          predicates: #断言,匹配访问的路径
            - Path=/order/**    #服务访问路径
          filters:
            - StripPrefix=1    #请求转发的时候会去掉 /user访问路径
eureka:
  instance:
    instance-id: ${spring.application.name}-${server.port}
  client:
    fetch-registry: true
    register-with-eureka: true
    serviceUrl:
      defaultZone: http://localhost:1000/eureka
4.4.3启动项目
4.5Predicate断言工厂
4.5.1概念

其实断言工厂就是用来判断http请求的匹配方式。比如我们再上面案例中配置的:"Path=/user/**" ,就是使用的是 "Path Route Predicate Factory" 路径匹配工厂,意思是http请求的资源地址必须是 /user 才会被匹配到对应的路由,然后继续执行对应的服务获取资源。

在Spring Cloud Gateway中,针对不同的场景内置的路由断言工厂,比如

  • Query Route Predicate Factory:根据查询参数来做路由匹配
  • RemoteAddr Route Predicate Factory:根据ip来做路由匹配
  • Header Route Predicate Factory:根据请求头中的参数来路由匹配
  • Host Route Predicate Factory:根据主机名来进行路由匹配
  • Method Route Predicate Factory :根据方法来路由匹配
  • Cookie Route Predicate Factory:根据cookie中的属性值来匹配
  • Before Route Predicate Factory:指定时间之间才能匹配
  • After Route Predicate Factory: 指定时间之前才能匹配
  • Weight Route Predicate Factory: 根据权重把流量分发到不同的主机
4.5.2断言工厂示例

根据查询参数断言

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green

根据path断言-Path Route Predicate Factory(重要)

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://example.org
        predicates:
        - Path=/red/{segment},/blue/{segment}
        
        请求路径如: `/red/1`或`/red/blue`或`/blue/green` 就可以断言成功

根据权重比例断言-Weight Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

根据远程ip断言 - RemoteAddr Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24
        
如果请求的远程地址为 `192.168.1.1`到`192.168.1.24`之间,则此路由匹配        

指定时间之后断言-After Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

在指定时间之前断言-Before Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: https://example.org
        predicates:
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

在指定时间段之间断言-Between Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://example.org
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

根据cookie断言-Cookie Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=chocolate, ch.p

根据请求头断言-Header Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://example.org
        predicates:
        - Header=X-Request-Id, \d+

根据请求方式断言-Method Route Predicate Factory

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
        - Method=GET,POST
4.6Fileter过滤器
4.6.1内置的Gateway filter
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue

在spring.cloud.gateway.routes.filters配置项配置了一个AddRequestHeader ,他"AddRequestHeader GatewayFilter Factory"的名称,意思是在请求头中添加一个"X-Request-red"的属性,值为blue 。

4.6.2gateway filter局部过滤器(路由过滤器)

1.自定义路由过滤器

java 复制代码
@Slf4j
public class TimeRoutesFilter implements GatewayFilter, Ordered {
    /**
     * 过滤器
     *
     * @param exchange 交换
     * @param chain    链
     * @return {@link Mono}<{@link Void}>
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long start = System.currentTimeMillis();
        return chain.filter(exchange).then(
                Mono.fromRunnable(() ->
                        log.info("请求路径:{},耗时:{}ms", exchange.getRequest().getPath(), System.currentTimeMillis() - start)));
    }

    /**
     * 过滤器执行顺序
     *
     * @return int
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

2.路由规则中添加自定义的过滤器

java 复制代码
@Configuration
public class RoutesFilterConfig {


    //配置Filter作用于那个访问规则上
    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {

        return builder.routes().route(r -> r.path("/test/user/**")
                //去掉2个前缀
                .filters(f -> f.stripPrefix(2)
                        .filter(new TimeRoutesFilter())//添加自定义过滤器
                        .addResponseHeader("X-Response-test", "test"))//添加响应头
                .uri("lb://user-service")
        ).build();

    }
}
4.6.3GlobalFilter全局过滤器

GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

java 复制代码
@Component
@Slf4j
public class TimeGlobleFilter implements GlobalFilter , Ordered {

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

        List<String> token = exchange.getRequest().getHeaders().get("token");

        log.info("检查 TOKEN = {}" ,token);
        if(token == null || token.isEmpty()){
            //响应对象
            ServerHttpResponse response = exchange.getResponse();
            //构建错误结果
            HashMap<String,Object> data = new HashMap<>();
            data.put("code",401);
            data.put("message","未登录");

            DataBuffer buffer = null;
            try {
                byte[] bytes = JSON.toJSONString(data).getBytes("utf-8");
                buffer = response.bufferFactory().wrap(bytes);

                //设置完成相应,不会继续执行后面的filter
                //response.setComplete();
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            //把结果写给客户端
            return response.writeWith(Mono.just(buffer));
        }

        log.info("Token不为空 ,放行");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
4.7Gateway跨域配置

Gateway 网关是微服务的访问入口,所以我们只需要在Gateway配置跨域即可

spring:
  cloud:
    globalcors: 
            cors-configurations:
              '[/**]':
                allowedOriginPatterns: "*" 
                allowedMethods:
                  - GET
                  - POST
                  - DELETE
                  - PUT
                  - HEAD
                  - CONNECT
                  - TRACE
                  - OPTIONS
                allowHeaders:
                  - Content-Type
4.8Gateway超时配置
4.8.1全局超时设置:
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 10000
        response-timeout: 10000
4.8.2局部超时设置:
spring:
  cloud:
    gateway:
      routes:
      - id: per_route_timeouts
        uri: https://example.org
        predicates:
          - name: Path
            args:
              pattern: /delay/{timeout}
        metadata:
          response-timeout: 5000
          connect-timeout: 5000
4.9全局异常

gateway是微服务的入口和出口,所有的异常都回来到gateway这里,所以在网关层是非常适合做全局异常处理的。在gateway中通过 ErrorWebExceptionHandler 来处理异常,我们通过实现它来处理异常,加入下面的配置类即可

java 复制代码
@Component
@Order(-1)
@Slf4j
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {


    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {

        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        log.error("defaultExceptionHandler 发生异常 {} ,{} ,{}", request.getURI(), request.getMethod(), ex);
        JsonResult jsonResult = null;
        if (ex instanceof ResponseStatusException) {
            jsonResult = JsonResult.error(((ResponseStatusException) ex).getReason(), ((ResponseStatusException) ex).getStatus().value());
        } else {
            jsonResult = JsonResult.error(ex.getMessage(), 500);
        }
        DataBuffer dataBuffer = response.bufferFactory()
                .allocateBuffer().write(JSON.toJSONString(jsonResult).getBytes());
        response.setStatusCode(HttpStatus.OK);
        //基于流形式
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        return response.writeAndFlushWith(Mono.just(ByteBufMono.just(dataBuffer)));
    }

    //处理系统异常,兜底处理所有的一切
    @ExceptionHandler(value = Exception.class)
    public  JsonResult defaultExceptionHandler(ServerWebExchange exchange,Throwable ex) {
        ServerHttpRequest request = exchange.getRequest();
        log.error("defaultExceptionHandler 发生异常 {} ,{} ,{}", request.getURI(), request.getMethod(), ex);
        return JsonResult.error(ex.getMessage(),200);
    }
}

5.sleuth链路追踪

5.1概念

解决如何快速定位服务故障点,并且服务之间的调用链关系,服务之间通信时间等等都需要去监控,Zipkin分布式跟踪系统就能很好的解决这样的问题,在SpringCloud中Spring Cloud Sleuth 组件基于Zipkin进行封装,让链路追踪实现起来更为简单。

5.2ZipKin

Zipkin是Twitter的一个开源项目,它基于Google Dapper实现。我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的API接口之外,它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。

ZipKin可以分为两部分,一部分是zipkin server,用来作为数据的采集存储、数据分析与展示;第二部分是zipkin client,是基于不同的语言及框架封装的一些列客户端工具,这些工具完成了追踪数据的生成与上报功能,架构如下:

  • Collector:收集器组件,它主要用于处理从外部系统(要链路追踪的微服务)发送过来的跟踪信息,将这些信息转换为Zipkin内部处理的Span格式,以支持后续的存储、分析、展示等功能。
  • Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
  • RESTful API:API组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
  • Web UI:UI组件,基于API组件实现的上层应用。通过UI组件用户可以方便而有直观地查询和分析跟踪信息
5.3核心概念

ZipKin实现的基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以可视化地分析服务调用链路和服务间的依赖关系。

  • traceId用来确定一个追踪链的16字符长度的字符串,在某个追踪链中保持不变。Zipkin使用Trace结构表示对一次请求的跟踪,一次请求可能由后台的若干服务负责处理,每个服务的处理是一 个Span,Span之间有依赖关系,Trace就是树结构的Span集合;
  • spanId区域Id,在一个追踪链中spanId可能存在多个,每个spanId用于表明在某个服务中的身份,也是16字符长度的字符串。 每个服务的处理跟踪是一个Span,可以理解为一个基本的工作单元,包含了一些描述信息:id,parentId,name,timestamp,duration,annotations等
  • parentId在跨服务调用者的spanId会传递给被调用者,被调用者会将调用者的spanId作为自己的parentId,然后自己再生成spanId。
5.4快速入门
5.4.1下载服务端

到zipkin-server官网下载服务端: Central Repository: io/zipkin/zipkin-server/2.20.0 ,加载好之后通过java -jar 启动 , 默认端口是 9411

5.4.2项目集成客户端
5.4.2.1导包
XML 复制代码
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
5.4.2.2添加yml配置
spring:
  zipkin:
    base-url: http://localhost:9411 #Zipkin服务端地址 ,可以使用ip地址也可以使用服务发现方式。
    discovery-client-enabled: false #如果使用服务发现的方式查找zipkin则打开为true
    sender:
      type: web #使用http方式发送信息给zipkinServer , 还可以使用MQ的方式
  sleuth:
    sampler:
      probability: 1.0

6.Hystrix熔断器(已淘汰)

6.1雪崩效应

雪崩效应是指在分布式系统中,当某个服务出现故障或不可用时,其它服务也因为请求超时或者失败而出现级联故障,最终导致整个系统不可用的现象。这种现象类似于雪崩,一旦开始,就会迅速扩大并影响到整个系统。

雪崩效应通常是由于分布式系统中的服务之间存在复杂的依赖关系,当某个服务出现故障时,其它服务无法正常响应请求,进而导致这些服务的负载迅速增加,最终导致整个系统的崩溃。

6.2Hystrix介绍

Netflix所开源的非常流行的高可用架构框架,Hystrix是处理依赖隔离的框架,将出现故障的服务通过熔断、降级等手段隔离开来

Hystrix其设计原则如下:

  1. 防止单个服务异常导致整个微服务故障。
  2. 快速失败,如果服务出现故障,服务的请求快速失败,线程不会等待。
  3. 服务降级,请求故障可以返回设定好的二手方案数据(兜底数据)。
  4. 熔断机制,防止故障的扩散,导致整个服务瘫痪。
  5. 服务监控,提供了Hystrix Bashboard仪表盘,实时监控熔断器状态
6.3资源隔离(限流,流控)

资源隔离包括线程池隔离和信号量隔离,作用是限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用 ,这里可以简单的理解为资源隔离就是限制请求的数量。

线程池隔离:使用一个线程池来存储当前请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求先入线程池队列。这种方式要为每个依赖服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)

信号量隔离:使用一个原子计数器(或信号量)记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)

6.4服务熔断

熔断机制是对服务链路的保护机制,如果链路上的某个服务不可访问,调用超时,发生异常等,服务会触发降级返回托底数据,然后熔断服务的调用(失败率达到某个阀值服务标记为短路状态),当检查到该节点能正常使用时服务会快速恢复。

6.5服务降级

超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。

简单理解就是服务降级就是当服务因为网络故障,服务器故障,读取超时等原因造成服务不可达的情况下返回一些预先准备好的数据给客户端

6.6Hystrix工作机制

Hystrix它是一个Netflix公司开发的一个产品组件,是SpringC1oud第一代微服务熔断器它主要的作用是保护我们的微服务的,当我们一个请求去访问微服务的时候,如果该微服务挂了那如果没有熔断器,它会出现等待5秒的时间,当然如果并发量比较大的话,很可能造成整个项目崩塌,这时候如果项目集成了熔断器,该项目不会长时间等待5秒,如果达到了指定时间的闽值,它会快速降级响应兜底数据,如果多个请求依然访问微服务失败,它会把该微服务设置为熔断状态后续的请求再访问微服务的时候,它就会快速降级响应兜底数据,过了一段时间之后,该微服务会变为半熔断状态,它会发送少量请求给该微服务如果请求依然是失败的状态,那半熔断状态又会变为熔断状态,如果请求是成功的,那就会把半熔断状态标记为正常状态!!!!

6.7快速入门(Feign中使用)

新版springcloud中淘汰了Hystrix,需要手动导包

6.7.1导包
XML 复制代码
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  <version>2.2.9.RELEASE</version>
</dependency>
6.7.2添加配置yml
feign:
  circuitbreaker:
    enabled: true
6.7.3指定降级方法
java 复制代码
@FeignClient(value = "user-service",path = "/user",fallback = UserFallback.class)
6.8.4编写降级类
java 复制代码
@Component
public class UserFallback implements UserFeign {
    @Override
    public User getUser(Long id) {
        return new User().setName("用户服务降级").setPassword("xxxxxxxxxxxxx");
    }
}
相关推荐
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
李小白662 小时前
Spring MVC(上)
java·spring·mvc
Java程序之猿3 小时前
微服务分布式(一、项目初始化)
分布式·微服务·架构
Lojarro4 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
Yvemil75 小时前
《开启微服务之旅:Spring Boot Web开发举例》(一)
前端·spring boot·微服务
zjw_rp5 小时前
Spring-AOP
java·后端·spring·spring-aop
撒呼呼8 小时前
# 起步专用 - 哔哩哔哩全模块超还原设计!(内含接口文档、数据库设计)
数据库·spring boot·spring·mvc·springboot
Yvemil79 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
维李设论9 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
天使day9 小时前
SpringMVC
java·spring·java-ee