SpringCloud第六篇-Zuul

来源:互联网 发布:数据真实性 英文 编辑:程序博客网 时间:2024/05/16 19:42

Zuul简介

Zuul可以通过加载动态过滤机制,从而实现以下各项功能:

  • 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
  • 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
  • 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
  • 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
  • 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
  • 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。

除此之外,Netflix公司还利用Zuul的功能通过金丝雀版本实现精确路由与压力测试。

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

这里写图片描述
注意:A服务和B服务是可以相互调用的,作图的时候远作者方志朋忘记了。并且配置服务也是注册到服务注册中心的。

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

项目准备

  • 运行注册中心eureka-server
  • 修改home方法,并运行service-hello:只用运行一个就可以了

改写home方法如下:方便后面的请求的不同

public String home(String name ) {        return "hello "+name+"from port:" +port;    }
  • 修改service-ribbon下的HelloService方法,运行service-ribbon
    修改部分如下,name参数加入ribbon_前缀
 public String helloService(String name) {         return restTemplate.getForObject("http://SERVICE-HELLO/hello?name=ribbon_"+name,String.class);        //return restTemplate.getForObject("http://SERVICE-HELLO/hello",String.class);    }
  • 运行service-feign

通过运行http://desktop-180i6sh:8764/hello?name=bamboo
http://desktop-180i6sh:8765/hello?name=bamboo
我们可也看出返回的字符串区别,加了ribbon_前缀的是从service-ribbon请求过去,而没有加的则是service-feign请求过去

hello ribbon_bamboofrom port:8762hello bamboofrom port:8762
  • service-zuul:新建项目

创建service-zuul工程

另起一个project,取名叫service-zuul

pom

<?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>    <groupId>com.bamboo</groupId>    <artifactId>service-zuul</artifactId>    <version>1.0-SNAPSHOT</version>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>1.5.2.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>        <java.version>1.8</java.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-eureka</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-zuul</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>    </dependencies>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>Dalston.RC1</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build>    <repositories>        <repository>            <id>spring-milestones</id>            <name>Spring Milestones</name>            <url>https://repo.spring.io/milestone</url>            <snapshots>                <enabled>false</enabled>            </snapshots>        </repository>    </repositories></project>

yml

application.yml

server:  port: 8769spring:  application:    name: service-zuul    #服务名为service-zuul;eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/#以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务zuul:  routes:    api-a:      path: /api-a/**      serviceId: service-ribbon    api-b:      path: /api-b/**      serviceId: service-feign

指定服务注册中心的地址为http://localhost:8761/eureka/,服务的端口为8769,服务名为service-zuul;以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务;

java

package com.bamboo;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;/** * * * Created by bamboo on 2017/10/24. */@EnableZuulProxy@EnableEurekaClient@SpringBootApplicationpublic class ZuulApplication {    public static void main(String[] args) {        SpringApplication.run(ZuulApplication.class, args);    }}

启动运行并查看结果

依次启动上面的项目,服务如下
这里写图片描述

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

hello ribbon_bamboofrom port:8762

http://localhost:8769/api-b/hello?name=bamboo ;浏览器显示:

hello ribbonfrom port:8762

通过返回的字符串我们知道,service-zuul根据路径把请求发送到不同的服务上了,因此路由转发的功能是确定的。

服务授权过滤

相当于servlet中的filter拦截器功能,但是显然要比它强大。
创建MyFilter.java,实现一个简单的接口授权功能

package com.bamboo;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;/** * * zuul不仅只是路由,并且还能过滤,做一些安全验证 * Created by xialeme on 2017/10/24. */@Componentpublic 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;    }    //过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。    @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");//获取请求中的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;    }}

filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
pre:路由之前
routing:路由之时
post: 路由之后
error:发送错误调用
filterOrder:过滤的顺序
shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。

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

token is empty

加上token再试一次
http://desktop-180i6sh:8769/api-a/hello?name=bamboo&token=111

hello ribbon_bamboofrom port:8762

接口访问成功了

参考资料

zuul官网
方志朋-史上最简单的SpringCloud教程 | 第五篇: 路由网关(zuul)