3. zuul网关的应用--实践操作

目录: 

1. nginx网关和zuul网关的区别

2. zuul项目搭建及演示

3. 配置特殊路由规则

四. 动态的路由机制

五. zuul的全局拦截

六. 在zuul里面实现限流

七. 在zuul里面实现熔断器


 

一. nginx网关和zuul网关的区别

1. 相同点

都是网关. 用户访问, 都是先访问网关. 不能直接访问里面的服务. 他们都能做路由, 负债均衡, 以及限流

2. 异同点

nginx在做路由,负载均衡,限流之前, 都有修改nginx.conf的配置文件. 把需要负载均衡,限流,路由的规则定义在nginx.conf配置文件中
eg: 使用nginx做tomcat负载均衡, 需要修改nginx.conf配置文件

Upsteam www.car.com {
    server ip: port,
    server ip: port
}
Location / {
    proxy_pass http://www.car.com
}

 

但是zuul不同, zuul是自动负载均衡和路由, zuul和eureka高度集成, 实现自动的路由, 和ribbon结合,实现了负载均衡, zuul也能轻易的实现限流和权限验证

性能相比: nginx的性能比zuul要高. nginx是c写的

 

二.zuul项目搭建及演示

2.1 新建网关项目--zuul-test-gateway

 

 

 

 

2.1.1 引入项目依赖

我搭建的是一个全新的项目

https://start.spring.io/创建一个项目, 项目名称zuul-test-gateway

引入的依赖: web, zuul, nacos-config, nacos-discovery

引入的配置如下:

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

<!-- 服务之间http调用, 引入feign -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>    

2.1.2 新建bootstrap.yml配置文件

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
        namespace: 482c42bd-fba1-4147-a700-5b678d7c0747
        group: ZUUL_TEST

2.1.3 修改配置文件application.yml

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: 482c42bd-fba1-4147-a700-5b678d7c0747
  redis:
    host: localhost
    port: 6379

 

2.1.4 在启动类引入相关注解

1. 开启zuul网关的注解:@EnableZuulProxy

2. nacos注册发现注解: @EnableDiscoveryClient

3. web框架注解: @RestController

4. 微服务间调用注解feign: @EnableFeignClients

5. 动态更新配置注解: @RefreshScope

package com.lxl.www.gateway;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
@EnableZuulProxy // 开启zuul网关功能
@RefreshScope
public class ZuulTestGatewayApplication {



    @Value("${config}")
    private String config;

    public static void main(String[] args) {
        SpringApplication.run(ZuulTestGatewayApplication.class, args);
    }

    @GetMapping("config")
    public String getconfig(){
        return this.config;
    }
}

2.1.5 启动nacos服务端

我们这里配置中心使用的是nacos, 因此我们需要启动nacos服务.

如何配置nacos,启动nacos, 参考文章:

nacos配置中心: https://www.cnblogs.com/ITPower/p/12630193.html

nacos服务中心: https://www.cnblogs.com/ITPower/p/12651152.html

 

启动nacos, 我这里是单机模式启动, 进入到nacos的目录

cd /users/nacos/nacos/bin
./startup.sh -m standalone

 

2.1.6 启动gateway网关项目

上面的配置文件中已经包含了nacos配置和服务发现的配置内容

2.1.7 在nacos中查看网关项目

1. 网关的配置文件, 如下图所示

 

 2.网关项目启动后, 查看服务列表

 

 2.2 创建另一个服务,用户服务----zuul-test-user

 

 

2.2.1 添加依赖

需要添加的依赖有: nacos配置和发现, web,以及feign

      <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 服务之间http调用, 引入feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.2.2 添加bootstrap.yml配置文件

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
        namespace: 482c42bd-fba1-4147-a700-5b678d7c0747
        group: ZUUL_TEST

 

2.2.3 修改application.yml配置文件

server:
  port: ${port:8089}
spring:
  application:
    name: user
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: 482c42bd-fba1-4147-a700-5b678d7c0747

2.2.4 启动类引入注解

package com.lxl.www.user;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@EnableDiscoveryClient
@SpringBootApplication
@RefreshScope
public class ZuulTestUserApplication {

    @Value("${config}")
    private String config;
    public static void main(String[] args) {
        SpringApplication.run(ZuulTestUserApplication.class, args);
    }

    @GetMapping("/config")
    public String getconfig(){
        return config + "2222";
    }
}

2.2.5 启动user服务

2.2.6 查看nacos

1. 查看user配置文件

 

2. 查看项目启动情况 

 

 

2.3. 启动另一个user服务. 换一个端口

1. 上面我们定义user服务的端口的时候,定义的是动态端口

 

 

 下面我们构建集群环境,再启动一个user服务.

