微服务(Feign,Gateway)

522 阅读6分钟

SpringCloud

Feign

Feign替代RestTemplate(快速入门)

存在问题

  • 代码可读性差,编程体验不统一
  • 参数复杂,URL难以维护

Feign是声明式客户端,帮助我们优雅的完成请求发送

步骤1:引入依赖

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

步骤2:开启Feign

在启动类上加 @EnableFeignClients

@SpringBootApplication
@EnableFeignClients //标注这个
public class ClNacosApplication {
    public static void main(String[] args) {
        SpringApplication.run(ClNacosApplication.class, args);
    }
}

步骤3:新建 feign 客户端

image.png

  • user1service1 是服务名
@FeignClient("user1service1")
public interface UserClient {
    
    //根据 ID 查询
    @GetMapping("/user/{userId}")
    User findById(@PathVariable Integer userId);
}

如果报以下错误:

由于SpringCloud Feign在Hoxton.M2 RELEASED版本之后不再使用Ribbon
而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错

增加依赖

<!-- 去掉ribbon -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 新增 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

自定义Feign的配置

  1. 修改日志级别
    1. feign.Logger.Level(修改日志级别)有以下四种
    2. NONE:不打印日志
    3. BASIC:发起请求的时候,记录开始,结束,耗时时长
    4. HEADERS:包含以上,带上请求头响应头信息
    5. FULL:包含以上,并且带上请求体和响应体
  2. 响应结果的解析器
    1. feign.codec.Decoder(响应结果的解析器)
    2. 说明:http远程调用的结果做解析,比如json转java对象
  3. 请求参数编码
    1. feign.codec.Encoder(请求参数编码)
    2. 说明:将请求参数编码,便于通过http请求发送
  4. 支持的注解格式
    1. feign. Contract(支持的注解格式)
    2. 说明:默认是SpringMVC的注解
  5. 失败重试机制
    1. feign. Retryer(重试机制)
    2. 说明:请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

第一种修改方式:配置文件

spring:
  application:
    name: order1service
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8845
      config:
        file-extension: yaml
        # 下面都需要配置
        enabled: false
logging:
  level:
    com.orderservice: debug
feign:
  client:
    config:
      default:
        loggerLevel: full #这是全局,写服务名就是局部

java代码实现

首先

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level loglevel() {
        return Logger.Level.FULL; // 这是日志级别
    }
}

如果是全局,就要在启动类上面加载

@SpringBootApplication
// 在下面
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
@MapperScan("com.orderservice.dao")
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
    
}

如果是局部就要在服务上面加载

@FeignClient("user1service1")
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
public interface UserClient {

配置文件中增加

spring:
  application:
    name: order1service
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8845
      config:
        file-extension: yaml
        # 加入以下这些
        enabled: false #禁用
logging:
  level: # 下面是哪些包需要打印日志
    com.orderservice: debug

Feign 性能优化

Feign底层实现:

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

优化主要包括:

  • 使用连接池代替默认的URLConnection
  • 日志级别,最好用basic或none

步骤1:引入依赖

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

步骤2:打开连接池

feign:
  httpclient:
    enabled: true # 支持httpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径最大连接数

Feign 最佳实践

方案1:继承实现

给消费者的FeignClient和提供者的controller定义统一的父接口作为标准

image.png

缺点:

  • 官方不推荐,共享接口,因为紧耦合

方案2:引入依赖

方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

image.png

实现方式二

  1. 首先创建module,命令为 feign-api,然后引入feign的starter依赖(openfeign)
  2. 把clients,config,domain复制过去
// 需要指定以下客户端,因为不在一个项目会报错
@EnableFeignClients(clients = {UserClient.class})
// 方式二扫包
@EnableFeignClients(basePackages = "com.feign.clients")
  1. 其他包抽取、调用即可

Feign 传参

Feign客户端必须加@PathVariable 或者 @RequestParam ,否则报错405

Gateway 网关

统一网关

微服务都要访问数据库完成自己业务

微服务都要去nacos中去注册,配置的管理

如果有相互调用,可以用feign实现

外部怎么办,直接调用不太安全,就要使用网关了

网关功能:

  • 身份认证和权限校验
  • 服务路由、负载均衡
  • 请求限流

Zuul是基于Servlet的实现,属于阻塞式编程。

而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

网关搭建(基本路由配置)

  1. 创建一个新的Module叫 gateway,引入两个依赖
<!--网关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency><!--nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--负载均衡, 因为新版本不用ribbon了-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
  1. 编写基本路由配置
application:
  name: gateway # 服务名称
cloud:
  nacos:
    server-addr: localhost:80 # nacos地址
  gateway:
    routes: # 网关路由配置
      - id: user-service # 路由id,自定义,只要唯一即可
        # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
        uri: lb://user1service1 # 路由的目标地址 lb就是负载均衡,后面跟服务名称
        predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
          - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
  1. 执行过程

image.png

路由断言工厂

读取用户配置的路由规则,不通过就404

image.png

predicates:
  - Path=/order/**
  - Before=2022-04-15T15:14:33.133+08:00[Asia/Shanghai]

过滤器

GatewayFilter 可以对网关请求和微服务的响应做处理

image.png 添加请求头

image.png

spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:80 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: order-service
          uri: lb://order1service
          predicates:
            - Path=/order/**
            - Before=2022-04-15T15:14:33.133+08:00[Asia/Shanghai]
          # 配置过滤--添加请求头 AddRequestHeader=名称,内容
          filters:
            - AddRequestHeader=Truth,Itcast is freaking aowsome!

添加默认请求,对所有路由都生效

default-filters:
  - AddRequestHeader=Truth,Itcast is freaking aowsome!

全局过滤器(GlobalFilter)

定义在 Gateway 服务类中

全局过滤器,是过滤一切进入路由的请求和响应

用java代码实现,可以做逻辑处理

实现未登录禁止访问(简介的)

  • @Order(-1) 表示优先级,越小越高
@Component
@Order(-1)
public class AuthorizeFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        // 2. 获取 authorization
        String s = queryParams.getFirst("authorization");
        if("admin".equals(s)){
            // 放行
            return chain.filter(exchange);
        }
        // 设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); // 401
        return exchange.getResponse().setComplete();
    }
}

过滤器的执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

会将当前路由过滤器和DefaultFilter,GlobalFilter合并到一个过滤器链(集合)中 排序后会依次执行每个过滤器

globalfilter的优先级由我们自己指定

每一个过滤器都必须指定一个Order,Order值越小,优先级越高

defaultFilter的优先级由spring指定,默认按照顺序依次递增

当过滤器值一样,会按照defaultfilter > 当前过滤器 > globalfilter依次执行

跨域问题处理(解决CORS)

跨域:域名不一致就是跨域,主要包括

  • 域名后缀不同
  • 端口不同

浏览器禁止请求于服务端发生跨域的Ajax请求。被浏览器拦截问题,添加到Gateway

spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]': # 拦截一切请求
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息,*表示全部
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期,浏览器不再发起询问,在有效期内

注意事项:

nacos 服务中必须和服务的分组在一块,否则 503,无映射