Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul

目录

  • Spring-Cloud 学习笔记-(7)路由网关Zuul
  • 1、简介
  • 2、Zuul加入后的架构
  • 3、快速入门
    • 3.1、新建一个Model
      • 3.1.1、pom文件
      • 3.1.2、启动类
      • 3.1.3、application.yml
    • 3.2、Zuul的路由
      • 3.2.1、方式一
      • 3.2.2、方式二
      • 3.2.3、方式三
      • 3.2.4、方式四
      • 3.2.5、方式五
    • 3.3、Zuul的权限控制
      • 3.3.1、顶级父类ZuulFilter
      • 3.3.2、过滤器执行生命周期
      • 3.3.3、使用场景
      • 3.3.4、自定义登录过滤器
    • 3.4、Zuul的限流
      • 3.4.1、令牌桶算法
    • 3.5、Zuul的熔断和降级
    • 3.6、Zuul的高可用
Spring-Cloud 学习笔记-(7)路由网关Zuul 1、简介 Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

2、Zuul加入后的架构 Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

  1. 所有的服务启动后回想Eureka注册。
  2. Zuul作为请求的入口,先进行一顿筛选,对有权限的请求去Eureka注册中心拉去列表,获取服务实例,然后路由到制定的服务上。
  3. 服务之间调用同样也可以先经过网关,由网关获取对应实例去路由。
    最主要的功能,鉴权和限流
3、快速入门 3.1、新建一个Model Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

3.1.1、pom文件
【Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul】cloud-demo com.bigfly 1.0.0-SNAPSHOT 4.0.0com.bigfly gateway org.springframework.cloud spring-cloud-starter-netflix-zuul

3.1.2、启动类
//GateWayApplication类package com.bigfly; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableZuulProxy//开启Zuul注解 public class GateWayApplication {public static void main(String[] args) { SpringApplication.run(GateWayApplication.class); } }

3.1.3、application.yml
#端口号 server: port: 10000#应用名称 spring: application: name: gateway

3.2、Zuul的路由 3.2.1、方式一
#方式一: zuul: routes: #路由的规则 hehe: #(规则式key-value形式,key只要不重复就行) path: /user-service/**#用来表示以什么开头的路径 url: http://127.0.0.1:8771 #路由到的url

测试:
分别启动eureka-server和user-service两个服务,然后访问http://127.0.0.1:8771/api/v1/user/2
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

然后启动gateway(如果启动报错,可能是版本不兼容,把springboot版本降到2.0.6就可以了),访问http://127.0.0.1:10000/user-service/api/v1/user/2
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

这就证明了,通过上面的配置,我们把所有访问gateway的请求中,只要以ser-service开头的请求全部路由到http://127.0.0.1:8771上。
3.2.2、方式二
上面的方式存在的问题:请求路径匹配写死了。如果后期,用户服务路径修改,没办法及时调整,而且如果用户服务集群搭建,没有办法负载均衡。所以我们就应该把zuul注册到eureka注册中心。****
  1. 加依赖
    org.springframework.cloud spring-cloud-starter-netflix-eureka-client

  2. 加配置
    # 注册中心 eureka: client: service-url: defaultZone: http://127.0.0.1:8761/eureka

  3. 修改规则
    zuul: routes: #路由的规则 hehe: #(规则式key-value形式,key只要不重复就行) path: /user-service/**#用来表示以什么开头的路径 serviceId: user-service #路由到的serviceId

  4. 同样我们访问http://127.0.0.1:10000/user-service/api/v1/user/2
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

虽然表面上看是一样的,但是本质已经发生了改变,用户请求到网关后,网关根据路由规则拿到serviceId,然后根据serviceId去注册中心拉去服务列表,通过负载均衡得到最后的实例,最后调用接口。
3.2.3、方式三
zuul: routes: #路由的规则 user-service: /user-service/** #key:服务的id,value:服务的映射路径

这种方式本质就是跟方式二一样的,只是简写了而已,因为在方式二中,我们路由的id是任意的,可以叫hehe,也可以叫haha,只要不重复就行,因为我们的serviceId也是唯一的,所以干脆把serviceId当做路由的id,然后再配置一个映射路径就行了。
测试:访问http://127.0.0.1:10000/user-service/api/v1/user/2
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

3.2.4、方式四
把zuul配置全部删除。我们再访问一下http://127.0.0.1:10000/user-service/api/v1/user/2
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

我们惊奇的发现依旧可以路由成功。这是什么原因呢,什么都没有配置为什么可以成功呢,因为形如方法三的配置简直是太常见了,所以我们的zuul完全可以把所有的注册列表拉去出来一一匹配。虽然这样看来前面都是白讲了,但是我们一步步的知道了为什么,知其然,知其所以然。
3.2.5、方式五
方式四存在一个问题,就是zuul把所有的微服务都匹配了一个默认路由,但是如果有一个服务我们不想对外开放呢。我们修改一下配置
zuul: # routes: #路由的规则 #user-service: /user-service/** ignored-services:#排除哪些服务,是一个集合 - user-service - order-service

3.3、Zuul的权限控制 3.3.1、顶级父类ZuulFilter
在ZuulFilter中有四个重要的抽象方法:
public abstract class ZuulFilter implements IZuulFilter, Comparable { abstract public String filterType(); //过滤器类型abstract public int filterOrder(); //过滤器优先级boolean shouldFilter(); // 来自IZuulFilter 要不要过滤Object run() throws ZuulException; // IZuulFilter过滤的逻辑 }

  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
3.3.2、过滤器执行生命周期
这张是Zuul官网提供的请求生命周期图,清晰的表现了一个请求在各个过滤器的执行顺序。
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

