2018-06-19-Spring cloud(5)-路由网关(Zuul)

Spring Cloud Zuul(路由网关)

基于Netflix的开源框架zuul实现的
各个微服务之间都不存在单点,并且都注册于 Eureka ,基于此进行服务的注册于发现,再通过 Ribbon 进行服务调用,并具有客户端负载功能。
问题点?

  • 将我们具体的微服务地址加端口暴露出去?
  • 如果系统庞大,服务拆分的足够多那又有谁来维护这些路由关系呢?
  • 服务调用之间的一些鉴权、签名校验怎么做?
  • 由于服务端地址较多,客户端请求维护难度?

针对上述问题:SpringCloud 全家桶自然也有对应的解决方案: Zuul
Spring Cloud Zuul 将自身注册为 Eureka 服务治理下的应用,从 Eureka 中获取服务实例信息,从而维护路由规则和服务实例。

API服务网关(API Gateway)服务

我们在所有的请求进来之前抽出一层网关应用,将服务提供的所有细节都进行了包装,这样所有的客户端都是和网关进行交互(统一入口),简化了客户端开发

  • 将细粒度的服务组合起来提供一个粗粒度的服务
  • 所有请求都导入一个统一的入口,那么整个服务只需要暴露一个api
  • 对外屏蔽了服务端的实现细节,也减少了客户端与服务器的网络调用次数
    zuul

    通过Zuul我们可以完成以下功能
  • 客户端负载:Zuul 注册于 Eureka 并集成了 Ribbon 所以自然也是可以从注册中心获取到服务列表进行客户端负载。
  • 动态路由:解放运维。
  • 监控与审查
  • 身份认证与安全
  • 压力测试: 逐渐增加某一个服务集群的流量,以了解服务性能;
  • 金丝雀测试
  • 服务迁移
  • 负载剪裁: 为每一个负载类型分配对应的容量,对超过限定值的请求弃用;
  • 静态应答处理

添加依赖

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

spring-cloud-starter-zuul本身已经集成了hystrix和ribbon
注意点

  • (传统路由)当使用path与url的映射关系来配置路由规则时,对于路由转发的请求则不会采用HystrixCommand来包装,所以这类路由请求就没有线程隔离和断路器保护功能,并且也不会有负载均衡的能力
  • (服务路由)使用Zuul的时候尽量使用path和serviceId的组合进行配置,这样不仅可以保证API网关的健壮和稳定,也能用到Ribbon的客户端负载均衡功能。

开启服务注册

