上文講到了Zuul的基本使用:html
http://www.javashuo.com/article/p-bvipxwmc-du.htmljava
自定義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 Gatewayspring
這裏對模擬的下單接口進行了過濾數據庫
訪問:http://localhost:9000/order/api/order/save?user_id=1&product_id=1apache
就會打印:攔截成功api
進一步編碼:緩存
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進行處理