Eclipse配置运行SpringCloud(Hoxton + 2.2.4)微服务框架 + 搭建服务网关Zuul

简介

服务网关

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

服务网关 = 路由转发 + 过滤器

  • 路由转发:接收一切外界请求,转发到后端的微服务上去;例如,/可以映射到您的Web应用程序,/api/users映射到用户服务,并将/api/shop映射到商店服务。
  • 过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。
Zuul

Netflix使用Zuul进行以下操作:

  • 认证
  • 洞察
  • 压力测试
  • 金丝雀测试
  • 动态路由
  • 服务迁移
  • 负载脱落
  • 安全
  • 静态响应处理
  • 主动/主动流量管理

Zuul、Ribbon以及Eureka结合可以实现智能路由和负载均衡的功能;网关将所有服务的API接口统一聚合,统一对外暴露。外界调用API接口时,不需要知道微服务系统中各服务相互调用的复杂性,保护了内部微服务单元的API接口;网关可以做用户身份认证和权限认证,防止非法请求操作API接口;网关可以实现监控功能,实时日志输出,对请求进行记录;网关可以实现流量监控,在高流量的情况下,对服务降级;API接口从内部服务分离出来,方便做测试。

Zuul通过Servlet来实现,通过自定义的ZuulServlet来对请求进行控制。核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列过滤器。Zuul采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。

Zuul生命周期如下图。 当一个客户端Request请求进入Zuul网关服务时,网关先进入”pre filter“,进行一系列的验证、操作或者判断。然后交给”routing filter“进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完成后,最后由”post filter“进行处理,该类型的处理器处理完成之后,将Request信息返回客户端。 在其他阶段发生错误时执行”error filter“。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
在这里插入图片描述

创建服务网关

在这里插入图片描述

添加依赖包文件POM.xml
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.springcloud</groupId>
    <artifactId>springcloud-root</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>springcloud-zuul</artifactId>
  <name>springcloud-zuul</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
配置application.yml文件

在这里插入图片描述

  • application.yml
spring:
  application:
    name: springcloud-zuul
  freemarker:
    prefer-file-system-access: false
  security:
    user:
      name: admin
      password: 123456
    
server:
  port: 8120

eureka:
  instance:
    hostname: eureka-zuul.com
    instance-id: eureka-zuul
  client:
    service-url:
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eureka-peer1.com:8897/eureka/,http://${spring.security.user.name}:${spring.security.user.password}@eureka-peer2.com:8898/eureka/,http://${spring.security.user.name}:${spring.security.user.password}@eureka-peer3.com:8899/eureka/

