SpringCloud之微服务组件大全--第一话(微服务实现技术栈、Eureka、Ribbon负载、Feign性能优化、Nacos集群、Gateway三大过滤器)

微服务实现方案

学习总结,方便后续开发时快速回忆技术点。有兴趣看B站黑马技术视频,老师风趣幽默,技术点讲解通俗易懂,推荐!

微服务治理的技术栈,最早是阿里的dubbo,后国外的SpringCloud,阿里集成的SpringCloudAlibaba等技术栈,实现微服务协调治理,形成一个完整的技术栈生态群。

在这里插入图片描述

SpringCloudAlibaba集成Dubbo和SpringCloud技术栈,整合出即适用于Dubbo又可用于SpringCloud的技术群,在国内也是非常火热的。

那么实际开发中,可能会遇见不同的企业需求,不同的技术选型

在这里插入图片描述

SpringCloud集成了各种微服务的功能组件,基于Springboot实现这些组件的自动装配,提供了很好的开箱即用的体验:

在这里插入图片描述

Eureka(代码入侵性较高)

客服端服务每隔一定时间都会向Eureka服务器发送心跳,确保客服端服务是存活的。

搭建服务器

  • 引入相关依赖,一般spring-cloud-starter-netflix-eureka-server,可能不同版本中使用的组件不同
  • 启动类加注解@EnableEurekaServer
  • 添加eureka相关配置,注意不要将eureka服务器自己给注册到服务中心上。

搭建客户端

  • 引入依赖,一般spring-cloud-starter-netflix-eureka-client,可能不同版本中使用的组件不同
  • 添加eureka相关配置
  • 启动类加注解@EnableDiscoveryClient,启动服务发现

若要启动两个相同的实例,端口不同,那么直接在idea dashboard控制台中,拷贝一份,然后设置启动参数即可。

-Dserver.port=8082

Ribbon

Ribbon的作用就是负载均衡,当远程调用时Ribbon会拦截请求,然后到注册中心找对应的服务们(相同实例的服务),通过负载均衡算法(如轮询等),找出具体的某一个实例地址,进行接口访问。

@Bean
@LoadBalanced	// 标记@LoadBalanced后,表示此对象发起的请求,会被ribbon拦截,并负载均衡处理
public RestTemplate restTemplate() {
    
    
    return new RestTemplate();
}

是通过LoadBalancerInterceptor(实现自ClientHttpRequestInterceptor)实现的。ClientHttpRequestInterceptor是一个拦截器,会拦截所有客户端发起的Http类型的请求。后面项目业务中如果有用到,可以实现此接口的方法。(如进行http请求时,所有请求要走代理,可以这么玩)

Ribbon默认采用的懒加载模式,即第一次启动的时候才回去创建LoadBalanceClient,请求时间会很长。而饥饿加载,会在项目启动时就创建,降低第一次访问的耗时,可通过配置开启:

ribbon:
  eage-load:
    enabled: true # 开启饥饿模式
    clients:   # 指定对某个服务访问时的饥饿加载
      - userservice
      - xxxservice

Nacos(推荐)

阿里巴巴产品。功能丰富,包含服务注册发现、远程配置中心

注意:nacos所在目录不要有中文!!

是作为一个外部服务使用的,需要进行下载安装,启动。

注册

  • 引入相关的依赖,建议包管理中直接引入spring-cloud-alibaba-dependencies,然后方便管理其他依赖的版本信息

  • 添加spring-cloud-starter-alibaba-nacos-discovery

  • 配置文件中设置nacos服务器地址信息

    spring:
      cloud:
        nacos:
          server-addr: localhost:8848
    
  • 启动类加上注解@EnableDiscoveryClient(启用 服务注册发现 客户端)

  • 启动项目即可

集群配置

  • 配置文件中配置集群

    spring:
      cloud:
        nacos:
          server-addr: localhost:8848
          discovery:
            cluster-name: BJ  # 配置集群名称
    

服务分级模型(三级):

  • 首先是服务
  • 然后是集群
  • 最后是集群中某一个服务实例

Nacos负载均衡

Ribbon负载均衡不会去考虑集群情况,只会轮询所有的服务实例,那么涉及到不同地点的集群时,肯定要优先选择本地集群的服务实例,那么可以使用nacos的负载均衡

