SpringCloud(6):Feign与Zuul详解

1 Feign的使用

案例接着 Hystrix案例

在前面的学习中,我们使用了Ribbon的负载均衡功能,大大简化了远程调用时的代码:

String baseUrl = "http://user-service/user/";
User user = this.restTemplate.getForObject(baseUrl + id, User.class)

需要4点:提供服务的地址http://user-service/user/ ,参数id,请求方式get,返回类型User

如果就学到这里,你可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?这就是我们接下来要学的Feign的功能了。
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

在服务调用者user-consumer中操作

1)添加依赖

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

2)修改启动类

@EnableFeignClients //开启feign功能
@SpringCloudApplication  //点进去看看,抵三个注解
public class ConsumerApplicationStarter {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplicationStarter.class);
    }
}

3)编写UserClient接口

//去eureka拉取服务,使用负载均衡来挑选
//支持hystrix,但是默认是关闭的   写法比较麻烦可以不使用
@FeignClient("user-service")
public interface UserClient {

    @GetMapping("user/{id}")
    User findUserById(@PathVariable("id") Long id);
}

这与上面的四点刚好相对应

  • 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
  • @FeignClient,声明这是一个Feign客户端,类似@Mapper注解。同时通过value属性指定服务名称
  • 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果

4)修改ConsumerController

@RestController
@RequestMapping("consumer")
public class ConsumerController {

    @Autowired
    private UserClient userClient;
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id) {
        return userClient.findUserById(id);
    }
}

RestTemplate的注册被我删除了。Feign中已经自动集成了Ribbon负载均衡,因此我们不需要自己定义RestTemplate了

补充: Feign默认也有对Hystix的集成,只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:

feign:
  hystrix:
    enabled: true  #feign  开启熔断 默认关闭
ribbon:
  ConnectionTimeout: 500 #Feign的负载均衡时长   抛出异常
  ReadTimeout: 2000 #超过两秒没读取到数据  抛出异常

配置会比较麻烦,先要修改UserClient,添加fallBack属性

@FeignClient(value = "user-service",fallback = UserClientFallback.class)
public interface UserClient {

    @GetMapping("user/{id}")
    User findUserById(@PathVariable("id") Long id);
}

添加UserClient 的实现类,实现findUserById方法

@Component
public class UserClientFallback implements UserClient {
    @Override
    public User findUserById(Long id) {
        User user = new User();
        user.setUsername("用户未知");
        return user;
    }
}

既然能自己写Hystrix那就可以不使用Feign的Hystrix了。

主要功能还是简化远程调用,其他的小功能不再赘述。

2 网关Zuul

2.1 Zuul的功能介绍

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

在这里插入图片描述

Zuul的官网

2.2 加入Zuul后的架构

在这里插入图片描述

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。

从图中能够看出Zuul具有的功能有,最基本的路由,权限校验,负载均衡,下面将一一来介绍与使用。

2.3 Zuul快速入门

创建gateway模块

1)添加依赖 pom.xml内容如下

<parent>
   <artifactId>spring-cloud-demo</artifactId>
    <groupId>com.scu</groupId>
    <version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gateway</artifactId>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
</dependencies>

2)创建启动类

@SpringBootApplication
@EnableZuulProxy  //启用zuul
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class);
    }
}

3)添加配置文件 application.yml

server:
  port: 10086   #端口号
spring:
  application:
    name: gateway  #服务名称
