Spring Cloud(7):Zuul自定义过滤器和接口限流
上文讲到了Zuul的基本使用:
https://www.cnblogs.com/xuyiqing/p/10884860.html
自定义Zuul过滤器:
package org.dreamtech.apigateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; /** * 登陆过滤器 */ @Component public class LoginFilter extends ZuulFilter { /** * 设置过滤器类型 * * @return String */ @Override public String filterType() { //设置为前置过滤器 return PRE_TYPE; } /** * 过滤器顺序:值越小,越先执行 * * @return int */ @Override public int filterOrder() { //不能是最先执行的 return 4; } /** * 过滤是否生效 * * @return boolean */ @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); return "/order/api/order/save".equalsIgnoreCase(request.getRequestURI()); } /** * shouldFilter返回True则执行此方法,用于写业务逻辑 * * @return Object * @throws ZuulException 异常 */ @Override public Object run() throws ZuulException { System.out.println("拦截成功"); return null; } }
启动项目:Eureka Server->Product-Service->Order-Service->Api Gateway
这里对模拟的下单接口进行了过滤
访问:http://localhost:9000/order/api/order/save?user_id=1&product_id=1
就会打印:拦截成功
进一步编码:
package org.dreamtech.apigateway.filter; 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.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; /** * 登陆过滤器 */ @Component public class LoginFilter extends ZuulFilter { /** * 设置过滤器类型 * * @return String */ @Override public String filterType() { //设置为前置过滤器 return PRE_TYPE; } /** * 过滤器顺序:值越小,越先执行 * * @return int */ @Override public int filterOrder() { //不能是最先执行的 return 4; } /** * 过滤是否生效 * * @return boolean */ @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); return "/order/api/order/save".equalsIgnoreCase(request.getRequestURI()); } /** * 写业务逻辑 * * @return Object * @throws ZuulException 异常 */ @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { token = request.getParameter("token"); } //登陆校验逻辑 if (StringUtils.isBlank(token)) { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; } }
实际中,可以使用一种技术:JWT来做安全校验
这时候直接访问:http://localhost:9000/order/api/order/save?user_id=1&product_id=1
显示:401状态码(未授权)
访问:http://localhost:9000/order/api/order/save?user_id=1&product_id=1&token=12345
显示:
{"code":0,"data":{"id":0,"productName":"\"iPhone1 data from port=8771\"","tradeNo":"29d834be-f375-4112-b0a1-ed10b8e8679d","price":1111,"createTime":"2019-05-19T04:26:19.008+0000","userId":1,"userName":null}}
成功
为了方便,我直接把token放在参数里面了,根据编码也可以把token放在HttpHeader里面
进一步的编码可以这样尝试:
@Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); //TODO 从Resdis缓存中拿到List<[URL]>代替"/order/api/order/save" if ("/order/api/order/save".equalsIgnoreCase(request.getRequestURI())) { return true; } if ("/order/api/order/delete".equalsIgnoreCase(request.getRequestURI())) { return true; } // ...... return false; }
高并发的情况下,接口限流是很有必要的:
类似地铁:上地铁需要排队,才可以有效地运输;如果一群人拥挤,反而效率不高
实际应用:比如某电商网站搞活动,某一时刻同时访问上万人,而MySQL最大连接数3000,这时候就要进行限制:最高只能由2500人同时参与活动
限流的方式:Nginx进行限流、网关限流等。这里进行网关限流
使用Google Guava框架:令牌桶原理
Demo:对订单服务进行限流
package org.dreamtech.apigateway.filter; import com.google.common.util.concurrent.RateLimiter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; @Component public class OrderLimiterFilter extends ZuulFilter { //每秒创建1000个令牌 private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000); @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { //最先执行 return -4; } @Override public boolean shouldFilter() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); //对订单接口进行限流 return "/order/api/order/save".equalsIgnoreCase(request.getRequestURI()); } @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); //非阻塞式获取令牌 if (!RATE_LIMITER.tryAcquire()) { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value()); } return null; } }
实际开发中,不只是要保证数据库和服务的高可用,也要保证网关不会挂掉:
Nginx可以和LVS组合实现高可用,不过这是运维要做的事情,我们Java程序员需要关心的是Zuul的高可用
于是想到部署Zuul集群:Zuul的集群搭建很简单,启动多个Zuul项目即可
可能有人会关心,如果Zuul是集群的方式,那么Guava的令牌桶如何实现共用?
后面会介绍Spring Cloud统一配置Config进行处理