假如A、B服务,A远程调用B,那么负载时需要先找本地集群

  • A、B服务都要配置集群属性,也就是上面的

    spring:
      cloud:
        nacos:
          server-addr: localhost:8848
          discovery:
            cluster-name: BJ  # 配置集群名称
    
  • 在A配置中设置调用B服务时使用的负载算法

    Bservice:
      ribbon:
        NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载规则
    

配置后,再次A调用B时会先找本地集群中的B服务,如果没找到,那么就去其他外地集群找服务,且在日志中会打印警告信息,告知是跨集群访问

负载-权重

在nacos控制台中,可手动设置实例的权重值(默认都是1的权重),如设置为0.1设置后,此实例的访问频率会大大降低(注意:这里说的是集群模式下),如果权重设置为0,那么此实例不会被访问,通常情况下可用作平滑升级时使用

环境隔离

通过namespace,进行环境隔离

在这里插入图片描述

在nacos中可以设置不同的命名空间进行环境的隔离。

默认情况下是public,nacos自带的命名空间,若要放入其他命名空间,需要在代码中配置命名空间。

spring:
  cloud:
    nacos:
      discovery:
        namespace: xxxxxx-xxxx-xxx-xxxx # 命名空间的ID
        cluster-name: BJ # 集群

服务中心

在nacos中,所有的实例默认都是临时实例,每隔一段时间,临时实例都会给nacos发送健康心跳,确保存活。当临时实例死亡时,nacos会把临时实例剔除服务列表中。而非临时实例,是nacos主动询问服务是否存活,若服务挂了,那么nacos不会剔除它,而是给个不健康的状态,然后等它上线。

通过配置设置为非临时实例:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false # 设置为非临时实例

配置中心

nacos管理页面,创建配置,配置的DataId就是文件名,一般命名:服务名-profile.yml即可。如business-dev.yml,group默认即可,配置格式一般yaml即可。

项目启动时先读取nacos配置文件---->本地application.yml文件—>…,在springcloud中,bootstrap文件的优先级是高于application.yml的,所以cloud中就使用bootstrap文件即可。

  • 引入配置中心的相关依赖spring-cloud-starter-alibaba-nacos-config

  • 在代码中做配置即可:bootstrap.yml

    spring:
      application:
        name: business
      profiles:
        active: dev # 开发环境
      cloud:
        nacos:
          server-addr: localhost:8848
    

动态刷新

Nacos中的配置变更后,微服务不需重启可以感知。需要下面两种配置实现:

  • 方法一:在@Value注入的变量所在类上使用注解@RefreshScope实现属性刷新(单量注入属性)

    @Configuration
    @RefreshScope
    public class AConfig {
          
          
        @Value("${time.a}")
        private String a;
    }
    
  • 方法二:使用@ConfigurationProperties注解(批量注入属性)而且不管是nacos还是consul,他们的配置都是会自动刷新的

    @Component
    @Data
    @ConfigurationProperties(prefix = "time")
    public class AConfig {
          
          
    
        private String a;
    }
    

多环境配置共享

服务启动时会从nacos中读取多个配置文件,首先是读取服务名-profile.yml的文件,其次还会读取服务名.yml的文件,那么服务名.yml的文件可以作为共享的主配置文件来使用。

使用到时之间创建一个文件即可,按照上例的话就是business.yml的文件,然后做配置就行了。而且有一点:带环境区分的文件的优先级是比主配置文件高的,若有相同的配置,那么毫无疑问是环境区分的文件business-dev.yml的优先。

在这里插入图片描述

Nacos集群

  • 初始化mysql数据(mysql可以是集群也可以是单实例),sql文件可到网上找

  • 配置nacos

    进入config/cluster.conf文件(将cluster.conf.example文件重命名即可),添加内容

    127.0.0.1:8845
    127.0.0.1:8846
    127.0.0.1:8847
    

    在config/application.properties文件中添加

    mysql数据库相关连接
    端口号属性
    
  • 启动nacos集群

  • 通过nginx做反向代理,负载均衡

    upstream nacos-cluster {
    	server 127.0.0.1:8845;
    	server 127.0.0.1:8846;
    	server 127.0.0.1:8847;
    }
    server {
    	listen	80;
    	server_name localhost;
    	location /nacos {
    		proxy_pass http://nacos-cluster;
    	}
    }
    

