微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService、ProductService、UserService...,为了让调用更简单,一般会在这些服务前端再封装一层,类似下面这样:

前面这一层俗称为“网关层”,其存在意义在于,将"1对N"问题 转换成了"1对1”问题,同时在请求到达真正的微服务之前,可以做一些预处理,比如:来源合法性检测,权限校验,反爬虫之类...

传统方式下,最土的办法,网关层可以人肉封装,类似以下示例代码:

LoginResult login(...){   //TODO 预处理...return userService.login();//调用用户服务的登录方法
}Product queryProduct(...){   //TODO 预处理...return productService.queryProduct();//调用产品服务的查询方法
}Order submitOrder(...){   //TODO 预处理...return orderService.submitOrder();//调用订单服务的查询方法
}

这样做,当然能跑起来,但是维护量大,以后各个微服务增加了新方法,都需要在网关层手动增加相应的方法封装,而spring cloud 中的zuul很好的解决了这一问题,示意图如下:

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

使用步骤:

一、添加zuul依赖的jar包

compile 'org.springframework.cloud:spring-cloud-starter-zuul'

二、application.yml里配置路由

zuul:routes:api-a:path: /api-user/**service-id: service-providersensitive-headers:api-b:path: /api-order/**service-id: service-consumer 

解释一下:上面这段配置表示,/api-user/开头的url请求,将转发到service-provider这个微服务上,/api-order/开头的url请求,将转发到service-consumer这个微服务上。

三、熔断处理

如果网关后面的微服务挂了,zuul还允许定义一个fallback类,用于熔断处理,参考下面的代码:

package com.cnblogs.yjmyzz.spring.cloud.study.gateway;import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
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;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;/*** Created by yangjunming on 2017/7/14.*/
@Component
public class ServiceConsumerFallbackProvider implements ZuulFallbackProvider {@Overridepublic String getRoute() {return "service-consumer";}@Overridepublic ClientHttpResponse fallbackResponse() {return new ClientHttpResponse() {@Overridepublic HttpStatus getStatusCode() throws IOException {return HttpStatus.OK;}@Overridepublic int getRawStatusCode() throws IOException {return this.getStatusCode().value();}@Overridepublic String getStatusText() throws IOException {return this.getStatusCode().getReasonPhrase();}@Overridepublic void close() {}@Overridepublic InputStream getBody() throws IOException {return new ByteArrayInputStream("Service-Consumer不可用".getBytes());}@Overridepublic HttpHeaders getHeaders() {HttpHeaders headers = new HttpHeaders();MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));headers.setContentType(mt);return headers;}};}
}

开发人员只要在getRoute这个方法里指定要处理的微服务实例,然后重写fallbackResponse即可。

此时,如果观察/health端点,也可以看到hystrix处于融断开启状态

四、ZuulFilter过滤器

过滤器是一个很有用的机制,下面分几种经典场景演示下:

4.1、token校验/安全认证

网关直接暴露在公网上时,终端要调用某个服务,通常会把登录后的token传过来,网关层对token进行有效性验证,如果token无效(或没传token),提示重新登录或直接拒绝。另外,网关后面的微服务,如果设置了spring security中的basic Auth(即:不允许匿名访问,必须提供用户名、密码),也可以在Filter中处理。参考下面的代码:

package com.cnblogs.yjmyzz.spring.cloud.study.gateway;import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;/*** Created by yangjunming on 2017/7/13.*/
@Component
public class AccessFilter extends ZuulFilter {private static Logger logger = LoggerFactory.getLogger(AccessFilter.class);@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();Object token = request.getParameter("token");//校验tokenif (token == null) {logger.info("token为空,禁止访问!");ctx.setSendZuulResponse(false);ctx.setResponseStatusCode(401);return null;} else {//TODO 根据token获取相应的登录信息,进行校验(略)}//添加Basic Auth认证信息ctx.addZuulRequestHeader("Authorization", "Basic " + getBase64Credentials("app01", "*****"));return null;}private String getBase64Credentials(String username, String password) {String plainCreds = username + ":" + password;byte[] plainCredsBytes = plainCreds.getBytes();byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);return new String(base64CredsBytes);}
}

Filter一共有4种类型,其常量值在org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 中定义

  // Zuul Filter TYPE constants -----------------------------------/*** {@link ZuulFilter#filterType()} error type.*/String ERROR_TYPE = "error";/*** {@link ZuulFilter#filterType()} post type.*/String POST_TYPE = "post";/*** {@link ZuulFilter#filterType()} pre type.*/String PRE_TYPE = "pre";/*** {@link ZuulFilter#filterType()} route type.*/String ROUTE_TYPE = "route";  

