前言
欢迎来到菜鸟SpringCloud实战入门系列(SpringCloudForNoob),该系列通过层层递进的实战视角,来一步步学习和理解SpringCloud。
本系列适合有一定Java以及SpringBoot基础的同学阅读。
每篇文章末尾都附有本文对应的Github源代码,方便同学调试。
Github仓库地址:
菜鸟SpringCloud实战入门系列
你可以通过以下两种途径查看菜鸟SpringCloud实战入门系列:
- 关注我的公众号:Rude3Knife 点击公众号下方:技术推文——SpringCloud
前文回顾:
- ...更多文章请查看上方:菜鸟SpringCloud入门实战专栏导航页
实战版本
- SpringBoot:2.0.3.RELEASE
- SpringCloud:Finchley.RELEASE
-----正文开始-----
[菜鸟SpringCloud实战入门]第九章:服务网关Zuul体验
服务网关 Zuul
Zuul介绍
外部的应用如何来访问内部各种各样的微服务呢?在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
这个API网关便是Spring Cloud Zuul
Zuul提供动态路由,监控,弹性,安全等的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
Zuul与Ribbon以及Eureka配合:
Zuul、Ribbon以及Eureka结合可以实现智能路由和负载均衡的功能;网关将所有服务的API接口统一聚合,统一对外暴露。外界调用API接口时,不需要知道微服务系统中各服务相互调用的复杂性,保护了内部微服务单元的API接口;网关可以做用户身份认证和权限认证,防止非法请求操作API接口;网关可以实现监控功能,实时日志输出,对请求进行记录;网关可以实现流量监控,在高流量的情况下,对服务降级;API接口从内部服务分离出来,方便做测试。
Zuul通过Servlet来实现,通过自定义的ZuulServlet来对请求进行控制。核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列过滤器。Zuul采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。
Zuul生命周期如下图。
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
Zuul中默认实现的Filter:
图片来自:
在yml中可以禁用指定的Filter:
zuul: FormBodyWrapperFilter: pre: disable: true复制代码
新建Zuul子模块
本章需要用到之前章节的的模块:
- eureka(eureka):第一章
- eureka-hi(服务提供者):第二章
我们新建子模块(新建模块如有疑惑请看教程第一章),名为zuul
修改pom.xml:
复制代码 4.0.0 zuul 0.0.1-SNAPSHOT zuul jar Demo project for Spring Boot com.pricemonitor springcloud 0.0.1-SNAPSHOT org.springframework.cloud spring-cloud-starter-netflix-zuul
在主类中加上@EnableZuulProxy:
@SpringBootApplication@EnableZuulProxypublic class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); }}复制代码
修改yml文件:
- 设置端口为8773
- 在eureka注册
- 设置个producer路由,把/producer/映射到service-hi的服务
spring: application: name: gateway-service-zuulserver: port: 8773eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/zuul: routes: hello: path: /producer/** serviceId: service-hi复制代码
单机zuul运行
先后运行eureka,eureka-hi和zuul
使用官网来访问8763上eureka-hi提供的服务:
得到:
说明网关正确转发了请求
zuul服务集群模拟
为了更好的模拟服务集群,我们再新开一个eureka-hi,端口号为8764,只需要修改yml的配置文件,然后重新运行即可。
你会看到来自8763和8764随机提供的服务:
zuul默认路由规则
Zuul其实已经代理所有注册到Eureka Server的微服务,并且Zuul的路由规则如下:http://ZUUL_HOST:ZUUL_PORT/
微服务在Eureka上的serviceId/**会被转发到serviceId对应的微服务。
我们试验一下, :
zuul自定义filter
最上面的简介中我们提到,Zuul中有默认实现的Filter,当然也可以实现自己的自定义Filter,用来完成一些鉴权、流量转发、请求统计等工作。我们来实现一个自定义filter。
我们假设有这样一个场景,因为服务网关应对的是外部的所有请求,为了避免产生安全隐患,我们需要对请求做一定的限制,比如请求中含有Token便让请求继续往下走,如果请求不带Token就直接返回并给出提示。
新建TokenFilter类:
package com.pricemonitor.zuul;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 javax.servlet.http.HttpServletRequest;public class TokenFilter extends ZuulFilter { private final Logger logger = LoggerFactory.getLogger(TokenFilter.class); @Override public String filterType() { // 可以在请求被路由之前调用 return "pre"; } @Override public int filterOrder() { // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低 return 0; } @Override public boolean shouldFilter() { // 是否执行该过滤器,此处为true,说明需要过滤 return true; } @Override public Object run() { 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 { ctx.setSendZuulResponse(false); //不对其进行路由 ctx.setResponseStatusCode(400); ctx.setResponseBody("token is empty"); ctx.set("isSuccess", false); return null; } }}复制代码
随后,在启动类注入这个bean
@SpringBootApplication@EnableZuulProxypublic class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } @Bean public TokenFilter tokenFilter() { return new TokenFilter(); }}复制代码
运行eureka注册中心,eureka-hi服务提供者,zuul网关三个子模块。
不带token访问:
带着token访问:
通过上面这例子我们可以看出,我们可以使用“PRE”类型的Filter做很多的验证工作,在实际使用中我们可以结合shiro、oauth2.0等技术去做鉴权、验证。
网关路由熔断
之前我们学过hystrix的熔断,主要针对的是内部服务互相调用的熔断,而zuul的熔断则主要服务于外部接口调用。
注意:Zuul 目前只支持服务级别的熔断,不支持具体到某个URL进行熔断。
实现:主要是通过继承FallbackProvider(之前继承的是ZuulFallbackProvider:默认有两个方法,一个用来指明熔断拦截哪个服务,一个定制返回内容。)
网关路由重试
可以配合Spring Retry,来对暂时不可用的服务进行重试。
注意: 开启重试在某些情况下是有问题的,比如当压力过大,一个实例停止响应时,路由将流量转到另一个实例,很有可能导致最终所有的实例全被压垮。说到底,断路器的其中一个作用就是防止故障或者压力扩散。用了retry,断路器就只有在该服务的所有实例都无法运作的情况下才能起作用。这种时候,断路器的形式更像是提供一种友好的错误信息,或者假装服务正常运行的假象给使用者。
不用retry,仅使用负载均衡和熔断,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断。如果可以接受,就无需使用retry。
本章代码
参考
-----正文结束-----
菜鸟SpringCloud实战入门专栏全导航:通过以下两种途径查看
- 关注我的公众号:Rude3Knife 点击公众号下方:技术推文——SpringCloud
关注我
我是蛮三刀把刀,后端开发。主要关注后端开发,数据安全,爬虫等方向。微信:yangzd1102
Github:
个人博客:
- CSDN:
- 知乎:
- 简书:
- 掘金:
原创博客主要内容
- Java知识点复习全手册
- Leetcode算法题解析
- 剑指offer算法题解析
- SpringCloud菜鸟入门实战系列
- SpringBoot菜鸟入门实战系列
- Python爬虫相关技术文章
- 后端开发相关技术文章
个人公众号:Rude3Knife
如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~