zuul:
  routes:
    xixi:  #路径id  随意写
      path: /user-service/**   #映射路径
      url: http://127.0.0.1:8083  #映射路径对应的url地址

启动项目后,当页面访问http://localhost:10086/user-service/user/1,之后路由地址为http://127.0.0.1:8083/user/1,即:将符合path 规则的一切请求,都代理到 url参数指定的地址,本例中,我们将 /user-service/**开头的请求,代理到http://127.0.0.1:8083

在这里插入图片描述

2.4 Zuul面向服务的路由

在刚才的路由规则中,我们把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!

1)添加eureka依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

2)修改配置文件

server:
  port: 10086
spring:
  application:
    name: gateway
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10001/eureka  #zuul也注册到eureka
  instance:
      prefer-ip-address: true
      ip-address: 127.0.0.1
zuul:
  routes:
    xixi:
      path: /user-service/**
      serviceId: user-service   #服务提供方Id   zuul自动负载均衡

注意:似乎启动类上不加@EnableDiscoveryClient也行,但我还是加了,也不影响。

2.5 Zuul简化路由配置和默认配置

如果觉得上面写的麻烦, 那么可以进行简化,如下

zuul:
  routes: #服务id: 映射路径           默认配置也是这样
    user-service: /user-service/**

在这里插入图片描述
上面这种配置也是默认的配置,所以即使我们不配置,也能访问到。那么我们就来试试访问user-consumer这个微服务

在这里插入图片描述

正常访问,实际上我们也没有自己去配置。zuul会根据服务id来帮助我们提供这种配置。但是如果你希望忽略掉部分 服务id: 映射路径 那么该怎么做呢?另外希望加路由前缀怎么做呢?

zuul:
  routes: #服务id 映射路径           默认配置也是这样
    user-service: /user-service/**
  prefix: /api   #路由前缀
  ignored-services:   #忽略掉的服务
    - consumer-service

在这里插入图片描述
注意:这里加了前缀 /api 由于忽略掉了consumer-service服务,再来访问下

在这里插入图片描述

2.6 Zuul过滤器

Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。

2.6.1 ZuulFilter

ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();

    abstract public int filterOrder();
    
    boolean shouldFilter();// 来自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

2.6.2 过滤器生命周期

在这里插入图片描述

  • 正常流程:
    • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

景非常多:

  • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
  • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
  • 服务调用时长统计:pre和post结合使用。

2.6.3 自定义过滤器的使用

我们来自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。

package com.scu.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class LoginFilter extends ZuulFilter{

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;//pre
    }

    @Override
    public int filterOrder() {
        //至少要确保拿到了参数 5 - 1
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;//返回true 使用拦截器
    }

    @Override
    public Object run() throws ZuulException {
        //获取上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        //获取request
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("access-token");
        if(StringUtils.isBlank(token)){
            //登录校验失败   拦截 默认是true
            ctx.setSendZuulResponse(false);
            //返回403状态码 禁止访问
            ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
        }
        return null;
    }
}

在这里插入图片描述
那么加上access-token参数试试,访问正常
在这里插入图片描述

2.7 Zuul的负载均衡与熔断

Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
ribbon:
  ConnectionTimeout: 500
  ReadTimeout: 2000 #500*2+2000两个加起来*2不能超过timeoutInMillisecond
#  MaxAutoRetriesNextServer: 0 #不重试

重启后页面访问,查看日志,注意到warn的那行

2019-04-09 20:46:38.014  WARN 15792 --- [io-10086-exec-1] o.s.c.n.z.f.r.s.AbstractRibbonCommand    : The Hystrix timeout of 3000ms for the command user-service is set lower than the combination of the Ribbon read and connect timeout, 6000ms.

说的是hystrix的超时时间3000ms(针对user-service服务,实际上像上面那样写是针对所有服务的超时时间都是3000ms,如果是只针对user-service服务那么需要在之前补充user-service:后面不变),小于ribbon的读和连接超时时间6000毫秒

所以将hystrix的超时时间设置为6000ms吧,关于这个时间怎么计算的,查看AbstractRibbonCommand类的下方代码

			int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout",
				IClientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.DEFAULT_READ_TIMEOUT);
			int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout",
				IClientConfigKey.Keys.ConnectTimeout, RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT);
			int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries",
				IClientConfigKey.Keys.MaxAutoRetries, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES);
			int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer",
				IClientConfigKey.Keys.MaxAutoRetriesNextServer, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER);
			ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值