安全校验,一般放在请求真正处理之前,所以上面的示例filterType指定为pre,剩下的只要在shouldFilter()、run()方法中重写自己的逻辑即可。

4.2 动态修改请求参数

zuulFilter可以拦截所有请求参数,并对其进行修改,比如:终端发过来的数据,出于安全要求,可能是经过加密处理的,需要在网关层进行参数解密,再传递到后面的服务;再比如:用户传过来的token值,需要转换成userId/userName这些信息,再传递到背后的微服务。参考下面的run方法:

    public Object run() {try {RequestContext context = getCurrentContext();InputStream in = (InputStream) context.get("requestEntity");if (in == null) {in = context.getRequest().getInputStream();}String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));body = "动态增加一段内容到body中: " + body;byte[] bytes = body.getBytes("UTF-8");context.setRequest(new HttpServletRequestWrapper(getCurrentContext().getRequest()) {@Overridepublic ServletInputStream getInputStream() throws IOException {return new ServletInputStreamWrapper(bytes);}@Overridepublic int getContentLength() {return bytes.length;}@Overridepublic long getContentLengthLong() {return bytes.length;}});} catch (IOException e) {rethrowRuntimeException(e);}return null;}

更多filter的示例,可以参考官网:https://github.com/spring-cloud-samples/sample-zuul-filters

4.3 灰度发布(Gated Launch/Gray Release) 

大型分布式系统中,灰度发布是保证线上系统安全生产的重要手段,一般的做法为:从集群中指定一台(或某几台)机器,每次做新版本发布前,先只发布这些机器上,先观察一下是否正常,如果稳定运行后,再发布到其它机器。这种策略(相当于按部分节点来灰度),大多数情况下可以满足要求,但是有一些特定场景,可能不太适用。

比如:笔者所在的“美味不用等”公司,主要B端用户为各餐饮品牌的商家,多数情况下,如果新上了一个功能,希望找一些规模较小的餐厅做试点,先看看上线后的运行情况,如果运行良好,再推广到其它商家。

再比如:后端服务有N多个版本在同时运行,比如V1、V2,现在新加了一个V3版本(这在手机app应用中很常见),希望只有部分升级了app的用户访问最新的V3版本服务,其它用户仍然访问旧版本,待系统稳定后,再大规模提示用户升级。

对于这些看上去需求各异的灰度需求,其实本质是一样的:将请求(根据参数内容+业务规则),将其转向到特定的灰度机器上。Spring Cloud MicroService中有一个metadata-map(元数据)设置,可以很好的满足这类需求。

首先要引入一个jar包:(这是github上开源的一个项目ribbon-discovery-filter-spring-cloud-starter)

compile 'io.jmnarloch:ribbon-discovery-filter-spring-cloud-starter:2.1.0'

示例如下:

在各个服务的application.yml中设置以下metadata-map

eureka:instance:metadata-map:gated-launch: false

即:所有节点发布后,默认灰度模式为false。然后把特定的灰度机器上的配置,该参数改成true(表明这台机器是用于灰度验证的)。

然后在ZuulFilter中参考下面的代码:

    @Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();Object token = request.getParameter("token");//校验tokenif (token == null) {logger.info("token为空,禁止访问!");ctx.setSendZuulResponse(false);ctx.setResponseStatusCode(401);return null;} else {//TODO 根据token获取相应的登录信息,进行校验(略)//灰度示例RibbonFilterContextHolder.clearCurrentContext();if (token.equals("1234567890")) {RibbonFilterContextHolder.getCurrentContext().add("gated-launch", "true");} else {RibbonFilterContextHolder.getCurrentContext().add("gated-launch", "false");}}//添加Basic Auth认证信息ctx.addZuulRequestHeader("Authorization", "Basic " + getBase64Credentials("app01", "*****"));return null;}

注意18-23行,这里演示了通过特定的token参数值,将请求引导到gated-lanuch=true的机器上。(注:参考这个原理,大家可以把参数值,换成自己的version-版本号,shopId-商家Id之类)。只要请求参数中的token=1234567890,这次请求就会转发到灰度节点上。

如果有朋友好奇这是怎么做到的,可以看下io.jmnarloch.spring.cloud.ribbon.predicate.MetadataAwarePredicate 这个类:

    @Overrideprotected boolean apply(DiscoveryEnabledServer server) {final RibbonFilterContext context = RibbonFilterContextHolder.getCurrentContext();final Set<Map.Entry<String, String>> attributes = Collections.unmodifiableSet(context.getAttributes().entrySet());final Map<String, String> metadata = server.getInstanceInfo().getMetadata();return metadata.entrySet().containsAll(attributes);}  

