今天看spring cloud中的微服务网关,Spring Cloud Netflix下的路由器和过滤器:Zuul。
网关是什么?
网关是一个网络整体系统中的门户、入口。请求应该先请求网关微服务,通过网关微服务进行路径的路由,再定位到具体的服务节点上。
网关可以实现网络整体系统内部与外部的隔离,增强后端系统的安全性,不过没有网关我们也可以正常访问服务节点,那么说明网关并不是必须依赖的哈。
那么Zuul可以做什么呢?
一系列的过滤器。。。去看看源码,看来玩的就是这些实现类啦!
说起网关第一想到的肯定是路由地址和请求拦截。
上图所示,我们在client中发布了两个节点,下面试试新建一个网关zuul项目,访问zuul直接路由到client。
一、路由
1.建立StudyCloud-eureka-zuul子项目,既然实现网关与节点的交互,那肯定也是要注册到Eureka注册中心的。
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="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><parent><groupId>com.xing</groupId><artifactId>StudyCloud</artifactId><version>0.0.1-SNAPSHOT</version></parent><artifactId>StudyCloud-eureka-zuul</artifactId><version>0.0.1-SNAPSHOT</version><dependencies><!--web组件--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--eureka客户端--><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><!--Zuul限流保护--><dependency><groupId>com.marcosbarbero.cloud</groupId><artifactId>spring-cloud-zuul-ratelimit</artifactId><version>1.3.4.RELEASE</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
2.配置和client一样,注册到注册中心哈
spring.application.name=zuulserver.port=8883eureka.instance.instance-id=zuuleureka.client.service-url.defaultZone=http://172.23.13.15:8881/eureka/
3.使用@EnableZuulProxy注解开启zuul的api网关服务功能
packagecom.xing.study.cloud.zuul; importorg.springframework.boot.SpringApplication; importorg.springframework.boot.autoconfigure.SpringBootApplication; importorg.springframework.cloud.netflix.eureka.EnableEurekaClient; importorg.springframework.cloud.netflix.zuul.EnableZuulProxy; /*** @author xh*/publicclassZuulApplication { publicstaticvoidmain(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
4.启动Eureka Server、client、Zuul三个项目
查看Server可以看到都注册上去了。网关怎么访问呢?默认的zuul结合eureka会将注册到eureka的服务名作为访问的ContextPath。也就是说,通过zuul访问服务的,URL地址默认格式为:
http://zuulHostIp:port/要访问的服务名称/服务中的URL
服务名称:properties配置文件中的spring.application.name。
服务的URL:就是对应的服务对外提供的URL路径监听。
当然,我们还可以通过配置来自定义一些东西,比如我觉得eureka-client1有点长,我给他在application.properties中配置成client:
##自定义服务的应用路由eureka-client1换成client1zuul.routes.eureka-client1=/client1/**
把eureka-client1换成client1也能访问到了。
也可以指定一些路由机制,用于覆盖默认约定的规则:
##url用于配置符合path的请求路径路由到的服务地址。#zuul.routes.eureka-client1.url=http://127.0.0.1:8082/##path用于配置路径匹配规则。#可使用的通配符有:***??-单个字符*-任意多个字符,不包含多级路径**-任意多个字符,包含多级路径#zuul.routes.eureka-client1.path=/api/**## serviceId用于配置符合path的请求路径路由到的服务名称。#zuul.routes.eureka-client1.serviceId=eureka-application-service# 配置不被zuul管理的服务列表。多个服务名称使用逗号','分隔。#zuul.ignored-services=eureka-client1,zuul# 通配方式配置排除网关代理路径。所有符合ignored-patterns的请求路径都不被zuul网关代理。#zuul.ignored-patterns=/**/test/**# 通配符*配置排除列表相当于给所有新发现的服务默认排除zuul网关访问方式,只有配置了路由网关的服务才可以通过zuul网关访问#zuul.ignored-services=*# prefix URL pattern 前缀路由匹配# 配置请求路径前缀,所有基于此前缀的请求都由zuul网关提供代理。#zuul.prefix=/api
二、Zuul中的服务降级处理
zuul网关其底层使用ribbon来实现请求的路由,并内置Hystrix。点开pom依赖,可以看到zuul内置了熔断器hystrix来容错。
图片
1.实现FallbackProvider接口来提供网关fallback逻辑。
packagecom.xing.study.cloud.zuul; importorg.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; importorg.springframework.http.HttpHeaders; importorg.springframework.http.HttpStatus; importorg.springframework.http.MediaType; importorg.springframework.http.client.ClientHttpResponse; importorg.springframework.stereotype.Component; importjava.io.ByteArrayInputStream; importjava.io.IOException; importjava.io.InputStream; importjava.nio.charset.StandardCharsets; /*** zuul的服务降级处理* Zuul的fallback容错处理逻辑,只针对timeout异常处理,当请求被Zuul路由后,只要服务有返回(包括异常),都不会触发Zuul的fallback容错逻辑。* 因为对于Zuul网关来说,做请求路由分发的时候,结果由远程服务运算的。那么远程服务反馈了异常信息,Zuul网关不会处理异常,因为不确定异常是不是故意要返的* @author xh*/publicclassMyZuulFallbackProviderimplementsFallbackProvider { /*** return - 返回fallback处理哪一个服务。返回的是服务的名称*/publicStringgetRoute() { return"eureka-client1"; } publicClientHttpResponsefallbackResponse(Stringroute, Throwablecause) { returnnewClientHttpResponse() { /*** 设置响应的头信息*/publicHttpHeadersgetHeaders() { HttpHeadersheader=newHttpHeaders(); MediaTypemt=newMediaType("application", "json", StandardCharsets.UTF_8); header.setContentType(mt); returnheader; } /*** 设置响应体* zuul会将本方法返回的输入流数据读取,并通过HttpServletResponse的输出流输出到客户端。*/publicInputStreamgetBody() throwsIOException { Stringcontent="失败"; returnnewByteArrayInputStream(content.getBytes()); } /*** ClientHttpResponse的fallback的状态码 返回String*/publicStringgetStatusText() throwsIOException { returnthis.getStatusCode().getReasonPhrase(); } /*** ClientHttpResponse的fallback的状态码 返回HttpStatus*/publicHttpStatusgetStatusCode() throwsIOException { returnHttpStatus.OK; } /*** ClientHttpResponse的fallback的状态码 返回int*/publicintgetRawStatusCode() throwsIOException { returnthis.getStatusCode().value(); } /*** 回收资源方法* 用于回收当前fallback逻辑开启的资源对象的。* 不要关闭getBody方法返回的那个输入流对象。*/publicvoidclose() {} }; } }
网上搜了一个直接生成ClientHttpResponse。
2.关掉client服务,模拟节点宕机,测试异常是否会走fallback方法。
成功返回了我们定义的失败信息。
三、以上都是使用的源码提供的过滤器哈,我们也可以自定义一个过滤器。
packagecom.xing.study.cloud.zuul; importcom.netflix.zuul.ZuulFilter; importcom.netflix.zuul.context.RequestContext; importcom.netflix.zuul.exception.ZuulException; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.stereotype.Component; importjavax.servlet.http.HttpServletRequest; /*** 我的过滤器* @author rt*/publicclassMyZuulFilterextendsZuulFilter { privatestaticfinalLoggerlog=LoggerFactory.getLogger(MyZuulFilter.class); /*** 方法返回字符串数据,代表当前过滤器的类型。可选值有-pre, route, post, error。* pre - 前置过滤器,在请求被路由前执行,通常用于处理身份认证,日志记录等;* route - 在路由执行后,服务调用前被调用;* error - 任意一个filter发生异常的时候执行或远程服务调用没有反馈的时候执行(超时),通常用于处理异常;* post - 在route或error执行后被调用,一般用于收集服务信息,统计服务性能指标等,也可以对response结果做特殊处理。* @return pre, route, post, error。*/publicStringfilterType() { return"pre"; } /*** 用于为同filterType的多个过滤器定制执行顺序,返回值越小,执行顺序越优先。* @return 返回int数据*/publicintfilterOrder() { return0; } /*** 代表当前filter是否生效。* 默认值为false。返回true代表开启filter。* @return boolean*/publicbooleanshouldFilter() { returntrue; } /*** 具体的过滤执行逻辑。* 如pre类型的过滤器,可以通过对请求的验证来决定是否将请求路由到服务上;* 如post类型的过滤器,可以对服务响应结果做加工处理(如为每个响应增加footer数据)。* @return obj* @throws ZuulException e*/publicObjectrun() throwsZuulException { try { // 通过zuul,获取请求上下文RequestContextrc=RequestContext.getCurrentContext(); HttpServletRequestrequest=rc.getRequest(); log.info("MyZuulFilter.....method={},url={}",request.getMethod(),request.getRequestURL().toString()); ObjectaccessToken=request.getParameter("token"); if(accessToken==null){ log.warn("access token is empty"); //令zuul过滤该请求,不对其进行路由rc.setSendZuulResponse(false); //设置返回的错误码rc.setResponseStatusCode(403); } log.info("token ok 鉴权完毕, 记录日志完毕,统计性能"); }catch (Exceptione){ log.error("发生异常",e); } returnnull; } }
直接看没有传入token的结果:
这里也就是官网说的可以在这搞鉴权校验,识别每个请求的权限,拒绝不符合要求的请求。
四、Zuul网关的限流保护
减载 - 为每种类型的请求分配容量并丢弃超出限制的请求。这个可以用ratelimit来实现:
1.pom
<!--Zuul限流保护--><dependency><groupId>com.marcosbarbero.cloud</groupId><artifactId>spring-cloud-zuul-ratelimit</artifactId><version>1.3.4.RELEASE</version></dependency>
2.application.properties
#开启限流保护zuul.ratelimit.enabled=true#针对IP进行限流,不影响其他IPip-ORIGIN,url-URLzuul.ratelimit.default-policy.type=origin#全局限流配置#60s内请求超过3次,服务端就抛出异常,60s后可以恢复正常请求zuul.ratelimit.default-policy.limit=3zuul.ratelimit.default-policy.refresh-interval=60#局部限流配置#eureka-client1服务60s内请求超过3次,服务抛出异常。zuul.ratelimit.policies.eureka-client1.limit=3zuul.ratelimit.policies.eureka-client1.refresh-interval=60
3.同一IP在60秒内访问第3次的时候就会报错,等60秒就可以继续访问啦。
总结:
Zuul作为一个微服务网关,由一系列的过滤器构成,可以使用提供的过滤器来实现路由分发、服务降级、限流等功能,也可以自定义拦截器来实现鉴权、日志等功能。底层使用ribbon来实现请求的路由,并内置Hystrix,可选择性提供网关fallback逻辑。
END