1. 程式人生 > >SpringCloud學習記錄——網關(Zuul)

SpringCloud學習記錄——網關(Zuul)

cas red lse erp bubuko init ebe 配置 影響

在微服務中網關的作用:

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)