目錄javascript
Spring Cloud爲開發人員提供了快速構建分佈式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智能路由,微代理,控制總線)。分佈式系統的協調致使了樣板模式, 使用Spring Cloud開發人員能夠快速地支持實現這些模式的服務和應用程序。他們將在任何分佈式環境中運行良好,包括開發人員本身的筆記本電腦,裸機數據中心,以及Cloud Foundry等託管平臺。php
版本:Dalston.RELEASEcss
Spring Cloud專一於提供良好的開箱即用經驗的典型用例和可擴展性機制覆蓋。html
分佈式/版本化配置java
服務註冊和發現node
路由mysql
service - to - service調用react
負載均衡linux
斷路器ios
分佈式消息傳遞
雲原生是一種應用開發風格,鼓勵在持續交付和價值驅動開發領域輕鬆採用最佳實踐。相關的學科是創建12-factor Apps,其中開發實踐與交付和運營目標相一致,例如經過使用聲明式編程和管理和監控。Spring Cloud以多種具體方式促進這些開發風格,起點是一組功能,分佈式系統中的全部組件都須要或須要時輕鬆訪問。
許多這些功能都由Spring Boot覆蓋,咱們在Spring Cloud中創建。更多的由Spring Cloud提供爲兩個庫:Spring Cloud Context和Spring Cloud Commons。Spring Cloud上下文爲Spring Cloud應用程序(引導上下文,加密,刷新範圍和環境端點)的ApplicationContext
提供實用程序和特殊服務。Spring Cloud Commons是一組在不一樣的Spring Cloud實現中使用的抽象和經常使用類(例如Spring Cloud Netflix vs. Spring Cloud Consul)。
若是因爲「非法密鑰大小」而致使異常,而且您正在使用Sun的JDK,則須要安裝Java加密擴展(JCE)無限強度管理策略文件。有關詳細信息,請參閱如下連接:
將文件解壓縮到JDK / jre / lib / security文件夾(不管您使用的是哪一個版本的JRE / JDK x64 / x86)。
注意 |
Spring Cloud根據非限制性Apache 2.0許可證發佈。若是您想爲文檔的這一部分作出貢獻,或者發現錯誤,請在github中找到項目中的源代碼和問題跟蹤器。 |
Spring Boot對於如何使用Spring構建應用程序有一個見解:例如它具備常規配置文件的常規位置,以及用於常見管理和監視任務的端點。Spring Cloud創建在此之上,並添加了一些可能系統中全部組件將使用或偶爾須要的功能。
一個Spring Cloud應用程序經過建立一個「引導」上下文來進行操做,這個上下文是主應用程序的父上下文。開箱即用,負責從外部源加載配置屬性,還解密本地外部配置文件中的屬性。這兩個上下文共享一個Environment
,這是任何Spring應用程序的外部屬性的來源。Bootstrap屬性的優先級高,所以默認狀況下不能被本地配置覆蓋。
引導上下文使用與主應用程序上下文不一樣的外部配置約定,所以使用bootstrap.yml
application.yml
(或.properties
)代替引導和主上下文的外部配置。例:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: application: name: foo cloud: config: uri: ${SPRING_CONFIG_URI:http://localhost:8888}</span></span>
若是您的應用程序須要服務器上的特定於應用程序的配置,那麼設置spring.application.name
(在bootstrap.yml
或application.yml
)中是個好主意。
您能夠經過設置spring.cloud.bootstrap.enabled=false
(例如在系統屬性中)來徹底禁用引導過程。
若是您從SpringApplication
或SpringApplicationBuilder
構建應用程序上下文,則將Bootstrap上下文添加爲該上下文的父級。這是一個Spring的功能,即子上下文從其父進程繼承屬性源和配置文件,所以與不使用Spring Cloud Config構建相同上下文相比,「主」應用程序上下文將包含其餘屬性源。額外的財產來源是:
「bootstrap」:若是在Bootstrap上下文中找到任何PropertySourceLocators
,則可選CompositePropertySource
顯示爲高優先級,而且具備非空屬性。一個例子是來自Spring Cloud Config服務器的屬性。有關如何自定義此屬性源的內容的說明,請參閱 下文。
「applicationConfig:[classpath:bootstrap.yml]」(若是Spring配置文件處於活動狀態,則爲朋友)。若是您有一個bootstrap.yml
(或屬性),那麼這些屬性用於配置引導上下文,而後在父進程設置時將它們添加到子上下文中。它們的優先級低於application.yml
(或屬性)以及做爲建立Spring Boot應用程序的過程的正常部分添加到子級的任何其餘屬性源。有關如何自定義這些屬性源的內容的說明,請參閱下文。
因爲屬性源的排序規則,「引導」條目優先,但請注意,這些條目不包含來自bootstrap.yml
的任何數據,它具備很是低的優先級,但可用於設置默認值。
您能夠經過簡單地設置您建立的任何ApplicationContext
的父上下文來擴展上下文層次結構,例如使用本身的界面,或使用SpringApplicationBuilder
方便方法(parent()
,child()
和sibling()
)。引導環境將是您建立本身的最高級祖先的父級。層次結構中的每一個上下文都將有本身的「引導」屬性源(可能爲空),以免無心中將值從父級升級到其後代。層次結構中的每一個上下文(原則上)也能夠具備不一樣的spring.application.name
,所以若是存在配置服務器,則不一樣的遠程屬性源。普通的Spring應用程序上下文行爲規則適用於屬性解析:子環境中的屬性經過名稱和屬性源名稱覆蓋父項中的屬性(若是子級具備與父級名稱相同的屬性源,一個來自父母的孩子不包括在孩子中)。
請注意,SpringApplicationBuilder
容許您在整個層次結構中共享Environment
,但這不是默認值。所以,兄弟情境尤爲不須要具備相同的資料或財產來源,儘管它們與父母共享共同點。
能夠使用spring.cloud.bootstrap.name
(默認「引導」)或spring.cloud.bootstrap.location
(默認爲空)指定bootstrap.yml
(或.properties
)位置,例如在系統屬性中。這些屬性的行爲相似於具備相同名稱的spring.config.*
變體,實際上它們用於經過在其Environment
中設置這些屬性來設置引導ApplicationContext
。若是在正在構建的上下文中有活動的配置文件(來自spring.profiles.active
或經過Environment
API)),則該配置文件中的屬性也將被加載,就像常規的Spring Boot應用程序,例如來自bootstrap-development.properties
的「開發」簡介。
經過引導上下文添加到應用程序的屬性源一般是「遠程」(例如從配置服務器),而且默認狀況下,不能在本地覆蓋,除了在命令行上。若是要容許您的應用程序使用本身的系統屬性或配置文件覆蓋遠程屬性,則遠程屬性源必須經過設置spring.cloud.config.allowOverride=true
(在本地設置自己不起做用)授予權限。一旦設置了該標誌,就會有一些更精細的設置來控制遠程屬性與系統屬性和應用程序本地配置的位置:spring.cloud.config.overrideNone=true
覆蓋任何本地屬性源,spring.cloud.config.overrideSystemProperties=false
若是隻有系統屬性和env var應該覆蓋遠程設置,而不是本地配置文件。
能夠經過在org.springframework.cloud.bootstrap.BootstrapConfiguration
鍵下添加條目/META-INF/spring.factories
來訓練引導上下文來執行任何您喜歡的操做。這是用於建立上下文的Spring @Configuration
類的逗號分隔列表。您能夠在此處建立要用於自動裝配的主應用程序上下文的任何bean,而且還有ApplicationContextInitializer
類型的@Beans
的特殊合同。若是要控制啓動順序(默認順序爲「最後」),能夠使用@Order
標記類。
警告 |
添加自定義BootstrapConfiguration 時,請注意,您添加的類不是錯誤的@ComponentScanned 到您的「主」應用程序上下文中,可能不須要它們。對於您的@ComponentScan 或@SpringBootApplication 註釋配置類還沒有涵蓋的啓動配置類,請使用單獨的包名稱。 |
引導過程經過將初始化器注入主SpringApplication
實例(即正常的Spring Boot啓動順序,不管是做爲獨立應用程序運行仍是部署在應用程序服務器中)結束。首先,從spring.factories
中找到的類建立引導上下文,而後在ApplicationContextInitializer
類型的全部@Beans
添加到主SpringApplication
開始以前。
引導過程添加的外部配置的默認屬性源是Config Server,但您能夠經過將PropertySourceLocator
類型的bean添加到引導上下文(經過spring.factories
)添加其餘源。您能夠使用此方法從其餘服務器或數據庫中插入其餘屬性。
做爲一個例子,請考慮如下微不足道的自定義定位器:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class CustomPropertySourceLocator implements PropertySourceLocator { @Override public PropertySource<?> locate(Environment environment) { return new MapPropertySource("customProperty", Collections.<String, Object>singletonMap("property.from.sample.custom.source", "worked as intended")); } }</code></span></span>
傳入的Environment
是要建立的ApplicationContext
的Environment
,即爲咱們提供額外的屬性來源的。它將已經具備正常的Spring Boot提供的資源來源,所以您能夠使用它們來定位特定於此Environment
的屬性源(例如經過將其綁定在spring.application.name
上,如在默認狀況下所作的那樣Config Server屬性源定位器)。
若是你在這個類中建立一個jar,而後添加一個META-INF/spring.factories
包含:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator</span></span>
那麼「customProperty」PropertySource
將顯示在其類路徑中包含該jar的任何應用程序中。
應用程序將收聽EnvironmentChangeEvent
,並以幾種標準方式進行更改(用戶能夠以常規方式添加ApplicationListeners
附加ApplicationListeners
)。當觀察到EnvironmentChangeEvent
時,它將有一個已更改的鍵值列表,應用程序將使用如下內容:
從新綁定上下文中的任何@ConfigurationProperties
bean
爲logging.level.*
中的任何屬性設置記錄器級別
請注意,配置客戶端不會經過默認輪詢查找Environment
中的更改,一般咱們不建議檢測更改的方法(儘管能夠使用@Scheduled
註釋進行設置)。若是您有一個擴展的客戶端應用程序,那麼最好將EnvironmentChangeEvent
廣播到全部實例,而不是讓它們輪詢更改(例如使用Spring Cloud總線)。
EnvironmentChangeEvent
涵蓋了大量的刷新用例,只要您真的能夠更改Environment
併發布事件(這些API是公開的,部份內核爲Spring)。您能夠經過訪問/configprops
端點(普通Spring Boot執行器功能)來驗證更改是否綁定到@ConfigurationProperties
bean。例如,DataSource
能夠在運行時更改其maxPoolSize
(由Spring Boot建立的默認DataSource
是一個@ConfigurationProperties
bean),而且動態增長容量。從新綁定@ConfigurationProperties
不會覆蓋另外一大類用例,您須要更多的控制刷新,而且您須要更改在整個ApplicationContext
上是原子的。爲了解決這些擔心,咱們有@RefreshScope
。
當配置更改時,標有@RefreshScope
的Spring @Bean
將獲得特殊處理。這解決了狀態bean在初始化時只注入配置的問題。例如,若是經過Environment
更改數據庫URL時DataSource
有開放鏈接,那麼咱們可能但願這些鏈接的持有人可以完成他們正在作的工做。而後下一次有人從游泳池借用一個鏈接,他獲得一個新的URL。
刷新範圍bean是在使用時初始化的懶惰代理(即當調用一個方法時),而且做用域做爲初始值的緩存。要強制bean從新初始化下一個方法調用,您只須要使其緩存條目無效。
RefreshScope
是上下文中的一個bean,它有一個公共方法refreshAll()
來清除目標緩存中的範圍內的全部bean。還有一個refresh(String)
方法能夠按名稱刷新單個bean。此功能在/refresh
端點(經過HTTP或JMX)中公開。
注意 |
@RefreshScope (技術上)在@Configuration 類上工做,但可能會致使使人驚訝的行爲:例如,這並不 意味着該類中定義的全部@Beans 自己都是@RefreshScope 。具體來講,任何取決於這些bean的東西都不能依賴它們在刷新啓動時被更新,除非它自己在@RefreshScope (在其中將從新刷新並從新注入其依賴關係),那麼它們將從刷新的@Configuration )從新初始化。 |
Spring Cloud具備一個用於在本地解密屬性值的Environment
預處理器。它遵循與Config Server相同的規則,並經過encrypt.*
具備相同的外部配置。所以,您能夠使用{cipher}*
格式的加密值,只要有一個有效的密鑰,那麼在主應用程序上下文獲取Environment
以前,它們將被解密。要在應用程序中使用加密功能,您須要在您的類路徑中包含Spring安全性RSA(Maven協調「org.springframework.security:spring-security-rsa」),而且還須要全面強大的JCE擴展你的JVM
若是因爲「非法密鑰大小」而致使異常,而且您正在使用Sun的JDK,則須要安裝Java加密擴展(JCE)無限強度管理策略文件。有關詳細信息,請參閱如下連接:
將文件解壓縮到JDK / jre / lib / security文件夾(不管您使用的是哪一個版本的JRE / JDK x64 / x86)。
對於Spring Boot執行器應用程序,還有一些額外的管理端點:
POST到/env
以更新Environment
並從新綁定@ConfigurationProperties
和日誌級別
/refresh
從新加載引導帶上下文並刷新@RefreshScope
bean
/restart
關閉ApplicationContext
並從新啓動(默認狀況下禁用)
/pause
和/resume
調用Lifecycle
方法(stop()
和start()
ApplicationContext
)
諸如服務發現,負載平衡和斷路器之類的模式適用於全部Spring Cloud客戶端能夠獨立於實現(例如經過Eureka或Consul發現)的消耗的共同抽象層。
Commons提供@EnableDiscoveryClient
註釋。這經過META-INF/spring.factories
查找DiscoveryClient
接口的實現。Discovery Client的實現將在org.springframework.cloud.client.discovery.EnableDiscoveryClient
鍵下的spring.factories
中添加一個配置類。DiscoveryClient
實現的示例是Spring Cloud Netflix Eureka,Spring Cloud Consul發現和Spring Cloud Zookeeper發現。
默認狀況下,DiscoveryClient
的實現將使用遠程發現服務器自動註冊本地Spring Boot服務器。能夠經過在@EnableDiscoveryClient
中設置autoRegister=false
來禁用此功能。
Commons如今提供了一個ServiceRegistry
接口,它提供了諸如register(Registration)
和deregister(Registration)
之類的方法,容許您提供定製的註冊服務。Registration
是一個標記界面。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration @EnableDiscoveryClient(autoRegister=false) public class MyConfiguration { private ServiceRegistry registry; public MyConfiguration(ServiceRegistry registry) { this.registry = registry; } // called via some external process, such as an event or a custom actuator endpoint public void register() { Registration registration = constructRegistration(); this.registry.register(registration); } }</code></span></span>
每一個ServiceRegistry
實現都有本身的Registry
實現。
服務部門自動註冊
默認狀況下,ServiceRegistry
實現將自動註冊正在運行的服務。要禁用該行爲,有兩種方法。您能夠設置@EnableDiscoveryClient(autoRegister=false)
永久禁用自動註冊。您還能夠設置spring.cloud.service-registry.auto-registration.enabled=false
以經過配置禁用該行爲。
服務註冊執行器端點
Commons提供/service-registry
致動器端點。該端點依賴於Spring應用程序上下文中的Registration
bean。經過GET調用/service-registry/instance-status
將返回Registration
的狀態。具備String
主體的同一端點的POST將將當前Registration
的狀態更改成新值。請參閱您正在使用的ServiceRegistry
實現的文檔,以獲取更新狀態的容許值和爲狀態獲取的值。
RestTemplate
能夠自動配置爲使用功能區。要建立負載平衡RestTemplate
建立RestTemplate
@Bean
並使用@LoadBalanced
限定符。
警告 |
經過自動配置再也不建立RestTemplate bean。它必須由單個應用程序建立。 |
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class MyConfiguration { @LoadBalanced @Bean RestTemplate restTemplate() { return new RestTemplate(); } } public class MyClass { @Autowired private RestTemplate restTemplate; public String doOtherStuff() { String results = restTemplate.getForObject("http://stores/stores", String.class); return results; } }</code></span></span>
URI須要使用虛擬主機名(即服務名稱,而不是主機名)。Ribbon客戶端用於建立完整的物理地址。有關 如何設置RestTemplate
的詳細信息,請參閱 RibbonAutoConfiguration。
重試失敗的請求
負載平衡RestTemplate
能夠配置爲重試失敗的請求。默認狀況下,該邏輯被禁用,您能夠經過將Spring重試添加到應用程序的類路徑來啓用它。負載平衡RestTemplate
將符合與重試失敗請求相關的一些Ribbon配置值。若是要在類路徑中使用Spring重試來禁用重試邏輯,則能夠設置spring.cloud.loadbalancer.retry.enabled=false
。您能夠使用的屬性是client.ribbon.MaxAutoRetries
,client.ribbon.MaxAutoRetriesNextServer
和client.ribbon.OkToRetryOnAllOperations
。請參閱Ribbon文檔 ,瞭解屬性的具體內容。
注意 |
上述示例中的client 應替換爲您的Ribbon客戶端名稱。 |
若是你想要一個沒有負載平衡的RestTemplate
,建立一個RestTemplate
bean並注入它。要建立@Bean
時,使用@LoadBalanced
限定符來訪問負載平衡RestTemplate
。
重要 |
請注意下面示例中的普通RestTemplate 聲明的@Primary 註釋,以消除不合格的@Autowired 注入。 |
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class MyConfiguration { @LoadBalanced @Bean RestTemplate loadBalanced() { return new RestTemplate(); } @Primary @Bean RestTemplate restTemplate() { return new RestTemplate(); } } public class MyClass { @Autowired private RestTemplate restTemplate; @Autowired @LoadBalanced private RestTemplate loadBalanced; public String doOtherStuff() { return loadBalanced.getForObject("http://stores/stores", String.class); } public String doStuff() { return restTemplate.getForObject("http://example.com", String.class); } }</code></span></span>
提示 |
若是您看到錯誤java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89 ,請嘗試注入RestOperations 或設置spring.aop.proxyTargetClass=true 。 |
有時,忽略某些命名網絡接口是有用的,所以能夠將其從服務發現註冊中排除(例如,在Docker容器中運行)。能夠設置正則表達式的列表,這將致使所需的網絡接口被忽略。如下配置將忽略「docker0」接口和以「veth」開頭的全部接口。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: inetutils: ignoredInterfaces: - docker0 - veth.*</span></span>
您還能夠強制使用正則表達式列表中指定的網絡地址:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: inetutils: preferredNetworks: - 192.168 - 10.0</span></span>
您也能夠強制僅使用站點本地地址。有關更多詳細信息,請參閱Inet4Address.html.isSiteLocalAddress())是什麼是站點本地地址。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: inetutils: useOnlySiteLocalInterfaces: true</span></span>
Dalston.RELEASE
Spring Cloud Config爲分佈式系統中的外部配置提供服務器和客戶端支持。使用Config Server,您能夠在全部環境中管理應用程序的外部屬性。客戶端和服務器上的概念映射與Spring Environment
和PropertySource
抽象相同,所以它們與Spring應用程序很是契合,但能夠與任何以任何語言運行的應用程序一塊兒使用。隨着應用程序經過從開發人員到測試和生產的部署流程,您能夠管理這些環境之間的配置,並肯定應用程序具備遷移時須要運行的一切。服務器存儲後端的默認實現使用git,所以它輕鬆支持標籤版本的配置環境,以及能夠訪問用於管理內容的各類工具。很容易添加替代實現,並使用Spring配置將其插入。
啓動服務器:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ cd spring-cloud-config-server $ ../mvnw spring-boot:run</span></span>
該服務器是一個Spring Boot應用程序,因此您能夠從IDE運行它,而不是喜歡(主類是ConfigServerApplication
)。而後嘗試一個客戶端:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ curl localhost:8888/foo/development {"name":"development","label":"master","propertySources":[ {"name":"https://github.com/scratches/config-repo/foo-development.properties","source":{"bar":"spam"}}, {"name":"https://github.com/scratches/config-repo/foo.properties","source":{"foo":"bar"}} ]}</span></span>
定位資源的默認策略是克隆一個git倉庫(在spring.cloud.config.server.git.uri
),並使用它來初始化一個迷你SpringApplication
。小應用程序的Environment
用於枚舉屬性源並經過JSON端點發布。
HTTP服務具備如下格式的資源:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">/{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties</span></span>
其中「應用程序」做爲SpringApplication
中的spring.config.name
注入(即常規的Spring Boot應用程序中一般是「應用程序」),「配置文件」是活動配置文件(或逗號分隔列表的屬性),「label」是可選的git標籤(默認爲「master」)。
Spring Cloud Config服務器從git存儲庫(必須提供)爲遠程客戶端提供配置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo</code></span></span>
要在應用程序中使用這些功能,只需將其構建爲依賴於spring-cloud-config-client的Spring引導應用程序(例如,查看配置客戶端或示例應用程序的測試用例)。添加依賴關係的最方便的方法是經過Spring Boot啓動器org.springframework.cloud:spring-cloud-starter-config
。還有一個Maven用戶的父pom和BOM(spring-cloud-starter-parent
)和用於Gradle和Spring CLI用戶的Spring IO版本管理屬性文件。示例Maven配置:
的pom.xml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Brixton.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <!-- repositories also needed for snapshots and milestones --></code></span></span>
那麼你能夠建立一個標準的Spring Boot應用程序,像這個簡單的HTTP服務器:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@SpringBootApplication @RestController public class Application { @RequestMapping("/") public String home() { return "Hello World!"; } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }</span></span>
當它運行它將從端口8888上的默認本地配置服務器接收外部配置,若是它正在運行。要修改啓動行爲,您能夠使用bootstrap.properties
(如application.properties
)更改配置服務器的位置,但用於應用程序上下文的引導階段),例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.cloud.config.uri: http://myconfigserver.com</span></span>
引導屬性將在/env
端點中顯示爲高優先級屬性源,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ curl localhost:8080/env { "profiles":[], "configService:https://github.com/spring-cloud-samples/config-repo/bar.properties":{"foo":"bar"}, "servletContextInitParams":{}, "systemProperties":{...}, ... }</span></span>
(名爲「configService:<遠程存儲庫的URL> / <文件名>」的屬性源包含值爲「bar」的屬性「foo」,是最高優先級)。
注意 |
屬性源名稱中的URL是git存儲庫,而不是配置服務器URL。 |
服務器爲外部配置(名稱值對或等效的YAML內容)提供了基於資源的HTTP。服務器能夠使用@EnableConfigServer
註釋輕鬆嵌入到Spring Boot應用程序中。因此這個應用程序是一個配置服務器:
ConfigServer.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableConfigServer public class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); } }</code></span></span>
像全部的默認端口8080上運行的全部Spring Boot應用程序同樣,但您能夠經過各類方式將其切換到常規端口8888。最簡單的也是設置一個默認配置庫,它是經過啓動它的spring.config.name=configserver
(在Config Server jar中有一個configserver.yml
)。另外一個是使用你本身的application.properties
,例如
application.properties
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-properties">server.port: 8888 spring.cloud.config.server.git.uri: file://${user.home}/config-repo</code></span></span>
其中${user.home}/config-repo
是包含YAML和屬性文件的git倉庫。
注意 |
在Windows中,若是文件URL爲絕對驅動器前綴,例如file:///${user.home}/config-repo ,則須要額外的「/」。 |
提示 |
如下是上面示例中建立git倉庫的方法: <span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.6)"><span style="color:rgba(0, 0, 0, 0.9)">$ cd $HOME $ mkdir config-repo $ cd config-repo $ git init . $ echo info.foo: bar > application.properties $ git add -A . $ git commit -m "Add application.properties"</span></span></span> |
警告 |
使用本地文件系統進行git存儲庫僅用於測試。使用服務器在生產環境中託管配置庫。 |
警告 |
若是您只保留文本文件,則配置庫的初始克隆將會快速有效。若是您開始存儲二進制文件,尤爲是較大的文件,則可能會遇到服務器中第一個配置請求和/或內存不足錯誤的延遲。 |
您要在哪裏存儲配置服務器的配置數據?管理此行爲的策略是EnvironmentRepository
,服務於Environment
對象。此Environment
是Spring Environment
(包括propertySources
做爲主要功能)的域的淺層副本。Environment
資源由三個變量參數化:
{application}
映射到客戶端的「spring.application.name」;
{profile}
映射到客戶端上的「spring.profiles.active」(逗號分隔列表); 和
{label}
這是一個服務器端功能,標記「版本」的配置文件集。
存儲庫實現一般表現得像一個Spring Boot應用程序從「spring.config.name」等於{application}
參數加載配置文件,「spring.profiles.active」等於{profiles}
參數。配置文件的優先級規則也與常規啓動應用程序相同:活動配置文件優先於默認配置,若是有多個配置文件,則最後一個獲勝(例如向Map
添加條目)。
示例:客戶端應用程序具備此引導配置:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: application: name: foo profiles: active: dev,mysql</code></span></span>
(一般使用Spring Boot應用程序,這些屬性也能夠設置爲環境變量或命令行參數)。
若是存儲庫是基於文件的,則服務器將從application.yml
建立Environment
(在全部客戶端之間共享),foo.yml
(以foo.yml
優先))。若是YAML文件中有文件指向Spring配置文件,那麼應用的優先級更高(按照列出的配置文件的順序),而且若是存在特定於配置文件的YAML(或屬性)文件,那麼這些文件也應用於優先級高於默認值。較高優先級轉換爲Environment
以前列出的PropertySource
。(這些規則與獨立的Spring Boot應用程序相同。)
Git後端
EnvironmentRepository
的默認實現使用Git後端,這對於管理升級和物理環境以及審覈更改很是方便。要更改存儲庫的位置,能夠在Config Server中設置「spring.cloud.config.server.git.uri」配置屬性(例如application.yml
)。若是您使用file:
前綴進行設置,則應從本地存儲庫中工做,以便在沒有服務器的狀況下快速方便地啓動,但在這種狀況下,服務器將直接在本地存儲庫上進行操做,而不會克隆若是它不是裸機,由於配置服務器永遠不會更改「遠程」資源庫)。要擴展Config Server並使其高度可用,您須要將服務器的全部實例指向同一個存儲庫,所以只有共享文件系統才能正常工做。即便在這種狀況下,最好使用共享文件系統存儲庫的ssh:
協議,以便服務器能夠將其克隆並使用本地工做副本做爲緩存。
該存儲庫實現將HTTP資源的{label}
參數映射到git標籤(提交ID,分支名稱或標籤)。若是git分支或標籤名稱包含斜槓(「/」),則應使用特殊字符串「(_)」指定HTTP URL中的標籤,以免與其餘URL路徑模糊。例如,若是標籤爲foo/bar
,則替換斜槓將致使標籤看起來像foo(_)bar
。若是您使用像curl這樣的命令行客戶端(例如使用引號將其從shell中轉出來),請當心URL中的方括號。
Git URI中的佔位符
Spring Cloud Config服務器支持一個Git倉庫URL,其中包含{application}
和{profile}
(以及{label}
)的佔位符,若是須要,請記住標籤應用爲git標籤)。所以,您能夠使用(例如)輕鬆支持「每一個應用程序的一個repo」策略:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/myorg/{application}</code></span></span>
或使用相似模式但使用{profile}
的「每一個配置文件一個」策略。
模式匹配和多個存儲庫
還能夠經過應用程序和配置文件名稱的模式匹配來支持更復雜的需求。模式格式是帶有通配符的{application}/{profile}
名稱的逗號分隔列表(可能須要引用以通配符開頭的模式)。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo repos: simple: https://github.com/simple/config-repo special: pattern: special*/dev*,*special*/dev* uri: https://github.com/special/config-repo local: pattern: local* uri: file:/home/configsvc/config-repo</code></span></span>
若是{application}/{profile}
不匹配任何模式,它將使用在「spring.cloud.config.server.git.uri」下定義的默認uri。在上面的例子中,對於「簡單」存儲庫,模式是simple/*
(即全部配置文件中只匹配一個名爲「簡單」的應用程序)。「本地」存儲庫與全部配置文件中以「local」開頭的全部應用程序名稱匹配(將/*
後綴自動添加到任何沒有配置文件匹配器的模式)。
注意 |
在上述「簡單」示例中使用的「單行」快捷方式只能在惟一要設置的屬性爲URI的狀況下使用。若是您須要設置其餘任何內容(憑據,模式等),則須要使用完整的表單。 |
repo中的pattern
屬性其實是一個數組,所以您能夠使用屬性文件中的YAML數組(或[0]
,[1]
等後綴)綁定到多個模式。若是要運行具備多個配置文件的應用程序,則可能須要執行此操做。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo repos: development: pattern: - */development - */staging uri: https://github.com/development/config-repo staging: pattern: - */qa - */production uri: https://github.com/staging/config-repo</code></span></span>
注意 |
Spring Cloud將猜想包含不在* 中的配置文件的模式意味着您實際上要匹配今後模式開始的配置文件列表(所以*/staging 是["*/staging", "*/staging,*"] )。這是常見的,您須要在本地的「開發」配置文件中運行應用程序,但也能夠遠程運行「雲」配置文件。 |
每一個存儲庫還能夠選擇將配置文件存儲在子目錄中,搜索這些目錄的模式能夠指定爲searchPaths
。例如在頂層:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo searchPaths: foo,bar*</code></span></span>
在此示例中,服務器搜索頂級和「foo /」子目錄以及名稱以「bar」開頭的任何子目錄中的配置文件。
默認狀況下,首次請求配置時,服務器克隆遠程存儲庫。服務器能夠配置爲在啓動時克隆存儲庫。例如在頂層:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://git/common/config-repo.git repos: team-a: pattern: team-a-* cloneOnStart: true uri: http://git/team-a/config-repo.git team-b: pattern: team-b-* cloneOnStart: false uri: http://git/team-b/config-repo.git team-c: pattern: team-c-* uri: http://git/team-a/config-repo.git</code></span></span>
在此示例中,服務器在啓動以前克隆了team-a的config-repo,而後它接受任何請求。全部其餘存儲庫將不被克隆,直到請求從存儲庫配置。
注意 |
在配置服務器啓動時設置要克隆的存儲庫能夠幫助在配置服務器啓動時快速識別錯誤配置的源(例如,無效的存儲庫URI)。配置源不啓用cloneOnStart 時,配置服務器可能會成功啓動配置錯誤或無效的配置源,而不會檢測到錯誤,直到應用程序從該配置源請求配置爲止。 |
認證
要在遠程存儲庫上使用HTTP基自己份驗證,請分別添加「username」和「password」屬性(不在URL中),例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo username: trolley password: strongpassword</code></span></span>
若是您不使用HTTPS和用戶憑據,當您將密鑰存儲在默認目錄(~/.ssh
)中,而且uri指向SSH位置時,SSH也應該開箱即用,例如「 git@github.com:配置/雲配置」。必須在~/.ssh/known_hosts
文件中存在Git服務器的條目,而且它是ssh-rsa
格式。其餘格式(如ecdsa-sha2-nistp256
)不受支持。爲了不意外,您應該確保Git服務器的known_hosts
文件中只有一個條目,而且與您提供給配置服務器的URL匹配。若是您在URL中使用了主機名,那麼您但願在known_hosts
文件中具備這一點,而不是IP。使用JGit訪問存儲庫,所以您發現的任何文檔都應適用。HTTPS代理設置能夠~/.git/config
設置,也能夠經過系統屬性(-Dhttps.proxyHost
和-Dhttps.proxyPort
)與任何其餘JVM進程相同。
提示 |
若是您不知道~/.git 目錄使用git config --global 來處理設置的位置(例如git config --global http.sslVerify false )。 |
使用AWS CodeCommit進行認證
AWS CodeCommit認證也能夠完成。當從命令行使用Git時,AWS CodeCommit使用身份驗證助手。該幫助器不與JGit庫一塊兒使用,所以若是Git URI與AWS CodeCommit模式匹配,則將建立用於AWS CodeCommit的JGit CredentialProvider。AWS CodeCommit URI始終看起來像 https://git-codecommit.$ {AWS_REGION} .amazonaws.com / $ {repopath}。
若是您使用AWS CodeCommit URI提供用戶名和密碼,那麼這些URI必須 是用於訪問存儲庫的AWS accessKeyId和secretAccessKey。若是不指定用戶名和密碼,則將使用AWS默認憑據提供程序鏈檢索accessKeyId和secretAccessKey 。
若是您的Git URI與CodeCommit URI模式(上述)匹配,則必須在用戶名和密碼或默認憑據提供程序鏈支持的某個位置中提供有效的AWS憑據。AWS EC2實例能夠使用EC2實例的 IAM角色。
注意:aws-java-sdk-core jar是一個可選的依賴關係。若是aws-java-sdk-core jar不在您的類路徑上,則不管git服務器URI如何,都將不會建立AWS代碼提交憑據提供程序。
Git搜索路徑中的佔位符
Spring Cloud Config服務器還支持具備{application}
和{profile}
(以及{label}
(若是須要))佔位符的搜索路徑。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo searchPaths: '{application}'</code></span></span>
在資源庫中搜索與目錄(以及頂級)相同名稱的文件。通配符在具備佔位符的搜索路徑中也是有效的(搜索中包含任何匹配的目錄)。
力拉入Git存儲庫
如前所述Spring Cloud Config服務器克隆遠程git存儲庫,若是某種方式本地副本變髒(例如,經過操做系統進程更改文件夾內容),則Spring Cloud Config服務器沒法從遠程存儲庫更新本地副本。
要解決這個問題,有一個force-pull
屬性,若是本地副本是髒的,將使Spring Cloud Config Server強制從遠程存儲庫拉。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo force-pull: true</code></span></span>
若是您有多個存儲庫配置,則能夠爲每一個存儲庫配置force-pull
屬性。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: git: uri: https://git/common/config-repo.git force-pull: true repos: team-a: pattern: team-a-* uri: http://git/team-a/config-repo.git force-pull: true team-b: pattern: team-b-* uri: http://git/team-b/config-repo.git force-pull: true team-c: pattern: team-c-* uri: http://git/team-a/config-repo.git</code></span></span>
注意 |
force-pull 屬性的默認值爲false 。 |
版本控制後端文件系統使用
警告 |
使用基於VCS的後端(git,svn)文件被檢出或克隆到本地文件系統。默認狀況下,它們放在系統臨時目錄中,前綴爲config-repo- 。在linux上,例如能夠是/tmp/config-repo-<randomid> 。一些操做系統會按期清除臨時目錄。這可能會致使意外的行爲,例如缺乏屬性。爲避免此問題,請經過將spring.cloud.config.server.git.basedir 或spring.cloud.config.server.svn.basedir 設置爲不駐留在系統臨時結構中的目錄來更改Config Server使用的目錄。 |
文件系統後端
配置服務器中還有一個不使用Git的「本機」配置文件,只是從本地類路徑或文件系統加載配置文件(您想要指向的任何靜態URL「spring.cloud.config.server .native.searchLocations「)。要使用本機配置文件,只需使用「spring.profiles.active = native」啓動Config Server。
注意 |
請記住使用file: 前綴的文件資源(缺省沒有前綴一般是classpath)。與任何Spring Boot配置同樣,您能夠嵌入${} 樣式的環境佔位符,但請記住,Windows中的絕對路徑須要額外的「/」,例如file:///${user.home}/config-repo |
警告 |
searchLocations 的默認值與本地Spring Boot應用程序(因此[classpath:/, classpath:/config, file:./, file:./config] )相同。這不會將application.properties 從服務器暴露給全部客戶端,由於在發送到客戶端以前,服務器中存在的任何屬性源都將被刪除。 |
提示 |
文件系統後端對於快速入門和測試是很是好的。要在生產中使用它,您須要確保文件系統是可靠的,並在配置服務器的全部實例中共享。 |
搜索位置能夠包含{application}
,{profile}
和{label}
的佔位符。以這種方式,您能夠隔離路徑中的目錄,並選擇一個有用的策略(例如每一個應用程序的子目錄或每一個配置文件的子目錄)。
若是您不在搜索位置使用佔位符,則該存儲庫還將HTTP資源的{label}
參數附加到搜索路徑上的後綴,所以屬性文件將從每一個搜索位置加載並具備相同名稱的子目錄做爲標籤(標記的屬性在Spring環境中優先)。所以,沒有佔位符的默認行爲與添加以/{label}/. For example `file:/tmp/config
結尾的搜索位置與file:/tmp/config,file:/tmp/config/{label}
相同
Vault後端
Spring Cloud Config服務器還支持Vault做爲後端。
Vault是安全訪問祕密的工具。一個祕密是你想要嚴格控制訪問的任何東西,如API密鑰,密碼,證書等等。Vault爲任何祕密提供統一的界面,同時提供嚴格的訪問控制和記錄詳細的審覈日誌。
有關Vault的更多信息,請參閱Vault快速入門指南。
要使配置服務器使用Vault後端,您必須使用vault
配置文件運行配置服務器。例如在配置服務器的application.properties
中,您能夠添加spring.profiles.active=vault
。
默認狀況下,配置服務器將假定您的Vault服務器正在運行於http://127.0.0.1:8200
。它還將假定後端名稱爲secret
,密鑰爲application
。全部這些默認值均可以在配置服務器的application.properties
中配置。如下是可配置Vault屬性的表。全部屬性前綴爲spring.cloud.config.server.vault
。
名稱 | 默認值 |
---|---|
host |
127.0.0.1 |
port |
8200 |
scheme |
HTTP |
backend |
祕密 |
defaultKey |
應用 |
profileSeparator |
, |
全部可配置的屬性能夠在org.springframework.cloud.config.server.environment.VaultEnvironmentRepository
找到。
運行配置服務器後,能夠向服務器發出HTTP請求,以從Vault後端檢索值。爲此,您須要爲Vault服務器建立一個令牌。
首先放置一些數據給你Vault。例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-sh">$ vault write secret/application foo=bar baz=bam $ vault write secret/myapp foo=myappsbar</code></span></span>
如今,將HTTP請求發送給您的配置服務器以檢索值。
$ curl -X "GET" "http://localhost:8888/myapp/default" -H "X-Config-Token: yourtoken"
在提出上述要求後,您應該會看到相似的回覆。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "name":"myapp", "profiles":[ "default" ], "label":null, "version":null, "state":null, "propertySources":[ { "name":"vault:myapp", "source":{ "foo":"myappsbar" } }, { "name":"vault:application", "source":{ "baz":"bam", "foo":"bar" } } ] }</code></span></span>
多個Properties來源
使用Vault時,您能夠爲應用程序提供多個屬性源。例如,假設您已將數據寫入Vault中的如下路徑。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-sh">secret/myApp,dev secret/myApp secret/application,dev secret/application</code></span></span>
寫入secret/application
的Properties可 用於使用配置服務器的全部應用程序。名稱爲myApp
的應用程序將具備寫入secret/myApp
和secret/application
的任何屬性。當myApp
啓用dev
配置文件時,寫入全部上述路徑的屬性將可用,列表中第一個路徑中的屬性優先於其餘路徑。
與全部應用共享配置
基於文件的存儲庫
使用基於文件(即git,svn和native)的存儲庫,文件名爲application*
的資源在全部客戶端應用程序(因此application.properties
,application.yml
,application-*.properties
等)之間共享)。您能夠使用這些文件名的資源來配置全局默認值,並根據須要將其覆蓋應用程序特定的文件。
#_property_overrides [屬性覆蓋]功能也可用於設置全局默認值,而且容許佔位符應用程序在本地覆蓋它們。
提示 |
使用「本機」配置文件(本地文件系統後端),建議您使用不屬於服務器自身配置的顯式搜索位置。不然,默認搜索位置中的application* 資源將被刪除,由於它們是服務器的一部分。 |
Vault服務器
當使用Vault做爲後端時,能夠經過將配置放在secret/application
中與全部應用程序共享配置。例如,若是您運行此Vault命令
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-sh">$ vault write secret/application foo=bar baz=bam</code></span></span>
使用配置服務器的全部應用程序均可以使用屬性foo
和baz
。
複合環境庫
在某些狀況下,您可能但願從多個環境存儲庫中提取配置數據。爲此,只需在配置服務器的應用程序屬性或YAML文件中啓用多個配置文件便可。例如,若是您要從Git存儲庫以及SVN存儲庫中提取配置數據,那麼您將爲配置服務器設置如下屬性。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: profiles: active: git, svn cloud: config: server: svn: uri: file:///path/to/svn/repo order: 2 git: uri: file:///path/to/git/repo order: 1</code></span></span>
除了指定URI的每一個repo以外,還能夠指定order
屬性。order
屬性容許您指定全部存儲庫的優先級順序。order
屬性的數值越低,優先級越高。存儲庫的優先順序將有助於解決包含相同屬性的值的存儲庫之間的任何潛在衝突。
注意 |
從環境倉庫檢索值時的任何類型的故障將致使整個複合環境的故障。 |
注意 |
當使用複合環境時,重要的是全部repos都包含相同的標籤。若是您有相似於上述的環境,而且使用標籤master 請求配置數據,可是SVN repo不包含稱爲master 的分支,則整個請求將失敗。 |
自定義複合環境庫
除了使用來自Spring Cloud的環境存儲庫以外,還能夠提供本身的EnvironmentRepository
bean做爲複合環境的一部分。要作到這一點,你的bean必須實現EnvironmentRepository
接口。若是要在複合環境中控制自定義EnvironmentRepository
的優先級,您還應該實現Ordered
接口並覆蓋getOrdered
方法。若是您不實現Ordered
接口,那麼您的EnvironmentRepository
將被賦予最低優先級。
屬性覆蓋
配置服務器具備「覆蓋」功能,容許操做員爲應用程序使用普通的Spring Boot鉤子不會意外更改的全部應用程序提供配置屬性。要聲明覆蓋,只需將名稱/值對的地圖添加到spring.cloud.config.server.overrides
。例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: overrides: foo: bar</code></span></span>
將致使配置客戶端的全部應用程序獨立於本身的配置讀取foo=bar
。(固然,應用程序能夠以任何方式使用Config Server中的數據,所以覆蓋不可強制執行,但若是它們是Spring Cloud Config客戶端,則它們確實提供有用的默認行爲。)
提示 |
經過使用反斜槓(「\」)來轉義「$」或「{」,例如\${app.foo:bar} 解析,能夠轉義正常的Spring具備「$ {}」的環境佔位符到「bar」,除非應用程序提供本身的「app.foo」。請注意,在YAML中,您不須要轉義反斜槓自己,而是在您執行的屬性文件中配置服務器上的覆蓋。 |
您能夠經過在遠程存儲庫中設置標誌spring.cloud.config.overrideNone=true
(默認爲false),將客戶端中全部覆蓋的優先級更改成更爲默認值,容許應用程序在環境變量或系統屬性中提供本身的值。
配置服務器附帶運行情況指示器,檢查配置的EnvironmentRepository
是否正常工做。默認狀況下,它要求EnvironmentRepository
應用程序名稱爲app
,default
配置文件和EnvironmentRepository
實現提供的默認標籤。
您能夠配置運行情況指示器以檢查更多應用程序以及自定義配置文件和自定義標籤,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: server: health: repositories: myservice: label: mylabel myservice-dev: name: myservice profiles: development</code></span></span>
您能夠經過設置spring.cloud.config.server.health.enabled=false
來禁用運行情況指示器。
您能夠以任何對您有意義的方式(從物理網絡安全性到OAuth2承載令牌)保護您的Config Server,而且Spring Security和Spring Boot能夠輕鬆作任何事情。
要使用默認的Spring Boot配置的HTTP Basic安全性,只需在類路徑中包含Spring Security(例如經過spring-boot-starter-security
)。默認值爲「user」的用戶名和隨機生成的密碼,這在實踐中不會很是有用,所以建議您配置密碼(經過security.user.password
)並對其進行加密(請參閱下文的說明怎麼作)。
重要 |
先決條件:要使用加密和解密功能,您須要在JVM中安裝全面的JCE(默認狀況下不存在)。您能夠從Oracle下載「Java加密擴展(JCE)無限強度管理策略文件」,並按照安裝說明(實際上將JRE lib / security目錄中的2個策略文件替換爲您下載的文件)。 |
若是遠程屬性源包含加密內容(以{cipher}
開頭的值),則在經過HTTP發送到客戶端以前,它們將被解密。這種設置的主要優勢是,當它們「靜止」時,屬性值沒必要是純文本(例如在git倉庫中)。若是值沒法解密,則從屬性源中刪除該值,並添加具備相同鍵的附加屬性,但以「無效」做爲前綴。和「不適用」的值(一般爲「<n / a>」)。這主要是爲了防止密碼被用做密碼並意外泄漏。
若是要爲config客戶端應用程序設置遠程配置存儲庫,可能會包含一個application.yml
,例如:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: datasource: username: dbuser password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'</code></span></span>
.properties文件中的加密值不能用引號括起來,不然不會解密該值:
application.properties
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.datasource.username: dbuser spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ</span></span>
您能夠安全地將此純文本推送到共享git存儲庫,而且保密密碼。
服務器還暴露了/encrypt
和/decrypt
端點(假設這些端點將被保護,而且只能由受權代理訪問)。若是您正在編輯遠程配置文件,能夠使用Config Server經過POST到/encrypt
端點來加密值,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ curl localhost:8888/encrypt -d mysecret 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda</span></span>
注意 |
若是要加密的值具備須要進行URL編碼的字符,則應使用--data-urlencode 選項curl 來確保它們已正確編碼。 |
逆向操做也可經過/decrypt
得到(若是服務器配置了對稱密鑰或全密鑰對):
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda mysecret</span></span>
提示 |
若是您使用curl進行測試,則使用--data-urlencode (而不是-d )或設置顯式Content-Type: text/plain ,以確保在有特殊字符時正確地對數據進行編碼('+'特別是棘手)。 |
將加密的值添加到{cipher}
前綴,而後再將其放入YAML或屬性文件中,而後再提交併將其推送到遠程可能不安全的存儲區。
/encrypt
和/decrypt
端點也都接受/*/{name}/{profiles}
形式的路徑,當客戶端調用到主環境資源時,能夠用於每一個應用程序(名稱)和配置文件控制密碼。
注意 |
爲了以這種細微的方式控制密碼,您還必須提供一種TextEncryptorLocator 類型的@Bean ,能夠爲每一個名稱和配置文件建立不一樣的加密器。默認提供的不會這樣作(全部加密使用相同的密鑰)。 |
spring
命令行客戶端(安裝了Spring Cloud CLI擴展)也能夠用於加密和解密,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ spring encrypt mysecret --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda $ spring decrypt --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda mysecret</span></span>
要在文件中使用密鑰(例如用於加密的RSA公鑰),使用「@」鍵入鍵值,並提供文件路徑,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ spring encrypt mysecret --key @${HOME}/.ssh/id_rsa.pub AQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+...</span></span>
關鍵參數是強制性的(儘管有一個--
前綴)。
Config Server能夠使用對稱(共享)密鑰或非對稱密鑰(RSA密鑰對)。非對稱選擇在安全性方面是優越的,可是使用對稱密鑰每每更方便,由於它只是配置的一個屬性值。
要配置對稱密鑰,您只須要將encrypt.key
設置爲一個祕密字符串(或使用環境變量ENCRYPT_KEY
將其從純文本配置文件中刪除)。
要配置非對稱密鑰,您能夠將密鑰設置爲PEM編碼的文本值(encrypt.key
),也能夠經過密鑰庫設置密鑰(例如由JDK附帶的keytool
實用程序建立)。密鑰庫屬性爲encrypt.keyStore.*
,*
等於
location
(a Resource
位置),
password
(解鎖密鑰庫)和
alias
(以識別商店中使用的密鑰)。
使用公鑰進行加密,須要私鑰進行解密。所以,原則上您只能在服務器中配置公鑰,若是您只想進行加密(並準備使用私鑰本地解密值)。實際上,您可能不想這樣作,由於它圍繞全部客戶端傳播密鑰管理流程,而不是將其集中在服務器中。另外一方面,若是您的配置服務器真的相對不安全,而且只有少數客戶端須要加密的屬性,這是一個有用的選項。
要建立一個密鑰庫進行測試,您能夠執行如下操做:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ keytool -genkeypair -alias mytestkey -keyalg RSA \ -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" \ -keypass changeme -keystore server.jks -storepass letmein</span></span>
將server.jks
文件放在類路徑(例如)中,而後在您的application.yml
中配置服務器:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">encrypt: keyStore: location: classpath:/server.jks password: letmein alias: mytestkey secret: changeme</code></span></span>
除了加密屬性值中的{cipher}
前綴以外,配置服務器在(Base64編碼)密文開始前查找{name:value}
前綴(零或多個)。密鑰被傳遞給TextEncryptorLocator
,它能夠執行找到密碼的TextEncryptor
所需的任何邏輯。若是配置了密鑰庫(encrypt.keystore.location
),默認定位器將使用「key」前綴提供的別名,即便用以下密碼查找存儲中的密鑰:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">foo: bar: `{cipher}{key:testkey}...`</code></span></span>
定位器將尋找一個名爲「testkey」的鍵。也能夠經過前綴中的{secret:…}
值提供一個祕密,可是若是不是默認值,則使用密鑰庫密碼(這是您在構建密鑰庫時得到的,而且不指定密碼)。若是你這樣作 提供一個祕密建議你也加密使用自定義SecretLocator
的祕密。
若是密鑰只用於加密幾個字節的配置數據(即它們沒有在其餘地方使用),則密碼轉換幾乎不是必需的,可是若是存在安全漏洞,有時您可能須要更改密鑰實例。在這種狀況下,全部客戶端都須要更改其源配置文件(例如,以git格式),並在全部密碼中使用新的{key:…}
前綴,固然事先檢查密鑰別名在配置服務器密鑰庫中是否可用。
提示 |
若是要讓Config Server處理全部加密以及解密,也能夠將{name:value} 前綴添加到發佈到/encrypt 端點的明文中。 |
有時您但願客戶端在本地解密配置,而不是在服務器中進行配置。在這種狀況下,您仍然能夠擁有/加密和解密端點(若是您提供encrypt.*
配置來定位密鑰),可是您須要使用spring.cloud.config.server.encrypt.enabled=false
明確地關閉傳出屬性的解密。若是您不關心端點,那麼若是您既不配置密鑰也不配置使能的標誌,則應該起做用。
來自環境端點的默認JSON格式對於Spring應用程序的消費是完美的,由於它直接映射到Environment
抽象。若是您喜歡,能夠經過向資源路徑(「.yml」,「.yaml」或「.properties」)添加後綴來使用與YAML或Java屬性相同的數據。這對於不關心JSON端點的結構的應用程序或其提供的額外的元數據的應用程序來講多是有用的,例如,不使用Spring的應用程序可能會受益於此方法的簡單性。
YAML和屬性表示有一個額外的標誌(做爲一個布爾查詢參數resolvePlaceholders
提供)),以標示Spring ${…}
形式的源文檔中的佔位符,應在輸出中解析可能在渲染以前。對於不瞭解Spring佔位符慣例的消費者來講,這是一個有用的功能。
注意 |
使用YAML或屬性格式存在侷限性,主要是與元數據的丟失有關。JSON被構造爲屬性源的有序列表,例如,名稱與源相關聯。即便源的起源具備多個源,而且原始源文件的名稱丟失,YAML和屬性表也合併成一個映射。YAML表示不必定是後臺存儲庫中YAML源的忠實表示:它是由平面屬性源的列表構建的,而且必須對鍵的形式進行假設。 |
您的應用程序可能須要通用的純文本配置文件,而不是使用Environment
抽象(或YAML中的其餘替表明示形式或屬性格式)。配置服務器經過/{name}/{profile}/{label}/{path}
附加的端點提供這些服務,其中「name」,「profile」和「label」的含義與常規環境端點相同,但「path」是文件名(例如log.xml
)。此端點的源文件位於與環境端點相同的方式:與屬性或YAML文件相同的搜索路徑,而不是聚合全部匹配的資源,只返回匹配的第一個。
找到資源後,使用正確格式(${…}
)的佔位符將使用有效的Environment
解析爲應用程序名稱,配置文件和標籤提供。以這種方式,資源端點與環境端點緊密集成。例如,若是您有一個GIT(或SVN)資源庫的佈局:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">application.yml nginx.conf</span></span>
其中nginx.conf
以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">server { listen 80; server_name ${nginx.server.name}; }</span></span>
和application.yml
這樣:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">nginx: server: name: example.com --- spring: profiles: development nginx: server: name: develop.com</code></span></span>
那麼/foo/default/master/nginx.conf
資源以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">server { listen 80; server_name example.com; }</span></span>
和/foo/development/master/nginx.conf
這樣:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">server { listen 80; server_name develop.com; }</span></span>
注意 |
就像環境配置的源文件同樣,「配置文件」用於解析文件名,所以,若是您想要一個特定於配置文件的文件,則/*/development/*/logback.xml 將由一個名爲logback-development.xml 的文件解析(優先於logback.xml )。 |
配置服務器最好做爲獨立應用程序運行,但若是須要,能夠將其嵌入到另外一個應用程序中。只需使用@EnableConfigServer
註釋。在這種狀況下能夠使用的可選屬性是spring.cloud.config.server.bootstrap
,它是一個標誌,表示服務器應該從其本身的遠程存儲庫配置自身。該標誌默認關閉,由於它可能會延遲啓動,可是當嵌入在另外一個應用程序中時,以與其餘應用程序相同的方式初始化是有意義的。
注意 |
應該是顯而易見的,但請記住,若是您使用引導標誌,配置服務器將須要在bootstrap.yml 中配置其名稱和存儲庫URI。 |
要更改服務器端點的位置,您能夠(可選)設置spring.cloud.config.server.prefix
,例如「/ config」,以提供前綴下的資源。前綴應該開始但不以「/」結尾。它被應用於配置服務器中的@RequestMappings
(即Spring Boot前綴server.servletPath
和server.contextPath
)之下。
若是您想直接從後端存儲庫(而不是從配置服務器)讀取應用程序的配置,這基本上是一個沒有端點的嵌入式配置服務器。若是不使用@EnableConfigServer
註釋(僅設置spring.cloud.config.server.bootstrap=true
),則能夠徹底關閉端點。
許多源代碼存儲庫提供程序(例如Github,Gitlab或Bitbucket)將經過webhook通知您存儲庫中的更改。您能夠經過提供商的用戶界面將webhook配置爲URL和一組感興趣的事件。例如, Github 將使用包含提交列表的JSON主體和「X-Github-Event」等於「push」的頭文件發送到webhook。若是在spring-cloud-config-monitor
庫中添加依賴關係並激活配置服務器中的Spring Cloud Bus,則啓用「/ monitor」端點。
當Webhook被激活時,配置服務器將發送一個RefreshRemoteApplicationEvent
針對他認爲可能已經改變的應用程序。變動檢測能夠進行策略化,但默認狀況下,它只是查找與應用程序名稱匹配的文件的更改(例如,「foo.properties」針對的是「foo」應用程序,「application.properties」針對全部應用程序) 。若是要覆蓋該行爲的策略是PropertyPathNotificationExtractor
,它接受請求標頭和正文做爲參數,並返回更改的文件路徑列表。
默認配置與Github,Gitlab或Bitbucket配合使用。除了來自Github,Gitlab或Bitbucket的JSON通知以外,您還能夠經過使用表單編碼的身體參數path={name}
經過POST爲「/ monitor」來觸發更改通知。這將廣播到匹配「{name}」模式的應用程序(能夠包含通配符)。
注意 |
只有在配置服務器和客戶端應用程序中激活spring-cloud-bus 時纔會傳送RefreshRemoteApplicationEvent 。 |
注意 |
默認配置還檢測本地git存儲庫中的文件系統更改(在這種狀況下不使用webhook,可是一旦編輯配置文件,將會播放刷新)。 |
Spring Boot應用程序能夠當即利用Spring配置服務器(或應用程序開發人員提供的其餘外部屬性源),而且還將獲取與Environment
更改事件相關的一些其餘有用功能。
這是在類路徑上具備Spring Cloud Config Client的任何應用程序的默認行爲。配置客戶端啓動時,它將經過配置服務器(經過引導配置屬性spring.cloud.config.uri
)綁定,並使用遠程屬性源初始化Spring Environment
。
這樣作的最終結果是全部想要使用Config Server的客戶端應用程序須要bootstrap.yml
(或環境變量),服務器地址位於spring.cloud.config.uri
(默認爲「http:// localhost:8888」 )。
若是您正在使用DiscoveryClient實現,例如Spring Cloud Netflix和Eureka服務發現或Spring Cloud Consul(Spring Cloud Zookeeper不支持此功能),那麼您能夠使用Config Server若是您想要發現服務註冊,但在默認的「配置優先」模式下,客戶端將沒法利用註冊。
若是您但願使用DiscoveryClient
找到配置服務器,能夠經過設置spring.cloud.config.discovery.enabled=true
(默認爲「false」)來實現。最終的結果是,客戶端應用程序都須要具備適當發現配置的bootstrap.yml
(或環境變量)。例如,使用Spring Cloud Netflix,您須要定義Eureka服務器地址,例如eureka.client.serviceUrl.defaultZone
。使用此選項的價格是啓動時額外的網絡往返,以定位服務註冊。好處是配置服務器能夠更改其座標,只要發現服務是一個固定點。默認的服務標識是「configserver」,但您能夠使用spring.cloud.config.discovery.serviceId
在客戶端進行更改(在服務器上以服務的一般方式更改,例如設置spring.application.name
)。
發現客戶端實現都支持某種元數據映射(例如Eureka,咱們有eureka.instance.metadataMap
)。可能須要在其服務註冊元數據中配置Config Server的一些其餘屬性,以便客戶端能夠正確鏈接。若是使用HTTP Basic安全配置服務器,則能夠將憑據配置爲「用戶名」和「密碼」。而且若是配置服務器具備上下文路徑,您能夠設置「configPath」。例如,對於做爲Eureka客戶端的配置服務器:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">eureka: instance: ... metadataMap: user: osufhalskjrtl password: lviuhlszvaorhvlo5847 configPath: /config</code></span></span>
在某些狀況下,若是服務沒法鏈接到配置服務器,則可能但願啓動服務失敗。若是這是所需的行爲,請設置引導配置屬性spring.cloud.config.failFast=true
,客戶端將以異常中止。
若是您但願配置服務器在您的應用程序啓動時可能偶爾不可用,您能夠要求它在發生故障後繼續嘗試。首先,您須要設置spring.cloud.config.failFast=true
,而後您須要添加spring-retry
和spring-boot-starter-aop
到您的類路徑。默認行爲是重試6次,初始退避間隔爲1000ms,指數乘數爲1.1,用於後續退避。您能夠使用spring.cloud.config.retry.*
配置屬性配置這些屬性(和其餘)。
提示 |
要徹底控制重試,請使用ID「configServerRetryInterceptor」添加RetryOperationsInterceptor 類型的@Bean 。Spring重試有一個RetryInterceptorBuilder 能夠輕鬆建立一個。 |
配置服務從/{name}/{profile}/{label}
提供屬性源,客戶端應用程序中的默認綁定
「name」= ${spring.application.name}
「profile」= ${spring.profiles.active}
(其實是Environment.getActiveProfiles()
)
「label」=「master」
全部這些均可以經過設置spring.cloud.config.*
(其中*
是「name」,「profile」或「label」)來覆蓋。「標籤」可用於回滾到之前版本的配置; 使用默認的Config Server實現,它能夠是git標籤,分支名稱或提交ID。標籤也能夠以逗號分隔的列表形式提供,在這種狀況下,列表中的項目會逐個嘗試,直到成功。例如,當您可能但願將配置標籤與您的分支對齊,但使其成爲可選(例如spring.cloud.config.label=myfeature,develop
)時,這對於在特徵分支上工做時可能頗有用。
若是您在服務器上使用HTTP基本安全性,那麼客戶端只須要知道密碼(若是不是默認用戶名)。您能夠經過配置服務器URI,或經過單獨的用戶名和密碼屬性,例如
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: uri: https://user:secret@myconfig.mycompany.com</code></span></span>
要麼
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: uri: https://myconfig.mycompany.com username: user password: secret</code></span></span>
spring.cloud.config.password
和spring.cloud.config.username
值覆蓋URI中提供的任何內容。
若是您在Cloud Foundry部署應用程序,則提供密碼的最佳方式是經過服務憑證(例如URI),由於它甚至不須要在配置文件中。在Cloud Foundry上爲本地工做的用戶提供的服務的一個例子,名爲「configserver」:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: uri: ${vcap.services.configserver.credentials.uri:http://user:password@localhost:8888}</code></span></span>
若是您使用另外一種形式的安全性,則可能須要向ConfigServicePropertySourceLocator
提供RestTemplate
(例如,經過在引導上下文中獲取它並注入一個)。ConfigServicePropertySourceLocator
提供{470 /}(例如經過在引導上下文中獲取它並注入)。
健康指標
Config Client提供一個嘗試從Config Server加載配置的Spring Boot運行情況指示器。能夠經過設置health.config.enabled=false
來禁用運行情況指示器。因爲性能緣由,響應也被緩存。默認緩存生存時間爲5分鐘。要更改該值,請設置health.config.time-to-live
屬性(以毫秒爲單位)。
提供自定義RestTemplate
在某些狀況下,您可能須要從客戶端自定義對配置服務器的請求。一般這涉及傳遞特殊的Authorization
標頭來對服務器的請求進行身份驗證。要提供自定義RestTemplate
,請按照如下步驟操做。
設置spring.cloud.config.enabled=false
以禁用現有的配置服務器屬性源。
使用PropertySourceLocator
實現建立一個新的配置bean。
CustomConfigServiceBootstrapConfiguration.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class CustomConfigServiceBootstrapConfiguration { @Bean public ConfigClientProperties configClientProperties() { ConfigClientProperties client = new ConfigClientProperties(this.environment); client.setEnabled(false); return client; } @Bean public ConfigServicePropertySourceLocator configServicePropertySourceLocator() { ConfigClientProperties clientProperties = configClientProperties(); ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(clientProperties); configServicePropertySourceLocator.setRestTemplate(customRestTemplate(clientProperties)); return configServicePropertySourceLocator; } }</code></span></span>
在resources/META-INF
中建立一個名爲spring.factories
的文件,並指定您的自定義配置。
spring.factorties
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-properties">org.springframework.cloud.bootstrap.BootstrapConfiguration = com.my.config.client.CustomConfigServiceBootstrapConfiguration</code></span></span>
Vault
當使用Vault做爲配置服務器的後端時,客戶端將須要爲服務器提供一個令牌,以從Vault中檢索值。能夠經過在bootstrap.yml
中設置spring.cloud.config.token
在客戶端中提供此令牌。
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: cloud: config: token: YourVaultToken</code></span></span>
Vault中的嵌套密鑰
Vault支持將鍵嵌入存儲在Vault中的值。例如
echo -n '{"appA": {"secret": "appAsecret"}, "bar": "baz"}' | vault write secret/myapp -
此命令將向您的Vault編寫一個JSON對象。要在Spring中訪問這些值,您將使用傳統的點(。)註釋。例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Value("${appA.secret}") String name = "World";</code></span></span>
上述代碼將name
變量設置爲appAsecret
。
Dalston.RELEASE
該項目經過自動配置爲Spring Boot應用程序提供Netflix OSS集成,並綁定到Spring環境和其餘Spring編程模型成語。經過幾個簡單的註釋,您能夠快速啓用和配置應用程序中的常見模式,並經過通過測試的Netflix組件構建大型分佈式系統。提供的模式包括服務發現(Eureka),斷路器(Hystrix),智能路由(Zuul)和客戶端負載平衡(Ribbon)。
服務發現是基於微服務架構的關鍵原則之一。嘗試配置每一個客戶端或某種形式的約定可能很是困難,能夠很是脆弱。Netflix服務發現服務器和客戶端是Eureka。能夠將服務器配置和部署爲高可用性,每一個服務器將註冊服務的狀態複製到其餘服務器。
要在您的項目中包含Eureka客戶端,請使用組org.springframework.cloud
和工件ID spring-cloud-starter-eureka
的啓動器。有關 使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
當客戶端註冊Eureka時,它提供關於自身的元數據,例如主機和端口,健康指示符URL,主頁等。Eureka從屬於服務的每一個實例接收心跳消息。若是心跳失敗超過可配置的時間表,則一般將該實例從註冊表中刪除。
示例eureka客戶端:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration @ComponentScan @EnableAutoConfiguration @EnableEurekaClient @RestController public class Application { @RequestMapping("/") public String home() { return "Hello world"; } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }</code></span></span>
(即徹底正常的Spring Boot應用程序)。在這個例子中,咱們明確地使用@EnableEurekaClient
,但只有Eureka可用,你也能夠使用@EnableDiscoveryClient
。須要配置才能找到Eureka服務器。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/</span></span>
其中「defaultZone」是一個魔術字符串後備值,爲任何不表示首選項的客戶端提供服務URL(即它是有用的默認值)。
從Environment
獲取的默認應用程序名稱(服務ID),虛擬主機和非安全端口分別爲${spring.application.name}
,${spring.application.name}
和${server.port}
。
@EnableEurekaClient
將應用程序同時進入一個Eureka「實例」(即註冊本身)和一個「客戶端」(即它能夠查詢註冊表以查找其餘服務)。實例行爲由eureka.instance.*
配置鍵驅動,可是若是您確保您的應用程序具備spring.application.name
(這是Eureka服務ID或VIP的默認值),那麼默認值將是正常的。
有關可配置選項的更多詳細信息,請參閱EurekaInstanceConfigBean和EurekaClientConfigBean。
若是其中一個eureka.client.serviceUrl.defaultZone
網址中包含一個憑據(如http://user:password@localhost:8761/eureka
)),HTTP基自己份驗證將自動添加到您的eureka客戶端。對於更復雜的需求,您能夠建立DiscoveryClientOptionalArgs
類型的@Bean
,並將ClientFilter
實例注入到其中,全部這些都將應用於從客戶端到服務器的調用。
注意 |
因爲Eureka中的限制,不可能支持每一個服務器的基自己份驗證憑據,因此只能使用第一個找到的集合。 |
Eureka實例的狀態頁面和運行情況指示器分別默認爲「/ info」和「/ health」,它們是Spring Boot執行器應用程序中有用端點的默認位置。若是您使用非默認上下文路徑或servlet路徑(例如server.servletPath=/foo
)或管理端點路徑(例如management.contextPath=/admin
),則須要更改這些,即便是執行器應用程序。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">eureka: instance: statusPageUrlPath: ${management.context-path}/info healthCheckUrlPath: ${management.context-path}/health</span></span>
這些連接顯示在客戶端使用的元數據中,並在某些狀況下用於決定是否將請求發送到應用程序,所以若是它們是準確的,這是有幫助的。
若是您的應用程序想經過HTTPS聯繫,則能夠分別在EurekaInstanceConfig
,即 eureka.instance.[nonSecurePortEnabled,securePortEnabled]=[false,true]
中設置兩個標誌。這將使Eureka發佈實例信息顯示安全通訊的明確偏好。Spring Cloud DiscoveryClient
將始終爲以這種方式配置的服務返回一個https://…;
URI,而且Eureka(本機)實例信息將具備安全的健康檢查URL。
因爲Eureka內部的工做方式,它仍然會發布狀態和主頁的非安全網址,除非您也明確地覆蓋。您能夠使用佔位符來配置eureka實例URL,例如
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">eureka: instance: statusPageUrl: https://${eureka.hostname}/info healthCheckUrl: https://${eureka.hostname}/health homePageUrl: https://${eureka.hostname}/</span></span>
(請注意,${eureka.hostname}
是僅在稍後版本的Eureka中可用的本地佔位符,您也能夠使用Spring佔位符實現一樣的功能,例如使用${eureka.instance.hostName}
。
注意 |
若是您的應用程序在代理服務器後面運行,而且SSL終止服務在代理中(例如,若是您運行在Cloud Foundry或其餘平臺做爲服務),則須要確保代理「轉發」頭部被截取並處理應用程序。Spring Boot應用程序中的嵌入式Tomcat容器會自動執行「X-Forwarded - \ *」標頭的顯式配置。你這個錯誤的一個跡象就是你的應用程序自己所呈現的連接是錯誤的(錯誤的主機,端口或協議)。 |
默認狀況下,Eureka使用客戶端心跳來肯定客戶端是否啓動。除非另有規定,不然發現客戶端將不會根據Spring Boot執行器傳播應用程序的當前運行情況檢查狀態。這意味着成功註冊後Eureka將永遠宣佈申請處於「UP」狀態。經過啓用Eureka運行情況檢查能夠改變此行爲,從而將應用程序狀態傳播到Eureka。所以,每一個其餘應用程序將不會在「UP」以外的狀態下將流量發送到應用程序。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">eureka: client: healthcheck: enabled: true</span></span>
警告 |
eureka.client.healthcheck.enabled=true 只能在application.yml 中設置。設置bootstrap.yml 中的值將致使不指望的反作用,例如在具備UNKNOWN 狀態的eureka中註冊。 |
若是您須要更多的控制健康檢查,您能夠考慮實施本身的com.netflix.appinfo.HealthCheckHandler
。
值得花點時間瞭解Eureka元數據的工做原理,以便您能夠在平臺上使用它。有主機名,IP地址,端口號,狀態頁和運行情況檢查等標準元數據。這些發佈在服務註冊表中,由客戶使用,以直接的方式聯繫服務。額外的元數據能夠添加到eureka.instance.metadataMap
中的實例註冊中,而且這將在遠程客戶端中可訪問,但通常不會更改客戶端的行爲,除非意識到元數據的含義。下面描述了幾個特殊狀況,其中Spring Cloud已經爲元數據映射指定了含義。
在Cloudfoundry上使用Eureka
Cloudfoundry有一個全局路由器,因此同一個應用程序的全部實例都具備相同的主機名(在具備類似架構的其餘PaaS解決方案中也是如此)。這不必定是使用Eureka的障礙,但若是您使用路由器(建議,甚至是強制性的,具體取決於您的平臺的設置方式),則須要明確設置主機名和端口號(安全或非安全),以便他們使用路由器。您可能還須要使用實例元數據,以便您能夠區分客戶端上的實例(例如,在自定義負載平衡器中)。默認狀況下,eureka.instance.instanceId
爲vcap.application.instance_id
。例如:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">eureka: instance: hostname: ${vcap.application.uris[0]} nonSecurePort: 80</span></span>
根據Cloudfoundry實例中安全規則的設置方式,您能夠註冊並使用主機VM的IP地址進行直接的服務到服務調用。此功能還沒有在Pivotal Web Services(PWS)上提供。
在AWS上使用Eureka
若是應用程序計劃將部署到AWS雲,那麼Eureka實例必須被配置爲AWS意識到,這能夠經過定製來完成EurekaInstanceConfigBean方式以下:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean @Profile("!default") public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) { EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(inetUtils); AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka"); b.setDataCenterInfo(info); return b; }</code></span></span>
更改Eureka實例ID
香草Netflix Eureka實例註冊了與其主機名相同的ID(即每一個主機只有一個服務)。Spring Cloud Eureka提供了一個明智的默認,以下所示:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}
。例如myhost:myappname:8080
。
使用Spring Cloud,您能夠經過在eureka.instance.instanceId
中提供惟一的標識符來覆蓋此。例如:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">eureka: instance: instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}</span></span>
使用這個元數據和在localhost上部署的多個服務實例,隨機值將在那裏進行,以使實例是惟一的。在Cloudfoundry中,vcap.application.instance_id
將在Spring Boot應用程序中自動填充,所以不須要隨機值。
一旦您擁有@EnableDiscoveryClient
(或@EnableEurekaClient
)的應用程序,您就能夠使用它來從Eureka服務器發現服務實例。一種方法是使用本機com.netflix.discovery.EurekaClient
(而不是Spring雲DiscoveryClient
),例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@Autowired private EurekaClient discoveryClient; public String serviceUrl() { InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false); return instance.getHomePageUrl(); }</span></span>
提示 |
不要使用 |
您沒必要使用原始的Netflix EurekaClient
,一般在某種包裝器後面使用它更爲方便。Spring Cloud支持Feign(REST客戶端構建器),還支持Spring RestTemplate
使用邏輯Eureka服務標識符(VIP)而不是物理URL。要使用固定的物理服務器列表配置Ribbon,您能夠將<client>.ribbon.listOfServers
設置爲逗號分隔的物理地址(或主機名)列表,其中<client>
是客戶端的ID。
您還能夠使用org.springframework.cloud.client.discovery.DiscoveryClient
,它爲Netflix不具體的發現客戶端提供簡單的API,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@Autowired private DiscoveryClient discoveryClient; public String serviceUrl() { List<ServiceInstance> list = discoveryClient.getInstances("STORES"); if (list != null && list.size() > 0 ) { return list.get(0).getUri(); } return null; }</span></span>
做爲一個實例也包括按期心跳到註冊表(經過客戶端的serviceUrl
),默認持續時間爲30秒。在實例,服務器和客戶端在其本地緩存中都具備相同的元數據(所以可能須要3個心跳)以前,客戶端才能發現服務。您能夠使用eureka.instance.leaseRenewalIntervalInSeconds
更改期限,這將加快客戶端鏈接到其餘服務的過程。在生產中,最好堅持使用默認值,由於服務器內部有一些計算能夠對租賃更新期進行假設。
若是您已將Eureka客戶端部署到多個區域,您可能但願這些客戶端在使用另外一個區域中的服務以前,利用同一區域內的服務。爲此,您須要正確配置您的Eureka客戶端。
首先,您須要確保將Eureka服務器部署到每一個區域,而且它們是彼此的對等體。有關詳細信息,請參閱區域和區域部分 。
接下來,您須要告知Eureka您的服務所在的區域。您能夠使用metadataMap
屬性來執行此操做。例如,若是service 1
部署到zone 1
和zone 2
,則須要在service 1
中設置如下Eureka屬性
1區服務1
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>eureka.instance.metadataMap.zone = zone1 eureka.client.preferSameZoneEureka = true</code></span></span>
第2區的服務1
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>eureka.instance.metadataMap.zone = zone2 eureka.client.preferSameZoneEureka = true</code></span></span>
要在項目中包含Eureka服務器,請使用組org.springframework.cloud
和工件id spring-cloud-starter-eureka-server
的啓動器。有關 使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
示例eureka服務器;
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableEurekaServer public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }</code></span></span>
服務器具備一個帶有UI的主頁,而且根據/eureka/*
下的正常Eureka功能的HTTP API端點。
提示 |
因爲Gradle的依賴關係解決規則和父母的bom功能缺少,只要依靠spring-cloud-starter-eureka-server就可能致使應用程序啓動失敗。要解決這個問題,必須添加Spring Boot Gradle插件,而且必須導入Spring雲啓動器父母bom: 的build.gradle <span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.6)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">buildscript { dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.5.RELEASE") } } apply plugin: "spring-boot" dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE" } }</code></span></span></span> |
Eureka服務器沒有後端存儲,可是註冊表中的服務實例都必須發送心跳以保持其註冊更新(所以能夠在內存中完成)。客戶端還具備eureka註冊的內存緩存(所以,他們沒必要爲註冊表提供每一個服務請求)。
默認狀況下,每一個Eureka服務器也是一個Eureka客戶端,而且須要(至少一個)服務URL來定位對等體。若是您不提供該服務將運行和工做,但它將淋浴您的日誌與大量的噪音沒法註冊對等體。
關於區域和區域的客戶端Ribbon支持的詳細信息,請參見下文。
只要存在某種監視器或彈性運行時間(例如Cloud Foundry),兩個高速緩存(客戶機和服務器)和心跳的組合使獨立的Eureka服務器對故障具備至關的彈性。在獨立模式下,您可能更喜歡關閉客戶端行爲,所以不會繼續嘗試而且沒法訪問其對等體。例:
application.yml(Standalone Eureka Server)
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">server: port: 8761 eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/</span></span>
請注意,serviceUrl
指向與本地實例相同的主機。
經過運行多個實例並請求他們相互註冊,能夠使Eureka更具彈性和可用性。事實上,這是默認的行爲,因此你須要作的只是爲對方添加一個有效的serviceUrl
,例如
application.yml(Two Peer Aware Eureka服務器)
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">--- spring: profiles: peer1 eureka: instance: hostname: peer1 client: serviceUrl: defaultZone: http://peer2/eureka/ --- spring: profiles: peer2 eureka: instance: hostname: peer2 client: serviceUrl: defaultZone: http://peer1/eureka/</span></span>
在這個例子中,咱們有一個YAML文件,能夠經過在不一樣的Spring配置文件中運行,在2臺主機(peer1和peer2)上運行相同的服務器。您能夠使用此配置來測試單個主機上的對等體感知(經過操做/etc/hosts
來解析主機名,在生產中沒有太多價值)。事實上,若是您在一臺知道本身的主機名的機器上運行(默認狀況下使用java.net.InetAddress
查找),則不須要eureka.instance.hostname
。
您能夠向系統添加多個對等體,只要它們至少一個邊緣彼此鏈接,則它們將在它們之間同步註冊。若是對等體在物理上分離(在數據中心內或多個數據中心之間),則系統原則上能夠分裂腦型故障。
在某些狀況下,Eureka優先發布服務的IP地址而不是主機名。將eureka.instance.preferIpAddress
設置爲true
,而且當應用程序向eureka註冊時,它將使用其IP地址而不是其主機名。
Netflix的創造了一個調用的庫Hystrix實現了斷路器圖案。在微服務架構中,一般有多層服務調用。
圖1.微服務圖
較低級別的服務中的服務故障可能致使用戶級聯故障。當對特定服務的呼叫達到必定閾值時(Hystrix中的默認值爲5秒內的20次故障),電路打開,不進行通話。在錯誤和開路的狀況下,開發人員能夠提供後備。
圖2. Hystrix回退防止級聯故障
開放式電路會中止級聯故障,並容許沒必要要的或失敗的服務時間來癒合。回退能夠是另外一個Hystrix保護的調用,靜態數據或一個正常的空值。回退可能被連接,因此第一個回退使得一些其餘業務電話又回到靜態數據。
要在項目中包含Hystrix,請使用組org.springframework.cloud
和artifact id spring-cloud-starter-hystrix
的啓動器。有關 使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
示例啓動應用程序:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@SpringBootApplication @EnableCircuitBreaker public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } } @Component public class StoreIntegration { @HystrixCommand(fallbackMethod = "defaultStores") public Object getStores(Map<String, Object> parameters) { //do stuff that might fail } public Object defaultStores(Map<String, Object> parameters) { return /* something useful */; } }</span></span>
@HystrixCommand
由名爲「javanica」的Netflix contrib庫提供 。Spring Cloud在鏈接到Hystrix斷路器的代理中使用該註釋自動包裝Spring bean。斷路器計算什麼時候打開和關閉電路,以及在發生故障時應該作什麼。
要配置@HystrixCommand
,您能夠使用commandProperties
屬性列出@HystrixProperty
註釋。請參閱 這裏 瞭解更多詳情。有關 可用屬性的詳細信息,請參閱Hystrix維基。
若是您但願某些線程本地上下文傳播到@HystrixCommand
,默認聲明將不起做用,由於它在線程池中執行命令(超時)。您能夠使用某些配置或直接在註釋中使用與使用相同的線程來調用Hystrix,方法是要求使用不一樣的「隔離策略」。例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@HystrixCommand(fallbackMethod = "stubMyService", commandProperties = { @HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE") } ) ...</code></span></span>
若是您使用@SessionScope
或@RequestScope
,一樣的事情也適用。您將知道什麼時候須要執行此操做,由於運行時異常說它找不到範圍的上下文。
您還能夠將hystrix.shareSecurityContext
屬性設置爲true
。這樣作會自動配置一個Hystrix併發策略插件鉤子,他將SecurityContext
從主線程傳送到Hystrix命令使用的鉤子。Hystrix不容許註冊多個hystrix併發策略,所以能夠經過將本身的HystrixConcurrencyStrategy
聲明爲Spring bean來實現擴展機制。Spring Cloud將在Spring上下文中查找您的實現,並將其包裝在本身的插件中。
鏈接斷路器的狀態也暴露在呼叫應用程序的/health
端點中。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "hystrix": { "openCircuitBreakers": [ "StoreIntegration::getStoresByLocationLink" ], "status": "CIRCUIT_OPEN" }, "status": "UP" }</code></span></span>
要使Hystrix指標流包含對spring-boot-starter-actuator
的依賴。這將使/hystrix.stream
做爲管理端點。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency></code></span></span>
Hystrix的主要優勢之一是它收集關於每一個HystrixCommand的一套指標。Hystrix儀表板以有效的方式顯示每一個斷路器的運行情況。
圖3. Hystrix儀表板
當使用包含Ribbon客戶端的Hystrix命令時,您須要確保您的Hystrix超時配置爲長於配置的Ribbon超時,包括可能進行的任何潛在的重試。例如,若是您的Ribbon鏈接超時爲一秒鐘,而且Ribbon客戶端可能會重試該請求三次,那麼您的Hystrix超時應該略超過三秒鐘。
要在項目中包含Hystrix儀表板,請使用組org.springframework.cloud
和工件ID spring-cloud-starter-hystrix-dashboard
的啓動器。有關 使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
要運行Hystrix儀表板使用@EnableHystrixDashboard
註釋您的Spring Boot主類。而後訪問/hystrix
,並將儀表板指向Hystrix客戶端應用程序中的單個實例/hystrix.stream
端點。
從我的實例看,Hystrix數據在系統總體健康方面不是很是有用。Turbine是將全部相關/hystrix.stream
端點聚合到Hystrix儀表板中使用的/turbine.stream
的應用程序。我的實例位於Eureka。運行Turbine就像使用@EnableTurbine
註釋(例如使用spring-cloud-starter-turbine設置類路徑)註釋主類同樣簡單。來自Turbine 1維基的全部文檔配置屬性都適用。惟一的區別是turbine.instanceUrlSuffix
不須要預先添加的端口,除非turbine.instanceInsertPort=false
自動處理。
注意 |
默認狀況下,Turbine經過在Eureka中查找其homePageUrl 條目,而後將/hystrix.stream 附加到註冊的實例上查找/hystrix.stream 端點。這意味着若是spring-boot-actuator 在本身的端口上運行(這是默認值),則對/hystrix.stream 的調用將失敗。要使渦輪機找到正確端口的Hystrix流,您須要向實例的元數據中添加management.port : |
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">eureka: instance: metadata-map: management.port: ${management.port:8081}</span></span>
配置密鑰turbine.appConfig
是渦輪機將用於查找實例的尤里卡服務列表。渦輪流而後在Hystrix儀表板中使用以下URL:http://my.turbine.sever:8080/turbine.stream?cluster=<CLUSTERNAME>;
(若是名稱爲「默認值」,則能夠省略羣集參數)。cluster
參數必須與turbine.aggregator.clusterConfig
中的條目相匹配。從eureka返回的值是大寫字母,所以若是有一個名爲「customers」的Eureka註冊了一個應用程序,咱們預計此示例能夠正常工做:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">turbine: aggregator: clusterConfig: CUSTOMERS appConfig: customers</span></span>
clusterName
能夠經過turbine.clusterNameExpression
中的SPEL表達式以root身份InstanceInfo
進行自定義。默認值爲appName
,這意味着Eureka serviceId最終做爲集羣密鑰(即客戶的InstanceInfo
具備appName
「CUSTOMERS」)。一個不一樣的例子是turbine.clusterNameExpression=aSGName
,它將從AWS ASG名稱獲取集羣名稱。另外一個例子:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">turbine: aggregator: clusterConfig: SYSTEM,USER appConfig: customers,stores,ui,admin clusterNameExpression: metadata['cluster']</span></span>
在這種狀況下,來自4個服務的集羣名稱從其元數據映射中提取,而且預期具備包含「SYSTEM」和「USER」的值。
要爲全部應用程序使用「默認」集羣,您須要一個字符串文字表達式(帶單引號,而且若是它在YAML中也使用雙引號進行轉義):
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">turbine: appConfig: customers,stores clusterNameExpression: "'default'"</span></span>
Spring Cloud提供了一個spring-cloud-starter-turbine
,它具備運行Turbine服務器所需的全部依賴關係。只需建立一個Spring Boot應用程序並用@EnableTurbine
註釋它。
注意 |
默認狀況下,Spring Cloud容許Turbine使用主機和端口容許每一個主機在每一個羣集中進行多個進程。若是你想建成Turbine本地Netflix的行爲,它不會容許每一個主機上的多個過程,每簇(關鍵實例ID是主機名),而後將該屬性設置turbine.combineHostPort=false 。 |
在某些環境中(例如,在PaaS設置中),從全部分佈式Hystrix命令中提取度量的經典Turbine模型不起做用。在這種狀況下,您可能但願讓Hystrix命令將度量標準推送到Turbine,而且Spring Cloud能夠使用消息傳遞。您須要在客戶端上執行的全部操做都爲您選擇的spring-cloud-netflix-hystrix-stream
和spring-cloud-starter-stream-*
添加依賴關係(有關經紀人的詳細信息,請參閱Spring Cloud Stream文檔,以及如何配置客戶端憑據,可是應該爲當地經紀人開箱即用)。
在服務器端只需建立一個Spring Boot應用程序並使用@EnableTurbineStream
進行註釋,默認狀況下將在8989端口(將您的Hystrix儀表板指向該端口,任何路徑)。您能夠使用server.port
或turbine.stream.port
自定義端口。若是類路徑中還有spring-boot-starter-web
和spring-boot-starter-actuator
,那麼您能夠經過提供不一樣的management.port
在單獨端口(默認狀況下使用Tomcat)打開Actuator端點。
而後,您能夠將Hystrix儀表板指向Turbine Stream服務器,而不是單個Hystrix流。若是Turbine Stream在myhost上的端口8989上運行,則將http://myhost:8989
放在Hystrix儀表板中的流輸入字段中。電路將以各自的serviceId爲前綴,後跟一個點,而後是電路名稱。
Spring Cloud提供了一個spring-cloud-starter-turbine-stream
,它具備您須要的Turbine Stream服務器運行所需的全部依賴項,只需添加您選擇的Stream binder,例如spring-cloud-starter-stream-rabbit
。您須要Java 8來運行應用程序,由於它是基於Netty的。
Ribbon是一個客戶端負載均衡器,它能夠很好地控制HTTP和TCP客戶端的行爲。Feign已經使用Ribbon,因此若是您使用@FeignClient
,則本節也適用。
Ribbon中的中心概念是指定客戶端的概念。每一個負載平衡器是組合的組合的一部分,它們一塊兒工做以根據須要聯繫遠程服務器,而且集合具備您將其做爲應用程序開發人員(例如使用@FeignClient
註釋)的名稱。Spring Cloud使用RibbonClientConfiguration
爲每一個命名的客戶端根據須要建立一個新的合奏做爲ApplicationContext
。這包含(除其餘外)ILoadBalancer
,RestClient
和ServerListFilter
。
要在項目中包含Ribbon,請使用組org.springframework.cloud
和工件ID spring-cloud-starter-ribbon
的起始器。有關 使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
您能夠使用<client>.ribbon.*
中的外部屬性來配置Ribbon客戶端的某些位,這與使用Netflix API自己沒有什麼不一樣,只能使用Spring Boot配置文件。本機選項能夠在CommonClientConfigKey
(功能區內核心部分)中做爲靜態字段進行檢查。
Spring Cloud還容許您經過使用@RibbonClient
聲明其餘配置(位於RibbonClientConfiguration
之上)來徹底控制客戶端。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration @RibbonClient(name = "foo", configuration = FooConfiguration.class) public class TestConfiguration { }</code></span></span>
在這種狀況下,客戶端由RibbonClientConfiguration
中已經存在的組件與FooConfiguration
中的任何組件組成(後者一般會覆蓋前者)。
警告 |
FooConfiguration 必須是@Configuration ,但請注意,它不在主應用程序上下文的@ComponentScan 中,不然將由全部@RibbonClients 共享。若是您使用@ComponentScan (或@SpringBootApplication ),則須要採起措施避免包含(例如將其放在一個單獨的,不重疊的包中,或者指定要在@ComponentScan )。 |
Spring Cloud Netflix默認狀況下爲Ribbon(BeanType
beanName:ClassName
)提供如下bean:
IClientConfig
ribbonClientConfig:DefaultClientConfigImpl
IRule
ribbonRule:ZoneAvoidanceRule
IPing
ribbonPing:NoOpPing
ServerList<Server>
ribbonServerList:ConfigurationBasedServerList
ServerListFilter<Server>
ribbonServerListFilter:ZonePreferenceServerListFilter
ILoadBalancer
ribbonLoadBalancer:ZoneAwareLoadBalancer
ServerListUpdater
ribbonServerListUpdater:PollingServerListUpdater
建立一個類型的bean並將其放置在@RibbonClient
配置(例如上面的FooConfiguration
)中)容許您覆蓋所描述的每一個bean。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class FooConfiguration { @Bean public IPing ribbonPing(IClientConfig config) { return new PingUrl(); } }</code></span></span>
這用PingUrl
代替NoOpPing
。
從版本1.2.0開始,Spring Cloud Netflix如今支持使用屬性與Ribbon文檔兼容來自定義Ribbon客戶端。
這容許您在不一樣環境中更改啓動時的行爲。
支持的屬性以下所示,應以<clientName>.ribbon.
爲前綴:
NFLoadBalancerClassName
:應實施ILoadBalancer
NFLoadBalancerRuleClassName
:應實施IRule
NFLoadBalancerPingClassName
:應實施IPing
NIWSServerListClassName
:應實施ServerList
NIWSServerListFilterClassName
應實施ServerListFilter
注意 |
在這些屬性中定義的類優先於使用@RibbonClient(configuration=MyRibbonConfig.class) 定義的bean和由Spring Cloud Netflix提供的默認值。 |
要設置服務名稱users
的IRule
,您能夠設置如下內容:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">users: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule</span></span>
當Eureka與Ribbon結合使用(即二者都在類路徑上)時,ribbonServerList
將被擴展爲DiscoveryEnabledNIWSServerList
,擴展名爲Eureka的服務器列表。它還用NIWSDiscoveryPing
替換IPing
接口,代理到Eureka以肯定服務器是否啓動。默認狀況下安裝的ServerList
是一個DomainExtractingServerList
,其目的是使物理元數據可用於負載平衡器,而不使用AWS AMI元數據(這是Netflix依賴的)。默認狀況下,服務器列表將使用實例元數據(如遠程客戶端集合eureka.instance.metadataMap.zone
)中提供的「區域」信息構建,若是缺乏,則能夠使用服務器主機名中的域名做爲代理用於區域(若是設置了標誌approximateZoneFromHostname
)。一旦區域信息可用,它能夠在ServerListFilter
中使用。默認狀況下,它將用於定位與客戶端相同區域的服務器,由於默認值爲ZonePreferenceServerListFilter
。默認狀況下,客戶端的區域與遠程實例的方式相同,即經過eureka.instance.metadataMap.zone
。
注意 |
設置客戶端區域的正統「archaius」方式是經過一個名爲「@zone」的配置屬性,若是可用,Spring Cloud將優先使用全部其餘設置(請注意,該鍵必須被引用)在YAML配置中)。 |
注意 |
若是沒有其餘的區域數據源,則基於客戶端配置(與實例配置相反)進行猜想。咱們將eureka.client.availabilityZones (從區域名稱映射到區域列表),並將實例本身的區域的第一個區域(即eureka.client.region ,其默認爲「us-east-1」爲與本機Netflix的兼容性)。 |
Eureka是一種方便的方式來抽象遠程服務器的發現,所以您沒必要在客戶端中對其URL進行硬編碼,但若是您不想使用它,Ribbon和Feign仍然是適用的。假設您已經爲「商店」申請了@RibbonClient
,而且Eureka未被使用(甚至不在類路徑上)。Ribbon客戶端默認爲已配置的服務器列表,您能夠提供這樣的配置
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">stores: ribbon: listOfServers: example.com,google.com</span></span>
設置屬性ribbon.eureka.enabled = false
將明確禁用在Ribbon中使用Eureka。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">ribbon: eureka: enabled: false</span></span>
您也能夠直接使用LoadBalancerClient
。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public class MyClass { @Autowired private LoadBalancerClient loadBalancer; public void doStuff() { ServiceInstance instance = loadBalancer.choose("stores"); URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort())); // ... do something with the URI } }</code></span></span>
每一個Ribbon命名的客戶端都有一個相應的子應用程序上下文,Spring Cloud維護,這個應用程序上下文在第一個請求中被延遲加載到命名的客戶端。能夠經過指定Ribbon客戶端的名稱,在啓動時,能夠更改此延遲加載行爲,從而熱切加載這些子應用程序上下文。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">ribbon: eager-load: enabled: true clients: client1, client2, client3</span></span>
Feign是一個聲明式的Web服務客戶端。這使得Web服務客戶端的寫入更加方便 要使用Feign建立一個界面並對其進行註釋。它具備可插入註釋支持,包括Feign註釋和JAX-RS註釋。Feign還支持可插拔編碼器和解碼器。Spring Cloud增長了對Spring MVC註釋的支持,並使用Spring Web中默認使用的HttpMessageConverters
。Spring Cloud集成Ribbon和Eureka以在使用Feign時提供負載均衡的http客戶端。
要在您的項目中包含Feign,請使用組org.springframework.cloud
和工件ID spring-cloud-starter-feign
的啓動器。有關 使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
示例spring boot應用
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration @ComponentScan @EnableAutoConfiguration @EnableEurekaClient @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }</code></span></span>
StoreClient.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@FeignClient("stores") public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = "/stores") List<Store> getStores(); @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") Store update(@PathVariable("storeId") Long storeId, Store store); }</code></span></span>
在@FeignClient
註釋中,String值(以上「存儲」)是一個任意的客戶端名稱,用於建立Ribbon負載平衡器(有關Ribbon支持的詳細信息,請參閱下文))。您還能夠使用url
屬性(絕對值或只是主機名)指定URL。應用程序上下文中的bean的名稱是該接口的徹底限定名稱。要指定您本身的別名值,您能夠使用@FeignClient
註釋的qualifier
值。
以上的Ribbon客戶端將會發現「商店」服務的物理地址。若是您的應用程序是Eureka客戶端,那麼它將解析Eureka服務註冊表中的服務。若是您不想使用Eureka,您能夠簡單地配置外部配置中的服務器列表(例如,參見 上文)。
Spring Cloud的Feign支持的中心概念是指定的客戶端。每一個僞裝客戶端都是組合的組件的一部分,它們一塊兒工做以根據須要聯繫遠程服務器,而且該集合具備您將其做爲應用程序開發人員使用@FeignClient
註釋的名稱。Spring Cloud根據須要,使用FeignClientsConfiguration
爲每一個已命名的客戶端建立一個新的集合ApplicationContext
。這包含(除其餘外)feign.Decoder
,feign.Encoder
和feign.Contract
。
Spring Cloud能夠經過使用@FeignClient
聲明額外的配置(FeignClientsConfiguration
)來徹底控制假客戶端。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@FeignClient(name = "stores", configuration = FooConfiguration.class) public interface StoreClient { //.. }</code></span></span>
在這種狀況下,客戶端由FeignClientsConfiguration
中的組件與FooConfiguration
中的任何組件組成(後者將覆蓋前者)。
注意 |
FooConfiguration 不須要使用@Configuration 註釋。可是,若是是,則請注意將其從任何@ComponentScan 中排除,不然將包含此配置,由於它將成爲feign.Decoder ,feign.Encoder ,feign.Contract 等的默認來源,指定時。這能夠經過將其放置在任何@ComponentScan 或@SpringBootApplication 的單獨的不重疊的包中,或者能夠在@ComponentScan 中明確排除。 |
注意 |
serviceId 屬性如今已被棄用,有利於name 屬性。 |
警告 |
之前,使用url 屬性,不須要name 屬性。如今須要使用name 。 |
name
和url
屬性支持佔位符。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@FeignClient(name = "${feign.name}", url = "${feign.url}") public interface StoreClient { //.. }</code></span></span>
Spring Cloud Netflix默認爲feign(BeanType
beanName:ClassName
)提供如下bean:
Decoder
feignDecoder:ResponseEntityDecoder
(其中包含SpringDecoder
)
Encoder
feignEncoder:SpringEncoder
Logger
feignLogger:Slf4jLogger
Contract
feignContract:SpringMvcContract
Feign.Builder
feignBuilder:HystrixFeign.Builder
Client
feignClient:若是Ribbon啓用,則爲LoadBalancerFeignClient
,不然將使用默認的feign客戶端。
能夠經過將feign.okhttp.enabled
或feign.httpclient.enabled
設置爲true
,並將它們放在類路徑上來使用OkHttpClient和ApacheHttpClient feign客戶端。
Spring Cloud Netflix 默認狀況下不提供如下bean,可是仍然從應用程序上下文中查找這些類型的bean以建立假客戶機:
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory
建立一個類型的bean並將其放置在@FeignClient
配置(例如上面的FooConfiguration
)中)容許您覆蓋所描述的每一個bean。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class FooConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("user", "password"); } }</code></span></span>
這將SpringMvcContract
替換爲feign.Contract.Default
,並將RequestInterceptor
添加到RequestInterceptor
的集合中。
能夠在@EnableFeignClients
屬性defaultConfiguration
中以與上述類似的方式指定默認配置。不一樣之處在於,此配置將適用於全部假客戶端。
注意 |
若是您須要在RequestInterceptor`s you will need to either set the thread isolation strategy for Hystrix to `SEMAPHORE 中使用ThreadLocal 綁定變量,或在Feign中禁用Hystrix。 |
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"># To disable Hystrix in Feign feign: hystrix: enabled: false # To set thread isolation to SEMAPHORE hystrix: command: default: execution: isolation: strategy: SEMAPHORE</code></span></span>
在某些狀況下,可能須要以上述方法不可能自定義您的Feign客戶端。在這種狀況下,您能夠使用Feign Builder API建立客戶端 。下面是一個建立兩個具備相同接口的Feign客戶端的示例,可是使用單獨的請求攔截器配置每一個客戶端。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Import(FeignClientsConfiguration.class) class FooController { private FooClient fooClient; private FooClient adminClient; @Autowired public FooController( Decoder decoder, Encoder encoder, Client client) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) .target(FooClient.class, "http://PROD-SVC"); this.adminClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) .target(FooClient.class, "http://PROD-SVC"); } }</code></span></span>
注意 |
在上面的例子中,FeignClientsConfiguration.class 是Spring Cloud Netflix提供的默認配置。 |
注意 |
PROD-SVC 是客戶端將要求的服務的名稱。 |
若是Hystrix在類路徑上,feign.hystrix.enabled=true
,Feign將用斷路器包裝全部方法。還能夠返回com.netflix.hystrix.HystrixCommand
。這樣就能夠使用無效模式(調用.toObservable()
或.observe()
或異步使用(調用.queue()
))。
要在每一個客戶端基礎上禁用Hystrix支持建立一個帶有「原型」範圍的香草Feign.Builder
,例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class FooConfiguration { @Bean @Scope("prototype") public Feign.Builder feignBuilder() { return Feign.builder(); } }</code></span></span>
警告 |
在Spring Cloud達爾斯頓發佈以前,若是Hystrix在類路徑Feign中默認將全部方法包裝在斷路器中。這種默認行爲在Spring Cloud達爾斯頓改變了同意選擇加入的方式。 |
Hystrix支持回退的概念:當電路斷開或出現錯誤時執行的默認代碼路徑。要爲給定的@FeignClient
啓用回退,請將fallback
屬性設置爲實現回退的類名。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@FeignClient(name = "hello", fallback = HystrixClientFallback.class) protected interface HystrixClient { @RequestMapping(method = RequestMethod.GET, value = "/hello") Hello iFailSometimes(); } static class HystrixClientFallback implements HystrixClient { @Override public Hello iFailSometimes() { return new Hello("fallback"); } }</code></span></span>
若是須要訪問致使回退觸發的緣由,能夠使用@FeignClient
內的fallbackFactory
屬性。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class) protected interface HystrixClient { @RequestMapping(method = RequestMethod.GET, value = "/hello") Hello iFailSometimes(); } @Component static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> { @Override public HystrixClient create(Throwable cause) { return new HystrixClientWithFallBackFactory() { @Override public Hello iFailSometimes() { return new Hello("fallback; reason was: " + cause.getMessage()); } }; } }</code></span></span>
警告 |
在Feign中執行回退以及Hystrix回退的工做方式存在侷限性。當前返回com.netflix.hystrix.HystrixCommand 和rx.Observable 的方法目前不支持回退。 |
@Primary
當使用Feign與Hystrix回退時,在同一類型的ApplicationContext
中有多個bean。這將致使@Autowired
不起做用,由於沒有一個bean,或者標記爲主。要解決這個問題,Spring Cloud Netflix將全部Feign實例標記爲@Primary
,因此Spring Framework將知道要注入哪一個bean。在某些狀況下,這多是不可取的。要關閉此行爲,將@FeignClient
的primary
屬性設置爲false。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@FeignClient(name = "hello", primary = false) public interface HelloClient { // methods here }</code></span></span>
Feign經過單繼承接口支持樣板apis。這樣就能夠將經常使用操做分紅方便的基本界面。
UserService.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface UserService { @RequestMapping(method = RequestMethod.GET, value ="/users/{id}") User getUser(@PathVariable("id") long id); }</code></span></span>
UserResource.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RestController public class UserResource implements UserService { }</code></span></span>
UserClient.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package project.user; @FeignClient("users") public interface UserClient extends UserService { }</code></span></span>
注意 |
一般不建議在服務器和客戶端之間共享接口。它引入了緊耦合,而且實際上並不適用於當前形式的Spring MVC(方法參數映射不被繼承)。 |
您能夠考慮爲Feign請求啓用請求或響應GZIP壓縮。您能夠經過啓用其中一個屬性來執行此操做:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">feign.compression.request.enabled=true feign.compression.response.enabled=true</code></span></span>
Feign請求壓縮爲您提供與您爲Web服務器設置的設置類似的設置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">feign.compression.request.enabled=true feign.compression.request.mime-types=text/xml,application/xml,application/json feign.compression.request.min-request-size=2048</code></span></span>
這些屬性可讓您對壓縮介質類型和最小請求閾值長度有選擇性。
爲每一個建立的Feign客戶端建立一個記錄器。默認狀況下,記錄器的名稱是用於建立Feign客戶端的接口的完整類名。Feign日誌記錄僅響應DEBUG
級別。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">logging.level.project.user.UserClient: DEBUG</code></span></span>
您能夠爲每一個客戶端配置的Logger.Level
對象告訴Feign記錄多少。選擇是:
NONE
,無記錄(DEFAULT)。
BASIC
,只記錄請求方法和URL以及響應狀態代碼和執行時間。
HEADERS
,記錄基本信息以及請求和響應標頭。
FULL
,記錄請求和響應的頭文件,正文和元數據。
例如,如下將Logger.Level
設置爲FULL
:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public class FooConfiguration { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }</code></span></span>
Netflix客戶端配置庫Archaius 它是全部Netflix OSS組件用於配置的庫。Archaius是Apache Commons Configuration項目的擴展。它容許經過輪詢源進行更改或將源更改推送到客戶端來進行配置更新。Archaius使用Dynamic <Type> Property類做爲屬性的句柄。
Archaius示例
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">class ArchaiusTest { DynamicStringProperty myprop = DynamicPropertyFactory .getInstance() .getStringProperty("my.prop"); void doSomething() { OtherClass.someMethod(myprop.get()); } }</code></span></span>
Archaius具備本身的一組配置文件和加載優先級。Spring應用程序通常不該直接使用Archaius,但本地仍然須要配置Netflix工具。Spring Cloud具備Spring環境橋,因此Archaius能夠從Spring環境讀取屬性。這容許Spring Boot項目使用正常的配置工具鏈,同時容許他們在文檔中大部分配置Netflix工具。
路由在微服務體系結構的一個組成部分。例如,/
能夠映射到您的Web應用程序,/api/users
映射到用戶服務,並將/api/shop
映射到商店服務。Zuul是Netflix的基於JVM的路由器和服務器端負載均衡器。
Netflix使用Zuul進行如下操做:
認證
洞察
壓力測試
金絲雀測試
動態路由
服務遷移
負載脫落
安全
靜態響應處理
主動/主動流量管理
Zuul的規則引擎容許基本上寫任何JVM語言編寫規則和過濾器,內置Java和Groovy。
注意 |
配置屬性zuul.max.host.connections 已被兩個新屬性zuul.host.maxTotalConnections 和zuul.host.maxPerRouteConnections 替換,分別默認爲200和20。 |
注意 |
全部路由的默認Hystrix隔離模式(ExecutionIsolationStrategy)爲SEMAPHORE。若是此隔離模式是首選,則zuul.ribbonIsolationStrategy 能夠更改成THREAD。 |
要在您的項目中包含Zuul,請使用組org.springframework.cloud
和artifact id spring-cloud-starter-zuul
的啓動器。有關 使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
Spring Cloud已經建立了一個嵌入式Zuul代理,以簡化UI應用程序想要代理對一個或多個後端服務的呼叫的很是常見的用例的開發。此功能對於用戶界面對其所需的後端服務進行代理是有用的,避免了對全部後端獨立管理CORS和驗證問題的需求。
要啓用它,使用@EnableZuulProxy
註釋Spring Boot主類,並將本地調用轉發到相應的服務。按照慣例,具備ID「用戶」的服務將接收來自位於/users
(具備前綴stripped)的代理的請求。代理使用Ribbon來定位一個經過發現轉發的實例,而且全部請求都以 hystrix命令執行,因此故障將顯示在Hystrix指標中,一旦電路打開,代理將不會嘗試聯繫服務。
注意 |
Zuul啓動器不包括髮現客戶端,所以對於基於服務ID的路由,您還須要在類路徑中提供其中一個路由(例如Eureka)。 |
要跳過自動添加的服務,請將zuul.ignored-services
設置爲服務標識模式列表。若是一個服務匹配一個被忽略的模式,並且包含在明確配置的路由映射中,那麼它將被無符號。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: ignoredServices: '*' routes: users: /myusers/**</code></span></span>
在此示例中,除 「用戶」 以外,全部服務都被忽略。
要擴充或更改代理路由,能夠添加以下所示的外部配置:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: users: /myusers/**</code></span></span>
這意味着對「/ myusers」的http呼叫轉發到「用戶」服務(例如「/ myusers / 101」轉發到「/ 101」)。
要得到對路由的更細粒度的控制,您能夠獨立地指定路徑和serviceId:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: users: path: /myusers/** serviceId: users_service</code></span></span>
這意味着對「/ myusers」的http呼叫轉發到「users_service」服務。路由必須有一個「路徑」,能夠指定爲螞蟻樣式模式,因此「/ myusers / *」只匹配一個級別,但「/ myusers / **」分層匹配。
後端的位置能夠被指定爲「serviceId」(用於發現的服務)或「url」(對於物理位置),例如
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: users: path: /myusers/** url: http://example.com/users_service</code></span></span>
這些簡單的URL路由不會被執行爲HystrixCommand
,也不能使用Ribbon對多個URL進行負載平衡。爲此,請指定service-route併爲serviceId配置Ribbon客戶端(目前須要在Ribbon中禁用Eureka支持:詳見上文),例如
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">zuul: routes: users: path: /myusers/** serviceId: users ribbon: eureka: enabled: false users: ribbon: listOfServers: example.com,google.com</code></span></span>
您能夠使用regexmapper在serviceId和路由之間提供約定。它使用名爲group的正則表達式從serviceId中提取變量並將它們注入到路由模式中。
ApplicationConfiguration.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }</code></span></span>
這意味着serviceId「myusers-v1」將被映射到路由「/ v1 / myusers / **」。任何正則表達式都被接受,但全部命名組都必須存在於servicePattern和routePattern中。若是servicePattern與serviceId不匹配,則使用默認行爲。在上面的示例中,serviceId「myusers」將被映射到路由「/ myusers / **」(檢測不到版本)此功能默認禁用,僅適用於已發現的服務。
要爲全部映射添加前綴,請將zuul.prefix
設置爲一個值,例如/api
。默認狀況下,請求被轉發以前,代理前綴被刪除(使用zuul.stripPrefix=false
關閉此行爲)。您還能夠關閉從各路線剝離服務特定的前綴,例如
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: users: path: /myusers/** stripPrefix: false</code></span></span>
注意 |
zuul.stripPrefix 僅適用於zuul.prefix 中設置的前綴。它對給定路由path 中定義的前綴有影響。 |
在本示例中,對「/ myusers / 101」的請求將轉發到「/ myusers / 101」上的「users」服務。
zuul.routes
條目實際上綁定到類型爲ZuulProperties
的對象。若是您查看該對象的屬性,您將看到它還具備「可重試」標誌。將該標誌設置爲「true」使Ribbon客戶端自動重試失敗的請求(若是須要,能夠使用Ribbon客戶端配置修改重試操做的參數)。
默認狀況下,將X-Forwarded-Host
標頭添加到轉發的請求中。關閉set zuul.addProxyHeaders = false
。默認狀況下,前綴路徑被刪除,對後端的請求會拾取一個標題「X-Forwarded-Prefix」(上述示例中的「/ myusers」)。
若是您設置默認路由(「/」),則@EnableZuulProxy
的應用程序能夠做爲獨立服務器,例如zuul.route.home: /
將路由全部流量(即「/ **」)到「home」服務。
若是須要更細粒度的忽略,能夠指定要忽略的特定模式。在路由位置處理開始時評估這些模式,這意味着前綴應包含在模式中以保證匹配。忽略的模式跨越全部服務,並取代任何其餘路由規範。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: ignoredPatterns: /**/admin/** routes: users: /myusers/**</code></span></span>
這意味着諸如「/ myusers / 101」的全部呼叫將被轉發到「用戶」服務上的「/ 101」。可是包含「/ admin /」的呼叫將沒法解決。
警告 |
若是您須要您的路由保留訂單,則須要使用YAML文件,由於使用屬性文件將會丟失訂購。例如: |
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: users: path: /myusers/** legacy: path: /**</code></span></span>
若是要使用屬性文件,則legacy
路徑可能會在users
路徑前面展開,從而使users
路徑不可達。
zuul使用的默認HTTP客戶端如今由Apache HTTP Client支持,而不是不推薦使用的Ribbon RestClient
。要分別使用RestClient
或使用okhttp3.OkHttpClient
集合ribbon.restclient.enabled=true
或ribbon.okhttp.enabled=true
。
在同一個系統中的服務之間共享標題是可行的,可是您可能不但願敏感標頭泄漏到外部服務器的下游。您能夠在路由配置中指定被忽略頭文件列表。Cookies起着特殊的做用,由於它們在瀏覽器中具備明確的語義,而且它們老是被視爲敏感的。若是代理的消費者是瀏覽器,則下游服務的cookie也會致使用戶出現問題,由於它們都被混淆(全部下游服務看起來都是來自同一個地方)。
若是您對服務的設計很是謹慎,例如,若是隻有一個下游服務設置了Cookie,那麼您可能可讓他們從後臺一直到調用者。另外,若是您的代理設置cookie和全部後臺服務都是同一系統的一部分,那麼簡單地共享它們就能夠天然(例如使用Spring Session將它們連接到一些共享狀態)。除此以外,由下游服務設置的任何Cookie可能對呼叫者來講都不是頗有用,所以建議您將(至少)「Set-Cookie」和「Cookie」設置爲不屬於您的域名。即便是屬於您域名的路線,請嘗試仔細考慮容許Cookie在代理之間流動的含義。
靈敏頭能夠配置爲每一個路由的逗號分隔列表,例如
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: users: path: /myusers/** sensitiveHeaders: Cookie,Set-Cookie,Authorization url: https://downstream</code></span></span>
注意 |
這是sensitiveHeaders 的默認值,所以您不須要設置它,除非您但願它不一樣。注意這是Spring Cloud Netflix 1.1中的新功能(1.0中,用戶沒法控制標題,全部Cookie都在兩個方向上流動)。 |
sensitiveHeaders
是一個黑名單,默認值不爲空,因此要使Zuul發送全部標題(「被忽略」除外),您必須將其顯式設置爲空列表。若是您要將Cookie或受權標頭傳遞到後端,這是必要的。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: users: path: /myusers/** sensitiveHeaders: url: https://downstream</code></span></span>
也能夠經過設置zuul.sensitiveHeaders
來全局設置敏感標題。若是在路由上設置sensitiveHeaders
,則將覆蓋全局sensitiveHeaders
設置。
除了每一個路由的敏感標頭,您還能夠爲與下游服務交互期間應該丟棄的值(請求和響應)設置全局值爲zuul.ignoredHeaders
。默認狀況下,若是Spring安全性不在類路徑上,則它們是空的,不然它們被初始化爲由Spring Security指定的一組衆所周知的「安全性」頭(例如涉及緩存)。在這種狀況下的假設是下游服務可能也添加這些頭,咱們但願代理的值。爲了避免丟棄這些衆所周知的安全標頭,只要Spring安全性在類路徑上,您能夠將zuul.ignoreSecurityHeaders
設置爲false
。若是您禁用Spring安全性中的HTTP安全性響應頭,並但願由下游服務提供的值,這可能頗有用
若是您在Spring Boot執行器中使用@EnableZuulProxy
,您將啓用(默認狀況下)另外一個端點,經過HTTP可用/routes
。到此端點的GET將返回映射路由的列表。POST將強制刷新現有路由(例如,若是服務目錄中有更改)。您能夠經過將endpoints.routes.enabled
設置爲false
來禁用此端點。
注意 |
路由應自動響應服務目錄中的更改,但POST到/路由是強制更改當即發生的一種方式。 |
遷移現有應用程序或API時的常見模式是「扼殺」舊端點,用不一樣的實現慢慢替換它們。Zuul代理是一個有用的工具,由於您能夠使用它來處理來自舊端點的客戶端的全部流量,但將一些請求重定向到新端點。
示例配置:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: first: path: /first/** url: http://first.example.com second: path: /second/** url: forward:/second third: path: /third/** url: forward:/3rd legacy: path: /** url: http://legacy.example.com</code></span></span>
在這個例子中,咱們扼殺了「遺留」應用程序,該應用程序映射到全部與其餘模式不匹配的請求。/first/**
中的路徑已被提取到具備外部URL的新服務中。而且/second/**
中的路徑被轉發,以便它們能夠在本地處理,例如具備正常的Spring @RequestMapping
。/third/**
中的路徑也被轉發,但具備不一樣的前綴(即/third/foo
轉發到/3rd/foo
)。
注意 |
被忽略的模式並不徹底被忽略,它們只是不被代理處理(所以它們也被有效地轉發到本地)。 |
若是您@EnableZuulProxy
您能夠使用代理路徑上傳文件,只要文件很小,它就應該工做。對於大文件,有一個替代路徑繞過「/ zuul / *」中的Spring DispatcherServlet
(以免多部分處理)。也就是說,若是zuul.routes.customers=/customers/**
則能夠將大文件發送到「/ zuul / customers / *」。servlet路徑經過zuul.servletPath
進行外部化。若是代理路由引導您經過Ribbon負載均衡器,例如,超大文件也將須要提高超時設置
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000</code></span></span>
請注意,要使用大型文件進行流式傳輸,您須要在請求中使用分塊編碼(某些瀏覽器默認狀況下不會執行)。例如在命令行:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ curl -v -H "Transfer-Encoding: chunked" \ -F "file=@mylarge.iso" localhost:9999/zuul/simple/file</span></span>
處理傳入的請求時,查詢參數被解碼,所以能夠在Zuul過濾器中進行修改。而後在路由過濾器中構建後端請求時從新編碼它們。若是使用Javascript的encodeURIComponent()
方法編碼,結果可能與原始輸入不一樣。雖然這在大多數狀況下不會出現任何問題,但一些Web服務器能夠用複雜查詢字符串的編碼來挑選。
要強制查詢字符串的原始編碼,能夠將特殊標誌傳遞給ZuulProperties
,以便查詢字符串與HttpServletRequest::getQueryString
方法相同:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: forceOriginalQueryStringEncoding: true</code></span></span>
注意:此特殊標誌僅適用於SimpleHostRoutingFilter
,您能夠使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)
輕鬆覆蓋查詢參數,由於查詢字符串如今直接在原始的HttpServletRequest
上獲取。
若是您使用@EnableZuulServer
(而不是@EnableZuulProxy
),您也能夠運行不帶代理的Zuul服務器,或者有選擇地切換代理平臺的部分。您添加到ZuulFilter
類型的應用程序的任何bean都將自動安裝,與@EnableZuulProxy
同樣,但不會自動添加任何代理過濾器。
在這種狀況下,仍然經過配置「zuul.routes。*」來指定進入Zuul服務器的路由,但沒有服務發現和代理,因此「serviceId」和「url」設置將被忽略。例如:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml"> zuul: routes: api: /api/**</code></span></span>
將「/ api / **」中的全部路徑映射到Zuul過濾器鏈。
Spring Cloud的Zuul在代理和服務器模式下默認啓用了多個ZuulFilter
bean。有關啓用的可能過濾器,請參閱zuul過濾器包。若是要禁用它,只需設置zuul.<SimpleClassName>.<filterType>.disable=true
。按照慣例,filters
以後的包是Zuul過濾器類型。例如,禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
設置zuul.SendResponseFilter.post.disable=true
。
當Zuul中給定路由的電路跳閘時,您能夠經過建立類型爲ZuulFallbackProvider
的bean來提供回退響應。在這個bean中,您須要指定回退的路由ID,並提供返回的ClientHttpResponse
做爲後備。這是一個很是簡單的ZuulFallbackProvider
實現。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">class MyFallbackProvider implements ZuulFallbackProvider { @Override public String getRoute() { return "customers"; } @Override 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("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }</code></span></span>
這裏是路由配置的樣子。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">zuul: routes: customers: /customers/**</code></span></span>
若是您但願爲全部路由提供默認的回退,您能夠建立一個類型爲ZuulFallbackProvider
的bean,而且getRoute
方法返回*
或null
。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">class MyFallbackProvider implements ZuulFallbackProvider { @Override public String getRoute() { return "*"; } @Override 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("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }</code></span></span>
有關Zuul如何工做的通常概述,請參閱Zuul維基。
Zuul Servlet
Zuul被實現爲Servlet。對於通常狀況,Zuul嵌入到Spring調度機制中。這容許Spring MVC控制路由。在這種狀況下,Zuul被配置爲緩衝請求。若是須要經過Zuul不緩衝請求(例如大文件上傳),Servlet也將安裝在Spring調度程序以外。默認狀況下,它位於/zuul
。能夠使用zuul.servlet-path
屬性更改此路徑。
Zuul RequestContext
要在過濾器之間傳遞信息,Zuul使用a RequestContext
。其數據按照每一個請求的ThreadLocal
進行。關於路由請求,錯誤以及實際HttpServletRequest
和HttpServletResponse
的路由信息。RequestContext
擴展ConcurrentHashMap
,因此任何東西均可以存儲在上下文中。FilterConstants
包含由Spring Cloud Netflix安裝的過濾器使用的密鑰(稍後再安裝)。
@EnableZuulProxy
與@EnableZuulServer
Spring Cloud Netflix根據使用何種註釋來啓用Zuul安裝多個過濾器。@EnableZuulProxy
是@EnableZuulServer
的超集。換句話說,@EnableZuulProxy
包含@EnableZuulServer
安裝的全部過濾器。「代理」中的其餘過濾器啓用路由功能。若是你想要一個「空白」Zuul,你應該使用@EnableZuulServer
。
@EnableZuulServer
過濾器
建立從Spring Boot配置文件加載路由定義的SimpleRouteLocator
。
安裝瞭如下過濾器(正常Spring豆類):
前置過濾器
ServletDetectionFilter
:檢測請求是否經過Spring調度程序。使用鍵FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
設置布爾值。
FormBodyWrapperFilter
:解析表單數據,並對下游請求進行從新編碼。
DebugFilter
:若是設置debug
請求參數,則此過濾器將RequestContext.setDebugRouting()
和RequestContext.setDebugRequest()
設置爲true。
路由過濾器
SendForwardFilter
:此過濾器使用Servlet RequestDispatcher
轉發請求。轉發位置存儲在RequestContext
屬性FilterConstants.FORWARD_TO_KEY
中。這對於轉發到當前應用程序中的端點頗有用。
過濾器:
SendResponseFilter
:將代理請求的響應寫入當前響應。
錯誤過濾器:
SendErrorFilter
:若是RequestContext.getThrowable()
不爲null,則轉發到/錯誤(默認狀況下)。能夠經過設置error.path
屬性來更改默認轉發路徑(/error
)。
@EnableZuulProxy
過濾器
建立從DiscoveryClient
(如Eureka)以及屬性加載路由定義的DiscoveryClientRouteLocator
。每一個serviceId
從DiscoveryClient
建立路由。隨着新服務的添加,路由將被刷新。
除了上述過濾器以外,還安裝瞭如下過濾器(正常Spring豆類):
前置過濾器
PreDecorationFilter
:此過濾器根據提供的RouteLocator
肯定在哪裏和如何路由。它還爲下游請求設置各類與代理相關的頭。
路由過濾器
RibbonRoutingFilter
:此過濾器使用Ribbon,Hystrix和可插拔HTTP客戶端發送請求。服務ID位於RequestContext
屬性FilterConstants.SERVICE_ID_KEY
中。此過濾器能夠使用不一樣的HTTP客戶端。他們是:
Apache HttpClient
。這是默認的客戶端。
Squareup OkHttpClient
v3。經過在類路徑上設置com.squareup.okhttp3:okhttp
庫並設置ribbon.okhttp.enabled=true
來啓用此功能。
Netflix Ribbon HTTP客戶端。這能夠經過設置ribbon.restclient.enabled=true
來啓用。這個客戶端有限制,好比它不支持PATCH方法,還有內置的重試。
SimpleHostRoutingFilter
:此過濾器經過Apache HttpClient發送請求到預約的URL。URL位於RequestContext.getRouteHost()
。
自定義Zuul過濾示例
如下大部分如下「如何撰寫」示例都包含示例Zuul過濾器項目。還有一些操做該存儲庫中的請求或響應正文的例子。
如何編寫預過濾器
前置過濾器用於設置RequestContext
中的數據,用於下游的過濾器。主要用例是設置路由過濾器所需的信息。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public class QueryParamPreFilter extends ZuulFilter { @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration } @Override public String filterType() { return PRE_TYPE; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded && !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); if (request.getParameter("foo") != null) { // put the serviceId in `RequestContext` ctx.put(SERVICE_ID_KEY, request.getParameter("foo")); } return null; } }</code></span></span>
上面的過濾器從foo
請求參數填充SERVICE_ID_KEY
。實際上,作這種直接映射並非一個好主意,而是從foo
的值來查看服務ID。
如今填寫SERVICE_ID_KEY
,PreDecorationFilter
將不會運行,RibbonRoutingFilter
將會。若是您想要路由到完整的網址,請改用ctx.setRouteHost(url)
。
要修改路由過濾器將轉發的路徑,請設置REQUEST_URI_KEY
。
如何編寫路由過濾器
路由過濾器在預過濾器以後運行,並用於向其餘服務發出請求。這裏的大部分工做是將請求和響應數據轉換到客戶端所需的模型。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public class OkHttpRoutingFilter extends ZuulFilter { @Autowired private ProxyRequestHelper helper; @Override public String filterType() { return ROUTE_TYPE; } @Override public int filterOrder() { return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); } @Override public Object run() { OkHttpClient httpClient = new OkHttpClient.Builder() // customize .build(); RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String method = request.getMethod(); String uri = this.helper.buildZuulRequestURI(request); Headers.Builder headers = new Headers.Builder(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); Enumeration<String> values = request.getHeaders(name); while (values.hasMoreElements()) { String value = values.nextElement(); headers.add(name, value); } } InputStream inputStream = request.getInputStream(); RequestBody requestBody = null; if (inputStream != null && HttpMethod.permitsRequestBody(method)) { MediaType mediaType = null; if (headers.get("Content-Type") != null) { mediaType = MediaType.parse(headers.get("Content-Type")); } requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream)); } Request.Builder builder = new Request.Builder() .headers(headers.build()) .url(uri) .method(method, requestBody); Response response = httpClient.newCall(builder.build()).execute(); LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>(); for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) { responseHeaders.put(entry.getKey(), entry.getValue()); } this.helper.setResponse(response.code(), response.body().byteStream(), responseHeaders); context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running return null; } }</code></span></span>
上述過濾器將Servlet請求信息轉換爲OkHttp3請求信息,執行HTTP請求,而後將OkHttp3響應信息轉換爲Servlet響應。警告:此過濾器可能有錯誤,但功能不正確。
如何編寫過濾器
後置過濾器一般操縱響應。在下面的過濾器中,咱們添加一個隨機UUID
做爲X-Foo
頭。其餘操做,如轉換響應體,要複雜得多,計算密集。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public class AddResponseHeaderFilter extends ZuulFilter { @Override public String filterType() { return POST_TYPE; } @Override public int filterOrder() { return SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletResponse servletResponse = context.getResponse(); servletResponse.addHeader("X-Foo", UUID.randomUUID().toString()); return null; } }</code></span></span>
Zuul錯誤如何工做
若是在Zuul過濾器生命週期的任何部分拋出異常,則會執行錯誤過濾器。SendErrorFilter
只有RequestContext.getThrowable()
不是null
纔會運行。而後在請求中設置特定的javax.servlet.error.*
屬性,並將請求轉發到Spring Boot錯誤頁面。
Zuul渴望應用程序上下文加載
Zuul內部使用Ribbon調用遠程URL,而且Ribbon客戶端默認在第一次調用時由Spring Cloud加載。能夠使用如下配置更改Zuul的此行爲,並將致使在應用程序啓動時,子Ribbon相關的應用程序上下文正在加載。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">zuul: ribbon: eager-load: enabled: true</span></span>
你有沒有非jvm的語言你想利用Eureka,Ribbon和配置服務器?Netflix Prana啓發了Spring Cloud Netflix Sidecar 。它包含一個簡單的http api來獲取給定服務的全部實例(即主機和端口)。您還能夠經過從Eureka獲取其路由條目的嵌入式Zuul代理來代理服務調用。能夠經過主機查找或經過Zuul代理訪問Spring Cloud Config服務器。非jvm應用程序應該執行健康檢查,以便Sidecar能夠嚮應用程序啓動或關閉時向eureka報告。
要在項目中包含Sidecar,使用組org.springframework.cloud
和artifact id spring-cloud-netflix-sidecar
的依賴關係。
要啓用Sidecar,請使用@EnableSidecar
建立Spring Boot應用程序。此註釋包括@EnableCircuitBreaker
,@EnableDiscoveryClient
和@EnableZuulProxy
。在與非jvm應用程序相同的主機上運行生成的應用程序。
要配置側車將sidecar.port
和sidecar.health-uri
添加到application.yml
。sidecar.port
屬性是非jvm應用程序正在偵聽的端口。這樣,Sidecar能夠使用Eureka正確註冊該應用。sidecar.health-uri
是能夠在非jvm應用程序上訪問的,能夠模擬Spring Boot健康指標。它應該返回一個json文檔,以下所示:
健康-URI文檔
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "status":"UP" }</code></span></span>
如下是Sidecar應用程序的application.yml示例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">server: port: 5678 spring: application: name: sidecar sidecar: port: 8000 health-uri: http://localhost:8000/health.json</code></span></span>
DiscoveryClient.getInstances()
方法的api爲/hosts/{serviceId}
。如下是/hosts/customers
在不一樣主機上返回兩個實例的示例響應。這個api能夠訪問http://localhost:5678/hosts/{serviceId}
的非jvm應用程序(若是側面在端口5678上)。
/主機/客戶
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">[ { "host": "myhost", "port": 9000, "uri": "http://myhost:9000", "serviceId": "CUSTOMERS", "secure": false }, { "host": "myhost2", "port": 9000, "uri": "http://myhost2:9000", "serviceId": "CUSTOMERS", "secure": false } ]</code></span></span>
Zuul代理自動將eureka中已知的每一個服務的路由添加到/<serviceId>
,所以客戶服務可在/customers
得到。非jvm應用程序能夠經過http://localhost:5678/customers
訪問客戶服務(假設邊界正在偵聽端口5678)。
若是配置服務器已註冊到Eureka,則非jvm應用程序能夠經過Zuul代理訪問它。若是ConfigServer的serviceId爲configserver
且端口5678爲Sidecar,則能夠在 http:// localhost:5678 / configserver
非jvm應用程序能夠利用Config Server返回YAML文檔的功能。例如,調用http://sidecar.local.spring.io:5678/configserver/default-master.yml 可能會致使一個YAML文檔,以下所示
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ password: password info: description: Spring Cloud Samples url: https://github.com/spring-cloud-samples</code></span></span>
Spring Cloud Netflix包括RxJava。
RxJava是Reactive Extensions的Java VM實現:用於經過使用observable序列來構建異步和基於事件的程序的庫。
Spring Cloud Netflix支持從Spring MVC控制器返回rx.Single
對象。它還支持對服務器發送事件(SSE)使用rx.Observable
對象。若是您的內部API已經使用RxJava構建(參見Feign Hystrix支持示例),這可能很是方便。
如下是使用rx.Single
的一些示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RequestMapping(method = RequestMethod.GET, value = "/single") public Single<String> single() { return Single.just("single value"); } @RequestMapping(method = RequestMethod.GET, value = "/singleWithResponse") public ResponseEntity<Single<String>> singleWithResponse() { return new ResponseEntity<>(Single.just("single value"), HttpStatus.NOT_FOUND); } @RequestMapping(method = RequestMethod.GET, value = "/singleCreatedWithResponse") public Single<ResponseEntity<String>> singleOuterWithResponse() { return Single.just(new ResponseEntity<>("single value", HttpStatus.CREATED)); } @RequestMapping(method = RequestMethod.GET, value = "/throw") public Single<Object> error() { return Single.error(new RuntimeException("Unexpected")); }</code></span></span>
若是您有Observable
而不是單個,則能夠使用.toSingle()
或.toList().toSingle()
。這裏有些例子:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RequestMapping(method = RequestMethod.GET, value = "/single") public Single<String> single() { return Observable.just("single value").toSingle(); } @RequestMapping(method = RequestMethod.GET, value = "/multiple") public Single<List<String>> multiple() { return Observable.just("multiple", "values").toList().toSingle(); } @RequestMapping(method = RequestMethod.GET, value = "/responseWithObservable") public ResponseEntity<Single<String>> responseWithObservable() { Observable<String> observable = Observable.just("single value"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(APPLICATION_JSON_UTF8); return new ResponseEntity<>(observable.toSingle(), headers, HttpStatus.CREATED); } @RequestMapping(method = RequestMethod.GET, value = "/timeout") public Observable<String> timeout() { return Observable.timer(1, TimeUnit.MINUTES).map(new Func1<Long, String>() { @Override public String call(Long aLong) { return "single value"; } }); }</code></span></span>
若是您有流式端點和客戶端,SSE能夠是一個選項。要將rx.Observable
轉換爲Spring SseEmitter
使用RxResponse.sse()
。這裏有些例子:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RequestMapping(method = RequestMethod.GET, value = "/sse") public SseEmitter single() { return RxResponse.sse(Observable.just("single value")); } @RequestMapping(method = RequestMethod.GET, value = "/messages") public SseEmitter messages() { return RxResponse.sse(Observable.just("message 1", "message 2", "message 3")); } @RequestMapping(method = RequestMethod.GET, value = "/events") public SseEmitter event() { return RxResponse.sse(APPLICATION_JSON_UTF8, Observable.just(new EventDto("Spring io", getDate(2016, 5, 19)), new EventDto("SpringOnePlatform", getDate(2016, 8, 1)))); }</code></span></span>
當一塊兒使用時,Spectator / Servo和Atlas提供了近乎實時的操做洞察平臺。
Netflix的度量收集庫Spectator和Servo Atlas是用於管理維度時間序列數據的Netflix指標後端。
Servo爲Netflix服務了好幾年,仍然能夠使用,但逐漸被淘汰出局Spectator,這隻適用於Java 8. Spring Cloud Netflix提供了支持,但Java 8鼓勵基於應用的應用程序使用Spectator。
Spring Boot執行器指標是層次結構,指標只能由名稱分隔。這些名稱一般遵循將密鑰/值屬性對(維)嵌入到以句點分隔的名稱中的命名約定。考慮如下兩個端點(root和star-star)的指標:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "counter.status.200.root": 20, "counter.status.400.root": 3, "counter.status.200.star-star": 5, }</code></span></span>
第一個指標給出了每單位時間內針對根端點的成功請求的歸一化計數。可是若是系統有20個端點,而且想要得到針對全部端點的成功請求計數呢?一些分級度量後端將容許您指定一個通配符,例如counter.status.200.
,它將讀取全部20個指標並聚合結果。或者,您能夠提供HandlerInterceptorAdapter
攔截並記錄全部成功請求的counter.status.200.all
等指標,而不考慮端點,但如今您必須編寫20 + 1個不一樣的指標。一樣,若是您想知道服務中全部端點的成功請求總數,您能夠指定一個通配符,例如counter.status.2
.*
。
即便在分級度量後端的通配符支持的狀況下,命名一致性也是困難的。具體來講,這些標籤在名稱字符串中的位置可能會隨着時間而滑落,從而致使查詢錯 例如,假設咱們爲上述HTTP方法添加了一個額外的維度。那麼counter.status.200.root
成爲counter.status.200.method.get.root
等等。咱們的counter.status.200.*
忽然再也不具備相同的語義。此外,若是新的維度在整個代碼庫中不均勻地應用,某些查詢可能會變得不可能。這能夠很快失控。
Netflix指標被標記(又稱維度)。每一個指標都有一個名稱,可是這個單一的命名度量能夠包含多個統計信息和「標籤」鍵/值對,這容許更多的查詢靈活性。實際上統計自己就是記錄在一個特殊的標籤上。
使用Netflix Servo或Spectator記錄,上述根端點的計時器包含每一個狀態碼的4個統計信息,其中計數統計信息與Spring Boot執行器計數器相同。若是到目前爲止,咱們遇到了HTTP 200和400,將有8個可用數據點:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "root(status=200,stastic=count)": 20, "root(status=200,stastic=max)": 0.7265630630000001, "root(status=200,stastic=totalOfSquares)": 0.04759702862580789, "root(status=200,stastic=totalTime)": 0.2093076914666667, "root(status=400,stastic=count)": 1, "root(status=400,stastic=max)": 0, "root(status=400,stastic=totalOfSquares)": 0, "root(status=400,stastic=totalTime)": 0, }</code></span></span>
沒有任何附加依賴或配置,基於Spring Cloud的服務將自動配置Servo MonitorRegistry
,並開始收集每一個Spring MVC請求的指標。默認狀況下,將爲每一個MVC請求記錄名稱爲rest
的Servo定時器,其標記爲:
HTTP方法
HTTP狀態(例如200,400,500)
URI(若是URI爲空,則爲「root」),爲Atlas
異常類名稱,若是請求處理程序拋出異常
若是在請求上設置了匹配netflix.metrics.rest.callerHeader
的密鑰的請求頭,則呼叫者。netflix.metrics.rest.callerHeader
沒有默認鍵。若是您但願收集來電者信息,則必須將其添加到應用程序屬性中。
設置netflix.metrics.rest.metricName
屬性將度量值的名稱從rest
更改成您提供的名稱。
若是Spring AOP已啓用,而且org.aspectj:aspectjweaver
存在於您的運行時類路徑上,則Spring Cloud還將收集每一個使用RestTemplate
進行的客戶端調用的指標。將爲每一個具備如下標籤的MVC請求記錄名稱爲restclient
的Servo定時器:
HTTP方法
HTTP狀態(例如200,400,500),若是響應返回爲空,則爲「CLIENT_ERROR」;若是在執行RestTemplate
方法期間發生IOException
,則爲「IO_ERROR」
URI,爲Atlas
客戶名稱
警告 |
避免在RestTemplate 內使用硬編碼的url參數。定位動態端點時使用URL變量。這將避免ServoMonitorCache 將每一個網址視爲惟一密鑰的潛在「GC覆蓋限制達到」問題。 |
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// recommended String orderid = "1"; restTemplate.getForObject("http://testeurekabrixtonclient/orders/{orderid}", String.class, orderid) // avoid restTemplate.getForObject("http://testeurekabrixtonclient/orders/1", String.class)</code></span></span>
要啓用Spectator指標,請在spring-boot-starter-spectator
上包含依賴關係:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-spectator</artifactId> </dependency></code></span></span>
在Spectator說明中,儀表是一個命名,打字和標記的配置,而指標表示給定儀表在某個時間點的值。Spectator米由註冊表建立和控制,註冊表目前有幾個不一樣的實現。Spectator提供4米類型:計數器,定時器,量規和分配摘要。
Spring Cloud Spectator集成爲您配置可注入的com.netflix.spectator.api.Registry
實例。具體來講,它配置一個ServoRegistry
實例,以統一REST度量標準的集合,並將度量標準導出到Servo API下的Atlas後端。實際上,這意味着您的代碼可能會使用Servo顯示器和Spectator米的混合,而且都將由Spring Boot Actuator MetricReader
實例舀取,並將二者都發送到Atlas後端
Spectator櫃檯
計數器用於測量某些事件發生的速率。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// create a counter with a name and a set of tags Counter counter = registry.counter("counterName", "tagKey1", "tagValue1", ...); counter.increment(); // increment when an event occurs counter.increment(10); // increment by a discrete amount</code></span></span>
計數器記錄單個時間歸一化統計量。
Spectator計時器
一個計時器用於測量一些事件須要多長時間。Spring Cloud自動記錄Spring MVC請求和有條件RestTemplate
請求的定時器,稍後可用於爲請求相關指標建立儀表板,如延遲:
圖4.請求延遲
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// create a timer with a name and a set of tags Timer timer = registry.timer("timerName", "tagKey1", "tagValue1", ...); // execute an operation and time it at the same time T result = timer.record(() -> fooReturnsT()); // alternatively, if you must manually record the time Long start = System.nanoTime(); T result = fooReturnsT(); timer.record(System.nanoTime() - start, TimeUnit.NANOSECONDS);</code></span></span>
計時器同時記錄4個統計信息:count,max,totalOfSquares和totalTime。若是您在每次記錄時間時在計數器上調用了increment()
一次,計數統計量將始終與計數器提供的單個歸一化值相匹配,所以對於單個操做,不須要單獨計數和分時。
對於長時間運行的操做,Spectator提供了一個特殊的LongTaskTimer
。
Spectator量規
量規用於肯定一些當前值,如隊列的大小或處於運行狀態的線程數。因爲儀表被採樣,它們不提供關於這些值在樣品之間如何波動的信息。
儀器的正常使用包括在初始化中使用標識符註冊儀表,對要採樣的對象的引用,以及基於對象獲取或計算數值的功能。對對象的引用被單獨傳遞,Spectator註冊表將保留對該對象的弱引用。若是對象被垃圾回收,則Spectator將自動刪除註冊。見注 Spectator是關於潛在的內存泄漏的文件中,若是這個API被濫用。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// the registry will automatically sample this gauge periodically registry.gauge("gaugeName", pool, Pool::numberOfRunningThreads); // manually sample a value in code at periodic intervals -- last resort! registry.gauge("gaugeName", Arrays.asList("tagKey1", "tagValue1", ...), 1000);</code></span></span>
Spectator分發摘要
分發摘要用於跟蹤事件的分佈狀況。它相似於一個計時器,但更廣泛的是,大小不必定是一段時間。例如,分發摘要可用於測量服務器的請求的有效載荷大小。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// the registry will automatically sample this gauge periodically DistributionSummary ds = registry.distributionSummary("dsName", "tagKey1", "tagValue1", ...); ds.record(request.sizeInBytes());</code></span></span>
警告 |
若是您的代碼在Java 8上編譯,請使用Spectator而不是Servo,由於Spectator註定要從長期來替換Servo。 |
在Servo語言中,監視器是一個命名,鍵入和標記的配置,而指標表示給定監視器在某個時間點的值。Servo顯示器在邏輯上至關於Spectator米。Servo顯示器由MonitorRegistry
建立和控制。儘管有上述警告,Servo確實具備比Spectator有米的更普遍的監視器選項。
Spring Cloud集成爲您配置可注入的com.netflix.servo.MonitorRegistry
實例。在Servo中建立了相應的Monitor
類型後,記錄數據的過程徹底相似於Spectator。
建立Servo顯示器
若是您正在使用由Spring Cloud提供的Servo MonitorRegistry
實例(具體來講是DefaultMonitorRegistry
的實例),則Servo提供了用於檢索計數器和計時器的便利類。這些便利類確保每一個惟一的名稱和標籤組合只註冊一個Monitor
。
要在Servo中手動建立監視器類型,特別是對於不提供方便方法的異域監視器類型,經過提供MonitorConfig
實例來實例化適當的類型:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">MonitorConfig config = MonitorConfig.builder("timerName").withTag("tagKey1", "tagValue1").build(); // somewhere we should cache this Monitor by MonitorConfig Timer timer = new BasicTimer(config); monitorRegistry.register(timer);</code></span></span>
Netflix開發了Atlas來管理維度時間序列數據,實現近實時操做洞察。Atlas具備內存中數據存儲功能,能夠很是快速地收集和報告大量的指標。
Atlas捕獲操做情報。而商業智能是收集的數據,用於分析一段時間內的趨勢,操做情報提供了系統中目前發生的狀況。
Spring Cloud提供了一個spring-cloud-starter-atlas
,它具備您須要的全部依賴關係。而後只需使用@EnableAtlas
註釋您的Spring Boot應用程序,併爲您運行的Atlas服務器提供netflix.atlas.uri
屬性的位置。
全球標籤
您能夠經過Spring Cloud向發送到Atlas後端的每一個度量標準添加標籤。全局標籤可用於按應用程序名稱,環境,區域等分隔度量。
實現AtlasTagProvider
的每一個bean將貢獻全局標籤列表:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean AtlasTagProvider atlasCommonTags( @Value("${spring.application.name}") String appName) { return () -> Collections.singletonMap("app", appName); }</code></span></span>
使用Atlas
要引導內存獨立的Atlas實例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">$ curl -LO https://github.com/Netflix/atlas/releases/download/v1.4.2/atlas-1.4.2-standalone.jar $ java -jar atlas-1.4.2-standalone.jar</code></span></span>
提示 |
運行在r3.2xlarge(61GB RAM)上的Atlas獨立節點能夠在給定的6小時窗口內每分鐘處理大約200萬個度量值。 |
一旦運行,您收集了少許指標,請經過在Atlas服務器上列出代碼來驗證您的設置是否正確:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">$ curl http://ATLAS/api/v1/tags</code></span></span>
提示 |
在針對您的服務執行多個請求後,您能夠經過在瀏覽器中粘貼如下URL來收集關於每一個請求的請求延遲的一些很是基本的信息:http://ATLAS/api/v1/graph?q=name,rest,:eq,:avg |
Atlas wiki包含各類場景樣本查詢的彙編。
確保使用雙指數平滑來查看警報原理和文檔,以生成動態警報閾值。
Spring Cloud Netflix提供了多種方式來進行HTTP請求。您能夠使用負載平衡RestTemplate
,Ribbon或Feign。不管您如何選擇HTTP請求,始終有可能失敗的請求。當請求失敗時,您可能但願自動重試該請求。要在使用Sping Cloud Netflix時完成此操做,您須要在應用程序的類路徑中包含 Spring重試。當Spring重試出現負載平衡RestTemplates
時,Feign和Zuul將自動重試任何失敗的請求(假設配置容許)。
組態
隨時Ribbon與Spring重試一塊兒使用,您能夠經過配置某些Ribbon屬性來控制重試功能。您能夠使用的屬性是client.ribbon.MaxAutoRetries
,client.ribbon.MaxAutoRetriesNextServer
和client.ribbon.OkToRetryOnAllOperations
。請參閱Ribbon文檔 ,瞭解屬性的具體內容。
此外,您可能但願在響應中返回某些狀態代碼時重試請求。您能夠列出您但願Ribbon客戶端使用屬性clientName.ribbon.retryableStatusCodes
重試的響應代碼。例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">clientName: ribbon: retryableStatusCodes: 404,502</code></span></span>
您還能夠建立一個類型爲LoadBalancedRetryPolicy
的bean,並實現retryableStatusCode
方法來肯定是否要重試發出狀態代碼的請求。
Zuul
您能夠經過將zuul.retryable
設置爲false
來關閉Zuul的重試功能。您還能夠經過將zuul.routes.routename.retryable
設置爲false
,以路由方式禁用重試功能。
本節將詳細介紹如何使用Spring Cloud Stream。它涵蓋了建立和運行流應用程序等主題。
Spring Cloud Stream是構建消息驅動的微服務應用程序的框架。Spring Cloud Stream基於Spring Boot創建獨立的生產級Spring應用程序,並使用Spring Integration提供與消息代理的鏈接。它提供了來自幾家供應商的中間件的意見配置,介紹了持久發佈訂閱語義,消費者組和分區的概念。
您能夠將@EnableBinding
註釋添加到應用程序,以便當即鏈接到消息代理,而且能夠將@StreamListener
添加到方法中,以使其接收流處理的事件。如下是接收外部消息的簡單接收器應用程序。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableBinding(Sink.class) public class VoteRecordingSinkApplication { public static void main(String[] args) { SpringApplication.run(VoteRecordingSinkApplication.class, args); } @StreamListener(Sink.INPUT) public void processVote(Vote vote) { votingService.recordVote(vote); } }</code></span></span>
@EnableBinding
註釋須要一個或多個接口做爲參數(在這種狀況下,該參數是單個Sink
接口)。接口聲明輸入和/或輸出通道。Spring Cloud Stream提供了接口Source
,Sink
和Processor
; 您還能夠定義本身的界面。
如下是Sink
接口的定義:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Sink { String INPUT = "input"; @Input(Sink.INPUT) SubscribableChannel input(); }</code></span></span>
@Input
註釋標識輸入通道,經過該輸入通道接收到的消息進入應用程序; @Output
註釋標識輸出通道,發佈的消息將經過該通道離開應用程序。@Input
和@Output
註釋能夠使用頻道名稱做爲參數; 若是未提供名稱,將使用註釋方法的名稱。
Spring Cloud Stream將爲您建立一個界面的實現。您能夠在應用程序中經過自動鏈接來使用它,以下面的測試用例示例。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = VoteRecordingSinkApplication.class) @WebAppConfiguration @DirtiesContext public class StreamApplicationTests { @Autowired private Sink sink; @Test public void contextLoads() { assertNotNull(this.sink.input()); } }</code></span></span>
Spring Cloud Stream提供了一些簡化了消息驅動的微服務應用程序編寫的抽象和原語。本節概述瞭如下內容:
Spring Cloud Stream的應用模型
Binder抽象
持續的發佈 - 訂閱支持
消費者羣體支持
分區支持
一個可插拔的Binder API
一個Spring Cloud Stream應用程序由一箇中間件中立的核心組成。該應用程序經過Spring Cloud Stream注入到其中的輸入和輸出通道與外界進行通訊。渠道經過中間件特定的Binder實現鏈接到外部經紀人。
圖5. Spring Cloud Stream應用
胖JAR
Spring Cloud Stream應用程序能夠在獨立模式下從IDE運行進行測試。要在生產中運行Spring Cloud Stream應用程序,您能夠使用爲Maven或Gradle提供的標準Spring Boot工具建立可執行文件(或「胖」)JAR。
Spring Cloud Stream爲Kafka和Rabbit MQ提供Binder實現。Spring Cloud Stream還包括一個TestSupportBinder,它保留了一個未修改的通道,以便測試能夠直接和可靠地與通道進行交互。您能夠使用可擴展API編寫本身的Binder。
Spring Cloud Stream使用Spring Boot進行配置,Binder抽象使得Spring Cloud Stream應用程序能夠靈活地鏈接到中間件。例如,部署者能夠在運行時動態地選擇通道鏈接的目的地(例如,Kafka主題或RabbitMQ交換)。能夠經過外部配置屬性和Spring Boot(包括應用程序參數,環境變量和application.yml
或application.properties
文件)支持的任何形式提供此類配置。在引入Spring Cloud Stream部分的接收器示例中,將應用程序屬性spring.cloud.stream.bindings.input.destination
設置爲raw-sensor-data
將使其從raw-sensor-data
Kafka主題或從綁定到raw-sensor-data
RabbitMQ交換。
Spring Cloud Stream自動檢測並使用類路徑中找到的binder。您能夠使用相同的代碼輕鬆使用不一樣類型的中間件:在構建時只包含不一樣的綁定器。對於更復雜的用例,您還能夠在應用程序中打包多個綁定器,並在運行時選擇綁定器,甚至是否爲不一樣的通道使用不一樣的綁定器。
應用之間的通訊遵循發佈訂閱模式,其中經過共享主題廣播數據。這能夠在下圖中看到,它顯示了一組交互式的Spring Cloud Stream應用程序的典型部署。
圖6. Spring Cloud Stream Publish-Subscribe
傳感器向HTTP端點報告的數據將發送到名爲raw-sensor-data
的公共目標。從目的地,它由微服務應用程序獨立處理,該應用程序計算時間窗口平均值,以及另外一個將原始數據導入HDFS的微服務應用程序。爲了處理數據,兩個應用程序在運行時將主題聲明爲它們的輸入。
發佈訂閱通訊模型下降了生產者和消費者的複雜性,並容許將新應用程序添加到拓撲中,而不會中斷現有流。例如,在平均計算應用程序的下游,您能夠添加一個計算顯示和監視的最高溫度值的應用程序。而後,您能夠添加另外一個解釋相同的故障檢測平均流程的應用程序。經過共享主題而不是點對點隊列進行全部通訊能夠減小微服務之間的耦合。
雖然發佈訂閱消息的概念不是新的,可是Spring Cloud Stream須要額外的步驟才能使其成爲其應用模型的一個有意義的選擇。經過使用本地中間件支持,Spring Cloud Stream還簡化了在不一樣平臺上使用發佈訂閱模型。
雖然發佈訂閱模型能夠輕鬆地經過共享主題鏈接應用程序,但經過建立給定應用程序的多個實例來擴展的能力一樣重要。當這樣作時,應用程序的不一樣實例被放置在競爭的消費者關係中,其中只有一個實例預期處理給定消息。
Spring Cloud Stream經過消費者組的概念來模擬此行爲。(Spring Cloud Stream消費者組與Kafka消費者組類似並受到啓發。)每一個消費者綁定能夠使用spring.cloud.stream.bindings.<channelName>.group
屬性來指定組名稱。對於下圖所示的消費者,此屬性將設置爲spring.cloud.stream.bindings.<channelName>.group=hdfsWrite
或spring.cloud.stream.bindings.<channelName>.group=average
。
圖7. Spring Cloud Stream消費者組
訂閱給定目標的全部組都會收到已發佈數據的副本,但每一個組中只有一個成員從該目的地接收給定的消息。默認狀況下,當未指定組時,Spring Cloud Stream將應用程序分配給與全部其餘消費者組發佈 - 訂閱關係的匿名獨立單個成員消費者組。
耐久力
符合Spring Cloud Stream的有意義的應用模式,消費者羣體訂閱是持久的。也就是說,綁定實現確保組預訂是持久的,一旦已經建立了一個組的至少一個訂閱,即便組中的全部應用程序都被中止,組也將接收消息。
注意 |
匿名訂閱本質上是不耐用的。對於某些binder實現(例如RabbitMQ),能夠具備非持久組的訂閱。 |
一般,當將應用綁定到給定目的地時,最好始終指定消費者組。在擴展Spring Cloud Stream應用程序時,必須爲每一個輸入綁定指定一個使用者組。這樣能夠防止應用程序的實例收到重複的消息(除非須要這種行爲,這是不尋常的)。
Spring Cloud Stream提供對給定應用程序的多個實例之間的分區數據的支持。在分區場景中,物理通訊介質(例如,代理主題)被視爲被構形成多個分區。一個或多個生產者應用程序實例將數據發送到多個消費者應用程序實例,並確保由共同特徵標識的數據由相同的消費者實例處理。
Spring Cloud Stream提供了統一方式實現分區處理用例的通用抽象。所以,不管代理自己是否天然分區(例如Kafka)(例如RabbitMQ),分區能夠被使用。
圖8. Spring Cloud Stream分區
分區是狀態處理中的一個關鍵概念,不管是性能仍是一致性緣由,它都是批評性的,以確保全部相關數據一塊兒處理。例如,在時間平均計算示例中,重要的是全部給定傳感器的全部測量都由相同的應用實例進行處理。
注意 |
要設置分區處理方案,您必須同時配置數據生成和數據消耗結束。 |
本節介紹Spring Cloud Stream的編程模型。Spring Cloud Stream提供了許多預約義的註釋,用於聲明綁定的輸入和輸出通道,以及如何收聽頻道。
觸發綁定@EnableBinding
您能夠將Spring應用程序轉換爲Spring Cloud Stream應用程序,將@EnableBinding
註釋應用於應用程序的配置類之一。@EnableBinding
註釋自己使用@Configuration
進行元註釋,並觸發Spring Cloud Stream基礎架構的配置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">... @Import(...) @Configuration @EnableIntegration public @interface EnableBinding { ... Class<?>[] value() default {}; }</code></span></span>
@EnableBinding
註釋能夠將一個或多個接口類做爲參數,這些接口類包含表示可綁定組件(一般是消息通道)的方法。
注意 |
在Spring Cloud Stream 1.0中,惟一支持的可綁定組件是Spring消息傳遞 |
@Input
和@Output
Spring Cloud Stream應用程序能夠在接口中定義任意數量的輸入和輸出通道爲@Input
和@Output
方法:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Barista { @Input SubscribableChannel orders(); @Output MessageChannel hotDrinks(); @Output MessageChannel coldDrinks(); }</code></span></span>
使用此接口做爲參數@EnableBinding
將分別觸發三個綁定的通道名稱爲orders
,hotDrinks
和coldDrinks
。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Barista.class) public class CafeConfiguration { ... }</code></span></span>
自定義頻道名稱
使用@Input
和@Output
註釋,您能夠指定頻道的自定義頻道名稱,如如下示例所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Barista { ... @Input("inboundOrders") SubscribableChannel orders(); }</code></span></span>
在這個例子中,建立的綁定通道將被命名爲inboundOrders
。
Source
,Sink
和Processor
爲了方便尋址最多見的用例,涉及輸入通道,輸出通道或二者,Spring Cloud Stream提供了開箱即用的三個預約義接口。
Source
可用於具備單個出站通道的應用程序。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Source { String OUTPUT = "output"; @Output(Source.OUTPUT) MessageChannel output(); }</code></span></span>
Sink
可用於具備單個入站通道的應用程序。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Sink { String INPUT = "input"; @Input(Sink.INPUT) SubscribableChannel input(); }</code></span></span>
Processor
可用於具備入站通道和出站通道的應用程序。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Processor extends Source, Sink { }</code></span></span>
Spring Cloud Stream不爲任何這些接口提供特殊處理; 它們只是開箱即用。
訪問綁定通道
注入綁定界面
對於每一個綁定接口,Spring Cloud Stream將生成一個實現該接口的bean。調用其中一個bean的@Input
註釋或@Output
註釋方法將返回相關的綁定通道。
如下示例中的bean在調用其hello
方法時在輸出通道上發送消息。它在注入的Source
bean上調用output()
來檢索目標通道。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Component public class SendingBean { private Source source; @Autowired public SendingBean(Source source) { this.source = source; } public void sayHello(String name) { source.output().send(MessageBuilder.withPayload(name).build()); } }</code></span></span>
直接注入渠道
綁定通道也能夠直接注入:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Component public class SendingBean { private MessageChannel output; @Autowired public SendingBean(MessageChannel output) { this.output = output; } public void sayHello(String name) { output.send(MessageBuilder.withPayload(name).build()); } }</code></span></span>
若是在聲明註釋上定製了通道的名稱,則應使用該名稱而不是方法名稱。給出如下聲明:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface CustomSource { ... @Output("customOutput") MessageChannel output(); }</code></span></span>
通道將被注入,以下例所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Component public class SendingBean { private MessageChannel output; @Autowired public SendingBean(@Qualifier("customOutput") MessageChannel output) { this.output = output; } public void sayHello(String name) { this.output.send(MessageBuilder.withPayload(name).build()); } }</code></span></span>
生產和消費消息
您能夠使用Spring Integration註解或Spring Cloud Stream的@StreamListener
註釋編寫Spring Cloud Stream應用程序。@StreamListener
註釋在其餘Spring消息傳遞註釋(例如@MessageMapping
,@JmsListener
,@RabbitListener
等)以後建模,但添加內容類型管理和類型強制功能。
本地Spring Integration支持
因爲Spring Cloud Stream基於Spring Integration,Stream徹底繼承了Integration的基礎和基礎架構以及組件自己。例如,您能夠將Source
的輸出通道附加到MessageSource
:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Source.class) public class TimerSource { @Value("${format}") private String format; @Bean @InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedDelay = "${fixedDelay}", maxMessagesPerPoll = "1")) public MessageSource<String> timerMessageSource() { return () -> new GenericMessage<>(new SimpleDateFormat(format).format(new Date())); } }</code></span></span>
或者您能夠在變壓器中使用處理器的通道:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Processor.class) public class TransformProcessor { @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) public Object transform(String message) { return message.toUpperCase(); } }</code></span></span>
Spring Integration錯誤通道支持
Spring Cloud Stream支持發佈Spring Integration全局錯誤通道收到的錯誤消息。發送到errorChannel
的錯誤消息能夠經過爲名爲error
的出站目標配置綁定,將其發佈到代理的特定目標。例如,要將錯誤消息發佈到名爲「myErrors」的代理目標,請提供如下屬性:spring.cloud.stream.bindings.error.destination=myErrors
使用@StreamListener進行自動內容類型處理
Spring Integration支持Spring Cloud Stream提供本身的@StreamListener
註釋,以其餘Spring消息傳遞註釋(例如@MessageMapping
,@JmsListener
,@RabbitListener
等) )。@StreamListener
註釋提供了一種更簡單的處理入站郵件的模型,特別是在處理涉及內容類型管理和類型強制的用例時。
Spring Cloud Stream提供了一種可擴展的MessageConverter
機制,用於經過綁定通道處理數據轉換,而且在這種狀況下,將調度到使用@StreamListener
註釋的方法。如下是處理外部Vote
事件的應用程序的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Sink.class) public class VoteHandler { @Autowired VotingService votingService; @StreamListener(Sink.INPUT) public void handle(Vote vote) { votingService.record(vote); } }</code></span></span>
當考慮String
有效載荷和contentType
標題application/json
的入站Message
時,能夠看到@StreamListener
和Spring Integration @ServiceActivator
之間的區別。在@StreamListener
的狀況下,MessageConverter
機制將使用contentType
標頭將String
有效載荷解析爲Vote
對象。
與其餘Spring消息傳遞方法同樣,方法參數能夠用@Payload
,@Headers
和@Header
註釋。
注意 |
對於返回數據的方法,您必須使用 <span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.6)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Processor.class) public class TransformProcessor { @Autowired VotingService votingService; @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public VoteResult handle(Vote vote) { return votingService.record(vote); } }</code></span></span></span> |
使用@StreamListener將消息分派到多個方法
自1.2版本以來,Spring Cloud Stream支持根據條件向在輸入通道上註冊的多個@StreamListener
方法發送消息。
爲了有資格支持有條件的調度,一種方法必須知足如下條件:
它不能返回值
它必須是一個單獨的消息處理方法(不支持的反應API方法)
條件經過註釋的condition
屬性中的SpEL表達式指定,併爲每一個消息進行評估。匹配條件的全部處理程序將在同一個線程中被調用,而且沒必要對調用發生的順序作出假設。
使用@StreamListener
具備調度條件的示例能夠在下面看到。在此示例中,帶有值爲foo
的標題type
的全部消息將被分派到receiveFoo
方法,全部帶有值爲bar
的標題type
的消息將被分派到receiveBar
方法。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Sink.class) @EnableAutoConfiguration public static class TestPojoWithAnnotatedArguments { @StreamListener(target = Sink.INPUT, condition = "headers['type']=='foo'") public void receiveFoo(@Payload FooPojo fooPojo) { // handle the message } @StreamListener(target = Sink.INPUT, condition = "headers['type']=='bar'") public void receiveBar(@Payload BarPojo barPojo) { // handle the message } }</code></span></span>
注意 |
僅經過 |
反應式編程支持
Spring Cloud Stream還支持使用反應性API,其中將傳入和傳出數據做爲連續數據流處理。經過spring-cloud-stream-reactive
提供對反應性API的支持,須要將其明確添加到您的項目中。
具備反應性API的編程模型是聲明式的,而不是指定如何處理每一個單獨的消息,您能夠使用描述從入站到出站數據流的功能轉換的運算符。
Spring Cloud Stream支持如下反應性API:
反應堆
RxJava 1.x
未來,它旨在支持基於活動流的更通用的模型。
反應式編程模型還使用@StreamListener
註釋來設置反應處理程序。差別在於:
@StreamListener
註釋不能指定輸入或輸出,由於它們做爲參數提供,並從方法返回值;
必須使用@Input
和@Output
註釋方法的參數,指示輸入和分別輸出的數據流鏈接到哪一個輸入或輸出;
方法的返回值(若是有的話)將用@Output
註釋,表示要發送數據的輸入。
注意 |
反應式編程支持須要Java 1.8。 |
注意 |
截至Spring Cloud Stream 1.1.1及更高版本(從布魯克林發行版開始列出),反應式編程支持須要使用Reactor 3.0.4.RELEASE和更高版本。不支持早期的Reactor版本(包括3.0.1.RELEASE,3.0.2.RELEASE和3.0.3.RELEASE)。 |
注意 |
術語 |
基於反應器的處理程序
基於反應器的處理程序能夠具備如下參數類型:
對於用@Input
註釋的參數,它支持反應器類型Flux
。入站通量的參數化遵循與單個消息處理相同的規則:它能夠是整個Message
,一個能夠是Message
有效負載的POJO,也能夠是一個POJO基於Message
內容類型頭的轉換。提供多個輸入;
對於使用Output
註釋的參數,它支持將方法生成的Flux
與輸出鏈接的類型FluxSender
。通常來講,僅當方法能夠有多個輸出時才建議指定輸出做爲參數;
基於反應器的處理程序支持Flux
的返回類型,其中必須使用@Output
註釋。當單個輸出通量可用時,咱們建議使用該方法的返回值。
這是一個簡單的基於反應器的處理器的例子。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Processor.class) @EnableAutoConfiguration public static class UppercaseTransformer { @StreamListener @Output(Processor.OUTPUT) public Flux<String> receive(@Input(Processor.INPUT) Flux<String> input) { return input.map(s -> s.toUpperCase()); } }</code></span></span>
使用輸出參數的同一個處理器以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Processor.class) @EnableAutoConfiguration public static class UppercaseTransformer { @StreamListener public void receive(@Input(Processor.INPUT) Flux<String> input, @Output(Processor.OUTPUT) FluxSender output) { output.send(input.map(s -> s.toUpperCase())); } }</code></span></span>
RxJava 1.x支持
RxJava 1.x處理程序遵循與基於反應器的規則相同的規則,但將使用Observable
和ObservableSender
參數和返回類型。
因此上面的第一個例子會變成:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Processor.class) @EnableAutoConfiguration public static class UppercaseTransformer { @StreamListener @Output(Processor.OUTPUT) public Observable<String> receive(@Input(Processor.INPUT) Observable<String> input) { return input.map(s -> s.toUpperCase()); } }</code></span></span>
上面的第二個例子將會變成:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Processor.class) @EnableAutoConfiguration public static class UppercaseTransformer { @StreamListener public void receive(@Input(Processor.INPUT) Observable<String> input, @Output(Processor.OUTPUT) ObservableSender output) { output.send(input.map(s -> s.toUpperCase())); } }</code></span></span>
聚合
Spring Cloud Stream支持將多個應用程序聚合在一塊兒,直接鏈接其輸入和輸出通道,並避免經過代理交換消息的額外成本。從版本1.0的Spring Cloud Stream開始,僅對如下類型的應用程序支持聚合:
來源 - 具備名爲output
的單個輸出通道的應用程序,一般具備類型爲org.springframework.cloud.stream.messaging.Source
的單個綁定
接收器 - 具備名爲input
的單個輸入通道的應用程序,一般具備類型爲org.springframework.cloud.stream.messaging.Sink
的單個綁定
處理器 - 具備名爲input
的單個輸入通道和名爲output
的單個輸出通道的應用程序,一般具備類型爲org.springframework.cloud.stream.messaging.Processor
的單個綁定。
它們能夠經過建立一系列互連的應用程序來聚合在一塊兒,其中序列中的元素的輸出通道鏈接到下一個元素的輸入通道(若是存在)。序列能夠從源或處理器開始,它能夠包含任意數量的處理器,而且必須以處理器或接收器結束。
根據起始和結束元素的性質,序列能夠具備一個或多個可綁定的信道,以下所示:
若是序列從源頭開始並以sink結束,則應用程序之間的全部通訊都是直接的,而且不會綁定任何通道
若是序列以處理器開始,則其輸入通道將成爲聚合的input
通道,並將相應地進行綁定
若是序列以處理器結束,則其輸出通道將成爲聚合的output
通道,並將相應地進行綁定
使用AggregateApplicationBuilder
實用程序類執行聚合,如如下示例所示。咱們考慮一個項目,咱們有源,處理器和匯點,能夠在項目中定義,或者能夠包含在項目的依賴之一中。
注意 |
若是配置類使用 |
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.app.mysink; @SpringBootApplication @EnableBinding(Sink.class) public class SinkApplication { private static Logger logger = LoggerFactory.getLogger(SinkApplication.class); @ServiceActivator(inputChannel=Sink.INPUT) public void loggerSink(Object payload) { logger.info("Received: " + payload); } }</code></span></span>
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.app.myprocessor; @SpringBootApplication @EnableBinding(Processor.class) public class ProcessorApplication { @Transformer public String loggerSink(String payload) { return payload.toUpperCase(); } }</code></span></span>
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.app.mysource; @SpringBootApplication @EnableBinding(Source.class) public class SourceApplication { @Bean @InboundChannelAdapter(value = Source.OUTPUT) public String timerMessageSource() { return new SimpleDateFormat().format(new Date()); } }</code></span></span>
每一個配置能夠用於運行一個單獨的組件,但在這種狀況下,它們能夠聚合在一塊兒,以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.app; @SpringBootApplication public class SampleAggregateApplication { public static void main(String[] args) { new AggregateApplicationBuilder() .from(SourceApplication.class).args("--fixedDelay=5000") .via(ProcessorApplication.class) .to(SinkApplication.class).args("--debug=true").run(args); } }</code></span></span>
該序列的起始組件做爲from()
方法的參數提供。序列的結尾部分做爲to()
方法的參數提供。中間處理器做爲via()
方法的參數提供。同一類型的多個處理器能夠一塊兒連接(例如,用於具備不一樣配置的流水線轉換)。對於每一個組件,構建器能夠爲Spring Boot配置提供運行時參數。
配置聚合應用程序
Spring Cloud Stream支持使用'namespace'做爲前綴向聚合應用程序內的各個應用程序傳遞屬性。
命名空間能夠爲應用程序設置以下:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication public class SampleAggregateApplication { public static void main(String[] args) { new AggregateApplicationBuilder() .from(SourceApplication.class).namespace("source").args("--fixedDelay=5000") .via(ProcessorApplication.class).namespace("processor1") .to(SinkApplication.class).namespace("sink").args("--debug=true").run(args); } }</code></span></span>
一旦爲單個應用程序設置了「命名空間」,則能夠使用任何支持的屬性源(命令行,環境屬性等)將具備namespace
做爲前綴的應用程序屬性傳遞到聚合應用程序,
例如,要覆蓋「source」和「sink」應用程序的默認fixedDelay
和debug
屬性:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>java -jar target/MyAggregateApplication-0.0.1-SNAPSHOT.jar --source.fixedDelay=10000 --sink.debug=false</code></span></span>
爲非自包含聚合應用程序配置綁定服務屬性
非自包含聚合應用程序經過聚合應用程序的入站/出站組件(一般爲消息通道)中的一個或二者綁定到外部代理,而聚合應用程序內的應用程序是直接綁定的。例如:源應用程序的輸出和處理器應用程序的輸入是直接綁定的,而處理器的輸出通道綁定到代理的外部目的地。當傳遞非自包含聚合應用程序的綁定服務屬性時,須要將綁定服務屬性傳遞給聚合應用程序,而不是將它們設置爲單個子應用程序的「args」。例如,
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication public class SampleAggregateApplication { public static void main(String[] args) { new AggregateApplicationBuilder() .from(SourceApplication.class).namespace("source").args("--fixedDelay=5000") .via(ProcessorApplication.class).namespace("processor1").args("--debug=true").run(args); } }</code></span></span>
須要將綁定屬性--spring.cloud.stream.bindings.output.destination=processor-output
指定爲外部配置屬性(cmdline arg等)之一。
Spring Cloud Stream提供了一個Binder抽象,用於鏈接到外部中間件的物理目標。本節提供有關Binder SPI,其主要組件和實現特定詳細信息背後的主要概念的信息。
圖9.生產者和消費者
甲生產者是將消息發送到信道的任何組分。該通道能夠經過該代理的Binder實現綁定到外部消息代理。當調用bindProducer()
方法時,第一個參數是代理中目標的名稱,第二個參數是生成器將發送消息的本地通道實例,第三個參數包含屬性(如分區鍵表達式)在爲該通道建立的適配器中使用。
甲消費者的是,從一個信道接收的消息的任何組分。與生產者同樣,消費者的頻道能夠綁定到外部消息代理。當調用bindConsumer()
方法時,第一個參數是目標名稱,第二個參數提供消費者邏輯組的名稱。由給定目的地的消費者綁定表示的每一個組接收生產者發送到該目的地的每一個消息的副本(即,發佈 - 訂閱語義)。若是有多個使用相同組名稱的消費者實例綁定,那麼消息將在這些消費者實例之間進行負載平衡,以便生產者發送的每一個消息僅由每一個組中的單個消費者實例消耗(即排隊語義)。
Binder SPI包括許多接口,即插即用實用程序類和發現策略,提供可插拔機制鏈接到外部中間件。
SPI的關鍵點是Binder
接口,它是將輸入和輸出鏈接到外部中間件的策略。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> { Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties); Binding<T> bindProducer(String name, T outboundBindTarget, P producerProperties); }</code></span></span>
界面參數化,提供多個擴展點:
輸入和輸出綁定目標 - 從版本1.0開始,只支持MessageChannel
,可是這個目標是未來用做擴展點;
擴展的消費者和生產者屬性 - 容許特定的Binder實現來添加能夠以類型安全的方式支持的補充屬性。
典型的綁定實現包括如下內容
一個實現Binder
接口的類;
一個Spring @Configuration
類,與中間件鏈接基礎架構一塊兒建立上述類型的bean;
在類路徑中找到的包含一個或多個綁定器定義的META-INF/spring.binders
文件,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>kafka:\ org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration</code></span></span>
Spring Cloud Stream依賴於Binder SPI的實現來執行將通道鏈接到消息代理的任務。每一個Binder實現一般鏈接到一種類型的消息系統。
類路徑檢測
默認狀況下,Spring Cloud Stream依賴於Spring Boot的自動配置來配置綁定過程。若是在類路徑中找到單個Binder實現,則Spring Cloud Stream將自動使用。例如,一個旨在綁定到RabbitMQ的Spring Cloud Stream項目能夠簡單地添加如下依賴項:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency></code></span></span>
對於其餘綁定依賴關係的特定maven座標,請參閱該binder實現的文檔。
當類路徑中存在多個綁定器時,應用程序必須指明每一個通道綁定將使用哪一個綁定器。每一個binder配置都包含一個META-INF/spring.binders
,它是一個簡單的屬性文件:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>rabbit:\ org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration</code></span></span>
對於其餘提供的綁定實現(例如Kafka),存在相似的文件,而且預期自定義綁定實現也能夠提供它們。鍵表明binder實現的標識名稱,而該值是以逗號分隔的配置類列表,每一個配置類都包含惟一的一個類型爲org.springframework.cloud.stream.binder.Binder
的bean定義。
能夠使用spring.cloud.stream.defaultBinder
屬性(例如spring.cloud.stream.defaultBinder=rabbit
)全局執行Binder選擇,或經過在每一個通道綁定上配置binder來單獨執行。例如,從Kafka讀取並寫入RabbitMQ的處理器應用程序(分別具備用於讀/寫的名稱爲input
和output
的通道)能夠指定如下配置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.cloud.stream.bindings.input.binder=kafka spring.cloud.stream.bindings.output.binder=rabbit</span></span>
默認狀況下,綁定器共享應用程序的Spring Boot自動配置,以便在類路徑中找到每一個綁定器的一個實例。若是您的應用程序鏈接到同一類型的多個代理,則能夠指定多個綁定器配置,每一個具備不一樣的環境設置。
注意 |
打開顯式綁定器配置將徹底禁用默認綁定器配置過程。若是這樣作,全部使用的綁定器都必須包含在配置中。打算透明使用Spring Cloud Stream的框架可能會建立能夠經過名稱引用的binder配置,但不會影響默認的綁定器配置。爲此,綁定器配置可能將其 |
例如,這是鏈接到兩個RabbitMQ代理實例的處理器應用程序的典型配置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yml">spring: cloud: stream: bindings: input: destination: foo binder: rabbit1 output: destination: bar binder: rabbit2 binders: rabbit1: type: rabbit environment: spring: rabbitmq: host: <host1> rabbit2: type: rabbit environment: spring: rabbitmq: host: <host2></code></span></span>
建立自定義綁定器配置時,如下屬性可用。它們必須以spring.cloud.stream.binders.<configurationName>
爲前綴。
類型
粘合劑類型。它一般引用在類路徑中找到的綁定器之一,特別是META-INF/spring.binders
文件中的鍵。
默認狀況下,它具備與配置名稱相同的值。
inheritEnvironment
配置是否會繼承應用程序自己的環境。
默認true
。
環境
一組可用於自定義綁定環境的屬性的根。配置此配置後,建立綁定器的上下文不是應用程序上下文的子級。這容許粘合劑組分和應用組分之間的徹底分離。
默認empty
。
defaultCandidate
粘合劑配置是否被認爲是默認的粘合劑的候選者,或者僅在明確引用時才能使用。這容許添加binder配置,而不會干擾默認處理。
默認true
。
Spring Cloud Stream支持常規配置選項以及綁定和綁定器的配置。一些綁定器容許額外的綁定屬性來支持中間件特定的功能。
能夠經過Spring Boot支持的任何機制將配置選項提供給Spring Cloud Stream應用程序。這包括應用程序參數,環境變量和YAML或.properties文件。
spring.cloud.stream.instanceCount
應用程序部署實例的數量。必須設置分區,若是使用Kafka。
默認值:1
。
spring.cloud.stream.instanceIndex
應用程序的實例索引:從0
到instanceCount
- 1的數字。用於分區和使用Kafka。在Cloud Foundry中自動設置以匹配應用程序的實例索引。
spring.cloud.stream.dynamicDestinations
能夠動態綁定的目標列表(例如,在動態路由方案中)。若是設置,只能列出目的地。
默認值:空(容許任何目的地綁定)。
spring.cloud.stream.defaultBinder
若是配置了多個綁定器,則使用默認的binder。請參閱Classpath上的Multiple Binders。
默認值:空。
spring.cloud.stream.overrideCloudConnectors
此屬性僅適用於cloud
配置文件激活且Spring Cloud鏈接器隨應用程序一塊兒提供。若是屬性爲false(默認值),綁定器將檢測適合的綁定服務(例如,在Cloud Foundry中爲RabbitMQ綁定器綁定的RabbitMQ服務),並將使用它來建立鏈接(一般經過Spring Cloud鏈接器)。當設置爲true時,此屬性指示綁定器徹底忽略綁定的服務,並依賴Spring Boot屬性(例如,依賴於RabbitMQ綁定器環境中提供的spring.rabbitmq.*
屬性)。當鏈接到多個系統時,此屬性的典型用法將嵌套在定製環境中。
默認值:false。
綁定屬性使用格式spring.cloud.stream.bindings.<channelName>.<property>=<value>
提供。<channelName>
表示正在配置的通道的名稱(例如Source
的output
)。
爲了不重複,Spring Cloud Stream支持全部通道的設置值,格式爲spring.cloud.stream.default.<property>=<value>
。
在下面的內容中,咱們指出咱們在哪裏省略了spring.cloud.stream.bindings.<channelName>.
前綴,而且只關注屬性名稱,但有一個理解,前綴將被包含在運行時。
Properties使用Spring Cloud Stream
如下綁定屬性可用於輸入和輸出綁定,而且必須以spring.cloud.stream.bindings.<channelName>.
爲前綴,例如spring.cloud.stream.bindings.input.destination=ticktock
。
能夠使用前綴spring.cloud.stream.default
設置默認值,例如spring.cloud.stream.default.contentType=application/json
。
目的地
綁定中間件上的通道的目標目標(例如,RabbitMQ交換或Kafka主題)。若是通道綁定爲消費者,則能夠將其綁定到多個目標,而且目標名稱能夠指定爲逗號分隔的字符串值。若是未設置,則使用通道名稱。此屬性的默認值不能被覆蓋。
組
渠道的消費羣體。僅適用於入站綁定。參見消費者羣體。
默認值:null(表示匿名消費者)。
內容類型
頻道的內容類型。
默認值:null(以便不執行類型強制)。
粘合劑
這種綁定使用的粘合劑。有關詳細信息,請參閱Classpath上的Multiple Binders。
默認值:null(默認的binder將被使用,若是存在)。
消費者物業
如下綁定屬性僅適用於輸入綁定,而且必須以spring.cloud.stream.bindings.<channelName>.consumer.
爲前綴,例如spring.cloud.stream.bindings.input.consumer.concurrency=3
。
默認值能夠使用前綴spring.cloud.stream.default.consumer
設置,例如spring.cloud.stream.default.consumer.headerMode=raw
。
併發
入站消費者的併發性。
默認值:1
。
分區
消費者是否從分區生產者接收數據。
默認值:false
。
headerMode
設置爲raw
時,禁用輸入頭文件解析。僅適用於不支持消息頭的消息中間件,而且須要頭部嵌入。入站數據來自外部Spring Cloud Stream應用程序時頗有用。
默認值:embeddedHeaders
。
maxAttempts
若是處理失敗,則嘗試處理消息的次數(包括第一個)。設置爲1以禁用重試。
默認值:3
。
backOffInitialInterval
退避初始間隔重試。
默認值:1000
。
backOffMaxInterval
最大回退間隔。
默認值:10000
。
backOffMultiplier
退避倍數。
默認值:2.0
。
instanceIndex
當設置爲大於等於零的值時,容許自定義此消費者的實例索引(若是與spring.cloud.stream.instanceIndex
不一樣)。設置爲負值時,它將默認爲spring.cloud.stream.instanceIndex
。
默認值:-1
。
instanceCount
當設置爲大於等於零的值時,容許自定義此消費者的實例計數(若是與spring.cloud.stream.instanceCount
不一樣)。當設置爲負值時,它將默認爲spring.cloud.stream.instanceCount
。
默認值:-1
。
製做人Properties
如下綁定屬性僅可用於輸出綁定,而且必須以spring.cloud.stream.bindings.<channelName>.producer.
爲前綴,例如spring.cloud.stream.bindings.input.producer.partitionKeyExpression=payload.id
。
默認值能夠使用前綴spring.cloud.stream.default.producer
設置,例如spring.cloud.stream.default.producer.partitionKeyExpression=payload.id
。
partitionKeyExpression
一個肯定如何分配出站數據的SpEL表達式。若是設置,或者若是設置了partitionKeyExtractorClass
,則該通道上的出站數據將被分區,而且partitionCount
必須設置爲大於1的值才能生效。這兩個選項是相互排斥的。請參閱分區支持。
默認值:null。
partitionKeyExtractorClass
一個PartitionKeyExtractorStrategy
實現。若是設置,或者若是設置了partitionKeyExpression
,則該通道上的出站數據將被分區,而且partitionCount
必須設置爲大於1的值才能生效。這兩個選項是相互排斥的。請參閱分區支持。
默認值:null。
partitionSelectorClass
一個PartitionSelectorStrategy
實現。與partitionSelectorExpression
相互排斥。若是沒有設置,則分區將被選爲hashCode(key) % partitionCount
,其中key
經過partitionKeyExpression
或partitionKeyExtractorClass
計算。
默認值:null。
partitionSelectorExpression
用於自定義分區選擇的SpEL表達式。與partitionSelectorClass
相互排斥。若是沒有設置,則分區將被選爲hashCode(key) % partitionCount
,其中key
經過partitionKeyExpression
或partitionKeyExtractorClass
計算。
默認值:null。
partitionCount
若是啓用分區,則數據的目標分區數。若是生產者被分區,則必須設置爲大於1的值。在Kafka,解釋爲提示; 而是使用更大的和目標主題的分區計數。
默認值:1
。
requiredGroups
生成者必須確保消息傳遞的組合的逗號分隔列表,即便它們在建立以後啓動(例如,經過在RabbitMQ中預先建立持久隊列)。
headerMode
設置爲raw
時,禁用輸出上的標題嵌入。僅適用於不支持消息頭的消息中間件,而且須要頭部嵌入。生成非Spring Cloud Stream應用程序的數據時頗有用。
默認值:embeddedHeaders
。
useNativeEncoding
當設置爲true
時,出站消息由客戶端庫直接序列化,必須相應配置(例如設置適當的Kafka生產者值序列化程序)。當使用此配置時,出站消息編組不是基於綁定的contentType
。當使用本地編碼時,消費者有責任使用適當的解碼器(例如:Kafka消費者價值解串器)來對入站消息進行反序列化。此外,當使用本機編碼/解碼時,headerMode
屬性將被忽略,標題不會嵌入到消息中。
默認值:false
。
除了經過@EnableBinding
定義的通道以外,Spring Cloud Stream容許應用程序將消息發送到動態綁定的目的地。這是有用的,例如,當目標目標須要在運行時肯定。應用程序能夠使用@EnableBinding
註冊自動註冊的BinderAwareChannelResolver
bean。
屬性「spring.cloud.stream.dynamicDestinations」可用於將動態目標名稱限制爲預先已知的集合(白名單)。若是屬性未設置,任何目的地均可以動態綁定。
能夠直接使用BinderAwareChannelResolver
,如如下示例所示,其中REST控制器使用路徑變量來肯定目標通道。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding @Controller public class SourceWithDynamicDestination { @Autowired private BinderAwareChannelResolver resolver; @RequestMapping(path = "/{target}", method = POST, consumes = "*/*") @ResponseStatus(HttpStatus.ACCEPTED) public void handleRequest(@RequestBody String body, @PathVariable("target") target, @RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) { sendMessage(body, target, contentType); } private void sendMessage(String body, String target, Object contentType) { resolver.resolveDestination(target).send(MessageBuilder.createMessage(body, new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType)))); } }</code></span></span>
在默認端口8080上啓動應用程序後,發送如下數據時:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">curl -H "Content-Type: application/json" -X POST -d "customer-1" http://localhost:8080/customers curl -H "Content-Type: application/json" -X POST -d "order-1" http://localhost:8080/orders</span></span>
目的地的客戶和「訂單」是在經紀人中建立的(例如:在Rabbit的狀況下進行交換,或者在Kafka的狀況下爲主題),其名稱爲「客戶」和「訂單」,數據被髮布到適當的目的地。
BinderAwareChannelResolver
是通用的Spring Integration DestinationResolver
,能夠注入其餘組件。例如,在使用基於傳入JSON消息的target
字段的SpEL表達式的路由器中。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding @Controller public class SourceWithDynamicDestination { @Autowired private BinderAwareChannelResolver resolver; @RequestMapping(path = "/", method = POST, consumes = "application/json") @ResponseStatus(HttpStatus.ACCEPTED) public void handleRequest(@RequestBody String body, @RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) { sendMessage(body, contentType); } private void sendMessage(Object body, Object contentType) { routerChannel().send(MessageBuilder.createMessage(body, new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType)))); } @Bean(name = "routerChannel") public MessageChannel routerChannel() { return new DirectChannel(); } @Bean @ServiceActivator(inputChannel = "routerChannel") public ExpressionEvaluatingRouter router() { ExpressionEvaluatingRouter router = new ExpressionEvaluatingRouter(new SpelExpressionParser().parseExpression("payload.target")); router.setDefaultOutputChannelName("default-output"); router.setChannelResolver(resolver); return router; } }</code></span></span>
要容許您傳播關於已生成消息的內容類型的信息,默認狀況下,Spring Cloud Stream附加contentType
標頭到出站消息。對於不直接支持頭文件的中間件,Spring Cloud Stream提供了本身的自動將郵件包裹在本身的信封中的機制。對於支持頭文件的中間件,Spring Cloud Stream應用程序能夠從非Spring Cloud Stream應用程序接收具備給定內容類型的消息。
Spring Cloud Stream能夠經過兩種方式處理基於此信息的消息:
經過其入站和出站渠道的contentType
設置
經過對@StreamListener
註釋的方法執行的參數映射
Spring Cloud Stream容許您使用綁定的spring.cloud.stream.bindings.<channelName>.content-type
屬性聲明性地配置輸入和輸出的類型轉換。請注意,通常類型轉換也能夠經過在應用程序中使用變壓器輕鬆實現。目前,Spring Cloud Stream本機支持流中經常使用的如下類型轉換:
來自/從POJO的JSON
對象到/來自byte []:用於遠程傳輸的原始字節序列化,應用程序發出的字節,或使用Java序列化轉換爲字節(要求對象爲Serializable)
字符串到/來自byte []
對象到純文本(調用對象的toString()方法)
其中JSON表示包含JSON的字節數組或字符串有效負載。目前,對象能夠從JSON字節數組或字符串轉換。轉換爲JSON老是產生一個String。
若是在出站通道上沒有設置content-type
屬性,則Spring Cloud Stream將使用基於Kryo序列化框架的序列化程序對有效負載進行序列化。在目的地反序列化消息須要在接收者的類路徑上存在有效載荷類。
content-type
值被解析爲媒體類型,例如application/json
或text/plain;charset=UTF-8
。MIME類型對於指示如何轉換爲String或byte []內容特別有用。Spring Cloud Stream還使用MIME類型格式來表示Java類型,使用具備type
參數的通常類型application/x-java-object
。例如,application/x-java-object;type=java.util.Map
或application/x-java-object;type=com.bar.Foo
能夠設置爲輸入綁定的content-type
屬性。此外,Spring Cloud Stream提供自定義MIME類型,特別是application/x-spring-tuple
來指定元組。
類型轉換Spring Cloud Stream提供的開箱即用以下表所示:「源有效載荷」是指轉換前的有效載荷,「目標有效載荷」是指轉換後的「有效載荷」。類型轉換能夠在「生產者」一側(輸出)或「消費者」一側(輸入)上進行。
來源有效載荷 | 目標有效載荷 | content-type 標題(來源訊息) |
content-type 標題(轉換後) |
註釋 |
---|---|---|---|---|
POJO |
JSON String |
ignored |
application/json |
|
Tuple |
JSON String |
ignored |
application/json |
JSON是爲Tuple量身定製的 |
POJO |
String (toString()) |
ignored |
text/plain, java.lang.String |
|
POJO |
byte[] (java.io serialized) |
ignored |
application/x-java-serialized-object |
|
JSON byte[] or String |
POJO |
application/json (or none) |
application/x-java-object |
|
byte[] or String |
Serializable |
application/x-java-serialized-object |
application/x-java-object |
|
JSON byte[] or String |
Tuple |
application/json (or none) |
application/x-spring-tuple |
|
byte[] |
String |
any |
text/plain, java.lang.String |
將應用在content-type頭中指定的任何Charset |
String |
byte[] |
any |
application/octet-stream |
將應用在content-type頭中指定的任何Charset |
注意 |
轉換適用於須要類型轉換的有效內容。例如,若是應用程序生成帶有outputType = application / json的XML字符串,則該有效載荷將不會從XML轉換爲JSON。這是由於發送到出站通道的有效載荷已是一個String,因此在運行時不會應用轉換。一樣重要的是要注意,當使用默認的序列化機制時,必須在發送和接收應用程序之間共享有效負載類,而且與二進制內容兼容。當應用程序代碼在兩個應用程序中獨立更改時,這可能會產生問題,由於二進制格式和代碼可能會變得不兼容。 |
提示 |
雖然入站和出站渠道都支持轉換,但特別推薦將其用於轉發出站郵件。對於入站郵件的轉換,特別是當目標是POJO時, |
除了支持開箱即用的轉換,Spring Cloud Stream還支持註冊您本身的郵件轉換實現。這容許您以各類自定義格式(包括二進制)發送和接收數據,並將其與特定的contentTypes
關聯。Spring Cloud Stream將全部類型爲org.springframework.messaging.converter.MessageConverter
的bean註冊爲自定義消息轉換器以及開箱即用消息轉換器。
若是您的消息轉換器須要使用特定的content-type
和目標類(用於輸入和輸出),則消息轉換器須要擴展org.springframework.messaging.converter.AbstractMessageConverter
。對於使用@StreamListener
的轉換,實現org.springframework.messaging.converter.MessageConverter
的消息轉換器就足夠了。
如下是在Spring Cloud Stream應用程序中建立消息轉換器bean(內容類型爲application/bar
)的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Sink.class) @SpringBootApplication public static class SinkApplication { ... @Bean public MessageConverter customMessageConverter() { return new MyCustomMessageConverter(); }</code></span></span>
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public class MyCustomMessageConverter extends AbstractMessageConverter { public MyCustomMessageConverter() { super(new MimeType("application", "bar")); } @Override protected boolean supports(Class<?> clazz) { return (Bar.class == clazz); } @Override protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) { Object payload = message.getPayload(); return (payload instanceof Bar ? payload : new Bar((byte[]) payload)); } }</code></span></span>
Spring Cloud Stream還爲基於Avro的轉換器和模式演進提供支持。詳情請參閱具體章節。
@StreamListener
和訊息轉換@StreamListener
註釋提供了一種方便的方式來轉換傳入的消息,而不須要指定輸入通道的內容類型。在使用@StreamListener
註釋的方法的調度過程當中,若是參數須要轉換,將自動應用轉換。
例如,讓咱們考慮一個帶有{"greeting":"Hello, world"}
的String內容的消息,而且在輸入通道上收到application/json
的application/json
標題。讓咱們考慮接收它的如下應用程序:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public class GreetingMessage { String greeting; public String getGreeting() { return greeting; } public void setGreeting(String greeting) { this.greeting = greeting; } } @EnableBinding(Sink.class) @EnableAutoConfiguration public static class GreetingSink { @StreamListener(Sink.INPUT) public void receive(Greeting greeting) { // handle Greeting } }</code></span></span>
該方法的參數將自動填充包含JSON字符串的未編組形式的POJO。
Spring Cloud Stream經過其spring-cloud-stream-schema
模塊爲基於模式的消息轉換器提供支持。目前,基於模式的消息轉換器開箱即用的惟一序列化格式是Apache Avro,在未來的版本中能夠添加更多的格式。
spring-cloud-stream-schema
模塊包含可用於Apache Avro序列化的兩種類型的消息轉換器:
使用序列化/反序列化對象的類信息的轉換器,或者啓動時已知位置的模式;
轉換器使用模式註冊表 - 他們在運行時定位模式,以及隨着域對象的發展動態註冊新模式。
AvroSchemaMessageConverter
支持使用預約義模式或使用類中可用的模式信息(反射或包含在SpecificRecord
)中的序列化和反序列化消息。若是轉換的目標類型是GenericRecord
,則必須設置模式。
對於使用它,您能夠簡單地將其添加到應用程序上下文中,可選地指定一個或多個MimeTypes
將其關聯。默認MimeType
爲application/avro
。
如下是在註冊Apache Avro MessageConverter
的宿應用程序中進行配置的示例,而不須要預約義的模式:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Sink.class) @SpringBootApplication public static class SinkApplication { ... @Bean public MessageConverter userMessageConverter() { return new AvroSchemaMessageConverter(MimeType.valueOf("avro/bytes")); } }</code></span></span>
相反,這裏是一個應用程序,註冊一個具備預約義模式的轉換器,能夠在類路徑中找到:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(Sink.class) @SpringBootApplication public static class SinkApplication { ... @Bean public MessageConverter userMessageConverter() { AvroSchemaMessageConverter converter = new AvroSchemaMessageConverter(MimeType.valueOf("avro/bytes")); converter.setSchemaLocation(new ClassPathResource("schemas/User.avro")); return converter; } }</code></span></span>
爲了瞭解模式註冊表客戶端轉換器,咱們將首先描述模式註冊表支持。
大多數序列化模型,特別是旨在跨不一樣平臺和語言進行可移植性的序列化模型,依賴於描述數據如何在二進制有效載荷中被序列化的模式。爲了序列化數據而後解釋它,發送方和接收方都必須訪問描述二進制格式的模式。在某些狀況下,能夠從序列化的有效載荷類型或從反序列化時的目標類型中推斷出模式,可是在許多狀況下,應用程序能夠從訪問描述二進制數據格式的顯式模式中受益。模式註冊表容許您以文本格式(一般爲JSON)存儲模式信息,並使該信息可訪問須要它的各類應用程序以二進制格式接收和發送數據。一個模式能夠做爲一個元組引用,它由
做爲模式的邏輯名稱的主題 ;
模式版本 ;
描述數據 的二進制格式的模式格式。
Spring Cloud Stream提供了模式註冊表服務器實現。爲了使用它,您能夠簡單地將spring-cloud-stream-schema-server
工件添加到項目中,並使用@EnableSchemaRegistryServer
註釋,將模式註冊表服務器REST控制器添加到應用程序中。此註釋旨在與Spring Boot Web應用程序一塊兒使用,服務器的監聽端口由server.port
設置控制。spring.cloud.stream.schema.server.path
設置可用於控制模式服務器的根路徑(特別是嵌入其餘應用程序時)。spring.cloud.stream.schema.server.allowSchemaDeletion
布爾設置能夠刪除模式。默認狀況下,這是禁用的。
模式註冊表服務器使用關係數據庫來存儲模式。默認狀況下,它使用一個嵌入式數據庫。您能夠使用Spring Boot SQL數據庫和JDBC配置選項自定義模式存儲。
啓用模式註冊表的Spring Boot應用程序以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableSchemaRegistryServer public class SchemaRegistryServerApplication { public static void main(String[] args) { SpringApplication.run(SchemaRegistryServerApplication.class, args); } }</code></span></span>
Schema註冊服務器API
Schema註冊服務器API由如下操做組成:
POST /
註冊一個新的架構
接受具備如下字段的JSON有效載荷:
subject
模式主題;
format
模式格式;
definition
模式定義。
響應是JSON格式的模式對象,包含如下字段:
id
模式標識;
subject
模式主題;
format
模式格式;
version
模式版本;
definition
模式定義。
GET /{subject}/{format}/{version}
根據其主題,格式和版本檢索現有模式。
響應是JSON格式的模式對象,包含如下字段:
id
模式標識;
subject
模式主題;
format
模式格式;
version
模式版本;
definition
模式定義。
GET /{subject}/{format}
根據其主題和格式檢索現有模式的列表。
響應是JSON格式的每一個模式對象的模式列表,包含如下字段:
id
模式標識;
subject
模式主題;
format
模式格式;
version
模式版本;
definition
模式定義。
GET /schemas/{id}
經過其id來檢索現有的模式。
響應是JSON格式的模式對象,包含如下字段:
id
模式標識;
subject
模式主題;
format
模式格式;
version
模式版本;
definition
模式定義。
DELETE /{subject}/{format}/{version}
按其主題,格式和版本刪除現有模式。
DELETE /schemas/{id}
按其ID刪除現有模式。
DELETE /{subject}
按其主題刪除現有模式。
注意 |
本說明僅適用於Spring Cloud Stream 1.1.0.RELEASE的用戶。Spring Cloud Stream 1.1.0.RELEASE使用表名 |
與模式註冊表服務器交互的客戶端抽象是SchemaRegistryClient
接口,具備如下結構:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface SchemaRegistryClient { SchemaRegistrationResponse register(String subject, String format, String schema); String fetch(SchemaReference schemaReference); String fetch(Integer id); }</code></span></span>
Spring Cloud Stream提供了開箱即用的實現,用於與其本身的模式服務器交互,以及與Confluent Schema註冊表進行交互。
能夠使用@EnableSchemaRegistryClient
配置Spring Cloud Stream模式註冊表的客戶端,以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java"> @EnableBinding(Sink.class) @SpringBootApplication @EnableSchemaRegistryClient public static class AvroSinkApplication { ... }</code></span></span>
注意 |
優化了默認轉換器,以緩存來自遠程服務器的模式,並且還會很是昂貴的 |
Schema註冊表客戶端屬性
Schema註冊表客戶端支持如下屬性:
spring.cloud.stream.schemaRegistryClient.endpoint
模式服務器的位置。在設置時使用完整的URL,包括協議(http
或https
),端口和上下文路徑。
默認
spring.cloud.stream.schemaRegistryClient.cached
客戶端是否應緩存模式服務器響應。一般設置爲false
,由於緩存發生在消息轉換器中。使用模式註冊表客戶端的客戶端應將其設置爲true
。
默認
true
對於在應用程序上下文中註冊了SchemaRegistryClient
bean的Spring Boot應用程序,Spring Cloud Stream將自動配置使用模式註冊表客戶端進行模式管理的Apache Avro消息轉換器。這簡化了模式演進,由於接收消息的應用程序能夠輕鬆訪問可與本身的讀取器模式進行協調的寫入器模式。
對於出站郵件,若是頻道的內容類型設置爲application/*+avro
,MessageConverter
將被激活,例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-properties">spring.cloud.stream.bindings.output.contentType=application/*+avro</code></span></span>
在出站轉換期間,消息轉換器將嘗試基於其類型推斷出站消息的模式,並使用SchemaRegistryClient
根據有效載荷類型將其註冊到主題。若是已經找到相同的模式,那麼將會檢索對它的引用。若是沒有,則將註冊模式並提供新的版本號。該消息將使用application/[prefix].[subject].v[version]+avro
的方案contentType
頭髮送,其中prefix
是可配置的,而且從有效載荷類型推導出subject
。
例如,類型爲User
的消息能夠做爲內容類型爲application/vnd.user.v2+avro
的二進制有效載荷發送,其中user
是主題,2
是版本號。
當接收到消息時,轉換器將從傳入消息的頭部推斷出模式引用,並嘗試檢索它。該模式將在反序列化過程當中用做寫入器模式。
Avro Schema註冊表消息轉換器屬性
若是您已經過設置spring.cloud.stream.bindings.output.contentType=application/*+avro
啓用基於Avro的模式註冊表客戶端,則能夠使用如下屬性自定義註冊的行爲。
spring.cloud.stream.schema.avro.dynamicSchemaGenerationEnabled
若是您但願轉換器使用反射從POJO推斷Schema,則啓用。
默認
false
spring.cloud.stream.schema.avro.readerSchema
Avro經過查看編寫器模式(源有效載荷)和讀取器模式(應用程序有效負載)來比較模式版本,查看Avro文檔以獲取更多信息。若是設置,這將覆蓋模式服務器上的任何查找,並將本地模式用做讀取器模式。
默認
null
spring.cloud.stream.schema.avro.schemaLocations
使用Schema服務器註冊此屬性中列出的任何.avsc
文件。
默認
empty
spring.cloud.stream.schema.avro.prefix
要在Content-Type頭上使用的前綴。
默認
vnd
爲了更好地瞭解Spring Cloud Stream註冊和解決新模式以及其使用Avro模式比較功能,咱們將提供兩個單獨的子部分:一個用於註冊,一個用於解析模式。
Schema註冊流程(序列化)
註冊過程的第一部分是從經過信道發送的有效載荷中提取模式。Avro類型,如SpecificRecord
或GenericRecord
已經包含一個模式,能夠從實例中當即檢索。在POJO的狀況下,若是屬性spring.cloud.stream.schema.avro.dynamicSchemaGenerationEnabled
設置爲true
(默認),則會推斷出一個模式。
圖10. Schema Writer Resolution Process
一旦得到了架構,轉換器就會從遠程服務器加載其元數據(版本)。首先,它查詢本地緩存,若是沒有找到它,則將數據提交到將使用版本控制信息回覆的服務器。轉換器將始終緩存結果,以免爲每一個須要序列化的新消息查詢Schema服務器的開銷。
圖11. Schema註冊流程
使用模式版本信息,轉換器設置消息的contentType
頭,以攜帶版本信息,如application/vnd.user.v1+avro
Schema解析過程(反序列化)
當讀取包含版本信息的消息(即,具備上述方案的contentType
標頭)時,轉換器將查詢Schema服務器以獲取消息的寫入器架構。一旦找到傳入消息的正確架構,它就會檢索讀取器架構,並使用Avro的架構解析支持將其讀入讀取器定義(設置默認值和缺乏的屬性)。
圖12. Schema閱讀決議程序
注意 |
瞭解編寫器架構(寫入消息的應用程序)和讀取器架構(接收應用程序)之間的區別很重要。請花點時間閱讀Avro術語並瞭解此過程。Spring Cloud Stream將始終提取writer模式以肯定如何讀取消息。若是您想要Avro的架構演進支持工做,您須要確保爲您的應用程序正確設置了readerSchema。 |
雖然Spring Cloud Stream使我的Spring Boot應用程序輕鬆鏈接到消息傳遞系統,可是Spring Cloud Stream的典型場景是建立多應用程序管道,其中微服務應用程序將數據發送給彼此。您能夠經過將相鄰應用程序的輸入和輸出目標相關聯來實現此場景。
假設設計要求時間源應用程序將數據發送到日誌接收應用程序,則能夠在兩個應用程序中使用名爲ticktock
的公共目標進行綁定。
時間來源(具備頻道名稱output
)將設置如下屬性:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.cloud.stream.bindings.output.destination=ticktock</span></span>
日誌接收器(通道名稱爲input
)將設置如下屬性:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.cloud.stream.bindings.input.destination=ticktock</span></span>
當擴展Spring Cloud Stream應用程序時,每一個實例均可以接收有關同一個應用程序的其餘實例數量以及本身的實例索引的信息。Spring Cloud Stream經過spring.cloud.stream.instanceCount
和spring.cloud.stream.instanceIndex
屬性執行此操做。例如,若是HDFS宿應用程序有三個實例,則全部三個實例將spring.cloud.stream.instanceCount
設置爲3
,而且各個應用程序將spring.cloud.stream.instanceIndex
設置爲0
,1
和2
。
當經過Spring Cloud數據流部署Spring Cloud Stream應用程序時,這些屬性將自動配置; 當Spring Cloud Stream應用程序獨立啓動時,必須正確設置這些屬性。默認狀況下,spring.cloud.stream.instanceCount
爲1
,spring.cloud.stream.instanceIndex
爲0
。
在放大的狀況下,這兩個屬性的正確配置對於解決分區行爲(見下文)通常很重要,而且某些綁定器(例如,Kafka binder)老是須要這兩個屬性,以確保該數據在多個消費者實例之間正確分割。
配置輸出綁定進行分區
輸出綁定被配置爲經過設置其惟一的一個partitionKeyExpression
或partitionKeyExtractorClass
屬性以及其partitionCount
屬性來發送分區數據。例如,如下是一個有效和典型的配置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload.id spring.cloud.stream.bindings.output.producer.partitionCount=5</span></span>
基於上述示例配置,使用如下邏輯將數據發送到目標分區。
基於partitionKeyExpression
,爲發送到分區輸出通道的每一個消息計算分區密鑰的值。partitionKeyExpression
是一個Spel表達式,它根據出站消息進行評估,以提取分區鍵。
若是SpEL表達式不足以知足您的須要,您能夠經過將屬性partitionKeyExtractorClass
設置爲實現org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy
接口的類來計算分區鍵值。雖然Spel表達式一般足夠,但更復雜的狀況可能會使用自定義實現策略。在這種狀況下,屬性「partitionKeyExtractorClass」能夠設置以下:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.cloud.stream.bindings.output.producer.partitionKeyExtractorClass=com.example.MyKeyExtractor spring.cloud.stream.bindings.output.producer.partitionCount=5</span></span>
一旦計算了消息密鑰,分區選擇過程將肯定目標分區爲0
和partitionCount - 1
之間的值。在大多數狀況下,默認計算基於公式key.hashCode() % partitionCount
。這能夠經過設置要針對'key'(經過partitionSelectorExpression
屬性)進行評估的Spel表達式或經過設置org.springframework.cloud.stream.binder.PartitionSelectorStrategy
實現(經過partitionSelectorClass
屬性))進行自定義。
「partitionSelectorExpression」和「partitionSelectorClass」的綁定級屬性能夠相似於上述示例中指定的「partitionKeyExpression」和「partitionKeyExtractorClass」屬性的類型。能夠爲更高級的場景配置其餘屬性,如如下部分所述。
Spring - 管理的自定義PartitionKeyExtractorClass
實現
在上面的示例中,MyKeyExtractor
之類的自定義策略由Spring Cloud Stream直接實例化。在某些狀況下,必須將這樣的自定義策略實現建立爲Spring bean,以便可以由Spring管理,以便它能夠執行依賴注入,屬性綁定等。能夠經過將其配置爲應用程序上下文中的@Bean,並使用徹底限定類名做爲bean的名稱,如如下示例所示。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@Bean(name="com.example.MyKeyExtractor") public MyKeyExtractor extractor() { return new MyKeyExtractor(); }</span></span>
做爲Spring bean,自定義策略從Spring bean的完整生命週期中受益。例如,若是實現須要直接訪問應用程序上下文,則能夠實現「ApplicationContextAware」。
配置輸入綁定進行分區
輸入綁定(通道名稱爲input
)被配置爲經過在應用程序自己設置其partitioned
屬性以及instanceIndex
和instanceCount
屬性來接收分區數據,如如下示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.cloud.stream.bindings.input.consumer.partitioned=true spring.cloud.stream.instanceIndex=3 spring.cloud.stream.instanceCount=5</span></span>
instanceCount
值表示數據須要分區的應用程序實例的總數,instanceIndex
必須是0
和instanceCount - 1
之間的多個實例的惟一值。實例索引幫助每一個應用程序實例識別從其接收數據的惟一分區(或者在Kafka的分區集合的狀況下)。重要的是正確設置兩個值,以確保全部數據都被使用,而且應用程序實例接收到互斥數據集。
雖然使用多個實例進行分區數據處理的場景可能會在獨立狀況下進行復雜化,可是經過將輸入和輸出值正確填充並依賴於運行時基礎架構,Spring Cloud數據流能夠顯着簡化流程。提供有關實例索引和實例計數的信息。
Spring Cloud Stream支持測試您的微服務應用程序,而無需鏈接到消息系統。您能夠使用spring-cloud-stream-test-support
庫提供的TestSupportBinder
,能夠將其做爲測試依賴項添加到應用程序中:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-test-support</artifactId> <scope>test</scope> </dependency></code></span></span>
注意 |
|
TestSupportBinder
容許用戶與綁定的頻道進行交互,並檢查應用程序發送和接收的消息
對於出站消息通道,TestSupportBinder
註冊單個訂戶,並將應用程序發送的消息保留在MessageCollector
中。它們能夠在測試過程當中被檢索,並對它們作出斷言。
用戶還能夠將消息發送到入站消息通道,以便消費者應用程序能夠使用消息。如下示例顯示瞭如何在處理器上測試輸入和輸出通道。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) public class ExampleTest { @Autowired private Processor processor; @Autowired private MessageCollector messageCollector; @Test @SuppressWarnings("unchecked") public void testWiring() { Message<String> message = new GenericMessage<>("hello"); processor.input().send(message); Message<String> received = (Message<String>) messageCollector.forChannel(processor.output()).poll(); assertThat(received.getPayload(), equalTo("hello world")); } @SpringBootApplication @EnableBinding(Processor.class) public static class MyProcessor { @Autowired private Processor channels; @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) public String transform(String in) { return in + " world"; } } }</code></span></span>
在上面的示例中,咱們正在建立一個具備輸入和輸出通道的應用程序,經過Processor
接口綁定。綁定的接口被注入測試,因此咱們能夠訪問這兩個通道。咱們正在輸入頻道發送消息,咱們使用Spring Cloud Stream測試支持提供的MessageCollector
來捕獲消息已經被髮送到輸出通道。收到消息後,咱們能夠驗證組件是否正常工做。
Spring Cloud Stream爲粘合劑提供健康指標。它以binders
的名義註冊,能夠經過設置management.health.binders.enabled
屬性啓用或禁用。
Spring Cloud Stream提供了一個名爲spring-cloud-stream-metrics
的模塊,能夠用來從Spring Boot度量端點到命名通道發出任何可用度量。該模塊容許運營商從流應用收集指標,而不依賴輪詢其端點。
當您設置度量綁定的目標名稱(例如spring.cloud.stream.bindings.applicationMetrics.destination=<DESTINATION_NAME>
)時,該模塊將被激活。能夠以與任何其餘生成器綁定類似的方式配置applicationMetrics
。applicationMetrics
的contentType
默認設置爲application/json
。
如下屬性可用於自定義度量標準的排放:
spring.cloud.stream.metrics.key
要發射的度量的名稱。應該是每一個應用程序的惟一值。
默認
${spring.application.name:${vcap.application.name:${spring.config.name:application}}}
spring.cloud.stream.metrics.prefix
前綴字符串,之前綴到度量鍵。
默認值:``
spring.cloud.stream.metrics.properties
就像includes
選項同樣,它容許將白名單應用程序屬性添加到度量有效負載
默認值:null。
有關度量導出過程的詳細概述,請參見Spring Boot參考文檔。Spring Cloud Stream提供了一個名爲application
的指標導出器,能夠經過常規Spring Boot指標配置屬性進行配置。
能夠經過使用出口商的全局Spring Boot配置設置或使用特定於導出器的屬性來配置導出器。要使用全局配置設置,屬性應以spring.metric.export
爲前綴(例如spring.metric.export.includes=integration**
)。這些配置選項將適用於全部出口商(除非它們的配置不一樣)。或者,若是要使用與其餘出口商不一樣的配置設置(例如,限制發佈的度量數量),則能夠使用前綴spring.metrics.export.triggers.application
配置Spring Cloud Stream提供的度量導出器(例如spring.metrics.export.triggers.application.includes=integration**
)。
注意 |
因爲Spring Boot的輕鬆約束,所包含的屬性的值可能與原始值稍有不一樣。 做爲經驗法則,度量導出器將嘗試使用點符號(例如 規範化的目標是使下游用戶可以始終如一地接收屬性名稱,不管它們如何設置在受監視的應用程序上( |
如下是經過如下命令以JSON格式發佈到頻道的數據的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>java -jar time-source.jar \ --spring.cloud.stream.bindings.applicationMetrics.destination=someMetrics \ --spring.cloud.stream.metrics.properties=spring.application** \ --spring.metrics.export.includes=integration.channel.input**,integration.channel.output**</code></span></span>
獲得的JSON是:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-javascript">{ "name":"time-source", "metrics":[ { "name":"integration.channel.output.errorRate.mean", "value":0.0, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.errorRate.max", "value":0.0, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.errorRate.min", "value":0.0, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.errorRate.stdev", "value":0.0, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.errorRate.count", "value":0.0, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.sendCount", "value":6.0, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.sendRate.mean", "value":0.994885872292989, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.sendRate.max", "value":1.006247080013156, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.sendRate.min", "value":1.0012035220116378, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.sendRate.stdev", "value":6.505181111084848E-4, "timestamp":"2017-04-11T16:56:35.790Z" }, { "name":"integration.channel.output.sendRate.count", "value":6.0, "timestamp":"2017-04-11T16:56:35.790Z" } ], "createdTime":"2017-04-11T20:56:35.790Z", "properties":{ "spring.application.name":"time-source", "spring.application.index":"0" } }</code></span></span>
對於Spring Cloud Stream示例,請參閱GitHub上的spring-cloud-stream樣本存儲庫。
要開始建立Spring Cloud Stream應用程序,請訪問Spring Initializr並建立一個名爲「GreetingSource」的新Maven項目。在下拉菜單中選擇Spring Boot {supported-spring-boot-version}。在「 搜索依賴關係」文本框中鍵入Stream Rabbit
或Stream Kafka
,具體取決於您要使用的binder。
接下來,在與GreetingSourceApplication
類相同的包中建立一個新類GreetingSource
。給它如下代碼:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.messaging.Source; import org.springframework.integration.annotation.InboundChannelAdapter; @EnableBinding(Source.class) public class GreetingSource { @InboundChannelAdapter(Source.OUTPUT) public String greet() { return "hello world " + System.currentTimeMillis(); } }</code></span></span>
@EnableBinding
註釋是觸發Spring Integration基礎架構組件的建立。具體來講,它將建立一個Kafka鏈接工廠,一個Kafka出站通道適配器,並在Source界面中定義消息通道:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">public interface Source { String OUTPUT = "output"; @Output(Source.OUTPUT) MessageChannel output(); }</code></span></span>
自動配置還建立一個默認輪詢器,以便每秒調用greet()
方法一次。標準的Spring Integration @InboundChannelAdapter
註釋使用返回值做爲消息的有效內容向源的輸出通道發送消息。
要測試驅動此設置,請運行Kafka消息代理。一個簡單的方法是使用Docker鏡像:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code># On OS X $ docker run -p 2181:2181 -p 9092:9092 --env ADVERTISED_HOST=`docker-machine ip \`docker-machine active\`` --env ADVERTISED_PORT=9092 spotify/kafka # On Linux $ docker run -p 2181:2181 -p 9092:9092 --env ADVERTISED_HOST=localhost --env ADVERTISED_PORT=9092 spotify/kafka</code></span></span>
構建應用程序:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">./mvnw clean package</span></span>
消費者應用程序以相似的方式進行編碼。返回Initializr並建立另外一個名爲LoggingSink的項目。而後在與類LoggingSinkApplication
相同的包中建立一個新類LoggingSink
,並使用如下代碼:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Sink; @EnableBinding(Sink.class) public class LoggingSink { @StreamListener(Sink.INPUT) public void log(String message) { System.out.println(message); } }</code></span></span>
構建應用程序:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">./mvnw clean package</span></span>
要將GreetingSource應用程序鏈接到LoggingSink應用程序,每一個應用程序必須共享相同的目標名稱。啓動這兩個應用程序以下所示,您將看到消費者應用程序打印「hello world」和時間戳到控制檯:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>cd GreetingSource java -jar target/GreetingSource-0.0.1-SNAPSHOT.jar --spring.cloud.stream.bindings.output.destination=mydest cd LoggingSink java -jar target/LoggingSink-0.0.1-SNAPSHOT.jar --server.port=8090 --spring.cloud.stream.bindings.input.destination=mydest</code></span></span>
(不一樣的服務器端口能夠防止兩個應用程序中用於維護Spring Boot執行器端點的HTTP端口的衝突。)
LoggingSink應用程序的輸出將以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>[ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090 (http) [ main] com.example.LoggingSinkApplication : Started LoggingSinkApplication in 6.828 seconds (JVM running for 7.371) hello world 1458595076731 hello world 1458595077732 hello world 1458595078733 hello world 1458595079734 hello world 1458595080735</code></span></span>
對於使用Apache Kafka綁定器,您只須要使用如下Maven座標將其添加到您的Spring Cloud Stream應用程序:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-kafka</artifactId> </dependency></code></span></span>
或者,您也能夠使用Spring Cloud Stream Kafka Starter。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-kafka</artifactId> </dependency></code></span></span>
如下能夠看到Apache Kafka綁定器操做的簡化圖。
圖13. Kafka Binder
Apache Kafka Binder實現將每一個目標映射到Apache Kafka主題。消費者組織直接映射到相同的Apache Kafka概念。分區也直接映射到Apache Kafka分區。
本節包含Apache Kafka綁定器使用的配置選項。
有關binder的常見配置選項和屬性,請參閱核心文檔。
Kafka Binder Properties
spring.cloud.stream.kafka.binder.brokers
Kafka活頁夾將鏈接的經紀人列表。
默認值:localhost
。
spring.cloud.stream.kafka.binder.defaultBrokerPort
brokers
容許使用或不使用端口信息指定的主機(例如,host1,host2:port2
)。當在代理列表中沒有配置端口時,這將設置默認端口。
默認值:9092
。
spring.cloud.stream.kafka.binder.zkNodes
Kafka綁定器能夠鏈接的ZooKeeper節點列表。
默認值:localhost
。
spring.cloud.stream.kafka.binder.defaultZkPort
zkNodes
容許使用或不使用端口信息指定的主機(例如,host1,host2:port2
)。當在節點列表中沒有配置端口時,這將設置默認端口。
默認值:2181
。
spring.cloud.stream.kafka.binder.configuration
客戶端屬性(生產者和消費者)的密鑰/值映射傳遞給由綁定器建立的全部客戶端。因爲這些屬性將被生產者和消費者使用,因此使用應該限於常見的屬性,特別是安全設置。
默認值:空地圖。
spring.cloud.stream.kafka.binder.headers
將由活頁夾傳送的自定義標題列表。
默認值:空。
spring.cloud.stream.kafka.binder.offsetUpdateTimeWindow
以毫秒爲單位的頻率(以毫秒爲單位)保存偏移量。0
忽略。
默認值:10000
。
spring.cloud.stream.kafka.binder.offsetUpdateCount
頻率,更新次數,哪些消耗的偏移量會持續存在。0
忽略。與offsetUpdateTimeWindow
相互排斥。
默認值:0
。
spring.cloud.stream.kafka.binder.requiredAcks
經紀人所需的acks數量。
默認值:1
。
spring.cloud.stream.kafka.binder.minPartitionCount
只有設置autoCreateTopics
或autoAddPartitions
纔有效。綁定器在其生成/消耗數據的主題上配置的全局最小分區數。它能夠由生產者的partitionCount
設置或生產者的instanceCount
* concurrency
設置的值替代(若是更大)。
默認值:1
。
spring.cloud.stream.kafka.binder.replicationFactor
若是autoCreateTopics
處於活動狀態,則自動建立主題的複製因子。
默認值:1
。
spring.cloud.stream.kafka.binder.autoCreateTopics
若是設置爲true
,綁定器將自動建立新主題。若是設置爲false
,則綁定器將依賴於已配置的主題。在後一種狀況下,若是主題不存在,則綁定器將沒法啓動。值得注意的是,此設置與代理的auto.topic.create.enable
設置無關,並不影響它:若是服務器設置爲自動建立主題,則能夠將其建立爲元數據檢索請求的一部分,並使用默認代理設置。
默認值:true
。
spring.cloud.stream.kafka.binder.autoAddPartitions
若是設置爲true
,則綁定器將根據須要建立新的分區。若是設置爲false
,則綁定器將依賴於已配置的主題的分區大小。若是目標主題的分區計數小於預期值,則綁定器將沒法啓動。
默認值:false
。
spring.cloud.stream.kafka.binder.socketBufferSize
Kafka消費者使用的套接字緩衝區的大小(以字節爲單位)。
默認值:2097152
。
Kafka消費者Properties
如下屬性僅適用於Kafka消費者,必須以spring.cloud.stream.kafka.bindings.<channelName>.consumer.
爲前綴。
autoRebalanceEnabled
當true
,主題分區將在消費者組的成員之間自動從新平衡。當false
根據spring.cloud.stream.instanceCount
和spring.cloud.stream.instanceIndex
爲每一個消費者分配一組固定的分區。這須要在每一個啓動的實例上適當地設置spring.cloud.stream.instanceCount
和spring.cloud.stream.instanceIndex
屬性。在這種狀況下,屬性spring.cloud.stream.instanceCount
一般必須大於1。
默認值:true
。
autoCommitOffset
是否在處理郵件時自動提交偏移量。若是設置爲false
,則入站消息中將顯示帶有org.springframework.kafka.support.Acknowledgment
類型的密鑰kafka_acknowledgment
的報頭。應用程序能夠使用此標頭來確認消息。有關詳細信息,請參閱示例部分。當此屬性設置爲false
時,Kafka binder將ack模式設置爲org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode.MANUAL
。
默認值:true
。
autoCommitOnError
只有autoCommitOffset
設置爲true
纔有效。若是設置爲false
,它會禁止致使錯誤的郵件的自動提交,而且只會爲成功的郵件執行提交,容許流在上次成功處理的郵件中自動重播,以防持續發生故障。若是設置爲true
,它將始終自動提交(若是啓用了自動提交)。若是沒有設置(默認),它實際上具備與enableDlq
相同的值,若是它們被髮送到DLQ,則自動提交錯誤的消息,不然不提交它們。
默認值:未設置。
recoveryInterval
鏈接恢復嘗試之間的間隔,以毫秒爲單位。
默認值:5000
。
resetOffsets
是否將消費者的偏移量重置爲startOffset
提供的值。
默認值:false
。
開始偏移
新組的起始偏移量,或resetOffsets
爲true
時的起始偏移量。容許的值:earliest
,latest
。若是消費者組被明確設置爲消費者'綁定'(經過spring.cloud.stream.bindings.<channelName>.group
),那麼'startOffset'設置爲earliest
; 不然對於anonymous
消費者組,設置爲latest
。
默認值:null(至關於earliest
)。
enableDlq
當設置爲true時,它將爲消費者發送啓用DLQ行爲。默認狀況下,致使錯誤的郵件將轉發到名爲error.<destination>.<group>
的主題。DLQ主題名稱能夠經過屬性dlqName
配置。對於錯誤數量相對較少而且重播整個原始主題可能太麻煩的狀況,這爲更常見的Kafka重播場景提供了另外一種選擇。
默認值:false
。
組態
使用包含通用Kafka消費者屬性的鍵/值對映射。
默認值:空地圖。
dlqName
接收錯誤消息的DLQ主題的名稱。
默認值:null(若是未指定,將致使錯誤的消息將轉發到名爲error.<destination>.<group>
的主題)。
Kafka生產者Properties
如下屬性僅適用於Kafka生產者,必須以spring.cloud.stream.kafka.bindings.<channelName>.producer.
爲前綴。
緩衝區大小
上限(以字節爲單位),Kafka生產者將在發送以前嘗試批量的數據量。
默認值:16384
。
同步
生產者是不是同步的
默認值:false
。
batchTimeout
生產者在發送以前等待多長時間,以便容許更多消息在同一批次中累積。(一般,生產者根本不等待,而且簡單地發送在先前發送進行中累積的全部消息。)非零值可能會以延遲爲代價增長吞吐量。
默認值:0
。
組態
使用包含通用Kafka生產者屬性的鍵/值對映射。
默認值:空地圖。
注意 |
Kafka綁定器將使用生產者的 |
用法示例
在本節中,咱們舉例說明了上述屬性在具體狀況下的使用。
示例:設置autoCommitOffset
false並依賴手動確認。
該示例說明了如何在消費者應用程序中手動確認偏移量。
此示例要求spring.cloud.stream.kafka.bindings.input.consumer.autoCommitOffset
設置爲false。使用相應的輸入通道名稱做爲示例。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>@SpringBootApplication @EnableBinding(Sink.class) public class ManuallyAcknowdledgingConsumer { public static void main(String[] args) { SpringApplication.run(ManuallyAcknowdledgingConsumer.class, args); } @StreamListener(Sink.INPUT) public void process(Message<?> message) { Acknowledgment acknowledgment = message.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class); if (acknowledgment != null) { System.out.println("Acknowledgment provided"); acknowledgment.acknowledge(); } } }</code></span></span>
示例:安全配置
Apache Kafka 0.9支持客戶端和代理商之間的安全鏈接。要充分利用此功能,請遵循彙編文檔中的Apache Kafka文檔以及Kafka 0.9 安全性指導原則。使用spring.cloud.stream.kafka.binder.configuration
選項爲綁定器建立的全部客戶端設置安全屬性。
例如,要將security.protocol
設置爲SASL_SSL
,請設置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_SSL</code></span></span>
全部其餘安全屬性能夠以相似的方式設置。
使用Kerberos時,請按照參考文檔中的說明建立和引用JAAS配置。
Spring Cloud Stream支持使用JAAS配置文件並使用Spring Boot屬性將JAAS配置信息傳遞到應用程序。
使用JAAS配置文件
能夠經過使用系統屬性爲Spring Cloud Stream應用程序設置JAAS和(可選)krb5文件位置。如下是使用JAAS配置文件啓動帶有SASL和Kerberos的Spring Cloud Stream應用程序的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code> java -Djava.security.auth.login.config=/path.to/kafka_client_jaas.conf -jar log.jar \ --spring.cloud.stream.kafka.binder.brokers=secure.server:9092 \ --spring.cloud.stream.kafka.binder.zkNodes=secure.zookeeper:2181 \ --spring.cloud.stream.bindings.input.destination=stream.ticktock \ --spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT</code></span></span>
使用Spring Boot屬性
做爲使用JAAS配置文件的替代方案,Spring Cloud Stream提供了一種使用Spring Boot屬性爲Spring Cloud Stream應用程序設置JAAS配置的機制。
如下屬性可用於配置Kafka客戶端的登陸上下文。
spring.cloud.stream.kafka.binder.jaas.loginModule
登陸模塊名稱。在正常狀況下不須要設置。
默認值:com.sun.security.auth.module.Krb5LoginModule
。
spring.cloud.stream.kafka.binder.jaas.controlFlag
登陸模塊的控制標誌。
默認值:required
。
spring.cloud.stream.kafka.binder.jaas.options
使用包含登陸模塊選項的鍵/值對映射。
默認值:空地圖。
如下是使用Spring Boot配置屬性啓動帶有SASL和Kerberos的Spring Cloud Stream應用程序的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code> java --spring.cloud.stream.kafka.binder.brokers=secure.server:9092 \ --spring.cloud.stream.kafka.binder.zkNodes=secure.zookeeper:2181 \ --spring.cloud.stream.bindings.input.destination=stream.ticktock \ --spring.cloud.stream.kafka.binder.autoCreateTopics=false \ --spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT \ --spring.cloud.stream.kafka.binder.jaas.options.useKeyTab=true \ --spring.cloud.stream.kafka.binder.jaas.options.storeKey=true \ --spring.cloud.stream.kafka.binder.jaas.options.keyTab=/etc/security/keytabs/kafka_client.keytab \ --spring.cloud.stream.kafka.binder.jaas.options.principal=kafka-client-1@EXAMPLE.COM</code></span></span>
這至關於如下JAAS文件:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>KafkaClient { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true storeKey=true keyTab="/etc/security/keytabs/kafka_client.keytab" principal="kafka-client-1@EXAMPLE.COM"; };</code></span></span>
若是所需的主題已經存在於代理上,或將由管理員建立,則自動建立能夠被關閉,而且僅須要發送客戶端JAAS屬性。做爲設置spring.cloud.stream.kafka.binder.autoCreateTopics
的替代方法,您能夠簡單地從應用程序中刪除代理依賴關係。有關詳細信息,請參閱基於綁定器的應用程序的類路徑中排除Kafka代理jar。
注意 |
不要在同一應用程序中混合JAAS配置文件和Spring Boot屬性。若是 |
注意 |
使用 |
使用綁定器與Apache Kafka 0.10
Spring Cloud Stream Kafka binder中的默認Kafka支持是針對Kafka版本0.10.1.1的。粘合劑還支持鏈接到其餘0.10版本和0.9客戶端。爲了作到這一點,當你建立包含你的應用程序的項目時,包括spring-cloud-starter-stream-kafka
,你一般會對默認的綁定器作。而後將這些依賴項添加到pom.xml文件中的<dependencies>
部分的頂部以覆蓋依賴關係。
如下是將應用程序降級到0.10.0.1的示例。因爲它仍在0.10行,所以能夠保留默認的spring-kafka
和spring-integration-kafka
版本。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>0.10.0.1</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>0.10.0.1</version> </dependency></code></span></span>
這是使用0.9.0.1版本的另外一個例子。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>1.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-kafka</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>0.9.0.1</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>0.9.0.1</version> </dependency></code></span></span>
注意 |
以上版本僅爲了舉例而提供。爲得到最佳效果,咱們建議您使用最新的0.10兼容版本的項目。 |
從基於綁定器的應用程序的類路徑中排除Kafka代理jar
Apache Kafka Binder使用做爲Apache Kafka服務器庫一部分的管理實用程序來建立和從新配置主題。若是在運行時不須要包含Apache Kafka服務器庫及其依賴關係,由於應用程序將依賴於管理中配置的主題,Kafka binder容許排除Apache Kafka服務器依賴關係從應用程序。
若是您使用上述建議的Kafka依賴關係的非默認版本,則只須要包含kafka代理依賴項。若是您使用默認的Kafka版本,請確保從spring-cloud-starter-stream-kafka
依賴關係中排除kafka broker jar,以下所示。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-kafka</artifactId> <exclusions> <exclusion> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> </exclusion> </exclusions> </dependency></code></span></span>
若是您排除Apache Kafka服務器依賴關係,而且該主題不在服務器上,那麼若是在服務器上啓用了自動主題建立,則Apache Kafka代理將建立該主題。請注意,若是您依賴此,則Kafka服務器將使用默認數量的分區和複製因子。另外一方面,若是在服務器上禁用自動主題建立,則在運行應用程序以前必須注意建立具備所需數量分區的主題。
若是要徹底控制分區的分配方式,請保留默認設置,即不要排除kafka代理程序jar,並確保將spring.cloud.stream.kafka.binder.autoCreateTopics
設置爲true
,這是默認設置。
由於不可能預料到用戶如何處理死信消息,因此框架不提供任何標準的機制來處理它們。若是死刑的緣由是短暫的,您可能但願將郵件路由到原始主題。可是,若是問題是一個永久性的問題,那可能會致使無限循環。如下spring-boot
應用程序是如何將這些消息路由到原始主題的示例,但在三次嘗試後將其移動到第三個「停車場」主題。該應用程序只是從死信主題中讀取的另外一個spring-cloud-stream應用程序。5秒內沒有收到消息時終止。
這些示例假定原始目的地是so8400out
,而消費者組是so8400
。
有幾個注意事項
當主應用程序未運行時,請考慮僅運行從新路由。不然,瞬態錯誤的重試將很快用盡。
或者,使用兩階段方法 - 使用此應用程序路由到第三個主題,另外一個則從那裏路由到主題。
因爲這種技術使用消息標頭來跟蹤重試,因此它不會與headerMode=raw
一塊兒使用。在這種狀況下,請考慮將一些數據添加到有效載荷(主應用程序能夠忽略)。
必須將x-retries
添加到headers
屬性spring.cloud.stream.kafka.binder.headers=x-retries
和主應用程序,以便標頭在應用程序之間傳輸。
因爲kafka是發佈/訂閱,因此重播的消息將被髮送給每一個消費者組,即便是那些首次成功處理消息的消費者組。
application.properties
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>spring.cloud.stream.bindings.input.group=so8400replay spring.cloud.stream.bindings.input.destination=error.so8400out.so8400 spring.cloud.stream.bindings.output.destination=so8400out spring.cloud.stream.bindings.output.producer.partitioned=true spring.cloud.stream.bindings.parkingLot.destination=so8400in.parkingLot spring.cloud.stream.bindings.parkingLot.producer.partitioned=true spring.cloud.stream.kafka.binder.configuration.auto.offset.reset=earliest spring.cloud.stream.kafka.binder.headers=x-retries</code></span></span>
應用
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableBinding(TwoOutputProcessor.class) public class ReRouteDlqKApplication implements CommandLineRunner { private static final String X_RETRIES_HEADER = "x-retries"; public static void main(String[] args) { SpringApplication.run(ReRouteDlqKApplication.class, args).close(); } private final AtomicInteger processed = new AtomicInteger(); @Autowired private MessageChannel parkingLot; @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public Message<?> reRoute(Message<?> failed) { processed.incrementAndGet(); Integer retries = failed.getHeaders().get(X_RETRIES_HEADER, Integer.class); if (retries == null) { System.out.println("First retry for " + failed); return MessageBuilder.fromMessage(failed) .setHeader(X_RETRIES_HEADER, new Integer(1)) .setHeader(BinderHeaders.PARTITION_OVERRIDE, failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID)) .build(); } else if (retries.intValue() < 3) { System.out.println("Another retry for " + failed); return MessageBuilder.fromMessage(failed) .setHeader(X_RETRIES_HEADER, new Integer(retries.intValue() + 1)) .setHeader(BinderHeaders.PARTITION_OVERRIDE, failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID)) .build(); } else { System.out.println("Retries exhausted for " + failed); parkingLot.send(MessageBuilder.fromMessage(failed) .setHeader(BinderHeaders.PARTITION_OVERRIDE, failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID)) .build()); } return null; } @Override public void run(String... args) throws Exception { while (true) { int count = this.processed.get(); Thread.sleep(5000); if (count == this.processed.get()) { System.out.println("Idle, terminating"); return; } } } public interface TwoOutputProcessor extends Processor { @Output("parkingLot") MessageChannel parkingLot(); } }</code></span></span>
對於使用RabbitMQ綁定器,您只須要使用如下Maven座標將其添加到您的Spring Cloud Stream應用程序:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency></code></span></span>
或者,您也能夠使用Spring Cloud Stream RabbitMQ入門。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency></code></span></span>
如下能夠看到RabbitMQ活頁夾的操做簡化圖。
圖14. RabbitMQ Binder
RabbitMQ Binder實現將每一個目的地映射到TopicExchange
。對於每一個消費者組,Queue
將綁定到該TopicExchange
。每一個消費者實例對其組的Queue
具備相應的RabbitMQ Consumer
實例。對於分區生成器/消費者,隊列後綴爲分區索引,並使用分區索引做爲路由密鑰。
使用autoBindDlq
選項,您能夠選擇配置綁定器來建立和配置死信隊列(DLQ)(以及死信交換DLX
)。死信隊列具備目標名稱,附有.dlq
。若是重試啓用(maxAttempts > 1
),則會將失敗的消息傳遞到DLQ。若是禁用重試(maxAttempts = 1
),則應將requeueRejected
設置爲false
(默認),以使失敗的消息將路由到DLQ,而不是從新排隊。此外,republishToDlq
致使綁定器向DLQ發佈失敗的消息(而不是拒絕它); 這使得可以將標題中的附加信息添加到消息中,例如x-exception-stacktrace
頭中的堆棧跟蹤。此選項不須要重試啓用; 一次嘗試後,您能夠從新發布失敗的消息。從版本1.2開始,您能夠配置從新發布的消息傳遞模式; 見財產republishDeliveryMode
。
重要 |
將requeueRejected 設置爲true 將致使消息被從新排序並從新發送,這可能不是您想要的,除非故障問題是短暫的。通常來講,最好經過將maxAttempts 設置爲大於1,或將republishToDlq 設置爲true 來啓用binder內的重試。 |
有關這些屬性的更多信息,請參閱RabbitMQ Binder Properties。
框架不提供消耗死信消息(或從新路由到主隊列)的任何標準機制。Dead-Letter隊列處理中描述了一些選項。
注意 |
在Spring Cloud Stream應用程序中使用多個 RabbitMQ綁定器時,禁用「RabbitAutoConfiguration」以免將RabbitAutoConfiguration應用於兩個綁定器的相同配置很重要。 |
本節包含特定於RabbitMQ Binder和綁定頻道的設置。
有關通用綁定配置選項和屬性,請參閱Spring Cloud Stream核心文檔。
RabbitMQ Binder Properties
默認狀況下,RabbitMQ binder使用Spring Boot的ConnectionFactory
,所以它支持RabbitMQ的全部Spring Boot配置選項。(有關參考,請參閱Spring Boot文檔。)RabbitMQ配置選項使用spring.rabbitmq
前綴。
除Spring Boot選項以外,RabbitMQ binder還支持如下屬性:
spring.cloud.stream.rabbit.binder.adminAddresses
RabbitMQ管理插件網址的逗號分隔列表。僅在nodes
包含多個條目時使用。此列表中的每一個條目必須在spring.rabbitmq.addresses
中具備相應的條目。
默認值:空。
spring.cloud.stream.rabbit.binder.nodes
RabbitMQ節點名稱的逗號分隔列表。當多個條目用於查找隊列所在的服務器地址時。此列表中的每一個條目必須在spring.rabbitmq.addresses
中具備相應的條目。
默認值:空。
spring.cloud.stream.rabbit.binder.compressionLevel
壓縮綁定的壓縮級別。見java.util.zip.Deflater
。
默認值:1
(BEST_LEVEL)。
RabbitMQ消費者Properties
如下屬性僅適用於Rabbit消費者,而且必須以spring.cloud.stream.rabbit.bindings.<channelName>.consumer.
爲前綴。
acknowledgeMode
確認模式。
默認值:AUTO
。
autoBindDlq
是否自動聲明DLQ並將其綁定到綁定器DLX。
默認值:false
。
bindingRoutingKey
將隊列綁定到交換機的路由密鑰(若是bindQueue
爲true
)。將附加分區目的地-<instanceIndex>
。
默認值:#
。
bindQueue
是否將隊列綁定到目的地交換機?若是您已經設置了本身的基礎設施而且先前已經建立/綁定了隊列,請設置爲false
。
默認值:true
。
deadLetterQueueName
DLQ的名稱
默認值:prefix+destination.dlq
deadLetterExchange
分配給隊列的DLX; 若是autoBindDlq爲true
默認值:'prefix + DLX'
deadLetterRoutingKey
一個死信路由密鑰分配給隊列; 若是autoBindDlq爲true
默認值:destination
declareExchange
是否爲目的地申報交換。
默認值:true
。
delayedExchange
是否將交換聲明爲Delayed Message Exchange
- 須要在代理上延遲的消息交換插件。x-delayed-type
參數設置爲exchangeType
。
默認值:false
。
dlqDeadLetterExchange
若是DLQ被聲明,則將DLX分配給該隊列
默認值:none
dlqDeadLetterRoutingKey
若是DLQ被聲明,則會將一個死信路由密鑰分配給該隊列; 默認無
默認值:none
dlqExpires
未使用的死信隊列被刪除多久(ms)
默認值:no expiration
dlqMaxLength
死信隊列中的最大消息數
默認值:no limit
dlqMaxLengthBytes
來自全部消息的死信隊列中的最大字節數
默認值:no limit
dlqMaxPriority
死信隊列中消息的最大優先級(0-255)
默認值:none
dlqTtl
聲明(ms)時默認適用於死信隊列的時間
默認值:no limit
durableSubscription
訂閱是否應該耐用。僅當group
也被設置時纔有效。
默認值:true
。
exchangeAutoDelete
若是declareExchange
爲真,則交換機是否應該自動刪除(刪除最後一個隊列後刪除)。
默認值:true
。
exchangeDurable
若是declareExchange
爲真,則交換應該是否持久(經紀人從新啓動)。
默認值:true
。
exchangeType
交換類型; 非分區目的地的direct
,fanout
或topic
direct
或topic
分區目的地。
默認值:topic
。
到期
未使用的隊列被刪除多久(ms)
默認值:no expiration
headerPatterns
要從入站郵件映射的頭文件。
默認值:['*']
(全部標題)。
maxConcurrency
最大消費者人數
默認值:1
。
最長長度
隊列中最大消息數
默認值:no limit
maxLengthBytes
來自全部消息的隊列中最大字節數
默認:no limit
maxPriority
隊列中消息的最大優先級(0-255)
默認
none
預取
預取計數。
默認值:1
。
字首
要添加到destination
和隊列名稱的前綴。
默認值:「」。
recoveryInterval
鏈接恢復嘗試之間的間隔,以毫秒爲單位。
默認值:5000
。
requeueRejected
在重試禁用或從新發布ToDlq是否爲false時,是否應從新發送傳遞失敗。
默認值:false
。
republishDeliveryMode
當republishToDlq
爲true
時,指定從新發布的郵件的傳遞模式。
默認值:DeliveryMode.PERSISTENT
republishToDlq
默認狀況下,嘗試重試後失敗的消息將被拒絕。若是配置了死信隊列(DLQ),則RabbitMQ將將失敗的消息(未更改)路由到DLQ。若是設置爲true
,則綁定器將從新發布具備附加頭的DLQ的失敗消息,包括最終失敗的緣由的異常消息和堆棧跟蹤。
默認值:false
交易
是否使用交易渠道。
默認值:false
。
TTL
聲明(ms)時默認適用於隊列的時間
默認值:no limit
txSize
阿克斯之間的交付次數。
默認值:1
。
兔子生產者Properties
如下屬性僅適用於Rabbit生產者,必須以spring.cloud.stream.rabbit.bindings.<channelName>.producer.
爲前綴。
autoBindDlq
是否自動聲明DLQ並將其綁定到綁定器DLX。
默認值:false
。
batchingEnabled
是否啓用生產者的消息批處理。
默認值:false
。
BATCHSIZE
批量啓動時要緩衝的消息數。
默認值:100
。
batchBufferLimit
默認值:10000
。
batchTimeout
默認值:5000
。
bindingRoutingKey
將隊列綁定到交換機的路由密鑰(若是bindQueue
爲true
)。僅適用於非分區目的地。僅適用於requiredGroups
,而後僅提供給這些組。
默認值:#
。
bindQueue
是否將隊列綁定到目的地交換機?若是您已經設置了本身的基礎架構而且先前已經建立/綁定了隊列,請設置爲false
。僅適用於requiredGroups
,而後僅提供給這些組。
默認值:true
。
壓縮
發送時是否應壓縮數據。
默認值:false
。
deadLetterQueueName
DLQ的名稱僅適用於requiredGroups
,僅適用於這些組。
默認值:prefix+destination.dlq
deadLetterExchange
分配給隊列的DLX; 若是autoBindDlq爲true只適用於requiredGroups
,而後只提供給這些組。
默認值:'prefix + DLX'
deadLetterRoutingKey
一個死信路由密鑰分配給隊列; 若是autoBindDlq爲true只適用於requiredGroups
,而後只提供給這些組。
默認值:destination
declareExchange
是否爲目的地申報交換。
默認值:true
。
延遲
評估應用於消息(x-delay
頭)的延遲的Spel表達式 - 若是交換不是延遲的消息交換,則不起做用。
默認值:No x-delay
頭設置。
delayedExchange
是否將交換聲明爲Delayed Message Exchange
- 須要經紀人上的延遲消息交換插件。x-delayed-type
參數設置爲exchangeType
。
默認值:false
。
deliveryMode
交貨方式。
默認值:PERSISTENT
。
dlqDeadLetterExchange
若是DLQ被聲明,則分配給該隊列的DLX只適用於requiredGroups
,而後僅提供給這些組。
默認值:none
dlqDeadLetterRoutingKey
若是DLQ被聲明,則會將一個死信路由密鑰分配給該隊列; 默認值none僅在提供requiredGroups
時才適用,而後僅適用於這些組。
默認值:none
dlqExpires
未使用的死信隊列被刪除以前多久(ms)僅適用於requiredGroups
,而後僅提供給這些組。
默認值:no expiration
dlqMaxLength
死信隊列中的最大消息數僅適用於requiredGroups
,僅適用於這些組。
默認值:no limit
dlqMaxLengthBytes
來自全部消息的死信隊列中的最大字節數僅適用於requiredGroups
,而後僅提供給這些組。
默認值:no limit
dlqMaxPriority
死信隊列中消息的最大優先級(0-255)僅適用於requiredGroups
,而後僅提供給這些組。
默認值:none
dlqTtl
聲明(ms)的默認時間適用於死信隊列僅適用於requiredGroups
,而後僅提供給這些組。
默認值:no limit
exchangeAutoDelete
若是declareExchange
爲真,則交換機是否應該自動刪除(刪除最後一個隊列後刪除)。
默認值:true
。
exchangeDurable
若是declareExchange
爲真,則交換應該是持久的(經紀人從新啓動)。
默認值:true
。
exchangeType
交換類型; direct
,fanout
或topic
; direct
或topic
。
默認值:topic
。
到期
在未使用的隊列被刪除以前多久(ms)僅適用於requiredGroups
,而後只提供給這些組。
默認值:no expiration
headerPatterns
要將標頭映射到出站郵件的模式。
默認值:['*']
(全部標題)。
最長長度
隊列中最大消息數僅適用於requiredGroups
,僅適用於這些組。
默認值:no limit
maxLengthBytes
來自全部消息的隊列中最大字節數僅適用於requiredGroups
,僅適用於這些組。
默認值:no limit
maxPriority
隊列中消息的最大優先級(0-255)僅適用於requiredGroups
,僅適用於這些組。
默認
none
字首
要添加到destination
交換機名稱的前綴。
默認值:「」。
routingKeyExpression
一個SpEL表達式來肯定在發佈消息時使用的路由密鑰。
默認值:destination
或destination-<partition>
分區目的地。
交易
是否使用交易渠道。
默認值:false
。
TTL
聲明時默認適用於隊列的時間(ms)僅適用於requiredGroups
,而後僅適用於這些組。
默認值:no limit
注意 |
在RabbitMQ的狀況下,內容類型頭能夠由外部應用程序設置。Spring Cloud Stream支持它們做爲用於任何類型傳輸(包括一般不支持頭文件的Kafka)的傳輸的擴展內部協議的一部分)。 |
概觀
在綁定器中啓用重試時,偵聽器容器線程將被掛起,以配置任何後退時段。在單個消費者須要嚴格排序時,這可能很重要,可是對於其餘用例,它能夠防止在該線程上處理其餘消息。使用綁定器重試的另外一種方法是設置死機字符隨着時間生活在死信隊列(DLQ)上,以及DLQ自己的死信配置。有關這裏討論的屬性的更多信息,請參閱RabbitMQ Binder Properties。啓用此功能的示例配置:
將autoBindDlq
設置爲true
- 綁定器將建立一個DLQ; 您能夠選擇在deadLetterQueueName
中指定一個名稱
將dlqTtl
設置爲您要在從新投遞之間等待的退出時間
將dlqDeadLetterExchange
設置爲默認交換 - DLQ的過時消息將被路由到原始隊列,由於默認deadLetterRoutingKey
是隊列名稱(destination.group
)
要強制一個消息被填字,拋出一個AmqpRejectAndDontRequeueException
,或設置requeueRejected
到true
並拋出任何異常。
循環將繼續沒有結束,這對於短暫的問題是很好的,可是您可能想在一些嘗試後放棄。幸運的是,RabbitMQ提供了x-death
標題,容許您肯定發生了多少個週期。
在放棄以後確認一則消息,拋出一個ImmediateAcknowledgeAmqpException
。
把它放在一塊兒
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>--- spring.cloud.stream.bindings.input.destination=myDestination spring.cloud.stream.bindings.input.group=consumerGroup #disable binder retries spring.cloud.stream.bindings.input.consumer.max-attempts=1 #dlx/dlq setup spring.cloud.stream.rabbit.bindings.input.consumer.auto-bind-dlq=true spring.cloud.stream.rabbit.bindings.input.consumer.dlq-ttl=5000 spring.cloud.stream.rabbit.bindings.input.consumer.dlq-dead-letter-exchange= ---</code></span></span>
此配置建立一個與通配符路由密鑰#
交換主題的隊列myDestination.consumerGroup
的交換myDestination
。它建立一個綁定到具備路由密鑰myDestination.consumerGroup
的直接交換DLX
的DLQ。當消息被拒絕時,它們被路由到DLQ。5秒鐘後,消息過時,並使用隊列名稱做爲路由密鑰路由到原始隊列。
Spring Boot申請
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableBinding(Sink.class) public class XDeathApplication { public static void main(String[] args) { SpringApplication.run(XDeathApplication.class, args); } @StreamListener(Sink.INPUT) public void listen(String in, @Header(name = "x-death", required = false) Map<?,?> death) { if (death != null && death.get("count").equals(3L)) { // giving up - don't send to DLX throw new ImmediateAcknowledgeAmqpException("Failed after 4 attempts"); } throw new AmqpRejectAndDontRequeueException("failed"); } }</code></span></span>
請注意,x-death
標題中的count屬性是Long
。
由於不可能預料到用戶如何處理死信消息,因此框架不提供任何標準的機制來處理它們。若是死刑的緣由是暫時的,您可能但願將郵件路由到原始隊列。可是,若是問題是一個永久性的問題,那可能會致使無限循環。如下spring-boot
應用程序是如何將這些消息路由到原始隊列的示例,可是在三次嘗試以後將其移動到第三個「停車場」隊列。第二個例子使用RabbitMQ延遲消息交換來向被從新排序的消息引入延遲。在這個例子中,每次嘗試的延遲都會增長。這些示例使用@RabbitListener
從DLQ接收消息,您也能夠在批處理過程當中使用RabbitTemplate.receive()
。
這些示例假定原始目的地是so8400in
,消費者組是so8400
。
非分區目的地
前兩個示例是目的地未分區。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication public class ReRouteDlqApplication { private static final String ORIGINAL_QUEUE = "so8400in.so8400"; private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; private static final String X_RETRIES_HEADER = "x-retries"; public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); System.out.println("Hit enter to terminate"); System.in.read(); context.close(); } @Autowired private RabbitTemplate rabbitTemplate; @RabbitListener(queues = DLQ) public void rePublish(Message failedMessage) { Integer retriesHeader = (Integer) failedMessage.getMessageProperties().getHeaders().get(X_RETRIES_HEADER); if (retriesHeader == null) { retriesHeader = Integer.valueOf(0); } if (retriesHeader < 3) { failedMessage.getMessageProperties().getHeaders().put(X_RETRIES_HEADER, retriesHeader + 1); this.rabbitTemplate.send(ORIGINAL_QUEUE, failedMessage); } else { this.rabbitTemplate.send(PARKING_LOT, failedMessage); } } @Bean public Queue parkingLot() { return new Queue(PARKING_LOT); } }</code></span></span>
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication public class ReRouteDlqApplication { private static final String ORIGINAL_QUEUE = "so8400in.so8400"; private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; private static final String X_RETRIES_HEADER = "x-retries"; private static final String DELAY_EXCHANGE = "dlqReRouter"; public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); System.out.println("Hit enter to terminate"); System.in.read(); context.close(); } @Autowired private RabbitTemplate rabbitTemplate; @RabbitListener(queues = DLQ) public void rePublish(Message failedMessage) { Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders(); Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER); if (retriesHeader == null) { retriesHeader = Integer.valueOf(0); } if (retriesHeader < 3) { headers.put(X_RETRIES_HEADER, retriesHeader + 1); headers.put("x-delay", 5000 * retriesHeader); this.rabbitTemplate.send(DELAY_EXCHANGE, ORIGINAL_QUEUE, failedMessage); } else { this.rabbitTemplate.send(PARKING_LOT, failedMessage); } } @Bean public DirectExchange delayExchange() { DirectExchange exchange = new DirectExchange(DELAY_EXCHANGE); exchange.setDelayed(true); return exchange; } @Bean public Binding bindOriginalToDelay() { return BindingBuilder.bind(new Queue(ORIGINAL_QUEUE)).to(delayExchange()).with(ORIGINAL_QUEUE); } @Bean public Queue parkingLot() { return new Queue(PARKING_LOT); } }</code></span></span>
分區目的地
對於分區目的地,全部分區都有一個DLQ,咱們從頭部肯定原始隊列。
republishToDlq = FALSE
當republishToDlq
爲false
時,RabbitMQ將消息發佈到DLX / DLQ,其中包含有關原始目的地信息的x-death
標題。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication public class ReRouteDlqApplication { private static final String ORIGINAL_QUEUE = "so8400in.so8400"; private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; private static final String X_DEATH_HEADER = "x-death"; private static final String X_RETRIES_HEADER = "x-retries"; public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); System.out.println("Hit enter to terminate"); System.in.read(); context.close(); } @Autowired private RabbitTemplate rabbitTemplate; @SuppressWarnings("unchecked") @RabbitListener(queues = DLQ) public void rePublish(Message failedMessage) { Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders(); Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER); if (retriesHeader == null) { retriesHeader = Integer.valueOf(0); } if (retriesHeader < 3) { headers.put(X_RETRIES_HEADER, retriesHeader + 1); List<Map<String, ?>> xDeath = (List<Map<String, ?>>) headers.get(X_DEATH_HEADER); String exchange = (String) xDeath.get(0).get("exchange"); List<String> routingKeys = (List<String>) xDeath.get(0).get("routing-keys"); this.rabbitTemplate.send(exchange, routingKeys.get(0), failedMessage); } else { this.rabbitTemplate.send(PARKING_LOT, failedMessage); } } @Bean public Queue parkingLot() { return new Queue(PARKING_LOT); } }</code></span></span>
republishToDlq =真
當republishToDlq
爲true
時,從新發布恢復器將原始交換和路由密鑰添加到標題。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication public class ReRouteDlqApplication { private static final String ORIGINAL_QUEUE = "so8400in.so8400"; private static final String DLQ = ORIGINAL_QUEUE + ".dlq"; private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot"; private static final String X_RETRIES_HEADER = "x-retries"; private static final String X_ORIGINAL_EXCHANGE_HEADER = RepublishMessageRecoverer.X_ORIGINAL_EXCHANGE; private static final String X_ORIGINAL_ROUTING_KEY_HEADER = RepublishMessageRecoverer.X_ORIGINAL_ROUTING_KEY; public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args); System.out.println("Hit enter to terminate"); System.in.read(); context.close(); } @Autowired private RabbitTemplate rabbitTemplate; @RabbitListener(queues = DLQ) public void rePublish(Message failedMessage) { Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders(); Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER); if (retriesHeader == null) { retriesHeader = Integer.valueOf(0); } if (retriesHeader < 3) { headers.put(X_RETRIES_HEADER, retriesHeader + 1); String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER); String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER); this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage); } else { this.rabbitTemplate.send(PARKING_LOT, failedMessage); } } @Bean public Queue parkingLot() { return new Queue(PARKING_LOT); } }</code></span></span>
Spring Cloud Bus將分佈式系統的節點與輕量級消息代理連接。這能夠用於廣播狀態更改(例如配置更改)或其餘管理指令。一個關鍵的想法是,總線就像一個分佈式執行器,用於擴展的Spring Boot應用程序,但也能夠用做應用程序之間的通訊通道。目前惟一的實現是使用AMQP代理做爲傳輸,可是相同的基本功能集(還有一些取決於傳輸)在其餘傳輸的路線圖上。
注意 |
Spring Cloud根據非限制性Apache 2.0許可證發佈。若是您想爲文檔的這一部分作出貢獻,或者發現錯誤,請在github中找到項目中的源代碼和問題跟蹤器。 |
Spring Cloud Bus的工做原理是添加Spring Boot自動配置,若是它在類路徑中檢測到自身。全部您須要作的是啓用總線是將spring-cloud-starter-bus-amqp
或spring-cloud-starter-bus-kafka
添加到您的依賴關係管理中,而且Spring Cloud負責其他部分。確保代理(RabbitMQ或Kafka)可用和配置:在本地主機上運行,您不該該作任何事情,但若是您遠程運行使用Spring Cloud鏈接器或Spring Boot定義經紀人憑據的約定,例如Rabbit
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: rabbitmq: host: mybroker.com port: 5672 username: user password: secret</span></span>
總線當前支持向全部節點發送消息,用於特定服務的全部節點(由Eureka定義)。將來可能會添加更多的選擇器標準(即,僅數據中心Y中的服務X節點等)。/bus/*
執行器命名空間下還有一些http端點。目前有兩個實施。第一個/bus/env
發送密鑰/值對來更新每一個節點的Spring環境。第二個,/bus/refresh
,將從新加載每一個應用程序的配置,就好像他們在他們的/refresh
端點上都被ping過。
注意 |
總線起動器覆蓋了Rabbit和Kafka,由於這是兩種最經常使用的實現方式,可是Spring Cloud Stream很是靈活,綁定器將與spring-cloud-bus 結合使用。 |
HTTP端點接受「目的地」參數,例如「/ bus / refresh?destination = customers:9000」,其中目的地是ApplicationContext
ID。若是ID由總線上的一個實例擁有,那麼它將處理消息,全部其餘實例將忽略它。Spring Boot將ContextIdApplicationContextInitializer
中的ID設置爲spring.application.name
,活動配置文件和server.port
的組合。
「destination」參數用於Spring PathMatcher
(路徑分隔符爲冒號:
)以肯定實例是否處理該消息。使用上述示例,「/ bus / refresh?destination = customers:**」將針對「客戶」服務的全部實例,而無論配置文件和端口設置爲ApplicationContext
ID。
總線嘗試從原始ApplicationEvent
一次消除處理事件兩次,一次從隊列中消除。爲此,它會檢查發送應用程序上下文id,以從新顯示當前的應用程序上下文ID。若是服務的多個實例具備相同的應用程序上下文id,則不會處理事件。在本地機器上運行,每一個服務將在不一樣的端口上,這將是應用程序上下文ID的一部分。Cloud Foundry提供了區分的索引。要確保應用程序上下文ID是惟一的,請將spring.application.index
設置爲服務的每一個實例惟一的值。例如,在lattice中,在application.properties中設置spring.application.index=${INSTANCE_INDEX}
(若是使用configserver,請設置bootstrap.properties)。
Spring Cloud Bus使用 Spring Cloud Stream廣播消息,以便獲取消息流,只須要在類路徑中包含您選擇的binder實現。有AMQP(RabbitMQ)和Kafka(spring-cloud-starter-bus-[amqp,kafka]
)的公共汽車專用起動方便。通常來講,Spring Cloud Stream依賴於用於配置中間件的Spring Boot自動配置約定,所以例如AMQP代理地址能夠使用spring.rabbitmq.*
配置屬性更改。Spring Cloud Bus在spring.cloud.bus.*
中具備少許本地配置屬性(例如spring.cloud.bus.destination
是使用外部中間件的主題的名稱)。一般,默認值就足夠了。
要更多地瞭解如何自定義消息代理設置,請參閱Spring Cloud Stream文檔。
能夠經過設置spring.cloud.bus.trace.enabled=true
來跟蹤總線事件(RemoteApplicationEvent
的子類)。若是這樣作,那麼Spring Boot TraceRepository
(若是存在)將顯示每一個發送的事件和來自每一個服務實例的全部ack。示例(來自/trace
端點):
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "timestamp": "2015-11-26T10:24:44.411+0000", "info": { "signal": "spring.cloud.bus.ack", "type": "RefreshRemoteApplicationEvent", "id": "c4d374b7-58ea-4928-a312-31984def293b", "origin": "stores:8081", "destination": "*:**" } }, { "timestamp": "2015-11-26T10:24:41.864+0000", "info": { "signal": "spring.cloud.bus.sent", "type": "RefreshRemoteApplicationEvent", "id": "c4d374b7-58ea-4928-a312-31984def293b", "origin": "customers:9000", "destination": "*:**" } }, { "timestamp": "2015-11-26T10:24:41.862+0000", "info": { "signal": "spring.cloud.bus.ack", "type": "RefreshRemoteApplicationEvent", "id": "c4d374b7-58ea-4928-a312-31984def293b", "origin": "customers:9000", "destination": "*:**" } }</code></span></span>
該跟蹤顯示RefreshRemoteApplicationEvent
從customers:9000
發送到全部服務,而且已被customers:9000
和stores:8081
收到(acked)。
爲了處理信號,您能夠向您的應用添加AckRemoteApplicationEvent
和SentApplicationEvent
類型的@EventListener
(並啓用跟蹤)。或者您能夠利用TraceRepository
並從中挖掘數據。
注意 |
任何總線應用程序均可以跟蹤ack,但有時在一個能夠對數據進行更復雜查詢的中央服務器中執行此操做是有用的。或者將其轉發到專門的跟蹤服務。 |
總線能夠攜帶任何類型爲RemoteApplicationEvent
的事件,但默認傳輸是JSON,而且解串器須要知道哪些類型將提早使用。要註冊一個新類型,它須要在org.springframework.cloud.bus.event
的子包中。
要自定義事件名稱,您能夠在自定義類上使用@JsonTypeName
,或者依賴默認策略來使用類的簡單名稱。請注意,生產者和消費者都須要訪問類定義。
若是您不能或不想爲自定義事件使用org.springframework.cloud.bus.event
的子包,則必須使用@RemoteApplicationEventScan
指定要掃描類型爲RemoteApplicationEvent
的事件的包。使用@RemoteApplicationEventScan
指定的軟件包包括子包。
例如,若是您有一個名爲FooEvent
的自定義事件:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.acme; public class FooEvent extends RemoteApplicationEvent { ... }</code></span></span>
您能夠經過如下方式與解串器註冊此事件:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.acme; @Configuration @RemoteApplicationEventScan public class BusConfiguration { ... }</code></span></span>
沒有指定一個值,使用@RemoteApplicationEventScan
的類的包將被註冊。在這個例子中,com.acme
將使用BusConfiguration
的包進行註冊。
您還能夠使用@RemoteApplicationEventScan
上的value
,basePackages
或basePackageClasses
屬性明確指定要掃描的軟件包。例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.acme; @Configuration //@RemoteApplicationEventScan({"com.acme", "foo.bar"}) //@RemoteApplicationEventScan(basePackages = {"com.acme", "foo.bar", "fizz.buzz"}) @RemoteApplicationEventScan(basePackageClasses = BusConfiguration.class) public class BusConfiguration { ... }</code></span></span>
以上@RemoteApplicationEventScan
的全部示例都是等效的,由於com.acme
程序包將經過在@RemoteApplicationEventScan
上明確指定程序包來註冊。請注意,您能夠指定要掃描的多個基本軟件包。
Adrian Cole,Spencer Gibb,Marcin Grzejszczak,Dave Syer
Dalston.RELEASE
Spring Cloud Sleuth爲Spring Cloud實現分佈式跟蹤解決方案。
Spring Cloud Sleuth借用了Dapper的術語。
Span:工做的基本單位 例如,發送RPC是一個新的跨度,以及向RPC發送響應。Span由跨度的惟一64位ID標識,跨度是其中一部分的跟蹤的另外一個64位ID。跨度還具備其餘數據,例如描述,時間戳記事件,鍵值註釋(標籤),致使它們的跨度的ID以及進程ID(一般是IP地址)。
跨距開始和中止,他們跟蹤他們的時間信息。建立跨度後,必須在未來的某個時刻中止。
提示 |
啓動跟蹤的初始範圍稱爲root span 。該跨度的跨度id的值等於跟蹤ID。 |
跟蹤:一組spans造成樹狀結構。例如,若是您正在運行分佈式大數據存儲,則可能會由put請求造成跟蹤。
註釋: 用於及時記錄事件的存在。用於定義請求的開始和中止的一些核心註釋是:
cs - 客戶端發送 - 客戶端已經發出請求。此註釋描繪了跨度的開始。
sr - 服務器接收 - 服務器端獲得請求,並將開始處理它。若是今後時間戳中減去cs時間戳,則會收到網絡延遲。
ss - 服務器發送 - 在完成請求處理後(響應發送回客戶端時)註釋。若是今後時間戳中減去sr時間戳,則會收到服務器端處理請求所需的時間。
cr - 客戶端接收 - 表示跨度的結束。客戶端已成功接收到服務器端的響應。若是今後時間戳中減去cs時間戳,則會收到客戶端從服務器接收響應所需的整個時間。
可視化Span和Trace將與Zipkin註釋一塊兒查看系統:
一個音符的每一個顏色表示跨度(7 spans - 從A到G)。若是您在筆記中有這樣的信息:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>Trace Id = X Span Id = D Client Sent</code></span></span>
這意味着,當前的跨度痕量-ID設置爲X,Span -編號設置爲ð。它也發出了 客戶端發送的事件。
這樣,spans的父/子關係的可視化將以下所示:
在如下部分中,將考慮上述圖像中的示例。
分佈式跟蹤與Zipkin
共有7個spans。若是您在Zipkin中查看痕跡,您將在第二個曲目中看到這個數字:
可是,若是您選擇特定的跟蹤,那麼您將看到4 spans:
注意 |
當選擇特定的跟蹤時,您將看到合併的spans。這意味着若是發送到服務器接收和服務器發送/接收客戶端和客戶端發送註釋的Zipkin有2個spans,那麼它們將被顯示爲一個跨度。 |
爲何在這種狀況下,7和4 spans之間有區別?
2 spans來自http:/start
範圍。它具備服務器接收(SR)和服務器發送(SS)註釋。
2 spans來自service1
到service2
到http:/foo
端點的RPC呼叫。它在service1
方面具備客戶端發送(CS)和客戶端接收(CR)註釋。它還在service2
方面具備服務器接收(SR)和服務器發送(SS)註釋。在物理上有2個spans,但它們造成與RPC調用相關的1個邏輯跨度。
2 spans來自service2
到service3
到http:/bar
端點的RPC呼叫。它在service2
方面具備客戶端發送(CS)和客戶接收(CR)註釋。它還具備service3
端的服務器接收(SR)和服務器發送(SS)註釋。在物理上有2個spans,但它們造成與RPC調用相關的1個邏輯跨度。
2 spans來自service2
到service4
到http:/baz
端點的RPC呼叫。它在service2
方面具備客戶端發送(CS)和客戶接收(CR)註釋。它還在service4
側具備服務器接收(SR)和服務器發送(SS)註釋。在物理上有2個spans,但它們造成與RPC調用相關的1個邏輯跨度。
所以,若是咱們計算spans ,http:/start
中有1 個來自service1
的呼叫service2
,2(service2
)呼叫service3
和2(service2
) service4
。共7個 spans。
邏輯上,咱們看到Total Spans的信息:4,由於咱們有1個跨度與傳入請求相關的service1
和3 spans與RPC調用相關。
可視化錯誤
Zipkin容許您可視化跟蹤中的錯誤。當異常被拋出而且沒有被捕獲時,咱們在Zipkin能夠正確着色的跨度上設置適當的標籤。您能夠在痕跡列表中看到一條是紅色的痕跡。這是由於拋出了一個異常。
若是您點擊該軌跡,您將看到相似的圖片
而後,若是您點擊其中一個spans,您將看到如下內容
你能夠看到,你能夠很容易的看到錯誤的緣由和整個stacktrace相關的。
實例
點擊Pivotal Web Services圖標便可實時查看!點擊Pivotal Web Services圖標直播!
Zipkin中的依賴圖將以下所示:
點擊Pivotal Web Services圖標便可實時查看!點擊Pivotal Web Services圖標直播!
對數相關
當經過跟蹤id等於例如2485ec27856c56f4
來對這四個應用程序的日誌進行灰名單時,將會獲得如下內容:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>service1.log:2016-02-26 11:15:47.561 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Hello from service1. Calling service2 service2.log:2016-02-26 11:15:47.710 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Hello from service2. Calling service3 and then service4 service3.log:2016-02-26 11:15:47.895 INFO [service3,2485ec27856c56f4,1210be13194bfe5,true] 68060 --- [nio-8083-exec-1] i.s.c.sleuth.docs.service3.Application : Hello from service3 service2.log:2016-02-26 11:15:47.924 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service3 [Hello from service3] service4.log:2016-02-26 11:15:48.134 INFO [service4,2485ec27856c56f4,1b1845262ffba49d,true] 68061 --- [nio-8084-exec-1] i.s.c.sleuth.docs.service4.Application : Hello from service4 service2.log:2016-02-26 11:15:48.156 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service4 [Hello from service4] service1.log:2016-02-26 11:15:48.182 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Got response from service2 [Hello from service2, response from service3 [Hello from service3] and from service4 [Hello from service4]]</code></span></span>
若是你使用像一個日誌聚合工具Kibana, Splunk的等您能夠訂購所發生的事件。基巴納的例子以下所示:
若是你想使用Logstash,這裏是Logstash的Grok模式:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>filter { # pattern matching logback pattern grok { match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } } }</code></span></span>
注意 |
若是您想將Grok與Cloud Foundry的日誌一塊兒使用,則必須使用此模式: |
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>filter { # pattern matching logback pattern grok { match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" } } }</code></span></span>
使用Logstash進行JSON回溯
一般,您不但願將日誌存儲在文本文件中,而不是將Logstash能夠當即選擇的JSON文件中存儲。爲此,您必須執行如下操做(爲了可讀性,咱們將依賴關係傳遞給groupId:artifactId:version
符號。
依賴關係設置
確保Logback位於類路徑(ch.qos.logback:logback-core
)
添加Logstash Logback編碼 - 版本4.6
的示例:net.logstash.logback:logstash-logback-encoder:4.6
回讀設置
您能夠在下面找到一個Logback配置(名爲logback-spring.xml)的示例:
未來自應用程序的信息以JSON格式記錄到build/${spring.application.name}.json
文件
已經評論了兩個額外的追加者 - 控制檯和標準日誌文件
具備與上一節所述相同的記錄模式
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <springProperty scope="context" name="springAppName" source="spring.application.name"/> <!-- Example for logging into the build folder of your project --> <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/> <!-- You can override this to have a custom pattern --> <property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> <!-- Appender to log to console --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <!-- Minimum logging level to be presented in the console logs--> <level>DEBUG</level> </filter> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>utf8</charset> </encoder> </appender> <!-- Appender to log to file --> <appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_FILE}</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>utf8</charset> </encoder> </appender> <!-- Appender to log to file in a JSON format --> <appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_FILE}.json</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp> <timeZone>UTC</timeZone> </timestamp> <pattern> <pattern> { "severity": "%level", "service": "${springAppName:-}", "trace": "%X{X-B3-TraceId:-}", "span": "%X{X-B3-SpanId:-}", "parent": "%X{X-B3-ParentSpanId:-}", "exportable": "%X{X-Span-Export:-}", "pid": "${PID:-}", "thread": "%thread", "class": "%logger{40}", "rest": "%message" } </pattern> </pattern> </providers> </encoder> </appender> <root level="INFO"> <appender-ref ref="console"/> <!-- uncomment this to have also JSON logs --> <!--<appender-ref ref="logstash"/>--> <!--<appender-ref ref="flatfile"/>--> </root> </configuration></code></span></span>
注意 |
若是您使用自定義logback-spring.xml ,則必須經過bootstrap application 而不是application 屬性文件傳遞spring.application.name 。不然您的自定義logback文件將不會正確讀取該屬性。 |
傳播Span上下文
跨度上下文是必須傳播到任何子進程跨越進程邊界的狀態。Span背景的一部分是行李。跟蹤和跨度ID是跨度上下文的必需部分。行李是可選的部分。
行李是一組密鑰:存儲在範圍上下文中的值對。行李與痕跡一塊兒旅行,並附在每個跨度上。Spring Cloud若是HTTP標頭以baggage-
爲前綴,而且以baggage_
開頭的消息傳遞,Sleuth將會明白標題是行李相關的。
重要 |
行李物品的數量或大小目前沒有限制。可是,請記住,太多可能會下降系統吞吐量或增長RPC延遲。在極端狀況下,因爲超出了傳輸級消息或報頭容量,可能會使應用程序崩潰。 |
在跨度上設置行李的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">Span initialSpan = this.tracer.createSpan("span"); initialSpan.setBaggageItem("foo", "bar");</code></span></span>
行李與Span標籤
行李隨行旅行(即每一個孩子跨度都包含其父母的行李)。Zipkin不瞭解行李,甚至不會收到這些信息。
標籤附加到特定的跨度 - 它們僅針對該特定跨度呈現。可是,您能夠經過標籤搜索查找跟蹤,其中存在具備搜索標籤值的跨度。
若是您但願可以根據行李查找跨度,則應在根跨度中添加相應的條目做爲標籤。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Autowired Tracer tracer; Span span = tracer.getCurrentSpan(); String baggageKey = "key"; String baggageValue = "foo"; span.setBaggageItem(baggageKey, baggageValue); tracer.addTag(baggageKey, baggageValue);</code></span></span>
只有Sleuth(對數相關)
若是您只想從Spring Cloud Sleuth中獲利,而沒有Zipkin集成,只需將spring-cloud-starter-sleuth
模塊添加到您的項目中便可。
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencyManagement> <strong>(1)</strong> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <strong>(2)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency></code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-starter-sleuth
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">dependencyManagement { <strong>(1)</strong> imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } dependencies { <strong>(2)</strong> compile "org.springframework.cloud:spring-cloud-starter-sleuth" }</code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-starter-sleuth
經過HTTP訪問Zipkin
若是你想要Sleuth和Zipkin只需添加spring-cloud-starter-zipkin
依賴關係。
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencyManagement> <strong>(1)</strong> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <strong>(2)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency></code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-starter-zipkin
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">dependencyManagement { <strong>(1)</strong> imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } dependencies { <strong>(2)</strong> compile "org.springframework.cloud:spring-cloud-starter-zipkin" }</code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-starter-zipkin
經過Spring Cloud Stream使用Zipkin的Sleuth
若是你想要Sleuth和Zipkin只需添加spring-cloud-sleuth-stream
依賴關係。
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencyManagement> <strong>(1)</strong> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <strong>(2)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth-stream</artifactId> </dependency> <dependency> <strong>(3)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <!-- EXAMPLE FOR RABBIT BINDING --> <dependency> <strong>(4)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency></code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-sleuth-stream
將依賴關係添加到spring-cloud-starter-sleuth
中,這樣就能夠下載依賴關係
添加一個粘合劑(例如Rabbit binder)來告訴Spring Cloud Stream應該綁定什麼
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">dependencyManagement { <strong>(1)</strong> imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } dependencies { compile "org.springframework.cloud:spring-cloud-sleuth-stream" <strong>(2)</strong> compile "org.springframework.cloud:spring-cloud-starter-sleuth" <strong>(3)</strong> // Example for Rabbit binding compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit" <strong>(4)</strong> }</code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-sleuth-stream
將依賴關係添加到spring-cloud-starter-sleuth
中,這樣就能夠下載全部依賴關係
添加一個粘合劑(例如Rabbit binder)來告訴Spring Cloud Stream應該綁定什麼
Spring Cloud Sleuth Stream Zipkin收藏家
若是要啓動Spring Cloud Sleuth Stream Zipkin收藏夾,只需添加spring-cloud-sleuth-zipkin-stream
依賴關係便可
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencyManagement> <strong>(1)</strong> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <strong>(2)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId> </dependency> <dependency> <strong>(3)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <!-- EXAMPLE FOR RABBIT BINDING --> <dependency> <strong>(4)</strong> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency></code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-sleuth-zipkin-stream
將依賴關係添加到spring-cloud-starter-sleuth
- 這樣一來,全部的依賴依賴將被下載
添加一個粘合劑(例如Rabbit binder)來告訴Spring Cloud Stream應該綁定什麼
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">dependencyManagement { <strong>(1)</strong> imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE" } } dependencies { compile "org.springframework.cloud:spring-cloud-sleuth-zipkin-stream" <strong>(2)</strong> compile "org.springframework.cloud:spring-cloud-starter-sleuth" <strong>(3)</strong> // Example for Rabbit binding compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit" <strong>(4)</strong> }</code></span></span>
爲了避免本身選擇版本,若是您經過Spring BOM添加依賴關係管理,會更好
將依賴關係添加到spring-cloud-sleuth-zipkin-stream
將依賴關係添加到spring-cloud-starter-sleuth
- 這樣將依賴關係依賴下載
添加一個粘合劑(例如Rabbit binder)來告訴Spring Cloud Stream應該綁定什麼
而後使用@EnableZipkinStreamServer
註釋註釋你的主類:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer; @SpringBootApplication @EnableZipkinStreamServer public class ZipkinStreamServerApplication { public static void main(String[] args) throws Exception { SpringApplication.run(ZipkinStreamServerApplication.class, args); } }</code></span></span>
Marcin Grzejszczak談論Spring Cloud Sleuth和Zipkin
將跟蹤和跨度添加到Slf4J MDC,以便您能夠從日誌聚合器中的給定跟蹤或跨度中提取全部日誌。示例日誌:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ... 2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ... 2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...</span></span>
注意MDC中的[appname,traceId,spanId,exportable]
條目:
spanId - 發生特定操做的ID
appname - 記錄跨度的應用程序的名稱
traceId - 包含跨度的延遲圖的ID
導出 -日誌是否應該被導出到Zipkin與否。你何時但願跨度不能出口?在這種狀況下,你想在Span中包裝一些操做,並將它寫入日誌。
提供對共同分佈式跟蹤數據模型的抽象:trace,spans(造成DAG),註釋,鍵值註釋。鬆散地基於HTrace,但Zipkin(Dapper)兼容。
Sleuth記錄定時信息以輔助延遲分析。使用竊賊,您能夠精肯定位應用程序中的延遲緣由。Sleuth被寫入不會記錄太多,而且不會致使您的生產應用程序崩潰。
傳播有關您的呼叫圖表帶內的結構數據,其他的是帶外。
包括有意見的層次測試,如HTTP
包括採樣策略來管理卷
能夠向Zipkin系統報告查詢和可視化
儀器來自Spring應用程序(servlet過濾器,異步終結點,休息模板,調度操做,消息通道,zuul過濾器,假客戶端)的通用入口和出口點。
Sleuth包括在http或消息傳遞邊界上加入跟蹤的默認邏輯。例如,http傳播經過Zipkin兼容的請求標頭工做。該傳播邏輯是經過SpanInjector
和SpanExtractor
實現來定義和定製的。
Sleuth能夠在進程之間傳播上下文(也稱爲行李)。這意味着若是您設置了Span行李元素,那麼它將經過HTTP或消息傳遞到其餘進程發送到下游。
提供建立/繼續spans並經過註釋添加標籤和日誌的方法。
提供接受/刪除spans的簡單指標。
若是spring-cloud-sleuth-zipkin
,則應用程序將生成並收集Zipkin兼容的跟蹤。默認狀況下,它經過HTTP將其發送到localhost上的Zipkin服務器(端口9411)。使用spring.zipkin.baseUrl
配置服務的位置。
若是spring-cloud-sleuth-stream
,則該應用將經過Spring Cloud Stream生成和收集跟蹤。您的應用程序自動成爲經過您的代理商發送的跟蹤消息的生產者(例如RabbitMQ,Apache Kafka,Redis))。
重要 |
若是使用Zipkin或Stream,請使用spring.sleuth.sampler.percentage (默認0.1,即10%)配置spans的百分比。不然你可能認爲Sleuth不工做,由於它省略了一些spans。 |
注意 |
始終設置SLF4J MDC,而且Logback用戶將當即按照上述示例查看日誌中的跟蹤和跨度ID。其餘日誌記錄系統必須配置本身的格式化程序以得到相同的結果。默認值爲logging.pattern.level 設置爲%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}] (這是回訪用戶的Spring Boot功能)。 這意味着若是您不使用SLF4J,則不會自動應用此模式。 |
在分佈式跟蹤中,數據量可能很是高,所以採樣可能很重要(您一般不須要導出全部spans以得到正在發生的狀況)。Spring Cloud Sleuth具備Sampler
策略,您能夠實現該策略來控制採樣算法。採樣器不會中止生成跨度(相關)ids,可是它們確實阻止了附加和導出的標籤和事件。默認狀況下,您將得到一個策略,若是跨度已經處於活動狀態,則會繼續跟蹤,但新策略始終被標記爲不可導出。若是您的全部應用程序都使用此採樣器運行,您將看到日誌中的跟蹤,但不會在任何遠程存儲中。對於測試,默認值一般是足夠的,若是您僅使用日誌(例如使用ELK聚合器),則多是您須要的。若是要將span數據導出到Zipkin或Spring Cloud Stream,則還有一個AlwaysSampler
導出全部內容,而且PercentageBasedSampler
對spans的固定分數進行採樣。
注意 |
若是您使用spring-cloud-sleuth-zipkin 或spring-cloud-sleuth-stream ,則默認爲PercentageBasedSampler 。您能夠使用spring.sleuth.sampler.percentage 配置導出。經過的價值須要從0.0 到1.0 的雙倍,因此不是百分比。爲了向後兼容性緣由,咱們不會更改屬性名稱。 |
能夠經過建立一個bean定義來安裝採樣器,例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean public Sampler defaultSampler() { return new AlwaysSampler(); }</code></span></span>
提示 |
您能夠將HTTP標頭X-B3-Flags 設置爲1 ,或者在進行消息傳遞時,您能夠將spanFlags 標題設置爲1 。而後,不管採樣決定如何,當前跨度都將被強制輸出。 |
Spring Cloud Sleuth自動爲您的全部Spring應用程序設備,所以您沒必要執行任何操做便可激活它。根據可用的堆棧,例如對於咱們使用Filter
的servlet web應用程序,以及Spring Integration咱們使用ChannelInterceptors
),能夠使用各類技術添加儀器。
您能夠自定義span標籤中使用的鍵。爲了限制跨度數據量,默認狀況下,HTTP請求只會被標記爲少許的元數據,如狀態碼,主機和URL。您能夠經過配置spring.sleuth.keys.http.headers
(頭名稱列表)來添加請求頭。
注意 |
記住,若是有一個Sampler 容許它(默認狀況下沒有,因此沒有意外收集太多數據而沒有配置東西的危險),那麼只會收集和導出標籤。 |
注意 |
目前,Spring Cloud Sleuth中的測試儀器是渴望的 - 這意味着咱們正在積極地嘗試在線程之間傳遞跟蹤上下文。即便在沒有將數據導出到跟蹤系統的狀況下,也會捕獲定時事件。這種作法在未來可能會改變爲在這件事上懶惰。 |
您能夠經過org.springframework.cloud.sleuth.Tracer接口在Span上執行如下操做:
開始 - 當您啓動一個span時,它的名稱被分配,而且記錄開始時間戳。
關閉 - 跨度完成(記錄跨度的結束時間),若是跨度可導出,則它將有資格收集到Zipkin。該跨度也從當前線程中移除。
繼續 - 將建立一個新的跨度實例,而它將是它繼續的一個副本。
分離 - 跨度不會中止或關閉。它只從當前線程中刪除。
使用顯式父項建立 - 您能夠建立一個新的跨度,併爲其設置一個顯式父級
提示 |
Spring爲您建立了一個Tracer 的實例。爲了使用它,你須要的只是自動鏈接它。 |
您能夠使用Tracer界面手動建立spans 。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// Start a span. If there was a span present in this thread it will become // the `newSpan`'s parent. Span newSpan = this.tracer.createSpan("calculateTax"); try { // ... // You can tag a span this.tracer.addTag("taxValue", taxValue); // ... // You can log an event on a span newSpan.logEvent("taxCalculated"); } finally { // Once done remember to close the span. This will allow collecting // the span to send it to Zipkin this.tracer.close(newSpan); }</code></span></span>
在這個例子中,咱們能夠看到如何建立一個新的跨度實例。假設這個線程中已經存在跨度,那麼它將成爲該跨度的父代。
重要 |
建立跨度後始終清潔!若是要將其發送到Zipkin,請不要忘記關閉跨度。 |
重要 |
若是您的span包含的名稱大於50個字符,則該名稱將被截斷爲50個字符。你的名字必須是明確而具體的。大名稱致使延遲問題,有時甚至引起異常。 |
有時你不想建立一個新的跨度,但你想繼續。這種狀況的例子多是(固然這取決於用例):
AOP - 若是在達到方面以前已經建立了一個跨度,則可能不想建立一個新的跨度。
Hystrix - 執行Hystrix命令極可能是當前處理的邏輯部分。實際上,它只是一個技術實現細節,你不必定要反映在跟蹤中做爲一個單獨的存在。
持續的跨度實例等於它繼續的範圍:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">Span continuedSpan = this.tracer.continueSpan(spanToContinue); assertThat(continuedSpan).isEqualTo(spanToContinue);</code></span></span>
要繼續跨度,您能夠使用Tracer界面。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// let's assume that we're in a thread Y and we've received // the `initialSpan` from thread X Span continuedSpan = this.tracer.continueSpan(initialSpan); try { // ... // You can tag a span this.tracer.addTag("taxValue", taxValue); // ... // You can log an event on a span continuedSpan.logEvent("taxCalculated"); } finally { // Once done remember to detach the span. That way you'll // safely remove it from the current thread without closing it this.tracer.detach(continuedSpan); }</code></span></span>
重要 |
建立跨度後始終清潔!若是在一個線程(例如線程X)中開始了某些工做,而且正在等待其餘線程(例如Y,Z)完成,請不要忘記分離跨距。那麼線程Y,Z中的spans在工做結束時應該被分離。當收集結果時,螺紋X中的跨度應該被關閉。 |
您可能想要開始一個新的跨度,並提供該跨度的顯式父級。假設跨度的父項在一個線程中,而且要在另外一個線程中啓動一個新的跨度。Tracer
接口的startSpan
方法是您要查找的方法。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// let's assume that we're in a thread Y and we've received // the `initialSpan` from thread X. `initialSpan` will be the parent // of the `newSpan` Span newSpan = this.tracer.createSpan("calculateCommission", initialSpan); try { // ... // You can tag a span this.tracer.addTag("commissionValue", commissionValue); // ... // You can log an event on a span newSpan.logEvent("commissionCalculated"); } finally { // Once done remember to close the span. This will allow collecting // the span to send it to Zipkin. The tags and events set on the // newSpan will not be present on the parent this.tracer.close(newSpan); }</code></span></span>
重要 |
建立這樣一個跨度後,記得關閉它。不然,您將在您的日誌中看到不少警告,其中有一個事實,即您在當前線程中存在一個跨度,而不是您要關閉的線程。更糟糕的是,您的spans不會正確關閉,所以不會收集到Zipkin。 |
選擇一個跨度名稱不是一件小事。Span名稱應該描述一個操做名稱。名稱應該是低基數(例如不包括標識符)。
因爲有不少儀器儀表在一些跨度名稱將是人爲的:
controller-method-name
當控制器以方法名conrollerMethodName
接收時
async
經過包裝Callable
和Runnable
完成異步操做。
@Scheduled
註釋方法將返回類的簡單名稱。
幸運的是,對於異步處理,您能夠提供明確的命名。
您能夠經過@SpanName
註釋顯式指定該跨度。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpanName("calculateTax") class TaxCountingRunnable implements Runnable { @Override public void run() { // perform logic } }</code></span></span>
在這種狀況下,如下列方式處理時:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">Runnable runnable = new TraceRunnable(tracer, spanNamer, new TaxCountingRunnable()); Future<?> future = executorService.submit(runnable); // ... some additional logic ... future.get();</code></span></span>
該範圍將被命名爲calculateTax
。
爲Runnable
或Callable
建立單獨的課程不多見。一般,建立這些類的匿名實例。若是沒有@SpanName
註釋,咱們將檢查該類是否具備toString()
方法的自定義實現。
因此執行這樣的代碼:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">Runnable runnable = new TraceRunnable(tracer, spanNamer, new Runnable() { @Override public void run() { // perform logic } @Override public String toString() { return "calculateTax"; } }); Future<?> future = executorService.submit(runnable); // ... some additional logic ... future.get();</code></span></span>
將致使建立一個名爲calculateTax
的跨度。
這個功能的主要論據是
api-agnostic意味着與跨度進行合做
使用註釋容許用戶添加到跨度api沒有庫依賴的跨度。這容許Sleuth將其核心api的影響改變爲對用戶代碼的影響較小。
減小基礎跨度做業的表面積。
沒有這個功能,必須使用span api,它具備不正確使用的生命週期命令。經過僅顯示範圍,標籤和日誌功能,用戶能夠協做,而不會意外中斷跨度生命週期。
與運行時生成的代碼協做
使用諸如Spring Data / Feign的庫,在運行時生成接口的實現,從而跨越對象的包裝是乏味的。如今,您能夠經過這些接口的接口和參數提供註釋
若是您真的不想手動建立本地spans,您能夠從@NewSpan
註釋中獲利。此外,咱們還提供@SpanTag
註釋,以自動方式添加標籤。
咱們來看一些使用的例子。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@NewSpan void testMethod();</code></span></span>
註釋沒有任何參數的方法將致使建立名稱將等於註釋方法名稱的新跨度。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@NewSpan("customNameOnTestMethod4") void testMethod4();</code></span></span>
若是您在註釋中提供值(直接或經過name
參數),則建立的範圍將具備提供的值的名稱。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// method declaration @NewSpan(name = "customNameOnTestMethod5") void testMethod5(@SpanTag("testTag") String param); // and method execution this.testBean.testMethod5("test");</code></span></span>
您能夠組合名稱和標籤。咱們來關注後者。在這種狀況下,不管註釋方法的參數運行時值的值如何 - 這將是標記的值。在咱們的示例中,標籤密鑰將爲testTag
,標籤值爲test
。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@NewSpan(name = "customNameOnTestMethod3") @Override public void testMethod3() { }</code></span></span>
您能夠將@NewSpan
註釋放在類和接口上。若是覆蓋接口的方法並提供不一樣的@NewSpan
註釋值,則最具體的一個獲勝(在這種狀況下customNameOnTestMethod3
將被設置)。
若是您只想添加標籤和註釋到現有的跨度,就能夠使用以下所示的@ContinueSpan
註釋。請注意,與@NewSpan
註釋相反,您還能夠經過log
參數添加日誌:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">// method declaration @ContinueSpan(log = "testMethod11") void testMethod11(@SpanTag("testTag11") String param); // method execution this.testBean.testMethod11("test");</code></span></span>
這樣,跨越將繼續下去:
將建立名稱爲testMethod11.before
和testMethod11.after
的日誌
若是拋出異常,也將建立一個日誌testMethod11.afterFailure
將建立密鑰testTag11
和值test
的標籤
有三種不一樣的方法能夠將標籤添加到跨度。全部這些都由SpanTag
註釋控制。優先級是:
嘗試使用TagValueResolver
類型的bean,並提供名稱
若是沒有提供bean名稱,請嘗試評估一個表達式。咱們正在搜索一個TagValueExpressionResolver
bean。默認實現使用SPEL表達式解析。
若是沒有提供任何表達式來評估只返回參數的toString()
值
自定義提取器
如下方法的標籤值將由TagValueResolver
接口的實現來計算。其類名必須做爲resolver
屬性的值傳遞。
有這樣一個註釋的方法:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@NewSpan public void getAnnotationForTagValueResolver(@SpanTag(key = "test", resolver = TagValueResolver.class) String test) { }</code></span></span>
和這樣一個TagValueResolver
bean實現
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean(name = "myCustomTagValueResolver") public TagValueResolver tagValueResolver() { return parameter -> "Value from myCustomTagValueResolver"; }</code></span></span>
將致使標籤值的設置等於Value from myCustomTagValueResolver
。
解決表達式的價值
有這樣一個註釋的方法:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@NewSpan public void getAnnotationForTagValueExpression(@SpanTag(key = "test", expression = "length() + ' characters'") String test) { }</code></span></span>
而且沒有自定義的TagValueExpressionResolver
實現將致使對SPEL表達式的評估,而且將在span上設置值爲4 characters
的標籤。若是要使用其餘表達式解析機制,您能夠建立本身的bean實現。
使用toString方法
有這樣一個註釋的方法:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@NewSpan public void getAnnotationForArgumentToString(@SpanTag("test") Long param) { }</code></span></span>
若是使用值爲15
執行,則將致使設置String值爲"15"
的標記。
感謝SpanInjector
和SpanExtractor
,您能夠自定義spans的建立和傳播方式。
目前有兩種在進程之間傳遞跟蹤信息的內置方式:
經過Spring Integration
經過HTTP
Span ids從Zipkin兼容(B3)頭(Message
或HTTP頭)中提取,以啓動或加入現有跟蹤。跟蹤信息被注入到任何出站請求中,因此下一跳能夠提取它們。
與之前版本的Sleuth相比,重要的變化是Sleuth正在實施Open Tracing的TextMap
概念。在Sleuth,它被稱爲SpanTextMap
。基本上這個想法是經過SpanTextMap
能夠抽象出任何通訊手段(例如消息,http請求等)。這個抽象定義瞭如何將數據插入到載體中以及如何從那裏檢索數據。感謝這樣,若是您想要使用一個使用FooRequest
做爲發送HTTP請求的平均值的新HTTP庫,那麼您必須建立一個SpanTextMap
的實現,它將調用委託給FooRequest
檢索和插入HTTP標頭。
對於Spring Integration,有2個接口負責從Message
建立Span。這些是:
MessagingSpanTextMapExtractor
MessagingSpanTextMapInjector
您能夠經過提供本身的實現來覆蓋它們。
對於HTTP,有2個接口負責從Message
建立Span。這些是:
HttpSpanExtractor
HttpSpanInjector
您能夠經過提供本身的實現來覆蓋它們。
咱們假設,而不是標準的Zipkin兼容的跟蹤HTTP頭名稱
for trace id - correlationId
for span id - mySpanId
這是SpanExtractor
的一個例子
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">static class CustomHttpSpanExtractor implements HttpSpanExtractor { @Override public Span joinTrace(SpanTextMap carrier) { Map<String, String> map = TextMapUtil.asMap(carrier); long traceId = Span.hexToId(map.get("correlationid")); long spanId = Span.hexToId(map.get("myspanid")); // extract all necessary headers Span.SpanBuilder builder = Span.builder().traceId(traceId).spanId(spanId); // build rest of the Span return builder.build(); } } static class CustomHttpSpanInjector implements HttpSpanInjector { @Override public void inject(Span span, SpanTextMap carrier) { carrier.put("correlationId", span.traceIdString()); carrier.put("mySpanId", Span.idToHex(span.getSpanId())); } }</code></span></span>
你能夠這樣註冊:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean HttpSpanInjector customHttpSpanInjector() { return new CustomHttpSpanInjector(); } @Bean HttpSpanExtractor customHttpSpanExtractor() { return new CustomHttpSpanExtractor(); }</code></span></span>
Spring Cloud爲了安全起見,Sleuth不會將跟蹤/跨度相關的標頭添加到Http響應。若是您須要標題,那麼將標題注入Http響應的自定義SpanInjector
,而且能夠使用如下方式添加一個使用此標籤的Servlet過濾器:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">static class CustomHttpServletResponseSpanInjector extends ZipkinHttpSpanInjector { @Override public void inject(Span span, SpanTextMap carrier) { super.inject(span, carrier); carrier.put(Span.TRACE_ID_NAME, span.traceIdString()); carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); } } static class HttpResponseInjectingTraceFilter extends GenericFilterBean { private final Tracer tracer; private final HttpSpanInjector spanInjector; public HttpResponseInjectingTraceFilter(Tracer tracer, HttpSpanInjector spanInjector) { this.tracer = tracer; this.spanInjector = spanInjector; } @Override public void doFilter(ServletRequest request, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; Span currentSpan = this.tracer.getCurrentSpan(); this.spanInjector.inject(currentSpan, new HttpServletResponseTextMap(response)); filterChain.doFilter(request, response); } class HttpServletResponseTextMap implements SpanTextMap { private final HttpServletResponse delegate; HttpServletResponseTextMap(HttpServletResponse delegate) { this.delegate = delegate; } @Override public Iterator<Map.Entry<String, String>> iterator() { Map<String, String> map = new HashMap<>(); for (String header : this.delegate.getHeaderNames()) { map.put(header, this.delegate.getHeader(header)); } return map.entrySet().iterator(); } @Override public void put(String key, String value) { this.delegate.addHeader(key, value); } } }</code></span></span>
你能夠這樣註冊:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean HttpSpanInjector customHttpServletResponseSpanInjector() { return new CustomHttpServletResponseSpanInjector(); } @Bean HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) { return new HttpResponseInjectingTraceFilter(tracer, customHttpServletResponseSpanInjector()); }</code></span></span>
有時你想建立一個手動Span,將一個電話包裹到一個沒有被檢測的外部服務。您能夠作的是建立一個帶有peer.service
標籤的跨度,其中包含要調用的服務的值。下面你能夠看到一個調用Redis的例子,它被包裝在這樣一個跨度裏。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">org.springframework.cloud.sleuth.Span newSpan = tracer.createSpan("redis"); try { newSpan.tag("redis.op", "get"); newSpan.tag("lc", "redis"); newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_SEND); // call redis service e.g // return (SomeObj) redisTemplate.opsForHash().get("MYHASH", someObjKey); } finally { newSpan.tag("peer.service", "redisService"); newSpan.tag("peer.ipv4", "1.2.3.4"); newSpan.tag("peer.port", "1234"); newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_RECV); tracer.close(newSpan); }</code></span></span>
重要 |
記住不要添加peer.service 標籤和SA 標籤!您只需添加peer.service 。 |
默認狀況下,Sleuth假設當您將跨度發送到Zipkin時,您但願跨度的服務名稱等於spring.application.name
值。這並不老是這樣。在某些狀況下,您但願爲您應用程序中的全部spans提供不一樣的服務名稱。要實現這一點,只需將如下屬性傳遞給應用程序便可覆蓋該值(foo
服務名稱的示例):
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring.zipkin.service.name: foo</code></span></span>
爲了定義與特定跨度對應的主機,咱們須要解析主機名和端口。默認方法是從服務器屬性中獲取它。若是因爲某些緣由沒有設置,那麼咱們正在嘗試從網絡接口檢索主機名。
若是您啓用了發現客戶端,而且更願意從服務註冊表中註冊的實例檢索主機地址,那麼您必須設置屬性(適用於基於HTTP和Stream的跨度報告)。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring.zipkin.locator.discovery.enabled: true</code></span></span>
您能夠經過將spring-cloud-sleuth-stream
jar做爲依賴關係來累加併發送跨越Spring Cloud Stream的數據,併爲RabbitMQ或spring-cloud-starter-stream-kafka
添加通道Binder實現(例如spring-cloud-starter-stream-rabbit
)爲Kafka)。經過將spring-cloud-sleuth-stream
jar做爲依賴關係,並添加RabbitMQ或spring-cloud-starter-stream-kafka
的Binder通道spring-cloud-starter-stream-rabbit
來實現{ 22 /} Stream的累積和發送範圍數據。 Kafka)。這將自動將您的應用程序轉換爲有效載荷類型爲Spans
的郵件的製做者。
有一個特殊的便利註釋,用於爲Span數據設置消息使用者,並將其推入Zipkin SpanStore
。這個應用程序
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableZipkinStreamServer public class Consumer { public static void main(String[] args) { SpringApplication.run(Consumer.class, args); } }</code></span></span>
將經過Spring Cloud StreamBinder
(例如RabbitMQ包含spring-cloud-starter-stream-rabbit
)來收聽您提供的任何運輸的Span數據,Redis和Kafka的相似起始者) 。若是添加如下UI依賴關係
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><groupId>io.zipkin.java</groupId> <artifactId>zipkin-autoconfigure-ui</artifactId></code></span></span>
而後,您將有一個Zipkin服務器,您的應用程序 在端口9411上承載UI和API。
默認SpanStore
是內存中的(適合演示,快速入門)。對於更強大的解決方案,您能夠將MySQL和spring-boot-starter-jdbc
添加到類路徑中,並經過配置啓用JDBC SpanStore
,例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">spring: rabbitmq: host: ${RABBIT_HOST:localhost} datasource: schema: classpath:/mysql.sql url: jdbc:mysql://${MYSQL_HOST:localhost}/test username: root password: root # Switch this on to create the schema on startup: initialize: true continueOnError: true sleuth: enabled: false zipkin: storage: type: mysql</code></span></span>
注意 |
@EnableZipkinStreamServer 還用@EnableZipkinServer 註釋,所以該過程還將公開標準的Zipkin服務器端點,以經過HTTP收集spans,並在Zipkin Web UI中進行查詢。 |
也能夠使用spring-cloud-sleuth-stream
並綁定到SleuthSink
來輕鬆實現自定義消費者。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableBinding(SleuthSink.class) @SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class) @MessageEndpoint public class Consumer { @ServiceActivator(inputChannel = SleuthSink.INPUT) public void sink(Spans input) throws Exception { // ... process spans } }</code></span></span>
注意 |
上面的示例消費者應用程序明確排除SleuthStreamAutoConfiguration ,所以它不會向其本身發送消息,但這是可選的(您可能實際上想要將消息跟蹤到消費者應用程序中)。 |
爲了自定義輪詢機制,您能夠建立名稱等於StreamSpanReporter.POLLER
的PollerMetadata
類型的bean。在這裏能夠找到這樣一個配置的例子。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Configuration public static class CustomPollerConfiguration { @Bean(name = StreamSpanReporter.POLLER) PollerMetadata customPoller() { PollerMetadata poller = new PollerMetadata(); poller.setMaxMessagesPerPoll(500); poller.setTrigger(new PeriodicTrigger(5000L)); return poller; } }</code></span></span>
目前Spring Cloud Sleuth註冊了與spans相關的簡單指標。它使用Spring Boot的指標支持 來計算接受和刪除的數量spans。每次發送到Zipkin時,接受的spans的數量將增長。若是出現錯誤,那麼刪除的數字spans將會增長。
若是你在Runnable
或Callable
中包含你的邏輯,就能夠將這些類包裝在他們的Sleuth表明中。
Runnable
的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">Runnable runnable = new Runnable() { @Override public void run() { // do some work } @Override public String toString() { return "spanNameFromToStringMethod"; } }; // Manual `TraceRunnable` creation with explicit "calculateTax" Span name Runnable traceRunnable = new TraceRunnable(tracer, spanNamer, runnable, "calculateTax"); // Wrapping `Runnable` with `Tracer`. The Span name will be taken either from the // `@SpanName` annotation or from `toString` method Runnable traceRunnableFromTracer = tracer.wrap(runnable);</code></span></span>
Callable
的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { return someLogic(); } @Override public String toString() { return "spanNameFromToStringMethod"; } }; // Manual `TraceCallable` creation with explicit "calculateTax" Span name Callable<String> traceCallable = new TraceCallable<>(tracer, spanNamer, callable, "calculateTax"); // Wrapping `Callable` with `Tracer`. The Span name will be taken either from the // `@SpanName` annotation or from `toString` method Callable<String> traceCallableFromTracer = tracer.wrap(callable);</code></span></span>
這樣,您將確保爲每次執行建立並關閉新的Span。
自定義併發策略
咱們正在註冊HystrixConcurrencyStrategy
將全部Callable
實例包裝到他們的Sleuth表明 - TraceCallable
中的定製。該策略是啓動或繼續跨越,這取決於調用Hystrix命令以前跟蹤是否已經進行的事實。要禁用自定義Hystrix併發策略,將spring.sleuth.hystrix.strategy.enabled
設置爲false
。
手動命令設置
假設您有如下HystrixCommand
:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">HystrixCommand<String> hystrixCommand = new HystrixCommand<String>(setter) { @Override protected String run() throws Exception { return someLogic(); } };</code></span></span>
爲了傳遞跟蹤信息,您必須在HystrixCommand
的HystrixCommand
的Sleuth版本中包裝相同的邏輯:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">TraceCommand<String> traceCommand = new TraceCommand<String>(tracer, traceKeys, setter) { @Override public String doRun() throws Exception { return someLogic(); } };</code></span></span>
咱們正在註冊RxJavaSchedulersHook
將全部Action0
實例包裝到他們的Sleuth表明 - TraceAction
中的定製。鉤子起動或繼續一個跨度取決於跟蹤在Action被安排以前是否已經進行的事實。要禁用自定義RxJavaSchedulersHook,將spring.sleuth.rxjava.schedulers.hook.enabled
設置爲false
。
您能夠定義線程名稱的正則表達式列表,您不但願建立一個Span。只需在spring.sleuth.rxjava.schedulers.ignoredthreads
屬性中提供逗號分隔的正則表達式列表。
能夠經過提供值等於false
的spring.sleuth.web.enabled
屬性來禁用此部分的功能。
HTTP過濾器
經過TraceFilter
全部採樣的進入請求致使建立Span。Span的名稱是http:
+發送請求的路徑。例如,若是請求已發送到/foo/bar
,則該名稱將爲http:/foo/bar
。您能夠經過spring.sleuth.web.skipPattern
屬性配置要跳過的URI。若是您在類路徑上有ManagementServerProperties
,則其值contextPath
將附加到提供的跳過模式。
的HandlerInterceptor
因爲咱們但願跨度名稱是精確的,咱們使用的TraceHandlerInterceptor
包裝現有的HandlerInterceptor
,或直接添加到現有的HandlerInterceptors
列表中。TraceHandlerInterceptor
向給定的HttpServletRequest
添加了一個特殊請求屬性。若是TraceFilter
沒有看到此屬性集,它將建立一個「後備」跨度,這是在服務器端建立的一個額外的跨度,以便在UI中正確顯示跟蹤。看到最有可能意味着有一個缺失的儀器。在這種狀況下,請在Spring Cloud Sleuth中提出問題。
異步Servlet支持
若是您的控制器返回Callable
或WebAsyncTask
Spring Cloud,Sleuth將繼續現有的跨度,而不是建立一個新的跨度。
同步休息模板
咱們注入一個RestTemplate
攔截器,確保全部跟蹤信息都傳遞給請求。每次呼叫都會建立一個新的Span。收到迴應後關閉。爲了阻止將spring.sleuth.web.client.enabled
設置爲false
的同步RestTemplate
功能。
重要 |
你必須註冊RestTemplate 做爲一個bean,以便攔截器被注入。若是您使用new 關鍵字建立RestTemplate 實例,那麼該工具將不工做。 |
異步休息模板
重要 |
一個AsyncRestTemplate bean的跟蹤版本是爲您開箱即用的。若是你有本身的bean,你必須用TraceAsyncRestTemplate 表示來包裝它。最好的解決方案是隻定製ClientHttpRequestFactory 和/或AsyncClientHttpRequestFactory 。 若是您有本身的AsyncRestTemplate ,而且您不要包裝您的電話將不會被追蹤。 |
定製儀器設置爲在發送和接收請求時建立和關閉跨度。您能夠經過註冊您的bean來自定義ClientHttpRequestFactory
和AsyncClientHttpRequestFactory
。記住使用跟蹤兼容的實現(例如,不要忘記在TraceAsyncListenableTaskExecutor
中包裝ThreadPoolTaskScheduler
)。自定義請求工廠示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@EnableAutoConfiguration @Configuration public static class TestConfiguration { @Bean ClientHttpRequestFactory mySyncClientFactory() { return new MySyncClientHttpRequestFactory(); } @Bean AsyncClientHttpRequestFactory myAsyncClientFactory() { return new MyAsyncClientHttpRequestFactory(); } }</code></span></span>
將AsyncRestTemplate
功能集spring.sleuth.web.async.client.enabled
阻止爲false
。禁用TraceAsyncClientHttpRequestFactoryWrapper
設置spring.sleuth.web.async.client.factory.enabled
設置爲false
。若是您不想將全部spring.sleuth.web.async.client.template.enabled
false
的AsyncRestClient
建立爲false
。
默認狀況下,Spring Cloud Sleuth經過TraceFeignClientAutoConfiguration
提供與feign的集成。您能夠經過將spring.sleuth.feign.enabled
設置爲false來徹底禁用它。若是這樣作,那麼不會發生Feign相關的儀器。
Feign儀器的一部分是經過FeignBeanPostProcessor
完成的。您能夠經過提供spring.sleuth.feign.processor.enabled
等於false
來禁用它。若是你這樣設置,那麼Spring Cloud Sleuth不會調整你的任何自定義Feign組件。然而,全部默認的工具仍然存在。
@Async註釋方法
在Spring Cloud Sleuth中,咱們正在調用異步相關組件,以便跟蹤信息在線程之間傳遞。您能夠經過將spring.sleuth.async.enabled
的值設置爲false
來禁用此行爲。
若是您使用@Async
註釋方法,那麼咱們將自動建立一個具備如下特徵的新的Span:
Span名稱將是註釋方法名稱
Span將被該方法的類名稱和方法名稱標記
@Scheduled註釋方法
在Spring Cloud Sleuth中,咱們正在調試計劃的方法執行,以便跟蹤信息在線程之間傳遞。您能夠經過將spring.sleuth.scheduled.enabled
的值設置爲false
來禁用此行爲。
若是您使用@Scheduled
註釋方法,那麼咱們將自動建立一個具備如下特徵的新的Span:
Span名稱將是註釋方法名稱
Span將被該方法的類名稱和方法名稱標記
若是要跳過某些@Scheduled
註釋類的Span建立,您能夠使用與@Scheduled
註釋類的徹底限定名稱匹配的正則表達式來設置spring.sleuth.scheduled.skipPattern
。
提示 |
若是您一塊兒使用spring-cloud-sleuth-stream 和spring-cloud-netflix-hystrix-stream ,將爲每一個Hystrix指標建立Span併發送到Zipkin。這多是惱人的。您能夠設置spring.sleuth.scheduled.skipPattern=org.springframework.cloud.netflix.hystrix.stream.HystrixStreamTask |
Executor,ExecutorService和ScheduledExecutorService
咱們提供LazyTraceExecutor
,TraceableExecutorService
和TraceableScheduledExecutorService
。每次提交,調用或調度新任務時,這些實現都將建立Spans。
在這裏,您能夠看到使用CompletableFuture
使用TraceableExecutorService
傳遞跟蹤信息的示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">CompletableFuture<Long> completableFuture = CompletableFuture.supplyAsync(() -> { // perform some logic return 1_000_000L; }, new TraceableExecutorService(executorService, // 'calculateTax' explicitly names the span - this param is optional tracer, traceKeys, spanNamer, "calculateTax"));</code></span></span>
Spring Cloud Sleuth與Spring Integration集成。它建立spans發佈和訂閱事件。要禁用Spring Integration檢測,請將spring.sleuth.integration.enabled
設置爲false。
您能夠提供spring.sleuth.integration.patterns
模式,以明確提供要包括的用於跟蹤的通道的名稱。默認狀況下,全部通道都包含在內。
重要 |
當使用Executor 構建Spring Integration IntegrationFlow 時,請記住使用Executor 的未跟蹤版本。用TraceableExecutorService 裝飾Spring Integration執行者頻道將致使spans被關閉。 |
咱們正在註冊Zuul過濾器來傳播跟蹤信息(請求標頭豐富了跟蹤數據)。要禁用Zuul支持,請將spring.sleuth.zuul.enabled
屬性設置爲false
。
您能夠在Pivotal Web Services中找到部署的運行示例。在如下連接中查看它們:
Dalston.RELEASE
該項目經過自動配置並綁定到Spring環境和其餘Spring編程模型成語,爲Spring Boot應用程序提供Consul集成。經過幾個簡單的註釋,您能夠快速啓用和配置應用程序中的常見模式,並使用基於Consul的組件構建大型分佈式系統。提供的模式包括服務發現,控制總線和配置。智能路由(Zuul)和客戶端負載平衡(Ribbon),斷路器(Hystrix)經過與Spring Cloud Netflix的集成提供。
請參閱安裝文檔獲取有關如何安裝Consul指令。
全部Spring Cloud Consul應用程序必須能夠使用Consul Agent客戶端。默認狀況下,代理客戶端預計位於localhost:8500
。有關如何啓動代理客戶端以及如何鏈接到Consul Agent服務器集羣的詳細信息,請參閱代理文檔。對於開發,安裝領過後,您能夠使用如下命令啓動Consul Agent:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">./src/main/bash/local_run_consul.sh</span></span>
這將啓動端口8500上的服務器模式的代理,其中ui能夠在http:// localhost:8500上找到
服務發現是基於微服務架構的關鍵原則之一。嘗試配置每一個客戶端或某種形式的約定可能很是困難,能夠很是脆弱。Consul經過HTTP API和DNS提供服務發現服務。Spring Cloud Consul利用HTTP API進行服務註冊和發現。這不會阻止非Spring Cloud應用程序利用DNS界面。Consul代理服務器在經過八卦協議進行通訊的集羣中運行,並使用Raft協議協議。
要激活Consul服務發現,請使用組org.springframework.cloud
和artifact id spring-cloud-starter-consul-discovery
的啓動器。有關使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
當客戶端註冊Consul時,它提供有關自身的元數據,如主機和端口,ID,名稱和標籤。默認狀況下會建立一個HTTP 檢查,每隔10秒,Consul命中/health
端點。若是健康檢查失敗,則服務實例被標記爲關鍵。
示例Consul客戶端:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableDiscoveryClient @RestController public class Application { @RequestMapping("/") public String home() { return "Hello world"; } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }</code></span></span>
(即徹底正常的Spring Boot應用程序)。若是Consul客戶端位於localhost:8500
之外的位置,則須要配置來定位客戶端。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: consul: host: localhost port: 8500</span></span>
警告 |
若是您使用Spring Cloud Consul Config,上述值將須要放在bootstrap.yml 而不是application.yml 中。 |
來自Environment
的默認服務名稱,實例ID和端口分別爲${spring.application.name}
,Spring上下文ID和${server.port}
。
@EnableDiscoveryClient
將應用程序設爲Consul「服務」(即註冊本身)和「客戶端」(便可以查詢Consul查找其餘服務)。
Consul實例的運行情況檢查默認爲「/ health」,這是Spring Boot執行器應用程序中有用端點的默認位置。若是您使用非默認上下文路徑或servlet路徑(例如server.servletPath=/foo
)或管理端點路徑(例如management.context-path=/admin
),則須要更改這些,即便是執行器應用程序。也能夠配置Consul用於檢查運行情況端點的間隔。「10s」和「1m」分別表示10秒和1分鐘。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: consul: discovery: healthCheckPath: ${management.context-path}/health healthCheckInterval: 15s</span></span>
元數據和Consul標籤
Consul還沒有支持服務元數據。Spring Cloud的ServiceInstance
有一個Map<String, String> metadata
字段。Spring Cloud Consul使用Consul標籤來近似元數據,直到Consul正式支持元數據。使用key=value
形式的標籤將被分割並分別用做Map
鍵和值。標籤沒有相同的=
符號,將被用做鍵和值二者。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: consul: discovery: tags: foo=bar, baz</span></span>
上述配置將致使具備foo→bar
和baz→baz
的映射。
使Consul實例ID惟一
默認狀況下,一個領事實體註冊了一個等於其Spring應用程序上下文ID的ID。默認狀況下,Spring應用程序上下文ID爲${spring.application.name}:comma,separated,profiles:${server.port}
。在大多數狀況下,這將容許一個服務的多個實例在一臺機器上運行。若是須要進一步的惟一性,使用Spring Cloud,您能夠經過在spring.cloud.consul.discovery.instanceId
中提供惟一的標識來覆蓋此。例如:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: consul: discovery: instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}</span></span>
使用這個元數據和在localhost上部署的多個服務實例,隨機值將在那裏進行,以使實例是惟一的。在Cloudfoundry中,vcap.application.instance_id
將在Spring Boot應用程序中自動填充,所以不須要隨機值。
Spring Cloud支持Feign(REST客戶端構建器),Spring RestTemplate
使用邏輯服務名稱而不是物理URL。
您還能夠使用org.springframework.cloud.client.discovery.DiscoveryClient
,它爲Netflix不特定的發現客戶端提供了一個簡單的API,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@Autowired private DiscoveryClient discoveryClient; public String serviceUrl() { List<ServiceInstance> list = discoveryClient.getInstances("STORES"); if (list != null && list.size() > 0 ) { return list.get(0).getUri(); } return null; }</span></span>
Consul提供了一個用於存儲配置和其餘元數據的鍵/值存儲。Spring Cloud Consul Config是Config Server和Client的替代方案。在特殊的「引導」階段,配置被加載到Spring環境中。默認狀況下,配置存儲在/config
文件夾中。基於應用程序的名稱和模擬解析屬性的Spring Cloud Config順序的活動配置文件建立多個PropertySource
實例。例如,名爲「testApp」的應用程序和「dev」配置文件將建立如下屬性源:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">config/testApp,dev/ config/testApp/ config/application,dev/ config/application/</span></span>
最具體的物業來源位於頂部,底部最不具體。Properties是config/application
文件夾適用於使用consul進行配置的全部應用程序。config/testApp
文件夾中的Properties僅適用於名爲「testApp」的服務實例。
配置當前在應用程序啓動時被讀取。發送HTTP POST到/refresh
將致使配置被從新加載。觀看關鍵價值商店(Consul支持))目前不可能,但未來將是此項目的補充。
要開始使用Consul配置,請使用組org.springframework.cloud
和artifact id spring-cloud-starter-consul-config
的啓動器。有關使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
這將啓用自動配置,將設置Spring Cloud Consul配置。
Consul能夠使用如下屬性定製配置:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: consul: config: enabled: true prefix: configuration defaultContext: apps profileSeparator: '::'</span></span>
enabled
將此值設置爲「false」禁用Consul配置
prefix
設置配置值的基本文件夾
defaultContext
設置全部應用程序使用的文件夾名稱
profileSeparator
設置用於使用配置文件在屬性源中分隔配置文件名稱的分隔符的值
Consul配置觀察功能能夠利用領事看守鑰匙前綴的能力。Config Watch會阻止Consul HTTP API調用,以肯定當前應用程序是否有任何相關配置數據發生更改。若是有新的配置數據,則會發布刷新事件。這至關於調用/refresh
執行器端點。
要更改Config Watch調用的頻率change spring.cloud.consul.config.watch.delay
。默認值爲1000,以毫秒爲單位。
禁用配置觀察集spring.cloud.consul.config.watch.enabled=false
。
與單個鍵/值對相反,能夠更方便地將YBL或Properties格式的屬性塊存儲起來。將spring.cloud.consul.config.format
屬性設置爲YAML
或PROPERTIES
。例如使用YAML:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: consul: config: format: YAML</span></span>
YAML必須在合適的data
鍵中設置。使用鍵上面的默認值將以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">config/testApp,dev/data config/testApp/data config/application,dev/data config/application/data</span></span>
您能夠將YAML文檔存儲在上述任何鍵中。
您能夠使用spring.cloud.consul.config.data-key
更改數據密鑰。
git2consul是一個Consul社區項目,將文件從git存儲庫加載到各個密鑰到Consul。默認狀況下,密鑰的名稱是文件的名稱。YAML和Properties文件分別支持.yml
和.properties
的文件擴展名。將spring.cloud.consul.config.format
屬性設置爲FILES
。例如:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: consul: config: format: FILES</span></span>
給定/config
中的如下密鑰,development
配置文件和應用程序名稱爲foo
:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">.gitignore application.yml bar.properties foo-development.properties foo-production.yml foo.properties master.ref</span></span>
將建立如下屬性來源:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">config/foo-development.properties config/foo.properties config/application.yml</span></span>
每一個鍵的值須要是一個格式正確的YAML或Properties文件。
在某些狀況下(如本地開發或某些測試場景)可能會方便,若是不能配置領事,則不會失敗。在bootstrap.yml
中設置spring.cloud.consul.config.failFast=false
將致使配置模塊記錄一個警告而不是拋出異常。這將容許應用程序繼續正常啓動。
若是您但願您的應用程序啓動時可能偶爾沒法使用代理商,則能夠要求您在發生故障後繼續嘗試。您須要在您的類路徑中添加spring-retry
和spring-boot-starter-aop
。默認行爲是重試6次,初始退避間隔爲1000ms,指數乘數爲1.1,用於後續退避。您能夠使用spring.cloud.consul.retry.*
配置屬性配置這些屬性(和其餘)。這適用於Spring Cloud Consul配置和發現註冊。
提示 |
要徹底控制重試,請使用id爲「consulRetryInterceptor」添加RetryOperationsInterceptor 類型的@Bean 。Spring重試有一個RetryInterceptorBuilder 能夠輕鬆建立一個。 |
要開始使用Consul總線,使用組org.springframework.cloud
和工件id spring-cloud-starter-consul-bus
的起動器。有關使用當前的Spring Cloud發佈列表設置構建系統的詳細信息,請參閱Spring Cloud項目頁面。
有關可用的執行機構端點以及如何發送自定義消息,請參閱Spring Cloud Bus文檔。
應用程序能夠使用Spring Cloud Netflix項目提供的Hystrix斷路器將這個啓動器包含在項目pom.xml:spring-cloud-starter-hystrix
中。Hystrix不依賴於Netflix Discovery Client。@EnableHystrix
註釋應放置在配置類(一般是主類)上。那麼方法能夠用@HystrixCommand
註釋來被斷路器保護。有關詳細信息,請參閱文檔。
Turbine(由Spring Cloud Netflix項目提供))聚合多個實例Hystrix指標流,所以儀表板能夠顯示聚合視圖。Turbine使用DiscoveryClient
接口查找相關實例。要將Turbine與Spring Cloud Consul結合使用,請按如下示例配置Turbine應用程序:
的pom.xml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-turbine</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency></span></span>
請注意,Turbine依賴不是起始者。渦輪啓動器包括對Netflix Eureka的支持。
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.application.name: turbine applications: consulhystrixclient turbine: aggregator: clusterConfig: ${applications} appConfig: ${applications}</span></span>
clusterConfig
和appConfig
部分必須匹配,所以將逗號分隔的服務標識列表放在單獨的配置屬性中是有用的。
Turbine。java的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@EnableTurbine @EnableDiscoveryClient @SpringBootApplication public class Turbine { public static void main(String[] args) { SpringApplication.run(DemoturbinecommonsApplication.class, args); } }</span></span>
該項目經過自動配置並綁定到Spring環境和其餘Spring編程模型成語,爲Spring Boot應用程序提供Zookeeper集成。經過幾個簡單的註釋,您能夠快速啓用和配置應用程序中的常見模式,並使用基於Zookeeper的組件構建大型分佈式系統。提供的模式包括服務發現和配置。智能路由(Zuul)和客戶端負載平衡(Ribbon),斷路器(Hystrix)經過與Spring Cloud Netflix的集成提供。
請參閱安裝文檔獲取有關如何安裝Zookeeper指令。
服務發現是基於微服務架構的關鍵原則之一。嘗試配置每一個客戶端或某種形式的約定可能很是困難,能夠很是脆弱。策展人(一個用於Zookeeper的java庫)經過服務發現擴展提供服務發現服務。Spring Cloud Zookeeper利用此擴展功能進行服務註冊和發現。
包括對org.springframework.cloud:spring-cloud-starter-zookeeper-discovery
的依賴將啓用將設置Spring Cloud Zookeeper發現的自動配置。
注意 |
您仍然須要包含org.springframework.boot:spring-boot-starter-web 的網頁功能。 |
當客戶端註冊Zookeeper時,它提供有關自身的元數據,如主機和端口,ID和名稱。
示例Zookeeper客戶端:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@SpringBootApplication @EnableDiscoveryClient @RestController public class Application { @RequestMapping("/") public String home() { return "Hello world"; } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }</code></span></span>
(即徹底正常的Spring Boot應用程序)。若是Zookeeper位於localhost:2181
之外的地方,則須要配置來定位服務器。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: zookeeper: connect-string: localhost:2181</span></span>
警告 |
若是您使用Spring Cloud Zookeeper配置,上述值將須要放置在bootstrap.yml 而不是application.yml 中。 |
來自Environment
的默認服務名稱,實例ID和端口分別爲${spring.application.name}
,Spring上下文ID和${server.port}
。
@EnableDiscoveryClient
將應用程序同時進入Zookeeper「服務」(即註冊本身)和「客戶端」(便可以查詢Zookeeper查找其餘服務)。
Spring Cloud支持Feign(REST客戶端構建器),還支持Spring RestTemplate
使用邏輯服務名稱而不是物理URL。
您還能夠使用org.springframework.cloud.client.discovery.DiscoveryClient
,它爲Netflix不具體的發現客戶端提供簡單的API,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Autowired private DiscoveryClient discoveryClient; public String serviceUrl() { List<ServiceInstance> list = discoveryClient.getInstances("STORES"); if (list != null && list.size() > 0 ) { return list.get(0).getUri().toString(); } return null; }</code></span></span>
Spring Cloud Netflix提供有用的工具,不管使用哪一種DiscoveryClient
實現。Feign,Turbine,Ribbon和Zuul均與Spring Cloud Zookeeper合做。
Spring Cloud Zookeeper提供Ribbon的ServerList
的實現。當使用spring-cloud-starter-zookeeper-discovery
時,Ribbon默認狀況下自動配置爲使用ZookeeperServerList
。
Spring Cloud Zookeeper實現ServiceRegistry
接口,容許開發人員以編程方式註冊任意服務。
ServiceInstanceRegistration
類提供builder()
方法來建立能夠由ServiceRegistry
使用的Registration
對象。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Autowired private ZookeeperServiceRegistry serviceRegistry; public void registerThings() { ZookeeperRegistration registration = ServiceInstanceRegistration.builder() .defaultUriSpec() .address("anyUrl") .port(10) .name("/a/b/c/d/anotherservice") .build(); this.serviceRegistry.register(registration); }</code></span></span>
Netflix Eureka支持在服務器上註冊的實例是OUT_OF_SERVICE
,而不是做爲活動服務實例返回。這對於諸如藍色/綠色部署之類的行爲很是有用。策展人服務發現配方不支持此行爲。利用靈活的有效載荷,讓Spring Cloud Zookeeper經過更新一些特定的元數據,而後對Ribbon ZookeeperServerList
中的元數據進行過濾來實現OUT_OF_SERVICE
。ZookeeperServerList
過濾出不等於UP
的全部非空實例狀態。若是實例狀態字段爲空,則向後兼容性被認爲是UP
。將實例POST OUT_OF_SERVICE
的狀態更改成ServiceRegistry
實例狀態執行器端點。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">---- $ echo -n OUT_OF_SERVICE | http POST http://localhost:8081/service-registry/instance-status ----</span></span>
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">NOTE: The above example uses the `http` command from https://httpie.org</span></span>
Spring Cloud Zookeeper可讓您提供應用程序的依賴關係做爲屬性。做爲依賴關係,您能夠了解Zookeeper中註冊的其餘應用程序,您能夠經過Feign(REST客戶端構建器)以及Spring RestTemplate
呼叫。
您還能夠從Zookeeper依賴關係觀察者功能中受益,這些功能可以讓您控制和監視依賴關係的狀態,並決定如何處理。
包括對org.springframework.cloud:spring-cloud-starter-zookeeper-discovery
的依賴將啓用將自動配置Spring Cloud Zookeeper依賴關係的自動配置。
若是您必須正確設置spring.cloud.zookeeper.dependencies
部分 - 請查看後續部分以獲取更多詳細信息,而後該功能處於活動狀態
即便您在屬性中提供依賴關係,也能夠關閉依賴關係。只需將屬性spring.cloud.zookeeper.dependency.enabled
設置爲false(默認爲true
)。
咱們來仔細看一下依賴關係表示的例子:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring.application.name: yourServiceName spring.cloud.zookeeper: dependencies: newsletter: path: /path/where/newsletter/has/registered/in/zookeeper loadBalancerType: ROUND_ROBIN contentTypeTemplate: application/vnd.newsletter.$version+json version: v1 headers: header1: - value1 header2: - value2 required: false stubs: org.springframework:foo:stubs mailing: path: /path/where/mailing/has/registered/in/zookeeper loadBalancerType: ROUND_ROBIN contentTypeTemplate: application/vnd.mailing.$version+json version: v1 required: true</span></span>
如今讓咱們一個接一個地遍歷依賴的每一個部分。根屬性名稱爲spring.cloud.zookeeper.dependencies
。
別名
在根屬性下面,因爲Ribbon的限制,必須經過別名來表示每一個依賴關係(應用程序ID必須放在URL中,所以您不能傳遞任何複雜的路徑,如/ foo / bar / name )。別名將是您將使用的名稱,而不是DiscoveryClient
,Feign
或RestTemplate
的serviceId。
在上述例子中,別名是newsletter
和mailing
。使用newsletter
的Feign使用示例爲:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@FeignClient("newsletter") public interface NewsletterService { @RequestMapping(method = RequestMethod.GET, value = "/newsletter") String getNewsletters(); }</span></span>
路徑
表明path
yaml屬性。
Path是根據Zookeeper註冊依賴關係的路徑。像Ribbon以前提交的URL,所以這個路徑不符合其要求。這就是爲何Spring Cloud Zookeeper將別名映射到正確的路徑。
負載平衡器類型
表明loadBalancerType
yaml屬性。
若是您知道在調用此特定依賴關係時必須應用什麼樣的負載平衡策略,那麼您能夠在yaml文件中提供它,並將自動應用。您能夠選擇如下負載平衡策略之一
STICKY - 一旦選擇了該實例將始終被調用
隨機 - 隨機選擇一個實例
ROUND_ROBIN - 一遍又一遍地迭代實例
Content-Type模板和版本
表明contentTypeTemplate
和version
yaml屬性。
若是您經過Content-Type
標題版本您的api,那麼您不想將此標頭添加到您的每一個請求中。另外若是你想調用一個新版本的API,你不想漫遊你的代碼,以增長API版本。這就是爲何您能夠提供contentTypeTemplate
特殊$version
佔位符的緣由。該佔位符將由version
yaml屬性的值填充。咱們來看一個例子。
擁有如下contentTypeTemplate
:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">application/vnd.newsletter.$version+json</span></span>
和如下version
:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">v1</span></span>
將致使爲每一個請求設置Content-Type
標題:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">application/vnd.newsletter.v1+json</span></span>
默認標題
由yaml表明headers
映射
有時每次調用依賴關係都須要設置一些默認標頭。爲了避免在代碼中這樣作,您能夠在yaml文件中設置它們。擁有如下headers
部分:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">headers: Accept: - text/html - application/xhtml+xml Cache-Control: - no-cache</span></span>
結果在您的HTTP請求中添加適當的值列表的Accept
和Cache-Control
標頭。
強制依賴
在yaml中由required
屬性表示
若是您的一個依賴關係在您的應用程序啓動時須要啓動並運行,則能夠在yaml文件中設置required: true
屬性。
若是您的應用程序沒法在引導期間本地化所需的依賴關係,則會拋出異常,而且Spring上下文將沒法設置。換句話說,若是Zookeeper中沒有註冊所需的依賴關係,則您的應用程序將沒法啓動。
您能夠在如下部分閱讀有關Spring Cloud Zookeeper存在檢查器的更多信息。
存根
您能夠爲包含依賴關係的存根的JAR提供冒號分隔路徑。例
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>stubs: org.springframework:foo:stubs</code></span></span>
意味着對於特定的依賴關係能夠在下面找到:
groupId:org.springframework
artifactId:foo
分類器:stubs
- 這是默認值
這實際上等於
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>stubs: org.springframework:foo</code></span></span>
由於stubs
是默認分類器。
有一些屬性能夠設置爲啓用/禁用Zookeeper依賴關係功能的部分。
spring.cloud.zookeeper.dependencies
- 若是您不設置此屬性,則不會從Zookeeper依賴關係中受益
spring.cloud.zookeeper.dependency.ribbon.enabled
(默認狀況下啓用) - Ribbon須要顯式的全局配置或特定的依賴關係。經過打開此屬性,運行時負載平衡策略解決是可能的,您能夠從Zookeeper依賴關係的loadBalancerType
部分獲利。須要此屬性的配置具備LoadBalancerClient
的實現,委託給下一個子彈中的ILoadBalancer
spring.cloud.zookeeper.dependency.ribbon.loadbalancer
(默認狀況下啓用) - 感謝這個屬性,自定義ILoadBalancer
知道傳遞給Ribbon的URI部分實際上多是必須被解析爲Zookeeper。沒有此屬性,您將沒法在嵌套路徑下注冊應用程序。
spring.cloud.zookeeper.dependency.headers.enabled
(默認狀況下啓用) - 此屬性註冊這樣的一個RibbonClient
,它會自動附加適當的頭文件和內容類型,其中包含依賴關係配置中顯示的版本。沒有這兩個參數的設置將不會運行。
spring.cloud.zookeeper.dependency.resttemplate.enabled
(默認狀況下啓用) - 啓用時將修改@LoadBalanced
註釋的RestTemplate
的請求標頭,以便它經過依賴關係配置中設置的版本的標題和內容類型。Wihtout這兩個參數的設置將沒法運行。
依賴關係觀察器機制容許您將偵聽器註冊到依賴關係中。功能其實是Observator
模式的實現。當依賴關係改變其狀態(UP或DOWN)時,能夠應用一些自定義邏輯。
Spring Cloud Zookeeper依賴關係功能須要啓用從依賴關係觀察器機制中獲利。
爲了註冊一個監聽器,你必須實現一個接口org.springframework.cloud.zookeeper.discovery.watcher.DependencyWatcherListener
,並將其註冊爲一個bean。該界面爲您提供了一種方法:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"> void stateChanged(String dependencyName, DependencyState newState);</span></span>
若是要爲特定的依賴關係註冊一個偵聽器,那麼dependencyName
將是具體實現的鑑別器。newState
將提供您的依賴關係是否已更改成CONNECTED
或DISCONNECTED
的信息。
綁定與依賴關係觀察器是稱爲存在檢查器的功能。它容許您在啓動應用程序時提供自定義行爲,以根據您的依賴關係的狀態做出反應。
抽象org.springframework.cloud.zookeeper.discovery.watcher.presence.DependencyPresenceOnStartupVerifier
類的默認實現是org.springframework.cloud.zookeeper.discovery.watcher.presence.DefaultDependencyPresenceOnStartupVerifier
,它以如下方式工做。
若是依賴關係標記爲咱們required
,而且不在Zookeeper中,則在引導時,您的應用程序將拋出異常並關閉
若是依賴關係不是required
,org.springframework.cloud.zookeeper.discovery.watcher.presence.LogMissingDependencyChecker
將在WARN
級別上記錄該應用程序
功能能夠被覆蓋,由於只有當沒有DependencyPresenceOnStartupVerifier
的bean時纔會註冊DefaultDependencyPresenceOnStartupVerifier
。
Zookeeper提供了一個分層命名空間,容許客戶端存儲任意數據,如配置數據。Spring Cloud Zookeeper Config是Config Server和Client的替代方案。在特殊的「引導」階段,配置被加載到Spring環境中。默認狀況下,配置存儲在/config
命名空間中。根據應用程序的名稱和模擬解析屬性的Spring Cloud Config順序的活動配置文件,建立多個PropertySource
實例。例如,名爲「testApp」的應用程序和「dev」配置文件將建立如下屬性源:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">config/testApp,dev config/testApp config/application,dev config/application</span></span>
最具體的物業來源位於頂部,底部最不具體。Properties是config/application
命名空間適用於使用zookeeper進行配置的全部應用程序。config/testApp
命名空間中的Properties僅適用於名爲「testApp」的服務實例。
配置當前在應用程序啓動時被讀取。發送HTTP POST到/refresh
將致使從新加載配置。觀看配置命名空間(Zookeeper支持))目前還沒有實現,但未來將會添加到此項目中。
包括對org.springframework.cloud:spring-cloud-starter-zookeeper-config
的依賴將啓用將配置Spring Cloud Zookeeper配置的自動配置。
Zookeeper能夠使用如下屬性自定義配置:
bootstrap.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">spring: cloud: zookeeper: config: enabled: true root: configuration defaultContext: apps profileSeparator: '::'</span></span>
enabled
將此值設置爲「false」將禁用Zookeeper配置
root
設置配置值的基本命名空間
defaultContext
設置全部應用程序使用的名稱
profileSeparator
設置用於使用配置文件在屬性源中分隔配置文件名稱的分隔符的值
spring-cloud.adoc中的未解決的指令 - include :: / Users / sgibb / workspace / spring / spring-cloud-samples / scripts / docs /../ cli / docs / src / main / asciidoc / spring-cloud-cli。 ADOC []
Spring Cloud Security提供了一組用於構建安全應用程序和服務的原語,最小化。能夠從外部(或集中)高度配置的聲明式模型適用於一般使用中央契約管理服務的大型合做遠程組件系統的實現。在像Cloud Foundry這樣的服務平臺上也很容易使用。基於Spring Boot和Spring安全性OAuth2,咱們能夠快速建立實現常見模式的系統,如單點登陸,令牌中繼和令牌交換。
注意 |
Spring Cloud根據非限制性Apache 2.0許可證發佈。若是您想爲文檔的這一部分作出貢獻,或者發現錯誤,請在github中找到項目中的源代碼和問題跟蹤器。 |
這是一個具備HTTP基自己份驗證和單個用戶賬戶的Spring Cloud「Hello World」應用程序
app.groovy
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Grab('spring-boot-starter-security') @Controller class Application { @RequestMapping('/') String home() { 'Hello World' } }</code></span></span>
您能夠使用spring run app.groovy
運行它,並觀察日誌的密碼(用戶名爲「用戶」)。到目前爲止,這只是一個Spring Boot應用程序的默認設置。
這是一個使用OAuth2 SSO的Spring Cloud應用程序:
app.groovy
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Controller @EnableOAuth2Sso class Application { @RequestMapping('/') String home() { 'Hello World' } }</code></span></span>
指出不一樣?這個應用程序的行爲實際上與以前的同樣,由於它不知道它是OAuth2的信譽。
您能夠很容易地在github註冊一個應用程序,因此若是你想要一個生產應用程序在你本身的域上嘗試。若是您很樂意在localhost:8080上測試,那麼請在應用程序配置中設置這些屬性:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">security: oauth2: client: clientId: bd1c0a783ccdd1c9b9e4 clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1 accessTokenUri: https://github.com/login/oauth/access_token userAuthorizationUri: https://github.com/login/oauth/authorize clientAuthenticationScheme: form resource: userInfoUri: https://api.github.com/user preferTokenInfo: false</code></span></span>
運行上面的應用程序,它將重定向到github進行受權。若是您已經登陸github,您甚至不會注意到它已經經過身份驗證。只有您的應用程序在8080端口上運行,這些憑據纔會起做用。
要限制客戶端在獲取訪問令牌時要求的範圍,您能夠設置security.oauth2.client.scope
(逗號分隔或YAML中的數組)。默認狀況下,做用域爲空,由受權服務器肯定默認值是什麼,一般取決於客戶端註冊中的設置。
注意 |
上面的例子都是Groovy腳本。若是要在Java(或Groovy)中編寫相同的代碼,則須要將Spring Security OAuth2添加到類路徑中(例如,請參閱此處的 示例)。 |
您要使用OAuth2令牌保護API資源?這是一個簡單的例子(與上面的客戶端配對):
app.groovy
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Grab('spring-cloud-starter-security') @RestController @EnableResourceServer class Application { @RequestMapping('/') def home() { [message: 'Hello World'] } }</code></span></span>
和
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">security: oauth2: resource: userInfoUri: https://api.github.com/user preferTokenInfo: false</code></span></span>
注意 |
全部OAuth2 SSO和資源服務器功能在版本1.3中移動到Spring Boot。您能夠在Spring Boot用戶指南中找到文檔 。 |
令牌中繼是OAuth2消費者充當客戶端,並將傳入令牌轉發到外發資源請求。消費者能夠是純客戶端(如SSO應用程序)或資源服務器。
客戶端令牌中繼
若是您的應用是面向OAuth2客戶端的用戶(即聲明爲@EnableOAuth2Sso
或@EnableOAuth2Client
),那麼它的請求範圍爲spring security的OAuth2ClientContext
。您能夠今後上下文和自動連線OAuth2ProtectedResourceDetails
建立本身的OAuth2RestTemplate
,而後上下文將始終向下轉發訪問令牌,若是過時則自動刷新訪問令牌。(這些是Spring安全和Spring Boot的功能。)
注意 |
若是您使用client_credentials 令牌,則Spring Boot(1.4.1)不會自動建立OAuth2ProtectedResourceDetails 。在這種狀況下,您須要建立本身的ClientCredentialsResourceDetails 並使用@ConfigurationProperties("security.oauth2.client") 進行配置。 |
客戶端令牌中繼在Zuul代理
若是您的應用程式還有 Spring Cloud Zuul嵌入式反向代理(使用@EnableZuulProxy
),那麼您能夠要求將OAuth2訪問令牌轉發到其正在代理的服務。所以,上述的SSO應用程序能夠簡單地加強:
app.groovy
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Controller @EnableOAuth2Sso @EnableZuulProxy class Application { }</code></span></span>
而且(除了將用戶登陸並抓取令牌以外)將下載的身份驗證令牌傳遞到/proxy/*
服務。若是這些服務是用@EnableResourceServer
實現的,那麼他們將在正確的標題中得到一個有效的標記。
它是如何工做的?@EnableOAuth2Sso
註釋引入spring-cloud-starter-security
(您能夠在傳統應用程序中手動執行),而這又會觸發一個ZuulFilter
的自動配置,該屬性自己被激活,由於Zuul在classpath(經過@EnableZuulProxy
)。該 過濾器 僅從當前已認證的用戶提取訪問令牌,並將其放入下游請求的請求頭中。
資源服務器令牌中繼
若是您的應用有@EnableResourceServer
,您可能但願將傳入令牌下載到其餘服務。若是您使用RestTemplate
聯繫下游服務,那麼這只是如何使用正確的上下文建立模板的問題。
若是您的服務使用UserInfoTokenServices
驗證傳入令牌(即正在使用security.oauth2.user-info-uri
配置)),則能夠使用自動連線OAuth2ClientContext
建立OAuth2RestTemplate
(將由身份驗證過程以前它遇到後端代碼)。相等(使用Spring Boot 1.4),您能夠在配置中注入UserInfoRestTemplateFactory
並抓取其中的OAuth2RestTemplate
。例如:
MyConfiguration.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Bean public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) { return factory.getUserInfoRestTemplate(); }</code></span></span>
而後,此休息模板將具備由身份驗證過濾器使用的OAuth2ClientContext
(請求做用域)相同,所以您能夠使用它來發送具備相同訪問令牌的請求。
若是您的應用沒有使用UserInfoTokenServices
,但仍然是客戶端(即聲明@EnableOAuth2Client
或@EnableOAuth2Sso
),則使用Spring安全雲任何OAuth2RestOperations
,用戶從@Autowired
@OAuth2Context
也會轉發令牌。此功能默認實現爲MVC處理程序攔截器,所以它僅適用於Spring MVC。若是您不使用MVC,能夠使用包含AccessTokenContextRelay
的自定義過濾器或AOP攔截器來提供相同的功能。
如下是一個基本示例,顯示了使用其餘地方建立的自動連線休息模板(「foo.com」是一個資源服務器,接受與周圍應用程序相同的令牌):
MyController.java
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Autowired private OAuth2RestOperations restTemplate; @RequestMapping("/relay") public String relay() { ResponseEntity<String> response = restTemplate.getForEntity("https://foo.com/bar", String.class); return "Success! (" + response.getBody() + ")"; }</code></span></span>
若是您不想轉發令牌(這是一個有效的選擇,由於您可能但願以本身的身份而不是向您發送令牌的客戶端),那麼您只須要建立本身的OAuth2Context
的自動裝配默認值。
Feign客戶端也會選擇使用OAuth2ClientContext
的攔截器,若是它是可用的,那麼他們還應該在RestTemplate
將要執行的令牌中繼。
您能夠經過proxy.auth.*
設置控制@EnableZuulProxy
下游的受權行爲。例:
application.yml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">proxy: auth: routes: customers: oauth2 stores: passthru recommendations: none</code></span></span>
在此示例中,「客戶」服務獲取OAuth2令牌中繼,「存儲」服務獲取傳遞(受權頭只是經過下游),「建議」服務已刪除其受權頭。若是有令牌可用,則默認行爲是執行令牌中繼,不然爲passthru。
有關詳細信息,請參閱 ProxyAuthenticationProperties。
Cloudfoundry的Spring Cloud能夠輕鬆地在Cloud Foundry(平臺即服務)中運行 Spring Cloud應用程序 。Cloud Foundry有一個「服務」的概念,它是「綁定」到應用程序的中間件,本質上爲其提供包含憑據的環境變量(例如,用於服務的位置和用戶名)。
spring-cloud-cloudfoundry-web
項目爲Cloud Foundry中的webapps的一些加強功能提供基本支持:自動綁定到單點登陸服務,並可選擇啓用粘性路由進行發現。
spring-cloud-cloudfoundry-discovery
項目提供Spring Cloud Commons DiscoveryClient
的實施,所以您能夠@EnableDiscoveryClient
並將您的憑據提供爲spring.cloud.cloudfoundry.discovery.[email,password]
,而後直接或經過LoadBalancerClient
使用DiscoveryClient
/}(若是您沒有鏈接到Pivotal Web Services,則也爲*.url
)。
第一次使用它時,發現客戶端可能很慢,由於它必須從Cloud Foundry獲取訪問令牌。
如下是Cloud Foundry發現的Spring Cloud應用程序:
app.groovy
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Grab('org.springframework.cloud:spring-cloud-cloudfoundry') @RestController @EnableDiscoveryClient class Application { @Autowired DiscoveryClient client @RequestMapping('/') String home() { 'Hello from ' + client.getLocalServiceInstance() } }</code></span></span>
若是您運行它沒有任何服務綁定:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">$ spring jar app.jar app.groovy $ cf push -p app.jar</span></span>
它將在主頁中顯示其應用程序名稱。
DiscoveryClient
能夠根據身份驗證的憑據列出空間中的全部應用程序,其中的空間默認爲客戶端運行的空間(若是有的話)。若是組織和空間都不配置,則它們將根據Cloud Foundry中的用戶配置文件進行默認。
注意 |
全部OAuth2 SSO和資源服務器功能在版本1.3中移動到Spring Boot。您能夠在Spring Boot用戶指南中找到文檔 。 |
該項目提供從CloudFoundry服務憑據到Spring Boot功能的自動綁定。若是您有一個稱爲「sso」的CloudFoundry服務,例如,使用包含「client_id」,「client_secret」和「auth_domain」的憑據,它將自動綁定到您使用@EnableOAuth2Sso
啓用的Spring OAuth2客戶端來自Spring Boot)。能夠使用spring.oauth2.sso.serviceId
對服務的名稱進行參數化。
文獻做者:Adam Dudczak,MathiasDüsterhöft,Marcin Grzejszczak,Dennis Kieselhorst,JakubKubryński,Karol Lassak,Olga Maciaszek-Sharma,MariuszSmykuła,Dave Syer
Dalston.RELEASE
您始終須要的是將新功能推向分佈式系統中的新應用程序或服務的信心。該項目爲Spring應用程序中的消費者驅動Contracts和服務架構提供支持,涵蓋了一系列用於編寫測試的選項,將其做爲資產發佈,聲稱生產者和消費者保留合同用於HTTP和消息的交互。
模塊讓您有可能使用 WireMock使用嵌入在Spring Boot應用的「環境」服務器不一樣的服務器。查看 樣品 瞭解更多詳情。
重要 |
Spring Cloud發佈列表BOM導入spring-cloud-contract-dependencies ,這反過來又排除了WireMock所需的依賴關係。這可能致使一種狀況,即便你不使用Spring Cloud Contract,那麼你的依賴將會受到影響。 |
若是您有一個使用Tomcat做爲嵌入式服務器的Spring Boot應用程序(默認爲spring-boot-starter-web
)),那麼您能夠簡單地將spring-cloud-contract-wiremock
添加到類路徑中並添加@AutoConfigureWireMock
,以即可以在測試中使用Wiremock。Wiremock做爲存根服務器運行,您能夠使用Java API或經過靜態JSON聲明來註冊存根行爲,做爲測試的一部分。這是一個簡單的例子:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @AutoConfigureWireMock(port = 0) public class WiremockForDocsTests { // A service that calls out over HTTP @Autowired private Service service; // Using the WireMock APIs in the normal way: @Test public void contextLoads() throws Exception { // Stubbing WireMock stubFor(get(urlEqualTo("/resource")) .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); // We're asserting if WireMock responded properly assertThat(this.service.go()).isEqualTo("Hello World!"); } }</code></span></span>
要使用@AutoConfigureWireMock(port=9999)
(例如)啓動不一樣端口上的存根服務器,而且對於隨機端口使用值0.存根服務器端口將在測試應用程序上下文中綁定爲「wiremock.server.port」。使用@AutoConfigureWireMock
將一個類型爲WiremockConfiguration
的bean添加到測試應用程序上下文中,它將被緩存在具備相同上下文的方法和類之間,就像通常的Spring集成測試同樣。
若是您使用@AutoConfigureWireMock
,則它將從文件系統或類路徑註冊WireMock JSON存根,默認狀況下爲file:src/test/resources/mappings
。您能夠使用註釋中的stubs
屬性自定義位置,這能夠是資源模式(ant-style)或目錄,在這種狀況下,附加*/.json
。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)">@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureWireMock(stubs="classpath:/stubs") public class WiremockImportApplicationTests { @Autowired private Service service; @Test public void contextLoads() throws Exception { assertThat(this.service.go()).isEqualTo("Hello World!"); } }</span></span>
注意 |
實際上,WireMock老是從src/test/resources/mappings 中加載映射以及 stubs屬性中的自定義位置。要更改此行爲,您還必須以下所述指定文件根。 |
WireMock能夠從類路徑或文件系統上的文件讀取響應體。在這種狀況下,您將在JSON DSL中看到響應具備「bodyFileName」而不是(文字)「body」。默認狀況下,相對於根目錄src/test/resources/__files
解析文件。要自定義此位置,您能夠將@AutoConfigureWireMock
註釋中的files
屬性設置爲父目錄的位置(即,位置__files
是子目錄)。您能夠使用Spring資源符號來引用file:…
或classpath:…
位置(但不支持通用URL)。能夠給出值列表,而且WireMock將在須要查找響應體時解析存在的第一個文件。
注意 |
當配置files 根時,它會影響自動加載存根(它們來自稱爲「映射」的子目錄中的根位置)。files 的值對從stubs 屬性明確加載的存根沒有影響。 |
對於更常規的WireMock體驗,使用JUnit @Rules
啓動和中止服務器,只需使用WireMockSpring
便利類來獲取Options
實例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class WiremockForDocsClassRuleTests { // Start WireMock on some dynamic port // for some reason `dynamicPort()` is not working properly @ClassRule public static WireMockClassRule wiremock = new WireMockClassRule( WireMockSpring.options().dynamicPort()); // A service that calls out over HTTP to localhost:${wiremock.port} @Autowired private Service service; // Using the WireMock APIs in the normal way: @Test public void contextLoads() throws Exception { // Stubbing WireMock wiremock.stubFor(get(urlEqualTo("/resource")) .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); // We're asserting if WireMock responded properly assertThat(this.service.go()).isEqualTo("Hello World!"); } }</code></span></span>
使用@ClassRule
表示服務器將在此類中的全部方法後關閉。
Spring Cloud Contract提供了一個方便的類,能夠將JSON WireMock存根加載到Spring MockRestServiceServer
中。如下是一個例子:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.NONE) public class WiremockForDocsMockServerApplicationTests { @Autowired private RestTemplate restTemplate; @Autowired private Service service; @Test public void contextLoads() throws Exception { // will read stubs classpath MockRestServiceServer server = WireMockRestServiceServer.with(this.restTemplate) .baseUrl("http://example.org").stubs("classpath:/stubs/resource.json") .build(); // We're asserting if WireMock responded properly assertThat(this.service.go()).isEqualTo("Hello World"); server.verify(); } }</code></span></span>
baseUrl
前面是全部模擬調用,stubs()
方法將一個存根路徑資源模式做爲參數。因此在這個例子中,/stubs/resource.json
定義的存根被加載到模擬服務器中,因此若是RestTemplate
被要求訪問http://example.org/
,那麼它將獲得所聲明的響應。能夠指定多個存根模式,每一個能夠是一個目錄(對於全部「.json」的遞歸列表)或一個固定的文件名(如上例所示)或一個螞蟻樣式模式。JSON格式是一般的WireMock格式,您能夠在WireMock網站上閱讀。
目前,咱們支持Tomcat,Jetty和Undertow做爲Spring Boot嵌入式服務器,而Wiremock自己對特定版本的Jetty(目前爲9.2)具備「本機」支持。要使用本地Jetty,您須要添加本機線程依賴關係,並排除Spring Boot容器(若是有的話)。
Spring RestDocs可用於爲具備Spring MockMvc或RestEasy的HTTP API生成文檔(例如,asciidoctor格式)。在生成API文檔的同時,還能夠使用Spring Cloud Contract WireMock生成WireMock存根。只需編寫正常的RestDocs測試用例,並使用@AutoConfigureRestDocs
在restdocs輸出目錄中自動存儲存根。例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureRestDocs(outputDir = "target/snippets") @AutoConfigureMockMvc public class ApplicationTests { @Autowired private MockMvc mockMvc; @Test public void contextLoads() throws Exception { mockMvc.perform(get("/resource")) .andExpect(content().string("Hello World")) .andDo(document("resource")); } }</code></span></span>
今後測試將在「target / snippets / stubs / resource.json」上生成一個WireMock存根。它將全部GET請求與「/ resource」路徑相匹配。
沒有任何其餘配置,這將建立一個存根與HTTP方法的請求匹配器和除「主機」和「內容長度」以外的全部頭。爲了更準確地匹配請求,例如要匹配POST或PUT的正文,咱們須要明確建立一個請求匹配器。這將作兩件事情:1)建立一個只匹配您指定的方式的存根,2)斷言測試用例中的請求也匹配相同的條件。
主要的入口點是WireMockRestDocs.verify()
,能夠替代document()
便利方法。例如:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureRestDocs(outputDir = "target/snippets") @AutoConfigureMockMvc public class ApplicationTests { @Autowired private MockMvc mockMvc; @Test public void contextLoads() throws Exception { mockMvc.perform(post("/resource") .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) .andExpect(status().isOk()) .andDo(verify().jsonPath("$.id") .stub("resource")); } }</code></span></span>
因此這個合同是說:任何有效的POST與「id」字段將獲得與本測試相同的響應。您能夠未來電連接到.jsonPath()
以添加其餘匹配器。若是 您不熟悉JayWay文檔,能夠幫助您加快JSON路徑的速度。
您也能夠使用WireMock API來驗證請求是否與建立的存根匹配,而不是使用jsonPath
和contentType
方法。例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Test public void contextLoads() throws Exception { mockMvc.perform(post("/resource") .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) .andExpect(status().isOk()) .andDo(verify() .wiremock(WireMock.post( urlPathEquals("/resource")) .withRequestBody(matchingJsonPath("$.id")) .stub("post-resource")); }</code></span></span>
WireMock API是豐富的 - 您能夠經過正則表達式以及json路徑來匹配頭文件,查詢參數和請求正文,所以這能夠用於建立具備更普遍參數的存根。上面的例子會生成一個這樣的stub:
後resource.json
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "request" : { "url" : "/resource", "method" : "POST", "bodyPatterns" : [ { "matchesJsonPath" : "$.id" }] }, "response" : { "status" : 200, "body" : "Hello World", "headers" : { "X-Application-Context" : "application:-1", "Content-Type" : "text/plain" } } }</code></span></span>
注意 |
您能夠使用wiremock() 方法或jsonPath() 和contentType() 方法建立請求匹配器,但不能同時使用二者。 |
在消費方面,假設上面生成的resource.json
能夠在類路徑中使用,您能夠使用WireMock以多種不一樣的方式建立一個存根,其中包括上述使用@AutoConfigureWireMock(stubs="classpath:resource.json")
的描述。
能夠使用Spring RestDocs生成的另外一件事是Spring Cloud Contract DSL文件和文檔。若是您將其與Spring Cloud WireMock相結合,那麼您將得到合同和存根。
提示 |
您可能會想知道爲何該功能在WireMock模塊中。來想想,它確實有道理,由於只生成合同而且不生成存根就沒有意義。這就是爲何咱們建議作這兩個。 |
咱們來想象下面的測試:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java"> this.mockMvc.perform(post("/foo") .accept(MediaType.APPLICATION_PDF) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content("{\"foo\": 23 }")) .andExpect(status().isOk()) .andExpect(content().string("bar")) // first WireMock .andDo(WireMockRestDocs.verify() .jsonPath("$[?(@.foo >= 20)]") .contentType(MediaType.valueOf("application/json")) .stub("shouldGrantABeerIfOldEnough")) // then Contract DSL documentation .andDo(document("index", SpringCloudContractRestDocs.dslContract()));</code></span></span>
這將致使在上一節中介紹的存根的建立,合同將被生成和文檔文件。
合同將被稱爲index.groovy
,看起來更像是這樣。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">import org.springframework.cloud.contract.spec.Contract Contract.make { request { method 'POST' url 'http://localhost:8080/foo' body(''' {"foo": 23 } ''') headers { header('''Accept''', '''application/json''') header('''Content-Type''', '''application/json''') header('''Host''', '''localhost:8080''') header('''Content-Length''', '''12''') } } response { status 200 body(''' bar ''') headers { header('''Content-Type''', '''application/json;charset=UTF-8''') header('''Content-Length''', '''3''') } testMatchers { jsonPath('$[?(@.foo >= 20)]', byType()) } } }</code></span></span>
生成的文檔(Asciidoc的示例)將包含格式化的合同(此文件的位置將爲index/dsl-contract.adoc
)。
重要 |
1.1.0版中已棄用的Accurest項目的文檔可在此處獲取。 |
提示 |
Accurest項目最初是由Marcin Grzejszczak和Jakub Kubrynski(codearte.io) |
只是爲了簡短說明 - Spring Cloud Contract驗證程序是一種可以啓用消費者驅動合同(CDC)開發基於JVM的應用程序的工具。它與合同定義語言(DSL)一塊兒提供。合同定義用於生成如下資源:
在客戶端代碼(客戶端測試)上進行集成測試時,WireMock將使用JSON存根定義。測試代碼仍然須要手動編寫,測試數據由Spring Cloud Contract驗證器生成。
消息傳遞路由,若是你使用一個。咱們正在與Spring Integration,Spring Cloud Stream,Spring AMQP和Apache Camel進行整合。然而,您能夠設置本身的集成,若是你想
驗收測試(在JUnit或Spock中)用於驗證API的服務器端實現是否符合合同(服務器測試)。徹底測試由Spring Cloud Contract驗證器生成。
Spring Cloud Contract驗證者將TDD移動到軟件體系結構的層次。
Spring Cloud Contract視頻
您能夠查看華沙JUG關於Spring Cloud Contract的視頻: 點擊此處查看視頻
爲何?
讓咱們假設咱們有一個由多個微服務組成的系統:
測試問題
若是咱們想測試應用程序在左上角,若是它能夠與其餘服務通訊,那麼咱們能夠作兩件事之一:
部署全部微服務器並執行端到端測試
模擬其餘微型服務單元/集成測試
二者都有其優勢,但也有不少缺點。咱們來關注後者。
部署全部微服務器並執行端到端測試
優勢:
模擬生產
測試服務之間的真實溝通
缺點:
要測試一個微服務器,咱們將不得不部署6個微服務器,幾個數據庫等。
將進行測試的環境將被鎖定用於一套測試(即沒有人可以在此期間運行測試)。
長跑
很是遲的反饋
很是難調試
模擬其餘微型服務單元/集成測試
優勢:
很是快的反饋
沒有基礎架構要求
缺點:
服務的實現者建立存根,所以它們可能與現實無關
您能夠經過測試和生產不合格進行生產
爲了解決上述問題,Spring Cloud Contract Stub Runner的驗證器被建立。他們的主要思想是給你很是快的反饋,而不須要創建整個微服務的世界。
若是您在存根上工做,那麼您須要的惟一應用是應用程序直接使用的應用程序。
Spring Cloud Contract驗證者肯定您使用的存根是由您正在調用的服務建立的。此外,若是您能夠使用它們,這意味着它們是針對生產者的一方進行測試的。換句話說 - 你能夠信任這些存根。
目的
Spring Cloud Contract驗證器與Stub Runner的主要目的是:
確保WireMock / Messaging存根(在開發客戶端時使用)正在徹底實際執行服務器端實現,
推廣ATDD方法和微服務架構風格,
提供一種發佈雙方當即可見的合同變動的方法,
生成服務器端使用的樣板測試代碼。
重要 |
Spring Cloud Contract驗證者的目的不是開始在合同中編寫業務功能。咱們假設咱們有一個欺詐檢查的商業用例。若是一個用戶由於100個不一樣的緣由而被欺騙,咱們假設你會建立2個合同。一個爲正面,一個爲負面欺詐案。合同測試用於測試應用程序之間的合同,而不是模擬完整行爲。 |
客戶端
在測試期間,您但願啓動並運行一個模擬服務Y的WireMock實例/消息傳遞路由。您但願爲該實例提供適當的存根定義。該存根定義將須要有效,而且也應在服務器端可重用。
總結:在這方面,在存根定義中,您能夠使用模式進行請求存根,並須要確切的響應值。
服務器端
做爲開發您的存根的服務Y,您須要確保它實際上相似於您的具體實現。您不能以某種方式存在您的存根行爲,而且您的生產應用程序以不一樣的方式運行。
這就是爲何會生成提供的存根驗收測試,這將確保您的應用程序的行爲與您在存根中定義的相同。
總結:在這方面,在存根定義中,您須要精確的值做爲請求,並能夠使用模式/方法進行響應驗證。
逐步向CDC指導
舉一個欺詐檢測和貸款發行流程的例子。業務情景是這樣的,咱們想向人們發放貸款,但不但願他們從咱們那裏偷錢。目前咱們的系統實施給你們貸款。
假設Loan Issuance
是Fraud Detection
服務器的客戶端。在目前的衝刺中,咱們須要開發一個新的功能 - 若是客戶想要借到太多的錢,那麼咱們將他們標記爲欺詐。
技術說明 - 欺詐檢測將具備工件ID http-server
,貸款發行http-client
,二者都具備組ID com.example
。
社會聲明 - 客戶端和服務器開發團隊都須要直接溝通,並在整個過程當中討論變動。CDC是關於溝通的。
提示 |
在這種狀況下,合同的全部權在生產者方面。這意味着物理上全部的合同都存在於生產者存儲庫中 |
技術說明
若是使用SNAPSHOT / 里程碑 / 版本候選版本,請將如下部分添加到您的
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories></code></span></span>
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">repositories { mavenCentral() mavenLocal() maven { url "http://repo.spring.io/snapshot" } maven { url "http://repo.spring.io/milestone" } maven { url "http://repo.spring.io/release" } }</code></span></span>
消費方(貸款發行)
做爲貸款發行服務(欺詐檢測服務器的消費者)的開發人員:
經過對您的功能進行測試開始作TDD
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">@Test public void shouldBeRejectedDueToAbnormalLoanAmount() { // given: LoanApplication application = new LoanApplication(new Client("1234567890"), 99999); // when: LoanApplicationResult loanApplication = service.loanApplication(application); // then: assertThat(loanApplication.getLoanApplicationStatus()) .isEqualTo(LoanApplicationStatus.LOAN_APPLICATION_REJECTED); assertThat(loanApplication.getRejectionReason()).isEqualTo("Amount too high"); }</code></span></span>
咱們剛剛寫了一個關於咱們新功能的測試。若是收到大額的貸款申請,咱們應該拒絕有一些描述的貸款申請。
寫入缺乏的實現
在某些時間點,您須要向欺詐檢測服務發送請求。咱們假設咱們要發送包含客戶端ID和要從咱們借款的金額的請求。咱們想經過PUT
方法將其發送到/fraudcheck
網址。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">ResponseEntity<FraudServiceResponse> response = restTemplate.exchange("http://localhost:" + port + "/fraudcheck", HttpMethod.PUT, new HttpEntity<>(request, httpHeaders), FraudServiceResponse.class);</code></span></span>
爲了簡單起見,咱們已將8080
的欺詐檢測服務端口硬編碼,咱們的應用程序正在8090
上運行。
若是咱們開始寫測試,顯然會由於端口8080
上沒有運行而打斷。
在本地克隆欺詐檢測服務存儲庫
咱們將開始玩服務器端的合同。這就是爲何咱們須要先克隆它。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">git clone https://your-git-server.com/server-side.git local-http-server-repo</code></span></span>
在欺詐檢測服務的回購中本地定義合同
做爲消費者,咱們須要肯定咱們想要實現的目標。咱們須要制定咱們的指望。這就是爲何咱們寫下面的合同。
重要 |
咱們將合同放在src/test/resources/contract/fraud 下。fraud 文件夾是重要的,由於咱們將在生產者的測試基類中引用該文件夾。 |
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">package contracts org.springframework.cloud.contract.spec.Contract.make { request { // (1) method 'PUT' // (2) url '/fraudcheck' // (3) body([ // (4) clientId: $(regex('[0-9]{10}')), loanAmount: 99999 ]) headers { // (5) contentType('application/vnd.fraud.v1+json') } } response { // (6) status 200 // (7) body([ // (8) fraudCheckStatus: "FRAUD", rejectionReason: "Amount too high" ]) headers { // (9) contentType('application/vnd.fraud.v1+json') } } } /* Since we don't want to force on the user to hardcode values of fields that are dynamic (timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's value in a `$(...)` or `value(...)` and provide a dynamic value of a field then the concrete value will be generated for you. If you want to be really explicit about which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation. That way what's present in the `consumer` section will end up in the produced stub. What's there in the `producer` will end up in the autogenerated test. If you provide only the regular expression side without the concrete value then Spring Cloud Contract will generate one for you. From the Consumer perspective, when shooting a request in the integration test: (1) - If the consumer sends a request (2) - With the "PUT" method (3) - to the URL "/fraudcheck" (4) - with the JSON body that * has a field `clientId` that matches a regular expression `[0-9]{10}` * has a field `loanAmount` that is equal to `99999` (5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json` (6) - then the response will be sent with (7) - status equal `200` (8) - and JSON body equal to { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" } (9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json` From the Producer perspective, in the autogenerated producer-side test: (1) - A request will be sent to the producer (2) - With the "PUT" method (3) - to the URL "/fraudcheck" (4) - with the JSON body that * has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}` * has a field `loanAmount` that is equal to `99999` (5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json` (6) - then the test will assert if the response has been sent with (7) - status equal `200` (8) - and JSON body equal to { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" } (9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*` */</code></span></span>
合同使用靜態類型的Groovy DSL編寫。你可能想知道這些value(client(…), server(…))
部分是什麼。經過使用這種符號Spring Cloud Contract,您能夠定義動態的JSON / URL /等的部分。在標識符或時間戳的狀況下,您不想硬編碼一個值。你想容許一些不一樣的值範圍。這就是爲何對於消費者端,您能夠設置與這些值匹配的正則表達式。您能夠經過地圖符號或帶插值的String來提供身體。 有關更多信息,請參閱文檔。咱們強烈推薦使用地圖符號!
提示 |
瞭解地圖符號設置合同很是重要。請閱讀有關JSON的 Groovy文檔 |
上述合同是雙方達成的協議:
若是發送了HTTP請求
端點/fraudcheck
上的方法PUT
與clientPesel
的正則表達式[0-9]{10}
和loanAmount
等於99999
的JSON體
而且標題Content-Type
等於application/vnd.fraud.v1+json
那麼HTTP響應將被髮送給消費者
狀態爲200
包含JSON體,其中包含值爲FRAUD
的fraudCheckStatus
字段和值爲Amount too high
的rejectionReason
字段
和一個值爲application/vnd.fraud.v1+json
的Content-Type
標頭
一旦咱們準備好在集成測試中實際檢查API,咱們須要在本地安裝存根
添加Spring Cloud Contract驗證程序插件
咱們能夠添加Maven或Gradle插件 - 在這個例子中,咱們將展現如何添加Maven。首先咱們須要添加Spring Cloud Contract
BOM。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-dependencies.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement></code></span></span>
接下來,Spring Cloud Contract Verifier
Maven插件
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <packageWithBaseClasses>com.example.fraud</packageWithBaseClasses> </configuration> </plugin></code></span></span>
自添加插件後,咱們將從提供的合同中得到Spring Cloud Contract Verifier
功能:
生成並運行測試
生產並安裝存根
咱們不想生成測試,由於咱們做爲消費者,只想玩短線。這就是爲何咱們須要跳過測試生成和執行。當咱們執行:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">cd local-http-server-repo ./mvnw clean install -DskipTests</code></span></span>
在日誌中,咱們將看到以下:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server --- [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar [INFO] [INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server --- [INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar [INFO] [INFO] --- spring-boot-maven-plugin:1.5.0.BUILD-SNAPSHOT:repackage (default) @ http-server --- [INFO] [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server --- [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar [INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom [INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar</code></span></span>
這條線是很是重要的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar</code></span></span>
確認http-server
的存根已安裝在本地存儲庫中。
運行集成測試
爲了從自動存根下載的Spring Cloud Contract Stub Runner功能中獲利,您必須在咱們的消費者端項目(Loan Application service
)中執行如下操做。
添加Spring Cloud Contract
BOM
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-dependencies.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement></code></span></span>
將依賴關係添加到Spring Cloud Contract Stub Runner
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-stub-runner</artifactId> <scope>test</scope> </dependency></code></span></span>
用@AutoConfigureStubRunner
標註你的測試課程。在註釋中,提供Stub Runner下載協做者存根的組ID和工件ID。離線工做開關還能夠離線使用協做者(可選步驟)。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment=WebEnvironment.NONE) @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, workOffline = true) @DirtiesContext public class LoanApplicationServiceTests {</code></span></span>
如今若是你運行測試你會看到這樣的:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version 2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT 2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories [] 2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar 2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar] 2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265] 2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}]</code></span></span>
這意味着Stub Runner找到了您的存根,併爲具備組ID爲com.example
,artifact id http-server
,版本爲0.0.1-SNAPSHOT
的存根和stubs
分類器的端口8080
。
檔案公關
咱們到如今爲止是一個迭代的過程。咱們能夠玩合同,安裝在本地,在消費者身邊工做,直到咱們對合同感到滿意。
一旦咱們對結果感到滿意,測試經過將PR發佈到服務器端。目前消費者方面的工做已經完成。
生產者方(欺詐檢測服務器)
做爲欺詐檢測服務器(貸款發行服務的服務器)的開發人員:
初步實施
做爲提醒,您能夠看到初始實現
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RequestMapping( value = "/fraudcheck", method = PUT, consumes = FRAUD_SERVICE_JSON_VERSION_1, produces = FRAUD_SERVICE_JSON_VERSION_1) public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) { return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON); }</code></span></span>
接管公關
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">git checkout -b contract-change-pr master git pull https://your-git-server.com/server-side-fork.git contract-change-pr</code></span></span>
您必須添加自動生成測試所需的依賴關係
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-verifier</artifactId> <scope>test</scope> </dependency></code></span></span>
在Maven插件的配置中,咱們傳遞了packageWithBaseClasses
屬性
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <packageWithBaseClasses>com.example.fraud</packageWithBaseClasses> </configuration> </plugin></code></span></span>
重要 |
咱們決定使用「約定」命名,方法是設置packageWithBaseClasses 屬性。這意味着最後的兩個包將被組合成基本測試類的名稱。在咱們這個例子中,這些合約是src/test/resources/contract/fraud 。因爲咱們沒有從contracts 文件夾開始有2個包,咱們只挑選一個是fraud 。咱們正在添加Base 後綴,咱們正在大寫fraud 。這給了咱們FraudBase 測試類名稱。 |
這是由於全部生成的測試都會擴展該類。在那裏你能夠設置你的Spring上下文或任何須要的。在咱們的例子中,咱們使用Rest Assured MVC來啓動服務器端FraudDetectionController
。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">package com.example.fraud; import org.junit.Before; import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc; public class FraudBase { @Before public void setup() { RestAssuredMockMvc.standaloneSetup(new FraudDetectionController(), new FraudStatsController(stubbedStatsProvider())); } private StatsProvider stubbedStatsProvider() { return fraudType -> { switch (fraudType) { case DRUNKS: return 100; case ALL: return 200; } return 0; }; } public void assertThatRejectionReasonIsNull(Object rejectionReason) { assert rejectionReason == null; } }</code></span></span>
如今,若是你運行./mvnw clean install
,你會獲得這樣的sth:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">Results : Tests in error: ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed...</code></span></span>
這是由於您有一個新的合同,從中生成測試,而且因爲您還沒有實現該功能而失敗。自動生成測試將以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Test public void validate_shouldMarkClientAsFraud() throws Exception { // given: MockMvcRequestSpecification request = given() .header("Content-Type", "application/vnd.fraud.v1+json") .body("{\"clientPesel\":\"1234567890\",\"loanAmount\":99999}"); // when: ResponseOptions response = given().spec(request) .put("/fraudcheck"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("fraudCheckStatus").matches("[A-Z]{5}"); assertThatJson(parsedJson).field("rejectionReason").isEqualTo("Amount too high"); }</code></span></span>
您能夠看到value(consumer(…), producer(…))
塊中存在的全部producer()
部分合同注入測試。
重要的是在生產者方面,咱們也在作TDD。咱們有一個測試形式的指望。此測試正在向咱們本身的應用程序拍攝一個在合同中定義的URL,標題和主體的請求。它也期待響應中很是精確地定義的值。換句話說,您是red
green
和refactor
的red
部分。將red
轉換爲green
的時間。
寫入缺乏的實現
如今,因爲咱們如今預期的輸入和預期的輸出是什麼,咱們來寫這個缺乏的實現。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RequestMapping( value = "/fraudcheck", method = PUT, consumes = FRAUD_SERVICE_JSON_VERSION_1, produces = FRAUD_SERVICE_JSON_VERSION_1) public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) { if (amountGreaterThanThreshold(fraudCheck)) { return new FraudCheckResult(FraudCheckStatus.FRAUD, AMOUNT_TOO_HIGH); } return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON); }</code></span></span>
若是再次執行./mvnw clean install
,測試將經過。因爲Spring Cloud Contract Verifier
插件將測試添加到generated-test-sources
,您能夠從IDE中實際運行這些測試。
部署你的應用程序
完成工做後,如今是部署變動的時候了。首先合併分支
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">git checkout master git merge --no-ff contract-change-pr git push origin master</code></span></span>
那麼咱們假設你的CI將像./mvnw clean deploy
同樣運行,它將發佈應用程序和存根工件。
消費方(貸款發行)最後一步
做爲貸款發行服務(欺詐檢測服務器的消費者)的開發人員:
合併分支到主
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">git checkout master git merge --no-ff contract-change-pr</code></span></span>
在線工做
如今,您能夠禁用Spring Cloud Contract Stub Runner廣告的離線工做,以提供存儲庫與存根的位置。此時,服務器端的存根將自動從Nexus / Artifactory下載。您能夠關閉註釋中的workOffline
參數的值。在下面你能夠看到一個經過改變屬性實現相同的例子。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yaml">stubrunner: ids: 'com.example:http-server-dsl:+:stubs:8080' repositoryRoot: http://repo.spring.io/libs-snapshot</code></span></span>
就是這樣!
依賴
添加依賴關係的最佳方法是使用正確的starter
依賴關係。
對於stub-runner
使用spring-cloud-starter-stub-runner
,當您使用插件時,只需添加spring-cloud-starter-contract-verifier
。
附加連接
如下能夠找到與Spring Cloud Contract驗證器和Stub Runner相關的一些資源。注意,有些能夠過期,由於Spring Cloud Contract驗證程序項目正在不斷髮展。
閱讀
樣品
在這裏能夠找到一些樣品。
爲何使用Spring Cloud Contract驗證器而不是X?
目前Spring Cloud Contract驗證器是基於JVM的工具。所以,當您已經爲JVM建立軟件時,多是您的第一選擇。這個項目有不少很是有趣的功能,但特別是其中一些絕對讓Spring Cloud Contract Verifier在消費者驅動合同(CDC)工具的「市場」上脫穎而出。許多最有趣的是:
CDC能夠經過消息傳遞
清晰易用,靜態DSL
能夠將當前的JSON文件粘貼到合同中,而且僅編輯其元素
從定義的合同自動生成測試
Stub Runner功能 - 存根在運行時自動從Nexus / Artifactory下載
Spring Cloud集成 - 集成測試不須要發現服務
這個值是(consumer(),producer())?
與存根相關的最大挑戰之一是可重用性。只有若是他們可以被普遍使用,他們是否會服務於他們的目的。一般使得難點是請求/響應元素的硬編碼值。例如日期或ids。想象下面的JSON請求
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "time" : "2016-10-10 20:10:15", "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a", "body" : "foo" }</code></span></span>
和JSON響應
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "time" : "2016-10-10 21:10:15", "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051", "body" : "bar" }</code></span></span>
想象一下,經過更改系統中的時鐘或提供數據提供者的存根實現,設置time
字段的正確值(咱們假設這個內容是由數據庫生成的)所需的痛苦。這一樣涉及到稱爲id
的字段。你會建立一個UUID發生器的stubbed實現?沒有意義
因此做爲一個消費者,你想發送一個匹配任何形式的時間或任何UUID的請求。這樣,您的系統將照常工做 - 將生成數據,您沒必要將任何東西存入。假設在上述JSON的狀況下,最重要的部分是body
字段。您能夠專一於其餘領域,並提供匹配。換句話說,你想要的存根是這樣工做的:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "time" : "SOMETHING THAT MATCHES TIME", "id" : "SOMETHING THAT MATCHES UUID", "body" : "foo" }</code></span></span>
就響應做爲消費者而言,您須要具備可操做性的具體價值。因此這樣的JSON是有效的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "time" : "2016-10-10 21:10:15", "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051", "body" : "bar" }</code></span></span>
從前面的部分能夠看出,咱們從合同中產生測試。因此從生產者的角度看,狀況看起來差異很大。咱們正在解析提供的合同,在測試中咱們想向您的端點發送一個真正的請求。所以,對於請求的生產者來講,咱們不能進行任何匹配。咱們須要具體的價值觀,使製片人的後臺可以工做。這樣的JSON將是一個有效的:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "time" : "2016-10-10 20:10:15", "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a", "body" : "foo" }</code></span></span>
另外一方面,從合同的有效性的角度來看,響應不必定必須包含time
或id
的具體值。假設您在生產者方面產生這些 - 再次,您必須作不少樁,以確保始終返回相同的值。這就是爲何從生產者那邊你可能想要的是如下回應:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-json">{ "time" : "SOMETHING THAT MATCHES TIME", "id" : "SOMETHING THAT MATCHES UUID", "body" : "bar" }</code></span></span>
那麼您如何才能爲消費者提供一次匹配,併爲生產者提供具體的價值,反之亦然?在Spring Cloud Contract中,咱們容許您提供動態值。這意味着通訊雙方可能有所不一樣。你能夠傳遞值:
能夠經過value
方法
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">value(consumer(...), producer(...)) value(stub(...), test(...)) value(client(...), server(...))</code></span></span>
或使用$()
方法
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">$(consumer(...), producer(...)) $(stub(...), test(...)) $(client(...), server(...))</code></span></span>
您能夠在Contract DSL部分閱讀更多信息。
調用value()
或$()
告訴Spring Cloud Contract您將傳遞一個動態值。在consumer()
方法中,傳遞消費者端(在生成的存根)中應該使用的值。在producer()
方法中,傳遞應在生產者端使用的值(在生成的測試中)。
提示 |
若是一方面你已經經過了正則表達式,而你沒有經過另外一方,那麼對方就會自動生成。 |
大多數狀況下,您將使用該方法與regex
輔助方法。例如consumer(regex('[0-9]{10}'))
。
總而言之,上述情景的合同看起來或多或少是這樣的(正則表達式的時間和UUID被簡化,極可能是無效的,可是咱們但願在這個例子中保持很簡單):
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">org.springframework.cloud.contract.spec.Contract.make { request { method 'GET' url '/someUrl' body([ time : value(consumer(regex('[0-9]{4}-[0-9]{2}-[0-9]{2} [0-2][0-9]-[0-5][0-9]-[0-5][0-9]')), id: value(consumer(regex('[0-9a-zA-z]{8}-[0-9a-zA-z]{4}-[0-9a-zA-z]{4}-[0-9a-zA-z]{12}')) body: "foo" ]) } response { status 200 body([ time : value(producer(regex('[0-9]{4}-[0-9]{2}-[0-9]{2} [0-2][0-9]-[0-5][0-9]-[0-5][0-9]')), id: value([producer(regex('[0-9a-zA-z]{8}-[0-9a-zA-z]{4}-[0-9a-zA-z]{4}-[0-9a-zA-z]{12}')) body: "bar" ]) } }</code></span></span>
重要 |
請閱讀與JSON相關的Groovy文檔,以瞭解如何正確構建請求/響應實體。 |
如何作Stubs版本控制?
API版本控制
讓咱們嘗試回答一個真正意義上的版本控制的問題。若是你指的是API版本,那麼有不一樣的方法。
使用超媒體,連接,不要經過任何方式版本您的API
經過標題/網址傳遞版本
我不會試圖回答一個方法更好的問題。不管適合您的需求,並容許您創造商業價值應被挑選。
假設你作你的API版本。在這種狀況下,您應該提供與您支持的許多版本同樣多的合同。您能夠爲每一個版本建立一個子文件夾,或將其附加到合同名稱 - 不管如何適合您。
JAR版本控制
若是經過版本控制是指包含存根的JAR的版本,那麼基本上有兩種主要方法。
假設您正在進行連續交付/部署,這意味着您每次經過管道生成新版本的jar時,該jar能夠隨時進行生產。例如你的jar版本看起來像這樣(它創建在20.10.2016在20:15:21):
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">1.0.0.20161020-201521-RELEASE</code></span></span>
在這種狀況下,您生成的存根jar將看起來像這樣。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">1.0.0.20161020-201521-RELEASE-stubs.jar</code></span></span>
在這種狀況下,您應該在application.yml
或@AutoConfigureStubRunner
內引用存根提供最新版本的存根。您能夠經過傳遞+
號來作到這一點。例
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})</code></span></span>
若是版本控制是固定的(例如1.0.4.RELEASE
或2.1.1
),則必須設置jar版本的具體值。示例2.1.1。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})</code></span></span>
開發者或生產者存根
您能夠操做分類器,以針對其餘服務的存根或部署到生產的存根的當前開發版原本運行測試。若是您在構建生產部署以後,使用prod-stubs
分類器來更改構建部署,那麼您能夠在一個案例中使用dev stub運行測試,另外一個則使用prod stub進行測試。
使用開發版存根的測試示例
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})</code></span></span>
使用生產版本的存根的測試示例
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})</code></span></span>
您也能夠經過部署管道中的屬性傳遞這些值。
共同回購合同
存儲合同之外的另外一種方法是將它們保存在一個共同的地方。它可能與消費者沒法克隆生產者代碼的安全問題相關。另外,若是您在一個地方保留合約,那麼做爲生產者,您將知道有多少消費者,以及您的本地變動會消費哪些消費者。
回購結構
假設咱們有一個座標爲com.example:server
和3個消費者的生產者:client1
,client2
,client3
。而後在具備常見合同的存儲庫中,您將具備如下設置(您能夠在此處查看:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">├── com │ └── example │ └── server │ ├── client1 │ │ └── expectation.groovy │ ├── client2 │ │ └── expectation.groovy │ ├── client3 │ │ └── expectation.groovy │ └── pom.xml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── assembly └── contracts.xml</code></span></span>
您能夠看到下面的斜線分隔的groupid /
工件id文件夾(com/example/server
),您對3個消費者(client1
,client2
和client3
)有指望。指望是本文檔中描述的標準Groovy DSL合同文件。該存儲庫必須生成一個將一對一映射到回收內容的JAR文件。
server
文件夾內的pom.xml
示例。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Server Stubs</name> <description>POM used to install locally stubs for consumer side</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.0.BUILD-SNAPSHOT</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <spring-cloud-contract.version>1.1.0.BUILD-SNAPSHOT</spring-cloud-contract.version> <spring-cloud-dependencies.version>Dalston.BUILD-SNAPSHOT</spring-cloud-dependencies.version> <excludeBuildFolders>true</excludeBuildFolders> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-dependencies.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <!-- By default it would search under src/test/resources/ --> <contractsDirectory>${project.basedir}</contractsDirectory> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> </project></code></span></span>
你能夠看到除了Spring Cloud Contract Maven插件以外沒有依賴關係。這些垃圾是消費者運行mvn clean install -DskipTests
來本地安裝生產者項目的存根的必要條件。
根文件夾中的pom.xml
能夠以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.standalone</groupId> <artifactId>contracts</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Contracts</name> <description>Contains all the Spring Cloud Contracts, well, contracts. JAR used by the producers to generate tests and stubs</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>contracts</id> <phase>prepare-package</phase> <goals> <goal>single</goal> </goals> <configuration> <attach>true</attach> <descriptor>${basedir}/src/assembly/contracts.xml</descriptor> <!-- If you want an explicit classifier remove the following line --> <appendAssemblyId>false</appendAssemblyId> </configuration> </execution> </executions> </plugin> </plugins> </build> </project></code></span></span>
它正在使用程序集插件來構建全部合同的JAR。此類設置的示例以下:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> <id>project</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>${project.basedir}</directory> <outputDirectory>/</outputDirectory> <useDefaultExcludes>true</useDefaultExcludes> <excludes> <exclude>**/${project.build.directory}/**</exclude> <exclude>mvnw</exclude> <exclude>mvnw.cmd</exclude> <exclude>.mvn/**</exclude> <exclude>src/**</exclude> </excludes> </fileSet> </fileSets> </assembly></code></span></span>
工做流程
工做流程將與Step by step guide to CDC
中提供的工做流程相似。惟一的區別是生產者再也不擁有合同。因此消費者和生產者必須在共同的倉庫中處理共同的合同。
消費者
當消費者但願脫機工做,而不是克隆生產者代碼時,消費者團隊克隆了公用存儲庫,轉到所需的生產者的文件夾(例如com/example/server
),並運行mvn clean install -DskipTests
在本地安裝存根從合同轉換。
提示 |
您須要在本地安裝Maven |
製片人
做爲一個生產者,足以改變Spring Cloud Contract驗證器來提供包含合同的URL和依賴關係:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <configuration> <contractsRepositoryUrl>http://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl> <contractDependency> <groupId>com.example.standalone</groupId> <artifactId>contracts</artifactId> </contractDependency> </configuration> </plugin></code></span></span>
使用此設置,將從http://link/to/your/nexus/or/artifactory/or/sth
下載具備groupid com.example.standalone
和artifactid contracts
的JAR。而後將在本地臨時文件夾中解壓縮,並將com/example/server
下的合同做爲用於生成測試和存根的選擇。因爲這個慣例,生產者團隊將會知道當一些不兼容的更改完成時,哪些消費者團隊將被破壞。
其他的流程看起來是同樣的。
我能夠有多個基類進行測試嗎?
是! 查看Gradle或Maven插件的合同部分的不一樣基類。
如何調試生成的測試客戶端發送的請求/響應?
生成的測試都以某種形式或時尚的方式依賴於Apache HttpClient進行RestAssured。HttpClient有一個名爲wire logging的工具,它將整個請求和響應記錄到HttpClient。Spring Boot有一個日誌記錄通用應用程序屬性來作這種事情,只需將其添加到應用程序屬性中便可
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-properties">logging.level.org.apache.http.wire=DEBUG</code></span></span>
能夠從響應中引用請求嗎?
是! 使用版本1.1.0,咱們添加了這樣一種可能性。在HTTP存根服務器端,咱們正在爲WireMock提供支持。在其餘HTTP服務器存根的狀況下,您必須本身實現該方法。
畢業項目
先決條件
爲了在WireMock中使用Spring Cloud Contract驗證器,您必須使用Gradle或Maven插件。
警告 |
若是您想在項目中使用Spock,則必須單獨添加spock-core 和spock-spring 模塊。檢查Spock文檔以獲取更多信息 |
添加具備依賴關係的漸變插件
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">buildscript { repositories { mavenCentral() } dependencies { classpath "org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}" classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifier_version}" } } apply plugin: 'groovy' apply plugin: 'spring-cloud-contract' dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${verifier_version}" } } dependencies { testCompile 'org.codehaus.groovy:groovy-all:2.4.6' // example with adding Spock core and Spock Spring testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' testCompile 'org.spockframework:spock-spring:1.0-groovy-2.4' testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier' }</code></span></span>
Gradle的快照版本
將其餘快照存儲庫添加到您的build.gradle以使用快照版本,每次成功構建後都會自動上傳:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">buildscript { repositories { mavenCentral() mavenLocal() maven { url "http://repo.spring.io/snapshot" } maven { url "http://repo.spring.io/milestone" } maven { url "http://repo.spring.io/release" } } }</code></span></span>
添加存根
默認狀況下Spring Cloud Contract驗證器正在src/test/resources/contracts
目錄中查找存根。
包含存根定義的目錄被視爲一個類名稱,每一個存根定義被視爲單個測試。咱們假設它至少包含一個用做測試類名稱的目錄。若是有多個級別的嵌套目錄,除了最後一個級別將被用做包名稱。因此具備如下結構
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">src/test/resources/contracts/myservice/shouldCreateUser.groovy src/test/resources/contracts/myservice/shouldReturnUser.groovy</code></span></span>
Spring Cloud Contract驗證程序將使用兩種方法建立測試類defaultBasePackage.MyService
shouldCreateUser()
shouldReturnUser()
運行插件
插件註冊本身在check
任務以前被調用。只要您但願它成爲構建過程的一部分,您就無所事事。若是您只想生成測試,請調用generateContractTests
任務。
默認設置
默認的Gradle插件設置建立了如下Gradle部分的構建(它是一個僞代碼)
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">contracts { targetFramework = 'JUNIT' testMode = 'MockMvc' generatedTestSourcesDir = project.file("${project.buildDir}/generated-test-sources/contracts") contractsDslDir = "${project.rootDir}/src/test/resources/contracts" basePackageForTests = 'org.springframework.cloud.verifier.tests' stubsOutputDir = project.file("${project.buildDir}/stubs") // the following properties are used when you want to provide where the JAR with contract lays contractDependency { stringNotation = '' } contractsPath = '' contractsWorkOffline = false } tasks.create(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateClientStubs') { baseName = project.name classifier = contracts.stubsSuffix from contractVerifier.stubsOutputDir } project.artifacts { archives task } tasks.create(type: Copy, name: 'copyContracts') { from contracts.contractsDslDir into contracts.stubsOutputDir } verifierStubsJar.dependsOn 'copyContracts' publishing { publications { stubs(MavenPublication) { artifactId project.name artifact verifierStubsJar } } }</code></span></span>
配置插件
要更改默認配置,只需在您的Gradle配置中添加contracts
代碼段便可
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">contracts { testMode = 'MockMvc' baseClassForTests = 'org.mycompany.tests' generatedTestSourcesDir = project.file('src/generatedContract') }</code></span></span>
配置選項
testMode - 定義接受測試的模式。默認的基於Spring的MockMvc的MockMvc。也能夠將其更改成JaxRsClient或顯式爲真實的HTTP調用。
導入 - 應包含在生成的測試中的導入的數組(例如['org.myorg.Matchers'])。默認爲空數組[]
staticImports - 應該包含在生成的測試中的靜態導入的數組(例如['org.myorg.Matchers。*'])。默認爲空數組[]
basePackageForTests - 爲全部生成的測試指定基礎包。默認設置爲org.springframework.cloud.verifier.tests
baseClassForTests - 全部生成的測試的基類。若是使用Spock測試,默認爲spock.lang.Specification
。
packageWithBaseClasses - 而不是爲基類提供固定值,您能夠提供一個全部基類放置的包。優先於baseClassForTests。
baseClassMappings - 明確地將合約包映射到基類的FQN。優先於packageWithBaseClasses和baseClassForTests。
ruleClassForTests - 指定應該添加到生成的測試類的規則。
ignoredFiles - Ant匹配器,容許定義要跳過哪些處理的存根文件。默認爲空數組[]
contractsDslDir - 包含使用GroovyDSL編寫的合同的目錄。默認$rootDir/src/test/resources/contracts
generatedTestSourcesDir - 應該放置從Groovy DSL 生成測試的測試源目錄。默認$buildDir/generated-test-sources/contractVerifier
stubsOutputDir - 應該放置從Groovy DSL生成的WireMock存根的目錄
targetFramework - 要使用的目標測試框架; JUnit做爲默認框架,目前支持Spock和JUnit
當您但願提供合同所在JAR的位置時,將使用如下屬性
contractDependency - 提供groupid:artifactid:version:classifier
座標的依賴關係。您能夠使用contractDependency
關閉來設置它
contractPath - 若是下載合同部分將默認爲groupid/artifactid
,其中groupid
將被分隔。不然將掃描提供的目錄下的合同
contractsWorkOffline - 爲了避免下載依賴關係,每次下載一次,而後離線工做(重用本地Maven repo)
全部測試的單一基類
在默認的MockMvc中使用Spring Cloud Contract驗證器時,您須要爲全部生成的驗收測試建立一個基本規範。在這個類中,您須要指向應驗證的端點。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">abstract class BaseMockMvcSpec extends Specification { def setup() { RestAssuredMockMvc.standaloneSetup(new PairIdController()) } void isProperCorrelationId(Integer correlationId) { assert correlationId == 123456 } void isEmpty(String value) { assert value == null } }</code></span></span>
在使用Explicit
模式的狀況下,您能夠像普通集成測試同樣使用基類來初始化整個測試的應用程序。在JAXRSCLIENT
模式的狀況下,這個基類也應該包含protected WebTarget webTarget
字段,如今測試JAX-RS API的惟一選項是啓動Web服務器。
不一樣的基礎類別的合同
若是您的基類在合同之間不一樣,您能夠告訴Spring Cloud Contract插件哪一個類應該由自動生成測試擴展。你有兩個選擇:
遵循約定,提供packageWithBaseClasses
經過baseClassMappings
提供顯式映射
慣例
約定是這樣的,若是你有合同,例如src/test/resources/contract/foo/bar/baz/
,並將packageWithBaseClasses
屬性的值提供給com.example.base
,那麼咱們將假設com.example.base
下有一個BarBazBase
類包。換句話說,若是它們存在而且造成具備Base
後綴的類,那麼咱們將使用最後兩個包的部分。優先於baseClassForTests。contracts
關閉中的使用示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">packageWithBaseClasses = 'com.example.base'</code></span></span>
製圖
您能夠手動將合同包的正則表達式映射爲匹配合同的基類的徹底限定名稱。咱們來看看下面的例子:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">baseClassForTests = "com.example.FooBase" baseClassMappings { baseClassMapping('.*/com/.*', 'com.example.ComBase') baseClassMapping('.*/bar/.*':'com.example.BarBase') }</code></span></span>
咱們假設你有合同 - src/test/resources/contract/com/
- src/test/resources/contract/foo/
經過提供baseClassForTests
,咱們有一個後備案例,若是映射沒有成功(您也能夠提供packageWithBaseClasses
做爲備用)。這樣,從src/test/resources/contract/com/
合同產生的測試將擴展com.example.ComBase
,而其他的測試將擴展com.example.FooBase
。
調用生成的測試
爲確保提供方對定義的合同進行投訴,您須要調用:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">./gradlew generateContractTests test</code></span></span>
Spring Cloud Contract消費者驗證者
在消費者服務中,您須要以與提供商相同的方式配置Spring Cloud Contract驗證器插件。若是您不想使用Stub Runner,則須要複製存儲在src/test/resources/contracts
中的合同,並使用如下命令生成WireMock json存根:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">./gradlew generateClientStubs</code></span></span>
請注意,必須爲存根生成設置stubsOutputDir
選項才能正常工做。
當存在時,json存根可用於消費者自動測試。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application) class LoanApplicationServiceSpec extends Specification { @ClassRule @Shared WireMockClassRule wireMockRule == new WireMockClassRule() @Autowired LoanApplicationService sut def 'should successfully apply for loan'() { given: LoanApplication application = new LoanApplication(client: new Client(clientPesel: '12345678901'), amount: 123.123) when: LoanApplicationResult loanApplication == sut.loanApplication(application) then: loanApplication.loanApplicationStatus == LoanApplicationStatus.LOAN_APPLIED loanApplication.rejectionReason == null } }</code></span></span>
在LoanApplication下面調用FraudDetection服務。此請求由使用由Spring Cloud Contract驗證器生成的存根配置的WireMock服務器處理。
在您的Maven項目中使用
添加maven插件
添加Spring Cloud Contract BOM
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-dependencies.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement></code></span></span>
接下來,Spring Cloud Contract Verifier
Maven插件
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <packageWithBaseClasses>com.example.fraud</packageWithBaseClasses> </configuration> </plugin></code></span></span>
您能夠在Spring Cloud Contract Maven插件文檔中閱讀更多內容
Maven的快照版本
對於快照/里程碑版本,您必須將如下部分添加到您的pom.xml
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories></code></span></span>
添加存根
默認狀況下Spring Cloud Contract驗證器正在src/test/resources/contracts
目錄中查找存根。包含存根定義的目錄被視爲一個類名稱,每一個存根定義被視爲單個測試。咱們假設它至少包含一個用做測試類名稱的目錄。若是有多個級別的嵌套目錄,除了最後一個級別將被用做包名稱。因此具備如下結構
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">src/test/resources/contracts/myservice/shouldCreateUser.groovy src/test/resources/contracts/myservice/shouldReturnUser.groovy</code></span></span>
Spring Cloud Contract驗證者將使用兩種方法建立測試類defaultBasePackage.MyService
- shouldCreateUser()
-shouldReturnUser()
運行插件
插件目標generateTests
被分配爲階段generate-test-sources
。只要您但願它成爲構建過程的一部分,您就無所事事。若是您只想生成測試,請調用generateTests
目標。
配置插件
要更改默認配置,只需將configuration
部分添加到插件定義或execution
定義。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <executions> <execution> <goals> <goal>convert</goal> <goal>generateStubs</goal> <goal>generateTests</goal> </goals> </execution> </executions> <configuration> <basePackageForTests>org.springframework.cloud.verifier.twitter.place</basePackageForTests> <baseClassForTests>org.springframework.cloud.verifier.twitter.place.BaseMockMvcSpec</baseClassForTests> </configuration> </plugin></code></span></span>
重要配置選項
testMode - 定義接受測試的模式。默認MockMvc
,它基於Spring的MockMvc。對於真正的HTTP呼叫,它也能夠更改成JaxRsClient
或Explicit
。
basePackageForTests - 爲全部生成的測試指定基礎包。默認設置爲org.springframework.cloud.verifier.tests
。
ruleClassForTests - 指定應該添加到生成的測試類的規則。
baseClassForTests - 生成測試的基類。若是使用Spock測試,默認爲spock.lang.Specification
。
contractDir - 包含使用GroovyDSL編寫的合同的目錄。默認/src/test/resources/contracts
。
testFramework - 要使用的目標測試框架; JUnit做爲默認框架,目前支持Spock和JUnit
packageWithBaseClasses - 而不是爲基類提供固定值,您能夠提供一個全部基類放置的包。約定是這樣的,若是你有合同src/test/resources/contract/foo/bar/baz/
,並提供這個屬性的值到com.example.base
,那麼咱們將假設com.example.base
包含com.example.base
類。優先於baseClassForTests
baseClassMappings - 您必須提供contractPackageRegex
的基類映射列表,該列表根據合同所在的包進行檢查,而且baseClassFQN
映射到匹配合同的基類的徹底限定名稱。若是您有合同src/test/resources/contract/foo/bar/baz/
並映射了屬性.*
→com.example.base.BaseClass
,則從這些合同生成的測試類將擴展com.example.base.BaseClass
。優先於packageWithBaseClasses 和baseClassForTests。
若是要從Maven存儲庫中下載合同定義,能夠使用
contractsRepositoryUrl - 具備合同的工件的repo的URL(若是沒有提供)應使用當前的Maven
contractDependency - 包含全部打包合同的合同依賴關係
contractPath - 經過打包合同在JAR中具體合同的路徑。默認爲groupid/artifactid
,其中gropuid
被斜槓分隔。
contractWorkOffline - 若是依賴關係應該被下載,或者本地Maven只能被重用
有關完整信息,請參閱插件文檔
全部測試的單一基類
在默認的MockMvc中使用Spring Cloud Contract驗證器時,您須要爲全部生成的驗收測試建立一個基本規範。在這個類中,您須要指向應驗證的端點。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">package org.mycompany.tests import org.mycompany.ExampleSpringController import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc import spock.lang.Specification class MvcSpec extends Specification { def setup() { RestAssuredMockMvc.standaloneSetup(new ExampleSpringController()) } }</code></span></span>
在使用Explicit
模式的狀況下,您能夠像常規集成測試同樣使用基類來初始化整個測試的應用程序。在JAXRSCLIENT
模式的狀況下,這個基類也應該包含protected WebTarget webTarget
字段,如今測試JAX-RS API的惟一選項是啓動Web服務器。
不一樣的基礎類別的合同
若是您的基類在合同之間不一樣,您能夠告訴Spring Cloud Contract插件哪一個類應該由自動生成測試擴展。你有兩個選擇:
遵循約定,提供packageWithBaseClasses
經過baseClassMappings
提供顯式映射
慣例
約定是這樣的,若是你有合同,例如src/test/resources/contract/hello/v1/
,並將packageWithBaseClasses
屬性的值提供給hello
,那麼咱們將假設在hello
下有一個HelloV1Base
類包。換句話說,若是它們存在而且造成具備Base
後綴的類,那麼咱們將使用最後兩個包的部分。優先於baseClassForTests。使用示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <configuration> <packageWithBaseClasses>hello</packageWithBaseClasses> </configuration> </plugin></code></span></span>
製圖
您能夠手動將合同包的正則表達式映射爲匹配合同的基類的徹底限定名稱。您必須提供baseClassMappings
baseClassMapping
的contractPackageRegex
列表contractPackageRegex
到baseClassFQN
映射。咱們來看看下面的例子:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <configuration> <baseClassForTests>com.example.FooBase</baseClassForTests> <baseClassMappings> <baseClassMapping> <contractPackageRegex>.*com.*</contractPackageRegex> <baseClassFQN>com.example.TestBase</baseClassFQN> </baseClassMapping> </baseClassMappings> </configuration> </plugin></code></span></span>
咱們假設你有合同 - src/test/resources/contract/com/
- src/test/resources/contract/foo/
經過提供baseClassForTests
,咱們有一個後備程序,若是映射沒有成功(你也能夠提供packageWithBaseClasses
做爲備用)。這樣,從src/test/resources/contract/com/
合同生成的測試將擴展com.example.ComBase
,而其他的測試將擴展com.example.FooBase
。
調用生成的測試
Spring Cloud Contract Maven插件將驗證碼生成到目錄/generated-test-sources/contractVerifier
中,並將此目錄附加到testCompile
目標。
對於Groovy Spock代碼使用:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.codehaus.gmavenplus</groupId> <artifactId>gmavenplus-plugin</artifactId> <version>1.5</version> <executions> <execution> <goals> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <testSources> <testSource> <directory>${project.basedir}/src/test/groovy</directory> <includes> <include>**/*.groovy</include> </includes> </testSource> <testSource> <directory>${project.build.directory}/generated-test-sources/contractVerifier</directory> <includes> <include>**/*.groovy</include> </includes> </testSource> </testSources> </configuration> </plugin></code></span></span>
爲了確保提供方對定義的合同進行投訴,您須要調用mvn generateTest test
Maven插件常見問題
Maven插件和STS
若是在使用STS時看到如下異常
當您點擊標記時,您應該看到這樣的sth
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash"> plugin:1.1.0.M1:convert:default-convert:process-test-resources) org.apache.maven.plugin.PluginExecutionException: Execution default-convert of goal org.springframework.cloud:spring- cloud-contract-maven-plugin:1.1.0.M1:convert failed. at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:145) at org.eclipse.m2e.core.internal.embedder.MavenImpl.execute(MavenImpl.java:331) at org.eclipse.m2e.core.internal.embedder.MavenImpl$11.call(MavenImpl.java:1362) at ... org.eclipse.core.internal.jobs.Worker.run(Worker.java:55) Caused by: java.lang.NullPointerException at org.eclipse.m2e.core.internal.builder.plexusbuildapi.EclipseIncrementalBuildContext.hasDelta(EclipseIncrementalBuildContext.java:53) at org.sonatype.plexus.build.incremental.ThreadBuildContext.hasDelta(ThreadBuildContext.java:59) at</code></span></span>
爲了解決這個問題,請在pom.xml
中提供如下部分
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><build> <pluginManagement> <plugins> <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. --> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <versionRange>[1.0,)</versionRange> <goals> <goal>convert</goal> </goals> </pluginExecutionFilter> <action> <execute /> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement> </build></code></span></span>
Spring Cloud Contract消費者驗證者
您實際上也能夠爲消費者使用Spring Cloud Contract驗證器!您能夠使用插件,以便只轉換合同並生成存根。要實現這一點,您須要以與提供程序相同的方式配置Spring Cloud Contract驗證程序插件。您須要複製存儲在src/test/resources/contracts
中的合同,並使用如下命令生成WireMock json存根:mvn generateStubs
命令。默認生成的WireMock映射存儲在目錄target/mappings
中。您的項目應該今後生成的映射建立附加工件與分類器stubs
,以便輕鬆部署到maven存儲庫。
樣品配置:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${verifier-plugin.version}</version> <executions> <execution> <goals> <goal>convert</goal> <goal>generateStubs</goal> </goals> </execution> </executions> </plugin></code></span></span>
當存在時,json存根可用於消費者自動測試。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">@RunWith(SpringTestRunner.class) @SpringBootTest @AutoConfigureStubRunner public class LoanApplicationServiceTests { @Autowired LoanApplicationService service; @Test public void shouldSuccessfullyApplyForLoan() { //given: LoanApplication application = new LoanApplication(new Client("12345678901"), 123.123); //when: LoanApplicationResult loanApplication = service.loanApplication(application); // then: assertThat(loanApplication.loanApplicationStatus).isEqualTo(LoanApplicationStatus.LOAN_APPLIED); assertThat(loanApplication.rejectionReason).isNull(); } }</code></span></span>
LoanApplication
下方致電FraudDetection
服務。此請求由使用Spring Cloud Contract驗證器生成的存根配置的WireMock服務器進行處理。
方案
能夠使用Spring Cloud Contract驗證程序處理場景。全部您須要作的是在建立合同時堅持正確的命名約定。公約要求包括後面是下劃線的訂單號。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code>my_contracts_dir\ scenario1\ 1_login.groovy 2_showCart.groovy 3_logout.groovy</code></span></span>
這樣的樹將致使Spring Cloud Contract驗證器生成名爲scenario1
的WireMock場景和三個步驟:
登陸標記爲Started
,指向:
showCart標記爲Step1
指向:
註銷標記爲Step2
,這將關閉場景。
有關WireMock場景的更多詳細信息,請參見http://wiremock.org/stateful-behaviour.html
Spring Cloud Contract驗證者還將生成具備保證執行順序的測試。
存根和傳遞依賴
咱們建立的Maven和Gradle插件是爲您添加建立存根jar的任務。可能有問題的是,當重用存根時,您能夠錯誤地導入全部這些存根依賴關係!即便你有幾個不一樣的罐子,建造一個Maven的工件,他們都有一個pom:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-bash">├── github-webhook-0.0.1.BUILD-20160903.075506-1-stubs.jar ├── github-webhook-0.0.1.BUILD-20160903.075506-1-stubs.jar.sha1 ├── github-webhook-0.0.1.BUILD-20160903.075655-2-stubs.jar ├── github-webhook-0.0.1.BUILD-20160903.075655-2-stubs.jar.sha1 ├── github-webhook-0.0.1.BUILD-SNAPSHOT.jar ├── github-webhook-0.0.1.BUILD-SNAPSHOT.pom ├── github-webhook-0.0.1.BUILD-SNAPSHOT-stubs.jar ├── ... └── ...</code></span></span>
使用這些依賴關係有三種可能性,以便不會對傳遞依賴性產生任何問題。
將全部應用程序依賴項標記爲可選
若是在github-webhook
應用程序中,咱們將全部的依賴項標記爲可選的,當您將github-webhook
存根包含在另外一個應用程序中(或者當依賴關係由Stub Runner下載)時,由於全部的依賴關係是可選的,它們不會被下載。
爲存根建立一個單獨的artifactid
若是你建立一個單獨的artifactid,那麼你能夠設置任何你想要的方式。例如經過沒有依賴關係。
排除消費者方面的依賴關係
做爲消費者,若是將stub依賴關係添加到類路徑中,則能夠顯式排除不須要的依賴關係。
Spring Cloud Contract驗證器容許您驗證使用消息傳遞做爲通訊方式的應用程序。咱們全部的集成都使用Spring,但您也能夠本身建立並使用它。
集成
您能夠使用四種集成配置之一:
Apache Camel
Spring Integration
Spring Cloud Stream
Spring AMQP
因爲咱們使用Spring Boot,所以若是您已經將上述的一個庫添加到類路徑中,那麼將自動設置全部的消息傳遞配置。
重要 |
記住將@AutoConfigureMessageVerifier 放在生成的測試的基類上。不然Spring Cloud Contract驗證器的消息傳遞部分將沒法正常工做。 |
手動集成測試
測試使用的主界面是org.springframework.cloud.contract.verifier.messaging.MessageVerifier
。它定義瞭如何發送和接收消息。您能夠建立本身的實現來實現相同的目標。
在測試中,您能夠註冊ContractVerifierMessageExchange
發送和接收遵循合同的消息。而後將@AutoConfigureMessageVerifier
添加到您的測試中,例如
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@RunWith(SpringTestRunner.class) @SpringBootTest @AutoConfigureMessageVerifier public static class MessagingContractTests { @Autowired private MessageVerifier verifier; ... }</code></span></span>
注意 |
若是您的測試也須要存根,則@AutoConfigureStubRunner 包括消息傳遞配置,所以您只須要一個註釋。 |
發行人端測試一代
在您的DSL中擁有input
或outputMessage
部分將致使在發佈商方面建立測試。默認狀況下,將建立JUnit測試,可是也能夠建立Spock測試。
咱們應該考慮三個主要場景:
狀況1:沒有輸入消息產生輸出消息。輸出消息由應用程序內部的組件觸發(例如調度程序)
狀況2:輸入消息觸發輸出消息
方案3:輸入消息被消耗,沒有輸出消息
情景1(無輸入訊息)
對於給定的合同:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">def contractDsl = Contract.make { label 'some_label' input { triggeredBy('bookReturnedTriggered()') } outputMessage { sentTo('activemq:output') body('''{ "bookName" : "foo" }''') headers { header('BOOK-NAME', 'foo') } } }</code></span></span>
將建立如下JUnit測試:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">''' // when: bookReturnedTriggered(); // then: ContractVerifierMessage response = contractVerifierMessaging.receive("activemq:output"); assertThat(response).isNotNull(); assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo"); // and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())); assertThatJson(parsedJson).field("bookName").isEqualTo("foo"); '''</code></span></span>
而且將建立如下Spock測試:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">''' when: bookReturnedTriggered() then: ContractVerifierMessage response = contractVerifierMessaging.receive('activemq:output') assert response != null response.getHeader('BOOK-NAME') == 'foo' and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload)) assertThatJson(parsedJson).field("bookName").isEqualTo("foo") '''</code></span></span>
情景2(輸入觸發輸出)
對於給定的合同:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">def contractDsl = Contract.make { label 'some_label' input { messageFrom('jms:input') messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } } outputMessage { sentTo('jms:output') body([ bookName: 'foo' ]) headers { header('BOOK-NAME', 'foo') } } }</code></span></span>
將建立如下JUnit測試:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">''' // given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( "{\\"bookName\\":\\"foo\\"}" , headers() .header("sample", "header")); // when: contractVerifierMessaging.send(inputMessage, "jms:input"); // then: ContractVerifierMessage response = contractVerifierMessaging.receive("jms:output"); assertThat(response).isNotNull(); assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo"); // and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())); assertThatJson(parsedJson).field("bookName").isEqualTo("foo"); '''</code></span></span>
而且將建立如下Spock測試:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">"""\ given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( '''{"bookName":"foo"}''', ['sample': 'header'] ) when: contractVerifierMessaging.send(inputMessage, 'jms:input') then: ContractVerifierMessage response = contractVerifierMessaging.receive('jms:output') assert response !- null response.getHeader('BOOK-NAME') == 'foo' and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload)) assertThatJson(parsedJson).field("bookName").isEqualTo("foo") """</code></span></span>
情景3(無輸出訊息)
對於給定的合同:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">def contractDsl = Contract.make { label 'some_label' input { messageFrom('jms:delete') messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } assertThat('bookWasDeleted()') } }</code></span></span>
將建立如下JUnit測試:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">''' // given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( "{\\"bookName\\":\\"foo\\"}" , headers() .header("sample", "header")); // when: contractVerifierMessaging.send(inputMessage, "jms:delete"); // then: bookWasDeleted(); '''</code></span></span>
而且將建立如下Spock測試:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">''' given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( \'\'\'{"bookName":"foo"}\'\'\', ['sample': 'header'] ) when: contractVerifierMessaging.send(inputMessage, 'jms:delete') then: noExceptionThrown() bookWasDeleted() '''</code></span></span>
消費者存根側代
與HTTP部分不一樣 - 在消息傳遞中,咱們須要使用存根發佈JAR中的Groovy DSL。而後在消費者端進行解析,建立適當的stubbed路由。
有關更多信息,請參閱Stub Runner消息部分。
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-stub-runner</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-test-support</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.BUILD-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement></code></span></span>
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">ext { contractsDir = file("mappings") stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/") } // Automatically added by plugin: // copyContracts - copies contracts to the output folder from which JAR will be created // verifierStubsJar - JAR with a provided stub suffix // the presented publication is also added by the plugin but you can modify it as you wish publishing { publications { stubs(MavenPublication) { artifactId "${project.name}-stubs" artifact verifierStubsJar } } }</code></span></span>
使用Spring Cloud Contract驗證程序時可能遇到的一個問題是將生成的WireMock JSON存根從服務器端傳遞到客戶端(或各類客戶端)。在消息傳遞的客戶端生成方面也是如此。
複製JSON文件/手動設置客戶端進行消息傳遞是不成問題的。
這就是爲何咱們會介紹能夠爲您自動下載和運行存根的Spring Cloud Contract Stub Runner。
快照版本
將其餘快照存儲庫添加到您的build.gradle以使用快照版本,每次成功構建後都會自動上傳:
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories></code></span></span>
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">buildscript { repositories { mavenCentral() mavenLocal() maven { url "http://repo.spring.io/snapshot" } maven { url "http://repo.spring.io/milestone" } maven { url "http://repo.spring.io/release" } }</code></span></span>
將存根發佈爲JAR
最簡單的方法是集中保留存根的方式。例如,您能夠將它們做爲JAR存儲在Maven存儲庫中。
提示 |
對於Maven和Gradle來講,安裝程序都是開箱即用的。可是若是你想要的話可以自定義它。 |
Maven的
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-xml"><!-- First disable the default jar setup in the properties section--> <!-- we don't want the verifier to do a jar for us --> <spring.cloud.contract.verifier.skip>true</spring.cloud.contract.verifier.skip> <!-- Next add the assembly plugin to your build --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>stub</id> <phase>prepare-package</phase> <goals> <goal>single</goal> </goals> <inherited>false</inherited> <configuration> <attach>true</attach> <descriptor>$/Users/sgibb/workspace/spring/spring-cloud-samples/scripts/docs/../src/assembly/stub.xml</descriptor> </configuration> </execution> </executions> </plugin> <!-- Finally setup your assembly. Below you can find the contents of src/main/assembly/stub.xml --> <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> <id>stubs</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>src/main/java</directory> <outputDirectory>/</outputDirectory> <includes> <include>**com/example/model/*.*</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}/classes</directory> <outputDirectory>/</outputDirectory> <includes> <include>**com/example/model/*.*</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}/snippets/stubs</directory> <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory> <includes> <include>**/*</include> </includes> </fileSet> <fileSet> <directory>$/Users/sgibb/workspace/spring/spring-cloud-samples/scripts/docs/../src/test/resources/contracts</directory> <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts</outputDirectory> <includes> <include>**/*.groovy</include> </includes> </fileSet> </fileSets> </assembly></code></span></span>
搖籃
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">ext { contractsDir = file("mappings") stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/") } // Automatically added by plugin: // copyContracts - copies contracts to the output folder from which JAR will be created // verifierStubsJar - JAR with a provided stub suffix // the presented publication is also added by the plugin but you can modify it as you wish publishing { publications { stubs(MavenPublication) { artifactId "${project.name}-stubs" artifact verifierStubsJar } } }</code></span></span>
模塊
爲服務合做者運行存根。做爲服務合同處理存根容許使用stub-runner做爲 Consumer Driven Contracts的實現。
Stub Runner容許您自動下載提供的依賴項的存根,爲其啓動WireMock服務器,併爲其提供適當的存根定義。對於消息傳遞,定義了特殊的存根路由。
運行存根
限制
重要 |
StubRunner可能會在測試之間關閉端口時出現問題。您可能會遇到您遇到端口衝突的狀況。只要您在測試中使用相同的上下文,一切正常。可是當上下文不一樣(例如不一樣的存根或不一樣的配置文件)時,您必須使用@DirtiesContext 關閉存根服務器,不然在每一個測試的不一樣端口上運行它們。 |
運行使用主應用程序
您能夠將如下選項設置爲主類:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">-c, --classifier Suffix for the jar containing stubs (e. g. 'stubs' if the stub jar would have a 'stubs' classifier for stubs: foobar-stubs ). Defaults to 'stubs' (default: stubs) --maxPort, --maxp <Integer> Maximum port value to be assigned to the WireMock instance. Defaults to 15000 (default: 15000) --minPort, --minp <Integer> Minimum port value to be assigned to the WireMock instance. Defaults to 10000 (default: 10000) -p, --password Password to user when connecting to repository --phost, --proxyHost Proxy host to use for repository requests --pport, --proxyPort [Integer] Proxy port to use for repository requests -r, --root Location of a Jar containing server where you keep your stubs (e.g. http: //nexus. net/content/repositories/repository) -s, --stubs Comma separated list of Ivy representation of jars with stubs. Eg. groupid:artifactid1,groupid2: artifactid2:classifier -u, --username Username to user when connecting to repository --wo, --workOffline Switch to work offline. Defaults to 'false'</code></span></span>
HTTP存根
存根在JSON文檔中定義,其語法在WireMock文檔中定義
例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-javascript">{ "request": { "method": "GET", "url": "/ping" }, "response": { "status": 200, "body": "pong", "headers": { "Content-Type": "text/plain" } } }</code></span></span>
查看註冊的映射
每一個stubbed協做者公開__/admin/
端點下定義的映射列表。
消息存根
根據提供的Stub Runner依賴關係和DSL,消息路由將自動設置。
Stub Runner附帶一個JUnit規則,感謝您能夠輕鬆地下載和運行給定組和工件ID的存根:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@ClassRule public static StubRunnerRule rule = new StubRunnerRule() .repoRoot(repoRoot()) .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer");</code></span></span>
該規則執行後Stub Runner鏈接到您的Maven存儲庫,給定的依賴關係列表嘗試:
下載它們
在本地緩存
將它們解壓縮到臨時文件夾
從提供的端口/提供的端口範圍的隨機端口上爲每一個Maven依賴關係啓動WireMock服務器
爲WireMock服務器提供全部具備有效WireMock定義的JSON文件
Stub Runner使用Eclipse Aether機制下載Maven依賴關係。查看他們的文檔瞭解更多信息。
因爲StubRunnerRule
實現了StubFinder
,它容許您找到已啓動的存根:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">package org.springframework.cloud.contract.stubrunner; import java.net.URL; import java.util.Collection; import java.util.Map; import org.springframework.cloud.contract.spec.Contract; public interface StubFinder extends StubTrigger { /** * For the given groupId and artifactId tries to find the matching * URL of the running stub. * * @param groupId - might be null. In that case a search only via artifactId takes place * @return URL of a running stub or throws exception if not found */ URL findStubUrl(String groupId, String artifactId) throws StubNotFoundException; /** * For the given Ivy notation {@code [groupId]:artifactId:[version]:[classifier]} tries to * find the matching URL of the running stub. You can also pass only {@code artifactId}. * * @param ivyNotation - Ivy representation of the Maven artifact * @return URL of a running stub or throws exception if not found */ URL findStubUrl(String ivyNotation) throws StubNotFoundException; /** * Returns all running stubs */ RunningStubs findAllRunningStubs(); /** * Returns the list of Contracts */ Map<StubConfiguration, Collection<Contract>> getContracts(); }</code></span></span>
Spock測試中使用示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">@ClassRule @Shared StubRunnerRule rule = new StubRunnerRule() .repoRoot(StubRunnerRuleSpec.getResource("/m2repo/repository").toURI().toString()) .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer") def 'should start WireMock servers'() { expect: 'WireMocks are running' rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null rule.findStubUrl('loanIssuance') != null rule.findStubUrl('loanIssuance') == rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null and: rule.findAllRunningStubs().isPresent('loanIssuance') rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer') rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') and: 'Stubs were registered' "${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' "${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' }</code></span></span>
JUnit測試中的使用示例:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@Test public void should_start_wiremock_servers() throws Exception { // expect: 'WireMocks are running' then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")).isNotNull(); then(rule.findStubUrl("loanIssuance")).isNotNull(); then(rule.findStubUrl("loanIssuance")).isEqualTo(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")); then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isNotNull(); // and: then(rule.findAllRunningStubs().isPresent("loanIssuance")).isTrue(); then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs", "fraudDetectionServer")).isTrue(); then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isTrue(); // and: 'Stubs were registered' then(httpGet(rule.findStubUrl("loanIssuance").toString() + "/name")).isEqualTo("loanIssuance"); then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")).isEqualTo("fraudDetectionServer"); }</code></span></span>
有關如何應用Stub Runner的全局配置的更多信息,請查看JUnit和Spring的公共屬性。
Maven設置
存根下載器爲不一樣的本地存儲庫文件夾授予Maven設置。目前沒有考慮存儲庫和配置文件的身份驗證詳細信息,所以您須要使用上述屬性進行指定。
提供固定端口
您還能夠在固定端口上運行您的存根。你能夠經過兩種不一樣的方法來實現。一個是在屬性中傳遞它,另外一個是經過JUnit規則的流暢API。
流暢的API
使用StubRunnerRule
時,您能夠添加一個存根下載,而後經過上次下載的存根的端口。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">@ClassRule public static StubRunnerRule rule = new StubRunnerRule() .repoRoot(repoRoot()) .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") .withPort(12345) .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer:12346");</code></span></span>
您能夠看到,對於此示例,如下測試是有效的:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java">then(rule.findStubUrl("loanIssuance")).isEqualTo(URI.create("http://localhost:12345").toURL()); then(rule.findStubUrl("fraudDetectionServer")).isEqualTo(URI.create("http://localhost:12346").toURL());</code></span></span>
Stub Runner與Spring
設置Stub Runner項目的Spring配置。
經過在配置文件中提供存根列表,Stub Runner自動下載並註冊WireMock中所選擇的存根。
若是要查找stubbed依賴關係的URL,您能夠自動鏈接StubFinder
接口並使用其方法,以下所示:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">@ContextConfiguration(classes = Config, loader = SpringBootContextLoader) @SpringBootTest(properties = [" stubrunner.cloud.enabled=false", "stubrunner.camel.enabled=false", 'foo=${stubrunner.runningstubs.fraudDetectionServer.port}']) @AutoConfigureStubRunner @DirtiesContext @ActiveProfiles("test") class StubRunnerConfigurationSpec extends Specification { @Autowired StubFinder stubFinder @Autowired Environment environment @Value('${foo}') Integer foo @BeforeClass @AfterClass void setupProps() { System.clearProperty("stubrunner.repository.root") System.clearProperty("stubrunner.classifier") } def 'should start WireMock servers'() { expect: 'WireMocks are running' stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null stubFinder.findStubUrl('loanIssuance') != null stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance') stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs') stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null and: stubFinder.findAllRunningStubs().isPresent('loanIssuance') stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer') stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') and: 'Stubs were registered' "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' } def 'should throw an exception when stub is not found'() { when: stubFinder.findStubUrl('nonExistingService') then: thrown(StubNotFoundException) when: stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId') then: thrown(StubNotFoundException) } def 'should register started servers as environment variables'() { expect: environment.getProperty("stubrunner.runningstubs.loanIssuance.port") != null stubFinder.findAllRunningStubs().getPort("loanIssuance") == (environment.getProperty("stubrunner.runningstubs.loanIssuance.port") as Integer) and: environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") as Integer) } def 'should be able to interpolate a running stub in the passed test property'() { given: int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") expect: fraudPort > 0 environment.getProperty("foo", Integer) == fraudPort foo == fraudPort } @Configuration @EnableAutoConfiguration static class Config {} }</code></span></span>
對於如下配置文件:
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yml">stubrunner: repositoryRoot: classpath:m2repo/repository/ ids: - org.springframework.cloud.contract.verifier.stubs:loanIssuance - org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer - org.springframework.cloud.contract.verifier.stubs:bootService cloud: enabled: false camel: enabled: false spring.cloud: consul.enabled: false service-registry.enabled: false</code></span></span>
您也能夠使用@AutoConfigureStubRunner
內的屬性代替使用屬性。下面您能夠經過設置註釋的值來找到實現相同結果的示例。
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">@AutoConfigureStubRunner( ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", "org.springframework.cloud.contract.verifier.stubs:bootService"], repositoryRoot = "classpath:m2repo/repository/")</code></span></span>
Stub Runner Spring爲每一個註冊的WireMock服務器以如下方式註冊環境變量。Stub Runner ids com.example:foo
,com.example:bar
的示例。
stubrunner.runningstubs.foo.port
stubrunner.runningstubs.bar.port
你能夠在你的代碼中引用它。
Stub Runner能夠與Spring Cloud整合。
對於現實生活中的例子,你能夠檢查
Stubbing服務發現
Stub Runner Spring Cloud
的最重要的特徵就是它的存在
DiscoveryClient
Ribbon
ServerList
這意味着不管您是否使用Zookeeper,Consul,Eureka或其餘任何事情,您都不須要在測試中。咱們正在啓動您的依賴項的WireMock實例,只要您直接使用Feign
,負載平衡RestTemplate
或DiscoveryClient
,咱們會告訴您的應用程序來調用這些stubbed服務器,而不是調用真實的服務發現工具。
例如這個測試將經過
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-groovy">def 'should make service discovery work'() { expect: 'WireMocks are running' "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' and: 'Stubs can be reached via load service discovery' restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' }</code></span></span>
對於如下配置文件
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-yml">spring.cloud: zookeeper.enabled: false consul.enabled: false eureka.client.enabled: false stubrunner: camel.enabled: false idsToServiceIds: ivyNotation: someValueInsideYourCode fraudDetectionServer: someNameThatShouldMapFraudDetectionServer</code></span></span>
測試配置文件和服務發現
在集成測試中,您一般不想既不調用發現服務(例如Eureka)或調用服務器。這就是爲何你建立一個額外的測試配置,你要禁用這些功能。
因爲spring-cloud-commons
實現這一點的某些限制,您能夠經過下面的靜態塊來禁用這些屬性(例如Eureka)
<span style="color:rgba(0, 0, 0, 0.8)"><span style="color:rgba(0, 0, 0, 0.9)"><code class="language-java"> //Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156 static { System.setProperty("eureka.client.enabled", "false"); System.setProperty("spring.cloud.config.failFast", "false"); }</code></span></span>
附加配置
您能夠使用stubrunner.idsToServiceIds:
地圖將存根的artifactId與應用程序的名稱進行匹配。提供:stubrunner.cloud.ribbon.enabled
等於false
,您能夠禁用Stub Runner Ribbon支持。您能夠經過提供stubrunner.cloud.enabled
等於false
來禁用Stub Runner支持