• SpringCloud(五)Zuul网关与分布式配置中心


    在 Spring Cloud 微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(Ngnix),再到达服务网关(Zuul 集群),然后再到具体的服务。服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理,配置服务的配置文件放在 GIT 仓库,方便开发人员随时改配置。

    一、Zuul

    Zuul包含了对请求的路由和过滤两个最主要的功能: 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.

    Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。Zuul服务最终还是会注册进Eureka。

    总体来说Zuul提供代理、路由、过滤三大功能。

    1.1 创建Zuul项目

    创建一个spring-cloud-learn-zuul项目,创建方式与之前相同,pom.xml文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <parent>
            <groupId>com.yuanqinnan</groupId>
            <artifactId>spring-cloud-learn-parent</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </parent>
    
        <artifactId>spring-cloud-learn-zuul</artifactId>
        <packaging>jar</packaging>
    
        <name>spring-cloud-learn-zuul</name>
        <dependencies>
            <!-- Spring Boot Begin -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!-- Spring Boot End -->
    
            <!-- Spring Cloud Begin -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
            <!-- Spring Cloud End -->
        </dependencies>
    </project>

    新建启动类ZuulApplication,增加@EnableZuulProxy注解

    @SpringBootApplication
    @EnableEurekaClient
    @EnableZuulProxy
    public class ZuulApplication {
        public static void main(String[] args) {
            SpringApplication.run(ZuulApplication.class, args);
        }
    }

    application.yml配置如下:

    spring:
      application:
        name: spring-cloud-learn-zuul
    
    server:
      port: 8769
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    
    zuul:
      routes:
        api-a:
          path: /api/a/**
          serviceId: spring-cloud-learn-consumer-dept-ribbon
        api-b:
          path: /api/b/**
          serviceId: spring-cloud-learn-consumer-dept-feign

    这个配置文件也很好理解,主要是配置路由:

    • /api/a 开头的请求都转发给 spring-cloud-learn-consumer-dept-ribbon 服务

    • /api/b 开头的请求都转发给 spring-cloud-learn-consumer-dept-feign 服务

    然后再启动之前的所有项目

    打开浏览器访问:http://localhost:8769/api/a/hi?message=HelloZuul 浏览器显示

    Hi,your message is :"HelloZuul" i am from port:8763

    打开浏览器访问:http://localhost:8769/api/a/hi?message=HelloZuul 浏览器显示

    Hi,your message is : HelloZuul i am from port : 8763

    1.2 失败时的回调

    由于网络震荡或者一些未知原因,可能会导致网关调用失败,这个时候我们需要配置一个网关路由失败时的回调,继承FallbackProvider

    @Component
    public class ConsumerDeptFeignFallbackProvider implements FallbackProvider {
    
        @Override
        public String getRoute() {
            // ServiceId,如果需要所有调用都支持回退,则 return "*" 或 return null
            return "spring-cloud-learn-consumer-dept-feign";
        }
         /**
          * 如果请求服务失败,则返回指定的信息给调用者
          * @param
          * @return
           * @date
          */
        @Override
        public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
            return new ClientHttpResponse() {
                /**
                 * 网关向 api 服务请求失败了,但是消费者客户端向网关发起的请求是成功的,
                 * 不应该把 api 的 404,500 等问题抛给客户端
                 * 网关和 api 服务集群对于客户端来说是黑盒
                 * @return
                 * @throws IOException
                 */
                @Override
                public HttpStatus getStatusCode() throws IOException {
                    return HttpStatus.OK;
                }
    
                @Override
                public int getRawStatusCode() throws IOException {
                    return HttpStatus.OK.value();
                }
    
                @Override
                public String getStatusText() throws IOException {
                    return HttpStatus.OK.getReasonPhrase();
                }
    
                @Override
                public void close() {
    
                }
    
                @Override
                public InputStream getBody() throws IOException {
                    ObjectMapper objectMapper = new ObjectMapper();
                    Map<String, Object> map = new HashMap<>();
                    map.put("status", 200);
                    map.put("message", "无法连接,请检查您的网络");
                    return new ByteArrayInputStream(objectMapper.writeValueAsString(map).getBytes("UTF-8"));
                }
    
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    // 和 getBody 中的内容编码一致
                    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
                    return headers;
                }
            };
        }
    }

    1.3 过滤功能

    Zuul 不仅仅只是路由,还有很多强大的功能,本节演示一下它的服务过滤功能,比如用在安全验证方面。

    来创建下过滤器功能:只要继承 ZuulFilter 类并在类上增加 @Component 注解就可以使用服务过滤功能了

    @Component
    public class LoginFilter extends ZuulFilter {
        private static final Logger logger = LoggerFactory.getLogger(LoginFilter.class);
         /**
          * 配置过滤类型,有四种不同生命周期的过滤器类型
          * 1. pre:路由之前
          * 2. routing:路由之时
          * 3. post:路由之后
          * 4. error:发送错误调用
         */
        @Override
        public String filterType() {
            return "pre";
        }
         /**
          * 配置过滤的顺序
          */
        @Override
        public int filterOrder() {
            return 0;
        }
    
         /**
          * 配置是否需要过滤:true/需要,false/不需要
          */
        @Override
        public boolean shouldFilter() {
            return false;
        }
         /**
          * 过滤器的具体业务代码
          * @param
          */
        @Override
        public Object run() throws ZuulException {
            RequestContext context = RequestContext.getCurrentContext();
            HttpServletRequest request = context.getRequest();
            logger.info("{} >>> {}", request.getMethod(), request.getRequestURL().toString());
            String token = request.getParameter("token");
            if (token == null) {
                logger.warn("Token is empty");
                context.setSendZuulResponse(false);
                context.setResponseStatusCode(401);
                try {
                    context.getResponse().getWriter().write("Token is empty");
                } catch (IOException e) {
                }
            } else {
                logger.info("OK");
            }
            return null;
        }

    这里的四个方法:

    1. filterType:返回一个字符串代表过滤器的类型,在 Zuul 中定义了四种不同生命周期的过滤器类型

      pre:路由之前

      routing:路由之时

      post: 路由之后

      error:发送错误调用

    2. filterOrder:过滤的顺序

    3. shouldFilter:是否需要过滤,这里是 true,需要过滤

    4. run:过滤器的具体业务代码

    测试下:http://localhost:8769/api/a/hi?message=HelloZuul 网页显示

    Token is empty

    http://localhost:8769/api/b/hi?message=HelloZuul&token=123 网页显示

    Hi,your message is :"HelloZuul" i am from port:8763

    二、分布式配置中心

    在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在 Spring Cloud 中,有分布式配置中心组件 Spring Cloud Config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程 Git 仓库中。在 Spring Cloud Config 组件中,分两个角色,一是 Config Server,二是 Config Client。

    2.1 Config Server项目

    创建项目的方式与之前相同,新建spring-cloud-learn-config项目,pom.xml文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <parent>
            <groupId>com.yuanqinnan</groupId>
            <artifactId>spring-cloud-learn-parent</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </parent>
    
        <artifactId>spring-cloud-learn-config</artifactId>
        <packaging>jar</packaging>
    
        <name>spring-cloud-learn-config</name>
    
        <dependencies>
            <!-- Spring Boot Begin -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!-- Spring Boot End -->
    
            <!-- Spring Cloud Begin -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-server</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
            <!-- Spring Cloud End -->
        </dependencies>
    </project>

    新建启动项目ConfigApplication,增加@EnableConfigServer注解

    @SpringBootApplication
    @EnableConfigServer
    @EnableEurekaClient
    public class ConfigApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConfigApplication.class, args);
        }
    }

    application.yml 配置文件

    spring:
      application:
        name: spring-cloud-learn-config
      cloud:
        config:
          label: master
          server:
            git:
              uri: https://github.com/yuanqinnan/spring-cloud-config
              search-paths: respo
              username:
              password:
    
    server:
      port: 8888
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/

    这里注意的地方是端口号不要修改,使用8888,如要修改创建一个bootstrap.properties文件进行修改,原因是 bootstrap 开头的配置文件会被优先加载和配置,切记。

    这里的配置文件也比较好理解,相关配置说明,如下:

    • spring.cloud.config.label:配置仓库的分支

    • spring.cloud.config.server.git.uri:配置 Git 仓库地址(GitHub、GitLab、码云 ...)

    • spring.cloud.config.server.git.search-paths:配置仓库路径(存放配置文件的目录)

    • spring.cloud.config.server.git.username:访问 Git 仓库的账号

    • spring.cloud.config.server.git.password:访问 Git 仓库的密码

    注意事项:

    • 如果使用 GitLab 作为仓库的话,git.uri 需要在结尾加上 .git,GitHub 则不用

    我们想要测试的话,先在自己的github上新建一个仓库,并新增respo文件夹(存放配置文件的目录),然后新增一个配置文件consumer-dept-feign.yml(这个是配置feign项目的)即可,然后我们就可以看看效果:

    访问路径为:http://localhost:8888/consumer-dept-feign/master 这里路径也好理解,这里的是配合文件名称加上分支名称

     

    在实际开发过程中,我们一般有三个环境,开发、测试、生产,而我们一般会在配置文件后加后缀来区分,如consumer-dept-feign-dev.yml,这个时候的访问路径就是http://localhost:8888/consumer-dept-feign/dev/master,路径地址增加环境地址,为了方便测试,再上传一个consumer-dept-feign-prod.yml,作为正式环境的配置,只修改一下端口号(8766)即可。

    这里看出来,我们的feign项目的配置文件已经换成配置中心的,那现在可以去改造feign,让他去取配置中心的配置。

    2.2 改造Config Client项目

    因为我们刚刚上传了spring-cloud-learn-consumer-dept-feign项目的配置文件,现在拿来做下实践,第一步还是添加依赖:

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

    启动类没有变化,只需修改配置文件:

    spring:
      cloud:
        config:
          uri: http://localhost:8888
          name: consumer-dept-feign
          label: master
          profile: dev

    我们看到这里只保留了cloud.config下的相关配置,因为其他所有的配置,我们都可以去云配置中找,这里的配置说明如下:

    相关配置说明,如下:

    • spring.cloud.config.uri:配置服务中心的网址

    • spring.cloud.config.name:配置文件名称的前缀

    • spring.cloud.config.label:配置仓库的分支

    • spring.cloud.config.profile
      

      :配置文件的环境标识

      • dev:表示开发环境

      • test:表示测试环境

      • prod:表示生产环境

    现在只要能够启动feign项目,就说明配置中心生效了,我们启动一下,一切顺利,可以启动成功。当我们修改 profile为prod时,启动的就是8766端口。

  • 相关阅读:
    Gecko Bootloader的介绍(Silicon Labs)【一】
    使用模板新建ZigBee工程的方法
    代码控制ZigBee网络密钥的生成
    Ubuntu20编译最新版Android源码教程
    C和C++常用代码片段整理
    Java易错的知识点整理
    仿IntelliJ Darcula的Swing主题FlatLaf使用方法
    PuTTYTabManager汉化版
    WinSCP整合SecureCRT打开终端
    异想家博客图片批量压缩程序
  • 原文地址:https://www.cnblogs.com/yuanqinnan/p/11568787.html
Copyright © 2020-2023  润新知