zuul:
  #接口前缀(v1作为版本号)
  prefix: /v1
  routes:
    hiapi:
      path: /hiapi/**
      serviceId: springcloud-eureka-provider
    ribbonapi:
      path: /ribbonapi/**
      serviceId: springcloud-ribbon
    feignapi:
      path: /feignapi/**
      serviceId: springcloud-feign

修改C:\Windows\System32\drivers\etc\hosts

127.0.0.1 eureka-zuul.com

禁用指定的Filter

可以在 application.yml 中配置需要禁用的 filter,格式为

zuul.<SimpleClassName>.<filterType>.disable=true

比如要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter就设置

zuul:
  SendResponseFilter:
    post:
      disable: true
添加服务网关启动类

在这里插入图片描述

  • ZuulApplication.java

加上注解@EnableZuulProxy

package org.springcloud.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}
依次启动项目

springcloud-eureka-cluster-peer1
springcloud-eureka-cluster-peer2
springcloud-eureka-cluster-peer3
springcloud-eureka-provider1
springcloud-eureka-provider2
springcloud-eureka-provider3
springcloud-ribbon
springcloud-feign
springcloud-zuul

在这里插入图片描述

  1. 浏览器多次访问http://eureka-zuul.com:8120/v1/hiapi/hi?name=zhaojq
    在这里插入图片描述
    zuul在路由转发做了负载均衡

  2. 浏览器多次访问http://eureka-zuul.com:8120/v1/ribbonapi/hi?name=zhaojq
    在这里插入图片描述
    Zuul和Ribbon相结合,实现了负载均衡(随机策略)

  3. 浏览器多次访问http://eureka-zuul.com:8120/v1/feignapi/hi?name=zhaojq
    在这里插入图片描述
    Zuul和Feign相结合,实现了负载均衡(随机策略)

在服务网关Zuul上配置熔断器

实现熔断器功能需要实现FallbackProvider接口,实现该接口的两个方法,一个是getRoute(),用于指定熔断器功能应用于哪些路由的服务;另一个方法fallbackResponse()为进入熔断器功能时执行的逻辑。
在这里插入图片描述

  • ZuulHystrix.java

如果需要所有的路由服务都加熔断功能,需要在getRoute()方法上返回”*“的匹配符

package org.springcloud.zuul;

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

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 ZuulHystrix implements FallbackProvider {
    @Override
    //指定熔断器功能应用于哪些路由的服务
    public String getRoute() {
        //return "springcloud-eureka-provider";
    	return "*";
    }

    @Override
    //进入熔断器功能时执行的逻辑
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        System.out.println("route:"+route);
        System.out.println("exception:"+cause.getMessage());
        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("eureka-provider is down!! this is the fallback.".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

全部正常启动后,停止 springcloud-eureka-provider2 提供者,端口为:8002服务
访问命令窗口curl http://eureka-zuul.com:8120/v1/hiapi/hi?name=zhaojq,断路器已经生效,提示:提供者服务挂了
在这里插入图片描述

自定义过滤器Zuul Filter

Zuul包括以下4中过滤器

  • PRE过滤器:是在请求路由到具体服务之前执行的,可以做安全验证,如身份验证,参数验证。
  • ROUTING过滤器:它用于将请求 路由到具体的微服务实例。默认使用Http Client进行网络请求。
  • POST过滤器:在请求已被路由到微服务后执行的。可用作收集统计信息、指标,以及将响应传输到客户端。
  • ERROR过滤器:在其他过滤器发生错误时执行。

Zuul中默认实现的filter

类型顺序过滤器功能
pre-3ServletDetectionFilter标记处理Servlet的类型
pre-2Servlet30WrapperFilter包装HttpServletRequest请求
pre-1FormBodyWrapperFilter包装请求体
route1DebugFilter标记调试标志
route5PreDecorationFilter处理请求上下文供后续使用
route10RibbonRoutingFilterserviceId请求转发
route100SimpleHostRoutingFilterurl请求转发
route500SendForwardFilterforward请求转发
post0SendErrorFilter处理有错误的请求响应
post1000SendResponseFilter处理正常的请求响应

实现自定义滤器需要继承ZuulFilter,实现ZuulFilter中的抽象方法,包括filterType(),filterOrder()以及IZuulFilter的shouldFilter()和run()方法。

  • filterType()为过滤器类型,有4中类型:pre、post、routing和error。
  • filterOrder()是过滤顺序,它为一个int类型的值,值越小,越早执行该过滤器。
  • shouldFilter()表示是否需要执行该过滤器逻辑,true表示执行,false表示不执行,如果true则执行run()方法。
  • run()方法是具体的过滤的逻辑。本例中检查请求的参数中是否传了token或password这个参数,如果没有传,则请求不被路由到具体的服务实例,直接返回响应,状态码为401。
TokenFilter 过滤器

在这里插入图片描述

  • TokenFilter.java
package org.springcloud.zuul;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

public class TokenFilter extends ZuulFilter {

    private static Logger LOGGER=LoggerFactory.getLogger(TokenFilter.class);

    @Override
    //定义filter的类型,有pre、route、post、error四种
    public String filterType() {
        //可以在请求被路由之前调用
    	return "pre"; 
    }

    @Override
    //定义filter的顺序,数字越小表示顺序越高,越先执行
    public int filterOrder() {
        return 0;
    }

    @Override
    //表示是否需要执行该filter,true表示执行,false表示不执行
    public boolean shouldFilter() {
        return 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");
        
        if (StringUtils.isNotBlank(token)) {
        	//对请求进行路由
            ctx.setSendZuulResponse(true);
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);
            return null;
        } else {
        	LOGGER.warn("token is empty");
            //不对请求进行路由
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("token is empty");
            } catch (IOException e) {
                e.printStackTrace();
            }
            ctx.set("isSuccess", false);
            return null;
        }
    }
}

开启过滤器,在程序的启动类 ZuulFilterApplication 添加 Bean

  • ZuulApplication.java
package org.springcloud.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
    
    @Bean
    public TokenFilter TokenFilter() {
        return new TokenFilter();
    }
}

重启项目后,访问http://eureka-zuul.com:8120/v1/hiapi/hi?name=zhaojq
在这里插入图片描述
提示 token is empty。

访问http://eureka-zuul.com:8120/v1/hiapi/hi?name=zhaojq&token=cc
在这里插入图片描述
可见,TokenFilter这个Bean注入IOC容器后,对请求进行了过滤,并在请求路由转发之前进行了逻辑判断。

提示 token is empty显示两遍错误

在这里插入图片描述
在这里插入图片描述
把自定义TokenFilter的@Component取消掉就可以了,不让它被Spring容器管理。

PasswordFilter 过滤器

在这里插入图片描述

  • PasswordFilter.java
package org.springcloud.zuul;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

public class PasswordFilter extends ZuulFilter {

    private final Logger LOGGER = LoggerFactory.getLogger(PasswordFilter.class);

    @Override
    public String filterType() {
    	//请求已被路由到微服务后执行
        return "post";
    }

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

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        //判断上一个过滤器结果为true,否则就不走下面过滤器,直接跳过后面的所有过滤器并返回 上一个过滤器不通过的结果
        return (boolean) ctx.get("isSuccess");     
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        LOGGER.info("--->>> PasswordFilter {},{}", request.getMethod(), request.getRequestURL().toString());

        String username = request.getParameter("password");
        if (null != username && username.equals("123456")) {
            ctx.setSendZuulResponse(true);
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);
            return null;
        } else {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("The password cannot be empty");
            } catch (IOException e) {
                e.printStackTrace();
            }
            ctx.set("isSuccess", false);
            return null;
        }
    }
}

开启过滤器,在程序的启动类 ZuulFilterApplication 添加 Bean

  • ZuulApplication.java
package org.springcloud.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
    
    @Bean
    public TokenFilter TokenFilter() {
        return new TokenFilter();
    }

    @Bean
    public PasswordFilter PasswordFilter() {
        return new PasswordFilter();
    }
}

访问http://eureka-zuul.com:8120/v1/hiapi/hi?name=zhaojq&token=cc&password=123456
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值