2. 点击配置管理

 

3. 点击复制配置

 

 4. 选中要启动的应用, 然后设置端口号

 

 5. 接下来,选中配置,点击启动即可

 

 

6. 查看nacos中服务的启动情况 

 

我们看到user的实例数,现在是2台

至此项目搭建就完成了!!!

2.4. 下面来感受一下网关

现在有两个服务, 一个是网关服务,端口是8080;  一个是user服务, 端口分别是8089和8088. 

我们通过网关服务区请求user, 看看可不可以

 

 

 我在postman中输入的是网关的端口localhost:8080, 然后紧跟着服务名+path, 可以正确跳转到user服务上.

其实,我们在配置文件中, 没有做任何配置. 

网关是网关服务, 用户是用户服务, 他们都可已单独存在, 单独工作. 但是通过网关的地址却能够跳转到user服务上. 这是zuul自动为我们在服务发现上发现相应的集群中的其他服务.

 

下面来详细说说网关的应用

三. 配置特殊的路由规则

zuul: 
  routes:
    user-router: # 随便写, 是一个唯一的, 代表一个微服务的路由机制
      service-id: user # 该路由机制针对的是哪个微服务
      path: /user1/**

我们可以在网关中自定义服务的路由路径. 如上所示

 

 

这表示, 所有连接http://localhost:8080/user1/**的连接都会分发到服务名为user的服务上 
比如:我们请求路径写成http://localhost:8080/user1/config, 那么也会正确的请求的user服务上

 

 

 

四. 动态的路由机制

五. zuul的全局拦截

zuul是前端访问的唯一入口, 我们可以在zuul实现一个token的拦截验证

1. 定义一个TokenFilter过滤器,这个过滤器extends ZuulFilter

package com.lxl.www.gateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

@Component
public class TokenFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

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

    /**
     * 实现token 拦截验证
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        // 怎么判断用户的token?
        // RequestContext 请求的上下文, 包含所有的请求参数, 他默认和线程绑定
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();

        // 从请求头中拿出token
        String token = request.getHeader("token");

        if (!StringUtils.hasText(token)) {
            // token 为null
            currentContext.setResponseBody("token is null");
            currentContext.setResponseStatusCode(401);
            // 是否发送路由响应
            currentContext.setSendZuulResponse(false);
            return null;
        }

        if (!"123456".equals(token)) {
            currentContext.setResponseBody("token is error");
            currentContext.setResponseStatusCode(401);
            currentContext.setSendZuulResponse(false);
            return null;
        }

        currentContext.setSendZuulResponse(true);
        return null;
    }
}

这个过滤器extends自ZuulFilter, 后面的案例多了, 我们就发现, 其实zuul网关的本质就是拦截器, zuul的各种功能,也是通过拦截器来实现的

filterType() : 拦截器的类型是前置拦截器.

filterOrder(): 执行顺序是第一个执行. 

shouldFilter(): 过滤器执行的条件, 这里是所有的连接都需要过这个拦截器, 所以直接设置为true

run(): 拦截器的核心逻辑. 这里的拦截器逻辑很简单, 就是判断header中是否有一个叫做token的属性, 且其值为123456

2. 启动服务, 查看拦截器效果

当有header, 不启用的时候, 会被拦截, 提示token is null, 并且跳过后面的拦截器, 直接返回

 

 

 当有token ,但是token的值不是123456的时候, 会说token错误

 

只有当token符合我们的预期的时候, 才可以放行

 

 

 

这个拦截器是对所有微服务有效的.

六. 在zuul里面实现限流

当有个恶意用户每分钟访问超过一定的次数后, 我们可以对他实行限制, 让他1分钟内只能访问n次

6.1 添加zuul的限流组件

git地址:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
进入git查看zuul-ratelimit的使用方法, 上面是源码, 下面是使用方法

 

 

 

6.2 添加限流组件--引入依赖

     <!-- 添加限流组件  开始-->
        <!-- 1. 添加组件 -->
        <dependency>
            <groupId>com.marcosbarbero.cloud</groupId>
            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
            <version>2.4.1.RELEASE</version>
        </dependency>

        <!-- 2. 添加redis 保存数据-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- 添加限流组件  结束-->

我们引入限流组件, 同时引入redis. 用来记录当前用户登录的次数

6.3 在配置文件中增加redis配置. 

在本地先启动redis, 端口6379

然后增加redis配置

 

6.4 增加限流配置

6.4.1 全局限流配置

 根据文档, 限流配置有两种,

1. 通用限流配置: 对所有的微服务都生效

2. 特定服务限流配置: 针对某一个微服务设置的限流

文档内容如下: 

 

 

zuul:
  routes:
    user-router: # 随便写, 是一个唯一的, 代表一个微服务的路由机制
      service-id: user # 该路由机制针对的是哪个微服务
      path: /user1/**
  ratelimit:
    enabled: true
    repository: REDIS
    behind-proxy: true
    add-response-headers: true
    default-policy-list: # 全局配置, 改配置对所有的微服务都有效
      - limit: 10 #选填--限制的次数
        quota: 1000 #选填--时间的显示--刷新窗口时间隔的时间限制
        refresh-interval: 60 # 60s
        type: #选填--针对哪个服务有效
          #- user # 针对用户么
          - origin    #针对域名么
          #- url        #针对url么
          #- http_method    #针对方法么

 

 

如上限流的含义是: 对所有微服务都生效的限流策略是: 对某个微服务在60s内,请求次数限制是10次. 或者刷新窗口时间隔实现的限制是1秒. 也就是1秒刷新一次窗口.

配置中的具体含义如上注释

配置好以后, 启动微服务, 在postman中查看 是否起到限流作用

 

 

 当次数超过10次时候, 就会给出异常提示, too many requests.

6.4.2 特定微服务的限流限制

zuul:
  routes:
    user-router: # 随便写, 是一个唯一的, 代表一个微服务的路由机制
      service-id: user # 该路由机制针对的是哪个微服务
      path: /user1/**
  ratelimit:
    enabled: true
    repository: REDIS
    behind-proxy: true
    add-response-headers: true
    default-policy-list: # 全局配置, 改配置对所有的微服务都有效
      - limit: 10 #选填--限制的次数
        quota: 1000 #选填--时间的显示
        refresh-interval: 60 # 60s
        type: #选填--针对哪个服务有效
          #- user # 针对用户么
          - origin    #针对域名么
          #- url        #针对url么
          #- http_method    #针对方法么
    policy-list:
      user: #这里设置的是具体的服务id
        - limit: 10 #optional 设置限流的次数
          quota: 1000 #optional 设置限流的时间--刷新窗口间隔的时间限制
          refresh-interval: 60 # 60s为单位限流
          type: #optional 针对的限流类型
            #- user #针对用户限流么?
            - origin #针对域名限流么? 表示的是一个客户端
            #- url  # 针对url链接限流么?

设置单个微服务的限流策略, 如上所示, 具体含义: 60s内, 限制次数为10次, 窗口刷新间隔时间是1秒. 

效果如下, 当每分钟请求次数超过5次的时候, 爆出连接次数过多

6.5 限流的机制

限流器的本质是filter
在限流器通过filter来记录用户的访问次数, 当次数达到一定值, 直接让filter拦截

6.6 限流里面redis的作用

使用redis的原因, 当zuul是一个集群的时候,要在多个redis中共享访问次数

七. 在zuul里面实现熔断器

熔断, 其含义是当路由失败的时候, 执行熔断器.

zuul是自带熔断机制的. 不需要引入任何额外的依赖

我们需要的是, 实现熔断器中的FallbackProvider接口. 定义自己的熔断机制 

package com.lxl.www.gateway.fallback;

import org.apache.http.client.methods.HttpHead;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestHeader;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * zuul熔断器
 */
