zoukankan      html  css  js  c++  java
  • 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进行处理

  • 相关阅读:
    Mysql数学函数不求人数据库专栏,MySQL
    下班以后就不要开电脑了,
    在ubuntu下用eclipsecdt调试memcached C language ITeye论坛
    mperham/sidekiq
    Reference C++ Reference
    请问一下哪有机械键盘的实体店可以体验一下? 外设 Chiphell 分享与交流用户体验的最佳平台 Powered by Discuz!
    Linux的cron和crontab iTech 博客园
    MIUI官方论坛 发烧友必刷的Android ROM
    机器学习视频
    crossover 海客谈, 季小军, 义乌人
  • 原文地址:https://www.cnblogs.com/xuyiqing/p/10885310.html
Copyright © 2011-2022 走看看