当做好Nacos集群时,java客户端可以直接配置nginx的地址和端口访问即可

spring:
  cloud:
    nacos:
      server-addr: localhost:80

Feign

声明式远程调用,用来替代Ribbon的复杂远程调用。Ribbon远程调用:

  • 代码可读性差
  • 参数难以维护

实现方式

  • 引入相关依赖spring-cloud-starter-openfeign

  • 启动类加上注解@EnableFeignClients

  • 声明一个Feign客户端,比如要调用B中的服务接口,那么在A服务中创建此客户端

    @FeignClient("BService")
    public interface BServiceClient {
          
          
        @GetMapping("/user/{id}")
        User findById(@PathVariable("id") Long id);
    }
    

直接使用即可,且Feign集成了Ribbon,那么负载均衡也是Feign同样具备的功能。

自定义Feign配置

在这里插入图片描述

一般设置个日志级别完全够用了。Feign集成Ribbon,同样具有失败重试的功能了。

配置文件方式

  • 全局配置

    feign:
      client:
        config:
          default: # 这里写default就是指全局配置,如果写服务名称,则是针对某个服务的
            loggerLevel: BASIC
    
  • 局部配置

    feign:
      client:
        config:
          bservice: # 这里写default就是指全局配置,如果写服务名称,则是针对某个服务的
            loggerLevel: BASIC
    

Java代码方式

声明一个Bean

public class FeignClientConfiguration {
    
    
    @Bean
    public Logger.Level feignLogLevel() {
    
    
        return Logger.Level.BASIC;
    }
}
  • 全局配置

    在启动类的注解上

    @EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
    
  • 局部配置

    在相应的Feign客户端上

    @FeignClient(value = "BService", configuration = FeignClientConfiguration.class)
    

性能优化

Feign底层的客户端实现:

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

优化点:

  • 因为URLConnection是不支持连接池的,所以我们可以使用连接池的方式(改用其他的客户端实现),来提高性能。
  • 日志级别,最好使用BASIC或NONE

开始改造

  • 引入HttpClient的支持依赖feign-httpclient

  • 配置连接池,同样可配置一些超时时间等属性

    feign:
      client:
        config:
          default: # 这里写default就是指全局配置,如果写服务名称,则是针对某个服务的
            loggerLevel: BASIC
      httpclient:
        enabled: true # 开启feign对HttpClient的支持
        max-connections: 200 # 最大连接数
        max-connections-per-route: 50 # 每个路径的最大连接数
    

最佳实践

  • (紧耦合,不推荐)法一:继承。可以给Feign客户端和controller服务接口定义统一的父接口

    在这里插入图片描述

    参数映射不会被继承,注解需要再写一遍。。

  • 法二:抽取。将Feign客户端抽取为独立模块,并且将接口有关的POJO、默认的Feign配置都放入到这个模块中,作为基础服务提供给其他所有消费者使用。

    在这里插入图片描述

如果将feign单独作为一个模块使用时,可能会涉及到定义的Feign客户端不在引用者的Feign包扫描范围内,而无法实现注入。两种解决方式:

  • 法一:指定FeignClient的扫描包

    @EnableFeignClients(basePackages = "com.wlh.feign")
    
  • 法二:指定具体的Client

    @EnableFeignClients(clients = {
          
          UserClient.class})
    

当然,也可能会涉及到无法扫描到,其他的包下的一些POJO类等组件,那么我们可以在@SpringBootApplication中去指定,扫描多个包。

示例:

@SpringBootApplication(scanBasePackages = {
    
    
        "com.wlh.oss.config","com.wlh.oss.controller"
})

Gateway

所有的请求必须经由网关处理进行路由转发,不可直接访问某一个微服务,且要做好上游token,下游鉴权(cloud模板有相关代码实现)。

网关功能:

  • 身份认证、权限校验
  • 服务路由、负载均衡
  • 请求限流(配合redis实现)

