• 转载:springcloud 第六章 服务消费者(ZUUL)


    本章节,我们讲解springcloud重要组件:微服务网关Zuul。如果有同学从第一章看到本章的,会发现我们已经讲解了大部分微服务常用的基本组件。

    已经讲解过的:

    一起来学Spring Cloud | 第一章 :如何搭建一个多模块的springcloud项目

    一起来学Spring Cloud | 第二章:服务注册和发现组件 (Eureka)

    一起来学Spring Cloud | 第三章:服务消费者 (负载均衡Ribbon)

    一起来学Spring Cloud | 第四章:服务消费者 ( Feign )

    一起来学Spring Cloud | 第五章:熔断器 ( Hystrix)

    本章正在讲解的:一起来学Spring Cloud | 第六章:服务网关 ( Zuul)

    下章即将讲解的: 一起来学Spring Cloud | 第七章:分布式配置中心(Spring Cloud Config)

    刚入门的同学,如果把前面这七章都理解清楚,并且自己搭建一遍,在工作中,我们已经可以搭建一个最简单的微服务项目了,我曾经看过一个创业公司,他们使用微服务框架时,就用以上的组件在生产上运行着简单的后台业务系统。

    一、Zuul简介:

    Zuul是Netflix开源的微服务网关,它可以和Eureka、Feign、hystrix等组件配合使用,Zuul的核心是一系列过滤器,它主要功能是路由转发和过滤器。

    在实际项目中,一个复杂的业务系统后台,少则几十个服务模块,多则成百上千,随着业务场景的不断变更,我们的系统也会不断在演变,就会遇到如下的几个问题:

    1.  如果存在跨域请求,多个微服务在一定的场景下处理相对复杂。

    2.  客户端多次请求不同的微服务,增加了客户端的复杂性。

    3.  认证复杂,每个微服务都需要独立认证。

    4.  难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个微服务合并成一个或者将一个微服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很能实施。

    5.  某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定困难。

    Zuul提供的作用:

    1. 提供统一服务入口,微服务对前台透明

    2. 聚合后台服务,节省流量,提升性能

    3. 安全,过滤,流控等API管理功能

    4. 提供统一服务出口,解耦

    二、Zuul实现路由功能:

    1. 在前面2章讲解的两个服务模块上,新增两个方法,模拟前端请求,做为本次zuul的测试接口
    springcloud-ribbon-client模块的RibbonController类,增加/testzuul接口,具体模块信息参考:一起来学Spring Cloud | 第三章:服务消费者 (负载均衡Ribbon)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package com.haly.controller;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
     
    import com.haly.service.RibbonService;
     
     
    @RestController
    public class RibbonController {
        @Autowired
        RibbonService ribbonService;
         
        @GetMapping(value = "/getHello")
        public String getHello(@RequestParam String name) {
            return ribbonService.getHello(name);
        }
         
        @GetMapping(value = "/testzuul")
        public String testzuul(@RequestParam String name) {
            return name +"这是springcloud-ribbon-clientd的服务接口";
        }
     
    }

    springcloud-feign-client模块的FeignController类,增加/testzuul接口,具体模块信息参考:一起来学Spring Cloud | 第四章:服务消费者 ( Feign )

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.haly.controller;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
     
    import com.haly.romote.FeignRemoteService;
     
     
    @RestController
    public class FeignController {
         
        @Autowired
        FeignRemoteService feignRemoteService;
     
        @GetMapping(value = "/getHello")
        public String getHello(@RequestParam String name) {
            return feignRemoteService.hello(name);
        }
     
        @GetMapping(value = "/testzuul")
        public String testzuul(@RequestParam String name) {
            return name +",这是springcloud-feign-client的服务接口";
        }
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         

    2. 新建一个新的zuul服务工程,名称为:springcloud-zuul-server

    ①:修改pom.xml文件,parent标签引用的是父文件,具体父文件配置,参考:一起来学Spring Cloud | 第一章 :如何搭建一个多模块的springcloud项目

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <?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.haly</groupId>
            <artifactId>springcloud</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
     
        <groupId>com.haly</groupId>
        <artifactId>springcloud-zuul-server</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>springcloud-zuul-server</name>
        <description>新建一个zuuld项目</description>
     
        <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>
     
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>

    ②:新增模块启动类SpringcloudZuulServerApplication

    注解@EnableZuulProxy,表示开启zuul的功能,它默认也具有@EnableCircuitBreaker和@EnableDiscoveryClient两个注解的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.haly;
     
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
     
    @EnableZuulProxy
    @SpringBootApplication
    public class SpringcloudZuulServerApplication {
     
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudZuulServerApplication.class, args);
        }
     
    }

    ③:application.properties加上以下的配置代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server.port=9700
    spring.application.name=springcloud-zuul-server
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
     
    zuul.ignored-services: "*"
     
    zuul.routes.a.path = /api/a/**
    zuul.routes.a.serviceId = springcloud-feign-client
     
    zuul.routes.b.path =  /api/b/**
    zuul.routes.b.serviceId = springcloud-feign-client

    先解释下配置含义

    zuul.ignored-services: "*"  : 之前我们说过可以用服务名直接访问接口,如果我们不想向外界暴露除了application.properties配置映射的服务接口,配置这个属性,只能通过zuul映射的路径访问。

    zuul.routes.a.path = /api/a/**
    zuul.routes.a.serviceId = springcloud-feign-client

    当我们访问zuul服务模块时,只要包含 /api/a/ 路径的服务请求,默认请求到springcloud-ribbon-client模块上的接口

    zuul.routes.b.path =  /api/b/**
    zuul.routes.b.serviceId = springcloud-feign-client

    同理,当我们访问zuul服务模块时,只要包含 /api/b/ 路径的服务请求,默认请求到springcloud-feign-client模块上的接口

    3. 运行项目

    启动 注册中心 springcloud-eureka-server,启动springcloud-ribbon-client服务模块,启动springcloud-feign-client服务模块,启动springcloud-zuul-server模块

    在这里首先我要表达歉意,在第一章搭建多模块的微服务项目时,我使用的springcloud和springboot的版本会有问题,所以本章节启动springcloud-zuul-server模块时报错,具体报错如下:

    原因是springboot与springcloud的版本不一致导致的,以后有同学遇到同样问题,记得将对应的版本号改成一致

    在实际开发过程中,我们详细的版本对应关系:

    现在我们将父pom中springcloud的版本号修改为:Greenwich.SR1 ,再启动springcloud-zuul-server服务模块,可以启动成功了,eureka上服务信息如下:

     打开浏览器访问访问zuul服务的端口9700:http://localhost:9700/api/a/testzuul?name=young码农,我们发现/api/b/*的请求路由到 springcloud-ribbon-client模块

    打开浏览器访问zuul服务的端口9700:http://localhost:9700/api/b/testzuul?name=young码农,我们发现/api/b/*的请求路由到 springcloud-feign-client模块

    三、Zuul实现服务过滤:

    zuul不仅只是路由,并且还能过滤,可以用来做一些安全验证和日志记录,我写一个简单的接口执行时间记录的功能

    新建一个类:BaseZuulFilter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.haly.filter;
     
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
     
    @Component
    public class BaseZuulFilter extends ZuulFilter {<br>
        protected final Logger logger = LoggerFactory.getLogger(getClass());<br>
        // 单例多线程 开始时间绑定在线程上
        private ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
       <br>   @Override
        public String filterType() {
            // 在请求被处理之后,会进入该过滤器
            return "post";
        }
       <br>   @Override
        public int filterOrder() {
            return 0;
        }
     
        @Override
        public boolean shouldFilter() {
            // 请求开始计时
            long startTime = System.currentTimeMillis();
            startTimeThreadLocal.set(startTime);
            return true;
        }
     
        @Override
        public Object run() {
            RequestContext context = RequestContext.getCurrentContext();
            String requestURI = String.valueOf(context.get("requestURI"));
     
            // 请求结束时间
            Long startTime = startTimeThreadLocal.get();
            Long endTime = System.currentTimeMillis();
            logger.info("[进入zuul日志记录功能] RequestURI:{}, {}:ms", requestURI, endTime - startTime);
            return null;
        }
    }
     filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
     pre:路由之前
     routing:路由之时
     post: 路由之后
     error:发送错误调用
     filterOrder:过滤的顺序
     shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
     run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
     
    浏览器分别请求zuul服务模块:http://localhost:9700/api/b/testzuul?name=young码农,http://localhost:9700/api/b/testzuul?name=young码农,会打印如下日志:
    2019-05-25 16:41:07.228  INFO 20984 --- [io-9700-exec-10] com.haly.filter.BaseZuulFilter           : [进入zuul日志记录功能]请求地址:/testzuul, 耗时0:ms
    有兴趣的可以自己参考上面代码,做一个简单接口权限验证的功能。
     
    TOKEN过滤器,没有TOKEN的请求不能访问
    package com.yy.filter;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.apache.commons.lang.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * token检验,请求中含有Token便让请求继续往下走,如果请求不带Token就直接返回并给出提示
     */
    @Component
    public class TokenFilter extends ZuulFilter {
        private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);
        @Override
        public String filterType() {
            // //定义filter的类型,有pre、route、post、error四种
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0; //定义filter的顺序,数字越小表示顺序越高,越先执行
        }
    
        @Override
        public boolean shouldFilter() {
            return true; // 是否执行该过滤器,此处为true,说明需要过滤
        }
    
        @Override
        //filter需要执行的具体操作
        public Object run() throws ZuulException {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            logger.info("------>>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());
            String token = request.getParameter("token");
            // 请求中含有Token便让请求继续往下走,
            if(StringUtils.isNotBlank(token)){
                ctx.setSendZuulResponse(true); // 对请求进行路由
                ctx.setResponseStatusCode(200);
                ctx.set("isSuccess", true);
            } else {
                // 如果请求不带Token就直接返回并给出提示
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(400);
                ctx.setResponseBody("token is empty");
                ctx.set("isSuccess", false);
            }
            return null;
        }
    }

    四、总结:

    当前为止,项目结构:

     
  • 相关阅读:
    Oracle安装错误ora-00922(zhuan)
    Context上下文对象(抄书的)
    我的oracle账号
    jquery总结(1)
    JS改变input的value值不触发onchange事件解决方案 (转)
    写表单验证等页面的总结
    表单验证模板2
    Session随便写的(抄书笔记)
    cookie随便写的一点笔记(抄书的)
    Oracle触发器修改数据时同步执行插入该条数据
  • 原文地址:https://www.cnblogs.com/t96fxi/p/13759188.html
Copyright © 2020-2023  润新知