在微服務架構中,後端服務每每不直接開放給調用端,而是經過一個API網關根據請求的url,路由到相應的服務。當添加API網關後,在第三方調用端和服務提供方之間就建立了一面牆,這面牆直接與調用方通訊進行權限控制,後將請求均衡分發給後臺服務端。前端
在微服務架構模式下後端服務的實例數通常是動態的,對於客戶端而言很難發現動態改變的服務實例的訪問地址信息。所以在基於微服務的項目中爲了簡化前端的調用邏輯,一般會引入 API Gateway 做爲輕量級網關,同時 API Gateway 中也會實現相關的認證邏輯從而簡化內部服務之間相互調用的複雜度。spring
一般而言不一樣的客戶端對於顯示時對於數據的需求是不一致的,好比手機端或者 Web 端又或者在低延遲的網絡環境或者高延遲的網絡環境。後端
所以爲了優化客戶端的使用體驗,API Gateway 能夠對通用性的響應數據進行裁剪以適應不一樣客戶端的使用需求。同時還能夠將多個 API 調用邏輯進行聚合,從而減小客戶端的請求數,優化客戶端用戶體驗.api
固然咱們還能夠針對不一樣的渠道和客戶端提供不一樣的API Gateway,對於該模式的使用由另一個你們熟知的方式叫Backend for front-end, 在Backend for front-end模式當中,咱們能夠針對不一樣的客戶端分別建立其BFF,進一步瞭解BFF能夠參考這篇文章:Pattern: Backends For Frontends安全
對於系統而言進行微服務改造一般是因爲原有的系統存在或多或少的問題,好比技術債務,代碼質量,可維護性,可擴展性等等。API Gateway的模式一樣適用於這一類遺留系統的改造,經過微服務化的改造逐步實現對原有系統中的問題的修復,從而提高對於原有業務響應力的提高。經過引入抽象層,逐步使用新的實現替換舊的實現。網絡
在Spring Cloud體系中, Spring Cloud Zuul就是提供負載均衡、反向代理、權限認證的一個API gateway。架構
Spring Cloud Zuul路由是微服務架構的不可或缺的一部分,提供動態路由,監控,彈性,安全等的邊緣服務。Zuul是Netflix出品的一個基於JVM路由和服務端的負載均衡器。app
使用代碼示例進行說明,這裏直接將gateway服務化.負載均衡
引入依賴:ide
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
配置文件:
spring.application.name=zuul-server server.port=8888 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
啓動類添加註解:
@SpringBootApplication @EnableZuulProxy public class ZuulServerDemoApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerDemoApplication.class, args); } }
這樣zuul就搭建成功了
和前面的項目進行結合,啓動前面的註冊中心和eureka的兩個提供者,再將zuul-server啓動,這樣就能夠進行測試了.訪問localhost:8888/eureka-service-producter/hello?name=wangzhi,這樣就是經過zuul來訪問提供者,而且多刷新幾回,能夠看到實現了負載均衡,均勻出現. 感興趣的能夠將消費者也啓動一下,而後訪問消費者看看會出現什麼狀況.額外說一下前面路徑中的eureka-service-producter其實就是訪問項目的spring.applicaiton.name,也就是註冊中心能夠看到的application,這個看清楚就沒問題.
到這裏,zuul網關的使用和自動轉發機制就完成了,下面說說zuul相對的高級知識.
Filter是Zuul的核心,用來實現對外服務的控制。Filter的生命週期有4個,分別是「PRE」、「ROUTING」、「POST」、「ERROR」,整個生命週期能夠用下圖來表示。
Zuul大部分功能都是經過過濾器來實現的,這些過濾器類型對應於請求的典型生命週期。
類型 | 順序 | 過濾器 | 功能 |
pre | -3 | ServletDetectionFilter | 標記助理server的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標記調試標誌 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉發 |
route | 100 | SimpleHostRoutingFilter | url請求轉發 |
route | 500 | SendForwardFilter | forward請求轉發 |
post | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
zuul.FormBodyWrapperFilter.pre.disable=true
其實也很簡單,繼承ZuulFilter就能夠了,重寫四個方法,可是要了解個四個方法的返回值分別表示的是什麼意思?
public class MyFilter extends ZuulFilter { /** * 定義filter的類型,有pre、route、post、error四種 * @return */ @Override public String filterType() { return "pre"; } /** * 定義filter的順序,數字越小表示順序越高,越先執行 * @return */ @Override public int filterOrder() { return 10; } /** * 表示是否須要執行該filter,true表示執行,false表示不執行 * @return */ @Override public boolean shouldFilter() { return true; } /** * filter須要執行的具體操做,好比說權限認證,過濾等等 * @return * @throws ZuulException */ @Override public Object run() throws ZuulException { return null; } }
舉個例子:
public class TokenFilter extends ZuulFilter { private final Logger logger = LoggerFactory.getLogger(TokenFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString()); // 獲取請求參數信息 String token = request.getParameter("token"); if (StringUtils.isNotBlank(token)) { //對請求進行路由 context.setSendZuulResponse(true); context.setResponseStatusCode(200); context.set("isSuccess", true); return null; } else { //不對其進行路由 context.setSendZuulResponse(false); context.setResponseStatusCode(400); context.setResponseBody("token is empty"); context.set("isSuccess", false); return null; } } }
要想在過濾器中起做用,還須要額外配置一個,在啓動類中添加過濾器的Bean
@Bean public TokenFilter tokenFilter(){ return new TokenFilter(); }
啓動註冊中心,提供者和修改後的zuul,此次訪問的時候就須要在路徑後面加上token=xx,不然請求會被攔截.「PRE」類型的Filter作不少的驗證工做,在實際使用中咱們能夠結合shiro、oauth2.0等技術去作鑑權、驗證。
咱們的後端服務出現異常的時候,咱們不但願將異常拋出給最外層,指望服務能夠自動進行一降級。Zuul給咱們提供了這樣的支持。當某個服務出現異常時,直接返回咱們預設的信息。
咱們經過自定義的fallback方法,而且將其指定給某個route來實現該route訪問出問題的熔斷處理。主要繼承ZuulFallbackProvider接口來實現,ZuulFallbackProvider默認有兩個方法,一個用來指明熔斷攔截哪一個服務,一個定製返回內容。
public interface ZuulFallbackProvider { /** * The route this fallback will be used for. * @return The route the fallback will be used for. */ public String getRoute(); /** * Provides a fallback response. * @return The fallback response. */ public ClientHttpResponse fallbackResponse(); }
實現類經過實現getRoute方法,告訴Zuul它是負責哪一個route定義的熔斷。而fallbackResponse方法則是告訴 Zuul 斷路出現時,它會提供一個什麼返回值來處理請求。後來Spring又擴展了此類,豐富了返回方式,在返回的內容中添加了異常信息,所以最新版本建議直接繼承類FallbackProvider。
案例:修改zuul-server-demo
建立HelloFallback
@Component public class HelloFallback implements FallbackProvider { private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class); /** * 返回指定要處理的 service。 * @return */ @Override public String getRoute() { return "eureka-service-producter"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { if (cause != null && cause.getCause() != null) { String reason = cause.getCause().getMessage(); logger.info("Excption {}",reason); } return fallbackResponse(); } public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("The service is unavailable.".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
進行測試,啓動註冊中心,兩個提供者,zuul. 輸入http://localhost:8888/eureka-service-producter/hello?name=wangzhi&token=wangzhi進行訪問,多刷新幾回能夠看到沒有問題,這個時候將一個提供者關閉,再次刷新能夠看到問題就出來了,返回The service is unavailable. 這個就是zuul的路由熔斷. 可是注意的是: ** Zuul 目前只支持服務級別的熔斷,不支持具體到某個URL進行熔斷。**
有時候由於網絡或者其它緣由,服務可能會暫時的不可用,這個時候咱們但願能夠再次對服務進行重試,Zuul也幫咱們實現了此功能,須要結合Spring Retry 一塊兒來實現。下面咱們以上面的項目爲例作演示。
添加依賴: <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> 添加配置: #是否開啓重試功能 zuul.retryable=true #對當前服務的重試次數 ribbon.MaxAutoRetries=2 #切換相同Server的次數 ribbon.MaxAutoRetriesNextServer=0
修改controller方法就好,只修改一個項目的就好 private Logger logger = Logger.getLogger(HelloController.class); @GetMapping("hello") public String hello(@RequestParam String name){ logger.info("request two name is "+name); try{ Thread.sleep(1000000); }catch ( Exception e){ logger.error(" hello two error",e); } return "hello," + name + "222222"; }
此次訪問,雖然還會返回The service is unavailable.可是能夠在改動的提供者項目的控制檯看到日誌打印了3遍,說明重試了兩次.
開啓重試在某些狀況下是有問題的,好比當壓力過大,一個實例中止響應時,路由將流量轉到另外一個實例,頗有可能致使最終全部的實例全被壓垮。說到底,斷路器的其中一個做用就是防止故障或者壓力擴散。用了retry,斷路器就只有在該服務的全部實例都沒法運做的狀況下才能起做用。這種時候,斷路器的形式更像是提供一種友好的錯誤信息,或者僞裝服務正常運行的假象給使用者。
不用retry,僅使用負載均衡和熔斷,就必須考慮到是否可以接受單個服務實例關閉和eureka刷新服務列表之間帶來的短期的熔斷。若是能夠接受,就無需使用retry。
對於微服務來講,zuul也是很重要的,因此在必要狀況下應該實現高可用,也就是搭建集羣,和前面搭建集羣同樣道理沒有什麼區別.爲了保證Zuul的高可用性,前端能夠同時啓動多個Zuul實例進行負載,在Zuul的前端使用Nginx或者F5進行負載轉發以達到高可用性。
上面就是zuul的所有內容的,之後有須要再進行補充.