@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {

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

路由配置顺序注意

  • 按照配置的顺序进行路由规则控制,则需要使用YAML(文件格式,类似propeties)
  • 如果是使用propeties文件,则会丢失顺序。
# 项目配置
spring.application.name=sbc-gateway-zuul
server.port=8383
# 简化路由配置[zuul.routes.微服务Id = 指定路径]
zuul.routes.user = /api/**(指定正则表达式来匹配路径)

# 忽略指定微服务
zuul.ignored-services=微服务Id1,微服务Id2...

# 指定多个微服务负载
zuul.routes.user.path: /user/**
zuul.routes.user.serviceId: user
ribbon.eureka.enabled=false
user.ribbon.listOfServers: 微服务1, 微服务2...

# forward跳转本地
zuul.routes.user.path=/user/**
zuul.routes.user.url=forward:/user

# 对指定路由开启自定义敏感头
zuul.routes.[route].customSensitiveHeaders=true 
zuul.routes.[route].sensitiveHeaders=[这里设置要过滤的敏感头]
# 全局
zuul.sensitiveHeaders=[这里设置要过滤的敏感头]

# --Zuul的Http客户端支持Apache Http、Ribbon的RestClient和OkHttpClient,默认使用Apache HTTP客户端。--
# 启用Ribbon的RestClient
ribbon.restclient.enabled=true

# 启用OkHttpClient
ribbon.okhttp.enabled=true

# eureka地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

Zuul容错与回退

  • Zuul的Hystrix监控的粒度微服务,而不是某个API
  • Zuul提供了一个ZuulFallbackProvider接口(最新版本建议直接继承类FallbackProvider,新版增加异常处理),实现该接口就可以为Zuul实现回退功能
/**
 * @program: spring-cloud
 * @description: zuul容错与回退
 * @author: Mr.Tang
 * @create: 2018-06-20 14:25
 **/
@Component
public class ProductServiceFallbackProvider implements FallbackProvider {
    protected final Logger logger = LoggerFactory.getLogger(ProductServiceFallbackProvider.class);
    @Override
    public String getRoute() {
        // 注意: 这里是route的名称,不是服务的名称,否则无法起到回退作用 
        return "ribbon-consumer";
    }

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

            @Override
            public InputStream getBody() throws IOException {
                logger.info("ribbon-consumer: 服务不可以");
                return new ByteArrayInputStream("该服务暂不可用,请稍后重试!".getBytes());
            }

            @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 ClientHttpResponse fallbackResponse(Throwable cause) {
        if (cause != null && cause.getCause() != null) {
            String reason = cause.getCause().getMessage();
            logger.info("Excption {}",reason);
        }
        return fallbackResponse();
    }
}

路由重试(或者启动备用服务来分散压力)
网络等原因导致服务不可用,需要进行重试,zuul结合Spring Retry
添加Spring Retry依赖

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

开启重试
不使用重试,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断(在未刷新之前还是会访问到熔断中的服务降级方法)

#是否开启重试功能
zuul.retryable=true
#对当前服务的重试次数
ribbon.MaxAutoRetries=2
#切换相同Server的次数
ribbon.MaxAutoRetriesNextServer=0

注意
断路器的其中一个作用就是防止故障或者压力扩散。当压力过大是,一个服务的宕机,路由将压力转向其它服务时有可能也会压垮其它服务。断路器的形式更像是提供一种友好的错误信息,提高服务之间的容错性。

Zuul过滤器

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型

  • PRE 路由之前。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTING 路由之时。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  • POST 路由之后。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR 在其他阶段发生错误时执行该过滤器。

Zuul中默认实现的Filter

类型 顺序 过滤器 功能
pre -3 ServletDetectionFilter 标记处理Servlet的类型
pre -2 Servlet30WrapperFilter 包装HttpServletRequest请求
pre -1 FormBodyWrapperFilter 包装请求体
route 1 DebugFilter 标记调试标志
route 5 PreDecorationFilter 处理请求上下文供后续使用
route 10 RibbonRoutingFilter serviceId请求转发
route 100 SimpleHostRoutingFilter url请求转发
route 500 SendForwardFilter forward请求转发
post 0 SendErrorFilter 处理有错误的请求响应
post 1000 SendResponseFilter 处理正常的请求响应

除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。自定义(PRE)如下:
ZuulFilter 是Zuul中核心组件,通过继承该抽象类,覆写几个关键方法达到自定义调度请求的作用

/**
 * @program: spring-cloud
 * @description: token验证
 * @author: Mr.Tang
 * @create: 2018-06-21 10:08
 **/
public class TokenFilter extends ZuulFilter{
    protected final Logger logger = LoggerFactory.getLogger(TokenFilter.class);
    @Override
    public String filterType() {
        //pre:路由之前
        //routing:路由中
        //post:路由后
        //error:发生错误时
        return "pre";
    }

    @Override
    public int filterOrder() {
        // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        // 是否执行该过滤器,此处为true,说明需要过滤
        return true;
    }

    @Override
    public Object run() {
        this.logger.info("This is pre-type zuul filter.");
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        String token = request.getParameter("token");
        if(StringUtils.isNotBlank(token)){
            requestContext.setSendZuulResponse(true); //对请求进行路由
            requestContext.setResponseStatusCode(200);
            requestContext.set("isSuccess", true);
            return null;
        }else{
            requestContext.setSendZuulResponse(false); //不对其进行路由
            requestContext.setResponseStatusCode(400);
            requestContext.setResponseBody("token is empty");
            requestContext.set("isSuccess", false);
            return null;
        }
    }
}
  • filterType()方法是该过滤器的类型;
  • filterOrder()方法返回的是执行顺序;
  • shouldFilter()方法则是判断是否需要执行该过滤器;
  • run()则是所要执行的具体过滤动作。

过滤器之间并不会直接进行通信,而是通过RequestContext来共享信息,RequestContext是线程安全的。
开启过滤器
在程序的启动类 添加 Bean

@Bean
public TokenFilter tokenFilter() {
    return new TokenFilter();
}

禁用过滤器
格式为:zuul.[filter-name].[filter-type].disable=true 如:

zuul.FormBodyWrapperFilter.pre.disable=true

@EnableZuulServer和@EnableZuulProxy

  • @EnableZuulProxy包含@EnableZuulServer的功能,不会自动加载任何代理过滤器。
  • 当我们需要运行一个没有代理功能的Zuul服务,或者有选择的开关部分代理功能时,那么需要使用 @EnableZuulServer替代 @EnableZuulProxy。 这时候我们可以添加任何 ZuulFilter类型实体类都会被自动加载,

Zuul 高可用

Zuul 现在既然作为了对外的第一入口,那肯定不能是单节点,对于 Zuul 的高可用有以下两种方式实现。

  1. Eureka 高可用:部署多个 Zuul 节点,并且都注册于 Eureka(有一个严重的缺点:那就是客户端也得注册到 Eureka 上才能对 Zuul 的调用做到负载,这显然是不现实的。)
  2. 基于 Nginx 高可用:在调用 Zuul 之前使用 Nginx 之类的负载均衡工具进行负载,这样 Zuul 既能注册到 Eureka ,客户端也能实现对 Zuul 的负载
    nginx+zuul

结语

github上有关于Spring Cloud完整的部署。
其它相关文章
Spring cloud(1)-简介以及选择
Spring cloud(2)-服务发现(Eureka,Consul)
Spring cloud(3)-负载均衡(Feign,Ribbon)
Spring cloud(4)-熔断(Hystrix)
Spring cloud(5)-路由网关(Zuul)
Spring cloud(6)-配置管理及刷新(Config,Bus)
最后,给个 star 吧~
个人博客~
简书~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容