• SpringCloud(二)


    6.OpenFeign

    OpenFeign是一个声明式的http客户端,是SpringCloud在Netflix开源的Feign基础上改造而来。其作用就是帮助我们简单的实现http请求的发送。

    6.1.使用步骤

    OpenFegin可以被SpringBoot自动装配,使用起来非常简单,具体使用步骤如下:

    1.引入依赖

    在微服务的pom.xml文件中引入如下所示的依赖:

    1. <dependency>
    2.    <groupId>org.springframework.cloudgroupId>
    3.    <artifactId>spring-cloud-starter-openfeignartifactId>
    4. dependency>
    5. <dependency>
    6.    <groupId>org.springframework.cloudgroupId>
    7.    <artifactId>spring-cloud-starter-loadbalancerartifactId>
    8. dependency>

    2.添加注解

    在微服务的启动类上面添加注解,来开启OpenFeign的功能:

    3.编写Feign的客户端

    在order-service中新建一个接口,内容如下:

    1. @FeignClient("服务名字")
    2. public interface UserClient {
    3.    @GetMapping("/user/{id}")
    4.    User findById(@PathVariable("id") Long id);
    5. }

    这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

    • 服务名称:userservice

    • 请求方式:GET

    • 请求路径:/user/{id}

    • 请求参数:Long id

    • 返回值类型:User

    这样,OpenFeign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。

    4.使用OpenFeign,

    下面是使用OpenFeign实现远程调用的代码。另外,OpenFeign的底层是基于LoadBlancer组件的,所以会自动帮助我们进行负载均衡。

    1. //从容器中获取OpenFeign客户端
    2. @Autowired
    3. private UserClient userClient;
    4. public User queryOrderById(Long orderId){
    5. //通过OpenFeign发起Http请求,进行远程调用
    6. User user = userClient.findById(order.getUserId());
    7. //返回
    8. return user;
    9. }

    通过以上OpenFeign的使用,就可以简化之前的代码

    1. @Autowired
    2. private DiscoveryClient discoveryClient;
    3. @Autowired
    4. private RestTemplate restTemplate;
    5. public User queryOrderById(Long orderId){
    6. //根据服务名称获取服务实例
    7. List instances = discoveryClient.getInstances("服务名称");
    8. //负载均衡,挑选一个实例
    9. ServiceInstance instance = instance.get(RandomUtil.randomInt.size());
    10. //获取实例的IP和端口
    11. URI uri = instance.getUri();
    12. //根据IP和端口号使用RestTemplate进行远程访问
    13. ResponseEntity<实体类> response = restTemplate.exchange(
    14. String url,//请求路径
    15. HttpMethod method,//请求方式
    16. HttpEntity requestEntity,//请求实体,可以为null
    17. Class responseType,//返回值类型
    18. Map uriVariables//请求参数
    19. )
    20. //解析响应
    21. if(response.getStatusCode().is2xxSuccessful()){
    22. //查询失败,直接结束
    23. return;
    24. }
    25. 返回值类型 data = response.getBody();
    26. if(Object.isEmpty(data)){
    27. return;
    28. }
    29. }

    6.2.连接池

    Feign 底层发起 Http 请求,依赖于其它的框架。其底层客户端实现包括:

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

    • Apache HttpClient :支持连接池

    • OKHttp:支持连接池

    所以说,可以通过使用连接池代替默认的 URLConnection 来提高 OpenFeign 的性能。这里我们用Apache 的 HttpClient 来演示。

    第一步:引入依赖

    在微服务的pom文件中引入Apache的HttpClient依赖

    1. <dependency>
    2.    <groupId>io.github.openfeigngroupId>
    3.    <artifactId>feign-httpclientartifactId>
    4. dependency>

    第二步:配置连接池

    在微服务的配置文件application.yml中添加配置

    1. feign:
    2. client:
    3.   config:
    4.     default: # default全局的配置
    5.       loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
    6. httpclient:
    7.   enabled: true # 开启feign对HttpClient的支持
    8.   max-connections: 200 # 最大的连接数
    9.   max-connections-per-route: 50 # 每个路径的最大连接数

    接下来,在FeignClientFactoryBean中的loadBalance方法中打断点:

    Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient:

    6.3.最佳实践

    所谓最近实践,就是使用过程中总结的经验,最好的一种使用方式。仔细观察OpenFeign,可以发现客户端与服务提供者的Controller层的代码非常相似。

    feign客户端

    1. @FeginClient("userservice")
    2. public interface UserClient{
    3. @GetMapping("/user/{id}")
    4. User queryById(@PathVariable("id") Long id);
    5. }

    UserController:

    1. @GetMapping("/user/{id}")
    2. public User queryById(@PathVariable("id") Long id){
    3. return userService.queryBuId(id);
    4. }

    可以通过抽取的方式避免重复编码。企业当中一般有如下两种抽取思路。在抽取完成之后,就可以在微服务中的pom文件里引入依赖坐标,避免重复编码。

    • 思路1:抽取到微服务之外的公共module,再让微服务。抽取更加简单,工程结构也比较清晰,但缺点是整个项目耦合度偏高。

    • 思路2:每个微服务自己抽取一个module。抽取相对麻烦,工程结构相对更复杂,但服务之间耦合度降低。

    6.4.日志配置

    OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志,所以要想书输出OpenFeign的日志,要保证的第一个条件就是在application.yml配置文件中将FeignClient所在包的日志级别设置为DEBUG。OpenFeign本身的日志级别有四级,默认的日志级别是NONE,所以默认我们看不到请求日志。

    • NONE:不记录任何日志信息,这是默认值。

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

    • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

    • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

    要想改变 OpenFeign 的默认日志级别,让日志生效,我们需要声明一个类型为 Logger.Level 的 Bean,在其中定义日志级别。

    1. public class DefaultFeignConfig {
    2. @Bean
    3. public Logger.Level feignLogLevel(){
    4. return Logger.Level.FULL;
    5. }
    6. }

    但是,上面这个类并非配置类,要想让这个bean生效,有两种方式:

    局部生效:在某个FeignClient中配置,只对当前FeignClient生效

    @FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)

    全局生效:在启动类的@EnableFeignClients注解中配置,针对微服务中的所有FeignClient生效。

    @EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

    在实际应用中,我们可以在一个类似于工具类的微服务中,定义上述的DefaultFeignConfig。然后哪个微服务需要OpenFeign的日志,就在pom.xml文件里引入依赖坐标,并在自己的FeignClient注解或启动类上用上述二种方式使其生效即可。

    一般只在调试的时候开启OpenFeign的日志,其它时候一般不建议开启,毕竟对性能还是会产生一定的影响。

    7.Gateway

    Gateway是一个在分布式系统中作为入口的服务器,用于接收所有外部请求,并将这些请求路由到相应的服务器节点上。

    7.1.Gateway的核心功能

    在 SpringCloud 中网关的实现包括两种,分别是 Gateway、Zuul。Zuul 由 Netflix 出品,是基于 Servlet 的实现,属于阻塞式编程。而 Gateway 由 Spring 官方出品,基于 WebFlux 实现,属于响应式编程的实现,具备更好的性能。Gateway 的核心功能如下:

    • 权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

    • 路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

    • 限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

    7.2.Gateway使用步骤

    第一步:创建一个SpringBoot的项目,并引入Gateway的依赖

    1. <!--网关-->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-gateway</artifactId>
    5. </dependency>
    6. <!--nacos服务发现依赖-->
    7. <dependency>
    8. <groupId>com.alibaba.cloud</groupId>
    9. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    10. </dependency>

    第二步:在配置文件中编写基础配置和路由规则

    1. server:
    2. port: 10010 # 网关端口
    3. spring:
    4. application:
    5. name: gateway # 服务名称
    6. cloud:
    7. nacos:
    8. server-addr: localhost:8848 # nacos地址
    9. gateway:
    10. routes: #网关路由配置
    11. - id: user-service #路由id,自定义,只要唯一即可
    12. # uri: http://127.0.0.1:8081 #路由的目标地址 http就是固定地址
    13. uri: lb://userservice #路由的目标地址 lb就是负载均衡,后面跟服务名称
    14. predicates: #路由断言,也就是判断请求是否符合路由规则的条件
    15. - Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求

    第三步:启动项目,符合 Path 规则的一切请求,都会代理到 uri 参数指定的地址。

    7.3.Gateway断言

    在 Gateway 中,如果客户端发送的请求满足了断言的条件,则映射到指定的路由器,就能转发到指定的服务上进行处理。断言配置的示例如下,配置了两个路由规则,有一个 predicates 断言配置,当请求 url 中包含 api/thirdparty,就匹配到了第一个路由 route_thirdparty。

    1. server:
    2. port: 10010 # 网关端口
    3. spring:
    4. application:
    5. name: gateway # 服务名称
    6. cloud:
    7. nacos:
    8. server-addr: localhost:8848 # nacos地址
    9. gateway:
    10. routes: #网关路由配置
    11. - id: user-service #路由id,自定义,只要唯一即可
    12. # uri: http://127.0.0.1:8081 #路由的目标地址 http就是固定地址
    13. uri: lb://userservice #路由的目标地址 lb就是负载均衡,后面跟服务名称
    14. predicates: #路由断言,也就是判断请求是否符合路由规则的条件
    15. - Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
    16. - id: user-service #路由id,自定义,只要唯一即可
    17. # uri: http://127.0.0.1:8081 #路由的目标地址 http就是固定地址
    18. uri: lb://userservice #负载均衡,将请求转发到注册中心注册的passiava-thirdarty服务
    19. predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
    20. - Path=/api/question/** #路径匹配,如果请求路径包含api/question,则符合

    常见的断言规则如下: 

    7.4.Gateway的过滤器

    GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。Spring提供了31种不同的路由过滤器工厂。但是,每一种过滤器的作用都是固定的,如果我们希望拦截请求,做自己的业务逻辑可以自定义全局过滤器。

    下面Spring提供的几种常见过滤器:

    名称

    说明

    AddRequestHeader

    给当前请求添加一个请求头

    RemoveRequestHeader

    移除请求中的一个请求头

    AddResponseHeader

    给响应结果中添加一个响应头

    RemoveResponseHeader

    从响应结果中移除一个响应头

    RequestRateLimiter

    限制请求的流量

    下面是使用过滤器的一个例子:

    1. spring:
    2. cloud:
    3. gateway:
    4. routes:
    5. - id: user-service
    6. uri: lb://userservice
    7. predicates:
    8. - Path=/user/**
    9. filters: #过滤器
    10. #给所有进入userservice的请求添加一个请求头
    11. - AddRequestHeader=Truth, Itcast is freaking awesome!

    如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

    1. spring:
    2. cloud:
    3. gateway:
    4. routes:
    5. - id: user-service
    6. uri: lb://userservice
    7. predicates:
    8. - Path=/user/**
    9. default-filters: # 默认过滤项
    10. #对进入Gateway的所有请求添加请求头
    11. - AddRequestHeader=Truth, Itcast is freaking awesome!

    不同过滤器的执行顺序

    请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链中,排序后依次执行每个过滤器。排序的规则如下:

    • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
    • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
    • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
    • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

    7.5.跨域问题

    1.什么是跨域问题

    浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题,主要包括:

    解决方案:可以通过CORS进行解决,这个以前应该学习过,这里不再赘述了。不知道的话可以查看:跨域资源共享 CORS 详解 - 阮一峰的网络日志

    注意:这里请求的发起者一般是前端项目,浏览器本身发出的请求不会发生跨域问题,就像idea中运行一个web项目,从浏览器中去访问此项目,并不会出现跨域问题。

    2.模拟跨域问题

    找到课前资料的页面文件:

    放入tomcat或者nginx这样的web服务器中,启动并访问。

    可以在浏览器控制台看到下面的错误:

    从localhost:8090访问localhost:10010,端口不同,显然是跨域的请求。

    3.解决跨域问题

    在gateway服务的application.yml文件中,添加下面的配置:

    1. spring:
    2. cloud:
    3.   gateway:
    4.      # 。。。
    5.     globalcors: # 全局的跨域处理
    6.       add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
    7.       corsConfigurations:
    8.         '[/**]':
    9.           allowedOrigins: # 允许哪些网站的跨域请求
    10.             - "http://localhost:8090"
    11.           allowedMethods: # 允许的跨域ajax的请求方式
    12.             - "GET"
    13.             - "POST"
    14.             - "DELETE"
    15.             - "PUT"
    16.             - "OPTIONS"
    17.           allowedHeaders: "*" # 允许在请求中携带的头信息
    18.           allowCredentials: true # 是否允许携带cookie
    19.           maxAge: 360000 # 这次跨域检测的有效期
  • 相关阅读:
    使用doctest代码测试和Sphinx自动生成文档
    统计单词
    Android NDK CMakeLists.txt 说明
    构建系列之webpack窥探上
    sql语句怎样实现求在列中选择某些相同名称的行,对这些相同名称行的数据进行累加.
    SpringCloud搭建微服务之Gateway网关
    【无标题】Java 函数式接口
    Python编程必备:掌握列表遍历的6种神级技巧!
    第二章 Vue基础语法
    如何搭建专属的物联网私有云?需要考虑哪些因素?
  • 原文地址:https://blog.csdn.net/m0_59749089/article/details/132710478