目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Zuul

诞生背景

 微服务架构将后端拆解成许多个单独的应用:看似清晰的服务拆分,实则杂乱无章,完成一个业务逻辑,需要到不同主机和不同端口上面调取接口。于是一个面向服务治理、服务编排的㢟出现了——微服务网关。
网关示意图

 Zuul 是从设备和网站到后端应用程序所有请求的前门,为内部服务提供可配置的对外 URL 到服务的映射关系,基于 JVM 的后端路由器,其具备以下功能:

  • 统一接入:智能路由、AB 测试、灰度测试、负载均衡、容灾处理、日志埋点
  • 流量监控:限流处理、服务降级
  • 安全防护:鉴权处理、监控、机器网络隔离

 虽然 Zuul2.x 采用 Netty 有较大的性能提升,单改动较大,考虑到稳定性 Spring Cloud Finchley 继续沿用 Netflix Zuul 1.x 版本,另外由于 Spring Cloud Gateway 已经孵化成功,相较于 Zuul 在功能以及性能上都有明显提升,Pivotal 公司正在走一条“去 Netflix 化”的路线。

主流开源网关概览

主流开源网关对比

入门案例

Maven 依赖

1<dependency>
2            <groupId>org.springframework.cloud</groupId>
3            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
4        </dependency>
5        <dependency>
6            <groupId>org.springframework.cloud</groupId>
7            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
8        </dependency>

添加配置

 1#Zuul对外端口号
 2server:
 3  port: 9000
 4
 5#服务的名称
 6spring:
 7  application:
 8    name: api-gateway
 9  cloud:
10    client:
11      ipAddress: 192.168.31.230
12
13#指定注册中心地址
14eureka:
15  client:
16    serviceUrl:
17      defaultZone: http://localhost:8761/eureka/
18  instance:
19    prefer-ip-address: true #调试Eureka管控台实例的显示格式
20    instance-id: ${spring.cloud.client.ipAddress}:${server.port}:${spring.application.name}

启动类添加注解 @EnableZuulProxy

1@SpringBootApplication
2@EnableZuulProxy
3public class ApiGatewayApplication {
4
5    public static void main(String[] args) {
6        SpringApplication.run(ApiGatewayApplication.class, args);
7    }
8}

访问 Eureka 管控台 http://localhost:8761/
服务发现网关

#Zuul 内部实现细节
Zuul 内部实现细节

Zuul 原理

Zuul 流程

  1. 前置过滤器,在这个过程中可以自定义一些过滤器扩扩展功能(日志埋点、限流、鉴权等…)。
  2. 智能路由,将请求路由到对应的服务。
  3. 如果出错进入错误过滤器。
  4. 最终都会经过后置过滤器进行最后的处理,然后响应给前端请求。

过滤器执行顺序

 过滤器的 order 值越小,越优先执行。
 通过 RequestContext 对象共享上下文内容。

网关默认访问规则

改写请求,通过网关统一接入

1http://192.168.31.230:8781/api/v1/order/save?product_id=4&user_id=2
2http://192.168.31.230:9000/order-service/api/v1/order/save?product_id=4&user_id=2    #次请求经过网关
3
4http://192.168.31.230:8771/api/v1/product/list
5http://192.168.31.230:9000/product-service/api/v1/product/list    #次请求经过网关

自定义路由规则

访问路径

http://192.168.31.230:9000/apigateway/product/api/v1/product/list
http://192.168.31.230:9000/apigateway/orderapi/v1/order/save?product_id=4&user_id=2

 1#自定义路由映射
 2zuul:
 3  routes:
 4 #解决路由映射重复覆盖问题
 5    order-service: /apigateway/order/**
 6    product-service: /apigateway/product/**
 7  #统一入口为上面的配置,其他入口忽略
 8  ignored-patterns: /*-service/**
 9  #忽略整个服务,对外提供接口
10  #gnored-services: product-service

处理 http 请求头传递问题

 默认 "Cookie"、"Set-Cookie"、"Authorization" 这三个请求头会被 Zuul 过滤掉,并不会传递到后端,可以通过相关配置解决此问题:

1#自定义路由映射
2zuul:
3  routes:
4  #解决http请求头经过Zuul时不传递的问题
5  sensitive-headers:

自定义 Zuul 过滤器实现登录鉴权

新建类并实现 ZuulFilter,然后实现方法,最后在加上 @Component 注解

 1import com.netflix.zuul.ZuulFilter;
 2import com.netflix.zuul.context.RequestContext;
 3import com.netflix.zuul.exception.ZuulException;
 4import org.apache.commons.lang.StringUtils;
 5import org.apache.http.HttpStatus;
 6import org.springframework.stereotype.Component;
 7import javax.servlet.http.HttpServletRequest;
 8import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
 9
10@Component
11public class LoginFilter extends ZuulFilter {
12    /**
13     * 设置顾虑器类型:前置过滤器
14     * @return
15     */
16    @Override
17    public String filterType() {
18        return PRE_TYPE;
19    }
20
21    /**
22     * 过滤器执行优先级:数字越小,越优先执行。
23     * @return
24     */
25    @Override
26    public int filterOrder() {
27        return 4;
28    }
29    /**
30     * 过滤器是否生效
31     * @return
32     */
33    @Override
34    public boolean shouldFilter() {
35        RequestContext requestContext = RequestContext.getCurrentContext();
36        HttpServletRequest request = requestContext.getRequest();
37        System.out.println(request.getRequestURI());    // /api-gateway/order/api/v1/order/save
38        System.out.println(request.getRequestURL());    // http://192.168.31.230:9000/api-gateway/order/api/v1/order/save
39	//ACL  访问控制列表,一般都是定期从redis中拉取过来动态填写。
40	//针对指定的服务URI进行拦截,而不是对所有URI进行拦截,这里可以用if进行判断。
41        if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())){
42            return true;
43        }
44	//if(){ }   ......
45        return false;
46    }
47
48    /**
49     * 业务逻辑
50     * @return
51     * @throws ZuulException
52     */
53    @Override
54    public Object run() throws ZuulException {
55        //JWT  查看是否有token
56        RequestContext requestContext = RequestContext.getCurrentContext();
57        HttpServletRequest request = requestContext.getRequest();
58
59        //获取token
60        String token = request.getHeader("token");
61        if(StringUtils.isBlank(token)){//如果从head中获取为空,就从参数中获取
62            token = request.getParameter("token");
63        }
64
65        //登录校验逻辑,根据公司情况自定义
66        if(StringUtils.isBlank(token)) { //如果为token为空则返回错误响应
67            requestContext.setSendZuulResponse(false);
68            requestContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
69        }
70        return null;
71    }
72}

不带 token 参数的请求 http://192.168.31.230:9000/apigateway/order/api/v1/order/save?user_id=2&product_id=2
401
带 token 参数的请求
http://192.168.31.230:9000/apigateway/order/api/v1/order/save?user_id=2&product_id=2&token=asdasd
带 token 参数的请求

限流

 构建一个自我修复型系统一直是各大企业进行架构设计的难点所在,在 Hystrix 中,我们可以通过熔断器来实现,通过触发某个阈值对异常流量进行降级处理。除此之外,也可以对异常流量进行提前的预防措施,比如:异步队列的削峰、流量排队、限流、分流等…

限流算法

  • 漏桶(Leaky Bucket)
     请求数据涌来,在漏桶的作用下流量被整形,不能满足要求的部分请求被削减掉(收集到一个队列中,做流量排队),漏铜算法能够强制限定流量速率。
    漏桶算法
  • 令牌桶(Token Bucket)
     桶里面存放令牌,而令牌又是以一个恒定的速率被加入桶内,可以积压,可以溢出。当数据涌来,量化请求用于获取令牌,如果获取到令牌则放行,同时桶内丢掉这个令牌,如果不能去得到令牌,请求则被丢弃。由于令牌桶内可以存在一定数量的令牌,那么就可能存在一定程度的流量突发,这也是决定漏桶算法与令牌桶算法使用与不同应用场景的主要原因。
    令牌桶算法

谷歌 guava 框架
基于令牌桶算法
Guava
在网关中编写过滤器:集群中需要结合 Redis 进行限流,令牌可以存放在公共缓存中供集群中所有节点使用。

 1import com.google.common.util.concurrent.RateLimiter;
 2
 3import com.netflix.zuul.ZuulFilter;
 4import com.netflix.zuul.context.RequestContext;
 5import com.netflix.zuul.exception.ZuulException;
 6import org.springframework.http.HttpStatus;
 7import org.springframework.stereotype.Component;
 8
 9import javax.servlet.http.HttpServletRequest;
10
11import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
12
13/**
14 * 订单限流
15 */
16@Component
17public class OrderRateLimiterFilter extends ZuulFilter {
18    
19 
20    //每秒产生1000个令牌
21  令牌的具体数量需要根据业务进行测试而定。
22    private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);
23
24    @Override
25    public String filterType() {
26        return PRE_TYPE;
27    }
28
29    @Override
30    public int filterOrder() {
31        return -4;
32    }
33    
34    @Override
35    public boolean shouldFilter() {
36        
37        RequestContext requestContext = RequestContext.getCurrentContext();
38        HttpServletRequest request = requestContext.getRequest();
39
40        //只对订单接口限流
41        if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())){
42            return true;
43        }
44        
45        return false;
46    }
47
48    @Override
49    public Object run() throws ZuulException {
50        RequestContext requestContext = RequestContext.getCurrentContext();
51        if(!RATE_LIMITER.tryAcquire()){ //如果没有获得令牌,则返回错误状态码
52            requestContext.setSendZuulResponse(false);
53            requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
54        }
55        return null;
56    }
57}

网关的限流与高可用

  • 通常情况下限流需要反响代理 Nginx 结合使用
  • Zuul 的高可用比较简单,启动多个节点即可。
  • Nginx 的高可用可以通过 nginx+lvs+keepalive 实现。


作者:Soulboy