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進行處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM