zoukankan      html  css  js  c++  java
  • 第五篇: 路由网关(zuul)

    在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。

    在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服务。

    服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(重点),配置服务的配置文件放在git仓库,方便开发人员随时改配置。

    一、Zuul简介

    Zuul做为网关层,自身也是一个微服务,跟其它服务Service-1,Service-2, ... Service-N一样,都注册在eureka server上,可以相互发现,zuul能感知到哪些服务在线,同时通过配置路由规则(后面会给出示例),可以将请求自动转发到指定的后端微服务上,对于一些公用的预处理(比如:权限认证,token合法性校验,灰度验证时部分流量引导之类),可以放在所谓的过滤器(ZuulFilter)里处理,这样后端服务以后新增了服务,zuul层几乎不用修改。

    Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。

    二、创建eureka-service-zuul工程

    打jar包。pom.xml如下:

    <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>
      <groupId>com.sun</groupId>
      <artifactId>eureka-service-zuul</artifactId>
      <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
        
        <name>service-zuul</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>com.sun</groupId>
            <artifactId>springcloud-parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
        
         <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
        </dependencies>
    </project>
    View Code

    在其入口applicaton类加上注解@EnableZuulProxy,开启zuul的功能,新建ServiceZuulApplication启动类,代码如下:

    package com.sun;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    @SpringBootApplication
    @EnableZuulProxy
    @EnableEurekaClient
    public class ServiceZuulApplication {
    
        public static void main(String[] args) {
            SpringApplication.run( ServiceZuulApplication.class, args );
        }
    }
    View Code

    加上配置文件application.yml加上以下的配置代码:

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    server:
      port: 8769
    spring:
      application:
        name: service-zuul
    zuul:
      routes:
        api-a:
          path: /api-a/**
          serviceId: service-ribbon
        api-b:
          path: /api-b/**
          serviceId: service-feign
    View Code

    这里说一下,application.yml是放到src/main/resources文件夹下的。

    解释一下:上面这段配置表示,/api-a/**开头的url请求,将转发到service-ribbon这个微服务上,/api-b/**开头的url请求,将转发到service-feign这个微服务上。

    首先指定服务注册中心的地址为http://localhost:8761/eureka/,服务的端口为8769,服务名为service-zuul;

    以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务;

    三、启动工程

     依次启动eureka-server,eureka-client,eureka-service-ribbon,eureka-service-feign,eureka-service-zuul五个工程。

    打开浏览器访问:http://localhost:8769/api-a/hi?name=sun ;浏览器显示:

    hi sun ,i am from port:8763

    打开浏览器访问:http://localhost:8769/api-b/hi?name=sun ;浏览器显示:

    hi sun ,i am from port:8763

    这说明zuul起到了路由的作用.

    四、服务过滤

    zuul不仅只是路由,并且还能过滤,做一些安全验证。继续改造工程,新建一个类MyFilter继承自ZuulFilter:

    package com.sun;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    
    @Component
    public class MyFilter extends ZuulFilter {
        private static Logger log = LoggerFactory.getLogger(MyFilter.class);
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
            Object accessToken = request.getParameter("token");
            if(accessToken == null) {
                log.warn("token is empty");
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(401);
                try {
                    ctx.getResponse().getWriter().write("token is empty");
                }catch (Exception e){}
    
                return null;
            }
            log.info("ok");
            return null;
        }
    }
    View Code
    • filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,其常量值在org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 中定义具体如下:  
    // Zuul Filter TYPE constants -----------------------------------
    
        /**
         * {@link ZuulFilter#filterType()} error type.
         */
        public static final String ERROR_TYPE = "error";
    
        /**
         * {@link ZuulFilter#filterType()} post type.
         */
        public static final String POST_TYPE = "post";
    
        /**
         * {@link ZuulFilter#filterType()} pre type.
         */
        public static final String PRE_TYPE = "pre";
    
        /**
         * {@link ZuulFilter#filterType()} route type.
         */
        public static final String ROUTE_TYPE = "route";

    这时访问:http://localhost:8769/api-a/hi?name=sun ;网页显示:

      token is empty

    访问 http://localhost:8769/api-a/hi?name=sun&token=22 ; 网页显示:

      hi sun ,i am from port:8763

    按说到这里已经结束了。不过有一点思考:zuul路由了ribbon和feign,前面我们说了,ribbon和feign是有熔断机制的,那现在,如果ribbon或feign或eureka-client挂了,zuul该如何熔断?

    在这里我们可以看到,当关掉这几个客户端服务后,会出现:

    提示没有熔断的fallback,这样说就是zuul路由到服务消费者ribbon/feign,消费者访问eureka-server,通过server找生产者eureka-client,发现生产者over了。

    消费者ribbon/feign熔断,但是zuul不认,需要显式的对zuul进行熔断处理:

    五、zuul熔断

    新建ServiceHiFallbackProvider类,扩展自FallbackProvider接口:

    package com.sun;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.client.ClientHttpResponse;
    import org.springframework.stereotype.Component;
    
    @Component
    public class ServiceHiFallbackProvider implements FallbackProvider {
         private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);
            
        //指定要处理的 service。
        @Override
        public String getRoute() {
            return "service-ribbon";
        }
        
        @Override
        public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
            if (cause != null && cause.getCause() != null) {
                String reason = cause.getCause().getMessage();
                logger.info("Excption {}",reason);
            }
            return fallbackResponse();
        }
    
        public ClientHttpResponse fallbackResponse() {
            return new ClientHttpResponse() {
                @Override
                public HttpStatus getStatusCode() throws IOException {
                    return HttpStatus.OK;
                }
    
                @Override
                public int getRawStatusCode() throws IOException {
                    return 200;
                }
    
                @Override
                public String getStatusText() throws IOException {
                    return "OK";
                }
    
                @Override
                public void close() {
    
                }
    
                @Override
                public InputStream getBody() throws IOException {
                    return new ByteArrayInputStream("The service is unavailable.".getBytes());
                }
    
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_JSON);
                    return headers;
                }
            };
        }
    }
    View Code

    当ribbon/feign或者服务生产者client挂了,都会返回:

      The service is unavailable.

  • 相关阅读:
    JS基础学习
    Java_iText_PDF—生成PDF工具
    vc++ 编译连接错误and解决方法
    C++ 数据类型【转】
    jsp两种include指令区别
    程序员面试宝典问题及解析
    vc++6.0快捷键
    vue动态绑定图片和背景图
    配置webpack中dev.env.js、prod.env.js,解决不同命令下项目启动和打包到指定的环境
    配置webpack中externals来减少打包后vendor.js的体积
  • 原文地址:https://www.cnblogs.com/PPBoy/p/9395151.html
Copyright © 2011-2022 走看看