JavaEE 企业级分布式高级架构师(十三)微服务框架 SpringCloud (H 版)(4)

微服务网关Zuul

简介

网关简介

  • 网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、路由、监控等功能。

官网wiki

  • 地址:https://github.com/Netflix/zuul/wiki

在这里插入图片描述

  • 原文】Zuul is the front door for all requests from devices and web sites(设备和 web 站点) to the backend of the Netflix streaming application(Netflix 流应用后端). As an edge service application(边界服务应用), Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups(亚马逊自动缩放组,亚马逊的一种云计算方式)as appropriate(视情况而定, 酌情).
  • 翻译】ZUUL 是从设备和 web 站点到 Netflix 流应用后端的所有请求的前门。作为边界服务应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。它还具有根据情况将请求路由到多个 Amazon Auto Scaling Groups(亚马逊自动缩放组,亚马逊的一种云计算方式)的能力。

综合说明

  • Zuul 主要提供了对请求的路由与过滤功能。
    • 路由:将外部请求转发到具体的微服务主机,是外部访问微服务系统的统一入口。
    • 过滤:对外部请求进行干预等。
  • 将 Zuul 放到整个系统架构中,其作用是这样的:服务提供者是消费者通过 EurekaServer 进行访问的,即相当于 EurekaServer 是服务提供者的统一入口。那么服务消费者很多,用户怎样访问这些消费者工程呢?当然可以像之前那样直接访问这些工程。但这种方式没有统一的消费者工程调用入口,不便于访问与管理,而 Zuul 就是这样的一个对于消费者的统一入口。这点,从 Spring 官网 http://spring.io 最下面的图中可以体现出来。

在这里插入图片描述

  • 将官方的架构图再进一步抽象,就变为了下图:

在这里插入图片描述

基本环境搭建

创建消费者工程 05-consumer-zuul-8080

  • 复制 03-consumer-feign-8080 工程,重命名为 05-consumer-zuul-8080,并在此基础上修改。利用该工程启动三个消费者工程,端口号分别是 8080、8081、8082,接口稍微修改一下:
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
    @Autowired
    private DepartService departService;
    @Value("${server.port}")
    private int port;
	// ...略
    @GetMapping("/get/{id}")
    public Depart getHandle(@PathVariable("id") int id) {
        Depart depart = departService.getDepartById(id);
        depart.setName(depart.getName() + " " + port);
        return depart;
    }
}
  • 依次启动三个消费者工程,和任意一个提供者工程。

创建服务网关 00-zuul-server-9000

  • 复制 00-hystrix-turbine-8888 工程,重命名为 00-zuul-server-9000,并在此基础上修改。
  • 依赖:
<!-- zuul的依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
  • 修改启动类,开启 zuul 代理模式:
@EnableZuulProxy
@SpringBootApplication
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
}
  • application.yml 配置文件:
server:
  port: 9000

spring:
  application:
    name: msc-zuul-server
# eureka client配置
eureka:
  client:
    service-url:
      defaultZone: http://eureka8100.com:8100/eureka,http://eureka8200.com:8200/eureka,http://eureka8300.com:8300/eureka
  • 启动 zuul 网关服务,并访问地址 http://localhost:9000/msc-consumer-depart/consumer/depart/get/1,查看效果,这样就实现了最基本的 zuul 网关访问。

在这里插入图片描述

配置Zuul

路由策略配置

  • 前面的访问方式,需要将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略,可以在配置文件中添加如下配置:
# zuul相关配置
zuul:
  routes:
    # 指定微服务的路由规则
    # /** : 匹配任意多级路径;/* : 匹配一级路径;/?:匹配一级路径,但该路径仅能包含一个字符
    msc-consumer-depart: /depart/**
  • 这样就可以通过地址:http://localhost:9000/depart/consumer/depart/get/1 访问消费者服务了。

路由前缀

  • 在配置路由策略时,可以为路由路径配置一个统一的前辍,以便为请求归类。在前面的配置文件中增加如下配置:

在这里插入图片描述

  • 这样接口地址就是这样的:http://localhost:9000/api/depart/consumer/depart/get/1

服务名屏蔽

  • 前面的设置方式可以使用指定的路由路径访问到相应微服务,但使用微服务名称也可以访问到,为了防止服务侵入,可以将服务名称屏蔽。配置修改如下:

在这里插入图片描述

  • 这样一来,就只能通过指定的路由规则进行访问了 http://localhost:9000/api/depart/consumer/depart/get/1 可以, 但是 http://localhost:9000/api/msc-consumer-depart/consumer/depart/get/1 就不可以了。

路径屏蔽

  • 可以指定屏蔽掉的路径 URI,即只要用户请求中包含指定的 URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。

在这里插入图片描述

  • 这样一来,接口:http://localhost:9000/api/depart/consumer/depart/list 就无法正常访问了,但其他接口还可以继续访问。

敏感请求头屏蔽

  • 默认情况下,像 Cookie、Set-Cookie、Authorization 等敏感请求头信息会被 zuul 屏蔽掉

在这里插入图片描述

  • 我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。
  • 修改 00-zuul-server-9000 的配置文件:

在这里插入图片描述

  • 修改 05-consumer-zuul-8080 工程的接口:

在这里插入图片描述

  • 重启 Consumer 和 Zuul 服务,并用 postman 进行接口测试:

在这里插入图片描述

负载均衡

  • 用户提交的请求被路由到一个指定的微服务中,若该微服务名称的主机有多个,则默认采用负载均衡策略是轮询。

在这里插入图片描述

  • zuul 里面已经添加了 ribbon,所以使用的负载均衡策略是 ribbon 提供的。同时添加了 hystrix,具有降级功能。
  • 前面开启三个消费者工程,已经演示了负载均衡的效果,如果想修改负载均衡策略,可以在 00-zuul-server-9000 工程中做如下修改:

在这里插入图片描述

  • 测试接口:http://localhost:9000/api/depart/consumer/depart/get/1

服务降级

  • 当消费者调用提供者时由于各种原因出现无法调用的情况时,消费者可以进行服务降级。那么,若客户端通过网关调用消费者无法调用时,是否可以进行服务降级呢?当然可以,zuul 具有服务降级功能。
  • 继续在 00-zuul-server-9000 工程中修改,定义一个降级处理类:
@Component
public class ConsumerFallback implements FallbackProvider {
    @Override
    public String getRoute() {
        // 指定要降级的微服务名称
//        return "*"; // 所有微服务降级
        return "msc-consumer-depart";   // 对指定的进行降级
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                // 返回状态常量
                return HttpStatus.SERVICE_UNAVAILABLE;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                // 返回状态码,这里是503
                return HttpStatus.SERVICE_UNAVAILABLE.value();
            }

            @Override
            public String getStatusText() throws IOException {
                // 返回状态码对应的状态短语,这里为"Service Unavailable"
                return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                // 设置降级信息
                String msg = "fallback:" + ConsumerFallback.this.getRoute();
                return new ByteArrayInputStream(msg.getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}
  • 关闭所有的 Consumer 工程,然后访问 http://localhost:9000/api/depart/consumer/depart/get/1:

在这里插入图片描述

请求过滤

  • 在服务路由之前、中、后,可以对请求进行过滤,使其只能访问它应该访问到的资源,增强安全性。此时需要通过 ZuulFilter 过滤器来实现对外服务的安全控制。

路由过滤架构

在这里插入图片描述

路由过滤功能演示

  • 继续在 00-zuul-server-9000 工程中修改,定义一个路由过滤器类:
@Slf4j
@Component
public class RouteFilter extends ZuulFilter {
    @Override
    public String filterType() {
        // 指定路由之前执行过滤
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 在系统最小值-3的前面执行
        return -5;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String user = request.getParameter("user");
        String uri = request.getRequestURI();
        // 只有当请求访问的是 /depart 且 user 为空时是不能通过过滤的
        if (uri.contains("/depart") && StringUtils.isEmpty(user)) {
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 对请求通过过滤之后的处理逻辑
        log.info("通过过滤");
        return null;
    }
}
  • 测试地址:http://localhost:9000/api/depart/consumer/depart/get/1【访问401】、http://localhost:9000/api/depart/consumer/depart/get/1?user=sz【可以访问】

令牌桶限流

  • 通过对请求限流的方式避免系统遭受“雪崩之灾”。
  • 可以使用 Guava(Google 的一种基于 Java 的开源库)的 RateLimit 完成限流的,而其底层使用的是令牌桶算法实现的限流,所以我们先来学习一下令牌桶限流算法。

原理

  • 令牌桶算法可以应对突发流量的情况

在这里插入图片描述

  • 对比 漏斗限流算法

在这里插入图片描述

功能演示

  • 继续在 00-zuul-server-9000 工程中修改,主要修改 RouteFilter 这个类,RateLimiter 导入的是 Google 的类。为了测试的方便,使令牌桶每秒仅生成 2 个令牌。
@Component
@Slf4j     
public class RouterFilter extends ZuulFilter {
    // 创建一个令牌桶,每秒产生2个令牌
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);

    // ...

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();

//        // 1. 若可以立即获取到一个令牌,则返回true,否则返回false,不阻塞
//        RATE_LIMITER.tryAcquire();
//        // 2. 若可以在3秒内获取到5个令牌,则返回true,否则返回false,不阻塞
//        RATE_LIMITER.tryAcquire(5, 3, TimeUnit.SECONDS);
//        // 3. 获取1个令牌,若获取不到,则阻塞
//        RATE_LIMITER.acquire();
//        // 4. 获取5个令牌,若获取不到,则阻塞
//        RATE_LIMITER.acquire(5);
        if (!RATE_LIMITER.tryAcquire()) {
            log.warn("访问量超载");
            // 指定当前请求未通过zuul过滤,默认值为true
            context.setSendZuulResponse(false);
            // 向客户端响应状态码429,请求数量过多
            context.setResponseStatusCode(429);
        }
        return true;
    }
}
  • 测试接口地址:http://localhost:9000/api/depart/consumer/depart/get/1,正常访问是OK的,当访问比较频繁时就会出现下面的情况

在这里插入图片描述

多维请求限流

原理

  • 使用 Guava 的 RateLimit 令牌桶算法可以实现对请求的限流,但其限流粒度有些大。于是有人就针对 Zuul 编写了一个限流库(spring-cloud-zuul-ratelimit),提供多种细粒度限流策略,在导入该依赖后我们就可以直接使用了。其限流策略查验对象类型有:
    • user:针对用户的限流,即对单位时间窗内经过该网关的用户数量的限制
    • origin:针对客户端IP的限流,即对单位时间窗内经过该网关的IP数量的限制
    • url:针对请求URL的限流,即对单位时间窗内经过该网关进行请求的URL数量的限制

功能演示

  • 继续在 00-zuul-server-9000 工程中修改,可以注释掉 RouteFilter 类, 导入 spring-cloud-zuul-ratelimit 依赖,再在配置文件中对其进行相关配置。
<!-- spring-cloud-zuul-ratelimit依赖 -->
    <dependency>
      <groupId>com.marcosbarbero.cloud</groupId>
      <artifactId>spring-cloud-zuul-ratelimit</artifactId>
      <version>2.2.1.RELEASE</version>
    </dependency>
  • 修改配置文件,在配置文件中添加如下配置:
spring:
  application:
    name: msc-zuul-server
  # 连接Redis服务器
  redis:
    host: 192.168.254.128
    post: 6379
zuul:
  # ...
  # 对限流策略进行配置
  ratelimit:
    enabled: true            # 开启限流
    repository: redis
    default-policy-list:     # 设置限流策略
      - limit: 3             # 在指定的单位时间窗口内启动限流的限定值
        quota: 1             # 指定限流的时间窗口数量
        refresh-interval: 3  # 限流单位时间窗口
        type:                # 指定限流查验对象类型
          - user
          - origin
          - url
  • 测试接口地址:http://localhost:9000/api/depart/consumer/depart/get/1,正常访问是OK的,当访问比较频繁时就会出现429错误页面。

在这里插入图片描述

灰度发布

原理

什么是灰度发布?

  • 灰度发布,又名金丝雀发布(当然,也存在这是两种不同发布方式的说法),是系统迭代更新、平滑过渡的一种上线发布方式。

Zuul灰度发布原理是什么?

  • 生产环境中,可以实现灰度发布的技术很多,我们这里要讲的是 zuul 对于灰度发布的实现。而其实现是**基于 Eureka 元数据(自定义元数据)**的。

创建工程 00-zuul-gray-9000

  • 复制 00-zuul-server-9000 工程,重命名为 00-zuul-gray-9000,并在此基础上进行修改。
  • 添加依赖:
<dependency>
  <groupId>io.jmnarloch</groupId>
  <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
  <version>2.1.0</version>
</dependency>
  • 启动类:去掉负载均衡的配置
@EnableZuulProxy
@SpringBootApplication
public class ZuulGrayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulGrayApplication.class, args);
    }
}
  • 使用原子布尔值实现灰度发布的方式:
@Component
public class SimpleGrayFilter extends ZuulFilter {
    private AtomicBoolean flag = new AtomicBoolean(true);

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

    @Override
    public int filterOrder() {
        return -5;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 根据布尔值不同,路由到不同的主机,并将布尔值修改为相反值
        if (flag.get()) {
            RibbonFilterContextHolder.getCurrentContext().add("host-mark", "running-host");
            flag.set(false);
        } else {
            RibbonFilterContextHolder.getCurrentContext().add("host-mark", "gray-host");
            flag.set(true);
        }
        return null;
    }
}
  • 定义过滤器:使用 RibbonFilter 实现灰度发布
@Component
public class RibbonFilterGrayFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return -5;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        // 获取指定的头信息,该头信息在浏览器提交请求时携带
        String mark = request.getHeader("gray-mark");
        // 默认将请求路由到running-host上
        // "host-mark"与"running-host"是在消费者工程中添加的元数据key-value
        RibbonFilterContextHolder.getCurrentContext().add("host-mark", "running-host");
        // 若mark的值不空,且为"enable",则将请求路由到gray-host上
        if (!StringUtils.isEmpty(mark) && "enable".equals(mark)) {
            RibbonFilterContextHolder.getCurrentContext().add("host-mark", "gray-host");
        }
        return null;
    }
}
  • 利用 05-consumer-zuul-8080 工程,启动三个消费者服务,端口号分别是 8080、8081、8082,其中 8080 是灰度。
  • 利用 postman 进行接口测试:http://localhost:9000/api/depart/consumer/depart/get/1

在这里插入图片描述

Zuul的高可用

  • Zuul 的高可用非常关键,因为外部请求到后端微服务的流量都会经过 Zuul。故而在生产环境中,我们一般都需要部署高可用的 Zuul 以避免单点故障。
  • 作为整个系统入口路由的高可用,需要借助额外的负载均衡器来实现,例如 Nginx、HAProxy、F5 等。在 Zuul 集群的前端部分部署负载均衡服务器。Zuul 客户端将请求发送到负载均衡器,负载均衡器将请求转发到其代理的其中一个 Zuul 节点。这样,就可以实现 Zuul 的高可用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值