SpringCloud學習記錄——網關(Zuul)
在微服務中網關的作用:
1.接收請求——與一般訪問相同
2.轉發請求
3.請求過濾
網關作用圖示:
從上圖可以看出,網關的作用實際上是對原始的請求方式插入了一層;在請求中間加入了一層網關,這樣使得外部的所有請求都導向網關,再由網關來轉發給具體服務處理。
加入網關的優勢:
1.請求統一:原本的請求方式可能會因為訪問的服務不同,出現訪問地址也不同的問題,插入網關之後,訪問地址統一指向網關;對於請求方而言,只需要記住一個訪問地址就可以了,省事了;
2.通用功能的統一處理:與請求相關的各種操作都可以在網關這裏先完成,例如請求過濾,權限控制等功能抽取出來進行統一處理;
3.負載均衡:網關本身是對請求進行了攔截的,拿到請求後自然就可以做一些處理,比如同一服務的多次請求就可以轉發給不同的服務器處理;
缺點:
1.請求鏈路變長:從請求參與角色來看,加入網關的情況下,請求鏈路就增加了一個節點,提高了請求的復雜度,比如當一個請求出現問題時,就需要額外考慮是否網關出問題了,原本沒有網關的時候自然不用考慮。
2.網關成為了微服務中的單點:通常分布式服務都會盡可能地避免服務單點現象,因為單點現象很容易造成個體影響整體的情況;比如單一網關的情況下,一旦網關服務掛掉,基本整個服務就癱瘓了;為了解決這個問題,可以設置網關集群,降低網關服務不可用的概率。
1.搭建網關服務
1.1SpringBoot項目
1.首先創建一個SpringBoot微服務(後面單獨寫,這裏就不寫了)
2.引入相關依賴
<!--zuul網關依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!--springcloud核心依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-core</artifactId> <version>2.0.0.M2</version> </dependency>
3.開啟網關服務——啟動類上加註解@EnableZuulProxy
開啟網關註解後,網關服務可以使用
@EnableEurekaClient @EnableZuulProxy //開啟Zuul的API網關服務功能 @SpringBootApplication public class GateOfZuul { public static void main(String[] args){ SpringApplication.run(GateOfZuul.class,args); } }
4.配置文件——application.yml
server: port: 5580 #端口號 spring: application: name: service-zuul #服務註冊中心測試名 zuul: routes: api-a: path: /ribbon/** serviceId: service-ribbon api-b: path: /feign/** serviceId: service-feign eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ #服務註冊中心
上述網關配置中;涉及網關的路由轉發的配置是zuul.routes下的2個
其中api-a、api-b表示的是不同的微服務,可以自定義,但最好和微服務名稱保持一致;path表示的是請求訪問的路徑;serviceId表示的則是微服務在註冊中心的註冊名稱;path與serviceId是一 一對應關系,表示的是“ 如果是/ribbon/**路徑下的請求,則跳轉到service-ribbon”
該配置的主要作用就是定義好網關的路由規則,將不同的請求路由到不同的服務中;
2.網關服務功能探究
網關存在的意義是為了提供服務,那麽身為一個網關,它所應該具有的能力有哪些呢?
1.接收請求:網關最終的能力就是接收請求,然後將請求轉發出去;那麽首先它就要有MVC的能力,則它需要實現servlet;
2.發出請求:網關需要將請求轉發到其他服務,那麽它就要有發送請求的能力,則它需要實現Http相關方法;
3.過濾請求:網關提供對請求的權限、日誌等操作,那麽他就要有過濾請求的能力,則它需要實現filter;
4.獲取服務列表:網關提供路由功能,那麽它就需要獲取到路由地址,從微服務的架構設置來看,即它需要從註冊中心拿到服務列表;
5.路由配置:網關實現路由操作,那麽就需要設置請求路徑與服務的對應關系;
2.1.網關接收請求
實際項目中,對於網關的開發,我們通常只需要配置過濾器以及配置文件中的路由轉發,那麽網關是如何做到接收請求的呢?
既然接收請求不需要我們來做,那麽就肯定是已經有人幫我們做了,由此可以推斷源碼中應該已經寫好了相關的處理。
那麽現在就來分析源碼中到底是如何做到的:
Servlet初始化
要實現網關訪問,則首先要配置好servlet;
SpringBoot中通過ServletRegistrationBean 這個類實現了對servlet的註冊;這裏可能還是沿用的springMVC的那套DispatcherServlet;至於具體怎麽註冊的等分析SpringBoot時再研究!
下面通過源碼來說明Zuul網關如何配置訪問路徑的
首先Zuul網關的配置類是ZuulServerAutoConfiguration;在該類中配置了Servlet及過濾規則
ZuulServerAutoConfiguration中配置了servlet及過濾規則;該類中通過ZuulServlet對根路徑/進行過濾;
@Bean @ConditionalOnMissingBean( name = {"zuulServlet"} ) public ServletRegistrationBean zuulServlet() { ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean(new ZuulServlet(), new String[]{this.zuulProperties.getServletPattern()}); servlet.addInitParameter("buffer-requests", "false"); return servlet; } /*其中攔截規則是在zuulProperties配置的*/ public String getServletPattern() { String path = this.servletPath; if (!path.startsWith("/")) { path = "/" + path; } if (!path.contains("*")) { path = path.endsWith("/") ? path + "*" : path + "/*"; } return path; }
處理器映射器與處理器適配器為默認配置,無需處理
繼續分析這個配置類,可以看到有ZuulController這個配置
@Bean public ZuulController zuulController() { return new ZuulController(); }
到這裏可以看出,在網關這裏,zuul組件將所有的請求都會走到ZuulController中,由它來統一處理;處理方法為handleRequest方法
當一個url請求訪問網關時,服務器會來到ZuulController這個類中:
public class ZuulController extends ServletWrappingController { /*從這裏可以看出,zuulController在初始化時,會傳入zuulServlet這個實例*/ public ZuulController() { this.setServletClass(ZuulServlet.class); this.setServletName("zuul"); this.setSupportedMethods((String[])null); } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView var3; try { var3 = super.handleRequestInternal(request, response); } finally { RequestContext.getCurrentContext().unset(); } return var3; } }
在zuulController中,只有handleRequest這一個方法來處理請求,而該方法最終調用的是其父類ServletWrappingController中的方法
public class ServletWrappingController extends AbstractController implements BeanNameAware, InitializingBean, DisposableBean { @Nullable private Class<? extends Servlet> servletClass; @Nullable private String servletName; private Properties initParameters = new Properties(); @Nullable private String beanName; @Nullable private Servlet servletInstance; public ServletWrappingController() { super(false); } public void setServletClass(Class<? extends Servlet> servletClass) { this.servletClass = servletClass; } public void setServletName(String servletName) { this.servletName = servletName; } public void setInitParameters(Properties initParameters) { this.initParameters = initParameters; } public void setBeanName(String name) { this.beanName = name; } public void afterPropertiesSet() throws Exception { if (this.servletClass == null) { throw new IllegalArgumentException("‘servletClass‘ is required"); } else { if (this.servletName == null) { this.servletName = this.beanName; } this.servletInstance = (Servlet)ReflectionUtils.accessibleConstructor(this.servletClass, new Class[0]).newInstance(); this.servletInstance.init(new ServletWrappingController.DelegatingServletConfig()); } } /**zuulController處理請求最終調用的是這個方法
*其中servletInstance就是ZuulController實例化時一起實例化的zuulServlet
*可以看出最終請求做到了zuulServlet的service方法中
*/ protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { Assert.state(this.servletInstance != null, "No Servlet instance"); this.servletInstance.service(request, response); return null; } public void destroy() { if (this.servletInstance != null) { this.servletInstance.destroy(); } } private class DelegatingServletConfig implements ServletConfig { private DelegatingServletConfig() { } @Nullable public String getServletName() { return ServletWrappingController.this.servletName; } @Nullable public ServletContext getServletContext() { return ServletWrappingController.this.getServletContext(); } public String getInitParameter(String paramName) { return ServletWrappingController.this.initParameters.getProperty(paramName); } public Enumeration<String> getInitParameterNames() { return ServletWrappingController.this.initParameters.keys(); } } }
到這裏位置,url請求最終走到了ZuulServlet的service中,現在來分析url請求在zuulServlet中時如何走下去的
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { try { this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try {
//前置過濾器pre this.preRoute(); } catch (ZuulException var12) {
// this.error(var12); this.postRoute(); return; } try {
// route過濾器 this.route(); } catch (ZuulException var13) { this.error(var13); this.postRoute(); return; } try {
//後置過濾器 this.postRoute(); } catch (ZuulException var11) {
//錯誤處理方法 this.error(var11); } } catch (Throwable var14) { this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
this.zuulRunner.init(servletRequest, servletResponse);
}
從上面代碼可以看出,url請求會被init方法處理,而init方法實際調用的是zuulRunner的init方法;
之後,代碼會執行preRoute(),route(),postRoute()方法,而這三個方法實際就是我們做路由以及過濾等功能的切入點。
現在來分析這3個方法是如何讓我們實現過濾以及路由功能的;
首先進行代碼追蹤,看看這3個方法最後到底是如何運作的
/*在ZuulServlet中*/ void postRoute() throws ZuulException { this.zuulRunner.postRoute(); } void route() throws ZuulException { this.zuulRunner.route(); } void preRoute() throws ZuulException { this.zuulRunner.preRoute(); } void error(ZuulException e) { RequestContext.getCurrentContext().setThrowable(e); this.zuulRunner.error(); } /**可以看出最終這3個方法與init方法一樣,最終都走到了zuulRunner中了*/ /**在ZuulRunner中*/ public void postRoute() throws ZuulException { FilterProcessor.getInstance().postRoute(); } public void route() throws ZuulException { FilterProcessor.getInstance().route(); } public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute(); } public void error() { FilterProcessor.getInstance().error(); } /**可以看出方法統一由FilterProcessor來處理,而FilterProcessor是一個單例模式*/ /**在FilterProcessor類中*/ /**其他幾個方法基本相似,只是參數不同而已,這裏就不列出來了*/ public void postRoute() throws ZuulException { try { this.runFilters("post"); } catch (ZuulException var2) { throw var2; } catch (Throwable var3) { throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + var3.getClass().getName()); } } /** 這個方法通過參數判斷filter的不同類型,返回不同的filter集合列表;並執行相應的filter方法*/ public Object runFilters(String sType) throws Throwable { if (RequestContext.getCurrentContext().debugRouting()) { Debug.addRoutingDebug("Invoking {" + sType + "} type filters"); } // 根據過濾器類型,獲取過濾器列表。 boolean bResult = false; List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType); if (list != null) {
//依次調用過濾器 for(int i = 0; i < list.size(); ++i) { ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
//過濾器處理過程 Object result = this.processZuulFilter(zuulFilter); if (result != null && result instanceof Boolean) { bResult |= ((Boolean)result).booleanValue(); } } } return bResult; } /**FilterLoader類是用來加載filter的*/ public List<ZuulFilter> getFiltersByType(String filterType) { List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType); if (list != null) { return list; } else {
//獲取所有的過濾器;從filterRegistry List<ZuulFilter> list = new ArrayList(); Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters(); Iterator iterator = filters.iterator(); while(iterator.hasNext()) { ZuulFilter filter = (ZuulFilter)iterator.next();
//按照類型提取過濾器 if (filter.filterType().equals(filterType)) { list.add(filter); } } //過濾器重排序,按照過濾器filterOrder中定義的值
Collections.sort(list);
this.hashFiltersByType.putIfAbsent(filterType, list); return list;
}
}
/**FilterRegistry類————該類是用來存放filter的,通過spring,系統中的filter最終都會存放在這裏*/
/**從這裏可以看出,只有實現了ZuulFilter的過濾器才會被調用*/
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap();
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
自定義的filter的方法執行是在FilterProcessor類中的processZuulFilter方法中執行的
public Object processZuulFilter(ZuulFilter filter) throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); boolean bDebug = ctx.debugRouting(); String metricPrefix = "zuul.filter-"; long execTime = 0L; String filterName = ""; try { long ltime = System.currentTimeMillis(); filterName = filter.getClass().getSimpleName(); RequestContext copy = null; Object o = null; Throwable t = null; if (bDebug) { Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName); copy = ctx.copy(); } // 本方法的核心代碼就在這裏,運行filter ZuulFilterResult result = filter.runFilter(); ExecutionStatus s = result.getStatus(); execTime = System.currentTimeMillis() - ltime; switch(s) { case FAILED: t = result.getException(); ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime); break; case SUCCESS: o = result.getResult(); ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime); if (bDebug) { Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms"); Debug.compareContextState(filterName, copy); } } if (t != null) { throw t; } else { this.usageNotifier.notify(filter, s); return o; } } catch (Throwable var15) { if (bDebug) { Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + var15.getMessage()); } this.usageNotifier.notify(filter, ExecutionStatus.FAILED); if (var15 instanceof ZuulException) { throw (ZuulException)var15; } else { ZuulException ex = new ZuulException(var15, "Filter threw Exception", 500, filter.filterType() + ":" + filterName); ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime); throw ex; } } } /**ZuulFilter類中的runFilter()方法*/ public ZuulFilterResult runFilter() { ZuulFilterResult zr = new ZuulFilterResult(); if (!this.isFilterDisabled()) { // 判斷過濾器是否需要執行 if (this.shouldFilter()) { Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName()); try { //執行自定義過濾器中的run方法 Object res = this.run(); zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS); } catch (Throwable var7) { t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed"); zr = new ZuulFilterResult(ExecutionStatus.FAILED); zr.setException(var7); } finally { t.stopAndLog(); } } else { zr = new ZuulFilterResult(ExecutionStatus.SKIPPED); } } return zr; }processZuulFilter(ZuulFilter filter)
過濾器的分析就到此為止,現在來看看url在ZuulRunner類中是如何處理的
/**在ZuulRunner類中*/ /**在該方法中,url的請求與相應被封裝在了RequestContext中 * 其中RequestContext是一個單例的,且使用ThreadLocal進行處理過,能夠保證是線程安全的
* */ public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { RequestContext ctx = RequestContext.getCurrentContext(); if (this.bufferRequests) { ctx.setRequest(new HttpServletRequestWrapper(servletRequest)); } else { ctx.setRequest(servletRequest); } ctx.setResponse(new HttpServletResponseWrapper(servletResponse)); }
2.2路由配置
第一步:解析配置文件,將路由配置讀取到程序中
SpringCloud學習記錄——網關(Zuul)