1. 程式人生 > >Spring Cloud技術分析(4)- spring cloud zuul

Spring Cloud技術分析(4)- spring cloud zuul

地址:http://tech.lede.com/

    spring cloud zuul是netflix提供的一個元件,功能類似於nginx,用於反向代理,可以提供動態路由、監控、授權、安全、排程等邊緣服務。

1. zuul是什麼

微服務場景下,每一個微服務對外暴露了一組細粒度的服務。客戶端的請求可能會涉及到一串的服務呼叫,如果將這些微服務都暴露給客戶端,那麼會增加客戶端程式碼的複雜度。

參考GOF設計模式中的Facade模式,將細粒度的服務組合起來提供一個粗粒度的服務,所有請求都匯入一個統一的入口,那麼整個服務只需要暴露一個api,對外遮蔽了服務端的實現細節,也減少了客戶端與伺服器的網路呼叫次數。這就是api gateway。

api-gateway

有了api gateway之後,一些與業務關係並不大的通用處理邏輯可以從api gateway中剝離出來,api gateway僅僅負責服務的編排與結果的組裝。

Spring Cloud Netflix的Zuul元件可以做反向代理的功能,通過路由定址將請求轉發到後端的粗粒度服務上,並做一些通用的邏輯處理。

zull-loadbalancer

總結Zuul的作用:

  • 動態路由
  • 監控
  • 安全
  • 認證鑑權
  • 壓力測試
  • 金絲雀測試
  • 審查
  • 服務遷移
  • 負載剪裁
  • 靜態應答處理

2. 怎麼使用zuul

maven配置

在pom.xml中新增以下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.3.RELEASE</version>
</parent>

<dependencies>
	<dependency>
        <groupId>org.springframework.cloud</groupId
>
<artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

引入eureka的目的是為了後續與eureka做整合,使用serviceId做路由。

java程式設計

在啟動類新增@EnableZuulProxy註解

1
2
3
4
5
6
7
8
9
@EnableZuulProxy
@SpringCloudApplication
public class GatewayServer
{
    public static void main(String[] args)
{
        SpringApplication.run(GatewayServer.class, args);
    }
}

3. zuul對路由的配置

Zuul對路由跳轉的配置是在application.yml檔案中,定義了兩種對映方式:

  • url對映
  • serviceId對映

1. url直接對映

  • 單例項url直連