大致原理就是拿上下文中,开发人员设置的属性 与 服务节点里的metadata-map 进行比较,如果metadata-map中包括开发人员设置的属性,就返回成功(即:选择这台服务器)

示例源码:https://github.com/yjmyzz/spring-cloud-demo 

转载于:https://www.cnblogs.com/yjmyzz/p/spring-cloud-zuul-demo.html

spring cloud 学习(6) - zuul 微服务网关相关推荐

  1. Spring Cloud 学习笔记一 微服务架构

    文章目录 前言 一.微服务 二.微服务的特点 1.小型化 2.自治化 3. 扁平化 4. 轻量级设计 5. 渐进式设计 微服务架构和整体式架构的区别 微服务架构和SOA的比较 区别一:微服务通信的轻量 ...

  2. spring cloud+dotnet core搭建微服务架构:Api网关(三)

    前言 国庆假期,一直没有时间更新. 根据群里面的同学的提问,强烈推荐大家先熟悉下spring cloud.文章下面有纯洁大神的spring cloud系列. 上一章最后说了,因为服务是不对外暴露的,所 ...

  3. 【译文】用Spring Cloud和Docker搭建微服务平台

    by Kenny Bastani Sunday, July 12, 2015 转自:http://www.kennybastani.com/2015/07/spring-cloud-docker-mi ...

  4. 用Spring Cloud和Docker搭建微服务平台

    This blog series will introduce you to some of the foundational concepts of building a microservice- ...

  5. spring cloud+.net core搭建微服务架构:Api授权认证(六)

    前言 这篇文章拖太久了,因为最近实在太忙了,加上这篇文章也非常长,所以花了不少时间,给大家说句抱歉.好,进入正题.目前的项目基本都是前后端分离了,前端分Web,Ios,Android...,后端也基本 ...

  6. 基于Spring Cloud及K8S构建微服务应用

    摘要 广发证券蔡波斯先生通过三个大方向来为我们分享基于Spring Cloud及K8S构建微服务应用. 内容来源:2017年6月10日,广发证券蔡波斯在"Spring Cloud中国社区技术 ...

  7. Zuul微服务网关、容错与监控、Zuul路由端点、路由配置、Zuul上传文件、Zuul过滤器、Zuul异常处理、Zuul回退、Zuul聚合微服务

    一.为什么要使用微服务网关 二.Zuul 1.编写Zuul微服务网关 2.Zuul的Hystrix容错与监控 3.Zuul的路由端点 4.路由配置 1.自定义指定微服务的访问路径 2.忽略指定微服务 ...

  8. 软件架构-zuul微服务网关(上)

    不知不觉,文章都写100篇了,从0到1,从1到100,感谢老铁们的支持,不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才能完成一个业务需求.比如一个电影购票的收集APP,可 ...

  9. spring cloud+dotnet core搭建微服务架构:配置中心续(五)

    前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...

最新文章

  1. 一款优秀的翻译软件DeepL
  2. 使用nginx实现一个主机部署多域名指向不同docker项目
  3. 多列集合的索引器实现
  4. jrockit_JRockit JRCMD教程
  5. 基于webpack的react脚手架
  6. PowerShell变量——PowerShell三分钟(七)
  7. 弹性地基梁板实用计算_建筑地基基础设计规范要点
  8. python技巧——使用list comprehension生成素数(prime number)
  9. 【Java SE】数组的深入理解
  10. [USACO2008 Mar]土地购买
  11. 大数据发展与情报资源整合的关系探索
  12. 转--关于引擎优化的相关资料
  13. 【ESP 保姆级教程】疯狂传感器篇 —— 案例:ESP8266 + MQ3酒精传感器 + webserver(局域网内曲线变化图)
  14. 人类一败涂地做图教程_人类一败涂地皮肤怎么弄 人类一败涂地皮肤制作教程...
  15. Windows Mobile 播放声音文件
  16. 程序设计-在校整理-06 最常公共子序列与子串+2048小游戏+KNN简单实例
  17. 7-5 手机号码 (20 分)
  18. 谷本系数/相似度的计算和分子指纹
  19. 时序预测 | python实现仿生算法优化LSTM时间序列预测(全网最全仿生算法)
  20. 文件路径问题解决方案

热门文章

  1. java中paint_java中paint()的具体用法是什么?
  2. 电商库存锁_解密 Redis 助力双 11 背后电商秒杀系统
  3. Node.js nrm
  4. CUDA TOOlkit Programming Guide 2. Programming Model
  5. 22 File Links: Hard and Symbolic
  6. Python MD5
  7. scrapy.response
  8. DataFrame 排序
  9. html获取元素坐标,js 获取元素相对于页面的坐标(示例)
  10. mysql主从北_mysql主从复制(超简单)