上文讲到了 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 进行处理
来源: http://www.bubuko.com/infodetail-3064382.html