  • 正常流程:
    • 请求到达首先会经过前置(Pre)过滤器,而后到达路由(Routing)类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达后置(Post)过滤器。而后返回响应。前置后置是针对路由过滤器来说的。
  • 异常流程:
    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
所有内置过滤器列表:
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

3.3.3、使用场景
场景非常多:
  • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
  • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
  • 服务调用时长统计:pre和post结合使用。
3.3.4、自定义登录过滤器
校验用户请求中有没有access_token,有代表请求有效放行。
package com.bigfly.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpUtils; /** * 登录拦截过滤器 */ //注入到Spring容器中 @Component public class LoginFilter extends ZuulFilter { /** * 过滤器类型 * @return */ @Override public String filterType() { return FilterConstants.PRE_TYPE; }/** * 过滤器优先级 * @return */ @Override public int filterOrder() { return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; }/** * 需不需要过滤 * @return */ @Override public boolean shouldFilter() { //拿到上下文对象 RequestContext ctx = RequestContext.getCurrentContext(); //拿到请求路径 String url = ctx.getRequest().getRequestURI(); //针对订单服务过滤 if(StringUtils.isNotBlank(url)&&url.startsWith("/order-service/")){ return true; } return false; }/** * * @return * @throws ZuulException */ @Override public Object run() throws ZuulException { //拿到上下文对象 RequestContext ctx = RequestContext.getCurrentContext(); //拿到请求 HttpServletRequest request = ctx.getRequest(); String token = request.getHeader("token"); if(StringUtils.isBlank(token)){ token = request.getParameter("token"); } //拦截token为空的请求 if(StringUtils.isBlank(token)){ ctx.setSendZuulResponse(false); //是否放行 ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); //设置返回状态码 } return null; } }

测试:访问http://localhost:10000/order-service/api/v1/order/2
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

我们加上参数token再测试一下
http://localhost:10000/order-service/api/v1/order/2?token=123asd
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

注意:在ZuulProperties中配置了
private Set sensitiveHeaders = new LinkedHashSet(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));

为了安全起见,Zuul网关会过滤到请求头中的"Cookie", "Set-Cookie", "Authorization"。如果想不被过滤可加配置
zuul: routes: #路由的规则 user-service: /user-service/** #处理http请求为空问题 sensitiveHeaders:

3.4、Zuul的限流 如果服务器只能1秒最多能负担100个请求,如果一次性来了200个请求,这样服务器承担不了。所以我们就会对接口进行评估,评估过后然后对接口进行限流。我们使用谷歌的guava框架,SpringCloud已经集成了guava。
Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

3.4.1、令牌桶算法
每秒放一定量的令牌,然后每次请求会从桶中拿一个令牌,拿到令牌的请求放行,没有拿到令牌的请求就拦下来
3.4.2、代码编写
package com.bigfly.filter; import com.google.common.util.concurrent.RateLimiter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.http.HttpStatus; /** * 针对订单接口的限流 */ public class OrderRateLimiterFilter extends ZuulFilter { /** * 创建令牌桶 每一秒创建多少令牌 */ private static final RateLimiter RATE_LIMITER = RateLimiter.create(100); @Override public String filterType() { return FilterConstants.PRE_TYPE; }@Override public int filterOrder() { return FilterConstants.SERVLET_DETECTION_FILTER_ORDER -1; }@Override public boolean shouldFilter() { //拿到请求路径 String url = RequestContext.getCurrentContext().getRequest().getRequestURI(); //针对订单服务过滤 if(StringUtils.isNotBlank(url)&&url.startsWith("/order-service/")){ return true; } return false; }@Override public Object run() throws ZuulException { //用非阻塞的方式拿令牌 boolean flag = RATE_LIMITER.tryAcquire(); if(!false){ RequestContext ctx = RequestContext.getCurrentContext(); ctx.setSendZuulResponse(false); //是否放行 ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value()); //设置返回状态码 } return null; }}

这里就简单说一下,不做测试了。
3.5、Zuul的熔断和降级 Spring-Cloud|Spring-Cloud 学习笔记-(7)路由网关Zuul
文章图片

Zuul里面以及涵盖了Hystrix和Ribbon,并且和feign不同的是Zuul 默认Hystrix已经开启。所以这里我们只要配一下Hystrix和ribbon的超时时长就行了。
hystrix: command: default: execution.isolation.thread.timeoutInMilliseconds: 3000 ribbon: ConnectTimeout: 250 # Ribbon的连接超时时间 ReadTimeout: 1000 # Ribbon的数据读取超时时间 OkToRetryOnAllOperations: true # 是否对所有操作都进行重试 MaxAutoRetriesNextServer: 1 # 切换实例的重试次数 MaxAutoRetries: 0 # 对当前实例的重试次数

我们要求Ribbon的超时时长必须小于Hystrix的超时时长,这样就不会导致ribbon还没有完成重试,Hystrix就熔断了。
//AbstractRibbonCommand类//真正的ribbon的超时时长 ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);

3.6、Zuul的高可用 Zuul的高可用其实就是启动多台Zuul服务器就行了,但是这样Zuul高可用了,用户不知道要访问哪一个Zuul,所以在Zuul外面还要套一层nginx,通过nginx实现反向代理和负载均衡。但是这样Zuul没问题了nginx如何实现高可用呢,一般大型的电商网站都会用ip漂移,一个域名会绑定多个ip,多个nignx,然后网络运营商会根据用户的请求分配到最近的ngixn上,如果只有一个ip,也可以用主从nginx,(nginx+keeplive+LVS)一旦主nginx挂了,从nignx就会顶上。
转载于:https://www.cnblogs.com/bigfly277/p/10171814.html

    推荐阅读