@Component
public class ZuulFallbackProvider implements FallbackProvider {

    /**
     * 要对哪个微服务实现熔断
     * @return
     */
    @Override
    public String getRoute() {
        return "user"; // 这里写的是服务的id, *表示任何服务
    }

    /**
     * 在熔断时, 用户执行怎样的响应数据
     * @param route
     * @param cause
     * @return 当熔断被触发以后, 如何响应内容
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        System.out.println("熔断器被触发:" + route);
        System.out.println("熔断的原因:" + cause);
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.BAD_REQUEST;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 401;
            }

            @Override
            public String getStatusText() throws IOException {
                return "服务异常!";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                byte[] body = "server is error, get in the fallback".getBytes();
                return new ByteArrayInputStream(body);
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.add("reason", "server is error, get in fallback!");
                return headers;
            }
        };
    }
}

 getRoute(): 定义对哪个服务启用熔断策略

fallbackResponse(String route, Throwable cause): 当熔断时, 执行怎么样的操作

这里的熔断机制比较简单, 如果熔断了, 那么就打印日志, 并输出server is error, get in fallback! 

比如: 我的user微服务挂了, 通过网关请求, 就会进得到如下信息提示.

 

以上就是zuul在项目中通常使用的场景. demo很简单, 但框架即是如此, 具体可以根据详细的需求增减

 

posted @ 2020-06-22 19:42  盛开的太阳  阅读(555)  评论(0)    收藏  举报