搭建网关服务

  • 创建新的网关module,引入网关依赖spring-cloud-starter-gateway,和服务注册的依赖,如spring-cloud-starter-alibaba-nacos-discovery

  • 做配置bootstrap.yml

    server:
      port: 10010
    spring:
      application:
        name: gateway
      cloud:
        nacos:
          server-addr: localhost:8848
        gateway:
          routes:
            - id: user-service # 路由id,自定义,唯一
              uri: lb://userservice # 目标地址,lb指负载均衡,后面跟服务名称
              # uri: http://localhost:8080 # 也可跳转至某个具体url
              predicates: # 路由断言,判断路由是否符合规则
                - Path=/user/** # 以/user/ 开头就符合此路由
    

    当访问localhost:10010/user/1时,会跳转到lb://userservice/user/1的接口上面。

    在这里插入图片描述

路由断言工厂

断言工厂中有很多断言的条件,其中Path只是其中的一种,是由PathRoutePredicateFactory类进行处理的。

在这里插入图片描述

如果设置了多个断言规则,如果访问路径符合全部的断言规则,那么该请求就是符合断言规则的,可以进行路由转发。

快捷链接:SpringCloudGateway断言工厂之Path

网关过滤器

GatewayFilter是网关的过滤器,可以对进入网关的请求和微服务返回的响应做处理。

在这里插入图片描述

Gateway提供了30多种过滤器,可看官方文档:过滤器工厂

这些过滤器可直接在配置文件中进行配置bootstrap.yml或其他远程配置中心都可以

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
    gateway:
      routes:
        - id: user-service # 路由id,自定义,唯一
          uri: lb://userservice # 目标地址,lb指负载均衡,后面跟服务名称
          predicates: # 路由断言,判断路由是否符合规则
            - Path=/user/** # 以/user/ 开头就符合此路由
          filters: # 过滤器
            - AddRequestHeader=token,aabbcc

以上是给所有经过网关的且进行路由的请求,增加一个名为token的请求头参数,且值是aabbcc,这是局部设置过滤器。

如果要给所有的请求,路由转发时都设置一个过滤器,那么可以这样写:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
    gateway:
      routes:
        - id: user-service # 路由id,自定义,唯一
          uri: lb://userservice # 目标地址,lb指负载均衡,后面跟服务名称
          predicates: # 路由断言,判断路由是否符合规则
            - Path=/user/** # 以/user/ 开头就符合此路由
      default-filters:
        - AddRequestHeader=token,aabbcc

全局过滤器GlobalFilter

全局过滤器:处理一切进入网关的请求和微服务响应,上面的GatewayFilter是通过配置定义的,无法处理详细的业务逻辑,那么就需要使用这种代码形式的过滤器。

使用方法:实现GlobalFilter接口,实现方法

在这里插入图片描述

// @Order(-1) // 表示设置优先级
@Component
public class GlobalGWFilter implements GlobalFilter, Ordered {
    
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 获取请求参数
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        String token = queryParams.getFirst("token");
        if ("aabbcc".equals(token)) {
    
    
            // 链放行,交给后面的过滤器继续进行处理
            return chain.filter(exchange);
        }
        // 终止,返回响应
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
    
    	// 设置优先级,越小优先级越高
        return -1;
    }
}

设置优先级,可以使用注解,也可以实现Ordered接口,实现getOrder方法即可。

过滤器链执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter(所有路由)、GlobalFilter全局过滤器

请求转发路由前,会将当前过滤器、DefaultFilter和GlobalFilter合并到一个过滤器链中(集合中),排序后依次进行经过执行每个过滤器。底层是通过适配器模式将GlobalFilter适配为GatewayFilter类型。

  • 每个过滤器都要指定一个int类型的order值,值越小优先级越高
  • GlobalFilter通过Ordered接口,或@Order注解设置order值,自行指定
  • 路由过滤器和DefaultFilter是spring指定的,按照声明的顺序从1递增
  • 当过滤器的order值一样时,按照defaultFilter>路由过滤器>GlobalFilter顺序执行。

网关跨域问题

跨域:域名不一致就是跨域:

  • 域名不同
  • 端口不通
  • 协议不同

跨域问题的存在是:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。

解决方案:CORS

处理,在配置文件中配置:

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true # 解决options请求拦截问题
        cors-configurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站跨域
              - "*" # 表示允许所有跨域
            allowedMethods: # 允许跨域的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 允许携带cookie
            maxAge: 360000 # 跨域监测的有效期,网站一次跨域检测后,360000时间内不检查此网站的跨域

猜你喜欢

转载自blog.csdn.net/weixin_45248492/article/details/127789619