1
2
3
4
5
zuul:
	routes:
		wap:
			path: /wap/**
			url: http://192.168.1.10:8081
  • 多例項路由
1
2
3
4
5
6
7
8
9
10
11
zuul:
	routes:
		wap:
			path: /wap/**
			serviceId: wap
ribbon:
	eureka:
		enabled: false
wap:
	ribbon:
		listOfServers: http://192.168.1.10:8081, http://192.168.1.11:8081
  • forward跳轉到本地url
1
2
3
4
5
zuul:
	routes:
		wap:
			path: /wap/**
			url: forward:/wap

2. serviceId對映

  • 預設serviceId,serviceId:activity,路由規則:/activity/101 -> /101
1
2
3
zuul:
	routes:
		activity: /activity/**
  • 指定serviceId,serviceId:micro-activity,路由規則:/activity/101 -> /activity/101
1
2
3
4
5
6
zuul:
	routes:
		activity:
			path: /activity/** # 指定
			serviceId: micro-activity # 指定路由的serviceId
			stripPrefix: false

4. Cookie與頭資訊

預設情況下,Zuul在請求路由時,會過濾HTTP請求頭資訊中的一些敏感資訊,預設的敏感頭資訊通過zuul.sensitiveHeaders定義,包括Cookie、Set-Cookie、Authorization。

  • 設定全域性引數覆蓋預設值
1
2
zuul:
	sensitiveHeaders: # 使用空來覆蓋預設值
  • 指定路由的引數配置
1
2
3
4
zuul:
	routes:
		[route]:
			customSensitiveHeaders: true # 對指定路由開啟自定義敏感頭
1
2
3
4
zuul:
	routes:
		[route]:
			sensitiveHeaders: # 對指定路由的敏感頭設定為空

5. zuul的關鍵知識點

filter是Zuul的核心,用來實現對外服務的控制。filter的生命週期有4個,分別是”pre”、”route”、”post”、”error”,整個生命週期可以用下圖來表示。

zuul-life

具體的程式碼是在ZuulServletFilter類中,從實現可以看出,error可以在所有階段捕獲異常後執行,但是當post階段處理中出現異常不會再回到post階段,那麼這就需要保證在post階段不要有異常,因為一旦有異常後就不會走post執行SendErrorFilter了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class ZuulServletFilter implements Filter {
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		try {
			init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
			try {
				preRouting();
			} catch (ZuulException e) {
				error(e);
				postRouting();
				return;
			}

			// Only forward onto to the chain if a zuul response is not being sent
			if (!RequestContext.getCurrentContext().sendZuulResponse()) {
				filterChain.doFilter(servletRequest, servletResponse);
				return;
			}

			try {
				routing();
			} catch (ZuulException e) {
				error(e);
				postRouting();
				return;
			}

			try {
				postRouting();
			} catch (ZuulException e) {
				error(e);
				return;
			}
		} catch (Throwable e) {
			error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
		} finally {
			RequestContext.getCurrentContext().unset();
		}
	}
}

zuul中預設實現的filter

型別順序過濾器功能
pre-3ServletDetectionFilter標記處理Servlet的型別
pre-2Servlet30WrapperFilter包裝HttpServletRequest請求
pre-1FormBodyWrapperFilter包裝請求體
route1DebugFilter標記除錯標誌
route5PreDecorationFilter處理請求上下文供後續使用
route10RibbonRoutingFilterserviceId請求轉發
route100SimpleHostRoutingFilterurl請求轉發
route500SendForwardFilterforward請求轉發
post0SendErrorFilter處理有錯誤的請求響應
post1000SendResponseFilter處理正常的請求響應

禁用指定的filter

可以在application.yml中配置需要禁用的filter,格式:zuul:[filter-name]:[filter-type]:disable:true

1
2
3
4
zuul:
	FormBodyWrapperFilter:
		pre:
			disable: true

自定義filter

使用java實現自定義filter,需要新增繼承於ZuulFilter的類,覆蓋其中的4個方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CustomerPreFilter extends ZuulFilter {
    @Override
    String filterType() {
        return "pre"; //定義filter的型別,有pre、route、post、error四種
    }

    @Override
    int filterOrder() {
        return 10; //定義filter的順序,數字越小表示順序越高,越先執行
    }

    @Override
    boolean shouldFilter() {
        return true; //表示是否需要執行該filter,true表示執行,false表示不執行
    }

    @Override
    Object run() {
        return null; //filter需要執行的具體操作
    }
}

6. zuul對動態語言的支援

zuul支援使用groovy語言來動態修改filter,它是基於jvm的語言,語法簡單並且很多與java類似。

需要先將groovy的jar包引入

1
2
3
4
5
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>2.4.9</version>
</dependency>

然後掃描groovy檔案

1
2
3
FilterLoader.getInstance().setCompiler(new GroovyCompiler());
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(internal, path);

FilterFileManager會對groovy檔案變更載入如記憶體,其實現是開啟了一個後臺執行緒,以指定間隔的時間長度管理檔案,具體的程式碼如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class FilterFileManager {
    public static void init(int pollingIntervalSeconds, String... directories) throws Exception, IllegalAccessException, InstantiationException {
        if (INSTANCE == null) INSTANCE = new FilterFileManager();
        INSTANCE.aDirectories = directories;
        INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds;
        INSTANCE.manageFiles();
        INSTANCE.startPoller();
    }

    void startPoller() {
        poller = new Thread("GroovyFilterFileManagerPoller") {
            public void run() {
                while (bRunning) {
                    try {
                        sleep(pollingIntervalSeconds * 1000);
                        manageFiles();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        poller.setDaemon(true);
        poller.start();
    }
}

最終呼叫的是FilterLoader的putFilter方法,是否需要重新載入檔案,判斷依據是看檔案是否已經載入到記憶體中,同時檔案是否被修改過。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class FilterLoader {
    public boolean putFilter(File file) throws Exception {
        String sName = file.getAbsolutePath() + file.getName();
        if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
            LOG.debug("reloading filter " + sName);
            filterRegistry.remove(sName);
        }
        ZuulFilter filter = filterRegistry.get(sName);
        if (filter == null) {
            Class clazz = COMPILER.compile(file);
            if (!Modifier.isAbstract(clazz.getModifiers())) {
                filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
                List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
                if (list != null) {
                    hashFiltersByType.remove(filter.filterType()); //rebuild this list
                }
                filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
                filterClassLastModified.put(sName, file.lastModified());
                return true;
            }
        }

        return false;
    }
}

groovy實現的filter,groovy語法格式與java相近:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class PreFilter extends ZuulFilter {
	@Override
	String filterType() {
		return "pre"
	}

	@Override
	int filterOrder() {
		return 1000
	}

	@Override
	boolean shouldFilter() {
		return true
	}

	@Override
	Object run() {
		return null
	}
}

7. 全域性異常處理

介紹一下Zuul的全域性異常處理的一種方式:新增一個型別為”error”的filter,將錯誤資訊寫入RequestContext,這樣SendErrorFilter就可以獲取錯誤資訊了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ErrorFilter extends ZuulFilter {
	@Override
	String filterType() {
		return FilterConstants.ERROR_TYPE
	}

	@Override
	int filterOrder() {
		return 10
	}

	@Override
	boolean shouldFilter() {
		return true
	}

	@Override
	Object run() {
		RequestContext context = getRequestContext()
		Throwable throwable = context.getThrowable()
		LOGGER.error("[ErrorFilter] error message: {}", throwable.getCause().getMessage())
		ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
		ctx.set("error.exception", throwable.getCause())
		return null
	}
}

8. 效能優化參考

1. 在application.yml檔案中配置執行緒數、緩衝大小

1
2
3
4
5
6
7
8
9
10
server:
	tomcat:
		max-threads: 128 # 最大worker執行緒
		min-spare-threads: 64 # 最小worker執行緒
	undertow:
		io-threads: 8 # IO執行緒數,預設為CPU核心數,最小為2
		worker-threads: 40 # 阻塞任務執行緒池,值設定取決於系統的負載,預設為io-threads * 8
		buffer-size: 512 # 每塊buffer的空間大小
		buffers-per-region: 10 # 每個區分配的buffer數量
		direct-buffers: 512 # 是否分配的直接記憶體

相關推薦

Spring Cloud技術分析4- spring cloud zuul

地址:http://tech.lede.com/    spring cloud zuul是netflix提供的一個元件,功能類似於nginx,用於反向代理,可以提供動態路由、監控、授權、安全、排程等邊緣服務。1. zuul是什麼微服務場景下,每一個微服務對外暴露了一組細粒度

Spring Cloud技術分析3- spring cloud sleuth

地址:http://tech.lede.com/1. 目的提供鏈路追蹤。通過sleuth可以很清楚的看出一個請求都經過了哪些服務。可以很方便的理清服務間的呼叫關係。視覺化錯誤。對於程式未捕捉的異常,可以在zipkin介面上看到。分析耗時。通過sleuth可以很方便的看出每個取

Spring Cloud技術分析1——服務治理

地址:http://tech.lede.com/    本文作為系列的第一篇正文,從Spring Cloud中的核心專案Spring Cloud Netflix入手,闡述了Spring Cloud Netflix的優勢,介紹了Spring Cloud Netflix進行服務治

Spring核心技術原理-3-Spring歷史版本變遷和如今的生態帝國

前幾篇: 前兩篇從Web開發史的角度介紹了我們在開發的時候遇到的一個個坑,然後一步步衍生出Spring Ioc和Spring AOP的概念雛形。Spring從2004年第一個正式版1.0 Final Released發展至今,儼然已經成為了一個生態帝國

spring cloud系列教程4--eureka註冊中心叢集配置,微服務註冊資訊完善

給大家推薦個靠譜的公眾號程式設計師探索之路,大家一起加油 ​   1.Eureka是什麼 Eureka是Netflix的一個子模組之一,AP設計原則。Eureka是一個以及Rest的服務,用於定位服務,以實現雲端中間層服務發現和故障轉移。服務註冊與發現對於微服務架構來

Spring原始碼分析4---BeanFactoryPostProcessor看見的不一定是真的

在第二編對BeanFactory的分析中,我們老能看見BeanFactoyPostProcessor的身影,那麼在這一節中,我們來詳細的討論一下BeanFactoryPostProcessor的程式碼結構,從中學習他的優秀之處;BeanFactoryPostProcessor

Spring的IOC分析源碼

width single def app ali instance exc net classpath   承接上節繼續,分析Ioc的工作原理,在典型的 IOC 場景中,容器創建了所有對象,並設置必要的屬性將它們連接在一起(同時一個叫DI“依賴註入”或DL“依賴查找”的概念

Spring 源碼分析--整體架構和環境搭建

spring 事件傳播 com 之間 環境搭建 core模塊 batis bsp 元數據 本系統分析的spring源碼版本為4.3.8。 (一)整體架構 這些模塊被分為以下幾個部分 (1)Core Container Core容器(核心容器)包含Core,Bean

Spring 源碼分析--自定義標簽的使用

div protected 不同 space xsd文件 不同的 handle body img 在之前的代碼分析中,Spring標簽的解析分為 默認標簽和自定義標簽兩種,前一篇文章分析了Spring中對默認標簽的解析過程。 本文將分析Spring中自定義標

Spring 源碼分析--bean的加載整體分析

mean ash rep end on() 靈活性 available iat cleanup 通過前面的分析,我們結束了對XML配置文件的解析,接下來將進行bean加載的分析。對於加載bean的功能,在Spring中的調用方式為: 或者 MyTest

Spring 源碼分析--容器的功能擴展

use abs 提取 ext troy sha 根據 idc owb 經過前面幾篇的分析,相信大家對Spring中容器功能有了簡單的了解,在前面的章節中我們一直以BeanFactory接口以及它的默認實現類XmlBeanFactory為例進行分析。但是,Spring

Spring 源碼分析--AOP

see nbsp 完成 owa new util 定義 sep ret 我們知道,使用面向對象編程(OOP)有一些弊端,當需要為多個不具有繼承關系的對象引入同一個公共行為時,例如日誌,安全檢測等,我們只有在每個對象裏引用公共行為,這樣程序中就產生了大量的重復代碼,程

Spring框架學習4spring整合hibernate

location host mage too 自動 exception 4.0 數據庫連接 find 內容源自:spring整合hibernate spring整合註解形式的hibernate 這裏和上一部分學習一樣用了模板模式, 將hibernate開發流程封裝在O

Spring源碼分析獲取Document

規則 一個 自定義 trac tst nload indexof get t對象 摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。 這一篇開始進行Document加載了,XmlBeanFactoryR

Spring源碼分析AbstractBeanDefinition屬性

strac code 出現 shm main candidate 靜態變量 col aof 摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。 在上一篇中已經完成了XML文檔到GenericBeanDe

Spring源碼分析註冊解析的BeanDefinition

emp int style ash table 針對 全局變量 我們 名稱 摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。 對配置文件解析完成後,獲取的beanDefiniton已經可以進行使用了,

Spring源碼分析十三緩存中獲取單例bean

ould for 目的 存儲 不同 單例 color 正在 span 摘要:本文結合《Spring源碼深度解析》來分析Spring 5.0.6版本的源代碼。若有描述錯誤之處,歡迎指正。 介紹過FactoryBean的用法後,我們就可以了解bean加載的過程了。前面已

4Spring學習記錄---Spring_bean屬性配置細節

    這一節主要學了bean的詳細配置   特殊符號的解決方法: 如果字串裡有特殊符號需要用<![CDATA[]]> 引用其他bean 通過引用來使用bean。有時某類bean包含另一類bean。這裡要用到ref引用

Spring入門學習筆記4——JDBC的使用

目錄 Spring JDBC框架概覽 JdbcTemplate類 配置資料來源 資料訪問物件(Data Access Object,DAO) 執行SQL命令 Spring JDBC框架概覽 使用傳統的JDBC連線資料庫,需要編寫不必要的程式碼來處理

第三章 spring-bean之FactoryBeanRegistrySupport4

前言 從FactoryBeanRegistrySupport類的名字可以看出FactoryBeanRegistrySupport負責FactoryBean的註冊與支援。如果想知道FactoryBean相關的資料,請閱讀spring-bean中關於FactoryBean的解讀。