Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)愕贡,斷路器草雕,智能路由,微代理固以,控制總線)墩虹。分布式系統(tǒng)的協(xié)調(diào)導(dǎo)致了樣板模式, 使用Spring Cloud開(kāi)發(fā)人員可以快速地支持實(shí)現(xiàn)這些模式的服務(wù)和應(yīng)用程序。他們將在任何分布式環(huán)境中運(yùn)行良好憨琳,包括開(kāi)發(fā)人員自己的筆記本電腦诫钓,裸機(jī)數(shù)據(jù)中心,以及Cloud Foundry等托管平臺(tái)篙螟。
版本:Dalston.RELEASE
特性
Spring Cloud專注于提供良好的開(kāi)箱即用經(jīng)驗(yàn)的典型用例和可擴(kuò)展性機(jī)制覆蓋菌湃。
分布式/版本化配置
服務(wù)注冊(cè)和發(fā)現(xiàn)
路由
service - to - service調(diào)用
負(fù)載均衡
斷路器
分布式消息傳遞
云原生應(yīng)用程序
云原生是一種應(yīng)用開(kāi)發(fā)風(fēng)格,鼓勵(lì)在持續(xù)交付和價(jià)值驅(qū)動(dòng)開(kāi)發(fā)領(lǐng)域輕松采用最佳實(shí)踐遍略。相關(guān)的學(xué)科是建立12-factor Apps惧所,其中開(kāi)發(fā)實(shí)踐與交付和運(yùn)營(yíng)目標(biāo)相一致,例如通過(guò)使用聲明式編程和管理和監(jiān)控绪杏。Spring Cloud以多種具體方式促進(jìn)這些開(kāi)發(fā)風(fēng)格纯路,起點(diǎn)是一組功能,分布式系統(tǒng)中的所有組件都需要或需要時(shí)輕松訪問(wèn)寞忿。
許多這些功能都由Spring Boot覆蓋身冬,我們?cè)赟pring Cloud中建立。更多的由Spring Cloud提供為兩個(gè)庫(kù):Spring Cloud Context和Spring Cloud Commons精拟。Spring Cloud上下文為Spring Cloud應(yīng)用程序(引導(dǎo)上下文世曾,加密,刷新范圍和環(huán)境端點(diǎn))的ApplicationContext提供實(shí)用程序和特殊服務(wù)霹抛。Spring Cloud Commons是一組在不同的Spring Cloud實(shí)現(xiàn)中使用的抽象和常用類(例如Spring Cloud Netflix vs. Spring Cloud Consul)搓逾。
如果由于“非法密鑰大小”而導(dǎo)致異常,并且您正在使用Sun的JDK杯拐,則需要安裝Java加密擴(kuò)展(JCE)無(wú)限強(qiáng)度管理策略文件霞篡。有關(guān)詳細(xì)信息,請(qǐng)參閱以下鏈接:
將文件解壓縮到JDK / jre / lib / security文件夾(無(wú)論您使用的是哪個(gè)版本的JRE / JDK x64 / x86)端逼。
注意
Spring Cloud根據(jù)非限制性Apache 2.0許可證發(fā)布朗兵。如果您想為文檔的這一部分做出貢獻(xiàn),或者發(fā)現(xiàn)錯(cuò)誤顶滩,請(qǐng)?jiān)?a target="_blank" rel="nofollow">github中找到項(xiàng)目中的源代碼和問(wèn)題跟蹤器余掖。
Spring Cloud上下文:應(yīng)用程序上下文服務(wù)
Spring Boot對(duì)于如何使用Spring構(gòu)建應(yīng)用程序有一個(gè)看法:例如它具有常規(guī)配置文件的常規(guī)位置,以及用于常見(jiàn)管理和監(jiān)視任務(wù)的端點(diǎn)礁鲁。Spring Cloud建立在此之上盐欺,并添加了一些可能系統(tǒng)中所有組件將使用或偶爾需要的功能赁豆。
引導(dǎo)應(yīng)用程序上下文
一個(gè)Spring Cloud應(yīng)用程序通過(guò)創(chuàng)建一個(gè)“引導(dǎo)”上下文來(lái)進(jìn)行操作,這個(gè)上下文是主應(yīng)用程序的父上下文冗美。開(kāi)箱即用魔种,負(fù)責(zé)從外部源加載配置屬性,還解密本地外部配置文件中的屬性粉洼。這兩個(gè)上下文共享一個(gè)Environment务嫡,這是任何Spring應(yīng)用程序的外部屬性的來(lái)源。Bootstrap屬性的優(yōu)先級(jí)高漆改,因此默認(rèn)情況下不能被本地配置覆蓋心铃。
引導(dǎo)上下文使用與主應(yīng)用程序上下文不同的外部配置約定,因此使用bootstrap.ymlapplication.yml(或.properties)代替引導(dǎo)和主上下文的外部配置挫剑。例:
bootstrap.yml
spring:
application:
name: foo
cloud:
config:
uri: ${SPRING_CONFIG_URI:http://localhost:8888}
如果您的應(yīng)用程序需要服務(wù)器上的特定于應(yīng)用程序的配置去扣,那么設(shè)置spring.application.name(在bootstrap.yml或application.yml)中是個(gè)好主意。
您可以通過(guò)設(shè)置spring.cloud.bootstrap.enabled=false(例如在系統(tǒng)屬性中)來(lái)完全禁用引導(dǎo)過(guò)程樊破。
應(yīng)用程序上下文層次結(jié)構(gòu)
如果您從SpringApplication或SpringApplicationBuilder構(gòu)建應(yīng)用程序上下文愉棱,則將Bootstrap上下文添加為該上下文的父級(jí)。這是一個(gè)Spring的功能哲戚,即子上下文從其父進(jìn)程繼承屬性源和配置文件啰扛,因此與不使用Spring Cloud Config構(gòu)建相同上下文相比榨婆,“主”應(yīng)用程序上下文將包含其他屬性源沼填。額外的財(cái)產(chǎn)來(lái)源是:
“bootstrap”:如果在Bootstrap上下文中找到任何PropertySourceLocators倦始,則可選CompositePropertySource顯示為高優(yōu)先級(jí),并且具有非空屬性脆炎。一個(gè)例子是來(lái)自Spring Cloud Config服務(wù)器的屬性梅猿。有關(guān)如何自定義此屬性源的內(nèi)容的說(shuō)明,請(qǐng)參閱下文秒裕。
“applicationConfig:[classpath:bootstrap.yml]”(如果Spring配置文件處于活動(dòng)狀態(tài)袱蚓,則為朋友)。如果您有一個(gè)bootstrap.yml(或?qū)傩裕┘蛤撸敲催@些屬性用于配置引導(dǎo)上下文喇潘,然后在父進(jìn)程設(shè)置時(shí)將它們添加到子上下文中。它們的優(yōu)先級(jí)低于application.yml(或?qū)傩裕┮约白鳛閯?chuàng)建Spring Boot應(yīng)用程序的過(guò)程的正常部分添加到子級(jí)的任何其他屬性源梭稚。有關(guān)如何自定義這些屬性源的內(nèi)容的說(shuō)明颖低,請(qǐng)參閱下文。
由于屬性源的排序規(guī)則哨毁,“引導(dǎo)”條目?jī)?yōu)先枫甲,但請(qǐng)注意,這些條目不包含來(lái)自bootstrap.yml的任何數(shù)據(jù)扼褪,它具有非常低的優(yōu)先級(jí)想幻,但可用于設(shè)置默認(rèn)值。
您可以通過(guò)簡(jiǎn)單地設(shè)置您創(chuàng)建的任何ApplicationContext的父上下文來(lái)擴(kuò)展上下文層次結(jié)構(gòu)话浇,例如使用自己的界面脏毯,或使用SpringApplicationBuilder方便方法(parent(),child()和sibling())幔崖。引導(dǎo)環(huán)境將是您創(chuàng)建自己的最高級(jí)祖先的父級(jí)食店。層次結(jié)構(gòu)中的每個(gè)上下文都將有自己的“引導(dǎo)”屬性源(可能為空),以避免無(wú)意中將值從父級(jí)升級(jí)到其后代赏寇。層次結(jié)構(gòu)中的每個(gè)上下文(原則上)也可以具有不同的spring.application.name吉嫩,因此如果存在配置服務(wù)器,則不同的遠(yuǎn)程屬性源嗅定。普通的Spring應(yīng)用程序上下文行為規(guī)則適用于屬性解析:子環(huán)境中的屬性通過(guò)名稱和屬性源名稱覆蓋父項(xiàng)中的屬性(如果子級(jí)具有與父級(jí)名稱相同的屬性源自娩,一個(gè)來(lái)自父母的孩子不包括在孩子中)。
請(qǐng)注意渠退,SpringApplicationBuilder允許您在整個(gè)層次結(jié)構(gòu)中共享Environment休蟹,但這不是默認(rèn)值涵防。因此,兄弟情境尤其不需要具有相同的資料或財(cái)產(chǎn)來(lái)源,盡管它們與父母共享共同點(diǎn)赤套。
改變引導(dǎo)位置Properties
可以使用spring.cloud.bootstrap.name(默認(rèn)“引導(dǎo)”)或spring.cloud.bootstrap.location(默認(rèn)為空)指定bootstrap.yml(或.properties)位置,例如在系統(tǒng)屬性中沽讹。這些屬性的行為類似于具有相同名稱的spring.config.*變體宾符,實(shí)際上它們用于通過(guò)在其Environment中設(shè)置這些屬性來(lái)設(shè)置引導(dǎo)ApplicationContext。如果在正在構(gòu)建的上下文中有活動(dòng)的配置文件(來(lái)自spring.profiles.active或通過(guò)EnvironmentAPI))梗掰,則該配置文件中的屬性也將被加載删豺,就像常規(guī)的Spring Boot應(yīng)用程序,例如來(lái)自bootstrap-development.properties的“開(kāi)發(fā)”簡(jiǎn)介愧怜。
覆蓋遠(yuǎn)程Properties的值
通過(guò)引導(dǎo)上下文添加到應(yīng)用程序的屬性源通常是“遠(yuǎn)程”(例如從配置服務(wù)器)呀页,并且默認(rèn)情況下,不能在本地覆蓋拥坛,除了在命令行上蓬蝶。如果要允許您的應(yīng)用程序使用自己的系統(tǒng)屬性或配置文件覆蓋遠(yuǎn)程屬性,則遠(yuǎn)程屬性源必須通過(guò)設(shè)置spring.cloud.config.allowOverride=true(在本地設(shè)置本身不起作用)授予權(quán)限猜惋。一旦設(shè)置了該標(biāo)志丸氛,就會(huì)有一些更精細(xì)的設(shè)置來(lái)控制遠(yuǎn)程屬性與系統(tǒng)屬性和應(yīng)用程序本地配置的位置:spring.cloud.config.overrideNone=true覆蓋任何本地屬性源,spring.cloud.config.overrideSystemProperties=false如果只有系統(tǒng)屬性和env var應(yīng)該覆蓋遠(yuǎn)程設(shè)置著摔,而不是本地配置文件缓窜。
自定義引導(dǎo)配置
可以通過(guò)在org.springframework.cloud.bootstrap.BootstrapConfiguration鍵下添加條目/META-INF/spring.factories來(lái)訓(xùn)練引導(dǎo)上下文來(lái)執(zhí)行任何您喜歡的操作。這是用于創(chuàng)建上下文的Spring@Configuration類的逗號(hào)分隔列表。您可以在此處創(chuàng)建要用于自動(dòng)裝配的主應(yīng)用程序上下文的任何bean禾锤,并且還有ApplicationContextInitializer類型的@Beans的特殊合同私股。如果要控制啟動(dòng)順序(默認(rèn)順序?yàn)椤白詈蟆保梢允褂聾Order標(biāo)記類恩掷。
警告
添加自定義BootstrapConfiguration時(shí)倡鲸,請(qǐng)注意,您添加的類不是錯(cuò)誤的@ComponentScanned到您的“主”應(yīng)用程序上下文中黄娘,可能不需要它們峭状。對(duì)于您的@ComponentScan或@SpringBootApplication注釋配置類尚未涵蓋的啟動(dòng)配置類,請(qǐng)使用單獨(dú)的包名稱逼争。
引導(dǎo)過(guò)程通過(guò)將初始化器注入主SpringApplication實(shí)例(即正常的Spring Boot啟動(dòng)順序优床,無(wú)論是作為獨(dú)立應(yīng)用程序運(yùn)行還是部署在應(yīng)用程序服務(wù)器中)結(jié)束。首先誓焦,從spring.factories中找到的類創(chuàng)建引導(dǎo)上下文矾缓,然后在ApplicationContextInitializer類型的所有@Beans添加到主SpringApplication開(kāi)始之前交惯。
自定義引導(dǎo)屬性源
引導(dǎo)過(guò)程添加的外部配置的默認(rèn)屬性源是Config Server开呐,但您可以通過(guò)將PropertySourceLocator類型的bean添加到引導(dǎo)上下文(通過(guò)spring.factories)添加其他源序臂。您可以使用此方法從其他服務(wù)器或數(shù)據(jù)庫(kù)中插入其他屬性。
作為一個(gè)例子稿壁,請(qǐng)考慮以下微不足道的自定義定位器:
@Configuration
public class CustomPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource locate(Environment environment) {
return new MapPropertySource("customProperty",
Collections.singletonMap("property.from.sample.custom.source", "worked as intended"));
}
}
傳入的Environment是要?jiǎng)?chuàng)建的ApplicationContext的Environment幽钢,即為我們提供額外的屬性來(lái)源的。它將已經(jīng)具有正常的Spring Boot提供的資源來(lái)源傅是,因此您可以使用它們來(lái)定位特定于此Environment的屬性源(例如通過(guò)將其綁定在spring.application.name上匪燕,如在默認(rèn)情況下所做的那樣Config Server屬性源定位器)。
如果你在這個(gè)類中創(chuàng)建一個(gè)jar喧笔,然后添加一個(gè)META-INF/spring.factories包含:
org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator
那么“customProperty”PropertySource將顯示在其類路徑中包含該jar的任何應(yīng)用程序中帽驯。
環(huán)境變化
應(yīng)用程序?qū)⑹章?tīng)EnvironmentChangeEvent,并以幾種標(biāo)準(zhǔn)方式進(jìn)行更改(用戶可以以常規(guī)方式添加ApplicationListeners附加ApplicationListeners)书闸。當(dāng)觀察到EnvironmentChangeEvent時(shí)尼变,它將有一個(gè)已更改的鍵值列表,應(yīng)用程序?qū)⑹褂靡韵聝?nèi)容:
重新綁定上下文中的任何@ConfigurationPropertiesbean
為logging.level.*中的任何屬性設(shè)置記錄器級(jí)別
請(qǐng)注意浆劲,配置客戶端不會(huì)通過(guò)默認(rèn)輪詢查找Environment中的更改嫌术,通常我們不建議檢測(cè)更改的方法(盡管可以使用@Scheduled注釋進(jìn)行設(shè)置)。如果您有一個(gè)擴(kuò)展的客戶端應(yīng)用程序牌借,那么最好將EnvironmentChangeEvent廣播到所有實(shí)例度气,而不是讓它們輪詢更改(例如使用Spring Cloud總線)。
EnvironmentChangeEvent涵蓋了大量的刷新用例膨报,只要您真的可以更改Environment并發(fā)布事件(這些API是公開(kāi)的磷籍,部分內(nèi)核為Spring)适荣。您可以通過(guò)訪問(wèn)/configprops端點(diǎn)(普通Spring Boot執(zhí)行器功能)來(lái)驗(yàn)證更改是否綁定到@ConfigurationPropertiesbean。例如院领,DataSource可以在運(yùn)行時(shí)更改其maxPoolSize(由Spring Boot創(chuàng)建的默認(rèn)DataSource是一個(gè)@ConfigurationPropertiesbean)弛矛,并且動(dòng)態(tài)增加容量。重新綁定@ConfigurationProperties不會(huì)覆蓋另一大類用例栅盲,您需要更多的控制刷新汪诉,并且您需要更改在整個(gè)ApplicationContext上是原子的废恋。為了解決這些擔(dān)憂谈秫,我們有@RefreshScope。
刷新范圍
當(dāng)配置更改時(shí)鱼鼓,標(biāo)有@RefreshScope的Spring@Bean將得到特殊處理网持。這解決了狀態(tài)bean在初始化時(shí)只注入配置的問(wèn)題帅腌。例如,如果通過(guò)Environment更改數(shù)據(jù)庫(kù)URL時(shí)DataSource有開(kāi)放連接,那么我們可能希望這些連接的持有人能夠完成他們正在做的工作桐磁。然后下一次有人從游泳池借用一個(gè)連接,他得到一個(gè)新的URL膝晾。
刷新范圍bean是在使用時(shí)初始化的懶惰代理(即當(dāng)調(diào)用一個(gè)方法時(shí))亩冬,并且作用域作為初始值的緩存。要強(qiáng)制bean重新初始化下一個(gè)方法調(diào)用公条,您只需要使其緩存條目無(wú)效拇囊。
RefreshScope是上下文中的一個(gè)bean,它有一個(gè)公共方法refreshAll()來(lái)清除目標(biāo)緩存中的范圍內(nèi)的所有bean靶橱。還有一個(gè)refresh(String)方法可以按名稱刷新單個(gè)bean寥袭。此功能在/refresh端點(diǎn)(通過(guò)HTTP或JMX)中公開(kāi)。
注意
@RefreshScope(技術(shù)上)在@Configuration類上工作关霸,但可能會(huì)導(dǎo)致令人驚訝的行為:例如传黄,這并不意味著該類中定義的所有@Beans本身都是@RefreshScope。具體來(lái)說(shuō)队寇,任何取決于這些bean的東西都不能依賴它們?cè)谒⑿聠?dòng)時(shí)被更新膘掰,除非它本身在@RefreshScope(在其中將重新刷新并重新注入其依賴關(guān)系),那么它們將從刷新的@Configuration)重新初始化佳遣。
加密和解密
Spring Cloud具有一個(gè)用于在本地解密屬性值的Environment預(yù)處理器识埋。它遵循與Config Server相同的規(guī)則,并通過(guò)encrypt.*具有相同的外部配置苍日。因此惭聂,您可以使用{cipher}*格式的加密值,只要有一個(gè)有效的密鑰相恃,那么在主應(yīng)用程序上下文獲取Environment之前辜纲,它們將被解密。要在應(yīng)用程序中使用加密功能,您需要在您的類路徑中包含Spring安全性RSA(Maven協(xié)調(diào)“org.springframework.security:spring-security-rsa”)耕腾,并且還需要全面強(qiáng)大的JCE擴(kuò)展你的JVM
如果由于“非法密鑰大小”而導(dǎo)致異常见剩,并且您正在使用Sun的JDK,則需要安裝Java加密擴(kuò)展(JCE)無(wú)限強(qiáng)度管理策略文件扫俺。有關(guān)詳細(xì)信息苍苞,請(qǐng)參閱以下鏈接:
將文件解壓縮到JDK / jre / lib / security文件夾(無(wú)論您使用的是哪個(gè)版本的JRE / JDK x64 / x86)。
端點(diǎn)
對(duì)于Spring Boot執(zhí)行器應(yīng)用程序狼纬,還有一些額外的管理端點(diǎn):
POST到/env以更新Environment并重新綁定@ConfigurationProperties和日志級(jí)別
/refresh重新加載引導(dǎo)帶上下文并刷新@RefreshScopebean
/restart關(guān)閉ApplicationContext并重新啟動(dòng)(默認(rèn)情況下禁用)
/pause和/resume調(diào)用Lifecycle方法(stop()和start()ApplicationContext)
Spring Cloud Commons:普通抽象
諸如服務(wù)發(fā)現(xiàn)姥饰,負(fù)載平衡和斷路器之類的模式適用于所有Spring Cloud客戶端可以獨(dú)立于實(shí)現(xiàn)(例如通過(guò)Eureka或Consul發(fā)現(xiàn))的消耗的共同抽象層。
@EnableDiscoveryClient
Commons提供@EnableDiscoveryClient注釋界牡。這通過(guò)META-INF/spring.factories查找DiscoveryClient接口的實(shí)現(xiàn)秒咐。Discovery Client的實(shí)現(xiàn)將在org.springframework.cloud.client.discovery.EnableDiscoveryClient鍵下的spring.factories中添加一個(gè)配置類。DiscoveryClient實(shí)現(xiàn)的示例是Spring Cloud Netflix Eureka盈简,Spring Cloud Consul發(fā)現(xiàn)和Spring Cloud Zookeeper發(fā)現(xiàn)凑耻。
默認(rèn)情況下,DiscoveryClient的實(shí)現(xiàn)將使用遠(yuǎn)程發(fā)現(xiàn)服務(wù)器自動(dòng)注冊(cè)本地Spring Boot服務(wù)器柠贤∠愫疲可以通過(guò)在@EnableDiscoveryClient中設(shè)置autoRegister=false來(lái)禁用此功能。
ServiceRegistry
Commons現(xiàn)在提供了一個(gè)ServiceRegistry接口臼勉,它提供了諸如register(Registration)和deregister(Registration)之類的方法邻吭,允許您提供定制的注冊(cè)服務(wù)。Registration是一個(gè)標(biāo)記界面坚俗。
@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);
}
}
每個(gè)ServiceRegistry實(shí)現(xiàn)都有自己的Registry實(shí)現(xiàn)镜盯。
服務(wù)部門自動(dòng)注冊(cè)
默認(rèn)情況下,ServiceRegistry實(shí)現(xiàn)將自動(dòng)注冊(cè)正在運(yùn)行的服務(wù)猖败。要禁用該行為速缆,有兩種方法。您可以設(shè)置@EnableDiscoveryClient(autoRegister=false)永久禁用自動(dòng)注冊(cè)恩闻。您還可以設(shè)置spring.cloud.service-registry.auto-registration.enabled=false以通過(guò)配置禁用該行為艺糜。
服務(wù)注冊(cè)執(zhí)行器端點(diǎn)
Commons提供/service-registry致動(dòng)器端點(diǎn)。該端點(diǎn)依賴于Spring應(yīng)用程序上下文中的Registrationbean幢尚。通過(guò)GET調(diào)用/service-registry/instance-status將返回Registration的狀態(tài)破停。具有String主體的同一端點(diǎn)的POST將將當(dāng)前Registration的狀態(tài)更改為新值。請(qǐng)參閱您正在使用的ServiceRegistry實(shí)現(xiàn)的文檔尉剩,以獲取更新?tīng)顟B(tài)的允許值和為狀態(tài)獲取的值真慢。
Spring RestTemplate作為負(fù)載平衡器客戶端
RestTemplate可以自動(dòng)配置為使用功能區(qū)。要?jiǎng)?chuàng)建負(fù)載平衡RestTemplate創(chuàng)建RestTemplate@Bean并使用@LoadBalanced限定符理茎。
警告
通過(guò)自動(dòng)配置不再創(chuàng)建RestTemplatebean黑界。它必須由單個(gè)應(yīng)用程序創(chuàng)建管嬉。
@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;
}
}
URI需要使用虛擬主機(jī)名(即服務(wù)名稱,而不是主機(jī)名)朗鸠。Ribbon客戶端用于創(chuàng)建完整的物理地址蚯撩。有關(guān)如何設(shè)置RestTemplate的詳細(xì)信息,請(qǐng)參閱RibbonAutoConfiguration烛占。
重試失敗的請(qǐng)求
負(fù)載平衡RestTemplate可以配置為重試失敗的請(qǐng)求水评。默認(rèn)情況下涮母,該邏輯被禁用鸿脓,您可以通過(guò)將Spring重試添加到應(yīng)用程序的類路徑來(lái)啟用它全蝶。負(fù)載平衡RestTemplate將符合與重試失敗請(qǐng)求相關(guān)的一些Ribbon配置值。如果要在類路徑中使用Spring重試來(lái)禁用重試邏輯弦赖,則可以設(shè)置spring.cloud.loadbalancer.retry.enabled=false项栏。您可以使用的屬性是client.ribbon.MaxAutoRetries浦辨,client.ribbon.MaxAutoRetriesNextServer和client.ribbon.OkToRetryOnAllOperations蹬竖。請(qǐng)參閱Ribbon文檔,了解屬性的具體內(nèi)容流酬。
注意
上述示例中的client應(yīng)替換為您的Ribbon客戶端名稱币厕。
多個(gè)RestTemplate對(duì)象
如果你想要一個(gè)沒(méi)有負(fù)載平衡的RestTemplate,創(chuàng)建一個(gè)RestTemplatebean并注入它芽腾。要?jiǎng)?chuàng)建@Bean時(shí)旦装,使用@LoadBalanced限定符來(lái)訪問(wèn)負(fù)載平衡RestTemplate。
重要
請(qǐng)注意下面示例中的普通RestTemplate聲明的@Primary注釋摊滔,以消除不合格的@Autowired注入阴绢。
@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);
}
}
小費(fèi)
如果您看到錯(cuò)誤java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89,請(qǐng)嘗試注入RestOperations或設(shè)置spring.aop.proxyTargetClass=true艰躺。
忽略網(wǎng)絡(luò)接口
有時(shí)呻袭,忽略某些命名網(wǎng)絡(luò)接口是有用的,因此可以將其從服務(wù)發(fā)現(xiàn)注冊(cè)中排除(例如腺兴,在Docker容器中運(yùn)行)左电。可以設(shè)置正則表達(dá)式的列表页响,這將導(dǎo)致所需的網(wǎng)絡(luò)接口被忽略篓足。以下配置將忽略“docker0”接口和以“veth”開(kāi)頭的所有接口。
application.yml
spring:
cloud:
inetutils:
ignoredInterfaces:
- docker0
- veth.*
您還可以強(qiáng)制使用正則表達(dá)式列表中指定的網(wǎng)絡(luò)地址:
application.yml
spring:
cloud:
inetutils:
preferredNetworks:
- 192.168
- 10.0
您也可以強(qiáng)制僅使用站點(diǎn)本地地址闰蚕。有關(guān)更多詳細(xì)信息栈拖,請(qǐng)參閱Inet4Address.html.isSiteLocalAddress())是什么是站點(diǎn)本地地址。
application.yml
spring:
cloud:
inetutils:
useOnlySiteLocalInterfaces: true
Spring Cloud Config
Dalston.RELEASE
Spring Cloud Config為分布式系統(tǒng)中的外部配置提供服務(wù)器和客戶端支持没陡。使用Config Server涩哟,您可以在所有環(huán)境中管理應(yīng)用程序的外部屬性烟瞧。客戶端和服務(wù)器上的概念映射與SpringEnvironment和PropertySource抽象相同染簇,因此它們與Spring應(yīng)用程序非常契合茬斧,但可以與任何以任何語(yǔ)言運(yùn)行的應(yīng)用程序一起使用徐鹤。隨著應(yīng)用程序通過(guò)從開(kāi)發(fā)人員到測(cè)試和生產(chǎn)的部署流程,您可以管理這些環(huán)境之間的配置,并確定應(yīng)用程序具有遷移時(shí)需要運(yùn)行的一切悠轩。服務(wù)器存儲(chǔ)后端的默認(rèn)實(shí)現(xiàn)使用git,因此它輕松支持標(biāo)簽版本的配置環(huán)境急灭,以及可以訪問(wèn)用于管理內(nèi)容的各種工具辽旋。很容易添加替代實(shí)現(xiàn),并使用Spring配置將其插入杂拨。
快速開(kāi)始
啟動(dòng)服務(wù)器:
$ cd spring-cloud-config-server
$ ../mvnw spring-boot:run
該服務(wù)器是一個(gè)Spring Boot應(yīng)用程序专普,所以您可以從IDE運(yùn)行它,而不是喜歡(主類是ConfigServerApplication)弹沽。然后嘗試一個(gè)客戶端:
$ 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"}}
]}
定位資源的默認(rèn)策略是克隆一個(gè)git倉(cāng)庫(kù)(在spring.cloud.config.server.git.uri)檀夹,并使用它來(lái)初始化一個(gè)迷你SpringApplication。小應(yīng)用程序的Environment用于枚舉屬性源并通過(guò)JSON端點(diǎn)發(fā)布策橘。
HTTP服務(wù)具有以下格式的資源:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
其中“應(yīng)用程序”作為SpringApplication中的spring.config.name注入(即常規(guī)的Spring Boot應(yīng)用程序中通常是“應(yīng)用程序”)炸渡,“配置文件”是活動(dòng)配置文件(或逗號(hào)分隔列表的屬性),“l(fā)abel”是可選的git標(biāo)簽(默認(rèn)為“master”)丽已。
Spring Cloud Config服務(wù)器從git存儲(chǔ)庫(kù)(必須提供)為遠(yuǎn)程客戶端提供配置:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
客戶端使用
要在應(yīng)用程序中使用這些功能蚌堵,只需將其構(gòu)建為依賴于spring-cloud-config-client的Spring引導(dǎo)應(yīng)用程序(例如,查看配置客戶端或示例應(yīng)用程序的測(cè)試用例)沛婴。添加依賴關(guān)系的最方便的方法是通過(guò)Spring Boot啟動(dòng)器org.springframework.cloud:spring-cloud-starter-config吼畏。還有一個(gè)Maven用戶的父pom和BOM(spring-cloud-starter-parent)和用于Gradle和Spring CLI用戶的Spring IO版本管理屬性文件。示例Maven配置:
的pom.xml
org.springframework.boot
spring-boot-starter-parent
1.3.5.RELEASE
org.springframework.cloud
spring-cloud-dependencies
Brixton.RELEASE
pom
import
org.springframework.cloud
spring-cloud-starter-config
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
那么你可以創(chuàng)建一個(gè)標(biāo)準(zhǔn)的Spring Boot應(yīng)用程序嘁灯,像這個(gè)簡(jiǎn)單的HTTP服務(wù)器:
@SpringBootApplication
@RestController
public class Application {
@RequestMapping("/")
public String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
當(dāng)它運(yùn)行它將從端口8888上的默認(rèn)本地配置服務(wù)器接收外部配置泻蚊,如果它正在運(yùn)行。要修改啟動(dòng)行為旁仿,您可以使用bootstrap.properties(如application.properties)更改配置服務(wù)器的位置藕夫,但用于應(yīng)用程序上下文的引導(dǎo)階段),例如
spring.cloud.config.uri: http://myconfigserver.com
引導(dǎo)屬性將在/env端點(diǎn)中顯示為高優(yōu)先級(jí)屬性源枯冈,例如
$ curl localhost:8080/env
{
"profiles":[],
"configService:https://github.com/spring-cloud-samples/config-repo/bar.properties":{"foo":"bar"},
"servletContextInitParams":{},
"systemProperties":{...},
...
}
(名為“configService:<遠(yuǎn)程存儲(chǔ)庫(kù)的URL> / <文件名>”的屬性源包含值為“bar”的屬性“foo”毅贮,是最高優(yōu)先級(jí))。
注意
屬性源名稱中的URL是git存儲(chǔ)庫(kù)尘奏,而不是配置服務(wù)器URL滩褥。
Spring Cloud Config服務(wù)器
服務(wù)器為外部配置(名稱值對(duì)或等效的YAML內(nèi)容)提供了基于資源的HTTP。服務(wù)器可以使用@EnableConfigServer注釋輕松嵌入到Spring Boot應(yīng)用程序中炫加。所以這個(gè)應(yīng)用程序是一個(gè)配置服務(wù)器:
ConfigServer.java
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
像所有的默認(rèn)端口8080上運(yùn)行的所有Spring Boot應(yīng)用程序一樣瑰煎,但您可以通過(guò)各種方式將其切換到常規(guī)端口8888惦辛。最簡(jiǎn)單的也是設(shè)置一個(gè)默認(rèn)配置庫(kù)馍佑,它是通過(guò)啟動(dòng)它的spring.config.name=configserver(在Config Server jar中有一個(gè)configserver.yml)俗冻。另一個(gè)是使用你自己的application.properties虚婿,例如
application.properties
server.port: 8888
spring.cloud.config.server.git.uri: file://${user.home}/config-repo
其中${user.home}/config-repo是包含YAML和屬性文件的git倉(cāng)庫(kù)。
注意
在Windows中插勤,如果文件URL為絕對(duì)驅(qū)動(dòng)器前綴沽瘦,例如file:///${user.home}/config-repo,則需要額外的“/”农尖。
小費(fèi)
以下是上面示例中創(chuàng)建git倉(cāng)庫(kù)的方法:
$ 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"
警告
使用本地文件系統(tǒng)進(jìn)行g(shù)it存儲(chǔ)庫(kù)僅用于測(cè)試析恋。使用服務(wù)器在生產(chǎn)環(huán)境中托管配置庫(kù)。
警告
如果您只保留文本文件盛卡,則配置庫(kù)的初始克隆將會(huì)快速有效助隧。如果您開(kāi)始存儲(chǔ)二進(jìn)制文件,尤其是較大的文件滑沧,則可能會(huì)遇到服務(wù)器中第一個(gè)配置請(qǐng)求和/或內(nèi)存不足錯(cuò)誤的延遲并村。
環(huán)境庫(kù)
您要在哪里存儲(chǔ)配置服務(wù)器的配置數(shù)據(jù)?管理此行為的策略是EnvironmentRepository嚎货,服務(wù)于Environment對(duì)象橘霎。此Environment是SpringEnvironment(包括propertySources作為主要功能)的域的淺層副本。Environment資源由三個(gè)變量參數(shù)化:
{application}映射到客戶端的“spring.application.name”;
{profile}映射到客戶端上的“spring.profiles.active”(逗號(hào)分隔列表);和
{label}這是一個(gè)服務(wù)器端功能殖属,標(biāo)記“版本”的配置文件集。
存儲(chǔ)庫(kù)實(shí)現(xiàn)通常表現(xiàn)得像一個(gè)Spring Boot應(yīng)用程序從“spring.config.name”等于{application}參數(shù)加載配置文件瓦盛,“spring.profiles.active”等于{profiles}參數(shù)洗显。配置文件的優(yōu)先級(jí)規(guī)則也與常規(guī)啟動(dòng)應(yīng)用程序相同:活動(dòng)配置文件優(yōu)先于默認(rèn)配置,如果有多個(gè)配置文件原环,則最后一個(gè)獲勝(例如向Map添加條目)挠唆。
示例:客戶端應(yīng)用程序具有此引導(dǎo)配置:
bootstrap.yml
spring:
application:
name: foo
profiles:
active: dev,mysql
(通常使用Spring Boot應(yīng)用程序,這些屬性也可以設(shè)置為環(huán)境變量或命令行參數(shù))嘱吗。
如果存儲(chǔ)庫(kù)是基于文件的玄组,則服務(wù)器將從application.yml創(chuàng)建Environment(在所有客戶端之間共享),foo.yml(以foo.yml優(yōu)先))谒麦。如果YAML文件中有文件指向Spring配置文件俄讹,那么應(yīng)用的優(yōu)先級(jí)更高(按照列出的配置文件的順序),并且如果存在特定于配置文件的YAML(或?qū)傩裕┪募频拢敲催@些文件也應(yīng)用于優(yōu)先級(jí)高于默認(rèn)值患膛。較高優(yōu)先級(jí)轉(zhuǎn)換為Environment之前列出的PropertySource。(這些規(guī)則與獨(dú)立的Spring Boot應(yīng)用程序相同蛹稍。)
Git后端
EnvironmentRepository的默認(rèn)實(shí)現(xiàn)使用Git后端绣版,這對(duì)于管理升級(jí)和物理環(huán)境以及審核更改非常方便。要更改存儲(chǔ)庫(kù)的位置涩金,可以在Config Server中設(shè)置“spring.cloud.config.server.git.uri”配置屬性(例如application.yml)跃捣。如果您使用file:前綴進(jìn)行設(shè)置漱牵,則應(yīng)從本地存儲(chǔ)庫(kù)中工作,以便在沒(méi)有服務(wù)器的情況下快速方便地啟動(dòng)疚漆,但在這種情況下布疙,服務(wù)器將直接在本地存儲(chǔ)庫(kù)上進(jìn)行操作,而不會(huì)克隆如果它不是裸機(jī)愿卸,因?yàn)榕渲梅?wù)器永遠(yuǎn)不會(huì)更改“遠(yuǎn)程”資源庫(kù))灵临。要擴(kuò)展Config Server并使其高度可用,您需要將服務(wù)器的所有實(shí)例指向同一個(gè)存儲(chǔ)庫(kù)趴荸,因此只有共享文件系統(tǒng)才能正常工作儒溉。即使在這種情況下,最好使用共享文件系統(tǒng)存儲(chǔ)庫(kù)的ssh:協(xié)議发钝,以便服務(wù)器可以將其克隆并使用本地工作副本作為緩存顿涣。
該存儲(chǔ)庫(kù)實(shí)現(xiàn)將HTTP資源的{label}參數(shù)映射到git標(biāo)簽(提交ID,分支名稱或標(biāo)簽)酝豪。如果git分支或標(biāo)簽名稱包含斜杠(“/”)涛碑,則應(yīng)使用特殊字符串“(_)”指定HTTP URL中的標(biāo)簽,以避免與其他URL路徑模糊孵淘。例如蒲障,如果標(biāo)簽為foo/bar,則替換斜杠將導(dǎo)致標(biāo)簽看起來(lái)像foo(_)bar瘫证。如果您使用像curl這樣的命令行客戶端(例如使用引號(hào)將其從shell中轉(zhuǎn)出來(lái))揉阎,請(qǐng)小心URL中的方括號(hào)。
Git URI中的占位符
Spring Cloud Config服務(wù)器支持一個(gè)Git倉(cāng)庫(kù)URL背捌,其中包含{application}和{profile}(以及{label})的占位符毙籽,如果需要,請(qǐng)記住標(biāo)簽應(yīng)用為git標(biāo)簽)毡庆。因此坑赡,您可以使用(例如)輕松支持“每個(gè)應(yīng)用程序的一個(gè)repo”策略:
spring:
cloud:
config:
server:
git:
uri: https://github.com/myorg/{application}
或使用類似模式但使用{profile}的“每個(gè)配置文件一個(gè)”策略。
模式匹配和多個(gè)存儲(chǔ)庫(kù)
還可以通過(guò)應(yīng)用程序和配置文件名稱的模式匹配來(lái)支持更復(fù)雜的需求么抗。模式格式是帶有通配符的{application}/{profile}名稱的逗號(hào)分隔列表(可能需要引用以通配符開(kāi)頭的模式)毅否。例:
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
如果{application}/{profile}不匹配任何模式,它將使用在“spring.cloud.config.server.git.uri”下定義的默認(rèn)uri乖坠。在上面的例子中萄喳,對(duì)于“簡(jiǎn)單”存儲(chǔ)庫(kù)杯矩,模式是simple/*(即所有配置文件中只匹配一個(gè)名為“簡(jiǎn)單”的應(yīng)用程序)漂佩《中“本地”存儲(chǔ)庫(kù)與所有配置文件中以“l(fā)ocal”開(kāi)頭的所有應(yīng)用程序名稱匹配(將/*后綴自動(dòng)添加到任何沒(méi)有配置文件匹配器的模式)。
注意
在上述“簡(jiǎn)單”示例中使用的“單行”快捷方式只能在唯一要設(shè)置的屬性為URI的情況下使用。如果您需要設(shè)置其他任何內(nèi)容(憑據(jù),模式等),則需要使用完整的表單施蜜。
repo中的pattern屬性實(shí)際上是一個(gè)數(shù)組,因此您可以使用屬性文件中的YAML數(shù)組(或[0]雌隅,[1]等后綴)綁定到多個(gè)模式翻默。如果要運(yùn)行具有多個(gè)配置文件的應(yīng)用程序,則可能需要執(zhí)行此操作恰起。例:
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
注意
Spring Cloud將猜測(cè)包含不在*中的配置文件的模式意味著您實(shí)際上要匹配從此模式開(kāi)始的配置文件列表(因此*/staging是["*/staging", "*/staging,*"])修械。這是常見(jiàn)的,您需要在本地的“開(kāi)發(fā)”配置文件中運(yùn)行應(yīng)用程序检盼,但也可以遠(yuǎn)程運(yùn)行“云”配置文件肯污。
每個(gè)存儲(chǔ)庫(kù)還可以選擇將配置文件存儲(chǔ)在子目錄中,搜索這些目錄的模式可以指定為searchPaths吨枉。例如在頂層:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
searchPaths: foo,bar*
在此示例中蹦渣,服務(wù)器搜索頂級(jí)和“foo /”子目錄以及名稱以“bar”開(kāi)頭的任何子目錄中的配置文件。
默認(rèn)情況下貌亭,首次請(qǐng)求配置時(shí)柬唯,服務(wù)器克隆遠(yuǎn)程存儲(chǔ)庫(kù)。服務(wù)器可以配置為在啟動(dòng)時(shí)克隆存儲(chǔ)庫(kù)圃庭。例如在頂層:
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
在此示例中锄奢,服務(wù)器在啟動(dòng)之前克隆了team-a的config-repo,然后它接受任何請(qǐng)求冤议。所有其他存儲(chǔ)庫(kù)將不被克隆斟薇,直到請(qǐng)求從存儲(chǔ)庫(kù)配置。
注意
在配置服務(wù)器啟動(dòng)時(shí)設(shè)置要克隆的存儲(chǔ)庫(kù)可以幫助在配置服務(wù)器啟動(dòng)時(shí)快速識(shí)別錯(cuò)誤配置的源(例如恕酸,無(wú)效的存儲(chǔ)庫(kù)URI)。配置源不啟用cloneOnStart時(shí)胯陋,配置服務(wù)器可能會(huì)成功啟動(dòng)配置錯(cuò)誤或無(wú)效的配置源蕊温,而不會(huì)檢測(cè)到錯(cuò)誤,直到應(yīng)用程序從該配置源請(qǐng)求配置為止遏乔。
認(rèn)證
要在遠(yuǎn)程存儲(chǔ)庫(kù)上使用HTTP基本身份驗(yàn)證义矛,請(qǐng)分別添加“username”和“password”屬性(不在URL中)汹买,例如
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
username: trolley
password: strongpassword
如果您不使用HTTPS和用戶憑據(jù)蚊俺,當(dāng)您將密鑰存儲(chǔ)在默認(rèn)目錄(~/.ssh)中,并且uri指向SSH位置時(shí)内颗,SSH也應(yīng)該開(kāi)箱即用捻激,例如“git@github.com:配置/云配置”制轰。必須在~/.ssh/known_hosts文件中存在Git服務(wù)器的條目前计,并且它是ssh-rsa格式。其他格式(如ecdsa-sha2-nistp256)不受支持垃杖。為了避免意外男杈,您應(yīng)該確保Git服務(wù)器的known_hosts文件中只有一個(gè)條目,并且與您提供給配置服務(wù)器的URL匹配调俘。如果您在URL中使用了主機(jī)名伶棒,那么您希望在known_hosts文件中具有這一點(diǎn),而不是IP彩库。使用JGit訪問(wèn)存儲(chǔ)庫(kù)肤无,因此您發(fā)現(xiàn)的任何文檔都應(yīng)適用。HTTPS代理設(shè)置可以~/.git/config設(shè)置骇钦,也可以通過(guò)系統(tǒng)屬性(-Dhttps.proxyHost和-Dhttps.proxyPort)與任何其他JVM進(jìn)程相同宛渐。
小費(fèi)
如果您不知道~/.git目錄使用git config --global來(lái)處理設(shè)置的位置(例如git config --global http.sslVerify false)。
使用AWS CodeCommit進(jìn)行認(rèn)證
AWS CodeCommit認(rèn)證也可以完成司忱。當(dāng)從命令行使用Git時(shí)皇忿,AWS CodeCommit使用身份驗(yàn)證助手。該幫助器不與JGit庫(kù)一起使用坦仍,因此如果Git URI與AWS CodeCommit模式匹配鳍烁,則將創(chuàng)建用于AWS CodeCommit的JGit CredentialProvider。AWS CodeCommit URI始終看起來(lái)像https://git-codecommit.$ {AWS_REGION} .amazonaws.com / $ {repopath}繁扎。
如果您使用AWS CodeCommit URI提供用戶名和密碼幔荒,那么這些URI必須是用于訪問(wèn)存儲(chǔ)庫(kù)的AWS accessKeyId和secretAccessKey。如果不指定用戶名和密碼梳玫,則將使用AWS默認(rèn)憑據(jù)提供程序鏈檢索accessKeyId和secretAccessKey爹梁。
如果您的Git URI與CodeCommit URI模式(上述)匹配,則必須在用戶名和密碼或默認(rèn)憑據(jù)提供程序鏈支持的某個(gè)位置中提供有效的AWS憑據(jù)提澎。AWS EC2實(shí)例可以使用EC2實(shí)例的IAM角色姚垃。
注意:aws-java-sdk-core jar是一個(gè)可選的依賴關(guān)系。如果aws-java-sdk-core jar不在您的類路徑上盼忌,則無(wú)論git服務(wù)器URI如何积糯,都將不會(huì)創(chuàng)建AWS代碼提交憑據(jù)提供程序轨淌。
Git搜索路徑中的占位符
Spring Cloud Config服務(wù)器還支持具有{application}和{profile}(以及{label}(如果需要))占位符的搜索路徑训措。例:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
searchPaths: '{application}'
在資源庫(kù)中搜索與目錄(以及頂級(jí))相同名稱的文件瘩绒。通配符在具有占位符的搜索路徑中也是有效的(搜索中包含任何匹配的目錄)肠鲫。
力拉入Git存儲(chǔ)庫(kù)
如前所述Spring Cloud Config服務(wù)器克隆遠(yuǎn)程git存儲(chǔ)庫(kù)黄橘,如果某種方式本地副本變臟(例如跳夭,通過(guò)操作系統(tǒng)進(jìn)程更改文件夾內(nèi)容)驹饺,則Spring Cloud Config服務(wù)器無(wú)法從遠(yuǎn)程存儲(chǔ)庫(kù)更新本地副本侣夷。
要解決這個(gè)問(wèn)題,有一個(gè)force-pull屬性梦重,如果本地副本是臟的兑燥,將使Spring Cloud Config Server強(qiáng)制從遠(yuǎn)程存儲(chǔ)庫(kù)拉。例:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
force-pull: true
如果您有多個(gè)存儲(chǔ)庫(kù)配置忍饰,則可以為每個(gè)存儲(chǔ)庫(kù)配置force-pull屬性贪嫂。例:
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
注意
force-pull屬性的默認(rèn)值為false。
版本控制后端文件系統(tǒng)使用
警告
使用基于VCS的后端(git艾蓝,svn)文件被檢出或克隆到本地文件系統(tǒng)力崇。默認(rèn)情況下,它們放在系統(tǒng)臨時(shí)目錄中赢织,前綴為config-repo-亮靴。在linux上,例如可以是/tmp/config-repo-于置。一些操作系統(tǒng)會(huì)定期清除臨時(shí)目錄茧吊。這可能會(huì)導(dǎo)致意外的行為,例如缺少屬性八毯。為避免此問(wèn)題搓侄,請(qǐng)通過(guò)將spring.cloud.config.server.git.basedir或spring.cloud.config.server.svn.basedir設(shè)置為不駐留在系統(tǒng)臨時(shí)結(jié)構(gòu)中的目錄來(lái)更改Config Server使用的目錄。
文件系統(tǒng)后端
配置服務(wù)器中還有一個(gè)不使用Git的“本機(jī)”配置文件话速,只是從本地類路徑或文件系統(tǒng)加載配置文件(您想要指向的任何靜態(tài)URL“spring.cloud.config.server .native.searchLocations“)讶踪。要使用本機(jī)配置文件,只需使用“spring.profiles.active = native”啟動(dòng)Config Server泊交。
注意
請(qǐng)記住使用file:前綴的文件資源(缺省沒(méi)有前綴通常是classpath)乳讥。與任何Spring Boot配置一樣,您可以嵌入${}樣式的環(huán)境占位符廓俭,但請(qǐng)記住云石,Windows中的絕對(duì)路徑需要額外的“/”,例如file:///${user.home}/config-repo
警告
searchLocations的默認(rèn)值與本地Spring Boot應(yīng)用程序(所以[classpath:/, classpath:/config, file:./, file:./config])相同研乒。這不會(huì)將application.properties從服務(wù)器暴露給所有客戶端损敷,因?yàn)樵诎l(fā)送到客戶端之前肄扎,服務(wù)器中存在的任何屬性源都將被刪除旭从。
小費(fèi)
文件系統(tǒng)后端對(duì)于快速入門和測(cè)試是非常好的华嘹。要在生產(chǎn)中使用它,您需要確保文件系統(tǒng)是可靠的橄唬,并在配置服務(wù)器的所有實(shí)例中共享。
搜索位置可以包含{application}参歹,{profile}和{label}的占位符仰楚。以這種方式,您可以隔離路徑中的目錄,并選擇一個(gè)有用的策略(例如每個(gè)應(yīng)用程序的子目錄或每個(gè)配置文件的子目錄)僧界。
如果您不在搜索位置使用占位符侨嘀,則該存儲(chǔ)庫(kù)還將HTTP資源的{label}參數(shù)附加到搜索路徑上的后綴,因此屬性文件將從每個(gè)搜索位置加載并具有相同名稱的子目錄作為標(biāo)簽(標(biāo)記的屬性在Spring環(huán)境中優(yōu)先)捂襟。因此咬腕,沒(méi)有占位符的默認(rèn)行為與添加以/{label}/. For example `file:/tmp/config結(jié)尾的搜索位置與file:/tmp/config,file:/tmp/config/{label}相同
Vault后端
Spring Cloud Config服務(wù)器還支持Vault作為后端。
Vault是安全訪問(wèn)秘密的工具葬荷。一個(gè)秘密是你想要嚴(yán)格控制訪問(wèn)的任何東西涨共,如API密鑰,密碼宠漩,證書等等举反。Vault為任何秘密提供統(tǒng)一的界面,同時(shí)提供嚴(yán)格的訪問(wèn)控制和記錄詳細(xì)的審核日志扒吁。
有關(guān)Vault的更多信息火鼻,請(qǐng)參閱Vault快速入門指南。
要使配置服務(wù)器使用Vault后端雕崩,您必須使用vault配置文件運(yùn)行配置服務(wù)器魁索。例如在配置服務(wù)器的application.properties中,您可以添加spring.profiles.active=vault盼铁。
默認(rèn)情況下粗蔚,配置服務(wù)器將假定您的Vault服務(wù)器正在運(yùn)行于http://127.0.0.1:8200。它還將假定后端名稱為secret捉貌,密鑰為application支鸡。所有這些默認(rèn)值都可以在配置服務(wù)器的application.properties中配置。以下是可配置Vault屬性的表趁窃。所有屬性前綴為spring.cloud.config.server.vault牧挣。
名稱默認(rèn)值
host
127.0.0.1
port
8200
scheme
HTTP
backend
秘密
defaultKey
應(yīng)用
profileSeparator
,
所有可配置的屬性可以在org.springframework.cloud.config.server.environment.VaultEnvironmentRepository找到知押。
運(yùn)行配置服務(wù)器后草姻,可以向服務(wù)器發(fā)出HTTP請(qǐng)求,以從Vault后端檢索值些举。為此刨摩,您需要為Vault服務(wù)器創(chuàng)建一個(gè)令牌寺晌。
首先放置一些數(shù)據(jù)給你Vault。例如
$ vault write secret/application foo=bar baz=bam
$ vault write secret/myapp foo=myappsbar
現(xiàn)在澡刹,將HTTP請(qǐng)求發(fā)送給您的配置服務(wù)器以檢索值呻征。
$ curl -X "GET" "http://localhost:8888/myapp/default" -H "X-Config-Token: yourtoken"
在提出上述要求后,您應(yīng)該會(huì)看到類似的回復(fù)罢浇。
{
"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"
}
}
]
}
多個(gè)Properties來(lái)源
使用Vault時(shí)陆赋,您可以為應(yīng)用程序提供多個(gè)屬性源沐祷。例如,假設(shè)您已將數(shù)據(jù)寫入Vault中的以下路徑攒岛。
secret/myApp,dev
secret/myApp
secret/application,dev
secret/application
寫入secret/application的Properties可用于使用配置服務(wù)器的所有應(yīng)用程序赖临。名稱為myApp的應(yīng)用程序?qū)⒕哂袑懭雜ecret/myApp和secret/application的任何屬性。當(dāng)myApp啟用dev配置文件時(shí)灾锯,寫入所有上述路徑的屬性將可用兢榨,列表中第一個(gè)路徑中的屬性優(yōu)先于其他路徑。
與所有應(yīng)用共享配置
基于文件的存儲(chǔ)庫(kù)
使用基于文件(即git顺饮,svn和native)的存儲(chǔ)庫(kù)吵聪,文件名為application*的資源在所有客戶端應(yīng)用程序(所以application.properties,application.yml领突,application-*.properties等)之間共享)暖璧。您可以使用這些文件名的資源來(lái)配置全局默認(rèn)值,并根據(jù)需要將其覆蓋應(yīng)用程序特定的文件君旦。
#_property_overrides [屬性覆蓋]功能也可用于設(shè)置全局默認(rèn)值澎办,并且允許占位符應(yīng)用程序在本地覆蓋它們。
小費(fèi)
使用“本機(jī)”配置文件(本地文件系統(tǒng)后端)金砍,建議您使用不屬于服務(wù)器自身配置的顯式搜索位置局蚀。否則,默認(rèn)搜索位置中的application*資源將被刪除恕稠,因?yàn)樗鼈兪欠?wù)器的一部分琅绅。
Vault服務(wù)器
當(dāng)使用Vault作為后端時(shí),可以通過(guò)將配置放在secret/application中與所有應(yīng)用程序共享配置鹅巍。例如千扶,如果您運(yùn)行此Vault命令
$ vault write secret/application foo=bar baz=bam
使用配置服務(wù)器的所有應(yīng)用程序都可以使用屬性foo和baz搜囱。
復(fù)合環(huán)境庫(kù)
在某些情況下捅儒,您可能希望從多個(gè)環(huán)境存儲(chǔ)庫(kù)中提取配置數(shù)據(jù)缕减。為此装畅,只需在配置服務(wù)器的應(yīng)用程序?qū)傩曰験AML文件中啟用多個(gè)配置文件即可。例如梳码,如果您要從Git存儲(chǔ)庫(kù)以及SVN存儲(chǔ)庫(kù)中提取配置數(shù)據(jù)板惑,那么您將為配置服務(wù)器設(shè)置以下屬性必搞。
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
除了指定URI的每個(gè)repo之外枫攀,還可以指定order屬性括饶。order屬性允許您指定所有存儲(chǔ)庫(kù)的優(yōu)先級(jí)順序。order屬性的數(shù)值越低来涨,優(yōu)先級(jí)越高图焰。存儲(chǔ)庫(kù)的優(yōu)先順序?qū)⒂兄诮鉀Q包含相同屬性的值的存儲(chǔ)庫(kù)之間的任何潛在沖突。
注意
從環(huán)境倉(cāng)庫(kù)檢索值時(shí)的任何類型的故障將導(dǎo)致整個(gè)復(fù)合環(huán)境的故障蹦掐。
注意
當(dāng)使用復(fù)合環(huán)境時(shí)楞泼,重要的是所有repos都包含相同的標(biāo)簽驰徊。如果您有類似于上述的環(huán)境,并且使用標(biāo)簽master請(qǐng)求配置數(shù)據(jù)堕阔,但是SVN repo不包含稱為master的分支,則整個(gè)請(qǐng)求將失敗颗味。
自定義復(fù)合環(huán)境庫(kù)
除了使用來(lái)自Spring Cloud的環(huán)境存儲(chǔ)庫(kù)之外超陆,還可以提供自己的EnvironmentRepositorybean作為復(fù)合環(huán)境的一部分。要做到這一點(diǎn)浦马,你的bean必須實(shí)現(xiàn)EnvironmentRepository接口时呀。如果要在復(fù)合環(huán)境中控制自定義EnvironmentRepository的優(yōu)先級(jí),您還應(yīng)該實(shí)現(xiàn)Ordered接口并覆蓋getOrdered方法晶默。如果您不實(shí)現(xiàn)Ordered接口谨娜,那么您的EnvironmentRepository將被賦予最低優(yōu)先級(jí)。
屬性覆蓋
配置服務(wù)器具有“覆蓋”功能磺陡,允許操作員為應(yīng)用程序使用普通的Spring Boot鉤子不會(huì)意外更改的所有應(yīng)用程序提供配置屬性趴梢。要聲明覆蓋,只需將名稱/值對(duì)的地圖添加到spring.cloud.config.server.overrides币他。例如
spring:
cloud:
config:
server:
overrides:
foo: bar
將導(dǎo)致配置客戶端的所有應(yīng)用程序獨(dú)立于自己的配置讀取foo=bar坞靶。(當(dāng)然,應(yīng)用程序可以以任何方式使用Config Server中的數(shù)據(jù)蝴悉,因此覆蓋不可強(qiáng)制執(zhí)行彰阴,但如果它們是Spring Cloud Config客戶端也糊,則它們確實(shí)提供有用的默認(rèn)行為肄满。)
小費(fèi)
通過(guò)使用反斜杠(“\”)來(lái)轉(zhuǎn)義“$”或“{”,例如\${app.foo:bar}解析允瞧,可以轉(zhuǎn)義正常的Spring具有“$ {}”的環(huán)境占位符到“bar”庆杜,除非應(yīng)用程序提供自己的“app.foo”射众。請(qǐng)注意,在YAML中欣福,您不需要轉(zhuǎn)義反斜杠本身责球,而是在您執(zhí)行的屬性文件中配置服務(wù)器上的覆蓋。
您可以通過(guò)在遠(yuǎn)程存儲(chǔ)庫(kù)中設(shè)置標(biāo)志spring.cloud.config.overrideNone=true(默認(rèn)為false)拓劝,將客戶端中所有覆蓋的優(yōu)先級(jí)更改為更為默認(rèn)值雏逾,允許應(yīng)用程序在環(huán)境變量或系統(tǒng)屬性中提供自己的值。
健康指標(biāo)
配置服務(wù)器附帶運(yùn)行狀況指示器郑临,檢查配置的EnvironmentRepository是否正常工作栖博。默認(rèn)情況下,它要求EnvironmentRepository應(yīng)用程序名稱為app厢洞,default配置文件和EnvironmentRepository實(shí)現(xiàn)提供的默認(rèn)標(biāo)簽仇让。
您可以配置運(yùn)行狀況指示器以檢查更多應(yīng)用程序以及自定義配置文件和自定義標(biāo)簽典奉,例如
spring:
cloud:
config:
server:
health:
repositories:
myservice:
label: mylabel
myservice-dev:
name: myservice
profiles: development
您可以通過(guò)設(shè)置spring.cloud.config.server.health.enabled=false來(lái)禁用運(yùn)行狀況指示器。
安全
您可以以任何對(duì)您有意義的方式(從物理網(wǎng)絡(luò)安全性到OAuth2承載令牌)保護(hù)您的Config Server丧叽,并且Spring Security和Spring Boot可以輕松做任何事情卫玖。
要使用默認(rèn)的Spring Boot配置的HTTP Basic安全性,只需在類路徑中包含Spring Security(例如通過(guò)spring-boot-starter-security)踊淳。默認(rèn)值為“user”的用戶名和隨機(jī)生成的密碼假瞬,這在實(shí)踐中不會(huì)非常有用,因此建議您配置密碼(通過(guò)security.user.password)并對(duì)其進(jìn)行加密(請(qǐng)參閱下文的說(shuō)明怎么做)迂尝。
加密和解密
重要
先決條件:要使用加密和解密功能脱茉,您需要在JVM中安裝全面的JCE(默認(rèn)情況下不存在)。您可以從Oracle下載“Java加密擴(kuò)展(JCE)無(wú)限強(qiáng)度管理策略文件”垄开,并按照安裝說(shuō)明(實(shí)際上將JRE lib / security目錄中的2個(gè)策略文件替換為您下載的文件)琴许。
如果遠(yuǎn)程屬性源包含加密內(nèi)容(以{cipher}開(kāi)頭的值),則在通過(guò)HTTP發(fā)送到客戶端之前溉躲,它們將被解密榜田。這種設(shè)置的主要優(yōu)點(diǎn)是,當(dāng)它們“靜止”時(shí)签财,屬性值不必是純文本(例如在git倉(cāng)庫(kù)中)串慰。如果值無(wú)法解密熟菲,則從屬性源中刪除該值菜职,并添加具有相同鍵的附加屬性,但以“無(wú)效”作為前綴杈抢。和“不適用”的值(通常為“”)神汹。這主要是為了防止密碼被用作密碼并意外泄漏庆捺。
如果要為config客戶端應(yīng)用程序設(shè)置遠(yuǎn)程配置存儲(chǔ)庫(kù),可能會(huì)包含一個(gè)application.yml屁魏,例如:
application.yml
spring:
datasource:
username: dbuser
password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
.properties文件中的加密值不能用引號(hào)括起來(lái)滔以,否則不會(huì)解密該值:
application.properties
spring.datasource.username: dbuser
spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
您可以安全地將此純文本推送到共享git存儲(chǔ)庫(kù),并且保密密碼氓拼。
服務(wù)器還暴露了/encrypt和/decrypt端點(diǎn)(假設(shè)這些端點(diǎn)將被保護(hù)你画,并且只能由授權(quán)代理訪問(wèn))。如果您正在編輯遠(yuǎn)程配置文件桃漾,可以使用Config Server通過(guò)POST到/encrypt端點(diǎn)來(lái)加密值坏匪,例如
$ curl localhost:8888/encrypt -d mysecret
682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
注意
如果要加密的值具有需要進(jìn)行URL編碼的字符,則應(yīng)使用--data-urlencode選項(xiàng)curl來(lái)確保它們已正確編碼撬统。
逆向操作也可通過(guò)/decrypt獲得(如果服務(wù)器配置了對(duì)稱密鑰或全密鑰對(duì)):
$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
mysecret
小費(fèi)
如果您使用curl進(jìn)行測(cè)試适滓,則使用--data-urlencode(而不是-d)或設(shè)置顯式Content-Type: text/plain,以確保在有特殊字符時(shí)正確地對(duì)數(shù)據(jù)進(jìn)行編碼('+'特別是棘手)恋追。
將加密的值添加到{cipher}前綴凭迹,然后再將其放入YAML或?qū)傩晕募蟹N荩缓笤偬峤徊⑵渫扑偷竭h(yuǎn)程可能不安全的存儲(chǔ)區(qū)。
/encrypt和/decrypt端點(diǎn)也都接受/*/{name}/{profiles}形式的路徑嗅绸,當(dāng)客戶端調(diào)用到主環(huán)境資源時(shí)脾猛,可以用于每個(gè)應(yīng)用程序(名稱)和配置文件控制密碼。
注意
為了以這種細(xì)微的方式控制密碼朽砰,您還必須提供一種TextEncryptorLocator類型的@Bean尖滚,可以為每個(gè)名稱和配置文件創(chuàng)建不同的加密器。默認(rèn)提供的不會(huì)這樣做(所有加密使用相同的密鑰)瞧柔。
spring命令行客戶端(安裝了Spring Cloud CLI擴(kuò)展)也可以用于加密和解密,例如
$ spring encrypt mysecret --key foo
682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
$ spring decrypt --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
mysecret
要在文件中使用密鑰(例如用于加密的RSA公鑰)睦裳,使用“@”鍵入鍵值造锅,并提供文件路徑,例如
$ spring encrypt mysecret --key @${HOME}/.ssh/id_rsa.pub
AQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+...
關(guān)鍵參數(shù)是強(qiáng)制性的(盡管有一個(gè)--前綴)廉邑。
密鑰管理
Config Server可以使用對(duì)稱(共享)密鑰或非對(duì)稱密鑰(RSA密鑰對(duì))哥蔚。非對(duì)稱選擇在安全性方面是優(yōu)越的,但是使用對(duì)稱密鑰往往更方便蛛蒙,因?yàn)樗皇桥渲玫囊粋€(gè)屬性值椿肩。
要配置對(duì)稱密鑰箕母,您只需要將encrypt.key設(shè)置為一個(gè)秘密字符串(或使用環(huán)境變量ENCRYPT_KEY將其從純文本配置文件中刪除)。
要配置非對(duì)稱密鑰,您可以將密鑰設(shè)置為PEM編碼的文本值(encrypt.key)辈双,也可以通過(guò)密鑰庫(kù)設(shè)置密鑰(例如由JDK附帶的keytool實(shí)用程序創(chuàng)建)。密鑰庫(kù)屬性為encrypt.keyStore.*躏啰,*等于
location(aResource位置)婶博,
password(解鎖密鑰庫(kù))和
alias(以識(shí)別商店中使用的密鑰)。
使用公鑰進(jìn)行加密收奔,需要私鑰進(jìn)行解密掌呜。因此,原則上您只能在服務(wù)器中配置公鑰坪哄,如果您只想進(jìn)行加密(并準(zhǔn)備使用私鑰本地解密值)质蕉。實(shí)際上,您可能不想這樣做翩肌,因?yàn)樗鼑@所有客戶端傳播密鑰管理流程模暗,而不是將其集中在服務(wù)器中。另一方面摧阅,如果您的配置服務(wù)器真的相對(duì)不安全汰蓉,并且只有少數(shù)客戶端需要加密的屬性,這是一個(gè)有用的選項(xiàng)棒卷。
創(chuàng)建用于測(cè)試的密鑰庫(kù)
要?jiǎng)?chuàng)建一個(gè)密鑰庫(kù)進(jìn)行測(cè)試顾孽,您可以執(zhí)行以下操作:
$ 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
將server.jks文件放在類路徑(例如)中祝钢,然后在您的application.yml中配置服務(wù)器:
encrypt:
keyStore:
location: classpath:/server.jks
password: letmein
alias: mytestkey
secret: changeme
使用多個(gè)鍵和鍵旋轉(zhuǎn)
除了加密屬性值中的{cipher}前綴之外,配置服務(wù)器在(Base64編碼)密文開(kāi)始前查找{name:value}前綴(零或多個(gè))若厚。密鑰被傳遞給TextEncryptorLocator拦英,它可以執(zhí)行找到密碼的TextEncryptor所需的任何邏輯。如果配置了密鑰庫(kù)(encrypt.keystore.location)测秸,默認(rèn)定位器將使用“key”前綴提供的別名疤估,即使用如下密碼查找存儲(chǔ)中的密鑰:
foo:
bar: `{cipher}{key:testkey}...`
定位器將尋找一個(gè)名為“testkey”的鍵。也可以通過(guò)前綴中的{secret:…?}值提供一個(gè)秘密霎冯,但是如果不是默認(rèn)值铃拇,則使用密鑰庫(kù)密碼(這是您在構(gòu)建密鑰庫(kù)時(shí)獲得的,并且不指定密碼)沈撞。如果你這樣做提供一個(gè)秘密建議你也加密使用自定義SecretLocator的秘密慷荔。
如果密鑰只用于加密幾個(gè)字節(jié)的配置數(shù)據(jù)(即它們沒(méi)有在其他地方使用),則密碼轉(zhuǎn)換幾乎不是必需的缠俺,但是如果存在安全漏洞棋弥,有時(shí)您可能需要更改密鑰實(shí)例恐锦。在這種情況下头镊,所有客戶端都需要更改其源配置文件(例如毛好,以git格式),并在所有密碼中使用新的{key:…?}前綴躏救,當(dāng)然事先檢查密鑰別名在配置服務(wù)器密鑰庫(kù)中是否可用唯笙。
小費(fèi)
如果要讓Config Server處理所有加密以及解密,也可以將{name:value}前綴添加到發(fā)布到/encrypt端點(diǎn)的明文中落剪。
服務(wù)加密Properties
有時(shí)您希望客戶端在本地解密配置睁本,而不是在服務(wù)器中進(jìn)行配置。在這種情況下忠怖,您仍然可以擁有/加密和解密端點(diǎn)(如果您提供encrypt.*配置來(lái)定位密鑰)呢堰,但是您需要使用spring.cloud.config.server.encrypt.enabled=false明確地關(guān)閉傳出屬性的解密。如果您不關(guān)心端點(diǎn)凡泣,那么如果您既不配置密鑰也不配置使能的標(biāo)志枉疼,則應(yīng)該起作用。
服務(wù)替代格式
來(lái)自環(huán)境端點(diǎn)的默認(rèn)JSON格式對(duì)于Spring應(yīng)用程序的消費(fèi)是完美的鞋拟,因?yàn)樗苯佑成涞紼nvironment抽象骂维。如果您喜歡,可以通過(guò)向資源路徑(“.yml”贺纲,“.yaml”或“.properties”)添加后綴來(lái)使用與YAML或Java屬性相同的數(shù)據(jù)航闺。這對(duì)于不關(guān)心JSON端點(diǎn)的結(jié)構(gòu)的應(yīng)用程序或其提供的額外的元數(shù)據(jù)的應(yīng)用程序來(lái)說(shuō)可能是有用的,例如,不使用Spring的應(yīng)用程序可能會(huì)受益于此方法的簡(jiǎn)單性潦刃。
YAML和屬性表示有一個(gè)額外的標(biāo)志(作為一個(gè)布爾查詢參數(shù)resolvePlaceholders提供))侮措,以標(biāo)示Spring${…?}形式的源文檔中的占位符,應(yīng)在輸出中解析可能在渲染之前乖杠。對(duì)于不了解Spring占位符慣例的消費(fèi)者來(lái)說(shuō)分扎,這是一個(gè)有用的功能。
注意
使用YAML或?qū)傩愿袷酱嬖诰窒扌噪嗜鳎饕桥c元數(shù)據(jù)的丟失有關(guān)畏吓。JSON被構(gòu)造為屬性源的有序列表,例如卫漫,名稱與源相關(guān)聯(lián)菲饼。即使源的起源具有多個(gè)源,并且原始源文件的名稱丟失列赎,YAML和屬性表也合并成一個(gè)映射巴粪。YAML表示不一定是后臺(tái)存儲(chǔ)庫(kù)中YAML源的忠實(shí)表示:它是由平面屬性源的列表構(gòu)建的,并且必須對(duì)鍵的形式進(jìn)行假設(shè)。
服務(wù)純文本
您的應(yīng)用程序可能需要通用的純文本配置文件脓魏,而不是使用Environment抽象(或YAML中的其他替代表示形式或?qū)傩愿袷剑┒ケ稹E渲梅?wù)器通過(guò)/{name}/{profile}/{label}/{path}附加的端點(diǎn)提供這些服務(wù),其中“name”宅粥,“profile”和“l(fā)abel”的含義與常規(guī)環(huán)境端點(diǎn)相同,但“path”是文件名(例如log.xml)。此端點(diǎn)的源文件位于與環(huán)境端點(diǎn)相同的方式:與屬性或YAML文件相同的搜索路徑掺喻,而不是聚合所有匹配的資源,只返回匹配的第一個(gè)储矩。
找到資源后感耙,使用正確格式(${…?})的占位符將使用有效的Environment解析為應(yīng)用程序名稱,配置文件和標(biāo)簽提供持隧。以這種方式即硼,資源端點(diǎn)與環(huán)境端點(diǎn)緊密集成。例如屡拨,如果您有一個(gè)GIT(或SVN)資源庫(kù)的布局:
application.yml
nginx.conf
其中nginx.conf如下所示:
server {
listen? ? ? ? ? ? ? 80;
server_name? ? ? ? ${nginx.server.name};
}
和application.yml這樣:
nginx:
server:
name: example.com
---
spring:
profiles: development
nginx:
server:
name: develop.com
那么/foo/default/master/nginx.conf資源如下所示:
server {
listen? ? ? ? ? ? ? 80;
server_name? ? ? ? example.com;
}
和/foo/development/master/nginx.conf這樣:
server {
listen? ? ? ? ? ? ? 80;
server_name? ? ? ? develop.com;
}
注意
就像環(huán)境配置的源文件一樣只酥,“配置文件”用于解析文件名,因此呀狼,如果您想要一個(gè)特定于配置文件的文件裂允,則/*/development/*/logback.xml將由一個(gè)名為logback-development.xml的文件解析(優(yōu)先于logback.xml)。
嵌入配置服務(wù)器
配置服務(wù)器最好作為獨(dú)立應(yīng)用程序運(yùn)行哥艇,但如果需要绝编,可以將其嵌入到另一個(gè)應(yīng)用程序中。只需使用@EnableConfigServer注釋。在這種情況下可以使用的可選屬性是spring.cloud.config.server.bootstrap十饥,它是一個(gè)標(biāo)志窟勃,表示服務(wù)器應(yīng)該從其自己的遠(yuǎn)程存儲(chǔ)庫(kù)配置自身。該標(biāo)志默認(rèn)關(guān)閉绷跑,因?yàn)樗赡軙?huì)延遲啟動(dòng)拳恋,但是當(dāng)嵌入在另一個(gè)應(yīng)用程序中時(shí),以與其他應(yīng)用程序相同的方式初始化是有意義的砸捏。
注意
應(yīng)該是顯而易見(jiàn)的谬运,但請(qǐng)記住,如果您使用引導(dǎo)標(biāo)志垦藏,配置服務(wù)器將需要在bootstrap.yml中配置其名稱和存儲(chǔ)庫(kù)URI梆暖。
要更改服務(wù)器端點(diǎn)的位置,您可以(可選)設(shè)置spring.cloud.config.server.prefix掂骏,例如“/ config”轰驳,以提供前綴下的資源梯找。前綴應(yīng)該開(kāi)始但不以“/”結(jié)尾浦夷。它被應(yīng)用于配置服務(wù)器中的@RequestMappings(即Spring Boot前綴server.servletPath和server.contextPath)之下。
如果您想直接從后端存儲(chǔ)庫(kù)(而不是從配置服務(wù)器)讀取應(yīng)用程序的配置试伙,這基本上是一個(gè)沒(méi)有端點(diǎn)的嵌入式配置服務(wù)器田绑。如果不使用@EnableConfigServer注釋(僅設(shè)置spring.cloud.config.server.bootstrap=true)勤哗,則可以完全關(guān)閉端點(diǎn)。
推送通知和Spring Cloud Bus
許多源代碼存儲(chǔ)庫(kù)提供程序(例如Github掩驱,Gitlab或Bitbucket)將通過(guò)webhook通知您存儲(chǔ)庫(kù)中的更改芒划。您可以通過(guò)提供商的用戶界面將webhook配置為URL和一組感興趣的事件。例如欧穴,Github將使用包含提交列表的JSON主體和“X-Github-Event”等于“push”的頭文件發(fā)送到webhook民逼。如果在spring-cloud-config-monitor庫(kù)中添加依賴關(guān)系并激活配置服務(wù)器中的Spring Cloud Bus,則啟用“/ monitor”端點(diǎn)涮帘。
當(dāng)Webhook被激活時(shí)拼苍,配置服務(wù)器將發(fā)送一個(gè)RefreshRemoteApplicationEvent針對(duì)他認(rèn)為可能已經(jīng)改變的應(yīng)用程序。變更檢測(cè)可以進(jìn)行策略化焚辅,但默認(rèn)情況下映屋,它只是查找與應(yīng)用程序名稱匹配的文件的更改(例如,“foo.properties”針對(duì)的是“foo”應(yīng)用程序同蜻,“application.properties”針對(duì)所有應(yīng)用程序) 棚点。如果要覆蓋該行為的策略是PropertyPathNotificationExtractor,它接受??請(qǐng)求標(biāo)頭和正文作為參數(shù)湾蔓,并返回更改的文件路徑列表瘫析。
默認(rèn)配置與Github,Gitlab或Bitbucket配合使用。除了來(lái)自Github贬循,Gitlab或Bitbucket的JSON通知之外咸包,您還可以通過(guò)使用表單編碼的身體參數(shù)path={name}通過(guò)POST為“/ monitor”來(lái)觸發(fā)更改通知。這將廣播到匹配“{name}”模式的應(yīng)用程序(可以包含通配符)杖虾。
注意
只有在配置服務(wù)器和客戶端應(yīng)用程序中激活spring-cloud-bus時(shí)才會(huì)傳送RefreshRemoteApplicationEvent烂瘫。
注意
默認(rèn)配置還檢測(cè)本地git存儲(chǔ)庫(kù)中的文件系統(tǒng)更改(在這種情況下不使用webhook,但是一旦編輯配置文件奇适,將會(huì)播放刷新)坟比。
Spring Cloud Config客戶端
Spring Boot應(yīng)用程序可以立即利用Spring配置服務(wù)器(或應(yīng)用程序開(kāi)發(fā)人員提供的其他外部屬性源),并且還將獲取與Environment更改事件相關(guān)的一些其他有用功能嚷往。
配置第一引導(dǎo)
這是在類路徑上具有Spring Cloud Config Client的任何應(yīng)用程序的默認(rèn)行為葛账。配置客戶端啟動(dòng)時(shí),它將通過(guò)配置服務(wù)器(通過(guò)引導(dǎo)配置屬性spring.cloud.config.uri)綁定,并使用遠(yuǎn)程屬性源初始化SpringEnvironment。
這樣做的最終結(jié)果是所有想要使用Config Server的客戶端應(yīng)用程序需要bootstrap.yml(或環(huán)境變量)哩盲,服務(wù)器地址位于spring.cloud.config.uri(默認(rèn)為“http:// localhost:8888” )。
發(fā)現(xiàn)第一個(gè)引導(dǎo)
如果您正在使用DiscoveryClient實(shí)現(xiàn)趋急,例如Spring Cloud Netflix和Eureka服務(wù)發(fā)現(xiàn)或Spring Cloud Consul(Spring Cloud Zookeeper不支持此功能),那么您可以使用Config Server如果您想要發(fā)現(xiàn)服務(wù)注冊(cè)势誊,但在默認(rèn)的“配置優(yōu)先”模式下宣谈,客戶端將無(wú)法利用注冊(cè)。
如果您希望使用DiscoveryClient找到配置服務(wù)器键科,可以通過(guò)設(shè)置spring.cloud.config.discovery.enabled=true(默認(rèn)為“false”)來(lái)實(shí)現(xiàn)。最終的結(jié)果是漩怎,客戶端應(yīng)用程序都需要具有適當(dāng)發(fā)現(xiàn)配置的bootstrap.yml(或環(huán)境變量)勋颖。例如,使用Spring Cloud Netflix勋锤,您需要定義Eureka服務(wù)器地址饭玲,例如eureka.client.serviceUrl.defaultZone。使用此選項(xiàng)的價(jià)格是啟動(dòng)時(shí)額外的網(wǎng)絡(luò)往返叁执,以定位服務(wù)注冊(cè)茄厘。好處是配置服務(wù)器可以更改其坐標(biāo),只要發(fā)現(xiàn)服務(wù)是一個(gè)固定點(diǎn)谈宛。默認(rèn)的服務(wù)標(biāo)識(shí)是“configserver”次哈,但您可以使用spring.cloud.config.discovery.serviceId在客戶端進(jìn)行更改(在服務(wù)器上以服務(wù)的通常方式更改,例如設(shè)置spring.application.name)吆录。
發(fā)現(xiàn)客戶端實(shí)現(xiàn)都支持某種元數(shù)據(jù)映射(例如Eureka窑滞,我們有eureka.instance.metadataMap)。可能需要在其服務(wù)注冊(cè)元數(shù)據(jù)中配置Config Server的一些其他屬性哀卫,以便客戶端可以正確連接巨坊。如果使用HTTP Basic安全配置服務(wù)器,則可以將憑據(jù)配置為“用戶名”和“密碼”此改。并且如果配置服務(wù)器具有上下文路徑趾撵,您可以設(shè)置“configPath”。例如共啃,對(duì)于作為Eureka客戶端的配置服務(wù)器:
bootstrap.yml
eureka:
instance:
...
metadataMap:
user: osufhalskjrtl
password: lviuhlszvaorhvlo5847
configPath: /config
配置客戶端快速失敗
在某些情況下占调,如果服務(wù)無(wú)法連接到配置服務(wù)器,則可能希望啟動(dòng)服務(wù)失敗勋磕。如果這是所需的行為妈候,請(qǐng)?jiān)O(shè)置引導(dǎo)配置屬性spring.cloud.config.failFast=true,客戶端將以異常停止挂滓。
配置客戶端重試
如果您希望配置服務(wù)器在您的應(yīng)用程序啟動(dòng)時(shí)可能偶爾不可用拟赊,您可以要求它在發(fā)生故障后繼續(xù)嘗試植捎。首先,您需要設(shè)置spring.cloud.config.failFast=true,然后您需要添加spring-retry和spring-boot-starter-aop到您的類路徑胸梆。默認(rèn)行為是重試6次,初始退避間隔為1000ms蜈块,指數(shù)乘數(shù)為1.1曾沈,用于后續(xù)退避。您可以使用spring.cloud.config.retry.*配置屬性配置這些屬性(和其他)烙博。
小費(fèi)
要完全控制重試瑟蜈,請(qǐng)使用ID“configServerRetryInterceptor”添加RetryOperationsInterceptor類型的@Bean。Spring重試有一個(gè)RetryInterceptorBuilder可以輕松創(chuàng)建一個(gè)渣窜。
查找遠(yuǎn)程配置資源
配置服務(wù)從/{name}/{profile}/{label}提供屬性源铺根,客戶端應(yīng)用程序中的默認(rèn)綁定
“name”=${spring.application.name}
“profile”=${spring.profiles.active}(實(shí)際上是Environment.getActiveProfiles())
“l(fā)abel”=“master”
所有這些都可以通過(guò)設(shè)置spring.cloud.config.*(其中*是“name”,“profile”或“l(fā)abel”)來(lái)覆蓋乔宿∥挥兀“標(biāo)簽”可用于回滾到以前版本的配置;使用默認(rèn)的Config Server實(shí)現(xiàn),它可以是git標(biāo)簽详瑞,分支名稱或提交ID掂林。標(biāo)簽也可以以逗號(hào)分隔的列表形式提供,在這種情況下坝橡,列表中的項(xiàng)目會(huì)逐個(gè)嘗試泻帮,直到成功。例如计寇,當(dāng)您可能希望將配置標(biāo)簽與您的分支對(duì)齊刑顺,但使其成為可選(例如spring.cloud.config.label=myfeature,develop)時(shí)氯窍,這對(duì)于在特征分支上工作時(shí)可能很有用。
安全
如果您在服務(wù)器上使用HTTP基本安全性蹲堂,那么客戶端只需要知道密碼(如果不是默認(rèn)用戶名)狼讨。您可以通過(guò)配置服務(wù)器URI,或通過(guò)單獨(dú)的用戶名和密碼屬性柒竞,例如
bootstrap.yml
spring:
cloud:
config:
uri: https://user:secret@myconfig.mycompany.com
要么
bootstrap.yml
spring:
cloud:
config:
uri: https://myconfig.mycompany.com
username: user
password: secret
spring.cloud.config.password和spring.cloud.config.username值覆蓋URI中提供的任何內(nèi)容政供。
如果您在Cloud Foundry部署應(yīng)用程序,則提供密碼的最佳方式是通過(guò)服務(wù)憑證(例如URI)朽基,因?yàn)樗踔敛恍枰谂渲梦募胁几簟T贑loud Foundry上為本地工作的用戶提供的服務(wù)的一個(gè)例子,名為“configserver”:
bootstrap.yml
spring:
cloud:
config:
uri: ${vcap.services.configserver.credentials.uri:http://user:password@localhost:8888}
如果您使用另一種形式的安全性稼虎,則可能需要向ConfigServicePropertySourceLocator提供RestTemplate(例如衅檀,通過(guò)在引導(dǎo)上下文中獲取它并注入一個(gè))拄氯。ConfigServicePropertySourceLocator提供{470/}(例如通過(guò)在引導(dǎo)上下文中獲取它并注入)堕义。
健康指標(biāo)
Config Client提供一個(gè)嘗試從Config Server加載配置的Spring Boot運(yùn)行狀況指示器∠ⅲ可以通過(guò)設(shè)置health.config.enabled=false來(lái)禁用運(yùn)行狀況指示器打却。由于性能原因杉适,響應(yīng)也被緩存。默認(rèn)緩存生存時(shí)間為5分鐘柳击。要更改該值猿推,請(qǐng)?jiān)O(shè)置health.config.time-to-live屬性(以毫秒為單位)。
提供自定義RestTemplate
在某些情況下捌肴,您可能需要從客戶端自定義對(duì)配置服務(wù)器的請(qǐng)求蹬叭。通常這涉及傳遞特殊的Authorization標(biāo)頭來(lái)對(duì)服務(wù)器的請(qǐng)求進(jìn)行身份驗(yàn)證。要提供自定義RestTemplate状知,請(qǐng)按照以下步驟操作具垫。
設(shè)置spring.cloud.config.enabled=false以禁用現(xiàn)有的配置服務(wù)器屬性源。
使用PropertySourceLocator實(shí)現(xiàn)創(chuàng)建一個(gè)新的配置bean试幽。
CustomConfigServiceBootstrapConfiguration.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;
}
}
在resources/META-INF中創(chuàng)建一個(gè)名為spring.factories的文件,并指定您的自定義配置卦碾。
spring.factorties
org.springframework.cloud.bootstrap.BootstrapConfiguration = com.my.config.client.CustomConfigServiceBootstrapConfiguration
Vault
當(dāng)使用Vault作為配置服務(wù)器的后端時(shí)铺坞,客戶端將需要為服務(wù)器提供一個(gè)令牌,以從Vault中檢索值洲胖〖谜ィ可以通過(guò)在bootstrap.yml中設(shè)置spring.cloud.config.token在客戶端中提供此令牌。
bootstrap.yml
spring:
cloud:
config:
token: YourVaultToken
Vault
Vault中的嵌套密鑰
Vault支持將鍵嵌入存儲(chǔ)在Vault中的值绿映。例如
echo -n '{"appA": {"secret": "appAsecret"}, "bar": "baz"}' | vault write secret/myapp -
此命令將向您的Vault編寫一個(gè)JSON對(duì)象擒滑。要在Spring中訪問(wèn)這些值腐晾,您將使用傳統(tǒng)的點(diǎn)(。)注釋丐一。例如
@Value("${appA.secret}")
String name = "World";
上述代碼將name變量設(shè)置為appAsecret藻糖。
Spring Cloud Netflix
Dalston.RELEASE
該項(xiàng)目通過(guò)自動(dòng)配置為Spring Boot應(yīng)用程序提供Netflix OSS集成,并綁定到Spring環(huán)境和其他Spring編程模型成語(yǔ)库车。通過(guò)幾個(gè)簡(jiǎn)單的注釋巨柒,您可以快速啟用和配置應(yīng)用程序中的常見(jiàn)模式,并通過(guò)經(jīng)過(guò)測(cè)試的Netflix組件構(gòu)建大型分布式系統(tǒng)柠衍。提供的模式包括服務(wù)發(fā)現(xiàn)(Eureka)洋满,斷路器(Hystrix),智能路由(Zuul)和客戶端負(fù)載平衡(Ribbon)珍坊。
服務(wù)發(fā)現(xiàn):Eureka客戶端
服務(wù)發(fā)現(xiàn)是基于微服務(wù)架構(gòu)的關(guān)鍵原則之一昔园。嘗試配置每個(gè)客戶端或某種形式的約定可能非常困難,可以非常脆弱映胁。Netflix服務(wù)發(fā)現(xiàn)服務(wù)器和客戶端是Eureka绽左。可以將服務(wù)器配置和部署為高可用性袱饭,每個(gè)服務(wù)器將注冊(cè)服務(wù)的狀態(tài)復(fù)制到其他服務(wù)器川无。
如何包含Eureka客戶端
要在您的項(xiàng)目中包含Eureka客戶端,請(qǐng)使用組org.springframework.cloud和工件IDspring-cloud-starter-eureka的啟動(dòng)器虑乖。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息懦趋,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面。
注冊(cè)Eureka
當(dāng)客戶端注冊(cè)Eureka時(shí)疹味,它提供關(guān)于自身的元數(shù)據(jù)仅叫,例如主機(jī)和端口,健康指示符URL糙捺,主頁(yè)等诫咱。Eureka從屬于服務(wù)的每個(gè)實(shí)例接收心跳消息。如果心跳失敗超過(guò)可配置的時(shí)間表洪灯,則通常將該實(shí)例從注冊(cè)表中刪除坎缭。
示例eureka客戶端:
@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);
}
}
(即完全正常的Spring Boot應(yīng)用程序)。在這個(gè)例子中签钩,我們明確地使用@EnableEurekaClient掏呼,但只有Eureka可用,你也可以使用@EnableDiscoveryClient铅檩。需要配置才能找到Eureka服務(wù)器憎夷。例:
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
其中“defaultZone”是一個(gè)魔術(shù)字符串后備值,為任何不表示首選項(xiàng)的客戶端提供服務(wù)URL(即它是有用的默認(rèn)值)昧旨。
從Environment獲取的默認(rèn)應(yīng)用程序名稱(服務(wù)ID)拾给,虛擬主機(jī)和非安全端口分別為${spring.application.name}祥得,${spring.application.name}和${server.port}。
@EnableEurekaClient將應(yīng)用程序同時(shí)進(jìn)入一個(gè)Eureka“實(shí)例”(即注冊(cè)自己)和一個(gè)“客戶端”(即它可以查詢注冊(cè)表以查找其他服務(wù))蒋得。實(shí)例行為由eureka.instance.*配置鍵驅(qū)動(dòng)级及,但是如果您確保您的應(yīng)用程序具有spring.application.name(這是Eureka服務(wù)ID或VIP的默認(rèn)值),那么默認(rèn)值將是正常的窄锅。
有關(guān)可配置選項(xiàng)的更多詳細(xì)信息创千,請(qǐng)參閱EurekaInstanceConfigBean和EurekaClientConfigBean。
使用Eureka服務(wù)器進(jìn)行身份驗(yàn)證
如果其中一個(gè)eureka.client.serviceUrl.defaultZone網(wǎng)址中包含一個(gè)憑據(jù)(如http://user:password@localhost:8761/eureka))入偷,HTTP基本身份驗(yàn)證將自動(dòng)添加到您的eureka客戶端追驴。對(duì)于更復(fù)雜的需求髓帽,您可以創(chuàng)建DiscoveryClientOptionalArgs類型的@Bean韧献,并將ClientFilter實(shí)例注入到其中秉馏,所有這些都將應(yīng)用于從客戶端到服務(wù)器的調(diào)用迅箩。
注意
由于Eureka中的限制嫁审,不可能支持每個(gè)服務(wù)器的基本身份驗(yàn)證憑據(jù)仆救,所以只能使用第一個(gè)找到的集合拯杠。
狀態(tài)頁(yè)和健康指標(biāo)
Eureka實(shí)例的狀態(tài)頁(yè)面和運(yùn)行狀況指示器分別默認(rèn)為“/ info”和“/ health”翅萤,它們是Spring Boot執(zhí)行器應(yīng)用程序中有用端點(diǎn)的默認(rèn)位置其骄。如果您使用非默認(rèn)上下文路徑或servlet路徑(例如server.servletPath=/foo)或管理端點(diǎn)路徑(例如management.contextPath=/admin)亏镰,則需要更改這些,即使是執(zhí)行器應(yīng)用程序拯爽。例:
application.yml
eureka:
instance:
statusPageUrlPath: ${management.context-path}/info
healthCheckUrlPath: ${management.context-path}/health
這些鏈接顯示在客戶端使用的元數(shù)據(jù)中索抓,并在某些情況下用于決定是否將請(qǐng)求發(fā)送到應(yīng)用程序,因此如果它們是準(zhǔn)確的毯炮,這是有幫助的逼肯。
注冊(cè)安全應(yīng)用程序
如果您的應(yīng)用程序想通過(guò)HTTPS聯(lián)系,則可以分別在EurekaInstanceConfig桃煎,即eureka.instance.[nonSecurePortEnabled,securePortEnabled]=[false,true]中設(shè)置兩個(gè)標(biāo)志篮幢。這將使Eureka發(fā)布實(shí)例信息顯示安全通信的明確偏好。Spring CloudDiscoveryClient將始終為以這種方式配置的服務(wù)返回一個(gè)https://…?;URI为迈,并且Eureka(本機(jī))實(shí)例信息將具有安全的健康檢查URL三椿。
由于Eureka內(nèi)部的工作方式,它仍然會(huì)發(fā)布狀態(tài)和主頁(yè)的非安全網(wǎng)址葫辐,除非您也明確地覆蓋搜锰。您可以使用占位符來(lái)配置eureka實(shí)例URL,例如
application.yml
eureka:
instance:
statusPageUrl: https://${eureka.hostname}/info
healthCheckUrl: https://${eureka.hostname}/health
homePageUrl: https://${eureka.hostname}/
(請(qǐng)注意另患,${eureka.hostname}是僅在稍后版本的Eureka中可用的本地占位符,您也可以使用Spring占位符實(shí)現(xiàn)同樣的功能蛾绎,例如使用${eureka.instance.hostName}昆箕。
注意
如果您的應(yīng)用程序在代理服務(wù)器后面運(yùn)行鸦列,并且SSL終止服務(wù)在代理中(例如,如果您運(yùn)行在Cloud Foundry或其他平臺(tái)作為服務(wù))鹏倘,則需要確保代理“轉(zhuǎn)發(fā)”頭部被截取并處理應(yīng)用程序薯嗤。Spring Boot應(yīng)用程序中的嵌入式Tomcat容器會(huì)自動(dòng)執(zhí)行“X-Forwarded - \ *”標(biāo)頭的顯式配置。你這個(gè)錯(cuò)誤的一個(gè)跡象就是你的應(yīng)用程序本身所呈現(xiàn)的鏈接是錯(cuò)誤的(錯(cuò)誤的主機(jī)纤泵,端口或協(xié)議)骆姐。
Eureka的健康檢查
默認(rèn)情況下兵拢,Eureka使用客戶端心跳來(lái)確定客戶端是否啟動(dòng)搀矫。除非另有規(guī)定,否則發(fā)現(xiàn)客戶端將不會(huì)根據(jù)Spring Boot執(zhí)行器傳播應(yīng)用程序的當(dāng)前運(yùn)行狀況檢查狀態(tài)房蝉。這意味著成功注冊(cè)后Eureka將永遠(yuǎn)宣布申請(qǐng)?zhí)幱凇癠P”狀態(tài)公荧。通過(guò)啟用Eureka運(yùn)行狀況檢查可以改變此行為带射,從而將應(yīng)用程序狀態(tài)傳播到Eureka。因此循狰,每個(gè)其他應(yīng)用程序?qū)⒉粫?huì)在“UP”之外的狀態(tài)下將流量發(fā)送到應(yīng)用程序窟社。
application.yml
eureka:
client:
healthcheck:
enabled: true
警告
eureka.client.healthcheck.enabled=true只能在application.yml中設(shè)置。設(shè)置bootstrap.yml中的值將導(dǎo)致不期望的副作用绪钥,例如在具有UNKNOWN狀態(tài)的eureka中注冊(cè)灿里。
如果您需要更多的控制健康檢查,您可以考慮實(shí)施自己的com.netflix.appinfo.HealthCheckHandler程腹。
Eureka實(shí)例和客戶端的元數(shù)據(jù)
值得花點(diǎn)時(shí)間了解Eureka元數(shù)據(jù)的工作原理匣吊,以便您可以在平臺(tái)上使用它。有主機(jī)名跪楞,IP地址缀去,端口號(hào),狀態(tài)頁(yè)和運(yùn)行狀況檢查等標(biāo)準(zhǔn)元數(shù)據(jù)甸祭。這些發(fā)布在服務(wù)注冊(cè)表中缕碎,由客戶使用,以直接的方式聯(lián)系服務(wù)池户。額外的元數(shù)據(jù)可以添加到eureka.instance.metadataMap中的實(shí)例注冊(cè)中咏雌,并且這將在遠(yuǎn)程客戶端中可訪問(wèn),但一般不會(huì)更改客戶端的行為校焦,除非意識(shí)到元數(shù)據(jù)的含義赊抖。下面描述了幾個(gè)特殊情況,其中Spring Cloud已經(jīng)為元數(shù)據(jù)映射指定了含義寨典。
在Cloudfoundry上使用Eureka
Cloudfoundry有一個(gè)全局路由器氛雪,所以同一個(gè)應(yīng)用程序的所有實(shí)例都具有相同的主機(jī)名(在具有相似架構(gòu)的其他PaaS解決方案中也是如此)。這不一定是使用Eureka的障礙耸成,但如果您使用路由器(建議报亩,甚至是強(qiáng)制性的浴鸿,具體取決于您的平臺(tái)的設(shè)置方式),則需要明確設(shè)置主機(jī)名和端口號(hào)(安全或非安全)弦追,以便他們使用路由器岳链。您可能還需要使用實(shí)例元數(shù)據(jù),以便您可以區(qū)分客戶端上的實(shí)例(例如,在自定義負(fù)載平衡器中)。默認(rèn)情況下碑定,eureka.instance.instanceId為vcap.application.instance_id叁扫。例如:
application.yml
eureka:
instance:
hostname: ${vcap.application.uris[0]}
nonSecurePort: 80
根據(jù)Cloudfoundry實(shí)例中安全規(guī)則的設(shè)置方式,您可以注冊(cè)并使用主機(jī)VM的IP地址進(jìn)行直接的服務(wù)到服務(wù)調(diào)用。此功能尚未在Pivotal Web Services(PWS)上提供。
在AWS上使用Eureka
如果應(yīng)用程序計(jì)劃將部署到AWS云,那么Eureka實(shí)例必須被配置為AWS意識(shí)到俭嘁,這可以通過(guò)定制來(lái)完成EurekaInstanceConfigBean方式如下:
@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;
}
更改Eureka實(shí)例ID
香草Netflix Eureka實(shí)例注冊(cè)了與其主機(jī)名相同的ID(即每個(gè)主機(jī)只有一個(gè)服務(wù))。Spring Cloud Eureka提供了一個(gè)明智的默認(rèn)服猪,如下所示:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}供填。例如myhost:myappname:8080。
使用Spring Cloud罢猪,您可以通過(guò)在eureka.instance.instanceId中提供唯一的標(biāo)識(shí)符來(lái)覆蓋此近她。例如:
application.yml
eureka:
instance:
instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
使用這個(gè)元數(shù)據(jù)和在localhost上部署的多個(gè)服務(wù)實(shí)例,隨機(jī)值將在那里進(jìn)行膳帕,以使實(shí)例是唯一的粘捎。在Cloudfoundry中,vcap.application.instance_id將在Spring Boot應(yīng)用程序中自動(dòng)填充危彩,因此不需要隨機(jī)值攒磨。
使用EurekaClient
一旦您擁有@EnableDiscoveryClient(或@EnableEurekaClient)的應(yīng)用程序,您就可以使用它來(lái)從Eureka服務(wù)器發(fā)現(xiàn)服務(wù)實(shí)例汤徽。一種方法是使用本機(jī)com.netflix.discovery.EurekaClient(而不是Spring云DiscoveryClient)娩缰,例如
@Autowired
private EurekaClient discoveryClient;
public String serviceUrl() {
InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false);
return instance.getHomePageUrl();
}
小費(fèi)
不要使用@PostConstruct方法或@Scheduled方法(或ApplicationContext可能尚未啟動(dòng)的任何地方)EurekaClient。它被初始化為SmartLifecycle(帶有phase=0)谒府,所以最早可以依靠它可用的是另一個(gè)具有更高階段的SmartLifecycle拼坎。
本機(jī)Netflix EurekaClient的替代方案
您不必使用原始的NetflixEurekaClient,通常在某種包裝器后面使用它更為方便完疫。Spring Cloud支持Feign(REST客戶端構(gòu)建器)泰鸡,還支持SpringRestTemplate使用邏輯Eureka服務(wù)標(biāo)識(shí)符(VIP)而不是物理URL。要使用固定的物理服務(wù)器列表配置Ribbon壳鹤,您可以將.ribbon.listOfServers設(shè)置為逗號(hào)分隔的物理地址(或主機(jī)名)列表盛龄,其中是客戶端的ID。
您還可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它為Netflix不具體的發(fā)現(xiàn)客戶端提供簡(jiǎn)單的API余舶,例如
@Autowired
private DiscoveryClient discoveryClient;
public String serviceUrl() {
List list = discoveryClient.getInstances("STORES");
if (list != null && list.size() > 0 ) {
return list.get(0).getUri();
}
return null;
}
為什么注冊(cè)服務(wù)這么慢蹦锋?
作為一個(gè)實(shí)例也包括定期心跳到注冊(cè)表(通過(guò)客戶端的serviceUrl),默認(rèn)持續(xù)時(shí)間為30秒欧芽。在實(shí)例,服務(wù)器和客戶端在其本地緩存中都具有相同的元數(shù)據(jù)(因此可能需要3個(gè)心跳)之前葛圃,客戶端才能發(fā)現(xiàn)服務(wù)千扔。您可以使用eureka.instance.leaseRenewalIntervalInSeconds更改期限植榕,這將加快客戶端連接到其他服務(wù)的過(guò)程纱烘。在生產(chǎn)中,最好堅(jiān)持使用默認(rèn)值介杆,因?yàn)榉?wù)器內(nèi)部有一些計(jì)算可以對(duì)租賃更新期進(jìn)行假設(shè)褥符。
區(qū)
如果您已將Eureka客戶端部署到多個(gè)區(qū)域龙誊,您可能希望這些客戶端在使用另一個(gè)區(qū)域中的服務(wù)之前,利用同一區(qū)域內(nèi)的服務(wù)喷楣。為此趟大,您需要正確配置您的Eureka客戶端。
首先铣焊,您需要確保將Eureka服務(wù)器部署到每個(gè)區(qū)域逊朽,并且它們是彼此的對(duì)等體。有關(guān)詳細(xì)信息曲伊,請(qǐng)參閱區(qū)域和區(qū)域部分叽讳。
接下來(lái),您需要告知Eureka您的服務(wù)所在的區(qū)域坟募。您可以使用metadataMap屬性來(lái)執(zhí)行此操作岛蚤。例如,如果service 1部署到zone 1和zone 2懈糯,則需要在service 1中設(shè)置以下Eureka屬性
1區(qū)服務(wù)1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
第2區(qū)的服務(wù)1
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true
服務(wù)發(fā)現(xiàn):Eureka服務(wù)器
如何包含Eureka服務(wù)器
要在項(xiàng)目中包含Eureka服務(wù)器涤妒,請(qǐng)使用組org.springframework.cloud和工件idspring-cloud-starter-eureka-server的啟動(dòng)器。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息昂利,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面届腐。
如何運(yùn)行Eureka服務(wù)器
示例eureka服務(wù)器;
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
服務(wù)器具有一個(gè)帶有UI的主頁(yè),并且根據(jù)/eureka/*下的正常Eureka功能的HTTP API端點(diǎn)蜂奸。
小費(fèi)
由于Gradle的依賴關(guān)系解決規(guī)則和父母的bom功能缺乏,只要依靠spring-cloud-starter-eureka-server就可能導(dǎo)致應(yīng)用程序啟動(dòng)失敗扩所。要解決這個(gè)問(wèn)題围详,必須添加Spring Boot Gradle插件,并且必須導(dǎo)入Spring云啟動(dòng)器父母bom:
的build.gradle
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"
}
}
高可用性,區(qū)域和地區(qū)
Eureka服務(wù)器沒(méi)有后端存儲(chǔ)助赞,但是注冊(cè)表中的服務(wù)實(shí)例都必須發(fā)送心跳以保持其注冊(cè)更新(因此可以在內(nèi)存中完成)买羞。客戶端還具有eureka注冊(cè)的內(nèi)存緩存(因此雹食,他們不必為注冊(cè)表提供每個(gè)服務(wù)請(qǐng)求)畜普。
默認(rèn)情況下,每個(gè)Eureka服務(wù)器也是一個(gè)Eureka客戶端群叶,并且需要(至少一個(gè))服務(wù)URL來(lái)定位對(duì)等體蒋伦。如果您不提供該服務(wù)將運(yùn)行和工作号胚,但它將淋浴您的日志與大量的噪音無(wú)法注冊(cè)對(duì)等體刻撒。
關(guān)于區(qū)域和區(qū)域的客戶端Ribbon支持的詳細(xì)信息什湘,請(qǐng)參見(jiàn)下文。
獨(dú)立模式
只要存在某種監(jiān)視器或彈性運(yùn)行時(shí)間(例如Cloud Foundry)赎离,兩個(gè)高速緩存(客戶機(jī)和服務(wù)器)和心跳的組合使獨(dú)立的Eureka服務(wù)器對(duì)故障具有相當(dāng)?shù)膹椥怨溆獭T讵?dú)立模式下,您可能更喜歡關(guān)閉客戶端行為梁剔,因此不會(huì)繼續(xù)嘗試并且無(wú)法訪問(wèn)其對(duì)等體虽画。例:
application.yml(Standalone Eureka Server)
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
請(qǐng)注意,serviceUrl指向與本地實(shí)例相同的主機(jī)荣病。
同行意識(shí)
通過(guò)運(yùn)行多個(gè)實(shí)例并請(qǐng)求他們相互注冊(cè)狸捕,可以使Eureka更具彈性和可用性。事實(shí)上众雷,這是默認(rèn)的行為灸拍,所以你需要做的只是為對(duì)方添加一個(gè)有效的serviceUrl,例如
application.yml(Two Peer Aware Eureka服務(wù)器)
---
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/
在這個(gè)例子中砾省,我們有一個(gè)YAML文件鸡岗,可以通過(guò)在不同的Spring配置文件中運(yùn)行,在2臺(tái)主機(jī)(peer1和peer2)上運(yùn)行相同的服務(wù)器编兄。您可以使用此配置來(lái)測(cè)試單個(gè)主機(jī)上的對(duì)等體感知(通過(guò)操作/etc/hosts來(lái)解析主機(jī)名轩性,在生產(chǎn)中沒(méi)有太多價(jià)值)。事實(shí)上狠鸳,如果您在一臺(tái)知道自己的主機(jī)名的機(jī)器上運(yùn)行(默認(rèn)情況下使用java.net.InetAddress查找)揣苏,則不需要eureka.instance.hostname。
您可以向系統(tǒng)添加多個(gè)對(duì)等體件舵,只要它們至少一個(gè)邊緣彼此連接卸察,則它們將在它們之間同步注冊(cè)。如果對(duì)等體在物理上分離(在數(shù)據(jù)中心內(nèi)或多個(gè)數(shù)據(jù)中心之間)铅祸,則系統(tǒng)原則上可以分裂腦型故障坑质。
喜歡IP地址
在某些情況下合武,Eureka優(yōu)先發(fā)布服務(wù)的IP地址而不是主機(jī)名。將eureka.instance.preferIpAddress設(shè)置為true涡扼,并且當(dāng)應(yīng)用程序向eureka注冊(cè)時(shí)稼跳,它將使用其IP地址而不是其主機(jī)名。
斷路器:Hystrix客戶端
Netflix的創(chuàng)造了一個(gè)調(diào)用的庫(kù)Hystrix實(shí)現(xiàn)了斷路器圖案吃沪。在微服務(wù)架構(gòu)中汤善,通常有多層服務(wù)調(diào)用辨嗽。
圖1.微服務(wù)圖
較低級(jí)別的服務(wù)中的服務(wù)故障可能導(dǎo)致用戶級(jí)聯(lián)故障九孩。當(dāng)對(duì)特定服務(wù)的呼叫達(dá)到一定閾值時(shí)(Hystrix中的默認(rèn)值為5秒內(nèi)的20次故障)惨寿,電路打開(kāi)苔咪,不進(jìn)行通話。在錯(cuò)誤和開(kāi)路的情況下锌妻,開(kāi)發(fā)人員可以提供后備。
圖2. Hystrix回退防止級(jí)聯(lián)故障
開(kāi)放式電路會(huì)停止級(jí)聯(lián)故障,并允許不必要的或失敗的服務(wù)時(shí)間來(lái)愈合垮耳。回退可以是另一個(gè)Hystrix保護(hù)的調(diào)用遂黍,靜態(tài)數(shù)據(jù)或一個(gè)正常的空值终佛。回退可能被鏈接雾家,所以第一個(gè)回退使得一些其他業(yè)務(wù)電話又回到靜態(tài)數(shù)據(jù)铃彰。
如何加入Hystrix
要在項(xiàng)目中包含Hystrix,請(qǐng)使用組org.springframework.cloud和artifact idspring-cloud-starter-hystrix的啟動(dòng)器芯咧。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息牙捉,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面。
示例啟動(dòng)應(yīng)用程序:
@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 parameters) {
//do stuff that might fail
}
public Object defaultStores(Map parameters) {
return /* something useful */;
}
}
@HystrixCommand由名為“javanica”的Netflix contrib庫(kù)提供敬飒。Spring Cloud在連接到Hystrix斷路器的代理中使用該注釋自動(dòng)包裝Spring bean邪铲。斷路器計(jì)算何時(shí)打開(kāi)和關(guān)閉電路,以及在發(fā)生故障時(shí)應(yīng)該做什么无拗。
要配置@HystrixCommand带到,您可以使用commandProperties屬性列出@HystrixProperty注釋。請(qǐng)參閱這里了解更多詳情英染。有關(guān)可用屬性的詳細(xì)信息揽惹,請(qǐng)參閱Hystrix維基。
傳播安全上下文或使用Spring范圍
如果您希望某些線程本地上下文傳播到@HystrixCommand四康,默認(rèn)聲明將不起作用搪搏,因?yàn)樗诰€程池中執(zhí)行命令(超時(shí))。您可以使用某些配置或直接在注釋中使用與使用相同的線程來(lái)調(diào)用Hystrix闪金,方法是要求使用不同的“隔離策略”慕嚷。例如:
@HystrixCommand(fallbackMethod = "stubMyService",
commandProperties = {
@HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
}
)
...
如果您使用@SessionScope或@RequestScope,同樣的事情也適用。您將知道何時(shí)需要執(zhí)行此操作喝检,因?yàn)檫\(yùn)行時(shí)異常說(shuō)它找不到范圍的上下文嗅辣。
您還可以將hystrix.shareSecurityContext屬性設(shè)置為true。這樣做會(huì)自動(dòng)配置一個(gè)Hystrix并發(fā)策略插件鉤子挠说,他將SecurityContext從主線程傳送到Hystrix命令使用的鉤子腺办。Hystrix不允許注冊(cè)多個(gè)hystrix并發(fā)策略,因此可以通過(guò)將自己的HystrixConcurrencyStrategy聲明為Spring bean來(lái)實(shí)現(xiàn)擴(kuò)展機(jī)制粉楚。Spring Cloud將在Spring上下文中查找您的實(shí)現(xiàn)亿汞,并將其包裝在自己的插件中。
健康指標(biāo)
連接斷路器的狀態(tài)也暴露在呼叫應(yīng)用程序的/health端點(diǎn)中杆兵。
{
"hystrix": {
"openCircuitBreakers": [
"StoreIntegration::getStoresByLocationLink"
],
"status": "CIRCUIT_OPEN"
},
"status": "UP"
}
Hystrix指標(biāo)流
要使Hystrix指標(biāo)流包含對(duì)spring-boot-starter-actuator的依賴雁仲。這將使/hystrix.stream作為管理端點(diǎn)。
org.springframework.boot
spring-boot-starter-actuator
斷路器:Hystrix儀表板
Hystrix的主要優(yōu)點(diǎn)之一是它收集關(guān)于每個(gè)HystrixCommand的一套指標(biāo)琐脏。Hystrix儀表板以有效的方式顯示每個(gè)斷路器的運(yùn)行狀況攒砖。
圖3. Hystrix儀表板
Hystrix超時(shí)和Ribbon客戶
當(dāng)使用包含Ribbon客戶端的Hystrix命令時(shí),您需要確保您的Hystrix超時(shí)配置為長(zhǎng)于配置的Ribbon超時(shí)日裙,包括可能進(jìn)行的任何潛在的重試吹艇。例如,如果您的Ribbon連接超時(shí)為一秒鐘昂拂,并且Ribbon客戶端可能會(huì)重試該請(qǐng)求三次受神,那么您的Hystrix超時(shí)應(yīng)該略超過(guò)三秒鐘。
如何包含Hystrix儀表板
要在項(xiàng)目中包含Hystrix儀表板格侯,請(qǐng)使用組org.springframework.cloud和工件IDspring-cloud-starter-hystrix-dashboard的啟動(dòng)器鼻听。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面联四。
要運(yùn)行Hystrix儀表板使用@EnableHystrixDashboard注釋您的Spring Boot主類精算。然后訪問(wèn)/hystrix,并將儀表板指向Hystrix客戶端應(yīng)用程序中的單個(gè)實(shí)例/hystrix.stream端點(diǎn)碎连。
Turbine
從個(gè)人實(shí)例看灰羽,Hystrix數(shù)據(jù)在系統(tǒng)整體健康方面不是非常有用。Turbine是將所有相關(guān)/hystrix.stream端點(diǎn)聚合到Hystrix儀表板中使用的/turbine.stream的應(yīng)用程序鱼辙。個(gè)人實(shí)例位于Eureka廉嚼。運(yùn)行Turbine就像使用@EnableTurbine注釋(例如使用spring-cloud-starter-turbine設(shè)置類路徑)注釋主類一樣簡(jiǎn)單。來(lái)自Turbine 1維基的所有文檔配置屬性都適用倒戏。唯一的區(qū)別是turbine.instanceUrlSuffix不需要預(yù)先添加的端口怠噪,除非turbine.instanceInsertPort=false自動(dòng)處理。
注意
默認(rèn)情況下杜跷,Turbine通過(guò)在Eureka中查找其homePageUrl條目傍念,然后將/hystrix.stream附加到注冊(cè)的實(shí)例上查找/hystrix.stream端點(diǎn)矫夷。這意味著如果spring-boot-actuator在自己的端口上運(yùn)行(這是默認(rèn)值),則對(duì)/hystrix.stream的調(diào)用將失敗憋槐。要使渦輪機(jī)找到正確端口的Hystrix流双藕,您需要向?qū)嵗脑獢?shù)據(jù)中添加management.port:
eureka:
instance:
metadata-map:
management.port: ${management.port:8081}
配置密鑰turbine.appConfig是渦輪機(jī)將用于查找實(shí)例的尤里卡服務(wù)列表。渦輪流然后在Hystrix儀表板中使用如下URL:http://my.turbine.sever:8080/turbine.stream?cluster=;(如果名稱為“默認(rèn)值”阳仔,則可以省略群集參數(shù))忧陪。cluster參數(shù)必須與turbine.aggregator.clusterConfig中的條目相匹配戳气。從eureka返回的值是大寫字母售担,因此如果有一個(gè)名為“customers”的Eureka注冊(cè)了一個(gè)應(yīng)用程序,我們預(yù)計(jì)此示例可以正常工作:
turbine:
aggregator:
clusterConfig: CUSTOMERS
appConfig: customers
clusterName可以通過(guò)turbine.clusterNameExpression中的SPEL表達(dá)式以root身份InstanceInfo進(jìn)行自定義伏社。默認(rèn)值為appName评矩,這意味著Eureka serviceId最終作為集群密鑰(即客戶的InstanceInfo具有appName“CUSTOMERS”)叶堆。一個(gè)不同的例子是turbine.clusterNameExpression=aSGName,它將從AWS ASG名稱獲取集群名稱斥杜。另一個(gè)例子:
turbine:
aggregator:
clusterConfig: SYSTEM,USER
appConfig: customers,stores,ui,admin
clusterNameExpression: metadata['cluster']
在這種情況下虱颗,來(lái)自4個(gè)服務(wù)的集群名稱從其元數(shù)據(jù)映射中提取,并且預(yù)期具有包含“SYSTEM”和“USER”的值果录。
要為所有應(yīng)用程序使用“默認(rèn)”集群,您需要一個(gè)字符串文字表達(dá)式(帶單引號(hào)咐熙,并且如果它在YAML中也使用雙引號(hào)進(jìn)行轉(zhuǎn)義):
turbine:
appConfig: customers,stores
clusterNameExpression: "'default'"
Spring Cloud提供了一個(gè)spring-cloud-starter-turbine弱恒,它具有運(yùn)行Turbine服務(wù)器所需的所有依賴關(guān)系。只需創(chuàng)建一個(gè)Spring Boot應(yīng)用程序并用@EnableTurbine注釋它棋恼。
注意
默認(rèn)情況下返弹,Spring Cloud允許Turbine使用主機(jī)和端口允許每個(gè)主機(jī)在每個(gè)群集中進(jìn)行多個(gè)進(jìn)程。如果你想建成Turbine本地Netflix的行為爪飘,它不會(huì)允許每個(gè)主機(jī)上的多個(gè)過(guò)程义起,每簇(關(guān)鍵實(shí)例ID是主機(jī)名),然后將該屬性設(shè)置turbine.combineHostPort=false师崎。
Turbine Stream
在某些環(huán)境中(例如默终,在PaaS設(shè)置中),從所有分布式Hystrix命令中提取度量的經(jīng)典Turbine模型不起作用犁罩。在這種情況下齐蔽,您可能希望讓Hystrix命令將度量標(biāo)準(zhǔn)推送到Turbine,并且Spring Cloud可以使用消息傳遞床估。您需要在客戶端上執(zhí)行的所有操作都為您選擇的spring-cloud-netflix-hystrix-stream和spring-cloud-starter-stream-*添加依賴關(guān)系(有關(guān)經(jīng)紀(jì)人的詳細(xì)信息含滴,請(qǐng)參閱Spring Cloud Stream文檔,以及如何配置客戶端憑據(jù)丐巫,但是應(yīng)該為當(dāng)?shù)亟?jīng)紀(jì)人開(kāi)箱即用)谈况。
在服務(wù)器端只需創(chuàng)建一個(gè)Spring Boot應(yīng)用程序并使用@EnableTurbineStream進(jìn)行注釋勺美,默認(rèn)情況下將在8989端口(將您的Hystrix儀表板指向該端口,任何路徑)碑韵。您可以使用server.port或turbine.stream.port自定義端口赡茸。如果類路徑中還有spring-boot-starter-web和spring-boot-starter-actuator,那么您可以通過(guò)提供不同的management.port在單獨(dú)端口(默認(rèn)情況下使用Tomcat)打開(kāi)Actuator端點(diǎn)泼诱。
然后坛掠,您可以將Hystrix儀表板指向Turbine Stream服務(wù)器,而不是單個(gè)Hystrix流治筒。如果Turbine Stream在myhost上的端口8989上運(yùn)行屉栓,則將http://myhost:8989放在Hystrix儀表板中的流輸入字段中。電路將以各自的serviceId為前綴耸袜,后跟一個(gè)點(diǎn)友多,然后是電路名稱。
Spring Cloud提供了一個(gè)spring-cloud-starter-turbine-stream堤框,它具有您需要的Turbine Stream服務(wù)器運(yùn)行所需的所有依賴項(xiàng)尘惧,只需添加您選擇的Stream binder肴甸,例如spring-cloud-starter-stream-rabbit。您需要Java 8來(lái)運(yùn)行應(yīng)用程序,因?yàn)樗腔贜etty的氯迂。
客戶端負(fù)載平衡器:Ribbon
Ribbon是一個(gè)客戶端負(fù)載均衡器,它可以很好地控制HTTP和TCP客戶端的行為愚争。Feign已經(jīng)使用Ribbon毒返,所以如果您使用@FeignClient,則本節(jié)也適用腊嗡。
Ribbon中的中心概念是指定客戶端的概念着倾。每個(gè)負(fù)載平衡器是組合的組合的一部分,它們一起工作以根據(jù)需要聯(lián)系遠(yuǎn)程服務(wù)器燕少,并且集合具有您將其作為應(yīng)用程序開(kāi)發(fā)人員(例如使用@FeignClient注釋)的名稱卡者。Spring Cloud使用RibbonClientConfiguration為每個(gè)命名的客戶端根據(jù)需要?jiǎng)?chuàng)建一個(gè)新的合奏作為ApplicationContext。這包含(除其他外)ILoadBalancer客们,RestClient和ServerListFilter崇决。
如何加入Ribbon
要在項(xiàng)目中包含Ribbon,請(qǐng)使用組org.springframework.cloud和工件IDspring-cloud-starter-ribbon的起始器底挫。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息嗽桩,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面。
自定義Ribbon客戶端
您可以使用.ribbon.*中的外部屬性來(lái)配置Ribbon客戶端的某些位凄敢,這與使用Netflix API本身沒(méi)有什么不同碌冶,只能使用Spring Boot配置文件。本機(jī)選項(xiàng)可以在CommonClientConfigKey(功能區(qū)內(nèi)核心部分)中作為靜態(tài)字段進(jìn)行檢查涝缝。
Spring Cloud還允許您通過(guò)使用@RibbonClient聲明其他配置(位于RibbonClientConfiguration之上)來(lái)完全控制客戶端扑庞。例:
@Configuration
@RibbonClient(name = "foo", configuration = FooConfiguration.class)
public class TestConfiguration {
}
在這種情況下譬重,客戶端由RibbonClientConfiguration中已經(jīng)存在的組件與FooConfiguration中的任何組件組成(后者通常會(huì)覆蓋前者)。
警告
FooConfiguration必須是@Configuration罐氨,但請(qǐng)注意臀规,它不在主應(yīng)用程序上下文的@ComponentScan中,否則將由所有@RibbonClients共享栅隐。如果您使用@ComponentScan(或@SpringBootApplication)塔嬉,則需要采取措施避免包含(例如將其放在一個(gè)單獨(dú)的,不重疊的包中租悄,或者指定要在@ComponentScan)谨究。
Spring Cloud Netflix默認(rèn)情況下為Ribbon(BeanTypebeanName:ClassName)提供以下bean:
IClientConfigribbonClientConfig:DefaultClientConfigImpl
IRuleribbonRule:ZoneAvoidanceRule
IPingribbonPing:NoOpPing
ServerListribbonServerList:ConfigurationBasedServerList
ServerListFilterribbonServerListFilter:ZonePreferenceServerListFilter
ILoadBalancerribbonLoadBalancer:ZoneAwareLoadBalancer
ServerListUpdaterribbonServerListUpdater:PollingServerListUpdater
創(chuàng)建一個(gè)類型的bean并將其放置在@RibbonClient配置(例如上面的FooConfiguration)中)允許您覆蓋所描述的每個(gè)bean。例:
@Configuration
public class FooConfiguration {
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
}
這用PingUrl代替NoOpPing泣棋。
使用屬性自定義Ribbon客戶端
從版本1.2.0開(kāi)始胶哲,Spring Cloud Netflix現(xiàn)在支持使用屬性與Ribbon文檔兼容來(lái)自定義Ribbon客戶端。
這允許您在不同環(huán)境中更改啟動(dòng)時(shí)的行為潭辈。
支持的屬性如下所示鸯屿,應(yīng)以.ribbon.為前綴:
NFLoadBalancerClassName:應(yīng)實(shí)施ILoadBalancer
NFLoadBalancerRuleClassName:應(yīng)實(shí)施IRule
NFLoadBalancerPingClassName:應(yīng)實(shí)施IPing
NIWSServerListClassName:應(yīng)實(shí)施ServerList
NIWSServerListFilterClassName應(yīng)實(shí)施ServerListFilter
注意
在這些屬性中定義的類優(yōu)先于使用@RibbonClient(configuration=MyRibbonConfig.class)定義的bean和由Spring Cloud Netflix提供的默認(rèn)值。
要設(shè)置服務(wù)名稱users的IRule把敢,您可以設(shè)置以下內(nèi)容:
application.yml
users:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
有關(guān)Ribbon提供的實(shí)現(xiàn)寄摆,請(qǐng)參閱Ribbon文檔。
在Eureka中使用Ribbon
當(dāng)Eureka與Ribbon結(jié)合使用(即兩者都在類路徑上)時(shí)修赞,ribbonServerList將被擴(kuò)展為DiscoveryEnabledNIWSServerList婶恼,擴(kuò)展名為Eureka的服務(wù)器列表校镐。它還用NIWSDiscoveryPing替換IPing接口送朱,代理到Eureka以確定服務(wù)器是否啟動(dòng)。默認(rèn)情況下安裝的ServerList是一個(gè)DomainExtractingServerList,其目的是使物理元數(shù)據(jù)可用于負(fù)載平衡器搓扯,而不使用AWS AMI元數(shù)據(jù)(這是Netflix依賴的)。默認(rèn)情況下包归,服務(wù)器列表將使用實(shí)例元數(shù)據(jù)(如遠(yuǎn)程客戶端集合eureka.instance.metadataMap.zone)中提供的“區(qū)域”信息構(gòu)建锨推,如果缺少,則可以使用服務(wù)器主機(jī)名中的域名作為代理用于區(qū)域(如果設(shè)置了標(biāo)志approximateZoneFromHostname)公壤。一旦區(qū)域信息可用换可,它可以在ServerListFilter中使用。默認(rèn)情況下厦幅,它將用于定位與客戶端相同區(qū)域的服務(wù)器沾鳄,因?yàn)槟J(rèn)值為ZonePreferenceServerListFilter。默認(rèn)情況下确憨,客戶端的區(qū)域與遠(yuǎn)程實(shí)例的方式相同译荞,即通過(guò)eureka.instance.metadataMap.zone瓤的。
注意
設(shè)置客戶端區(qū)域的正統(tǒng)“archaius”方式是通過(guò)一個(gè)名為“@zone”的配置屬性,如果可用吞歼,Spring Cloud將優(yōu)先使用所有其他設(shè)置(請(qǐng)注意圈膏,該鍵必須被引用)在YAML配置中)。
注意
如果沒(méi)有其他的區(qū)域數(shù)據(jù)源篙骡,則基于客戶端配置(與實(shí)例配置相反)進(jìn)行猜測(cè)稽坤。我們將eureka.client.availabilityZones(從區(qū)域名稱映射到區(qū)域列表),并將實(shí)例自己的區(qū)域的第一個(gè)區(qū)域(即eureka.client.region糯俗,其默認(rèn)為“us-east-1”為與本機(jī)Netflix的兼容性)尿褪。
示例:如何使用Ribbon不使用Eureka
Eureka是一種方便的方式來(lái)抽象遠(yuǎn)程服務(wù)器的發(fā)現(xiàn),因此您不必在客戶端中對(duì)其URL進(jìn)行硬編碼叶骨,但如果您不想使用它茫多,Ribbon和Feign仍然是適用的。假設(shè)您已經(jīng)為“商店”申請(qǐng)了@RibbonClient忽刽,并且Eureka未被使用(甚至不在類路徑上)天揖。Ribbon客戶端默認(rèn)為已配置的服務(wù)器列表,您可以提供這樣的配置
application.yml
stores:
ribbon:
listOfServers: example.com,google.com
示例:在Ribbon中禁用Eureka使用
設(shè)置屬性ribbon.eureka.enabled = false將明確禁用在Ribbon中使用Eureka跪帝。
application.yml
ribbon:
eureka:
enabled: false
直接使用Ribbon API
您也可以直接使用LoadBalancerClient今膊。例:
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
}
}
緩存Ribbon配置
每個(gè)Ribbon命名的客戶端都有一個(gè)相應(yīng)的子應(yīng)用程序上下文,Spring Cloud維護(hù)伞剑,這個(gè)應(yīng)用程序上下文在第一個(gè)請(qǐng)求中被延遲加載到命名的客戶端斑唬。可以通過(guò)指定Ribbon客戶端的名稱黎泣,在啟動(dòng)時(shí)恕刘,可以更改此延遲加載行為,從而熱切加載這些子應(yīng)用程序上下文抒倚。
application.yml
ribbon:
eager-load:
enabled: true
clients: client1, client2, client3
聲明性REST客戶端:Feign
Feign是一個(gè)聲明式的Web服務(wù)客戶端褐着。這使得Web服務(wù)客戶端的寫入更加方便要使用Feign創(chuàng)建一個(gè)界面并對(duì)其進(jìn)行注釋。它具有可插入注釋支持托呕,包括Feign注釋和JAX-RS注釋含蓉。Feign還支持可插拔編碼器和解碼器亩歹。Spring Cloud增加了對(duì)Spring MVC注釋的支持蝶糯,并使用Spring Web中默認(rèn)使用的HttpMessageConverters笛臣。Spring Cloud集成Ribbon和Eureka以在使用Feign時(shí)提供負(fù)載均衡的http客戶端藤抡。
如何加入Feign
要在您的項(xiàng)目中包含F(xiàn)eign粮揉,請(qǐng)使用組org.springframework.cloud和工件IDspring-cloud-starter-feign的啟動(dòng)器侦讨。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息抖誉,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面憾筏。
示例spring boot應(yīng)用
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableEurekaClient
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
StoreClient.java
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List getStores();
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}
在@FeignClient注釋中任洞,String值(以上“存儲(chǔ)”)是一個(gè)任意的客戶端名稱蓄喇,用于創(chuàng)建Ribbon負(fù)載平衡器(有關(guān)Ribbon支持的詳細(xì)信息食绿,請(qǐng)參閱下文))。您還可以使用url屬性(絕對(duì)值或只是主機(jī)名)指定URL公罕。應(yīng)用程序上下文中的bean的名稱是該接口的完全限定名稱器紧。要指定您自己的別名值,您可以使用@FeignClient注釋的qualifier值楼眷。
以上的Ribbon客戶端將會(huì)發(fā)現(xiàn)“商店”服務(wù)的物理地址铲汪。如果您的應(yīng)用程序是Eureka客戶端,那么它將解析Eureka服務(wù)注冊(cè)表中的服務(wù)罐柳。如果您不想使用Eureka掌腰,您可以簡(jiǎn)單地配置外部配置中的服務(wù)器列表(例如,參見(jiàn)上文)张吉。
覆蓋Feign默認(rèn)值
Spring Cloud的Feign支持的中心概念是指定的客戶端齿梁。每個(gè)假裝客戶端都是組合的組件的一部分,它們一起工作以根據(jù)需要聯(lián)系遠(yuǎn)程服務(wù)器肮蛹,并且該集合具有您將其作為應(yīng)用程序開(kāi)發(fā)人員使用@FeignClient注釋的名稱勺择。Spring Cloud根據(jù)需要,使用FeignClientsConfiguration為每個(gè)已命名的客戶端創(chuàng)建一個(gè)新的集合ApplicationContext伦忠。這包含(除其他外)feign.Decoder省核,feign.Encoder和feign.Contract。
Spring Cloud可以通過(guò)使用@FeignClient聲明額外的配置(FeignClientsConfiguration)來(lái)完全控制假客戶端昆码。例:
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
在這種情況下气忠,客戶端由FeignClientsConfiguration中的組件與FooConfiguration中的任何組件組成(后者將覆蓋前者)。
注意
FooConfiguration不需要使用@Configuration注釋赋咽。但是旧噪,如果是,則請(qǐng)注意將其從任何@ComponentScan中排除脓匿,否則將包含此配置淘钟,因?yàn)樗鼘⒊蔀閒eign.Decoder,feign.Encoder亦镶,feign.Contract等的默認(rèn)來(lái)源日月,指定時(shí)袱瓮。這可以通過(guò)將其放置在任何@ComponentScan或@SpringBootApplication的單獨(dú)的不重疊的包中缤骨,或者可以在@ComponentScan中明確排除。
注意
serviceId屬性現(xiàn)在已被棄用尺借,有利于name屬性绊起。
警告
以前银锻,使用url屬性躁绸,不需要name屬性∮Ш悖現(xiàn)在需要使用name熬词。
name和url屬性支持占位符。
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Spring Cloud Netflix默認(rèn)為feign(BeanTypebeanName:ClassName)提供以下bean:
DecoderfeignDecoder:ResponseEntityDecoder(其中包含SpringDecoder)
EncoderfeignEncoder:SpringEncoder
LoggerfeignLogger:Slf4jLogger
ContractfeignContract:SpringMvcContract
Feign.BuilderfeignBuilder:HystrixFeign.Builder
ClientfeignClient:如果Ribbon啟用笋鄙,則為L(zhǎng)oadBalancerFeignClient师枣,否則將使用默認(rèn)的feign客戶端。
可以通過(guò)將feign.okhttp.enabled或feign.httpclient.enabled設(shè)置為true萧落,并將它們放在類路徑上來(lái)使用OkHttpClient和ApacheHttpClient feign客戶端践美。
Spring Cloud Netflix默認(rèn)情況下不提供以下bean,但是仍然從應(yīng)用程序上下文中查找這些類型的bean以創(chuàng)建假客戶機(jī):
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection
SetterFactory
創(chuàng)建一個(gè)類型的bean并將其放置在@FeignClient配置(例如上面的FooConfiguration)中)允許您覆蓋所描述的每個(gè)bean找岖。例:
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
這將SpringMvcContract替換為feign.Contract.Default陨倡,并將RequestInterceptor添加到RequestInterceptor的集合中。
可以在@EnableFeignClients屬性defaultConfiguration中以與上述相似的方式指定默認(rèn)配置许布。不同之處在于兴革,此配置將適用于所有假客戶端。
注意
如果您需要在RequestInterceptor`s you will need to either set the thread isolation strategy for Hystrix to `SEMAPHORE中使用ThreadLocal綁定變量蜜唾,或在Feign中禁用Hystrix杂曲。
application.yml
# To disable Hystrix in Feign
feign:
hystrix:
enabled: false
# To set thread isolation to SEMAPHORE
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
手動(dòng)創(chuàng)建Feign客戶端
在某些情況下,可能需要以上述方法不可能自定義您的Feign客戶端袁余。在這種情況下解阅,您可以使用Feign Builder API創(chuàng)建客戶端。下面是一個(gè)創(chuàng)建兩個(gè)具有相同接口的Feign客戶端的示例泌霍,但是使用單獨(dú)的請(qǐng)求攔截器配置每個(gè)客戶端货抄。
@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");
}
}
注意
在上面的例子中,F(xiàn)eignClientsConfiguration.class是Spring Cloud Netflix提供的默認(rèn)配置朱转。
注意
PROD-SVC是客戶端將要求的服務(wù)的名稱蟹地。
Feign Hystrix支持
如果Hystrix在類路徑上,feign.hystrix.enabled=true藤为,F(xiàn)eign將用斷路器包裝所有方法怪与。還可以返回com.netflix.hystrix.HystrixCommand。這樣就可以使用無(wú)效模式(調(diào)用.toObservable()或.observe()或異步使用(調(diào)用.queue()))缅疟。
要在每個(gè)客戶端基礎(chǔ)上禁用Hystrix支持創(chuàng)建一個(gè)帶有“原型”范圍的香草Feign.Builder分别,例如:
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
警告
在Spring Cloud達(dá)爾斯頓發(fā)布之前,如果Hystrix在類路徑Feign中默認(rèn)將所有方法包裝在斷路器中存淫。這種默認(rèn)行為在Spring Cloud達(dá)爾斯頓改變了贊成選擇加入的方式耘斩。
Feign Hystrix回退
Hystrix支持回退的概念:當(dāng)電路斷開(kāi)或出現(xiàn)錯(cuò)誤時(shí)執(zhí)行的默認(rèn)代碼路徑。要為給定的@FeignClient啟用回退桅咆,請(qǐng)將fallback屬性設(shè)置為實(shí)現(xiàn)回退的類名括授。
@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");
}
}
如果需要訪問(wèn)導(dǎo)致回退觸發(fā)的原因,可以使用@FeignClient內(nèi)的fallbackFactory屬性。
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
@Component
static class HystrixClientFallbackFactory implements FallbackFactory {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClientWithFallBackFactory() {
@Override
public Hello iFailSometimes() {
return new Hello("fallback; reason was: " + cause.getMessage());
}
};
}
}
警告
在Feign中執(zhí)行回退以及Hystrix回退的工作方式存在局限性荚虚。當(dāng)前返回com.netflix.hystrix.HystrixCommand和rx.Observable的方法目前不支持回退薛夜。
Feign和@Primary
當(dāng)使用Feign與Hystrix回退時(shí),在同一類型的ApplicationContext中有多個(gè)bean版述。這將導(dǎo)致@Autowired不起作用梯澜,因?yàn)闆](méi)有一個(gè)bean先鱼,或者標(biāo)記為主睹限。要解決這個(gè)問(wèn)題,Spring Cloud Netflix將所有Feign實(shí)例標(biāo)記為@Primary烁设,所以Spring Framework將知道要注入哪個(gè)bean檬某。在某些情況下撬腾,這可能是不可取的。要關(guān)閉此行為恢恼,將@FeignClient的primary屬性設(shè)置為false民傻。
@FeignClient(name = "hello", primary = false)
public interface HelloClient {
// methods here
}
Feign繼承支持
Feign通過(guò)單繼承接口支持樣板apis。這樣就可以將常用操作分成方便的基本界面场斑。
UserService.java
public interface UserService {
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
User getUser(@PathVariable("id") long id);
}
UserResource.java
@RestController
public class UserResource implements UserService {
}
UserClient.java
package project.user;
@FeignClient("users")
public interface UserClient extends UserService {
}
注意
通常不建議在服務(wù)器和客戶端之間共享接口漓踢。它引入了緊耦合,并且實(shí)際上并不適用于當(dāng)前形式的Spring MVC(方法參數(shù)映射不被繼承)漏隐。
Feign請(qǐng)求/響應(yīng)壓縮
您可以考慮為Feign請(qǐng)求啟用請(qǐng)求或響應(yīng)GZIP壓縮喧半。您可以通過(guò)啟用其中一個(gè)屬性來(lái)執(zhí)行此操作:
feign.compression.request.enabled=true
feign.compression.response.enabled=true
Feign請(qǐng)求壓縮為您提供與您為Web服務(wù)器設(shè)置的設(shè)置相似的設(shè)置:
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
這些屬性可以讓您對(duì)壓縮介質(zhì)類型和最小請(qǐng)求閾值長(zhǎng)度有選擇性。
Feign日志記錄
為每個(gè)創(chuàng)建的Feign客戶端創(chuàng)建一個(gè)記錄器青责。默認(rèn)情況下挺据,記錄器的名稱是用于創(chuàng)建Feign客戶端的接口的完整類名。Feign日志記錄僅響應(yīng)DEBUG級(jí)別脖隶。
application.yml
logging.level.project.user.UserClient: DEBUG
您可以為每個(gè)客戶端配置的Logger.Level對(duì)象告訴Feign記錄多少扁耐。選擇是:
NONE,無(wú)記錄(DEFAULT)产阱。
BASIC婉称,只記錄請(qǐng)求方法和URL以及響應(yīng)狀態(tài)代碼和執(zhí)行時(shí)間。
HEADERS构蹬,記錄基本信息以及請(qǐng)求和響應(yīng)標(biāo)頭王暗。
FULL,記錄請(qǐng)求和響應(yīng)的頭文件庄敛,正文和元數(shù)據(jù)俗壹。
例如,以下將Logger.Level設(shè)置為FULL:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
外部配置:Archaius
Netflix客戶端配置庫(kù)Archaius它是所有Netflix OSS組件用于配置的庫(kù)铐姚。Archaius是Apache Commons Configuration項(xiàng)目的擴(kuò)展策肝。它允許通過(guò)輪詢?cè)催M(jìn)行更改或?qū)⒃锤耐扑偷娇蛻舳藖?lái)進(jìn)行配置更新。Archaius使用Dynamic Property類作為屬性的句柄隐绵。
Archaius示例
class ArchaiusTest {
DynamicStringProperty myprop = DynamicPropertyFactory
.getInstance()
.getStringProperty("my.prop");
void doSomething() {
OtherClass.someMethod(myprop.get());
}
}
Archaius具有自己的一組配置文件和加載優(yōu)先級(jí)之众。Spring應(yīng)用程序一般不應(yīng)直接使用Archaius,但本地仍然需要配置Netflix工具依许。Spring Cloud具有Spring環(huán)境橋棺禾,所以Archaius可以從Spring環(huán)境讀取屬性。這允許Spring Boot項(xiàng)目使用正常的配置工具鏈峭跳,同時(shí)允許他們?cè)谖臋n中大部分配置Netflix工具膘婶。
路由器和過(guò)濾器:Zuul
路由在微服務(wù)體系結(jié)構(gòu)的一個(gè)組成部分。例如蛀醉,/可以映射到您的Web應(yīng)用程序悬襟,/api/users映射到用戶服務(wù),并將/api/shop映射到商店服務(wù)拯刁。Zuul是Netflix的基于JVM的路由器和服務(wù)器端負(fù)載均衡器脊岳。
Netflix使用Zuul進(jìn)行以下操作:
認(rèn)證
洞察
壓力測(cè)試
金絲雀測(cè)試
動(dòng)態(tài)路由
服務(wù)遷移
負(fù)載脫落
安全
靜態(tài)響應(yīng)處理
主動(dòng)/主動(dòng)流量管理
Zuul的規(guī)則引擎允許基本上寫任何JVM語(yǔ)言編寫規(guī)則和過(guò)濾器馏谨,內(nèi)置Java和Groovy笆制。
注意
配置屬性zuul.max.host.connections已被兩個(gè)新屬性zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections替換坞淮,分別默認(rèn)為200和20狸演。
注意
所有路由的默認(rèn)Hystrix隔離模式(ExecutionIsolationStrategy)為SEMAPHORE买喧。如果此隔離模式是首選瑟匆,則zuul.ribbonIsolationStrategy可以更改為THREAD黄娘。
如何加入Zuul
要在您的項(xiàng)目中包含Zuul骨杂,請(qǐng)使用組org.springframework.cloud和artifact idspring-cloud-starter-zuul的啟動(dòng)器账嚎。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息莫瞬,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面。
嵌入式Zuul反向代理
Spring Cloud已經(jīng)創(chuàng)建了一個(gè)嵌入式Zuul代理郭蕉,以簡(jiǎn)化UI應(yīng)用程序想要代理對(duì)一個(gè)或多個(gè)后端服務(wù)的呼叫的非常常見(jiàn)的用例的開(kāi)發(fā)乏悄。此功能對(duì)于用戶界面對(duì)其所需的后端服務(wù)進(jìn)行代理是有用的,避免了對(duì)所有后端獨(dú)立管理CORS和驗(yàn)證問(wèn)題的需求恳不。
要啟用它檩小,使用@EnableZuulProxy注釋Spring Boot主類,并將本地調(diào)用轉(zhuǎn)發(fā)到相應(yīng)的服務(wù)烟勋。按照慣例规求,具有ID“用戶”的服務(wù)將接收來(lái)自位于/users(具有前綴stripped)的代理的請(qǐng)求。代理使用Ribbon來(lái)定位一個(gè)通過(guò)發(fā)現(xiàn)轉(zhuǎn)發(fā)的實(shí)例卵惦,并且所有請(qǐng)求都以hystrix命令執(zhí)行阻肿,所以故障將顯示在Hystrix指標(biāo)中,一旦電路打開(kāi)沮尿,代理將不會(huì)嘗試聯(lián)系服務(wù)丛塌。
注意
Zuul啟動(dòng)器不包括發(fā)現(xiàn)客戶端较解,因此對(duì)于基于服務(wù)ID的路由,您還需要在類路徑中提供其中一個(gè)路由(例如Eureka)赴邻。
要跳過(guò)自動(dòng)添加的服務(wù)印衔,請(qǐng)將zuul.ignored-services設(shè)置為服務(wù)標(biāo)識(shí)模式列表。如果一個(gè)服務(wù)匹配一個(gè)被忽略的模式姥敛,而且包含在明確配置的路由映射中奸焙,那么它將被無(wú)符號(hào)。例:
application.yml
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
在此示例中彤敛,除“用戶”之外与帆,所有服務(wù)都被忽略。
要擴(kuò)充或更改代理路由墨榄,可以添加如下所示的外部配置:
application.yml
zuul:
routes:
users: /myusers/**
這意味著對(duì)“/ myusers”的http呼叫轉(zhuǎn)發(fā)到“用戶”服務(wù)(例如“/ myusers / 101”轉(zhuǎn)發(fā)到“/ 101”)玄糟。
要獲得對(duì)路由的更細(xì)粒度的控制,您可以獨(dú)立地指定路徑和serviceId:
application.yml
zuul:
routes:
users:
path: /myusers/**
serviceId: users_service
這意味著對(duì)“/ myusers”的http呼叫轉(zhuǎn)發(fā)到“users_service”服務(wù)袄秩。路由必須有一個(gè)“路徑”茶凳,可以指定為螞蟻樣式模式,所以“/ myusers / *”只匹配一個(gè)級(jí)別播揪,但“/ myusers / **”分層匹配贮喧。
后端的位置可以被指定為“serviceId”(用于發(fā)現(xiàn)的服務(wù))或“url”(對(duì)于物理位置),例如
application.yml
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
這些簡(jiǎn)單的URL路由不會(huì)被執(zhí)行為HystrixCommand猪狈,也不能使用Ribbon對(duì)多個(gè)URL進(jìn)行負(fù)載平衡箱沦。為此殿漠,請(qǐng)指定service-route并為serviceId配置Ribbon客戶端(目前需要在Ribbon中禁用Eureka支持:詳見(jiàn)上文)肮韧,例如
application.yml
zuul:
routes:
users:
path: /myusers/**
serviceId: users
ribbon:
eureka:
enabled: false
users:
ribbon:
listOfServers: example.com,google.com
您可以使用regexmapper在serviceId和路由之間提供約定。它使用名為group的正則表達(dá)式從serviceId中提取變量并將它們注入到路由模式中弥鹦。
ApplicationConfiguration.java
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?^.+)-(?v.+$)",
"${version}/${name}");
}
這意味著serviceId“myusers-v1”將被映射到路由“/ v1 / myusers / **”疆前。任何正則表達(dá)式都被接受寒跳,但所有命名組都必須存在于servicePattern和routePattern中。如果servicePattern與serviceId不匹配竹椒,則使用默認(rèn)行為童太。在上面的示例中,serviceId“myusers”將被映射到路由“/ myusers / **”(檢測(cè)不到版本)此功能默認(rèn)禁用胸完,僅適用于已發(fā)現(xiàn)的服務(wù)书释。
要為所有映射添加前綴,請(qǐng)將zuul.prefix設(shè)置為一個(gè)值赊窥,例如/api爆惧。默認(rèn)情況下,請(qǐng)求被轉(zhuǎn)發(fā)之前锨能,代理前綴被刪除(使用zuul.stripPrefix=false關(guān)閉此行為)扯再。您還可以關(guān)閉從各路線剝離服務(wù)特定的前綴芍耘,例如
application.yml
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
注意
zuul.stripPrefix僅適用于zuul.prefix中設(shè)置的前綴。它對(duì)給定路由path中定義的前綴有影響熄阻。
在本示例中斋竞,對(duì)“/ myusers / 101”的請(qǐng)求將轉(zhuǎn)發(fā)到“/ myusers / 101”上的“users”服務(wù)。
zuul.routes條目實(shí)際上綁定到類型為ZuulProperties的對(duì)象饺律。如果您查看該對(duì)象的屬性窃页,您將看到它還具有“可重試”標(biāo)志跺株。將該標(biāo)志設(shè)置為“true”使Ribbon客戶端自動(dòng)重試失敗的請(qǐng)求(如果需要复濒,可以使用Ribbon客戶端配置修改重試操作的參數(shù))。
默認(rèn)情況下乒省,將X-Forwarded-Host標(biāo)頭添加到轉(zhuǎn)發(fā)的請(qǐng)求中巧颈。關(guān)閉setzuul.addProxyHeaders = false。默認(rèn)情況下袖扛,前綴路徑被刪除砸泛,對(duì)后端的請(qǐng)求會(huì)拾取一個(gè)標(biāo)題“X-Forwarded-Prefix”(上述示例中的“/ myusers”)。
如果您設(shè)置默認(rèn)路由(“/”)蛆封,則@EnableZuulProxy的應(yīng)用程序可以作為獨(dú)立服務(wù)器唇礁,例如zuul.route.home: /將路由所有流量(即“/ **”)到“home”服務(wù)。
如果需要更細(xì)粒度的忽略惨篱,可以指定要忽略的特定模式盏筐。在路由位置處理開(kāi)始時(shí)評(píng)估這些模式,這意味著前綴應(yīng)包含在模式中以保證匹配砸讳。忽略的模式跨越所有服務(wù)琢融,并取代任何其他路由規(guī)范。
application.yml
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
這意味著諸如“/ myusers / 101”的所有呼叫將被轉(zhuǎn)發(fā)到“用戶”服務(wù)上的“/ 101”簿寂。但是包含“/ admin /”的呼叫將無(wú)法解決漾抬。
警告
如果您需要您的路由保留訂單,則需要使用YAML文件常遂,因?yàn)槭褂脤傩晕募?huì)丟失訂購(gòu)胶背。例如:
application.yml
zuul:
routes:
users:
path: /myusers/**
legacy:
path: /**
如果要使用屬性文件,則legacy路徑可能會(huì)在users路徑前面展開(kāi)雷滋,從而使users路徑不可達(dá)捎拯。
Zuul Http客戶端
zuul使用的默認(rèn)HTTP客戶端現(xiàn)在由Apache HTTP Client支持,而不是不推薦使用的RibbonRestClient毯欣。要分別使用RestClient或使用okhttp3.OkHttpClient集合ribbon.restclient.enabled=true或ribbon.okhttp.enabled=true馒过。
Cookie和敏感標(biāo)題
在同一個(gè)系統(tǒng)中的服務(wù)之間共享標(biāo)題是可行的,但是您可能不希望敏感標(biāo)頭泄漏到外部服務(wù)器的下游酗钞。您可以在路由配置中指定被忽略頭文件列表腹忽。Cookies起著特殊的作用来累,因?yàn)樗鼈冊(cè)跒g覽器中具有明確的語(yǔ)義,并且它們總是被視為敏感的窘奏。如果代理的消費(fèi)者是瀏覽器嘹锁,則下游服務(wù)的cookie也會(huì)導(dǎo)致用戶出現(xiàn)問(wèn)題,因?yàn)樗鼈兌急换煜ㄋ邢掠畏?wù)看起來(lái)都是來(lái)自同一個(gè)地方)着裹。
如果您對(duì)服務(wù)的設(shè)計(jì)非常謹(jǐn)慎领猾,例如,如果只有一個(gè)下游服務(wù)設(shè)置了Cookie骇扇,那么您可能可以讓他們從后臺(tái)一直到調(diào)用者摔竿。另外,如果您的代理設(shè)置cookie和所有后臺(tái)服務(wù)都是同一系統(tǒng)的一部分少孝,那么簡(jiǎn)單地共享它們就可以自然(例如使用Spring Session將它們鏈接到一些共享狀態(tài))继低。除此之外,由下游服務(wù)設(shè)置的任何Cookie可能對(duì)呼叫者來(lái)說(shuō)都不是很有用稍走,因此建議您將(至少)“Set-Cookie”和“Cookie”設(shè)置為不屬于您的域名袁翁。即使是屬于您域名的路線,請(qǐng)嘗試仔細(xì)考慮允許Cookie在代理之間流動(dòng)的含義婿脸。
靈敏頭可以配置為每個(gè)路由的逗號(hào)分隔列表粱胜,例如
application.yml
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
注意
這是sensitiveHeaders的默認(rèn)值,因此您不需要設(shè)置它狐树,除非您希望它不同焙压。注意這是Spring Cloud Netflix 1.1中的新功能(1.0中,用戶無(wú)法控制標(biāo)題褪迟,所有Cookie都在兩個(gè)方向上流動(dòng))冗恨。
sensitiveHeaders是一個(gè)黑名單,默認(rèn)值不為空味赃,所以要使Zuul發(fā)送所有標(biāo)題(“被忽略”除外)掀抹,您必須將其顯式設(shè)置為空列表。如果您要將Cookie或授權(quán)標(biāo)頭傳遞到后端心俗,這是必要的傲武。例:
application.yml
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
也可以通過(guò)設(shè)置zuul.sensitiveHeaders來(lái)全局設(shè)置敏感標(biāo)題。如果在路由上設(shè)置sensitiveHeaders城榛,則將覆蓋全局sensitiveHeaders設(shè)置揪利。
被忽略的標(biāo)題
除了每個(gè)路由的敏感標(biāo)頭,您還可以為與下游服務(wù)交互期間應(yīng)該丟棄的值(請(qǐng)求和響應(yīng))設(shè)置全局值為zuul.ignoredHeaders狠持。默認(rèn)情況下疟位,如果Spring安全性不在類路徑上,則它們是空的,否則它們被初始化為由Spring Security指定的一組眾所周知的“安全性”頭(例如涉及緩存)买鸽。在這種情況下的假設(shè)是下游服務(wù)可能也添加這些頭翁垂,我們希望代理的值惫皱。為了不丟棄這些眾所周知的安全標(biāo)頭,只要Spring安全性在類路徑上彭沼,您可以將zuul.ignoreSecurityHeaders設(shè)置為false廉侧。如果您禁用Spring安全性中的HTTP安全性響應(yīng)頭肖方,并希望由下游服務(wù)提供的值祥绞,這可能很有用
路線端點(diǎn)
如果您在Spring Boot執(zhí)行器中使用@EnableZuulProxy非洲,您將啟用(默認(rèn)情況下)另一個(gè)端點(diǎn),通過(guò)HTTP可用/routes蜕径。到此端點(diǎn)的GET將返回映射路由的列表两踏。POST將強(qiáng)制刷新現(xiàn)有路由(例如,如果服務(wù)目錄中有更改)丧荐。您可以通過(guò)將endpoints.routes.enabled設(shè)置為false來(lái)禁用此端點(diǎn)缆瓣。
注意
路由應(yīng)自動(dòng)響應(yīng)服務(wù)目錄中的更改喧枷,但POST到/路由是強(qiáng)制更改立即發(fā)生的一種方式虹统。
扼殺模式和本地前進(jìn)
遷移現(xiàn)有應(yīng)用程序或API時(shí)的常見(jiàn)模式是“扼殺”舊端點(diǎn),用不同的實(shí)現(xiàn)慢慢替換它們车荔。Zuul代理是一個(gè)有用的工具,因?yàn)槟梢允褂盟鼇?lái)處理來(lái)自舊端點(diǎn)的客戶端的所有流量戚扳,但將一些請(qǐng)求重定向到新端點(diǎn)忧便。
示例配置:
application.yml
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
在這個(gè)例子中,我們扼殺了“遺留”應(yīng)用程序帽借,該應(yīng)用程序映射到所有與其他模式不匹配的請(qǐng)求珠增。/first/**中的路徑已被提取到具有外部URL的新服務(wù)中。并且/second/**中的路徑被轉(zhuǎn)發(fā)砍艾,以便它們可以在本地處理蒂教,例如具有正常的Spring@RequestMapping。/third/**中的路徑也被轉(zhuǎn)發(fā)脆荷,但具有不同的前綴(即/third/foo轉(zhuǎn)發(fā)到/3rd/foo)凝垛。
注意
被忽略的模式并不完全被忽略,它們只是不被代理處理(因此它們也被有效地轉(zhuǎn)發(fā)到本地)蜓谋。
通過(guò)Zuul上傳文件
如果您@EnableZuulProxy您可以使用代理路徑上傳文件梦皮,只要文件很小,它就應(yīng)該工作桃焕。對(duì)于大文件剑肯,有一個(gè)替代路徑繞過(guò)“/ zuul / *”中的SpringDispatcherServlet(以避免多部分處理)。也就是說(shuō)观堂,如果zuul.routes.customers=/customers/**則可以將大文件發(fā)送到“/ zuul / customers / *”让网。servlet路徑通過(guò)zuul.servletPath進(jìn)行外部化岖妄。如果代理路由引導(dǎo)您通過(guò)Ribbon負(fù)載均衡器,例如寂祥,超大文件也將需要提升超時(shí)設(shè)置
application.yml
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
請(qǐng)注意荐虐,要使用大型文件進(jìn)行流式傳輸,您需要在請(qǐng)求中使用分塊編碼(某些瀏覽器默認(rèn)情況下不會(huì)執(zhí)行)丸凭。例如在命令行:
$ curl -v -H "Transfer-Encoding: chunked" \
-F "file=@mylarge.iso" localhost:9999/zuul/simple/file
查詢字符串編碼
處理傳入的請(qǐng)求時(shí)福扬,查詢參數(shù)被解碼拳昌,因此可以在Zuul過(guò)濾器中進(jìn)行修改金踪。然后在路由過(guò)濾器中構(gòu)建后端請(qǐng)求時(shí)重新編碼它們。如果使用Javascript的encodeURIComponent()方法編碼竞帽,結(jié)果可能與原始輸入不同虽界。雖然這在大多數(shù)情況下不會(huì)出現(xiàn)任何問(wèn)題汽烦,但一些Web服務(wù)器可以用復(fù)雜查詢字符串的編碼來(lái)挑選。
要強(qiáng)制查詢字符串的原始編碼莉御,可以將特殊標(biāo)志傳遞給ZuulProperties撇吞,以便查詢字符串與HttpServletRequest::getQueryString方法相同:
application.yml
zuul:
forceOriginalQueryStringEncoding: true
注意:此特殊標(biāo)志僅適用于SimpleHostRoutingFilter,您可以使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)輕松覆蓋查詢參數(shù)礁叔,因?yàn)椴樵冏址F(xiàn)在直接在原始的HttpServletRequest上獲取牍颈。
普通嵌入Zuul
如果您使用@EnableZuulServer(而不是@EnableZuulProxy),您也可以運(yùn)行不帶代理的Zuul服務(wù)器琅关,或者有選擇地切換代理平臺(tái)的部分煮岁。您添加到ZuulFilter類型的應(yīng)用程序的任何bean都將自動(dòng)安裝,與@EnableZuulProxy一樣涣易,但不會(huì)自動(dòng)添加任何代理過(guò)濾器画机。
在這種情況下,仍然通過(guò)配置“zuul.routes新症。*”來(lái)指定進(jìn)入Zuul服務(wù)器的路由步氏,但沒(méi)有服務(wù)發(fā)現(xiàn)和代理,所以“serviceId”和“url”設(shè)置將被忽略账劲。例如:
application.yml
zuul:
routes:
api: /api/**
將“/ api / **”中的所有路徑映射到Zuul過(guò)濾器鏈戳护。
禁用Zuul過(guò)濾器
Spring Cloud的Zuul在代理和服務(wù)器模式下默認(rèn)啟用了多個(gè)ZuulFilterbean。有關(guān)啟用的可能過(guò)濾器瀑焦,請(qǐng)參閱zuul過(guò)濾器包腌且。如果要禁用它,只需設(shè)置zuul...disable=true榛瓮。按照慣例铺董,filters之后的包是Zuul過(guò)濾器類型。例如,禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter設(shè)置zuul.SendResponseFilter.post.disable=true精续。
為路線提供Hystrix回退
當(dāng)Zuul中給定路由的電路跳閘時(shí)坝锰,您可以通過(guò)創(chuàng)建類型為ZuulFallbackProvider的bean來(lái)提供回退響應(yīng)。在這個(gè)bean中重付,您需要指定回退的路由ID顷级,并提供返回的ClientHttpResponse作為后備。這是一個(gè)非常簡(jiǎn)單的ZuulFallbackProvider實(shí)現(xiàn)确垫。
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;
}
};
}
}
這里是路由配置的樣子弓颈。
zuul:
routes:
customers: /customers/**
如果您希望為所有路由提供默認(rèn)的回退,您可以創(chuàng)建一個(gè)類型為ZuulFallbackProvider的bean删掀,并且getRoute方法返回*或null翔冀。
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;
}
};
}
}
Zuul開(kāi)發(fā)人員指南
有關(guān)Zuul如何工作的一般概述,請(qǐng)參閱Zuul維基披泪。
Zuul Servlet
Zuul被實(shí)現(xiàn)為Servlet纤子。對(duì)于一般情況,Zuul嵌入到Spring調(diào)度機(jī)制中款票。這允許Spring MVC控制路由箱熬。在這種情況下板丽,Zuul被配置為緩沖請(qǐng)求拜姿。如果需要通過(guò)Zuul不緩沖請(qǐng)求(例如大文件上傳)木柬,Servlet也將安裝在Spring調(diào)度程序之外善延。默認(rèn)情況下骤素,它位于/zuul愿卸√睿可以使用zuul.servlet-path屬性更改此路徑抄瓦。
Zuul RequestContext
要在過(guò)濾器之間傳遞信息潮瓶,Zuul使用aRequestContext。其數(shù)據(jù)按照每個(gè)請(qǐng)求的ThreadLocal進(jìn)行钙姊。關(guān)于路由請(qǐng)求毯辅,錯(cuò)誤以及實(shí)際HttpServletRequest和HttpServletResponse的路由信息??。RequestContext擴(kuò)展ConcurrentHashMap煞额,所以任何東西都可以存儲(chǔ)在上下文中思恐。FilterConstants包含由Spring Cloud Netflix安裝的過(guò)濾器使用的密鑰(稍后再安裝)。
@EnableZuulProxy與@EnableZuulServer
Spring Cloud Netflix根據(jù)使用何種注釋來(lái)啟用Zuul安裝多個(gè)過(guò)濾器膊毁。@EnableZuulProxy是@EnableZuulServer的超集胀莹。換句話說(shuō),@EnableZuulProxy包含@EnableZuulServer安裝的所有過(guò)濾器婚温∶柩妫“代理”中的其他過(guò)濾器啟用路由功能。如果你想要一個(gè)“空白”Zuul栅螟,你應(yīng)該使用@EnableZuulServer荆秦。
@EnableZuulServer過(guò)濾器
創(chuàng)建從Spring Boot配置文件加載路由定義的SimpleRouteLocator篱竭。
安裝了以下過(guò)濾器(正常Spring豆類):
前置過(guò)濾器
ServletDetectionFilter:檢測(cè)請(qǐng)求是否通過(guò)Spring調(diào)度程序。使用鍵FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY設(shè)置布爾值步绸。
FormBodyWrapperFilter:解析表單數(shù)據(jù)掺逼,并對(duì)下游請(qǐng)求進(jìn)行重新編碼。
DebugFilter:如果設(shè)置debug請(qǐng)求參數(shù)瓤介,則此過(guò)濾器將RequestContext.setDebugRouting()和RequestContext.setDebugRequest()設(shè)置為true坪圾。
路由過(guò)濾器
SendForwardFilter:此過(guò)濾器使用ServletRequestDispatcher轉(zhuǎn)發(fā)請(qǐng)求。轉(zhuǎn)發(fā)位置存儲(chǔ)在RequestContext屬性FilterConstants.FORWARD_TO_KEY中惑朦。這對(duì)于轉(zhuǎn)發(fā)到當(dāng)前應(yīng)用程序中的端點(diǎn)很有用兽泄。
過(guò)濾器:
SendResponseFilter:將代理請(qǐng)求的響應(yīng)寫入當(dāng)前響應(yīng)。
錯(cuò)誤過(guò)濾器:
SendErrorFilter:如果RequestContext.getThrowable()不為null漾月,則轉(zhuǎn)發(fā)到/錯(cuò)誤(默認(rèn)情況下)病梢。可以通過(guò)設(shè)置error.path屬性來(lái)更改默認(rèn)轉(zhuǎn)發(fā)路徑(/error)梁肿。
@EnableZuulProxy過(guò)濾器
創(chuàng)建從DiscoveryClient(如Eureka)以及屬性加載路由定義的DiscoveryClientRouteLocator蜓陌。每個(gè)serviceId從DiscoveryClient創(chuàng)建路由。隨著新服務(wù)的添加吩蔑,路由將被刷新钮热。
除了上述過(guò)濾器之外,還安裝了以下過(guò)濾器(正常Spring豆類):
前置過(guò)濾器
PreDecorationFilter:此過(guò)濾器根據(jù)提供的RouteLocator確定在哪里和如何路由烛芬。它還為下游請(qǐng)求設(shè)置各種與代理相關(guān)的頭隧期。
路由過(guò)濾器
RibbonRoutingFilter:此過(guò)濾器使用Ribbon疗疟,Hystrix和可插拔HTTP客戶端發(fā)送請(qǐng)求邑贴。服務(wù)ID位于RequestContext屬性FilterConstants.SERVICE_ID_KEY中。此過(guò)濾器可以使用不同的HTTP客戶端儿咱。他們是:
ApacheHttpClient遣臼。這是默認(rèn)的客戶端性置。
SquareupOkHttpClientv3。通過(guò)在類路徑上設(shè)置com.squareup.okhttp3:okhttp庫(kù)并設(shè)置ribbon.okhttp.enabled=true來(lái)啟用此功能揍堰。
Netflix Ribbon HTTP客戶端鹏浅。這可以通過(guò)設(shè)置ribbon.restclient.enabled=true來(lái)啟用。這個(gè)客戶端有限制屏歹,比如它不支持PATCH方法隐砸,還有內(nèi)置的重試。
SimpleHostRoutingFilter:此過(guò)濾器通過(guò)Apache HttpClient發(fā)送請(qǐng)求到預(yù)定的URL西采。URL位于RequestContext.getRouteHost()凰萨。
自定義Zuul過(guò)濾示例
以下大部分以下“如何撰寫”示例都包含示例Zuul過(guò)濾器項(xiàng)目。還有一些操作該存儲(chǔ)庫(kù)中的請(qǐng)求或響應(yīng)正文的例子。
如何編寫預(yù)過(guò)濾器
前置過(guò)濾器用于設(shè)置RequestContext中的數(shù)據(jù)胖眷,用于下游的過(guò)濾器武通。主要用例是設(shè)置路由過(guò)濾器所需的信息。
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;
}
}
上面的過(guò)濾器從foo請(qǐng)求參數(shù)填充SERVICE_ID_KEY珊搀。實(shí)際上冶忱,做這種直接映射并不是一個(gè)好主意,而是從foo的值來(lái)查看服務(wù)ID境析。
現(xiàn)在填寫SERVICE_ID_KEY囚枪,PreDecorationFilter將不會(huì)運(yùn)行,RibbonRoutingFilter將會(huì)劳淆。如果您想要路由到完整的網(wǎng)址链沼,請(qǐng)改用ctx.setRouteHost(url)。
要修改路由過(guò)濾器將轉(zhuǎn)發(fā)的路徑沛鸵,請(qǐng)?jiān)O(shè)置REQUEST_URI_KEY括勺。
如何編寫路由過(guò)濾器
路由過(guò)濾器在預(yù)過(guò)濾器之后運(yùn)行,并用于向其他服務(wù)發(fā)出請(qǐng)求曲掰。這里的大部分工作是將請(qǐng)求和響應(yīng)數(shù)據(jù)轉(zhuǎn)換到客戶端所需的模型疾捍。
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 headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration 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 responseHeaders = new LinkedMultiValueMap<>();
for (Map.Entry> 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;
}
}
上述過(guò)濾器將Servlet請(qǐng)求信息轉(zhuǎn)換為OkHttp3請(qǐng)求信息,執(zhí)行HTTP請(qǐng)求栏妖,然后將OkHttp3響應(yīng)信息轉(zhuǎn)換為Servlet響應(yīng)乱豆。警告:此過(guò)濾器可能有錯(cuò)誤,但功能不正確吊趾。
如何編寫過(guò)濾器
后置過(guò)濾器通常操縱響應(yīng)宛裕。在下面的過(guò)濾器中,我們添加一個(gè)隨機(jī)UUID作為X-Foo頭趾徽。其他操作续滋,如轉(zhuǎn)換響應(yīng)體,要復(fù)雜得多孵奶,計(jì)算密集。
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;
}
}
Zuul錯(cuò)誤如何工作
如果在Zuul過(guò)濾器生命周期的任何部分拋出異常蜡峰,則會(huì)執(zhí)行錯(cuò)誤過(guò)濾器了袁。SendErrorFilter只有RequestContext.getThrowable()不是null才會(huì)運(yùn)行喳逛。然后在請(qǐng)求中設(shè)置特定的javax.servlet.error.*屬性谋币,并將請(qǐng)求轉(zhuǎn)發(fā)到Spring Boot錯(cuò)誤頁(yè)面。
Zuul渴望應(yīng)用程序上下文加載
Zuul內(nèi)部使用Ribbon調(diào)用遠(yuǎn)程URL辙培,并且Ribbon客戶端默認(rèn)在第一次調(diào)用時(shí)由Spring Cloud加載油航≌赣梗可以使用以下配置更改Zuul的此行為,并將導(dǎo)致在應(yīng)用程序啟動(dòng)時(shí),子Ribbon相關(guān)的應(yīng)用程序上下文正在加載怕享。
application.yml
zuul:
ribbon:
eager-load:
enabled: true
Polyglot支持Sidecar
你有沒(méi)有非jvm的語(yǔ)言你想利用Eureka执赡,Ribbon和配置服務(wù)器?Netflix Prana啟發(fā)了Spring Cloud Netflix Sidecar函筋。它包含一個(gè)簡(jiǎn)單的http api來(lái)獲取給定服務(wù)的所有實(shí)例(即主機(jī)和端口)沙合。您還可以通過(guò)從Eureka獲取其路由條目的嵌入式Zuul代理來(lái)代理服務(wù)調(diào)用〉剩可以通過(guò)主機(jī)查找或通過(guò)Zuul代理訪問(wèn)Spring Cloud Config服務(wù)器首懈。非jvm應(yīng)用程序應(yīng)該執(zhí)行健康檢查,以便Sidecar可以向應(yīng)用程序啟動(dòng)或關(guān)閉時(shí)向eureka報(bào)告谨敛。
要在項(xiàng)目中包含Sidecar究履,使用組org.springframework.cloud和artifact idspring-cloud-netflix-sidecar的依賴關(guān)系。
要啟用Sidecar脸狸,請(qǐng)使用@EnableSidecar創(chuàng)建Spring Boot應(yīng)用程序挎袜。此注釋包括@EnableCircuitBreaker,@EnableDiscoveryClient和@EnableZuulProxy肥惭。在與非jvm應(yīng)用程序相同的主機(jī)上運(yùn)行生成的應(yīng)用程序盯仪。
要配置側(cè)車將sidecar.port和sidecar.health-uri添加到application.yml。sidecar.port屬性是非jvm應(yīng)用程序正在偵聽(tīng)的端口蜜葱。這樣全景,Sidecar可以使用Eureka正確注冊(cè)該應(yīng)用。sidecar.health-uri是可以在非jvm應(yīng)用程序上訪問(wèn)的牵囤,可以模擬Spring Boot健康指標(biāo)爸黄。它應(yīng)該返回一個(gè)json文檔,如下所示:
健康-URI文檔
{
"status":"UP"
}
以下是Sidecar應(yīng)用程序的application.yml示例:
application.yml
server:
port: 5678
spring:
application:
name: sidecar
sidecar:
port: 8000
health-uri: http://localhost:8000/health.json
DiscoveryClient.getInstances()方法的api為/hosts/{serviceId}揭鳞。以下是/hosts/customers在不同主機(jī)上返回兩個(gè)實(shí)例的示例響應(yīng)炕贵。這個(gè)api可以訪問(wèn)http://localhost:5678/hosts/{serviceId}的非jvm應(yīng)用程序(如果側(cè)面在端口5678上)。
/主機(jī)/客戶
[
{
"host": "myhost",
"port": 9000,
"uri": "http://myhost:9000",
"serviceId": "CUSTOMERS",
"secure": false
},
{
"host": "myhost2",
"port": 9000,
"uri": "http://myhost2:9000",
"serviceId": "CUSTOMERS",
"secure": false
}
]
Zuul代理自動(dòng)將eureka中已知的每個(gè)服務(wù)的路由添加到/野崇,因此客戶服務(wù)可在/customers獲得称开。非jvm應(yīng)用程序可以通過(guò)http://localhost:5678/customers訪問(wèn)客戶服務(wù)(假設(shè)邊界正在偵聽(tīng)端口5678)。
如果配置服務(wù)器已注冊(cè)到Eureka乓梨,則非jvm應(yīng)用程序可以通過(guò)Zuul代理訪問(wèn)它鳖轰。如果ConfigServer的serviceId為configserver且端口5678為Sidecar,則可以在http:// localhost:5678 / configserver
非jvm應(yīng)用程序可以利用Config Server返回YAML文檔的功能扶镀。例如蕴侣,調(diào)用http://sidecar.local.spring.io:5678/configserver/default-master.yml可能會(huì)導(dǎo)致一個(gè)YAML文檔,如下所示
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
password: password
info:
description: Spring Cloud Samples
url: https://github.com/spring-cloud-samples
RxJava與Spring MVC
Spring Cloud Netflix包括RxJava臭觉。
RxJava是Reactive Extensions的Java VM實(shí)現(xiàn):用于通過(guò)使用observable序列來(lái)構(gòu)建異步和基于事件的程序的庫(kù)昆雀。
Spring Cloud Netflix支持從Spring MVC控制器返回rx.Single對(duì)象辱志。它還支持對(duì)服務(wù)器發(fā)送事件(SSE)使用rx.Observable對(duì)象。如果您的內(nèi)部API已經(jīng)使用RxJava構(gòu)建(參見(jiàn)Feign Hystrix支持示例)狞膘,這可能非常方便袁梗。
以下是使用rx.Single的一些示例:
@RequestMapping(method = RequestMethod.GET, value = "/single")
public Single single() {
return Single.just("single value");
}
@RequestMapping(method = RequestMethod.GET, value = "/singleWithResponse")
public ResponseEntity> singleWithResponse() {
return new ResponseEntity<>(Single.just("single value"),
HttpStatus.NOT_FOUND);
}
@RequestMapping(method = RequestMethod.GET, value = "/singleCreatedWithResponse")
public Single> singleOuterWithResponse() {
return Single.just(new ResponseEntity<>("single value", HttpStatus.CREATED));
}
@RequestMapping(method = RequestMethod.GET, value = "/throw")
public Single error() {
return Single.error(new RuntimeException("Unexpected"));
}
如果您有Observable而不是單個(gè)颈墅,則可以使用.toSingle()或.toList().toSingle()。這里有些例子:
@RequestMapping(method = RequestMethod.GET, value = "/single")
public Single single() {
return Observable.just("single value").toSingle();
}
@RequestMapping(method = RequestMethod.GET, value = "/multiple")
public Single> multiple() {
return Observable.just("multiple", "values").toList().toSingle();
}
@RequestMapping(method = RequestMethod.GET, value = "/responseWithObservable")
public ResponseEntity> responseWithObservable() {
Observable 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 timeout() {
return Observable.timer(1, TimeUnit.MINUTES).map(new Func1() {
@Override
public String call(Long aLong) {
return "single value";
}
});
}
如果您有流式端點(diǎn)和客戶端,SSE可以是一個(gè)選項(xiàng)漾肮。要將rx.Observable轉(zhuǎn)換為SpringSseEmitter使用RxResponse.sse()反浓。這里有些例子:
@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))));
}
指標(biāo):Spectator橄唬,Servo和Atlas
當(dāng)一起使用時(shí)仁热,Spectator / Servo和Atlas提供了近乎實(shí)時(shí)的操作洞察平臺(tái)。
Netflix的度量收集庫(kù)Spectator和ServoAtlas是用于管理維度時(shí)間序列數(shù)據(jù)的Netflix指標(biāo)后端渠缕。
Servo為Netflix服務(wù)了好幾年鸽素,仍然可以使用,但逐漸被淘汰出局Spectator亦鳞,這只適用于Java 8. Spring Cloud Netflix提供了支持馍忽,但Java 8鼓勵(lì)基于應(yīng)用的應(yīng)用程序使用Spectator。
維度與層次度量
Spring Boot執(zhí)行器指標(biāo)是層次結(jié)構(gòu)燕差,指標(biāo)只能由名稱分隔遭笋。這些名稱通常遵循將密鑰/值屬性對(duì)(維)嵌入到以句點(diǎn)分隔的名稱中的命名約定⊥教剑考慮以下兩個(gè)端點(diǎn)(root和star-star)的指標(biāo):
{
"counter.status.200.root": 20,
"counter.status.400.root": 3,
"counter.status.200.star-star": 5,
}
第一個(gè)指標(biāo)給出了每單位時(shí)間內(nèi)針對(duì)根端點(diǎn)的成功請(qǐng)求的歸一化計(jì)數(shù)瓦呼。但是如果系統(tǒng)有20個(gè)端點(diǎn),并且想要獲得針對(duì)所有端點(diǎn)的成功請(qǐng)求計(jì)數(shù)呢测暗?一些分級(jí)度量后端將允許您指定一個(gè)通配符央串,例如counter.status.200.,它將讀取所有20個(gè)指標(biāo)并聚合結(jié)果碗啄≈屎停或者,您可以提供HandlerInterceptorAdapter攔截并記錄所有成功請(qǐng)求的counter.status.200.all等指標(biāo)稚字,而不考慮端點(diǎn)饲宿,但現(xiàn)在您必須編寫20 + 1個(gè)不同的指標(biāo)。同樣尉共,如果您想知道服務(wù)中所有端點(diǎn)的成功請(qǐng)求總數(shù)褒傅,您可以指定一個(gè)通配符,例如counter.status.2.*袄友。
即使在分級(jí)度量后端的通配符支持的情況下,命名一致性也是困難的霹菊。具體來(lái)說(shuō)剧蚣,這些標(biāo)簽在名稱字符串中的位置可能會(huì)隨著時(shí)間而滑落支竹,從而導(dǎo)致查詢錯(cuò)例如,假設(shè)我們?yōu)樯鲜鯤TTP方法添加了一個(gè)額外的維度鸠按。那么counter.status.200.root成為counter.status.200.method.get.root等等礼搁。我們的counter.status.200.*突然不再具有相同的語(yǔ)義。此外目尖,如果新的維度在整個(gè)代碼庫(kù)中不均勻地應(yīng)用馒吴,某些查詢可能會(huì)變得不可能。這可以很快失控瑟曲。
Netflix指標(biāo)被標(biāo)記(又稱維度)饮戳。每個(gè)指標(biāo)都有一個(gè)名稱,但是這個(gè)單一的命名度量可以包含多個(gè)統(tǒng)計(jì)信息和“標(biāo)簽”鍵/值對(duì)洞拨,這允許更多的查詢靈活性。實(shí)際上統(tǒng)計(jì)本身就是記錄在一個(gè)特殊的標(biāo)簽上。
使用Netflix Servo或Spectator記錄鸠信,上述根端點(diǎn)的計(jì)時(shí)器包含每個(gè)狀態(tài)碼的4個(gè)統(tǒng)計(jì)信息勤众,其中計(jì)數(shù)統(tǒng)計(jì)信息與Spring Boot執(zhí)行器計(jì)數(shù)器相同。如果到目前為止花吟,我們遇到了HTTP 200和400秸歧,將有8個(gè)可用數(shù)據(jù)點(diǎn):
{
"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,
}
默認(rèn)度量集合
沒(méi)有任何附加依賴或配置,基于Spring Cloud的服務(wù)將自動(dòng)配置ServoMonitorRegistry衅澈,并開(kāi)始收集每個(gè)Spring MVC請(qǐng)求的指標(biāo)键菱。默認(rèn)情況下,將為每個(gè)MVC請(qǐng)求記錄名稱為rest的Servo定時(shí)器矾麻,其標(biāo)記為:
HTTP方法
HTTP狀態(tài)(例如200,400,500)
URI(如果URI為空纱耻,則為“root”),為Atlas
異常類名稱险耀,如果請(qǐng)求處理程序拋出異常
如果在請(qǐng)求上設(shè)置了匹配netflix.metrics.rest.callerHeader的密鑰的請(qǐng)求頭弄喘,則呼叫者。netflix.metrics.rest.callerHeader沒(méi)有默認(rèn)鍵甩牺。如果您希望收集來(lái)電者信息蘑志,則必須將其添加到應(yīng)用程序?qū)傩灾小?/p>
設(shè)置netflix.metrics.rest.metricName屬性將度量值的名稱從rest更改為您提供的名稱。
如果Spring AOP已啟用贬派,并且org.aspectj:aspectjweaver存在于您的運(yùn)行時(shí)類路徑上急但,則Spring Cloud還將收集每個(gè)使用RestTemplate進(jìn)行的客戶端調(diào)用的指標(biāo)。將為每個(gè)具有以下標(biāo)簽的MVC請(qǐng)求記錄名稱為restclient的Servo定時(shí)器:
HTTP方法
HTTP狀態(tài)(例如200,400,500)搞乏,如果響應(yīng)返回為空波桩,則為“CLIENT_ERROR”;如果在執(zhí)行RestTemplate方法期間發(fā)生IOException,則為“IO_ERROR”
URI请敦,為Atlas
客戶名稱
警告
避免在RestTemplate內(nèi)使用硬編碼的url參數(shù)镐躲。定位動(dòng)態(tài)端點(diǎn)時(shí)使用URL變量储玫。這將避免ServoMonitorCache將每個(gè)網(wǎng)址視為唯一密鑰的潛在“GC覆蓋限制達(dá)到”問(wèn)題。
// recommended
String orderid = "1";
restTemplate.getForObject("http://testeurekabrixtonclient/orders/{orderid}", String.class, orderid)
// avoid
restTemplate.getForObject("http://testeurekabrixtonclient/orders/1", String.class)
指標(biāo)集:Spectator
要啟用Spectator指標(biāo)萤皂,請(qǐng)?jiān)趕pring-boot-starter-spectator上包含依賴關(guān)系:
org.springframework.cloud
spring-cloud-starter-spectator
在Spectator說(shuō)明中撒穷,儀表是一個(gè)命名,打字和標(biāo)記的配置裆熙,而指標(biāo)表示給定儀表在某個(gè)時(shí)間點(diǎn)的值端礼。Spectator米由注冊(cè)表創(chuàng)建和控制,注冊(cè)表目前有幾個(gè)不同的實(shí)現(xiàn)入录。Spectator提供4米類型:計(jì)數(shù)器蛤奥,定時(shí)器,量規(guī)和分配摘要纷跛。
Spring Cloud Spectator集成為您配置可注入的com.netflix.spectator.api.Registry實(shí)例喻括。具體來(lái)說(shuō),它配置一個(gè)ServoRegistry實(shí)例贫奠,以統(tǒng)一REST度量標(biāo)準(zhǔn)的集合唬血,并將度量標(biāo)準(zhǔn)導(dǎo)出到Servo API下的Atlas后端。實(shí)際上唤崭,這意味著您的代碼可能會(huì)使用Servo顯示器和Spectator米的混合拷恨,并且都將由Spring Boot ActuatorMetricReader實(shí)例舀取,并將兩者都發(fā)送到Atlas后端
Spectator柜臺(tái)
計(jì)數(shù)器用于測(cè)量某些事件發(fā)生的速率谢肾。
// 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
計(jì)數(shù)器記錄單個(gè)時(shí)間歸一化統(tǒng)計(jì)量腕侄。
Spectator計(jì)時(shí)器
一個(gè)計(jì)時(shí)器用于測(cè)量一些事件需要多長(zhǎng)時(shí)間楼肪。Spring Cloud自動(dòng)記錄Spring MVC請(qǐng)求和有條件RestTemplate請(qǐng)求的定時(shí)器民效,稍后可用于為請(qǐng)求相關(guān)指標(biāo)創(chuàng)建儀表板,如延遲:
圖4.請(qǐng)求延遲
// 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);
計(jì)時(shí)器同時(shí)記錄4個(gè)統(tǒng)計(jì)信息:count洼哎,max酸茴,totalOfSquares和totalTime分预。如果您在每次記錄時(shí)間時(shí)在計(jì)數(shù)器上調(diào)用了increment()一次,計(jì)數(shù)統(tǒng)計(jì)量將始終與計(jì)數(shù)器提供的單個(gè)歸一化值相匹配薪捍,因此對(duì)于單個(gè)操作笼痹,不需要單獨(dú)計(jì)數(shù)和分時(shí)。
對(duì)于長(zhǎng)時(shí)間運(yùn)行的操作酪穿,Spectator提供了一個(gè)特殊的LongTaskTimer凳干。
Spectator量規(guī)
量規(guī)用于確定一些當(dāng)前值,如隊(duì)列的大小或處于運(yùn)行狀態(tài)的線程數(shù)被济。由于儀表被采樣救赐,它們不提供關(guān)于這些值在樣品之間如何波動(dòng)的信息。
儀器的正常使用包括在初始化中使用標(biāo)識(shí)符注冊(cè)儀表只磷,對(duì)要采樣的對(duì)象的引用净响,以及基于對(duì)象獲取或計(jì)算數(shù)值的功能少欺。對(duì)對(duì)象的引用被單獨(dú)傳遞喳瓣,Spectator注冊(cè)表將保留對(duì)該對(duì)象的弱引用馋贤。如果對(duì)象被垃圾回收,則Spectator將自動(dòng)刪除注冊(cè)畏陕。見(jiàn)注Spectator是關(guān)于潛在的內(nèi)存泄漏的文件中配乓,如果這個(gè)API被濫用。
// 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);
Spectator分發(fā)摘要
分發(fā)摘要用于跟蹤事件的分布情況惠毁。它類似于一個(gè)計(jì)時(shí)器犹芹,但更普遍的是,大小不一定是一段時(shí)間鞠绰。例如腰埂,分發(fā)摘要可用于測(cè)量服務(wù)器的請(qǐng)求的有效載荷大小。
// the registry will automatically sample this gauge periodically
DistributionSummary ds = registry.distributionSummary("dsName", "tagKey1", "tagValue1", ...);
ds.record(request.sizeInBytes());
指標(biāo)集:Servo
警告
如果您的代碼在Java 8上編譯蜈膨,請(qǐng)使用Spectator而不是Servo屿笼,因?yàn)镾pectator注定要從長(zhǎng)期來(lái)替換Servo。
在Servo語(yǔ)言中翁巍,監(jiān)視器是一個(gè)命名驴一,鍵入和標(biāo)記的配置,而指標(biāo)表示給定監(jiān)視器在某個(gè)時(shí)間點(diǎn)的值灶壶。Servo顯示器在邏輯上相當(dāng)于Spectator米肝断。Servo顯示器由MonitorRegistry創(chuàng)建和控制。盡管有上述警告驰凛,Servo確實(shí)具有比Spectator有米的更廣泛的監(jiān)視器選項(xiàng)胸懈。
Spring Cloud集成為您配置可注入的com.netflix.servo.MonitorRegistry實(shí)例。在Servo中創(chuàng)建了相應(yīng)的Monitor類型后恰响,記錄數(shù)據(jù)的過(guò)程完全類似于Spectator趣钱。
創(chuàng)建Servo顯示器
如果您正在使用由Spring Cloud提供的ServoMonitorRegistry實(shí)例(具體來(lái)說(shuō)是DefaultMonitorRegistry的實(shí)例),則Servo提供了用于檢索計(jì)數(shù)器和計(jì)時(shí)器的便利類渔隶。這些便利類確保每個(gè)唯一的名稱和標(biāo)簽組合只注冊(cè)一個(gè)Monitor羔挡。
要在Servo中手動(dòng)創(chuàng)建監(jiān)視器類型醇锚,特別是對(duì)于不提供方便方法的異域監(jiān)視器類型专酗,通過(guò)提供MonitorConfig實(shí)例來(lái)實(shí)例化適當(dāng)?shù)念愋停?/p>
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);
指標(biāo)后端:Atlas
Netflix開(kāi)發(fā)了Atlas來(lái)管理維度時(shí)間序列數(shù)據(jù)择葡,實(shí)現(xiàn)近實(shí)時(shí)操作洞察烘跺。Atlas具有內(nèi)存中數(shù)據(jù)存儲(chǔ)功能铣揉,可以非称烀觯快速地收集和報(bào)告大量的指標(biāo)神汹。
Atlas捕獲操作情報(bào)曹仗。而商業(yè)智能是收集的數(shù)據(jù)被冒,用于分析一段時(shí)間內(nèi)的趨勢(shì)军掂,操作情報(bào)提供了系統(tǒng)中目前發(fā)生的情況轮蜕。
Spring Cloud提供了一個(gè)spring-cloud-starter-atlas,它具有您需要的所有依賴關(guān)系蝗锥。然后只需使用@EnableAtlas注釋您的Spring Boot應(yīng)用程序跃洛,并為您運(yùn)行的Atlas服務(wù)器提供netflix.atlas.uri屬性的位置。
全球標(biāo)簽
您可以通過(guò)Spring Cloud向發(fā)送到Atlas后端的每個(gè)度量標(biāo)準(zhǔn)添加標(biāo)簽终议。全局標(biāo)簽可用于按應(yīng)用程序名稱汇竭,環(huán)境,區(qū)域等分隔度量穴张。
實(shí)現(xiàn)AtlasTagProvider的每個(gè)bean將貢獻(xiàn)全局標(biāo)簽列表:
@Bean
AtlasTagProvider atlasCommonTags(
@Value("${spring.application.name}") String appName) {
return () -> Collections.singletonMap("app", appName);
}
使用Atlas
要引導(dǎo)內(nèi)存獨(dú)立的Atlas實(shí)例:
$ 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
小費(fèi)
運(yùn)行在r3.2xlarge(61GB RAM)上的Atlas獨(dú)立節(jié)點(diǎn)可以在給定的6小時(shí)窗口內(nèi)每分鐘處理大約200萬(wàn)個(gè)度量值细燎。
一旦運(yùn)行,您收集了少量指標(biāo)皂甘,請(qǐng)通過(guò)在Atlas服務(wù)器上列出代碼來(lái)驗(yàn)證您的設(shè)置是否正確:
$ curl http://ATLAS/api/v1/tags
小費(fèi)
在針對(duì)您的服務(wù)執(zhí)行多個(gè)請(qǐng)求后玻驻,您可以通過(guò)在瀏覽器中粘貼以下URL來(lái)收集關(guān)于每個(gè)請(qǐng)求的請(qǐng)求延遲的一些非常基本的信息:http://ATLAS/api/v1/graph?q=name,rest,:eq,:avg
Atlas wiki包含各種場(chǎng)景樣本查詢的匯編偿枕。
確保使用雙指數(shù)平滑來(lái)查看警報(bào)原理和文檔璧瞬,以生成動(dòng)態(tài)警報(bào)閾值。
重試失敗的請(qǐng)求
Spring Cloud Netflix提供了多種方式來(lái)進(jìn)行HTTP請(qǐng)求益老。您可以使用負(fù)載平衡RestTemplate彪蓬,Ribbon或Feign。無(wú)論您如何選擇HTTP請(qǐng)求捺萌,始終有可能失敗的請(qǐng)求档冬。當(dāng)請(qǐng)求失敗時(shí),您可能希望自動(dòng)重試該請(qǐng)求桃纯。要在使用Sping Cloud Netflix時(shí)完成此操作酷誓,您需要在應(yīng)用程序的類路徑中包含Spring重試。當(dāng)Spring重試出現(xiàn)負(fù)載平衡RestTemplates時(shí)态坦,F(xiàn)eign和Zuul將自動(dòng)重試任何失敗的請(qǐng)求(假設(shè)配置允許)盐数。
組態(tài)
隨時(shí)Ribbon與Spring重試一起使用,您可以通過(guò)配置某些Ribbon屬性來(lái)控制重試功能伞梯。您可以使用的屬性是client.ribbon.MaxAutoRetries玫氢,client.ribbon.MaxAutoRetriesNextServer和client.ribbon.OkToRetryOnAllOperations。請(qǐng)參閱Ribbon文檔谜诫,了解屬性的具體內(nèi)容漾峡。
此外,您可能希望在響應(yīng)中返回某些狀態(tài)代碼時(shí)重試請(qǐng)求喻旷。您可以列出您希望Ribbon客戶端使用屬性clientName.ribbon.retryableStatusCodes重試的響應(yīng)代碼生逸。例如
clientName:
ribbon:
retryableStatusCodes: 404,502
您還可以創(chuàng)建一個(gè)類型為L(zhǎng)oadBalancedRetryPolicy的bean,并實(shí)現(xiàn)retryableStatusCode方法來(lái)確定是否要重試發(fā)出狀態(tài)代碼的請(qǐng)求赵刑。
Zuul
您可以通過(guò)將zuul.retryable設(shè)置為false來(lái)關(guān)閉Zuul的重試功能驹闰。您還可以通過(guò)將zuul.routes.routename.retryable設(shè)置為false,以路由方式禁用重試功能详拙。
Spring Cloud Stream
本節(jié)將詳細(xì)介紹如何使用Spring Cloud Stream遍尺。它涵蓋了創(chuàng)建和運(yùn)行流應(yīng)用程序等主題截酷。
介紹Spring Cloud Stream
Spring Cloud Stream是構(gòu)建消息驅(qū)動(dòng)的微服務(wù)應(yīng)用程序的框架。Spring Cloud Stream基于Spring Boot建立獨(dú)立的生產(chǎn)級(jí)Spring應(yīng)用程序狮鸭,并使用Spring Integration提供與消息代理的連接合搅。它提供了來(lái)自幾家供應(yīng)商的中間件的意見(jiàn)配置,介紹了持久發(fā)布訂閱語(yǔ)義歧蕉,消費(fèi)者組和分區(qū)的概念。
您可以將@EnableBinding注釋添加到應(yīng)用程序康铭,以便立即連接到消息代理惯退,并且可以將@StreamListener添加到方法中,以使其接收流處理的事件从藤。以下是接收外部消息的簡(jiǎn)單接收器應(yīng)用程序催跪。
@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);
}
}
@EnableBinding注釋需要一個(gè)或多個(gè)接口作為參數(shù)(在這種情況下,該參數(shù)是單個(gè)Sink接口)夷野。接口聲明輸入和/或輸出通道懊蒸。Spring Cloud Stream提供了接口Source,Sink和Processor;您還可以定義自己的界面悯搔。
以下是Sink接口的定義:
public interface Sink {
String INPUT = "input";
@Input(Sink.INPUT)
SubscribableChannel input();
}
@Input注釋標(biāo)識(shí)輸入通道骑丸,通過(guò)該輸入通道接收到的消息進(jìn)入應(yīng)用程序;@Output注釋標(biāo)識(shí)輸出通道,發(fā)布的消息將通過(guò)該通道離開(kāi)應(yīng)用程序妒貌。@Input和@Output注釋可以使用頻道名稱作為參數(shù);如果未提供名稱通危,將使用注釋方法的名稱。
Spring Cloud Stream將為您創(chuàng)建一個(gè)界面的實(shí)現(xiàn)灌曙。您可以在應(yīng)用程序中通過(guò)自動(dòng)連接來(lái)使用它菊碟,如下面的測(cè)試用例示例。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = VoteRecordingSinkApplication.class)
@WebAppConfiguration
@DirtiesContext
public class StreamApplicationTests {
@Autowired
private Sink sink;
@Test
public void contextLoads() {
assertNotNull(this.sink.input());
}
}
主要概念
Spring Cloud Stream提供了一些簡(jiǎn)化了消息驅(qū)動(dòng)的微服務(wù)應(yīng)用程序編寫的抽象和原語(yǔ)在刺。本節(jié)概述了以下內(nèi)容:
Spring Cloud Stream的應(yīng)用模型
Binder抽象
持續(xù)的發(fā)布 - 訂閱支持
消費(fèi)者群體支持
分區(qū)支持
一個(gè)可插拔的Binder API
應(yīng)用模型
一個(gè)Spring Cloud Stream應(yīng)用程序由一個(gè)中間件中立的核心組成逆害。該應(yīng)用程序通過(guò)Spring Cloud Stream注入到其中的輸入和輸出通道與外界進(jìn)行通信。渠道通過(guò)中間件特定的Binder實(shí)現(xiàn)連接到外部經(jīng)紀(jì)人蚣驼。
圖5. Spring Cloud Stream應(yīng)用
胖JAR
Spring Cloud Stream應(yīng)用程序可以在獨(dú)立模式下從IDE運(yùn)行進(jìn)行測(cè)試魄幕。要在生產(chǎn)中運(yùn)行Spring Cloud Stream應(yīng)用程序,您可以使用為Maven或Gradle提供的標(biāo)準(zhǔn)Spring Boot工具創(chuàng)建可執(zhí)行文件(或“胖”)JAR隙姿。
Binder抽象
Spring Cloud Stream為Kafka和Rabbit MQ提供Binder實(shí)現(xiàn)梅垄。Spring Cloud Stream還包括一個(gè)TestSupportBinder,它保留了一個(gè)未修改的通道,以便測(cè)試可以直接和可靠地與通道進(jìn)行交互队丝。您可以使用可擴(kuò)展API編寫自己的Binder靡馁。
Spring Cloud Stream使用Spring Boot進(jìn)行配置,Binder抽象使得Spring Cloud Stream應(yīng)用程序可以靈活地連接到中間件机久。例如臭墨,部署者可以在運(yùn)行時(shí)動(dòng)態(tài)地選擇通道連接的目的地(例如,Kafka主題或RabbitMQ交換)膘盖‰食冢可以通過(guò)外部配置屬性和Spring Boot(包括應(yīng)用程序參數(shù),環(huán)境變量和application.yml或application.properties文件)支持的任何形式提供此類配置侠畔。在引入Spring Cloud Stream部分的接收器示例中叫挟,將應(yīng)用程序?qū)傩詓pring.cloud.stream.bindings.input.destination設(shè)置為raw-sensor-data將使其從raw-sensor-dataKafka主題或從綁定到raw-sensor-dataRabbitMQ交換赌结。
Spring Cloud Stream自動(dòng)檢測(cè)并使用類路徑中找到的binder。您可以使用相同的代碼輕松使用不同類型的中間件:在構(gòu)建時(shí)只包含不同的綁定器。對(duì)于更復(fù)雜的用例糖埋,您還可以在應(yīng)用程序中打包多個(gè)綁定器浴栽,并在運(yùn)行時(shí)選擇綁定器懈叹,甚至是否為不同的通道使用不同的綁定器蟹倾。
持續(xù)發(fā)布 - 訂閱支持
應(yīng)用之間的通信遵循發(fā)布訂閱模式,其中通過(guò)共享主題廣播數(shù)據(jù)瘦棋。這可以在下圖中看到稀火,它顯示了一組交互式的Spring Cloud Stream應(yīng)用程序的典型部署。
圖6. Spring Cloud Stream Publish-Subscribe
傳感器向HTTP端點(diǎn)報(bào)告的數(shù)據(jù)將發(fā)送到名為raw-sensor-data的公共目標(biāo)赌朋。從目的地凰狞,它由微服務(wù)應(yīng)用程序獨(dú)立處理,該應(yīng)用程序計(jì)算時(shí)間窗口平均值箕慧,以及另一個(gè)將原始數(shù)據(jù)導(dǎo)入HDFS的微服務(wù)應(yīng)用程序服球。為了處理數(shù)據(jù),兩個(gè)應(yīng)用程序在運(yùn)行時(shí)將主題聲明為它們的輸入颠焦。
發(fā)布訂閱通信模型降低了生產(chǎn)者和消費(fèi)者的復(fù)雜性斩熊,并允許將新應(yīng)用程序添加到拓?fù)渲校粫?huì)中斷現(xiàn)有流伐庭。例如粉渠,在平均計(jì)算應(yīng)用程序的下游,您可以添加一個(gè)計(jì)算顯示和監(jiān)視的最高溫度值的應(yīng)用程序圾另。然后霸株,您可以添加另一個(gè)解釋相同的故障檢測(cè)平均流程的應(yīng)用程序。通過(guò)共享主題而不是點(diǎn)對(duì)點(diǎn)隊(duì)列進(jìn)行所有通信可以減少微服務(wù)之間的耦合集乔。
雖然發(fā)布訂閱消息的概念不是新的去件,但是Spring Cloud Stream需要額外的步驟才能使其成為其應(yīng)用模型的一個(gè)有意義的選擇。通過(guò)使用本地中間件支持,Spring Cloud Stream還簡(jiǎn)化了在不同平臺(tái)上使用發(fā)布訂閱模型尤溜。
消費(fèi)群體
雖然發(fā)布訂閱模型可以輕松地通過(guò)共享主題連接應(yīng)用程序倔叼,但通過(guò)創(chuàng)建給定應(yīng)用程序的多個(gè)實(shí)例來(lái)擴(kuò)展的能力同樣重要。當(dāng)這樣做時(shí)宫莱,應(yīng)用程序的不同實(shí)例被放置在競(jìng)爭(zhēng)的消費(fèi)者關(guān)系中丈攒,其中只有一個(gè)實(shí)例預(yù)期處理給定消息。
Spring Cloud Stream通過(guò)消費(fèi)者組的概念來(lái)模擬此行為授霸。(Spring Cloud Stream消費(fèi)者組與Kafka消費(fèi)者組相似并受到啟發(fā)巡验。)每個(gè)消費(fèi)者綁定可以使用spring.cloud.stream.bindings..group屬性來(lái)指定組名稱。對(duì)于下圖所示的消費(fèi)者碘耳,此屬性將設(shè)置為spring.cloud.stream.bindings..group=hdfsWrite或spring.cloud.stream.bindings..group=average显设。
圖7. Spring Cloud Stream消費(fèi)者組
訂閱給定目標(biāo)的所有組都會(huì)收到已發(fā)布數(shù)據(jù)的副本,但每個(gè)組中只有一個(gè)成員從該目的地接收給定的消息藏畅。默認(rèn)情況下敷硅,當(dāng)未指定組時(shí),Spring Cloud Stream將應(yīng)用程序分配給與所有其他消費(fèi)者組發(fā)布 - 訂閱關(guān)系的匿名獨(dú)立單個(gè)成員消費(fèi)者組愉阎。
耐久力
符合Spring Cloud Stream的有意義的應(yīng)用模式,消費(fèi)者群體訂閱是持久的力奋。也就是說(shuō)榜旦,綁定實(shí)現(xiàn)確保組預(yù)訂是持久的涣楷,一旦已經(jīng)創(chuàng)建了一個(gè)組的至少一個(gè)訂閱篡九,即使組中的所有應(yīng)用程序都被停止,組也將接收消息炼蛤。
注意
匿名訂閱本質(zhì)上是不耐用的猿挚。對(duì)于某些binder實(shí)現(xiàn)(例如RabbitMQ)咐旧,可以具有非持久組的訂閱。
通常绩蜻,當(dāng)將應(yīng)用綁定到給定目的地時(shí)铣墨,最好始終指定消費(fèi)者組。在擴(kuò)展Spring Cloud Stream應(yīng)用程序時(shí)办绝,必須為每個(gè)輸入綁定指定一個(gè)使用者組伊约。這樣可以防止應(yīng)用程序的實(shí)例收到重復(fù)的消息(除非需要這種行為,這是不尋常的)孕蝉。
分區(qū)支持
Spring Cloud Stream提供對(duì)給定應(yīng)用程序的多個(gè)實(shí)例之間的分區(qū)數(shù)據(jù)的支持屡律。在分區(qū)場(chǎng)景中,物理通信介質(zhì)(例如降淮,代理主題)被視為被構(gòu)造成多個(gè)分區(qū)超埋。一個(gè)或多個(gè)生產(chǎn)者應(yīng)用程序?qū)嵗龑?shù)據(jù)發(fā)送到多個(gè)消費(fèi)者應(yīng)用程序?qū)嵗⒋_保由共同特征標(biāo)識(shí)的數(shù)據(jù)由相同的消費(fèi)者實(shí)例處理。
Spring Cloud Stream提供了統(tǒng)一方式實(shí)現(xiàn)分區(qū)處理用例的通用抽象霍殴。因此媒惕,無(wú)論代理本身是否自然分區(qū)(例如Kafka)(例如RabbitMQ),分區(qū)可以被使用繁成。
圖8. Spring Cloud Stream分區(qū)
分區(qū)是狀態(tài)處理中的一個(gè)關(guān)鍵概念吓笙,無(wú)論是性能還是一致性原因,它都是批評(píng)性的巾腕,以確保所有相關(guān)數(shù)據(jù)一起處理面睛。例如,在時(shí)間平均計(jì)算示例中尊搬,重要的是所有給定傳感器的所有測(cè)量都由相同的應(yīng)用實(shí)例進(jìn)行處理叁鉴。
注意
要設(shè)置分區(qū)處理方案,您必須同時(shí)配置數(shù)據(jù)生成和數(shù)據(jù)消耗結(jié)束佛寿。
編程模型
本節(jié)介紹Spring Cloud Stream的編程模型幌墓。Spring Cloud Stream提供了許多預(yù)定義的注釋,用于聲明綁定的輸入和輸出通道冀泻,以及如何收聽(tīng)頻道常侣。
聲明和綁定頻道
觸發(fā)綁定@EnableBinding
您可以將Spring應(yīng)用程序轉(zhuǎn)換為Spring Cloud Stream應(yīng)用程序,將@EnableBinding注釋應(yīng)用于應(yīng)用程序的配置類之一弹渔。@EnableBinding注釋本身使用@Configuration進(jìn)行元注釋胳施,并觸發(fā)Spring Cloud Stream基礎(chǔ)架構(gòu)的配置:
...
@Import(...)
@Configuration
@EnableIntegration
public @interface EnableBinding {
...
Class[] value() default {};
}
@EnableBinding注釋可以將一個(gè)或多個(gè)接口類作為參數(shù),這些接口類包含表示可綁定組件(通常是消息通道)的方法肢专。
注意
在Spring Cloud Stream 1.0中舞肆,唯一支持的可綁定組件是Spring消息傳遞MessageChannel及其擴(kuò)展名SubscribableChannel和PollableChannel。未來(lái)版本應(yīng)該使用相同的機(jī)制將此支持?jǐn)U展到其他類型的組件博杖。在本文檔中椿胯,我們將繼續(xù)參考渠道。
@Input和@Output
Spring Cloud Stream應(yīng)用程序可以在接口中定義任意數(shù)量的輸入和輸出通道為@Input和@Output方法:
public interface Barista {
@Input
SubscribableChannel orders();
@Output
MessageChannel hotDrinks();
@Output
MessageChannel coldDrinks();
}
使用此接口作為參數(shù)@EnableBinding將分別觸發(fā)三個(gè)綁定的通道名稱為orders剃根,hotDrinks和coldDrinks哩盲。
@EnableBinding(Barista.class)
public class CafeConfiguration {
...
}
自定義頻道名稱
使用@Input和@Output注釋,您可以指定頻道的自定義頻道名稱,如以下示例所示:
public interface Barista {
...
@Input("inboundOrders")
SubscribableChannel orders();
}
在這個(gè)例子中,創(chuàng)建的綁定通道將被命名為inboundOrders敷钾。
Source,Sink和Processor
為了方便尋址最常見(jiàn)的用例娱两,涉及輸入通道,輸出通道或兩者金吗,Spring Cloud Stream提供了開(kāi)箱即用的三個(gè)預(yù)定義接口十兢。
Source可用于具有單個(gè)出站通道的應(yīng)用程序趣竣。
public interface Source {
String OUTPUT = "output";
@Output(Source.OUTPUT)
MessageChannel output();
}
Sink可用于具有單個(gè)入站通道的應(yīng)用程序。
public interface Sink {
String INPUT = "input";
@Input(Sink.INPUT)
SubscribableChannel input();
}
Processor可用于具有入站通道和出站通道的應(yīng)用程序旱物。
public interface Processor extends Source, Sink {
}
Spring Cloud Stream不為任何這些接口提供特殊處理;它們只是開(kāi)箱即用遥缕。
訪問(wèn)綁定通道
注入綁定界面
對(duì)于每個(gè)綁定接口,Spring Cloud Stream將生成一個(gè)實(shí)現(xiàn)該接口的bean宵呛。調(diào)用其中一個(gè)bean的@Input注釋或@Output注釋方法將返回相關(guān)的綁定通道单匣。
以下示例中的bean在調(diào)用其hello方法時(shí)在輸出通道上發(fā)送消息。它在注入的Sourcebean上調(diào)用output()來(lái)檢索目標(biāo)通道宝穗。
@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());
}
}
直接注入渠道
綁定通道也可以直接注入:
@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());
}
}
如果在聲明注釋上定制了通道的名稱户秤,則應(yīng)使用該名稱而不是方法名稱。給出以下聲明:
public interface CustomSource {
...
@Output("customOutput")
MessageChannel output();
}
通道將被注入逮矛,如下例所示:
@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());
}
}
生產(chǎn)和消費(fèi)消息
您可以使用Spring Integration注解或Spring Cloud Stream的@StreamListener注釋編寫Spring Cloud Stream應(yīng)用程序鸡号。@StreamListener注釋在其他Spring消息傳遞注釋(例如@MessageMapping,@JmsListener须鼎,@RabbitListener等)之后建模鲸伴,但添加內(nèi)容類型管理和類型強(qiáng)制功能。
本地Spring Integration支持
由于Spring Cloud Stream基于Spring Integration晋控,Stream完全繼承了Integration的基礎(chǔ)和基礎(chǔ)架構(gòu)以及組件本身汞窗。例如,您可以將Source的輸出通道附加到MessageSource:
@EnableBinding(Source.class)
public class TimerSource {
@Value("${format}")
private String format;
@Bean
@InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedDelay = "${fixedDelay}", maxMessagesPerPoll = "1"))
public MessageSource timerMessageSource() {
return () -> new GenericMessage<>(new SimpleDateFormat(format).format(new Date()));
}
}
或者您可以在變壓器中使用處理器的通道:
@EnableBinding(Processor.class)
public class TransformProcessor {
@Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT)
public Object transform(String message) {
return message.toUpperCase();
}
}
Spring Integration錯(cuò)誤通道支持
Spring Cloud Stream支持發(fā)布Spring Integration全局錯(cuò)誤通道收到的錯(cuò)誤消息赡译。發(fā)送到errorChannel的錯(cuò)誤消息可以通過(guò)為名為error的出站目標(biāo)配置綁定杉辙,將其發(fā)布到代理的特定目標(biāo)。例如捶朵,要將錯(cuò)誤消息發(fā)布到名為“myErrors”的代理目標(biāo),請(qǐng)?zhí)峁┮韵聦傩裕簊pring.cloud.stream.bindings.error.destination=myErrors
使用@StreamListener進(jìn)行自動(dòng)內(nèi)容類型處理
Spring Integration支持Spring Cloud Stream提供自己的@StreamListener注釋狂男,以其他Spring消息傳遞注釋(例如@MessageMapping综看,@JmsListener,@RabbitListener等) )岖食。@StreamListener注釋提供了一種更簡(jiǎn)單的處理入站郵件的模型红碑,特別是在處理涉及內(nèi)容類型管理和類型強(qiáng)制的用例時(shí)。
Spring Cloud Stream提供了一種可擴(kuò)展的MessageConverter機(jī)制泡垃,用于通過(guò)綁定通道處理數(shù)據(jù)轉(zhuǎn)換析珊,并且在這種情況下,將調(diào)度到使用@StreamListener注釋的方法蔑穴。以下是處理外部Vote事件的應(yīng)用程序的示例:
@EnableBinding(Sink.class)
public class VoteHandler {
@Autowired
VotingService votingService;
@StreamListener(Sink.INPUT)
public void handle(Vote vote) {
votingService.record(vote);
}
}
當(dāng)考慮String有效載荷和contentType標(biāo)題application/json的入站Message時(shí)忠寻,可以看到@StreamListener和Spring Integration@ServiceActivator之間的區(qū)別。在@StreamListener的情況下存和,MessageConverter機(jī)制將使用contentType標(biāo)頭將String有效載荷解析為Vote對(duì)象奕剃。
與其他Spring消息傳遞方法一樣衷旅,方法參數(shù)可以用@Payload,@Headers和@Header注釋纵朋。
注意
對(duì)于返回?cái)?shù)據(jù)的方法柿顶,您必須使用@SendTo注釋來(lái)指定方法返回的數(shù)據(jù)的輸出綁定目的地:
@EnableBinding(Processor.class)
public class TransformProcessor {
@Autowired
VotingService votingService;
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public VoteResult handle(Vote vote) {
return votingService.record(vote);
}
}
使用@StreamListener將消息分派到多個(gè)方法
自1.2版本以來(lái)基协,Spring Cloud Stream支持根據(jù)條件向在輸入通道上注冊(cè)的多個(gè)@StreamListener方法發(fā)送消息荚坞。
為了有資格支持有條件的調(diào)度遣钳,一種方法必須滿足以下條件:
它不能返回值
它必須是一個(gè)單獨(dú)的消息處理方法(不支持的反應(yīng)API方法)
條件通過(guò)注釋的condition屬性中的SpEL表達(dá)式指定盔夜,并為每個(gè)消息進(jìn)行評(píng)估飞蚓。匹配條件的所有處理程序?qū)⒃谕粋€(gè)線程中被調(diào)用部逮,并且不必對(duì)調(diào)用發(fā)生的順序做出假設(shè)党饮。
使用@StreamListener具有調(diào)度條件的示例可以在下面看到述雾。在此示例中胆建,帶有值為foo的標(biāo)題type的所有消息將被分派到receiveFoo方法烤低,所有帶有值為bar的標(biāo)題type的消息將被分派到receiveBar方法。
@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
}
}
注意
僅通過(guò)@StreamListener條件進(jìn)行調(diào)度僅對(duì)單個(gè)消息的處理程序支持笆载,而不適用于無(wú)效編程支持(如下所述)扑馁。
反應(yīng)式編程支持
Spring Cloud Stream還支持使用反應(yīng)性API,其中將傳入和傳出數(shù)據(jù)作為連續(xù)數(shù)據(jù)流處理凉驻。通過(guò)spring-cloud-stream-reactive提供對(duì)反應(yīng)性API的支持腻要,需要將其明確添加到您的項(xiàng)目中。
具有反應(yīng)性API的編程模型是聲明式的涝登,而不是指定如何處理每個(gè)單獨(dú)的消息雄家,您可以使用描述從入站到出站數(shù)據(jù)流的功能轉(zhuǎn)換的運(yùn)算符。
Spring Cloud Stream支持以下反應(yīng)性API:
反應(yīng)堆
RxJava 1.x
將來(lái)胀滚,它旨在支持基于活動(dòng)流的更通用的模型趟济。
反應(yīng)式編程模型還使用@StreamListener注釋來(lái)設(shè)置反應(yīng)處理程序。差異在于:
@StreamListener注釋不能指定輸入或輸出咽笼,因?yàn)樗鼈冏鳛閰?shù)提供顷编,并從方法返回值;
必須使用@Input和@Output注釋方法的參數(shù),指示輸入和分別輸出的數(shù)據(jù)流連接到哪個(gè)輸入或輸出;
方法的返回值(如果有的話)將用@Output注釋剑刑,表示要發(fā)送數(shù)據(jù)的輸入媳纬。
注意
反應(yīng)式編程支持需要Java 1.8。
注意
截至Spring Cloud Stream 1.1.1及更高版本(從布魯克林發(fā)行版開(kāi)始列出)施掏,反應(yīng)式編程支持需要使用Reactor 3.0.4.RELEASE和更高版本钮惠。不支持早期的Reactor版本(包括3.0.1.RELEASE,3.0.2.RELEASE和3.0.3.RELEASE)七芭。spring-cloud-stream-reactive將會(huì)過(guò)渡地檢索正確的版本,但項(xiàng)目結(jié)構(gòu)有可能將io.projectreactor:reactor-core的版本管理到較早版本贮庞,特別是在使用Maven時(shí)几迄。對(duì)于通過(guò)Spring Initializr(Spring Boot 1.x)生成的項(xiàng)目属铁,這將覆蓋Reactor版本為2.0.8.RELEASE钢拧。在這種情況下泉手,您必須確保釋放正確版本的工件把还。這可以通過(guò)在io.projectreactor:reactor-core上直接依賴于3.0.4.RELEASE或更高版本的項(xiàng)目來(lái)簡(jiǎn)單地實(shí)現(xiàn)谐区。
注意
術(shù)語(yǔ)reactive的使用目前指的是正在使用的反應(yīng)性API冗尤,而不是執(zhí)行模型是無(wú)效的(即,綁定的端點(diǎn)仍然使用“推”而不是“拉”模型)。雖然通過(guò)使用Reactor提供了一些背壓支持巴刻,但我們希望長(zhǎng)期以來(lái)通過(guò)使用連接的中間件的本機(jī)反應(yīng)客戶端來(lái)支持完全無(wú)反應(yīng)的管道泪漂。
基于反應(yīng)器的處理程序
基于反應(yīng)器的處理程序可以具有以下參數(shù)類型:
對(duì)于用@Input注釋的參數(shù),它支持反應(yīng)器類型Flux萝勤。入站通量的參數(shù)化遵循與單個(gè)消息處理相同的規(guī)則:它可以是整個(gè)Message蛔外,一個(gè)可以是Message有效負(fù)載的POJO醇王,也可以是一個(gè)POJO基于Message內(nèi)容類型頭的轉(zhuǎn)換淋昭。提供多個(gè)輸入;
對(duì)于使用Output注釋的參數(shù)抹缕,它支持將方法生成的Flux與輸出連接的類型FluxSender。一般來(lái)說(shuō)趟径,僅當(dāng)方法可以有多個(gè)輸出時(shí)才建議指定輸出作為參數(shù);
基于反應(yīng)器的處理程序支持Flux的返回類型瘪吏,其中必須使用@Output注釋。當(dāng)單個(gè)輸出通量可用時(shí)蜗巧,我們建議使用該方法的返回值掌眠。
這是一個(gè)簡(jiǎn)單的基于反應(yīng)器的處理器的例子。
@EnableBinding(Processor.class)
@EnableAutoConfiguration
public static class UppercaseTransformer {
@StreamListener
@Output(Processor.OUTPUT)
public Flux receive(@Input(Processor.INPUT) Flux input) {
return input.map(s -> s.toUpperCase());
}
}
使用輸出參數(shù)的同一個(gè)處理器如下所示:
@EnableBinding(Processor.class)
@EnableAutoConfiguration
public static class UppercaseTransformer {
@StreamListener
public void receive(@Input(Processor.INPUT) Flux input,
@Output(Processor.OUTPUT) FluxSender output) {
output.send(input.map(s -> s.toUpperCase()));
}
}
RxJava 1.x支持
RxJava 1.x處理程序遵循與基于反應(yīng)器的規(guī)則相同的規(guī)則幕屹,但將使用Observable和ObservableSender參數(shù)和返回類型蓝丙。
所以上面的第一個(gè)例子會(huì)變成:
@EnableBinding(Processor.class)
@EnableAutoConfiguration
public static class UppercaseTransformer {
@StreamListener
@Output(Processor.OUTPUT)
public Observable receive(@Input(Processor.INPUT) Observable input) {
return input.map(s -> s.toUpperCase());
}
}
上面的第二個(gè)例子將會(huì)變成:
@EnableBinding(Processor.class)
@EnableAutoConfiguration
public static class UppercaseTransformer {
@StreamListener
public void receive(@Input(Processor.INPUT) Observable input,
@Output(Processor.OUTPUT) ObservableSender output) {
output.send(input.map(s -> s.toUpperCase()));
}
}
聚合
Spring Cloud Stream支持將多個(gè)應(yīng)用程序聚合在一起级遭,直接連接其輸入和輸出通道,并避免通過(guò)代理交換消息的額外成本渺尘。從版本1.0的Spring Cloud Stream開(kāi)始挫鸽,僅對(duì)以下類型的應(yīng)用程序支持聚合:
來(lái)源- 具有名為output的單個(gè)輸出通道的應(yīng)用程序,通常具有類型為org.springframework.cloud.stream.messaging.Source的單個(gè)綁定
接收器- 具有名為input的單個(gè)輸入通道的應(yīng)用程序鸥跟,通常具有類型為org.springframework.cloud.stream.messaging.Sink的單個(gè)綁定
處理器- 具有名為input的單個(gè)輸入通道和名為output的單個(gè)輸出通道的應(yīng)用程序丢郊,通常具有類型為org.springframework.cloud.stream.messaging.Processor的單個(gè)綁定。
它們可以通過(guò)創(chuàng)建一系列互連的應(yīng)用程序來(lái)聚合在一起医咨,其中序列中的元素的輸出通道連接到下一個(gè)元素的輸入通道(如果存在)蚂夕。序列可以從源或處理器開(kāi)始,它可以包含任意數(shù)量的處理器腋逆,并且必須以處理器或接收器結(jié)束。
根據(jù)起始和結(jié)束元素的性質(zhì)侈贷,序列可以具有一個(gè)或多個(gè)可綁定的信道惩歉,如下所示:
如果序列從源頭開(kāi)始并以sink結(jié)束,則應(yīng)用程序之間的所有通信都是直接的俏蛮,并且不會(huì)綁定任何通道
如果序列以處理器開(kāi)始撑蚌,則其輸入通道將成為聚合的input通道,并將相應(yīng)地進(jìn)行綁定
如果序列以處理器結(jié)束搏屑,則其輸出通道將成為聚合的output通道争涌,并將相應(yīng)地進(jìn)行綁定
使用AggregateApplicationBuilder實(shí)用程序類執(zhí)行聚合,如以下示例所示辣恋。我們考慮一個(gè)項(xiàng)目亮垫,我們有源,處理器和匯點(diǎn)伟骨,可以在項(xiàng)目中定義饮潦,或者可以包含在項(xiàng)目的依賴之一中。
注意
如果配置類使用@SpringBootApplication携狭,聚合應(yīng)用程序中的每個(gè)組件(源继蜡,宿或處理器)必須在單獨(dú)的包中提供。這是為了避免應(yīng)用程序之間的串?dāng)_逛腿,由于在同一個(gè)包內(nèi)的配置類上由@SpringBootApplication執(zhí)行的類路徑掃描稀并。在下面的示例中,可以看到单默,Source碘举,Processor和Sink應(yīng)用程序類分組在單獨(dú)的包中。一個(gè)可能的替代方法是在單獨(dú)的@Configuration類中提供源,宿或處理器配置腐宋,避免使用@SpringBootApplication/@ComponentScan并使用它們進(jìn)行聚合。
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);
}
}
package com.app.myprocessor;
@SpringBootApplication
@EnableBinding(Processor.class)
public class ProcessorApplication {
@Transformer
public String loggerSink(String payload) {
return payload.toUpperCase();
}
}
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());
}
}
每個(gè)配置可以用于運(yùn)行一個(gè)單獨(dú)的組件伯病,但在這種情況下线欲,它們可以聚合在一起明场,如下所示:
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);
}
}
該序列的起始組件作為from()方法的參數(shù)提供。序列的結(jié)尾部分作為to()方法的參數(shù)提供李丰。中間處理器作為via()方法的參數(shù)提供苦锨。同一類型的多個(gè)處理器可以一起鏈接(例如,用于具有不同配置的流水線轉(zhuǎn)換)趴泌。對(duì)于每個(gè)組件舟舒,構(gòu)建器可以為Spring Boot配置提供運(yùn)行時(shí)參數(shù)。
配置聚合應(yīng)用程序
Spring Cloud Stream支持使用'namespace'作為前綴向聚合應(yīng)用程序內(nèi)的各個(gè)應(yīng)用程序傳遞屬性嗜憔。
命名空間可以為應(yīng)用程序設(shè)置如下:
@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);
}
}
一旦為單個(gè)應(yīng)用程序設(shè)置了“命名空間”秃励,則可以使用任何支持的屬性源(命令行,環(huán)境屬性等)將具有namespace作為前綴的應(yīng)用程序?qū)傩詡鬟f到聚合應(yīng)用程序吉捶,
例如夺鲜,要覆蓋“source”和“sink”應(yīng)用程序的默認(rèn)fixedDelay和debug屬性:
java -jar target/MyAggregateApplication-0.0.1-SNAPSHOT.jar --source.fixedDelay=10000 --sink.debug=false
為非自包含聚合應(yīng)用程序配置綁定服務(wù)屬性
非自包含聚合應(yīng)用程序通過(guò)聚合應(yīng)用程序的入站/出站組件(通常為消息通道)中的一個(gè)或兩者綁定到外部代理,而聚合應(yīng)用程序內(nèi)的應(yīng)用程序是直接綁定的呐舔。例如:源應(yīng)用程序的輸出和處理器應(yīng)用程序的輸入是直接綁定的币励,而處理器的輸出通道綁定到代理的外部目的地。當(dāng)傳遞非自包含聚合應(yīng)用程序的綁定服務(wù)屬性時(shí)珊拼,需要將綁定服務(wù)屬性傳遞給聚合應(yīng)用程序食呻,而不是將它們?cè)O(shè)置為單個(gè)子應(yīng)用程序的“args”。例如澎现,
@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);
}
}
需要將綁定屬性--spring.cloud.stream.bindings.output.destination=processor-output指定為外部配置屬性(cmdline arg等)之一仅胞。
Binders
Spring Cloud Stream提供了一個(gè)Binder抽象,用于連接到外部中間件的物理目標(biāo)剑辫。本節(jié)提供有關(guān)Binder SPI饼问,其主要組件和實(shí)現(xiàn)特定詳細(xì)信息背后的主要概念的信息。
生產(chǎn)者和消費(fèi)者
圖9.生產(chǎn)者和消費(fèi)者
甲生產(chǎn)者是將消息發(fā)送到信道的任何組分揭斧。該通道可以通過(guò)該代理的Binder實(shí)現(xiàn)綁定到外部消息代理莱革。當(dāng)調(diào)用bindProducer()方法時(shí),第一個(gè)參數(shù)是代理中目標(biāo)的名稱讹开,第二個(gè)參數(shù)是生成器將發(fā)送消息的本地通道實(shí)例盅视,第三個(gè)參數(shù)包含屬性(如分區(qū)鍵表達(dá)式)在為該通道創(chuàng)建的適配器中使用。
甲消費(fèi)者的是旦万,從一個(gè)信道接收的消息的任何組分闹击。與生產(chǎn)者一樣,消費(fèi)者的頻道可以綁定到外部消息代理成艘。當(dāng)調(diào)用bindConsumer()方法時(shí)赏半,第一個(gè)參數(shù)是目標(biāo)名稱贺归,第二個(gè)參數(shù)提供消費(fèi)者邏輯組的名稱。由給定目的地的消費(fèi)者綁定表示的每個(gè)組接收生產(chǎn)者發(fā)送到該目的地的每個(gè)消息的副本(即断箫,發(fā)布 - 訂閱語(yǔ)義)拂酣。如果有多個(gè)使用相同組名稱的消費(fèi)者實(shí)例綁定,那么消息將在這些消費(fèi)者實(shí)例之間進(jìn)行負(fù)載平衡仲义,以便生產(chǎn)者發(fā)送的每個(gè)消息僅由每個(gè)組中的單個(gè)消費(fèi)者實(shí)例消耗(即排隊(duì)語(yǔ)義)婶熬。
Binder SPI
Binder SPI包括許多接口,即插即用實(shí)用程序類和發(fā)現(xiàn)策略埃撵,提供可插拔機(jī)制連接到外部中間件奈懒。
SPI的關(guān)鍵點(diǎn)是Binder接口窗怒,它是將輸入和輸出連接到外部中間件的策略。
public interface Binder {
Binding bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
Binding bindProducer(String name, T outboundBindTarget, P producerProperties);
}
界面參數(shù)化文判,提供多個(gè)擴(kuò)展點(diǎn):
輸入和輸出綁定目標(biāo) - 從版本1.0開(kāi)始浴捆,只支持MessageChannel到踏,但是這個(gè)目標(biāo)是將來(lái)用作擴(kuò)展點(diǎn);
擴(kuò)展的消費(fèi)者和生產(chǎn)者屬性 - 允許特定的Binder實(shí)現(xiàn)來(lái)添加可以以類型安全的方式支持的補(bǔ)充屬性鸦做。
典型的綁定實(shí)現(xiàn)包括以下內(nèi)容
一個(gè)實(shí)現(xiàn)Binder接口的類;
一個(gè)Spring@Configuration類出革,與中間件連接基礎(chǔ)架構(gòu)一起創(chuàng)建上述類型的bean;
在類路徑中找到的包含一個(gè)或多個(gè)綁定器定義的META-INF/spring.binders文件,例如
kafka:\
org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration
Binder檢測(cè)
Spring Cloud Stream依賴于Binder SPI的實(shí)現(xiàn)來(lái)執(zhí)行將通道連接到消息代理的任務(wù)芝发。每個(gè)Binder實(shí)現(xiàn)通常連接到一種類型的消息系統(tǒng)。
類路徑檢測(cè)
默認(rèn)情況下苛谷,Spring Cloud Stream依賴于Spring Boot的自動(dòng)配置來(lái)配置綁定過(guò)程辅鲸。如果在類路徑中找到單個(gè)Binder實(shí)現(xiàn),則Spring Cloud Stream將自動(dòng)使用腹殿。例如独悴,一個(gè)旨在綁定到RabbitMQ的Spring Cloud Stream項(xiàng)目可以簡(jiǎn)單地添加以下依賴項(xiàng):
org.springframework.cloud
spring-cloud-stream-binder-rabbit
對(duì)于其他綁定依賴關(guān)系的特定maven坐標(biāo),請(qǐng)參閱該binder實(shí)現(xiàn)的文檔锣尉。
Classpath上有多個(gè)Binders
當(dāng)類路徑中存在多個(gè)綁定器時(shí)刻炒,應(yīng)用程序必須指明每個(gè)通道綁定將使用哪個(gè)綁定器。每個(gè)binder配置都包含一個(gè)META-INF/spring.binders自沧,它是一個(gè)簡(jiǎn)單的屬性文件:
rabbit:\
org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration
對(duì)于其他提供的綁定實(shí)現(xiàn)(例如Kafka)坟奥,存在類似的文件,并且預(yù)期自定義綁定實(shí)現(xiàn)也可以提供它們拇厢。鍵代表binder實(shí)現(xiàn)的標(biāo)識(shí)名稱爱谁,而該值是以逗號(hào)分隔的配置類列表,每個(gè)配置類都包含唯一的一個(gè)類型為org.springframework.cloud.stream.binder.Binder的bean定義孝偎。
可以使用spring.cloud.stream.defaultBinder屬性(例如spring.cloud.stream.defaultBinder=rabbit)全局執(zhí)行Binder選擇访敌,或通過(guò)在每個(gè)通道綁定上配置binder來(lái)單獨(dú)執(zhí)行。例如衣盾,從Kafka讀取并寫入RabbitMQ的處理器應(yīng)用程序(分別具有用于讀/寫的名稱為input和output的通道)可以指定以下配置:
spring.cloud.stream.bindings.input.binder=kafka
spring.cloud.stream.bindings.output.binder=rabbit
連接到多個(gè)系統(tǒng)
默認(rèn)情況下寺旺,綁定器共享應(yīng)用程序的Spring Boot自動(dòng)配置爷抓,以便在類路徑中找到每個(gè)綁定器的一個(gè)實(shí)例。如果您的應(yīng)用程序連接到同一類型的多個(gè)代理阻塑,則可以指定多個(gè)綁定器配置蓝撇,每個(gè)具有不同的環(huán)境設(shè)置。
注意
打開(kāi)顯式綁定器配置將完全禁用默認(rèn)綁定器配置過(guò)程叮姑。如果這樣做唉地,所有使用的綁定器都必須包含在配置中。打算透明使用Spring Cloud Stream的框架可能會(huì)創(chuàng)建可以通過(guò)名稱引用的binder配置传透,但不會(huì)影響默認(rèn)的綁定器配置耘沼。為此,綁定器配置可能將其defaultCandidate標(biāo)志設(shè)置為false朱盐,例如spring.cloud.stream.binders..defaultCandidate=false群嗤。這表示將獨(dú)立于默認(rèn)binder配置過(guò)程存在的配置。
例如兵琳,這是連接到兩個(gè)RabbitMQ代理實(shí)例的處理器應(yīng)用程序的典型配置:
spring:
cloud:
stream:
bindings:
input:
destination: foo
binder: rabbit1
output:
destination: bar
binder: rabbit2
binders:
rabbit1:
type: rabbit
environment:
spring:
rabbitmq:
host:
rabbit2:
type: rabbit
environment:
spring:
rabbitmq:
host:
Binder配置屬性
創(chuàng)建自定義綁定器配置時(shí)狂秘,以下屬性可用。它們必須以spring.cloud.stream.binders.為前綴躯肌。
類型
粘合劑類型者春。它通常引用在類路徑中找到的綁定器之一酵颁,特別是META-INF/spring.binders文件中的鍵帆离。
默認(rèn)情況下,它具有與配置名稱相同的值眶俩。
inheritEnvironment
配置是否會(huì)繼承應(yīng)用程序本身的環(huán)境嫡丙。
默認(rèn)true拴袭。
環(huán)境
一組可用于自定義綁定環(huán)境的屬性的根。配置此配置后曙博,創(chuàng)建綁定器的上下文不是應(yīng)用程序上下文的子級(jí)拥刻。這允許粘合劑組分和應(yīng)用組分之間的完全分離。
默認(rèn)empty父泳。
defaultCandidate
粘合劑配置是否被認(rèn)為是默認(rèn)的粘合劑的候選者般哼,或者僅在明確引用時(shí)才能使用。這允許添加binder配置惠窄,而不會(huì)干擾默認(rèn)處理逝她。
默認(rèn)true。
配置選項(xiàng)
Spring Cloud Stream支持常規(guī)配置選項(xiàng)以及綁定和綁定器的配置睬捶。一些綁定器允許額外的綁定屬性來(lái)支持中間件特定的功能黔宛。
可以通過(guò)Spring Boot支持的任何機(jī)制將配置選項(xiàng)提供給Spring Cloud Stream應(yīng)用程序。這包括應(yīng)用程序參數(shù),環(huán)境變量和YAML或.properties文件臀晃。
Spring Cloud Stream Properties
spring.cloud.stream.instanceCount
應(yīng)用程序部署實(shí)例的數(shù)量觉渴。必須設(shè)置分區(qū),如果使用Kafka徽惋。
默認(rèn)值:1案淋。
spring.cloud.stream.instanceIndex
應(yīng)用程序的實(shí)例索引:從0到instanceCount- 1的數(shù)字。用于分區(qū)和使用Kafka险绘。在Cloud Foundry中自動(dòng)設(shè)置以匹配應(yīng)用程序的實(shí)例索引踢京。
spring.cloud.stream.dynamicDestinations
可以動(dòng)態(tài)綁定的目標(biāo)列表(例如,在動(dòng)態(tài)路由方案中)宦棺。如果設(shè)置瓣距,只能列出目的地。
默認(rèn)值:空(允許任何目的地綁定)代咸。
spring.cloud.stream.defaultBinder
如果配置了多個(gè)綁定器蹈丸,則使用默認(rèn)的binder。請(qǐng)參閱Classpath上的Multiple Binders呐芥。
默認(rèn)值:空逻杖。
spring.cloud.stream.overrideCloudConnectors
此屬性僅適用于cloud配置文件激活且Spring Cloud連接器隨應(yīng)用程序一起提供。如果屬性為false(默認(rèn)值)思瘟,綁定器將檢測(cè)適合的綁定服務(wù)(例如荸百,在Cloud Foundry中為RabbitMQ綁定器綁定的RabbitMQ服務(wù)),并將使用它來(lái)創(chuàng)建連接(通常通過(guò)Spring Cloud連接器)滨攻。當(dāng)設(shè)置為true時(shí)够话,此屬性指示綁定器完全忽略綁定的服務(wù)景埃,并依賴Spring Boot屬性(例如,依賴于RabbitMQ綁定器環(huán)境中提供的spring.rabbitmq.*屬性)剩失。當(dāng)連接到多個(gè)系統(tǒng)時(shí),此屬性的典型用法將嵌套在定制環(huán)境中盔几。
默認(rèn)值:false钥星。
綁定Properties
綁定屬性使用格式spring.cloud.stream.bindings..=提供。表示正在配置的通道的名稱(例如Source的output)鲁沥。
為了避免重復(fù)碧囊,Spring Cloud Stream支持所有通道的設(shè)置值,格式為spring.cloud.stream.default.=。
在下面的內(nèi)容中骚腥,我們指出我們?cè)谀睦锸÷粤藄pring.cloud.stream.bindings..前綴,并且只關(guān)注屬性名稱瓶逃,但有一個(gè)理解束铭,前綴將被包含在運(yùn)行時(shí)。
Properties使用Spring Cloud Stream
以下綁定屬性可用于輸入和輸出綁定厢绝,并且必須以spring.cloud.stream.bindings..為前綴契沫,例如spring.cloud.stream.bindings.input.destination=ticktock。
可以使用前綴spring.cloud.stream.default設(shè)置默認(rèn)值代芜,例如spring.cloud.stream.default.contentType=application/json埠褪。
目的地
綁定中間件上的通道的目標(biāo)目標(biāo)(例如浓利,RabbitMQ交換或Kafka主題)挤庇。如果通道綁定為消費(fèi)者,則可以將其綁定到多個(gè)目標(biāo)贷掖,并且目標(biāo)名稱可以指定為逗號(hào)分隔的字符串值嫡秕。如果未設(shè)置,則使用通道名稱苹威。此屬性的默認(rèn)值不能被覆蓋昆咽。
組
渠道的消費(fèi)群體。僅適用于入站綁定牙甫。參見(jiàn)消費(fèi)者群體掷酗。
默認(rèn)值:null(表示匿名消費(fèi)者)。
內(nèi)容類型
頻道的內(nèi)容類型窟哺。
默認(rèn)值:null(以便不執(zhí)行類型強(qiáng)制)泻轰。
粘合劑
這種綁定使用的粘合劑。有關(guān)詳細(xì)信息且轨,請(qǐng)參閱Classpath上的Multiple Binders浮声。
默認(rèn)值:null(默認(rèn)的binder將被使用,如果存在)旋奢。
消費(fèi)者物業(yè)
以下綁定屬性僅適用于輸入綁定泳挥,并且必須以spring.cloud.stream.bindings..consumer.為前綴,例如spring.cloud.stream.bindings.input.consumer.concurrency=3至朗。
默認(rèn)值可以使用前綴spring.cloud.stream.default.consumer設(shè)置屉符,例如spring.cloud.stream.default.consumer.headerMode=raw。
并發(fā)
入站消費(fèi)者的并發(fā)性。
默認(rèn)值:1筑煮。
分區(qū)
消費(fèi)者是否從分區(qū)生產(chǎn)者接收數(shù)據(jù)辛蚊。
默認(rèn)值:false。
headerMode
設(shè)置為raw時(shí)真仲,禁用輸入頭文件解析袋马。僅適用于不支持消息頭的消息中間件,并且需要頭部嵌入秸应。入站數(shù)據(jù)來(lái)自外部Spring Cloud Stream應(yīng)用程序時(shí)很有用虑凛。
默認(rèn)值:embeddedHeaders。
maxAttempts
如果處理失敗软啼,則嘗試處理消息的次數(shù)(包括第一個(gè))桑谍。設(shè)置為1以禁用重試。
默認(rèn)值:3祸挪。
backOffInitialInterval
退避初始間隔重試锣披。
默認(rèn)值:1000臊诊。
backOffMaxInterval
最大回退間隔恋博。
默認(rèn)值:10000。
backOffMultiplier
退避倍數(shù)玄组。
默認(rèn)值:2.0整以。
instanceIndex
當(dāng)設(shè)置為大于等于零的值時(shí)胧辽,允許自定義此消費(fèi)者的實(shí)例索引(如果與spring.cloud.stream.instanceIndex不同)。設(shè)置為負(fù)值時(shí)公黑,它將默認(rèn)為spring.cloud.stream.instanceIndex邑商。
默認(rèn)值:-1。
instanceCount
當(dāng)設(shè)置為大于等于零的值時(shí)凡蚜,允許自定義此消費(fèi)者的實(shí)例計(jì)數(shù)(如果與spring.cloud.stream.instanceCount不同)人断。當(dāng)設(shè)置為負(fù)值時(shí),它將默認(rèn)為spring.cloud.stream.instanceCount朝蜘。
默認(rèn)值:-1恶迈。
制作人Properties
以下綁定屬性僅可用于輸出綁定,并且必須以spring.cloud.stream.bindings..producer.為前綴芹务,例如spring.cloud.stream.bindings.input.producer.partitionKeyExpression=payload.id蝉绷。
默認(rèn)值可以使用前綴spring.cloud.stream.default.producer設(shè)置,例如spring.cloud.stream.default.producer.partitionKeyExpression=payload.id枣抱。
partitionKeyExpression
一個(gè)確定如何分配出站數(shù)據(jù)的SpEL表達(dá)式熔吗。如果設(shè)置,或者如果設(shè)置了partitionKeyExtractorClass佳晶,則該通道上的出站數(shù)據(jù)將被分區(qū)桅狠,并且partitionCount必須設(shè)置為大于1的值才能生效。這兩個(gè)選項(xiàng)是相互排斥的。請(qǐng)參閱分區(qū)支持中跌。
默認(rèn)值:null咨堤。
partitionKeyExtractorClass
一個(gè)PartitionKeyExtractorStrategy實(shí)現(xiàn)。如果設(shè)置漩符,或者如果設(shè)置了partitionKeyExpression一喘,則該通道上的出站數(shù)據(jù)將被分區(qū),并且partitionCount必須設(shè)置為大于1的值才能生效嗜暴。這兩個(gè)選項(xiàng)是相互排斥的凸克。請(qǐng)參閱分區(qū)支持。
默認(rèn)值:null闷沥。
partitionSelectorClass
一個(gè)PartitionSelectorStrategy實(shí)現(xiàn)萎战。與partitionSelectorExpression相互排斥。如果沒(méi)有設(shè)置舆逃,則分區(qū)將被選為hashCode(key) % partitionCount蚂维,其中key通過(guò)partitionKeyExpression或partitionKeyExtractorClass計(jì)算。
默認(rèn)值:null路狮。
partitionSelectorExpression
用于自定義分區(qū)選擇的SpEL表達(dá)式虫啥。與partitionSelectorClass相互排斥。如果沒(méi)有設(shè)置览祖,則分區(qū)將被選為hashCode(key) % partitionCount孝鹊,其中key通過(guò)partitionKeyExpression或partitionKeyExtractorClass計(jì)算炊琉。
默認(rèn)值:null展蒂。
partitionCount
如果啟用分區(qū)粱檀,則數(shù)據(jù)的目標(biāo)分區(qū)數(shù)薄榛。如果生產(chǎn)者被分區(qū),則必須設(shè)置為大于1的值熊泵。在Kafka团赏,解釋為提示;而是使用更大的和目標(biāo)主題的分區(qū)計(jì)數(shù)箕般。
默認(rèn)值:1。
requiredGroups
生成者必須確保消息傳遞的組合的逗號(hào)分隔列表舔清,即使它們?cè)趧?chuàng)建之后啟動(dòng)(例如丝里,通過(guò)在RabbitMQ中預(yù)先創(chuàng)建持久隊(duì)列)。
headerMode
設(shè)置為raw時(shí)体谒,禁用輸出上的標(biāo)題嵌入杯聚。僅適用于不支持消息頭的消息中間件,并且需要頭部嵌入抒痒。生成非Spring Cloud Stream應(yīng)用程序的數(shù)據(jù)時(shí)很有用幌绍。
默認(rèn)值:embeddedHeaders。
useNativeEncoding
當(dāng)設(shè)置為true時(shí),出站消息由客戶端庫(kù)直接序列化傀广,必須相應(yīng)配置(例如設(shè)置適當(dāng)?shù)腒afka生產(chǎn)者值序列化程序)颁独。當(dāng)使用此配置時(shí),出站消息編組不是基于綁定的contentType伪冰。當(dāng)使用本地編碼時(shí)誓酒,消費(fèi)者有責(zé)任使用適當(dāng)?shù)慕獯a器(例如:Kafka消費(fèi)者價(jià)值解串器)來(lái)對(duì)入站消息進(jìn)行反序列化。此外丰捷,當(dāng)使用本機(jī)編碼/解碼時(shí),headerMode屬性將被忽略寂汇,標(biāo)題不會(huì)嵌入到消息中病往。
默認(rèn)值:false。
使用動(dòng)態(tài)綁定目的地
除了通過(guò)@EnableBinding定義的通道之外骄瓣,Spring Cloud Stream允許應(yīng)用程序?qū)⑾l(fā)送到動(dòng)態(tài)綁定的目的地停巷。這是有用的,例如榕栏,當(dāng)目標(biāo)目標(biāo)需要在運(yùn)行時(shí)確定畔勤。應(yīng)用程序可以使用@EnableBinding注冊(cè)自動(dòng)注冊(cè)的BinderAwareChannelResolverbean。
屬性“spring.cloud.stream.dynamicDestinations”可用于將動(dòng)態(tài)目標(biāo)名稱限制為預(yù)先已知的集合(白名單)扒磁。如果屬性未設(shè)置庆揪,任何目的地都可以動(dòng)態(tài)綁定。
可以直接使用BinderAwareChannelResolver妨托,如以下示例所示缸榛,其中REST控制器使用路徑變量來(lái)確定目標(biāo)通道。
@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))));
}
}
在默認(rèn)端口8080上啟動(dòng)應(yīng)用程序后兰伤,發(fā)送以下數(shù)據(jù)時(shí):
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
目的地的客戶和“訂單”是在經(jīng)紀(jì)人中創(chuàng)建的(例如:在Rabbit的情況下進(jìn)行交換内颗,或者在Kafka的情況下為主題),其名稱為“客戶”和“訂單”敦腔,數(shù)據(jù)被發(fā)布到適當(dāng)?shù)哪康牡亍?/p>
BinderAwareChannelResolver是通用的Spring IntegrationDestinationResolver均澳,可以注入其他組件。例如符衔,在使用基于傳入JSON消息的target字段的SpEL表達(dá)式的路由器中找前。
@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;
}
}
內(nèi)容類型和轉(zhuǎn)換
要允許您傳播關(guān)于已生成消息的內(nèi)容類型的信息,默認(rèn)情況下判族,Spring Cloud Stream附加contentType標(biāo)頭到出站消息袱衷。對(duì)于不直接支持頭文件的中間件维哈,Spring Cloud Stream提供了自己的自動(dòng)將郵件包裹在自己的信封中的機(jī)制。對(duì)于支持頭文件的中間件,Spring Cloud Stream應(yīng)用程序可以從非Spring Cloud Stream應(yīng)用程序接收具有給定內(nèi)容類型的消息。
Spring Cloud Stream可以通過(guò)兩種方式處理基于此信息的消息:
通過(guò)其入站和出站渠道的contentType設(shè)置
通過(guò)對(duì)@StreamListener注釋的方法執(zhí)行的參數(shù)映射
Spring Cloud Stream允許您使用綁定的spring.cloud.stream.bindings..content-type屬性聲明性地配置輸入和輸出的類型轉(zhuǎn)換。請(qǐng)注意,一般類型轉(zhuǎn)換也可以通過(guò)在應(yīng)用程序中使用變壓器輕松實(shí)現(xiàn)。目前则吟,Spring Cloud Stream本機(jī)支持流中常用的以下類型轉(zhuǎn)換:
來(lái)自/從POJO的JSON
JSON/從org.springframework.tuple.Tuple
對(duì)象到/來(lái)自byte []:用于遠(yuǎn)程傳輸?shù)脑甲止?jié)序列化,應(yīng)用程序發(fā)出的字節(jié)锄蹂,或使用Java序列化轉(zhuǎn)換為字節(jié)(要求對(duì)象為Serializable)
字符串到/來(lái)自byte []
對(duì)象到純文本(調(diào)用對(duì)象的toString()方法)
其中JSON表示包含JSON的字節(jié)數(shù)組或字符串有效負(fù)載氓仲。目前穿香,對(duì)象可以從JSON字節(jié)數(shù)組或字符串轉(zhuǎn)換亭引。轉(zhuǎn)換為JSON總是產(chǎn)生一個(gè)String。
如果在出站通道上沒(méi)有設(shè)置content-type屬性皮获,則Spring Cloud Stream將使用基于Kryo序列化框架的序列化程序?qū)τ行ж?fù)載進(jìn)行序列化焙蚓。在目的地反序列化消息需要在接收者的類路徑上存在有效載荷類。
MIME類型
content-type值被解析為媒體類型洒宝,例如application/json或text/plain;charset=UTF-8购公。MIME類型對(duì)于指示如何轉(zhuǎn)換為String或byte []內(nèi)容特別有用。Spring Cloud Stream還使用MIME類型格式來(lái)表示Java類型雁歌,使用具有type參數(shù)的一般類型application/x-java-object宏浩。例如,application/x-java-object;type=java.util.Map或application/x-java-object;type=com.bar.Foo可以設(shè)置為輸入綁定的content-type屬性靠瞎。此外绘闷,Spring Cloud Stream提供自定義MIME類型,特別是application/x-spring-tuple來(lái)指定元組较坛。
MIME類型和Java類型
類型轉(zhuǎn)換Spring Cloud Stream提供的開(kāi)箱即用如下表所示:“源有效載荷”是指轉(zhuǎn)換前的有效載荷印蔗,“目標(biāo)有效載荷”是指轉(zhuǎn)換后的“有效載荷”。類型轉(zhuǎn)換可以在“生產(chǎn)者”一側(cè)(輸出)或“消費(fèi)者”一側(cè)(輸入)上進(jìn)行丑勤。
來(lái)源有效載荷目標(biāo)有效載荷content-type標(biāo)題(來(lái)源訊息)content-type標(biāo)題(轉(zhuǎn)換后)注釋
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
將應(yīng)用在content-type頭中指定的任何Charset
String
byte[]
any
application/octet-stream
將應(yīng)用在content-type頭中指定的任何Charset
注意
轉(zhuǎn)換適用于需要類型轉(zhuǎn)換的有效內(nèi)容华嘹。例如,如果應(yīng)用程序生成帶有outputType = application / json的XML字符串法竞,則該有效載荷將不會(huì)從XML轉(zhuǎn)換為JSON耙厚。這是因?yàn)榘l(fā)送到出站通道的有效載荷已經(jīng)是一個(gè)String,所以在運(yùn)行時(shí)不會(huì)應(yīng)用轉(zhuǎn)換岔霸。同樣重要的是要注意薛躬,當(dāng)使用默認(rèn)的序列化機(jī)制時(shí),必須在發(fā)送和接收應(yīng)用程序之間共享有效負(fù)載類呆细,并且與二進(jìn)制內(nèi)容兼容型宝。當(dāng)應(yīng)用程序代碼在兩個(gè)應(yīng)用程序中獨(dú)立更改時(shí),這可能會(huì)產(chǎn)生問(wèn)題絮爷,因?yàn)槎M(jìn)制格式和代碼可能會(huì)變得不兼容趴酣。
小費(fèi)
雖然入站和出站渠道都支持轉(zhuǎn)換,但特別推薦將其用于轉(zhuǎn)發(fā)出站郵件坑夯。對(duì)于入站郵件的轉(zhuǎn)換柜蜈,特別是當(dāng)目標(biāo)是POJO時(shí)仗谆,@StreamListener支持將自動(dòng)執(zhí)行轉(zhuǎn)換指巡。
自定義郵件轉(zhuǎn)換
除了支持開(kāi)箱即用的轉(zhuǎn)換,Spring Cloud Stream還支持注冊(cè)您自己的郵件轉(zhuǎn)換實(shí)現(xiàn)隶垮。這允許您以各種自定義格式(包括二進(jìn)制)發(fā)送和接收數(shù)據(jù)厌处,并將其與特定的contentTypes關(guān)聯(lián)池颈。Spring Cloud Stream將所有類型為org.springframework.messaging.converter.MessageConverter的bean注冊(cè)為自定義消息轉(zhuǎn)換器以及開(kāi)箱即用消息轉(zhuǎn)換器铺韧。
如果您的消息轉(zhuǎn)換器需要使用特定的content-type和目標(biāo)類(用于輸入和輸出),則消息轉(zhuǎn)換器需要擴(kuò)展org.springframework.messaging.converter.AbstractMessageConverter蛾默。對(duì)于使用@StreamListener的轉(zhuǎn)換捷绒,實(shí)現(xiàn)org.springframework.messaging.converter.MessageConverter的消息轉(zhuǎn)換器就足夠了瑰排。
以下是在Spring Cloud Stream應(yīng)用程序中創(chuàng)建消息轉(zhuǎn)換器bean(內(nèi)容類型為application/bar)的示例:
@EnableBinding(Sink.class)
@SpringBootApplication
public static class SinkApplication {
...
@Bean
public MessageConverter customMessageConverter() {
return new MyCustomMessageConverter();
}
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));
}
}
Spring Cloud Stream還為基于Avro的轉(zhuǎn)換器和模式演進(jìn)提供支持。詳情請(qǐng)參閱具體章節(jié)暖侨。
@StreamListener和訊息轉(zhuǎn)換
@StreamListener注釋提供了一種方便的方式來(lái)轉(zhuǎn)換傳入的消息椭住,而不需要指定輸入通道的內(nèi)容類型。在使用@StreamListener注釋的方法的調(diào)度過(guò)程中字逗,如果參數(shù)需要轉(zhuǎn)換京郑,將自動(dòng)應(yīng)用轉(zhuǎn)換。
例如葫掉,讓我們考慮一個(gè)帶有{"greeting":"Hello, world"}的String內(nèi)容的消息些举,并且在輸入通道上收到application/json的application/json標(biāo)題。讓我們考慮接收它的以下應(yīng)用程序:
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
}
}
該方法的參數(shù)將自動(dòng)填充包含JSON字符串的未編組形式的POJO俭厚。
Schema進(jìn)化支持
Spring Cloud Stream通過(guò)其spring-cloud-stream-schema模塊為基于模式的消息轉(zhuǎn)換器提供支持户魏。目前,基于模式的消息轉(zhuǎn)換器開(kāi)箱即用的唯一序列化格式是Apache Avro挪挤,在將來(lái)的版本中可以添加更多的格式叼丑。
Apache Avro訊息轉(zhuǎn)換器
spring-cloud-stream-schema模塊包含可用于Apache Avro序列化的兩種類型的消息轉(zhuǎn)換器:
使用序列化/反序列化對(duì)象的類信息的轉(zhuǎn)換器,或者啟動(dòng)時(shí)已知位置的模式;
轉(zhuǎn)換器使用模式注冊(cè)表 - 他們?cè)谶\(yùn)行時(shí)定位模式扛门,以及隨著域?qū)ο蟮陌l(fā)展動(dòng)態(tài)注冊(cè)新模式鸠信。
具有模式支持的轉(zhuǎn)換器
AvroSchemaMessageConverter支持使用預(yù)定義模式或使用類中可用的模式信息(反射或包含在SpecificRecord)中的序列化和反序列化消息。如果轉(zhuǎn)換的目標(biāo)類型是GenericRecord论寨,則必須設(shè)置模式星立。
對(duì)于使用它,您可以簡(jiǎn)單地將其添加到應(yīng)用程序上下文中政基,可選地指定一個(gè)或多個(gè)MimeTypes將其關(guān)聯(lián)贞铣。默認(rèn)MimeType為application/avro。
以下是在注冊(cè)Apache AvroMessageConverter的宿應(yīng)用程序中進(jìn)行配置的示例沮明,而不需要預(yù)定義的模式:
@EnableBinding(Sink.class)
@SpringBootApplication
public static class SinkApplication {
...
@Bean
public MessageConverter userMessageConverter() {
return new AvroSchemaMessageConverter(MimeType.valueOf("avro/bytes"));
}
}
相反,這里是一個(gè)應(yīng)用程序窍奋,注冊(cè)一個(gè)具有預(yù)定義模式的轉(zhuǎn)換器荐健,可以在類路徑中找到:
@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;
}
}
為了了解模式注冊(cè)表客戶端轉(zhuǎn)換器酱畅,我們將首先描述模式注冊(cè)表支持。
Schema注冊(cè)表支持
大多數(shù)序列化模型江场,特別是旨在跨不同平臺(tái)和語(yǔ)言進(jìn)行可移植性的序列化模型纺酸,依賴于描述數(shù)據(jù)如何在二進(jìn)制有效載荷中被序列化的模式。為了序列化數(shù)據(jù)然后解釋它址否,發(fā)送方和接收方都必須訪問(wèn)描述二進(jìn)制格式的模式餐蔬。在某些情況下,可以從序列化的有效載荷類型或從反序列化時(shí)的目標(biāo)類型中推斷出模式佑附,但是在許多情況下樊诺,應(yīng)用程序可以從訪問(wèn)描述二進(jìn)制數(shù)據(jù)格式的顯式模式中受益。模式注冊(cè)表允許您以文本格式(通常為JSON)存儲(chǔ)模式信息音同,并使該信息可訪問(wèn)需要它的各種應(yīng)用程序以二進(jìn)制格式接收和發(fā)送數(shù)據(jù)词爬。一個(gè)模式可以作為一個(gè)元組引用,它由
作為模式的邏輯名稱的主題;
模式版本;
描述數(shù)據(jù)的二進(jìn)制格式的模式格式对人。
Schema注冊(cè)服務(wù)器
Spring Cloud Stream提供了模式注冊(cè)表服務(wù)器實(shí)現(xiàn)歧寺。為了使用它葫督,您可以簡(jiǎn)單地將spring-cloud-stream-schema-server工件添加到項(xiàng)目中,并使用@EnableSchemaRegistryServer注釋恋沃,將模式注冊(cè)表服務(wù)器REST控制器添加到應(yīng)用程序中。此注釋旨在與Spring Boot Web應(yīng)用程序一起使用必指,服務(wù)器的監(jiān)聽(tīng)端口由server.port設(shè)置控制芽唇。spring.cloud.stream.schema.server.path設(shè)置可用于控制模式服務(wù)器的根路徑(特別是嵌入其他應(yīng)用程序時(shí))。spring.cloud.stream.schema.server.allowSchemaDeletion布爾設(shè)置可以刪除模式取劫。默認(rèn)情況下匆笤,這是禁用的。
模式注冊(cè)表服務(wù)器使用關(guān)系數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)模式谱邪。默認(rèn)情況下炮捧,它使用一個(gè)嵌入式數(shù)據(jù)庫(kù)。您可以使用Spring Boot SQL數(shù)據(jù)庫(kù)和JDBC配置選項(xiàng)自定義模式存儲(chǔ)惦银。
啟用模式注冊(cè)表的Spring Boot應(yīng)用程序如下所示:
@SpringBootApplication
@EnableSchemaRegistryServer
public class SchemaRegistryServerApplication {
public static void main(String[] args) {
SpringApplication.run(SchemaRegistryServerApplication.class, args);
}
}
Schema注冊(cè)服務(wù)器API
Schema注冊(cè)服務(wù)器API由以下操作組成:
POST /
注冊(cè)一個(gè)新的架構(gòu)
接受具有以下字段的JSON有效載荷:
subject模式主題;
format模式格式;
definition模式定義咆课。
響應(yīng)是JSON格式的模式對(duì)象,包含以下字段:
id模式標(biāo)識(shí);
subject模式主題;
format模式格式;
version模式版本;
definition模式定義扯俱。
GET /{subject}/{format}/{version}
根據(jù)其主題书蚪,格式和版本檢索現(xiàn)有模式。
響應(yīng)是JSON格式的模式對(duì)象迅栅,包含以下字段:
id模式標(biāo)識(shí);
subject模式主題;
format模式格式;
version模式版本;
definition模式定義殊校。
GET /{subject}/{format}
根據(jù)其主題和格式檢索現(xiàn)有模式的列表。
響應(yīng)是JSON格式的每個(gè)模式對(duì)象的模式列表读存,包含以下字段:
id模式標(biāo)識(shí);
subject模式主題;
format模式格式;
version模式版本;
definition模式定義为流。
GET /schemas/{id}
通過(guò)其id來(lái)檢索現(xiàn)有的模式呕屎。
響應(yīng)是JSON格式的模式對(duì)象,包含以下字段:
id模式標(biāo)識(shí);
subject模式主題;
format模式格式;
version模式版本;
definition模式定義敬察。
DELETE /{subject}/{format}/{version}
按其主題秀睛,格式和版本刪除現(xiàn)有模式。
DELETE /schemas/{id}
按其ID刪除現(xiàn)有模式莲祸。
DELETE /{subject}
按其主題刪除現(xiàn)有模式蹂安。
注意
本說(shuō)明僅適用于Spring Cloud Stream 1.1.0.RELEASE的用戶。Spring Cloud Stream 1.1.0.RELEASE使用表名schema存儲(chǔ)Schema對(duì)象锐帜,這是一些數(shù)據(jù)庫(kù)實(shí)現(xiàn)中的關(guān)鍵字田盈。為了避免將來(lái)發(fā)生任何沖突,從1.1.1.RELEASE開(kāi)始抹估,我們選擇了存儲(chǔ)表的名稱SCHEMA_REPOSITORY缠黍。建議任何正在升級(jí)的Spring Cloud Stream 1.1.0.RELEASE用戶在升級(jí)之前將其現(xiàn)有模式遷移到新表。
Schema注冊(cè)表客戶端
與模式注冊(cè)表服務(wù)器交互的客戶端抽象是SchemaRegistryClient接口药蜻,具有以下結(jié)構(gòu):
public interface SchemaRegistryClient {
SchemaRegistrationResponse register(String subject, String format, String schema);
String fetch(SchemaReference schemaReference);
String fetch(Integer id);
}
Spring Cloud Stream提供了開(kāi)箱即用的實(shí)現(xiàn)瓷式,用于與其自己的模式服務(wù)器交互,以及與Confluent Schema注冊(cè)表進(jìn)行交互语泽。
可以使用@EnableSchemaRegistryClient配置Spring Cloud Stream模式注冊(cè)表的客戶端贸典,如下所示:
@EnableBinding(Sink.class)
@SpringBootApplication
@EnableSchemaRegistryClient
public static class AvroSinkApplication {
...
}
注意
優(yōu)化了默認(rèn)轉(zhuǎn)換器,以緩存來(lái)自遠(yuǎn)程服務(wù)器的模式踱卵,而且還會(huì)非常昂貴的parse()和toString()方法廊驼。因此,它使用不緩存響應(yīng)的DefaultSchemaRegistryClient惋砂。如果您打算直接在代碼上使用客戶端妒挎,您可以請(qǐng)求一個(gè)也緩存要?jiǎng)?chuàng)建的響應(yīng)的bean。為此,只需將屬性spring.cloud.stream.schemaRegistryClient.cached=true添加到應(yīng)用程序?qū)傩灾屑纯伞?/p>
Schema注冊(cè)表客戶端屬性
Schema注冊(cè)表客戶端支持以下屬性:
spring.cloud.stream.schemaRegistryClient.endpoint
模式服務(wù)器的位置。在設(shè)置時(shí)使用完整的URL稀拐,包括協(xié)議(http或https)怎囚,端口和上下文路徑窃植。
默認(rèn)
spring.cloud.stream.schemaRegistryClient.cached
客戶端是否應(yīng)緩存模式服務(wù)器響應(yīng)。通常設(shè)置為false,因?yàn)榫彺姘l(fā)生在消息轉(zhuǎn)換器中。使用模式注冊(cè)表客戶端的客戶端應(yīng)將其設(shè)置為true镶苞。
默認(rèn)
true
Avro Schema注冊(cè)表客戶端消息轉(zhuǎn)換器
對(duì)于在應(yīng)用程序上下文中注冊(cè)了SchemaRegistryClientbean的Spring Boot應(yīng)用程序,Spring Cloud Stream將自動(dòng)配置使用模式注冊(cè)表客戶端進(jìn)行模式管理的Apache Avro消息轉(zhuǎn)換器鞠评。這簡(jiǎn)化了模式演進(jìn)茂蚓,因?yàn)榻邮障⒌膽?yīng)用程序可以輕松訪問(wèn)可與自己的讀取器模式進(jìn)行協(xié)調(diào)的寫入器模式。
對(duì)于出站郵件,如果頻道的內(nèi)容類型設(shè)置為application/*+avro煌贴,MessageConverter將被激活御板,例如:
spring.cloud.stream.bindings.output.contentType=application/*+avro
在出站轉(zhuǎn)換期間锥忿,消息轉(zhuǎn)換器將嘗試基于其類型推斷出站消息的模式牛郑,并使用SchemaRegistryClient根據(jù)有效載荷類型將其注冊(cè)到主題。如果已經(jīng)找到相同的模式敬鬓,那么將會(huì)檢索對(duì)它的引用淹朋。如果沒(méi)有,則將注冊(cè)模式并提供新的版本號(hào)钉答。該消息將使用application/[prefix].[subject].v[version]+avro的方案contentType頭發(fā)送础芍,其中prefix是可配置的,并且從有效載荷類型推導(dǎo)出subject数尿。
例如仑性,類型為User的消息可以作為內(nèi)容類型為application/vnd.user.v2+avro的二進(jìn)制有效載荷發(fā)送,其中user是主題右蹦,2是版本號(hào)诊杆。
當(dāng)接收到消息時(shí),轉(zhuǎn)換器將從傳入消息的頭部推斷出模式引用何陆,并嘗試檢索它晨汹。該模式將在反序列化過(guò)程中用作寫入器模式。
Avro Schema注冊(cè)表消息轉(zhuǎn)換器屬性
如果您已通過(guò)設(shè)置spring.cloud.stream.bindings.output.contentType=application/*+avro啟用基于Avro的模式注冊(cè)表客戶端贷盲,則可以使用以下屬性自定義注冊(cè)的行為淘这。
spring.cloud.stream.schema.avro.dynamicSchemaGenerationEnabled
如果您希望轉(zhuǎn)換器使用反射從POJO推斷Schema,則啟用巩剖。
默認(rèn)
false
spring.cloud.stream.schema.avro.readerSchema
Avro通過(guò)查看編寫器模式(源有效載荷)和讀取器模式(應(yīng)用程序有效負(fù)載)來(lái)比較模式版本铝穷,查看Avro文檔以獲取更多信息。如果設(shè)置佳魔,這將覆蓋模式服務(wù)器上的任何查找曙聂,并將本地模式用作讀取器模式。
默認(rèn)
null
spring.cloud.stream.schema.avro.schemaLocations
使用Schema服務(wù)器注冊(cè)此屬性中列出的任何.avsc文件吃引。
默認(rèn)
empty
spring.cloud.stream.schema.avro.prefix
要在Content-Type頭上使用的前綴筹陵。
默認(rèn)
vnd
Schema注冊(cè)和解決
為了更好地了解Spring Cloud Stream注冊(cè)和解決新模式以及其使用Avro模式比較功能,我們將提供兩個(gè)單獨(dú)的子部分:一個(gè)用于注冊(cè)镊尺,一個(gè)用于解析模式朦佩。
Schema注冊(cè)流程(序列化)
注冊(cè)過(guò)程的第一部分是從通過(guò)信道發(fā)送的有效載荷中提取模式。Avro類型庐氮,如SpecificRecord或GenericRecord已經(jīng)包含一個(gè)模式语稠,可以從實(shí)例中立即檢索。在POJO的情況下,如果屬性spring.cloud.stream.schema.avro.dynamicSchemaGenerationEnabled設(shè)置為true(默認(rèn))仙畦,則會(huì)推斷出一個(gè)模式越走。
圖10. Schema Writer Resolution Process
一旦獲得了架構(gòu),轉(zhuǎn)換器就會(huì)從遠(yuǎn)程服務(wù)器加載其元數(shù)據(jù)(版本)姑隅。首先蛛蒙,它查詢本地緩存,如果沒(méi)有找到它寸士,則將數(shù)據(jù)提交到將使用版本控制信息回復(fù)的服務(wù)器檐什。轉(zhuǎn)換器將始終緩存結(jié)果,以避免為每個(gè)需要序列化的新消息查詢Schema服務(wù)器的開(kāi)銷弱卡。
圖11. Schema注冊(cè)流程
使用模式版本信息乃正,轉(zhuǎn)換器設(shè)置消息的contentType頭,以攜帶版本信息婶博,如application/vnd.user.v1+avro
Schema解析過(guò)程(反序列化)
當(dāng)讀取包含版本信息的消息(即瓮具,具有上述方案的contentType標(biāo)頭)時(shí),轉(zhuǎn)換器將查詢Schema服務(wù)器以獲取消息的寫入器架構(gòu)凡人。一旦找到傳入消息的正確架構(gòu)名党,它就會(huì)檢索讀取器架構(gòu),并使用Avro的架構(gòu)解析支持將其讀入讀取器定義(設(shè)置默認(rèn)值和缺少的屬性)划栓。
圖12. Schema閱讀決議程序
注意
了解編寫器架構(gòu)(寫入消息的應(yīng)用程序)和讀取器架構(gòu)(接收應(yīng)用程序)之間的區(qū)別很重要兑巾。請(qǐng)花點(diǎn)時(shí)間閱讀Avro術(shù)語(yǔ)并了解此過(guò)程。Spring Cloud Stream將始終提取writer模式以確定如何讀取消息忠荞。如果您想要Avro的架構(gòu)演進(jìn)支持工作蒋歌,您需要確保為您的應(yīng)用程序正確設(shè)置了readerSchema。
應(yīng)用間通信
連接多個(gè)應(yīng)用程序?qū)嵗?/p>
雖然Spring Cloud Stream使個(gè)人Spring Boot應(yīng)用程序輕松連接到消息傳遞系統(tǒng)委煤,但是Spring Cloud Stream的典型場(chǎng)景是創(chuàng)建多應(yīng)用程序管道堂油,其中微服務(wù)應(yīng)用程序?qū)?shù)據(jù)發(fā)送給彼此。您可以通過(guò)將相鄰應(yīng)用程序的輸入和輸出目標(biāo)相關(guān)聯(lián)來(lái)實(shí)現(xiàn)此場(chǎng)景碧绞。
假設(shè)設(shè)計(jì)要求時(shí)間源應(yīng)用程序?qū)?shù)據(jù)發(fā)送到日志接收應(yīng)用程序府框,則可以在兩個(gè)應(yīng)用程序中使用名為ticktock的公共目標(biāo)進(jìn)行綁定。
時(shí)間來(lái)源(具有頻道名稱output)將設(shè)置以下屬性:
spring.cloud.stream.bindings.output.destination=ticktock
日志接收器(通道名稱為input)將設(shè)置以下屬性:
spring.cloud.stream.bindings.input.destination=ticktock
實(shí)例索引和實(shí)例計(jì)數(shù)
當(dāng)擴(kuò)展Spring Cloud Stream應(yīng)用程序時(shí)讥邻,每個(gè)實(shí)例都可以接收有關(guān)同一個(gè)應(yīng)用程序的其他實(shí)例數(shù)量以及自己的實(shí)例索引的信息迫靖。Spring Cloud Stream通過(guò)spring.cloud.stream.instanceCount和spring.cloud.stream.instanceIndex屬性執(zhí)行此操作。例如兴使,如果HDFS宿應(yīng)用程序有三個(gè)實(shí)例系宜,則所有三個(gè)實(shí)例將spring.cloud.stream.instanceCount設(shè)置為3,并且各個(gè)應(yīng)用程序?qū)pring.cloud.stream.instanceIndex設(shè)置為0发魄,1和2盹牧。
當(dāng)通過(guò)Spring Cloud數(shù)據(jù)流部署Spring Cloud Stream應(yīng)用程序時(shí)俩垃,這些屬性將自動(dòng)配置;當(dāng)Spring Cloud Stream應(yīng)用程序獨(dú)立啟動(dòng)時(shí),必須正確設(shè)置這些屬性汰寓。默認(rèn)情況下口柳,spring.cloud.stream.instanceCount為1,spring.cloud.stream.instanceIndex為0有滑。
在放大的情況下跃闹,這兩個(gè)屬性的正確配置對(duì)于解決分區(qū)行為(見(jiàn)下文)一般很重要,并且某些綁定器(例如俺孙,Kafka binder)總是需要這兩個(gè)屬性辣卒,以確保該數(shù)據(jù)在多個(gè)消費(fèi)者實(shí)例之間正確分割掷贾。
分區(qū)
配置輸出綁定進(jìn)行分區(qū)
輸出綁定被配置為通過(guò)設(shè)置其唯一的一個(gè)partitionKeyExpression或partitionKeyExtractorClass屬性以及其partitionCount屬性來(lái)發(fā)送分區(qū)數(shù)據(jù)睛榄。例如,以下是一個(gè)有效和典型的配置:
spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload.id
spring.cloud.stream.bindings.output.producer.partitionCount=5
基于上述示例配置想帅,使用以下邏輯將數(shù)據(jù)發(fā)送到目標(biāo)分區(qū)场靴。
基于partitionKeyExpression伟葫,為發(fā)送到分區(qū)輸出通道的每個(gè)消息計(jì)算分區(qū)密鑰的值强窖。partitionKeyExpression是一個(gè)Spel表達(dá)式朽寞,它根據(jù)出站消息進(jìn)行評(píng)估寝并,以提取分區(qū)鍵柴灯。
如果SpEL表達(dá)式不足以滿足您的需要软吐,您可以通過(guò)將屬性partitionKeyExtractorClass設(shè)置為實(shí)現(xiàn)org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy接口的類來(lái)計(jì)算分區(qū)鍵值膘盖。雖然Spel表達(dá)式通常足夠鞋拟,但更復(fù)雜的情況可能會(huì)使用自定義實(shí)現(xiàn)策略衩椒。在這種情況下蚌父,屬性“partitionKeyExtractorClass”可以設(shè)置如下:
spring.cloud.stream.bindings.output.producer.partitionKeyExtractorClass=com.example.MyKeyExtractor
spring.cloud.stream.bindings.output.producer.partitionCount=5
一旦計(jì)算了消息密鑰,分區(qū)選擇過(guò)程將確定目標(biāo)分區(qū)為0和partitionCount - 1之間的值毛萌。在大多數(shù)情況下苟弛,默認(rèn)計(jì)算基于公式key.hashCode() % partitionCount。這可以通過(guò)設(shè)置要針對(duì)'key'(通過(guò)partitionSelectorExpression屬性)進(jìn)行評(píng)估的Spel表達(dá)式或通過(guò)設(shè)置org.springframework.cloud.stream.binder.PartitionSelectorStrategy實(shí)現(xiàn)(通過(guò)partitionSelectorClass屬性))進(jìn)行自定義阁将。
“partitionSelectorExpression”和“partitionSelectorClass”的綁定級(jí)屬性可以類似于上述示例中指定的“partitionKeyExpression”和“partitionKeyExtractorClass”屬性的類型膏秫。可以為更高級(jí)的場(chǎng)景配置其他屬性做盅,如以下部分所述缤削。
Spring - 管理的自定義PartitionKeyExtractorClass實(shí)現(xiàn)
在上面的示例中,MyKeyExtractor之類的自定義策略由Spring Cloud Stream直接實(shí)例化吹榴。在某些情況下亭敢,必須將這樣的自定義策略實(shí)現(xiàn)創(chuàng)建為Spring bean,以便能夠由Spring管理腊尚,以便它可以執(zhí)行依賴注入吨拗,屬性綁定等。可以通過(guò)將其配置為應(yīng)用程序上下文中的@Bean劝篷,并使用完全限定類名作為bean的名稱哨鸭,如以下示例所示。
@Bean(name="com.example.MyKeyExtractor")
public MyKeyExtractor extractor() {
return new MyKeyExtractor();
}
作為Spring bean娇妓,自定義策略從Spring bean的完整生命周期中受益像鸡。例如,如果實(shí)現(xiàn)需要直接訪問(wèn)應(yīng)用程序上下文哈恰,則可以實(shí)現(xiàn)“ApplicationContextAware”只估。
配置輸入綁定進(jìn)行分區(qū)
輸入綁定(通道名稱為input)被配置為通過(guò)在應(yīng)用程序本身設(shè)置其partitioned屬性以及instanceIndex和instanceCount屬性來(lái)接收分區(qū)數(shù)據(jù),如以下示例:
spring.cloud.stream.bindings.input.consumer.partitioned=true
spring.cloud.stream.instanceIndex=3
spring.cloud.stream.instanceCount=5
instanceCount值表示數(shù)據(jù)需要分區(qū)的應(yīng)用程序?qū)嵗目倲?shù)着绷,instanceIndex必須是0和instanceCount - 1之間的多個(gè)實(shí)例的唯一值蛔钙。實(shí)例索引幫助每個(gè)應(yīng)用程序?qū)嵗R(shí)別從其接收數(shù)據(jù)的唯一分區(qū)(或者在Kafka的分區(qū)集合的情況下)。重要的是正確設(shè)置兩個(gè)值荠医,以確保所有數(shù)據(jù)都被使用吁脱,并且應(yīng)用程序?qū)嵗邮盏交コ鈹?shù)據(jù)集。
雖然使用多個(gè)實(shí)例進(jìn)行分區(qū)數(shù)據(jù)處理的場(chǎng)景可能會(huì)在獨(dú)立情況下進(jìn)行復(fù)雜化彬向,但是通過(guò)將輸入和輸出值正確填充并依賴于運(yùn)行時(shí)基礎(chǔ)架構(gòu)兼贡,Spring Cloud數(shù)據(jù)流可以顯著簡(jiǎn)化流程。提供有關(guān)實(shí)例索引和實(shí)例計(jì)數(shù)的信息娃胆。
測(cè)試
Spring Cloud Stream支持測(cè)試您的微服務(wù)應(yīng)用程序遍希,而無(wú)需連接到消息系統(tǒng)。您可以使用spring-cloud-stream-test-support庫(kù)提供的TestSupportBinder里烦,可以將其作為測(cè)試依賴項(xiàng)添加到應(yīng)用程序中:
org.springframework.cloud
spring-cloud-stream-test-support
test
注意
TestSupportBinder使用Spring Boot自動(dòng)配置機(jī)制取代類路徑中找到的其他綁定凿蒜。因此,添加binder作為依賴關(guān)系時(shí)招驴,請(qǐng)確保正在使用test范圍翰灾。
TestSupportBinder允許用戶與綁定的頻道進(jìn)行交互黎做,并檢查應(yīng)用程序發(fā)送和接收的消息
對(duì)于出站消息通道巩步,TestSupportBinder注冊(cè)單個(gè)訂戶哩俭,并將應(yīng)用程序發(fā)送的消息保留在MessageCollector中。它們可以在測(cè)試過(guò)程中被檢索触趴,并對(duì)它們做出斷言氮发。
用戶還可以將消息發(fā)送到入站消息通道,以便消費(fèi)者應(yīng)用程序可以使用消息冗懦。以下示例顯示了如何在處理器上測(cè)試輸入和輸出通道爽冕。
@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 message = new GenericMessage<>("hello");
processor.input().send(message);
Message received = (Message) 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";
}
}
}
在上面的示例中,我們正在創(chuàng)建一個(gè)具有輸入和輸出通道的應(yīng)用程序披蕉,通過(guò)Processor接口綁定颈畸。綁定的接口被注入測(cè)試乌奇,所以我們可以訪問(wèn)這兩個(gè)通道。我們正在輸入頻道發(fā)送消息眯娱,我們使用Spring Cloud Stream測(cè)試支持提供的MessageCollector來(lái)捕獲消息已經(jīng)被發(fā)??送到輸出通道礁苗。收到消息后,我們可以驗(yàn)證組件是否正常工作徙缴。
健康指標(biāo)
Spring Cloud Stream為粘合劑提供健康指標(biāo)试伙。它以binders的名義注冊(cè),可以通過(guò)設(shè)置management.health.binders.enabled屬性啟用或禁用于样。
指標(biāo)發(fā)射器
Spring Cloud Stream提供了一個(gè)名為spring-cloud-stream-metrics的模塊疏叨,可以用來(lái)從Spring Boot度量端點(diǎn)到命名通道發(fā)出任何可用度量。該模塊允許運(yùn)營(yíng)商從流應(yīng)用收集指標(biāo)穿剖,而不依賴輪詢其端點(diǎn)蚤蔓。
當(dāng)您設(shè)置度量綁定的目標(biāo)名稱(例如spring.cloud.stream.bindings.applicationMetrics.destination=)時(shí),該模塊將被激活携御〔粒可以以與任何其他生成器綁定相似的方式配置applicationMetrics。applicationMetrics的contentType默認(rèn)設(shè)置為application/json啄刹。
以下屬性可用于自定義度量標(biāo)準(zhǔn)的排放:
spring.cloud.stream.metrics.key
要發(fā)射的度量的名稱。應(yīng)該是每個(gè)應(yīng)用程序的唯一值凄贩。
默認(rèn)
${spring.application.name:${vcap.application.name:${spring.config.name:application}}}
spring.cloud.stream.metrics.prefix
前綴字符串誓军,以前綴到度量鍵。
默認(rèn)值:``
spring.cloud.stream.metrics.properties
就像includes選項(xiàng)一樣疲扎,它允許將白名單應(yīng)用程序?qū)傩蕴砑拥蕉攘坑行ж?fù)載
默認(rèn)值:null昵时。
有關(guān)度量導(dǎo)出過(guò)程的詳細(xì)概述,請(qǐng)參見(jiàn)Spring Boot參考文檔椒丧。Spring Cloud Stream提供了一個(gè)名為application的指標(biāo)導(dǎo)出器壹甥,可以通過(guò)常規(guī)Spring Boot指標(biāo)配置屬性進(jìn)行配置。
可以通過(guò)使用出口商的全局Spring Boot配置設(shè)置或使用特定于導(dǎo)出器的屬性來(lái)配置導(dǎo)出器壶熏。要使用全局配置設(shè)置句柠,屬性應(yīng)以spring.metric.export為前綴(例如spring.metric.export.includes=integration**)。這些配置選項(xiàng)將適用于所有出口商(除非它們的配置不同)棒假∷葜埃或者,如果要使用與其他出口商不同的配置設(shè)置(例如帽哑,限制發(fā)布的度量數(shù)量)谜酒,則可以使用前綴spring.metrics.export.triggers.application配置Spring Cloud Stream提供的度量導(dǎo)出器(例如spring.metrics.export.triggers.application.includes=integration**)。
注意
由于Spring Boot的輕松約束妻枕,所包含的屬性的值可能與原始值稍有不同僻族。
作為經(jīng)驗(yàn)法則誊涯,度量導(dǎo)出器將嘗試使用點(diǎn)符號(hào)(例如JAVA_HOME成為java.home)以一致的格式標(biāo)準(zhǔn)化所有屬性。
規(guī)范化的目標(biāo)是使下游用戶能夠始終如一地接收屬性名稱魂爪,無(wú)論它們?nèi)绾卧O(shè)置在受監(jiān)視的應(yīng)用程序上(--spring.application.name或SPRING_APPLICATION_NAME始終會(huì)生成spring.application.name)茄茁。
以下是通過(guò)以下命令以JSON格式發(fā)布到頻道的數(shù)據(jù)的示例:
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**
得到的JSON是:
{
"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"
}
}
樣品
對(duì)于Spring Cloud Stream示例,請(qǐng)參閱GitHub上的spring-cloud-stream樣本存儲(chǔ)庫(kù)碉输。
入門
要開(kāi)始創(chuàng)建Spring Cloud Stream應(yīng)用程序籽前,請(qǐng)?jiān)L問(wèn)Spring Initializr并創(chuàng)建一個(gè)名為“GreetingSource”的新Maven項(xiàng)目。在下拉菜單中選擇Spring Boot {supported-spring-boot-version}敷钾。在“搜索依賴關(guān)系”文本框中鍵入Stream Rabbit或Stream Kafka枝哄,具體取決于您要使用的binder。
接下來(lái)阻荒,在與GreetingSourceApplication類相同的包中創(chuàng)建一個(gè)新類GreetingSource挠锥。給它以下代碼:
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();
}
}
@EnableBinding注釋是觸發(fā)Spring Integration基礎(chǔ)架構(gòu)組件的創(chuàng)建。具體來(lái)說(shuō)侨赡,它將創(chuàng)建一個(gè)Kafka連接工廠蓖租,一個(gè)Kafka出站通道適配器,并在Source界面中定義消息通道:
public interface Source {
String OUTPUT = "output";
@Output(Source.OUTPUT)
MessageChannel output();
}
自動(dòng)配置還創(chuàng)建一個(gè)默認(rèn)輪詢器羊壹,以便每秒調(diào)用greet()方法一次蓖宦。標(biāo)準(zhǔn)的Spring Integration@InboundChannelAdapter注釋使用返回值作為消息的有效內(nèi)容向源的輸出通道發(fā)送消息。
要測(cè)試驅(qū)動(dòng)此設(shè)置油猫,請(qǐng)運(yùn)行Kafka消息代理稠茂。一個(gè)簡(jiǎn)單的方法是使用Docker鏡像:
# 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
構(gòu)建應(yīng)用程序:
./mvnw clean package
消費(fèi)者應(yīng)用程序以類似的方式進(jìn)行編碼。返回Initializr并創(chuàng)建另一個(gè)名為L(zhǎng)oggingSink的項(xiàng)目情妖。然后在與類LoggingSinkApplication相同的包中創(chuàng)建一個(gè)新類LoggingSink睬关,并使用以下代碼:
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);
}
}
構(gòu)建應(yīng)用程序:
./mvnw clean package
要將GreetingSource應(yīng)用程序連接到LoggingSink應(yīng)用程序,每個(gè)應(yīng)用程序必須共享相同的目標(biāo)名稱毡证。啟動(dòng)這兩個(gè)應(yīng)用程序如下所示电爹,您將看到消費(fèi)者應(yīng)用程序打印“hello world”和時(shí)間戳到控制臺(tái):
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
(不同的服務(wù)器端口可以防止兩個(gè)應(yīng)用程序中用于維護(hù)Spring Boot執(zhí)行器端點(diǎn)的HTTP端口的沖突。)
LoggingSink應(yīng)用程序的輸出將如下所示:
[? ? ? ? ? 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
Binder實(shí)施
Apache Kafka Binder
用法
對(duì)于使用Apache Kafka綁定器料睛,您只需要使用以下Maven坐標(biāo)將其添加到您的Spring Cloud Stream應(yīng)用程序:
org.springframework.cloud
spring-cloud-stream-binder-kafka
或者丐箩,您也可以使用Spring Cloud Stream Kafka Starter。
org.springframework.cloud
spring-cloud-starter-stream-kafka
Apache Kafka Binder概述
以下可以看到Apache Kafka綁定器操作的簡(jiǎn)化圖秦效。
圖13. Kafka Binder
Apache Kafka Binder實(shí)現(xiàn)將每個(gè)目標(biāo)映射到Apache Kafka主題雏蛮。消費(fèi)者組織直接映射到相同的Apache Kafka概念。分區(qū)也直接映射到Apache Kafka分區(qū)阱州。
配置選項(xiàng)
本節(jié)包含Apache Kafka綁定器使用的配置選項(xiàng)挑秉。
有關(guān)binder的常見(jiàn)配置選項(xiàng)和屬性,請(qǐng)參閱核心文檔苔货。
Kafka Binder Properties
spring.cloud.stream.kafka.binder.brokers
Kafka活頁(yè)夾將連接的經(jīng)紀(jì)人列表犀概。
默認(rèn)值:localhost立哑。
spring.cloud.stream.kafka.binder.defaultBrokerPort
brokers允許使用或不使用端口信息指定的主機(jī)(例如,host1,host2:port2)姻灶。當(dāng)在代理列表中沒(méi)有配置端口時(shí)铛绰,這將設(shè)置默認(rèn)端口。
默認(rèn)值:9092产喉。
spring.cloud.stream.kafka.binder.zkNodes
Kafka綁定器可以連接的ZooKeeper節(jié)點(diǎn)列表捂掰。
默認(rèn)值:localhost。
spring.cloud.stream.kafka.binder.defaultZkPort
zkNodes允許使用或不使用端口信息指定的主機(jī)(例如曾沈,host1,host2:port2)这嚣。當(dāng)在節(jié)點(diǎn)列表中沒(méi)有配置端口時(shí),這將設(shè)置默認(rèn)端口塞俱。
默認(rèn)值:2181惕稻。
spring.cloud.stream.kafka.binder.configuration
客戶端屬性(生產(chǎn)者和消費(fèi)者)的密鑰/值映射傳遞給由綁定器創(chuàng)建的所有客戶端旷痕。由于這些屬性將被生產(chǎn)者和消費(fèi)者使用,所以使用應(yīng)該限于常見(jiàn)的屬性露戒,特別是安全設(shè)置腿准。
默認(rèn)值:空地圖容燕。
spring.cloud.stream.kafka.binder.headers
將由活頁(yè)夾傳送的自定義標(biāo)題列表操软。
默認(rèn)值:空详瑞。
spring.cloud.stream.kafka.binder.offsetUpdateTimeWindow
以毫秒為單位的頻率(以毫秒為單位)保存偏移量。0忽略生棍。
默認(rèn)值:10000颤霎。
spring.cloud.stream.kafka.binder.offsetUpdateCount
頻率,更新次數(shù)涂滴,哪些消耗的偏移量會(huì)持續(xù)存在。0忽略晴音。與offsetUpdateTimeWindow相互排斥柔纵。
默認(rèn)值:0。
spring.cloud.stream.kafka.binder.requiredAcks
經(jīng)紀(jì)人所需的acks數(shù)量锤躁。
默認(rèn)值:1搁料。
spring.cloud.stream.kafka.binder.minPartitionCount
只有設(shè)置autoCreateTopics或autoAddPartitions才有效。綁定器在其生成/消耗數(shù)據(jù)的主題上配置的全局最小分區(qū)數(shù)系羞。它可以由生產(chǎn)者的partitionCount設(shè)置或生產(chǎn)者的instanceCount*concurrency設(shè)置的值替代(如果更大)郭计。
默認(rèn)值:1。
spring.cloud.stream.kafka.binder.replicationFactor
如果autoCreateTopics處于活動(dòng)狀態(tài)椒振,則自動(dòng)創(chuàng)建主題的復(fù)制因子昭伸。
默認(rèn)值:1。
spring.cloud.stream.kafka.binder.autoCreateTopics
如果設(shè)置為true澎迎,綁定器將自動(dòng)創(chuàng)建新主題庐杨。如果設(shè)置為false选调,則綁定器將依賴于已配置的主題。在后一種情況下灵份,如果主題不存在仁堪,則綁定器將無(wú)法啟動(dòng)。值得注意的是填渠,此設(shè)置與代理的auto.topic.create.enable設(shè)置無(wú)關(guān)弦聂,并不影響它:如果服務(wù)器設(shè)置為自動(dòng)創(chuàng)建主題,則可以將其創(chuàng)建為元數(shù)據(jù)檢索請(qǐng)求的一部分氛什,并使用默認(rèn)代理設(shè)置莺葫。
默認(rèn)值:true。
spring.cloud.stream.kafka.binder.autoAddPartitions
如果設(shè)置為true屉更,則綁定器將根據(jù)需要?jiǎng)?chuàng)建新的分區(qū)徙融。如果設(shè)置為false,則綁定器將依賴于已配置的主題的分區(qū)大小瑰谜。如果目標(biāo)主題的分區(qū)計(jì)數(shù)小于預(yù)期值欺冀,則綁定器將無(wú)法啟動(dòng)。
默認(rèn)值:false萨脑。
spring.cloud.stream.kafka.binder.socketBufferSize
Kafka消費(fèi)者使用的套接字緩沖區(qū)的大幸(以字節(jié)為單位)。
默認(rèn)值:2097152渤早。
Kafka消費(fèi)者Properties
以下屬性僅適用于Kafka消費(fèi)者职车,必須以spring.cloud.stream.kafka.bindings..consumer.為前綴。
autoRebalanceEnabled
當(dāng)true鹊杖,主題分區(qū)將在消費(fèi)者組的成員之間自動(dòng)重新平衡悴灵。當(dāng)false根據(jù)spring.cloud.stream.instanceCount和spring.cloud.stream.instanceIndex為每個(gè)消費(fèi)者分配一組固定的分區(qū)盾似。這需要在每個(gè)啟動(dòng)的實(shí)例上適當(dāng)?shù)卦O(shè)置spring.cloud.stream.instanceCount和spring.cloud.stream.instanceIndex屬性淹禾。在這種情況下,屬性spring.cloud.stream.instanceCount通常必須大于1丐一。
默認(rèn)值:true登下。
autoCommitOffset
是否在處理郵件時(shí)自動(dòng)提交偏移量茫孔。如果設(shè)置為false,則入站消息中將顯示帶有org.springframework.kafka.support.Acknowledgment類型的密鑰kafka_acknowledgment的報(bào)頭被芳。應(yīng)用程序可以使用此標(biāo)頭來(lái)確認(rèn)消息缰贝。有關(guān)詳細(xì)信息,請(qǐng)參閱示例部分畔濒。當(dāng)此屬性設(shè)置為false時(shí)剩晴,Kafka binder將ack模式設(shè)置為org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode.MANUAL。
默認(rèn)值:true篓冲。
autoCommitOnError
只有autoCommitOffset設(shè)置為true才有效李破。如果設(shè)置為false宠哄,它會(huì)禁止導(dǎo)致錯(cuò)誤的郵件的自動(dòng)提交,并且只會(huì)為成功的郵件執(zhí)行提交嗤攻,允許流在上次成功處理的郵件中自動(dòng)重播毛嫉,以防持續(xù)發(fā)生故障。如果設(shè)置為true妇菱,它將始終自動(dòng)提交(如果啟用了自動(dòng)提交)承粤。如果沒(méi)有設(shè)置(默認(rèn)),它實(shí)際上具有與enableDlq相同的值闯团,如果它們被發(fā)送到DLQ辛臊,則自動(dòng)提交錯(cuò)誤的消息,否則不提交它們房交。
默認(rèn)值:未設(shè)置彻舰。
recoveryInterval
連接恢復(fù)嘗試之間的間隔,以毫秒為單位候味。
默認(rèn)值:5000刃唤。
resetOffsets
是否將消費(fèi)者的偏移量重置為startOffset提供的值。
默認(rèn)值:false白群。
開(kāi)始偏移
新組的起始偏移量尚胞,或resetOffsets為true時(shí)的起始偏移量。允許的值:earliest帜慢,latest笼裳。如果消費(fèi)者組被明確設(shè)置為消費(fèi)者'綁定'(通過(guò)spring.cloud.stream.bindings..group),那么'startOffset'設(shè)置為earliest;否則對(duì)于anonymous消費(fèi)者組粱玲,設(shè)置為latest躬柬。
默認(rèn)值:null(相當(dāng)于earliest)。
enableDlq
當(dāng)設(shè)置為true時(shí)抽减,它將為消費(fèi)者發(fā)送啟用DLQ行為楔脯。默認(rèn)情況下,導(dǎo)致錯(cuò)誤的郵件將轉(zhuǎn)發(fā)到名為error..的主題胯甩。DLQ主題名稱可以通過(guò)屬性dlqName配置。對(duì)于錯(cuò)誤數(shù)量相對(duì)較少并且重播整個(gè)原始主題可能太麻煩的情況堪嫂,這為更常見(jiàn)的Kafka重播場(chǎng)景提供了另一種選擇偎箫。
默認(rèn)值:false。
組態(tài)
使用包含通用Kafka消費(fèi)者屬性的鍵/值對(duì)映射皆串。
默認(rèn)值:空地圖淹办。
dlqName
接收錯(cuò)誤消息的DLQ主題的名稱。
默認(rèn)值:null(如果未指定恶复,將導(dǎo)致錯(cuò)誤的消息將轉(zhuǎn)發(fā)到名為error..的主題)亚再。
Kafka生產(chǎn)者Properties
以下屬性僅適用于Kafka生產(chǎn)者朽色,必須以spring.cloud.stream.kafka.bindings..producer.為前綴低零。
緩沖區(qū)大小
上限(以字節(jié)為單位),Kafka生產(chǎn)者將在發(fā)送之前嘗試批量的數(shù)據(jù)量姥宝。
默認(rèn)值:16384。
同步
生產(chǎn)者是否是同步的
默認(rèn)值:false恐疲。
batchTimeout
生產(chǎn)者在發(fā)送之前等待多長(zhǎng)時(shí)間腊满,以便允許更多消息在同一批次中累積。(通常培己,生產(chǎn)者根本不等待碳蛋,并且簡(jiǎn)單地發(fā)送在先前發(fā)送進(jìn)行中累積的所有消息。)非零值可能會(huì)以延遲為代價(jià)增加吞吐量省咨。
默認(rèn)值:0肃弟。
組態(tài)
使用包含通用Kafka生產(chǎn)者屬性的鍵/值對(duì)映射。
默認(rèn)值:空地圖零蓉。
注意
Kafka綁定器將使用生產(chǎn)者的partitionCount設(shè)置作為提示笤受,以創(chuàng)建具有給定分區(qū)計(jì)數(shù)的主題(與minPartitionCount一起使用,最多兩個(gè)為正在使用的值) 壁公。配置綁定器的minPartitionCount和應(yīng)用程序的partitionCount時(shí)要小心感论,因?yàn)閷⑹褂幂^大的值。如果一個(gè)主題已經(jīng)存在較小的分區(qū)計(jì)數(shù)紊册,并且autoAddPartitions被禁用(默認(rèn)值)比肄,則綁定器將無(wú)法啟動(dòng)。如果一個(gè)主題已經(jīng)存在較小的分區(qū)計(jì)數(shù)囊陡,并且啟用了autoAddPartitions芳绩,則會(huì)添加新的分區(qū)。如果一個(gè)主題已經(jīng)存在的分區(qū)數(shù)量大于(minPartitionCount和partitionCount)的最大值撞反,則將使用現(xiàn)有的分區(qū)計(jì)數(shù)妥色。
用法示例
在本節(jié)中,我們舉例說(shuō)明了上述屬性在具體情況下的使用遏片。
示例:設(shè)置autoCommitOffsetfalse并依賴手動(dòng)確認(rèn)嘹害。
該示例說(shuō)明了如何在消費(fèi)者應(yīng)用程序中手動(dòng)確認(rèn)偏移量。
此示例要求spring.cloud.stream.kafka.bindings.input.consumer.autoCommitOffset設(shè)置為false吮便。使用相應(yīng)的輸入通道名稱作為示例笔呀。
@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();
}
}
}
示例:安全配置
Apache Kafka 0.9支持客戶端和代理商之間的安全連接。要充分利用此功能髓需,請(qǐng)遵循匯編文檔中的Apache Kafka文檔以及Kafka 0.9安全性指導(dǎo)原則许师。使用spring.cloud.stream.kafka.binder.configuration選項(xiàng)為綁定器創(chuàng)建的所有客戶端設(shè)置安全屬性。
例如,要將security.protocol設(shè)置為SASL_SSL微渠,請(qǐng)?jiān)O(shè)置:
spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_SSL
所有其他安全屬性可以以類似的方式設(shè)置搭幻。
使用Kerberos時(shí),請(qǐng)按照參考文檔中的說(shuō)明創(chuàng)建和引用JAAS配置逞盆。
Spring Cloud Stream支持使用JAAS配置文件并使用Spring Boot屬性將JAAS配置信息傳遞到應(yīng)用程序檀蹋。
使用JAAS配置文件
可以通過(guò)使用系統(tǒng)屬性為Spring Cloud Stream應(yīng)用程序設(shè)置JAAS和(可選)krb5文件位置。以下是使用JAAS配置文件啟動(dòng)帶有SASL和Kerberos的Spring Cloud Stream應(yīng)用程序的示例:
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
使用Spring Boot屬性
作為使用JAAS配置文件的替代方案纳击,Spring Cloud Stream提供了一種使用Spring Boot屬性為Spring Cloud Stream應(yīng)用程序設(shè)置JAAS配置的機(jī)制续扔。
以下屬性可用于配置Kafka客戶端的登錄上下文。
spring.cloud.stream.kafka.binder.jaas.loginModule
登錄模塊名稱焕数。在正常情況下不需要設(shè)置纱昧。
默認(rèn)值:com.sun.security.auth.module.Krb5LoginModule。
spring.cloud.stream.kafka.binder.jaas.controlFlag
登錄模塊的控制標(biāo)志堡赔。
默認(rèn)值:required识脆。
spring.cloud.stream.kafka.binder.jaas.options
使用包含登錄模塊選項(xiàng)的鍵/值對(duì)映射悬包。
默認(rèn)值:空地圖艺沼。
以下是使用Spring Boot配置屬性啟動(dòng)帶有SASL和Kerberos的Spring Cloud Stream應(yīng)用程序的示例:
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
這相當(dāng)于以下JAAS文件:
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";
};
如果所需的主題已經(jīng)存在于代理上峰弹,或?qū)⒂晒芾韱T創(chuàng)建案腺,則自動(dòng)創(chuàng)建可以被關(guān)閉,并且僅需要發(fā)送客戶端JAAS屬性眠砾。作為設(shè)置spring.cloud.stream.kafka.binder.autoCreateTopics的替代方法实蓬,您可以簡(jiǎn)單地從應(yīng)用程序中刪除代理依賴關(guān)系赊抖。有關(guān)詳細(xì)信息艘包,請(qǐng)參閱基于綁定器的應(yīng)用程序的類路徑中排除Kafka代理jar的猛。
注意
不要在同一應(yīng)用程序中混合JAAS配置文件和Spring Boot屬性。如果-Djava.security.auth.login.config系統(tǒng)屬性已存在想虎,則Spring Cloud Stream將忽略Spring Boot屬性卦尊。
注意
使用autoCreateTopics和autoAddPartitions如果使用Kerberos,請(qǐng)務(wù)必小心舌厨。通常應(yīng)用程序可能使用Kafka和Zookeeper中沒(méi)有管理權(quán)限的主體岂却,并且依賴Spring Cloud Stream創(chuàng)建/修改主題可能會(huì)失敗。在安全環(huán)境中裙椭,我們強(qiáng)烈建議您使用Kafka工具管理性地創(chuàng)建主題并管理ACL躏哩。
使用綁定器與Apache Kafka 0.10
Spring Cloud Stream Kafka binder中的默認(rèn)Kafka支持是針對(duì)Kafka版本0.10.1.1的。粘合劑還支持連接到其他0.10版本和0.9客戶端揉燃。為了做到這一點(diǎn)震庭,當(dāng)你創(chuàng)建包含你的應(yīng)用程序的項(xiàng)目時(shí),包括spring-cloud-starter-stream-kafka你雌,你通常會(huì)對(duì)默認(rèn)的綁定器做。然后將這些依賴項(xiàng)添加到pom.xml文件中的部分的頂部以覆蓋依賴關(guān)系。
以下是將應(yīng)用程序降級(jí)到0.10.0.1的示例婿崭。由于它仍在0.10行拨拓,因此可以保留默認(rèn)的spring-kafka和spring-integration-kafka版本。
org.apache.kafka
kafka_2.11
0.10.0.1
org.slf4j
slf4j-log4j12
org.apache.kafka
kafka-clients
0.10.0.1
這是使用0.9.0.1版本的另一個(gè)例子氓栈。
org.springframework.kafka
spring-kafka
1.0.5.RELEASE
org.springframework.integration
spring-integration-kafka
2.0.1.RELEASE
org.apache.kafka
kafka_2.11
0.9.0.1
org.slf4j
slf4j-log4j12
org.apache.kafka
kafka-clients
0.9.0.1
注意
以上版本僅為了舉例而提供渣磷。為獲得最佳效果,我們建議您使用最新的0.10兼容版本的項(xiàng)目授瘦。
從基于綁定器的應(yīng)用程序的類路徑中排除Kafka代理jar
Apache Kafka Binder使用作為Apache Kafka服務(wù)器庫(kù)一部分的管理實(shí)用程序來(lái)創(chuàng)建和重新配置主題醋界。如果在運(yùn)行時(shí)不需要包含Apache Kafka服務(wù)器庫(kù)及其依賴關(guān)系提完,因?yàn)閼?yīng)用程序?qū)⒁蕾囉诠芾碇信渲玫闹黝}形纺,Kafka binder允許排除Apache Kafka服務(wù)器依賴關(guān)系從應(yīng)用程序。
如果您使用上述建議的Kafka依賴關(guān)系的非默認(rèn)版本徒欣,則只需要包含kafka代理依賴項(xiàng)逐样。如果您使用默認(rèn)的Kafka版本,請(qǐng)確保從spring-cloud-starter-stream-kafka依賴關(guān)系中排除kafka broker jar打肝,如下所示脂新。
org.springframework.cloud
spring-cloud-starter-stream-kafka
org.apache.kafka
kafka_2.11
如果您排除Apache Kafka服務(wù)器依賴關(guān)系,并且該主題不在服務(wù)器上粗梭,那么如果在服務(wù)器上啟用了自動(dòng)主題創(chuàng)建争便,則Apache Kafka代理將創(chuàng)建該主題。請(qǐng)注意断医,如果您依賴此滞乙,則Kafka服務(wù)器將使用默認(rèn)數(shù)量的分區(qū)和復(fù)制因子。另一方面孩锡,如果在服務(wù)器上禁用自動(dòng)主題創(chuàng)建枫慷,則在運(yùn)行應(yīng)用程序之前必須注意創(chuàng)建具有所需數(shù)量分區(qū)的主題。
如果要完全控制分區(qū)的分配方式效斑,請(qǐng)保留默認(rèn)設(shè)置非春,即不要排除kafka代理程序jar,并確保將spring.cloud.stream.kafka.binder.autoCreateTopics設(shè)置為true缓屠,這是默認(rèn)設(shè)置奇昙。
Dead-Letter主題處理
因?yàn)椴豢赡茴A(yù)料到用戶如何處理死信消息,所以框架不提供任何標(biāo)準(zhǔn)的機(jī)制來(lái)處理它們敌完。如果死刑的原因是短暫的储耐,您可能希望將郵件路由到原始主題。但是滨溉,如果問(wèn)題是一個(gè)永久性的問(wèn)題什湘,那可能會(huì)導(dǎo)致無(wú)限循環(huán)长赞。以下spring-boot應(yīng)用程序是如何將這些消息路由到原始主題的示例,但在三次嘗試后將其移動(dòng)到第三個(gè)“停車場(chǎng)”主題禽炬。該應(yīng)用程序只是從死信主題中讀取的另一個(gè)spring-cloud-stream應(yīng)用程序涧卵。5秒內(nèi)沒(méi)有收到消息時(shí)終止。
這些示例假定原始目的地是so8400out腹尖,而消費(fèi)者組是so8400。
有幾個(gè)注意事項(xiàng)
當(dāng)主應(yīng)用程序未運(yùn)行時(shí)伐脖,請(qǐng)考慮僅運(yùn)行重新路由热幔。否則,瞬態(tài)錯(cuò)誤的重試將很快用盡讼庇。
或者绎巨,使用兩階段方法 - 使用此應(yīng)用程序路由到第三個(gè)主題,另一個(gè)則從那里路由到主題蠕啄。
由于這種技術(shù)使用消息標(biāo)頭來(lái)跟蹤重試场勤,所以它不會(huì)與headerMode=raw一起使用。在這種情況下歼跟,請(qǐng)考慮將一些數(shù)據(jù)添加到有效載荷(主應(yīng)用程序可以忽略)和媳。
必須將x-retries添加到headers屬性spring.cloud.stream.kafka.binder.headers=x-retries和主應(yīng)用程序,以便標(biāo)頭在應(yīng)用程序之間傳輸哈街。
由于kafka是發(fā)布/訂閱留瞳,所以重播的消息將被發(fā)送給每個(gè)消費(fèi)者組,即使是那些首次成功處理消息的消費(fèi)者組骚秦。
application.properties
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
應(yīng)用
@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();
}
}
RabbitMQ Binder
用法
對(duì)于使用RabbitMQ綁定器她倘,您只需要使用以下Maven坐標(biāo)將其添加到您的Spring Cloud Stream應(yīng)用程序:
org.springframework.cloud
spring-cloud-stream-binder-rabbit
或者,您也可以使用Spring Cloud Stream RabbitMQ入門作箍。
org.springframework.cloud
spring-cloud-starter-stream-rabbit
RabbitMQ Binder概述
以下可以看到RabbitMQ活頁(yè)夾的操作簡(jiǎn)化圖硬梁。
圖14. RabbitMQ Binder
RabbitMQ Binder實(shí)現(xiàn)將每個(gè)目的地映射到TopicExchange荧止。對(duì)于每個(gè)消費(fèi)者組,Queue將綁定到該TopicExchange懒震。每個(gè)消費(fèi)者實(shí)例對(duì)其組的Queue具有相應(yīng)的RabbitMQConsumer實(shí)例罩息。對(duì)于分區(qū)生成器/消費(fèi)者,隊(duì)列后綴為分區(qū)索引个扰,并使用分區(qū)索引作為路由密鑰瓷炮。
使用autoBindDlq選項(xiàng),您可以選擇配置綁定器來(lái)創(chuàng)建和配置死信隊(duì)列(DLQ)(以及死信交換DLX)递宅。死信隊(duì)列具有目標(biāo)名稱娘香,附有.dlq苍狰。如果重試啟用(maxAttempts > 1)鸿竖,則會(huì)將失敗的消息傳遞到DLQ信夫。如果禁用重試(maxAttempts = 1)蛀蜜,則應(yīng)將requeueRejected設(shè)置為false(默認(rèn))轩勘,以使失敗的消息將路由到DLQ锤灿,而不是重新排隊(duì)叛氨。此外瘪吏,republishToDlq導(dǎo)致綁定器向DLQ發(fā)布失敗的消息(而不是拒絕它);這使得能夠?qū)?biāo)題中的附加信息添加到消息中铃彰,例如x-exception-stacktrace頭中的堆棧跟蹤盏檐。此選項(xiàng)不需要重試啟用;一次嘗試后歇式,您可以重新發(fā)布失敗的消息。從版本1.2開(kāi)始胡野,您可以配置重新發(fā)布的消息傳遞模式;見(jiàn)財(cái)產(chǎn)republishDeliveryMode材失。
重要
將requeueRejected設(shè)置為true將導(dǎo)致消息被重新排序并重新發(fā)送,這可能不是您想要的硫豆,除非故障問(wèn)題是短暫的龙巨。一般來(lái)說(shuō),最好通過(guò)將maxAttempts設(shè)置為大于1熊响,或?qū)epublishToDlq設(shè)置為true來(lái)啟用binder內(nèi)的重試旨别。
有關(guān)這些屬性的更多信息,請(qǐng)參閱RabbitMQ Binder Properties耘眨。
框架不提供消耗死信消息(或重新路由到主隊(duì)列)的任何標(biāo)準(zhǔn)機(jī)制昼榛。Dead-Letter隊(duì)列處理中描述了一些選項(xiàng)。
注意
在Spring Cloud Stream應(yīng)用程序中使用多個(gè)RabbitMQ綁定器時(shí)剔难,禁用“RabbitAutoConfiguration”以避免將RabbitAutoConfiguration應(yīng)用于兩個(gè)綁定器的相同配置很重要胆屿。
配置選項(xiàng)
本節(jié)包含特定于RabbitMQ Binder和綁定頻道的設(shè)置。
有關(guān)通用綁定配置選項(xiàng)和屬性偶宫,請(qǐng)參閱Spring Cloud Stream核心文檔非迹。
RabbitMQ Binder Properties
默認(rèn)情況下,RabbitMQ binder使用Spring Boot的ConnectionFactory纯趋,因此它支持RabbitMQ的所有Spring Boot配置選項(xiàng)憎兽。(有關(guān)參考,請(qǐng)參閱Spring Boot文檔吵冒。)RabbitMQ配置選項(xiàng)使用spring.rabbitmq前綴纯命。
除Spring Boot選項(xiàng)之外,RabbitMQ binder還支持以下屬性:
spring.cloud.stream.rabbit.binder.adminAddresses
RabbitMQ管理插件網(wǎng)址的逗號(hào)分隔列表痹栖。僅在nodes包含多個(gè)條目時(shí)使用亿汞。此列表中的每個(gè)條目必須在spring.rabbitmq.addresses中具有相應(yīng)的條目。
默認(rèn)值:空揪阿。
spring.cloud.stream.rabbit.binder.nodes
RabbitMQ節(jié)點(diǎn)名稱的逗號(hào)分隔列表疗我。當(dāng)多個(gè)條目用于查找隊(duì)列所在的服務(wù)器地址時(shí)咆畏。此列表中的每個(gè)條目必須在spring.rabbitmq.addresses中具有相應(yīng)的條目。
默認(rèn)值:空吴裤。
spring.cloud.stream.rabbit.binder.compressionLevel
壓縮綁定的壓縮級(jí)別旧找。見(jiàn)java.util.zip.Deflater。
默認(rèn)值:1(BEST_LEVEL)麦牺。
RabbitMQ消費(fèi)者Properties
以下屬性僅適用于Rabbit消費(fèi)者钮蛛,并且必須以spring.cloud.stream.rabbit.bindings..consumer.為前綴。
acknowledgeMode
確認(rèn)模式剖膳。
默認(rèn)值:AUTO愿卒。
autoBindDlq
是否自動(dòng)聲明DLQ并將其綁定到綁定器DLX。
默認(rèn)值:false潮秘。
bindingRoutingKey
將隊(duì)列綁定到交換機(jī)的路由密鑰(如果bindQueue為true)。將附加分區(qū)目的地-祟印。
默認(rèn)值:#伦泥。
bindQueue
是否將隊(duì)列綁定到目的地交換機(jī)造烁?如果您已經(jīng)設(shè)置了自己的基礎(chǔ)設(shè)施并且先前已經(jīng)創(chuàng)建/綁定了隊(duì)列,請(qǐng)?jiān)O(shè)置為false躏精。
默認(rèn)值:true。
deadLetterQueueName
DLQ的名稱
默認(rèn)值:prefix+destination.dlq
deadLetterExchange
分配給隊(duì)列的DLX;如果autoBindDlq為true
默認(rèn)值:'prefix + DLX'
deadLetterRoutingKey
一個(gè)死信路由密鑰分配給隊(duì)列;如果autoBindDlq為true
默認(rèn)值:destination
declareExchange
是否為目的地申報(bào)交換鹦肿。
默認(rèn)值:true矗烛。
delayedExchange
是否將交換聲明為Delayed Message Exchange- 需要在代理上延遲的消息交換插件。x-delayed-type參數(shù)設(shè)置為exchangeType箩溃。
默認(rèn)值:false瞭吃。
dlqDeadLetterExchange
如果DLQ被聲明,則將DLX分配給該隊(duì)列
默認(rèn)值:none
dlqDeadLetterRoutingKey
如果DLQ被聲明涣旨,則會(huì)將一個(gè)死信路由密鑰分配給該隊(duì)列;默認(rèn)無(wú)
默認(rèn)值:none
dlqExpires
未使用的死信隊(duì)列被刪除多久(ms)
默認(rèn)值:no expiration
dlqMaxLength
死信隊(duì)列中的最大消息數(shù)
默認(rèn)值:no limit
dlqMaxLengthBytes
來(lái)自所有消息的死信隊(duì)列中的最大字節(jié)數(shù)
默認(rèn)值:no limit
dlqMaxPriority
死信隊(duì)列中消息的最大優(yōu)先級(jí)(0-255)
默認(rèn)值:none
dlqTtl
聲明(ms)時(shí)默認(rèn)適用于死信隊(duì)列的時(shí)間
默認(rèn)值:no limit
durableSubscription
訂閱是否應(yīng)該耐用歪架。僅當(dāng)group也被設(shè)置時(shí)才有效。
默認(rèn)值:true霹陡。
exchangeAutoDelete
如果declareExchange為真和蚪,則交換機(jī)是否應(yīng)該自動(dòng)刪除(刪除最后一個(gè)隊(duì)列后刪除)。
默認(rèn)值:true烹棉。
exchangeDurable
如果declareExchange為真攒霹,則交換應(yīng)該是否持久(經(jīng)紀(jì)人重新啟動(dòng))。
默認(rèn)值:true浆洗。
exchangeType
交換類型;非分區(qū)目的地的direct催束,fanout或topicdirect或topic分區(qū)目的地。
默認(rèn)值:topic辅髓。
到期
未使用的隊(duì)列被刪除多久(ms)
默認(rèn)值:no expiration
headerPatterns
要從入站郵件映射的頭文件泣崩。
默認(rèn)值:['*'](所有標(biāo)題)少梁。
maxConcurrency
最大消費(fèi)者人數(shù)
默認(rèn)值:1。
最長(zhǎng)長(zhǎng)度
隊(duì)列中最大消息數(shù)
默認(rèn)值:no limit
maxLengthBytes
來(lái)自所有消息的隊(duì)列中最大字節(jié)數(shù)
默認(rèn):no limit
maxPriority
隊(duì)列中消息的最大優(yōu)先級(jí)(0-255)
默認(rèn)
none
預(yù)取
預(yù)取計(jì)數(shù)矫付。
默認(rèn)值:1凯沪。
字首
要添加到destination和隊(duì)列名稱的前綴。
默認(rèn)值:“”买优。
recoveryInterval
連接恢復(fù)嘗試之間的間隔妨马,以毫秒為單位。
默認(rèn)值:5000杀赢。
requeueRejected
在重試禁用或重新發(fā)布ToDlq是否為false時(shí)烘跺,是否應(yīng)重新發(fā)送傳遞失敗。
默認(rèn)值:false脂崔。
republishDeliveryMode
當(dāng)republishToDlq為true時(shí)滤淳,指定重新發(fā)布的郵件的傳遞模式。
默認(rèn)值:DeliveryMode.PERSISTENT
republishToDlq
默認(rèn)情況下砌左,嘗試重試后失敗的消息將被拒絕脖咐。如果配置了死信隊(duì)列(DLQ),則RabbitMQ將將失敗的消息(未更改)路由到DLQ汇歹。如果設(shè)置為true屁擅,則綁定器將重新發(fā)布具有附加頭的DLQ的失敗消息,包括最終失敗的原因的異常消息和堆棧跟蹤产弹。
默認(rèn)值:false
交易
是否使用交易渠道派歌。
默認(rèn)值:false。
TTL
聲明(ms)時(shí)默認(rèn)適用于隊(duì)列的時(shí)間
默認(rèn)值:no limit
txSize
阿克斯之間的交付次數(shù)痰哨。
默認(rèn)值:1胶果。
兔子生產(chǎn)者Properties
以下屬性僅適用于Rabbit生產(chǎn)者,必須以spring.cloud.stream.rabbit.bindings..producer.為前綴作谭。
autoBindDlq
是否自動(dòng)聲明DLQ并將其綁定到綁定器DLX稽物。
默認(rèn)值:false辜梳。
batchingEnabled
是否啟用生產(chǎn)者的消息批處理农渊。
默認(rèn)值:false。
BATCHSIZE
批量啟動(dòng)時(shí)要緩沖的消息數(shù)泼诱。
默認(rèn)值:100锐秦。
batchBufferLimit
默認(rèn)值:10000咪奖。
batchTimeout
默認(rèn)值:5000。
bindingRoutingKey
將隊(duì)列綁定到交換機(jī)的路由密鑰(如果bindQueue為true)酱床。僅適用于非分區(qū)目的地羊赵。僅適用于requiredGroups,然后僅提供給這些組。
默認(rèn)值:#昧捷。
bindQueue
是否將隊(duì)列綁定到目的地交換機(jī)闲昭?如果您已經(jīng)設(shè)置了自己的基礎(chǔ)架構(gòu)并且先前已經(jīng)創(chuàng)建/綁定了隊(duì)列,請(qǐng)?jiān)O(shè)置為false靡挥。僅適用于requiredGroups序矩,然后僅提供給這些組。
默認(rèn)值:true跋破。
壓縮
發(fā)送時(shí)是否應(yīng)壓縮數(shù)據(jù)簸淀。
默認(rèn)值:false。
deadLetterQueueName
DLQ的名稱僅適用于requiredGroups毒返,僅適用于這些組租幕。
默認(rèn)值:prefix+destination.dlq
deadLetterExchange
分配給隊(duì)列的DLX;如果autoBindDlq為true只適用于requiredGroups,然后只提供給這些組拧簸。
默認(rèn)值:'prefix + DLX'
deadLetterRoutingKey
一個(gè)死信路由密鑰分配給隊(duì)列;如果autoBindDlq為true只適用于requiredGroups劲绪,然后只提供給這些組。
默認(rèn)值:destination
declareExchange
是否為目的地申報(bào)交換盆赤。
默認(rèn)值:true珠叔。
延遲
評(píng)估應(yīng)用于消息(x-delay頭)的延遲的Spel表達(dá)式 - 如果交換不是延遲的消息交換,則不起作用弟劲。
默認(rèn)值:Nox-delay頭設(shè)置。
delayedExchange
是否將交換聲明為Delayed Message Exchange- 需要經(jīng)紀(jì)人上的延遲消息交換插件姥芥。x-delayed-type參數(shù)設(shè)置為exchangeType兔乞。
默認(rèn)值:false。
deliveryMode
交貨方式凉唐。
默認(rèn)值:PERSISTENT庸追。
dlqDeadLetterExchange
如果DLQ被聲明,則分配給該隊(duì)列的DLX只適用于requiredGroups台囱,然后僅提供給這些組淡溯。
默認(rèn)值:none
dlqDeadLetterRoutingKey
如果DLQ被聲明,則會(huì)將一個(gè)死信路由密鑰分配給該隊(duì)列;默認(rèn)值none僅在提供requiredGroups時(shí)才適用簿训,然后僅適用于這些組咱娶。
默認(rèn)值:none
dlqExpires
未使用的死信隊(duì)列被刪除之前多久(ms)僅適用于requiredGroups,然后僅提供給這些組强品。
默認(rèn)值:no expiration
dlqMaxLength
死信隊(duì)列中的最大消息數(shù)僅適用于requiredGroups膘侮,僅適用于這些組。
默認(rèn)值:no limit
dlqMaxLengthBytes
來(lái)自所有消息的死信隊(duì)列中的最大字節(jié)數(shù)僅適用于requiredGroups的榛,然后僅提供給這些組琼了。
默認(rèn)值:no limit
dlqMaxPriority
死信隊(duì)列中消息的最大優(yōu)先級(jí)(0-255)僅適用于requiredGroups,然后僅提供給這些組夫晌。
默認(rèn)值:none
dlqTtl
聲明(ms)的默認(rèn)時(shí)間適用于死信隊(duì)列僅適用于requiredGroups雕薪,然后僅提供給這些組昧诱。
默認(rèn)值:no limit
exchangeAutoDelete
如果declareExchange為真,則交換機(jī)是否應(yīng)該自動(dòng)刪除(刪除最后一個(gè)隊(duì)列后刪除)所袁。
默認(rèn)值:true户誓。
exchangeDurable
如果declareExchange為真肌毅,則交換應(yīng)該是持久的(經(jīng)紀(jì)人重新啟動(dòng))。
默認(rèn)值:true。
exchangeType
交換類型;direct部翘,fanout或topic;direct或topic。
默認(rèn)值:topic睬捶。
到期
在未使用的隊(duì)列被刪除之前多久(ms)僅適用于requiredGroups屈藐,然后只提供給這些組。
默認(rèn)值:no expiration
headerPatterns
要將標(biāo)頭映射到出站郵件的模式鱼填。
默認(rèn)值:['*'](所有標(biāo)題)药有。
最長(zhǎng)長(zhǎng)度
隊(duì)列中最大消息數(shù)僅適用于requiredGroups,僅適用于這些組苹丸。
默認(rèn)值:no limit
maxLengthBytes
來(lái)自所有消息的隊(duì)列中最大字節(jié)數(shù)僅適用于requiredGroups愤惰,僅適用于這些組。
默認(rèn)值:no limit
maxPriority
隊(duì)列中消息的最大優(yōu)先級(jí)(0-255)僅適用于requiredGroups赘理,僅適用于這些組宦言。
默認(rèn)
none
字首
要添加到destination交換機(jī)名稱的前綴。
默認(rèn)值:“”商模。
routingKeyExpression
一個(gè)SpEL表達(dá)式來(lái)確定在發(fā)布消息時(shí)使用的路由密鑰奠旺。
默認(rèn)值:destination或destination-分區(qū)目的地。
交易
是否使用交易渠道施流。
默認(rèn)值:false响疚。
TTL
聲明時(shí)默認(rèn)適用于隊(duì)列的時(shí)間(ms)僅適用于requiredGroups,然后僅適用于這些組瞪醋。
默認(rèn)值:no limit
注意
在RabbitMQ的情況下忿晕,內(nèi)容類型頭可以由外部應(yīng)用程序設(shè)置。Spring Cloud Stream支持它們作為用于任何類型傳輸(包括通常不支持頭文件的Kafka)的傳輸?shù)臄U(kuò)展內(nèi)部協(xié)議的一部分)银受。
重試RabbitMQ Binder
概觀
在綁定器中啟用重試時(shí)践盼,偵聽(tīng)器容器線程將被掛起,以配置任何后退時(shí)段宾巍。在單個(gè)消費(fèi)者需要嚴(yán)格排序時(shí)宏侍,這可能很重要,但是對(duì)于其他用例蜀漆,它可以防止在該線程上處理其他消息谅河。使用綁定器重試的另一種方法是設(shè)置死機(jī)字符隨著時(shí)間生活在死信隊(duì)列(DLQ)上,以及DLQ本身的死信配置。有關(guān)這里討論的屬性的更多信息绷耍,請(qǐng)參閱RabbitMQ Binder Properties吐限。啟用此功能的示例配置:
將autoBindDlq設(shè)置為true- 綁定器將創(chuàng)建一個(gè)DLQ;您可以選擇在deadLetterQueueName中指定一個(gè)名稱
將dlqTtl設(shè)置為您要在重新投遞之間等待的退出時(shí)間
將dlqDeadLetterExchange設(shè)置為默認(rèn)交換 - DLQ的過(guò)期消息將被路由到原始隊(duì)列,因?yàn)槟J(rèn)deadLetterRoutingKey是隊(duì)列名稱(destination.group)
要強(qiáng)制一個(gè)消息被填字褂始,拋出一個(gè)AmqpRejectAndDontRequeueException诸典,或設(shè)置requeueRejected到true并拋出任何異常。
循環(huán)將繼續(xù)沒(méi)有結(jié)束崎苗,這對(duì)于短暫的問(wèn)題是很好的狐粱,但是您可能想在一些嘗試后放棄。幸運(yùn)的是胆数,RabbitMQ提供了x-death標(biāo)題肌蜻,允許您確定發(fā)生了多少個(gè)周期。
在放棄之后確認(rèn)一則消息必尼,拋出一個(gè)ImmediateAcknowledgeAmqpException蒋搜。
把它放在一起
---
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=
---
此配置創(chuàng)建一個(gè)與通配符路由密鑰#交換主題的隊(duì)列myDestination.consumerGroup的交換myDestination。它創(chuàng)建一個(gè)綁定到具有路由密鑰myDestination.consumerGroup的直接交換DLX的DLQ判莉。當(dāng)消息被拒絕時(shí)豆挽,它們被路由到DLQ。5秒鐘后券盅,消息過(guò)期避咆,并使用隊(duì)列名稱作為路由密鑰路由到原始隊(duì)列仓犬。
Spring Boot申請(qǐng)
@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");
}
}
請(qǐng)注意盈蛮,x-death標(biāo)題中的count屬性是Long呆抑。
Dead-Letter隊(duì)列處理
因?yàn)椴豢赡茴A(yù)料到用戶如何處理死信消息,所以框架不提供任何標(biāo)準(zhǔn)的機(jī)制來(lái)處理它們互站。如果死刑的原因是暫時(shí)的,您可能希望將郵件路由到原始隊(duì)列僵缺。但是胡桃,如果問(wèn)題是一個(gè)永久性的問(wèn)題,那可能會(huì)導(dǎo)致無(wú)限循環(huán)磕潮。以下spring-boot應(yīng)用程序是如何將這些消息路由到原始隊(duì)列的示例翠胰,但是在三次嘗試之后將其移動(dòng)到第三個(gè)“停車場(chǎng)”隊(duì)列。第二個(gè)例子使用RabbitMQ延遲消息交換來(lái)向被重新排序的消息引入延遲自脯。在這個(gè)例子中之景,每次嘗試的延遲都會(huì)增加。這些示例使用@RabbitListener從DLQ接收消息膏潮,您也可以在批處理過(guò)程中使用RabbitTemplate.receive()锻狗。
這些示例假定原始目的地是so8400in,消費(fèi)者組是so8400。
非分區(qū)目的地
前兩個(gè)示例是目的地未分區(qū)轻纪。
@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);
}
}
@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 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);
}
}
分區(qū)目的地
對(duì)于分區(qū)目的地油额,所有分區(qū)都有一個(gè)DLQ,我們從頭部確定原始隊(duì)列刻帚。
republishToDlq = FALSE
當(dāng)republishToDlq為false時(shí)潦嘶,RabbitMQ將消息發(fā)布到DLX / DLQ,其中包含有關(guān)原始目的地信息的x-death標(biāo)題崇众。
@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 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> xDeath = (List>) headers.get(X_DEATH_HEADER);
String exchange = (String) xDeath.get(0).get("exchange");
List routingKeys = (List) 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);
}
}
republishToDlq =真
當(dāng)republishToDlq為true時(shí)掂僵,重新發(fā)布恢復(fù)器將原始交換和路由密鑰添加到標(biāo)題。
@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 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);
}
}
Spring Cloud Bus
Spring Cloud Bus將分布式系統(tǒng)的節(jié)點(diǎn)與輕量級(jí)消息代理鏈接顷歌。這可以用于廣播狀態(tài)更改(例如配置更改)或其他管理指令锰蓬。一個(gè)關(guān)鍵的想法是,總線就像一個(gè)分布式執(zhí)行器衙吩,用于擴(kuò)展的Spring Boot應(yīng)用程序互妓,但也可以用作應(yīng)用程序之間的通信通道。目前唯一的實(shí)現(xiàn)是使用AMQP代理作為傳輸坤塞,但是相同的基本功能集(還有一些取決于傳輸)在其他傳輸?shù)穆肪€圖上冯勉。
注意
Spring Cloud根據(jù)非限制性Apache 2.0許可證發(fā)布。如果您想為文檔的這一部分做出貢獻(xiàn)摹芙,或者發(fā)現(xiàn)錯(cuò)誤灼狰,請(qǐng)?jiān)?a target="_blank" rel="nofollow">github中找到項(xiàng)目中的源代碼和問(wèn)題跟蹤器。
快速開(kāi)始
Spring Cloud Bus的工作原理是添加Spring Boot自動(dòng)配置浮禾,如果它在類路徑中檢測(cè)到自身交胚。所有您需要做的是啟用總線是將spring-cloud-starter-bus-amqp或spring-cloud-starter-bus-kafka添加到您的依賴關(guān)系管理中,并且Spring Cloud負(fù)責(zé)其余部分盈电。確保代理(RabbitMQ或Kafka)可用和配置:在本地主機(jī)上運(yùn)行蝴簇,??您不應(yīng)該做任何事情,但如果您遠(yuǎn)程運(yùn)行使用Spring Cloud連接器或Spring Boot定義經(jīng)紀(jì)人憑據(jù)的約定匆帚,例如Rabbit
application.yml
spring:
rabbitmq:
host: mybroker.com
port: 5672
username: user
password: secret
總線當(dāng)前支持向所有節(jié)點(diǎn)發(fā)送消息熬词,用于特定服務(wù)的所有節(jié)點(diǎn)(由Eureka定義)。未來(lái)可能會(huì)添加更多的選擇器標(biāo)準(zhǔn)(即吸重,僅數(shù)據(jù)中心Y中的服務(wù)X節(jié)點(diǎn)等)互拾。/bus/*執(zhí)行器命名空間下還有一些http端點(diǎn)。目前有兩個(gè)實(shí)施嚎幸。第一個(gè)/bus/env發(fā)送密鑰/值對(duì)來(lái)更新每個(gè)節(jié)點(diǎn)的Spring環(huán)境饮寞。第二個(gè)膳音,/bus/refresh顶滩,將重新加載每個(gè)應(yīng)用程序的配置敛滋,就好像他們?cè)谒麄兊?refresh端點(diǎn)上都被ping過(guò)田篇。
注意
總線起動(dòng)器覆蓋了Rabbit和Kafka,因?yàn)檫@是兩種最常用的實(shí)現(xiàn)方式封断,但是Spring Cloud Stream非常靈活斯辰,綁定器將與spring-cloud-bus結(jié)合使用。
處理實(shí)例
HTTP端點(diǎn)接受“目的地”參數(shù)坡疼,例如“/ bus / refresh彬呻?destination = customers:9000”,其中目的地是ApplicationContextID柄瑰。如果ID由總線上的一個(gè)實(shí)例擁有闸氮,那么它將處理消息,所有其他實(shí)例將忽略它教沾。Spring Boot將ContextIdApplicationContextInitializer中的ID設(shè)置為spring.application.name蒲跨,活動(dòng)配置文件和server.port的組合。
尋址服務(wù)的所有實(shí)例
“destination”參數(shù)用于SpringPathMatcher(路徑分隔符為冒號(hào):)以確定實(shí)例是否處理該消息授翻。使用上述示例或悲,“/ bus / refresh?destination = customers:**”將針對(duì)“客戶”服務(wù)的所有實(shí)例堪唐,而不管配置文件和端口設(shè)置為ApplicationContextID巡语。
應(yīng)用程序上下文ID必須是唯一的
總線嘗試從原始ApplicationEvent一次消除處理事件兩次,一次從隊(duì)列中消除淮菠。為此男公,它會(huì)檢查發(fā)送應(yīng)用程序上下文id,以重新顯示當(dāng)前的應(yīng)用程序上下文ID合陵。如果服務(wù)的多個(gè)實(shí)例具有相同的應(yīng)用程序上下文id枢赔,則不會(huì)處理事件。在本地機(jī)器上運(yùn)行拥知,每個(gè)服務(wù)將在不同的端口上踏拜,這將是應(yīng)用程序上下文ID的一部分。Cloud Foundry提供了區(qū)分的索引低剔。要確保應(yīng)用程序上下文ID是唯一的速梗,請(qǐng)將spring.application.index設(shè)置為服務(wù)的每個(gè)實(shí)例唯一的值。例如户侥,在lattice中,在application.properties中設(shè)置spring.application.index=${INSTANCE_INDEX}(如果使用configserver峦嗤,請(qǐng)?jiān)O(shè)置bootstrap.properties)蕊唐。
自定義Message Broker
Spring Cloud Bus使用Spring Cloud Stream廣播消息,以便獲取消息流烁设,只需要在類路徑中包含您選擇的binder實(shí)現(xiàn)替梨。有AMQP(RabbitMQ)和Kafka(spring-cloud-starter-bus-[amqp,kafka])的公共汽車專用起動(dòng)方便钓试。一般來(lái)說(shuō),Spring Cloud Stream依賴于用于配置中間件的Spring Boot自動(dòng)配置約定副瀑,因此例如AMQP代理地址可以使用spring.rabbitmq.*配置屬性更改弓熏。Spring Cloud Bus在spring.cloud.bus.*中具有少量本地配置屬性(例如spring.cloud.bus.destination是使用外部中間件的主題的名稱)。通常糠睡,默認(rèn)值就足夠了挽鞠。
要更多地了解如何自定義消息代理設(shè)置,請(qǐng)參閱Spring Cloud Stream文檔狈孔。
跟蹤Bus Events
可以通過(guò)設(shè)置spring.cloud.bus.trace.enabled=true來(lái)跟蹤總線事件(RemoteApplicationEvent的子類)信认。如果這樣做,那么Spring BootTraceRepository(如果存在)將顯示每個(gè)發(fā)送的事件和來(lái)自每個(gè)服務(wù)實(shí)例的所有ack均抽。示例(來(lái)自/trace端點(diǎn)):
{
"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": "*:**"
}
}
該跟蹤顯示RefreshRemoteApplicationEvent從customers:9000發(fā)送到所有服務(wù)止潘,并且已被customers:9000和stores:8081收到(acked)极阅。
為了處理信號(hào),您可以向您的應(yīng)用添加AckRemoteApplicationEvent和SentApplicationEvent類型的@EventListener(并啟用跟蹤)∩娣悖或者您可以利用TraceRepository并從中挖掘數(shù)據(jù)。
注意
任何總線應(yīng)用程序都可以跟蹤ack江解,但有時(shí)在一個(gè)可以對(duì)數(shù)據(jù)進(jìn)行更復(fù)雜查詢的中央服務(wù)器中執(zhí)行此操作是有用的块仆。或者將其轉(zhuǎn)發(fā)到專門的跟蹤服務(wù)翩迈。
廣播自己的Events
總線可以攜帶任何類型為RemoteApplicationEvent的事件持灰,但默認(rèn)傳輸是JSON,并且解串器需要知道哪些類型將提前使用负饲。要注冊(cè)一個(gè)新類型堤魁,它需要在org.springframework.cloud.bus.event的子包中。
要自定義事件名稱返十,您可以在自定義類上使用@JsonTypeName妥泉,或者依賴默認(rèn)策略來(lái)使用類的簡(jiǎn)單名稱。請(qǐng)注意洞坑,生產(chǎn)者和消費(fèi)者都需要訪問(wèn)類定義盲链。
在自定義包中注冊(cè)事件
如果您不能或不想為自定義事件使用org.springframework.cloud.bus.event的子包,則必須使用@RemoteApplicationEventScan指定要掃描類型為RemoteApplicationEvent的事件的包迟杂。使用@RemoteApplicationEventScan指定的軟件包包括子包刽沾。
例如,如果您有一個(gè)名為FooEvent的自定義事件:
package com.acme;
public class FooEvent extends RemoteApplicationEvent {
...
}
您可以通過(guò)以下方式與解串器注冊(cè)此事件:
package com.acme;
@Configuration
@RemoteApplicationEventScan
public class BusConfiguration {
...
}
沒(méi)有指定一個(gè)值排拷,使用@RemoteApplicationEventScan的類的包將被注冊(cè)侧漓。在這個(gè)例子中,com.acme將使用BusConfiguration的包進(jìn)行注冊(cè)监氢。
您還可以使用@RemoteApplicationEventScan上的value布蔗,basePackages或basePackageClasses屬性明確指定要掃描的軟件包藤违。例如:
package com.acme;
@Configuration
//@RemoteApplicationEventScan({"com.acme", "foo.bar"})
//@RemoteApplicationEventScan(basePackages = {"com.acme", "foo.bar", "fizz.buzz"})
@RemoteApplicationEventScan(basePackageClasses = BusConfiguration.class)
public class BusConfiguration {
...
}
以上@RemoteApplicationEventScan的所有示例都是等效的,因?yàn)閏om.acme程序包將通過(guò)在@RemoteApplicationEventScan上明確指定程序包來(lái)注冊(cè)纵揍。請(qǐng)注意顿乒,您可以指定要掃描的多個(gè)基本軟件包。
Spring Cloud Sleuth
Adrian Cole泽谨,Spencer Gibb璧榄,Marcin Grzejszczak,Dave Syer
Dalston.RELEASE
Spring Cloud Sleuth為Spring Cloud實(shí)現(xiàn)分布式跟蹤解決方案隔盛。
術(shù)語(yǔ)
Spring Cloud Sleuth借用了Dapper的術(shù)語(yǔ)犹菱。
Span:工作的基本單位例如,發(fā)送RPC是一個(gè)新的跨度吮炕,以及向RPC發(fā)送響應(yīng)腊脱。Span由跨度的唯一64位ID標(biāo)識(shí),跨度是其中一部分的跟蹤的另一個(gè)64位ID龙亲∩掳迹跨度還具有其他數(shù)據(jù),例如描述鳄炉,時(shí)間戳記事件杜耙,鍵值注釋(標(biāo)簽),導(dǎo)致它們的跨度的ID以及進(jìn)程ID(通常是IP地址)拂盯。
跨距開(kāi)始和停止佑女,他們跟蹤他們的時(shí)間信息。創(chuàng)建跨度后谈竿,必須在將來(lái)的某個(gè)時(shí)刻停止团驱。
小費(fèi)
啟動(dòng)跟蹤的初始范圍稱為root span。該跨度的跨度id的值等于跟蹤ID空凸。
跟蹤:一組spans形成樹(shù)狀結(jié)構(gòu)耕挨。例如我注,如果您正在運(yùn)行分布式大數(shù)據(jù)存儲(chǔ)书闸,則可能會(huì)由put請(qǐng)求形成跟蹤啡捶。
注釋:用于及時(shí)記錄事件的存在。用于定義請(qǐng)求的開(kāi)始和停止的一些核心注釋是:
cs- 客戶端發(fā)送 - 客戶端已經(jīng)發(fā)出請(qǐng)求道逗。此注釋描繪了跨度的開(kāi)始兵罢。
sr- 服務(wù)器接收 - 服務(wù)器端得到請(qǐng)求,并將開(kāi)始處理它滓窍。如果從此時(shí)間戳中減去cs時(shí)間戳卖词,則會(huì)收到網(wǎng)絡(luò)延遲。
ss- 服務(wù)器發(fā)送 - 在完成請(qǐng)求處理后(響應(yīng)發(fā)送回客戶端時(shí))注釋贰您。如果從此時(shí)間戳中減去sr時(shí)間戳坏平,則會(huì)收到服務(wù)器端處理請(qǐng)求所需的時(shí)間。
cr- 客戶端接收 - 表示跨度的結(jié)束锦亦〔疤妫客戶端已成功接收到服務(wù)器端的響應(yīng)。如果從此時(shí)間戳中減去cs時(shí)間戳杠园,則會(huì)收到客戶端從服務(wù)器接收響應(yīng)所需的整個(gè)時(shí)間顾瞪。
可視化Span和Trace將與Zipkin注釋一起查看系統(tǒng):
一個(gè)音符的每個(gè)顏色表示跨度(7 spans - 從A到G)。如果您在筆記中有這樣的信息:
Trace Id = X
Span Id = D
Client Sent
這意味著抛蚁,當(dāng)前的跨度痕量-ID設(shè)置為X陈醒,Span -編號(hào)設(shè)置為e。它也發(fā)出了客戶端發(fā)送的事件瞧甩。
這樣钉跷,spans的父/子關(guān)系的可視化將如下所示:
目的
在以下部分中,將考慮上述圖像中的示例肚逸。
分布式跟蹤與Zipkin
共有7個(gè)spans爷辙。如果您在Zipkin中查看痕跡,您將在第二個(gè)曲目中看到這個(gè)數(shù)字:
但是朦促,如果您選擇特定的跟蹤膝晾,那么您將看到4 spans:
注意
當(dāng)選擇特定的跟蹤時(shí),您將看到合并的spans务冕。這意味著如果發(fā)送到服務(wù)器接收和服務(wù)器發(fā)送/接收客戶端和客戶端發(fā)送注釋的Zipkin有2個(gè)spans血当,那么它們將被顯示為一個(gè)跨度。
為什么在這種情況下禀忆,7和4 spans之間有區(qū)別臊旭?
2 spans來(lái)自http:/start范圍。它具有服務(wù)器接收(SR)和服務(wù)器發(fā)送(SS)注釋油湖。
2 spans來(lái)自service1到service2到http:/foo端點(diǎn)的RPC呼叫巍扛。它在service1方面具有客戶端發(fā)送(CS)和客戶端接收(CR)注釋。它還在service2方面具有服務(wù)器接收(SR)和服務(wù)器發(fā)送(SS)注釋乏德。在物理上有2個(gè)spans撤奸,但它們形成與RPC調(diào)用相關(guān)的1個(gè)邏輯跨度。
2 spans來(lái)自service2到service3到http:/bar端點(diǎn)的RPC呼叫喊括。它在service2方面具有客戶端發(fā)送(CS)和客戶接收(CR)注釋胧瓜。它還具有service3端的服務(wù)器接收(SR)和服務(wù)器發(fā)送(SS)注釋。在物理上有2個(gè)spans郑什,但它們形成與RPC調(diào)用相關(guān)的1個(gè)邏輯跨度府喳。
2 spans來(lái)自service2到service4到http:/baz端點(diǎn)的RPC呼叫。它在service2方面具有客戶端發(fā)送(CS)和客戶接收(CR)注釋蘑拯。它還在service4側(cè)具有服務(wù)器接收(SR)和服務(wù)器發(fā)送(SS)注釋钝满。在物理上有2個(gè)spans脉让,但它們形成與RPC調(diào)用相關(guān)的1個(gè)邏輯跨度柬帕。
因此,如果我們計(jì)算spans,http:/start中有1個(gè)來(lái)自service1的呼叫service2借浊,2(service2)呼叫service3和2(service2)service4唐础。共7個(gè)spans彤枢。
邏輯上惨篱,我們看到Total Spans的信息:4,因?yàn)槲覀冇?b>1個(gè)跨度與傳入請(qǐng)求相關(guān)的service1和3spans與RPC調(diào)用相關(guān)收厨。
可視化錯(cuò)誤
Zipkin允許您可視化跟蹤中的錯(cuò)誤晋柱。當(dāng)異常被拋出并且沒(méi)有被捕獲時(shí),我們?cè)赯ipkin可以正確著色的跨度上設(shè)置適當(dāng)?shù)臉?biāo)簽诵叁。您可以在痕跡列表中看到一條是紅色的痕跡雁竞。這是因?yàn)閽伋隽艘粋€(gè)異常。
如果您點(diǎn)擊該軌跡拧额,您將看到類似的圖片
然后浓领,如果您點(diǎn)擊其中一個(gè)spans,您將看到以下內(nèi)容
你可以看到势腮,你可以很容易的看到錯(cuò)誤的原因和整個(gè)stacktrace相關(guān)的联贩。
實(shí)例
點(diǎn)擊Pivotal Web Services圖標(biāo)即可實(shí)時(shí)查看!點(diǎn)擊Pivotal Web Services圖標(biāo)直播捎拯!
Zipkin中的依賴圖將如下所示:
點(diǎn)擊Pivotal Web Services圖標(biāo)即可實(shí)時(shí)查看泪幌!點(diǎn)擊Pivotal Web Services圖標(biāo)直播!
對(duì)數(shù)相關(guān)
當(dāng)通過(guò)跟蹤id等于例如2485ec27856c56f4來(lái)對(duì)這四個(gè)應(yīng)用程序的日志進(jìn)行灰名單時(shí)署照,將會(huì)得到以下內(nèi)容:
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]]
如果你使用像一個(gè)日志聚合工具Kibana祸泪,Splunk的等您可以訂購(gòu)所發(fā)生的事件〗ㄜ剑基巴納的例子如下所示:
如果你想使用Logstash没隘,這里是Logstash的Grok模式:
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}" }
}
}
注意
如果您想將Grok與Cloud Foundry的日志一起使用,則必須使用此模式:
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}" }
}
}
使用Logstash進(jìn)行JSON回溯
通常禁荸,您不希望將日志存儲(chǔ)在文本文件中右蒲,而不是將Logstash可以立即選擇的JSON文件中存儲(chǔ)。為此赶熟,您必須執(zhí)行以下操作(為了可讀性瑰妄,我們將依賴關(guān)系傳遞給groupId:artifactId:version符號(hào)。
依賴關(guān)系設(shè)置
確保Logback位于類路徑(ch.qos.logback:logback-core)
添加Logstash Logback編碼 - 版本4.6的示例:net.logstash.logback:logstash-logback-encoder:4.6
回讀設(shè)置
您可以在下面找到一個(gè)Logback配置(名為logback-spring.xml)的示例:
將來(lái)自應(yīng)用程序的信息以JSON格式記錄到build/${spring.application.name}.json文件
已經(jīng)評(píng)論了兩個(gè)額外的追加者 - 控制臺(tái)和標(biāo)準(zhǔn)日志文件
具有與上一節(jié)所述相同的記錄模式
?
?
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}"/>
DEBUG
${CONSOLE_LOG_PATTERN}
utf8
?
${LOG_FILE}
${LOG_FILE}.%d{yyyy-MM-dd}.gz
7
${CONSOLE_LOG_PATTERN}
utf8
?
${LOG_FILE}.json
${LOG_FILE}.json.%d{yyyy-MM-dd}.gz
7
UTC
{
"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"
}
?
注意
如果您使用自定義logback-spring.xml映砖,則必須通過(guò)bootstrapapplication而不是application屬性文件傳遞spring.application.name间坐。否則您的自定義logback文件將不會(huì)正確讀取該屬性。
傳播Span上下文
跨度上下文是必須傳播到任何子進(jìn)程跨越進(jìn)程邊界的狀態(tài)。Span背景的一部分是行李竹宋。跟蹤和跨度ID是跨度上下文的必需部分劳澄。行李是可選的部分。
行李是一組密鑰:存儲(chǔ)在范圍上下文中的值對(duì)蜈七。行李與痕跡一起旅行浴骂,并附在每一個(gè)跨度上。Spring Cloud如果HTTP標(biāo)頭以baggage-為前綴宪潮,并且以baggage_開(kāi)頭的消息傳遞,Sleuth將會(huì)明白標(biāo)題是行李相關(guān)的趣苏。
重要
行李物品的數(shù)量或大小目前沒(méi)有限制狡相。但是,請(qǐng)記住食磕,太多可能會(huì)降低系統(tǒng)吞吐量或增加RPC延遲尽棕。在極端情況下胃碾,由于超出了傳輸級(jí)消息或報(bào)頭容量女蜈,可能會(huì)使應(yīng)用程序崩潰。
在跨度上設(shè)置行李的示例:
Span initialSpan = this.tracer.createSpan("span");
initialSpan.setBaggageItem("foo", "bar");
行李與Span標(biāo)簽
行李隨行旅行(即每個(gè)孩子跨度都包含其父母的行李)心俗。Zipkin不了解行李单绑,甚至不會(huì)收到這些信息回官。
標(biāo)簽附加到特定的跨度 - 它們僅針對(duì)該特定跨度呈現(xiàn)。但是搂橙,您可以通過(guò)標(biāo)簽搜索查找跟蹤歉提,其中存在具有搜索標(biāo)簽值的跨度。
如果您希望能夠根據(jù)行李查找跨度区转,則應(yīng)在根跨度中添加相應(yīng)的條目作為標(biāo)簽苔巨。
@Autowired Tracer tracer;
Span span = tracer.getCurrentSpan();
String baggageKey = "key";
String baggageValue = "foo";
span.setBaggageItem(baggageKey, baggageValue);
tracer.addTag(baggageKey, baggageValue);
添加到項(xiàng)目中
只有Sleuth(對(duì)數(shù)相關(guān))
如果您只想從Spring Cloud Sleuth中獲利,而沒(méi)有Zipkin集成废离,只需將spring-cloud-starter-sleuth模塊添加到您的項(xiàng)目中即可侄泽。
Maven的
(1)? ? ? ? ? ? ? ? ? ? ? ? ? ? org.springframework.cloud? ? ? ? ? ? ? ? spring-cloud-dependencies? ? ? ? ? ? ? ? Camden.RELEASE? ? ? ? ? ? ? ? pom? ? ? ? ? ? ? ? import? ? ? ? ? ? ? ? ? ? ? ? (2)org.springframework.cloud? ? ? spring-cloud-starter-sleuth?
為了不自己選擇版本,如果您通過(guò)Spring BOM添加依賴關(guān)系管理蜻韭,會(huì)更好
將依賴關(guān)系添加到spring-cloud-starter-sleuth
搖籃
dependencyManagement {(1)imports {? ? ? ? mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE"? ? }}dependencies {(2)compile "org.springframework.cloud:spring-cloud-starter-sleuth"}
為了不自己選擇版本悼尾,如果您通過(guò)Spring BOM添加依賴關(guān)系管理,會(huì)更好
將依賴關(guān)系添加到spring-cloud-starter-sleuth
通過(guò)HTTP訪問(wèn)Zipkin
如果你想要Sleuth和Zipkin只需添加spring-cloud-starter-zipkin依賴關(guān)系肖方。
Maven的
(1)? ? ? ? ? ? ? ? ? ? ? ? ? ? org.springframework.cloud? ? ? ? ? ? ? ? spring-cloud-dependencies? ? ? ? ? ? ? ? Camden.RELEASE? ? ? ? ? ? ? ? pom? ? ? ? ? ? ? ? import? ? ? ? ? ? ? ? ? ? ? ? (2)org.springframework.cloud? ? ? spring-cloud-starter-zipkin?
為了不自己選擇版本诀豁,如果您通過(guò)Spring BOM添加依賴關(guān)系管理,會(huì)更好
將依賴關(guān)系添加到spring-cloud-starter-zipkin
搖籃
dependencyManagement {(1)imports {? ? ? ? mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE"? ? }}dependencies {(2)compile "org.springframework.cloud:spring-cloud-starter-zipkin"}
為了不自己選擇版本窥妇,如果您通過(guò)Spring BOM添加依賴關(guān)系管理舷胜,會(huì)更好
將依賴關(guān)系添加到spring-cloud-starter-zipkin
通過(guò)Spring Cloud Stream使用Zipkin的Sleuth
如果你想要Sleuth和Zipkin只需添加spring-cloud-sleuth-stream依賴關(guān)系。
Maven的
(1)? ? ? ? ? ? ? ? ? ? ? ? ? ? org.springframework.cloud? ? ? ? ? ? ? ? spring-cloud-dependencies? ? ? ? ? ? ? ? Camden.RELEASE? ? ? ? ? ? ? ? pom? ? ? ? ? ? ? ? import? ? ? ? ? ? ? ? ? ? ? ? (2)org.springframework.cloud? ? ? spring-cloud-sleuth-stream? ? (3)org.springframework.cloud? ? ? spring-cloud-starter-sleuth? ? ? (4)org.springframework.cloud? ? ? spring-cloud-stream-binder-rabbit?
為了不自己選擇版本,如果您通過(guò)Spring BOM添加依賴關(guān)系管理烹骨,會(huì)更好
將依賴關(guān)系添加到spring-cloud-sleuth-stream
將依賴關(guān)系添加到spring-cloud-starter-sleuth中翻伺,這樣就可以下載依賴關(guān)系
添加一個(gè)粘合劑(例如Rabbit binder)來(lái)告訴Spring Cloud Stream應(yīng)該綁定什么
搖籃
dependencyManagement {(1)imports {? ? ? ? mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE"? ? }}dependencies {? ? compile "org.springframework.cloud:spring-cloud-sleuth-stream"(2)compile "org.springframework.cloud:spring-cloud-starter-sleuth"(3)// Example for Rabbit binding? ? compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit"(4)}
為了不自己選擇版本,如果您通過(guò)Spring BOM添加依賴關(guān)系管理沮焕,會(huì)更好
將依賴關(guān)系添加到spring-cloud-sleuth-stream
將依賴關(guān)系添加到spring-cloud-starter-sleuth中吨岭,這樣就可以下載所有依賴關(guān)系
添加一個(gè)粘合劑(例如Rabbit binder)來(lái)告訴Spring Cloud Stream應(yīng)該綁定什么
Spring Cloud Sleuth Stream Zipkin收藏家
如果要啟動(dòng)Spring Cloud Sleuth Stream Zipkin收藏夾,只需添加spring-cloud-sleuth-zipkin-stream依賴關(guān)系即可
Maven的
(1)? ? ? ? ? ? ? ? ? ? ? ? ? ? org.springframework.cloud? ? ? ? ? ? ? ? spring-cloud-dependencies? ? ? ? ? ? ? ? Camden.RELEASE? ? ? ? ? ? ? ? pom? ? ? ? ? ? ? ? import? ? ? ? ? ? ? ? ? ? ? ? (2)org.springframework.cloud? ? ? spring-cloud-sleuth-zipkin-stream? ? (3)org.springframework.cloud? ? ? spring-cloud-starter-sleuth? ? ? (4)org.springframework.cloud? ? ? spring-cloud-stream-binder-rabbit?
為了不自己選擇版本峦树,如果您通過(guò)Spring BOM添加依賴關(guān)系管理辣辫,會(huì)更好
將依賴關(guān)系添加到spring-cloud-sleuth-zipkin-stream
將依賴關(guān)系添加到spring-cloud-starter-sleuth- 這樣一來(lái),所有的依賴依賴將被下載
添加一個(gè)粘合劑(例如Rabbit binder)來(lái)告訴Spring Cloud Stream應(yīng)該綁定什么
搖籃
dependencyManagement {(1)imports {? ? ? ? mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.RELEASE"? ? }}dependencies {? ? compile "org.springframework.cloud:spring-cloud-sleuth-zipkin-stream"(2)compile "org.springframework.cloud:spring-cloud-starter-sleuth"(3)// Example for Rabbit binding? ? compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit"(4)}
為了不自己選擇版本魁巩,如果您通過(guò)Spring BOM添加依賴關(guān)系管理急灭,會(huì)更好
將依賴關(guān)系添加到spring-cloud-sleuth-zipkin-stream
將依賴關(guān)系添加到spring-cloud-starter-sleuth- 這樣將依賴關(guān)系依賴下載
添加一個(gè)粘合劑(例如Rabbit binder)來(lái)告訴Spring Cloud Stream應(yīng)該綁定什么
然后使用@EnableZipkinStreamServer注釋注釋你的主類:
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);
}
}
額外的資源
Marcin Grzejszczak談?wù)揝pring Cloud Sleuth和Zipkin
特征
將跟蹤和跨度添加到Slf4J MDC,以便您可以從日志聚合器中的給定跟蹤或跨度中提取所有日志谷遂。示例日志:
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] ...
注意MDC中的[appname,traceId,spanId,exportable]條目:
spanId- 發(fā)生特定操作的ID
appname- 記錄跨度的應(yīng)用程序的名稱
traceId- 包含跨度的延遲圖的ID
導(dǎo)出-日志是否應(yīng)該被導(dǎo)出到Zipkin與否葬馋。你什么時(shí)候希望跨度不能出口?在這種情況下肾扰,你想在Span中包裝一些操作畴嘶,并將它寫入日志。
提供對(duì)共同分布式跟蹤數(shù)據(jù)模型的抽象:trace集晚,spans(形成DAG)窗悯,注釋,鍵值注釋偷拔。松散地基于HTrace蟀瞧,但Zipkin(Dapper)兼容。
Sleuth記錄定時(shí)信息以輔助延遲分析条摸。使用竊賊悦污,您可以精確定位應(yīng)用程序中的延遲原因。Sleuth被寫入不會(huì)記錄太多钉蒲,并且不會(huì)導(dǎo)致您的生產(chǎn)應(yīng)用程序崩潰切端。
傳播有關(guān)您的呼叫圖表帶內(nèi)的結(jié)構(gòu)數(shù)據(jù),其余的是帶外。
包括有意見(jiàn)的層次測(cè)試,如HTTP
包括采樣策略來(lái)管理卷
可以向Zipkin系統(tǒng)報(bào)告查詢和可視化
儀器來(lái)自Spring應(yīng)用程序(servlet過(guò)濾器孽糖,異步終結(jié)點(diǎn),休息模板茵瀑,調(diào)度操作,消息通道躬厌,zuul過(guò)濾器马昨,假客戶端)的通用入口和出口點(diǎn)。
Sleuth包括在http或消息傳遞邊界上加入跟蹤的默認(rèn)邏輯。例如鸿捧,http傳播通過(guò)Zipkin兼容的請(qǐng)求標(biāo)頭工作屹篓。該傳播邏輯是通過(guò)SpanInjector和SpanExtractor實(shí)現(xiàn)來(lái)定義和定制的。
Sleuth可以在進(jìn)程之間傳播上下文(也稱為行李)匙奴。這意味著如果您設(shè)置了Span行李元素堆巧,那么它將通過(guò)HTTP或消息傳遞到其他進(jìn)程發(fā)送到下游。
提供創(chuàng)建/繼續(xù)spans并通過(guò)注釋添加標(biāo)簽和日志的方法泼菌。
提供接受/刪除spans的簡(jiǎn)單指標(biāo)谍肤。
如果spring-cloud-sleuth-zipkin,則應(yīng)用程序?qū)⑸刹⑹占痁ipkin兼容的跟蹤哗伯。默認(rèn)情況下荒揣,它通過(guò)HTTP將其發(fā)送到localhost上的Zipkin服務(wù)器(端口9411)。使用spring.zipkin.baseUrl配置服務(wù)的位置笋颤。
如果spring-cloud-sleuth-stream,則該應(yīng)用將通過(guò)Spring Cloud Stream生成和收集跟蹤内地。您的應(yīng)用程序自動(dòng)成為通過(guò)您的代理商發(fā)送的跟蹤消息的生產(chǎn)者(例如RabbitMQ伴澄,Apache Kafka,Redis))阱缓。
重要
如果使用Zipkin或Stream非凌,請(qǐng)使用spring.sleuth.sampler.percentage(默認(rèn)0.1,即10%)配置spans的百分比荆针。否則你可能認(rèn)為Sleuth不工作敞嗡,因?yàn)樗÷粤艘恍﹕pans。
注意
始終設(shè)置SLF4J MDC航背,并且Logback用戶將立即按照上述示例查看日志中的跟蹤和跨度ID喉悴。其他日志記錄系統(tǒng)必須配置自己的格式化程序以獲得相同的結(jié)果。默認(rèn)值為logging.pattern.level設(shè)置為%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}](這是回訪用戶的Spring Boot功能)玖媚。這意味著如果您不使用SLF4J箕肃,則不會(huì)自動(dòng)應(yīng)用此模式。
采樣
在分布式跟蹤中今魔,數(shù)據(jù)量可能非常高勺像,因此采樣可能很重要(您通常不需要導(dǎo)出所有spans以獲得正在發(fā)生的情況)。Spring Cloud Sleuth具有Sampler策略错森,您可以實(shí)現(xiàn)該策略來(lái)控制采樣算法吟宦。采樣器不會(huì)停止生成跨度(相關(guān))ids,但是它們確實(shí)阻止了附加和導(dǎo)出的標(biāo)簽和事件涩维。默認(rèn)情況下殃姓,您將獲得一個(gè)策略,如果跨度已經(jīng)處于活動(dòng)狀態(tài),則會(huì)繼續(xù)跟蹤辰狡,但新策略始終被標(biāo)記為不可導(dǎo)出锋叨。如果您的所有應(yīng)用程序都使用此采樣器運(yùn)行,您將看到日志中的跟蹤宛篇,但不會(huì)在任何遠(yuǎn)程存儲(chǔ)中娃磺。對(duì)于測(cè)試,默認(rèn)值通常是足夠的叫倍,如果您僅使用日志(例如使用ELK聚合器)偷卧,則可能是您需要的。如果要將span數(shù)據(jù)導(dǎo)出到Zipkin或Spring Cloud Stream吆倦,則還有一個(gè)AlwaysSampler導(dǎo)出所有內(nèi)容忍啸,并且PercentageBasedSampler對(duì)spans的固定分?jǐn)?shù)進(jìn)行采樣芽死。
注意
如果您使用spring-cloud-sleuth-zipkin或spring-cloud-sleuth-stream,則默認(rèn)為PercentageBasedSampler。您可以使用spring.sleuth.sampler.percentage配置導(dǎo)出眷蚓。通過(guò)的價(jià)值需要從0.0到1.0的雙倍,所以不是百分比菇篡。為了向后兼容性原因煞额,我們不會(huì)更改屬性名稱。
可以通過(guò)創(chuàng)建一個(gè)bean定義來(lái)安裝采樣器荒吏,例如:
@Bean
public Sampler defaultSampler() {
return new AlwaysSampler();
}
小費(fèi)
您可以將HTTP標(biāo)頭X-B3-Flags設(shè)置為1敛惊,或者在進(jìn)行消息傳遞時(shí),您可以將spanFlags標(biāo)題設(shè)置為1绰更。然后瞧挤,無(wú)論采樣決定如何,當(dāng)前跨度都將被強(qiáng)制輸出儡湾。
儀表
Spring Cloud Sleuth自動(dòng)為您的所有Spring應(yīng)用程序設(shè)備特恬,因此您不必執(zhí)行任何操作即可激活它。根據(jù)可用的堆棧徐钠,例如對(duì)于我們使用Filter的servlet web應(yīng)用程序鸵鸥,以及Spring Integration我們使用ChannelInterceptors),可以使用各種技術(shù)添加儀器丹皱。
您可以自定義span標(biāo)簽中使用的鍵妒穴。為了限制跨度數(shù)據(jù)量,默認(rèn)情況下摊崭,HTTP請(qǐng)求只會(huì)被標(biāo)記為少量的元數(shù)據(jù)讼油,如狀態(tài)碼,主機(jī)和URL呢簸。您可以通過(guò)配置spring.sleuth.keys.http.headers(頭名稱列表)來(lái)添加請(qǐng)求頭矮台。
注意
記住乏屯,如果有一個(gè)Sampler允許它(默認(rèn)情況下沒(méi)有,所以沒(méi)有意外收集太多數(shù)據(jù)而沒(méi)有配置東西的危險(xiǎn))瘦赫,那么只會(huì)收集和導(dǎo)出標(biāo)簽辰晕。
注意
目前,Spring Cloud Sleuth中的測(cè)試儀器是渴望的 - 這意味著我們正在積極地嘗試在線程之間傳遞跟蹤上下文确虱。即使在沒(méi)有將數(shù)據(jù)導(dǎo)出到跟蹤系統(tǒng)的情況下含友,也會(huì)捕獲定時(shí)事件。這種做法在將來(lái)可能會(huì)改變?yōu)樵谶@件事上懶惰校辩。
Span生命周期
您可以通過(guò)org.springframework.cloud.sleuth.Tracer接口在Span上執(zhí)行以下操作:
開(kāi)始- 當(dāng)您啟動(dòng)一個(gè)span時(shí)窘问,它的名稱被分配,并且記錄開(kāi)始時(shí)間戳宜咒。
關(guān)閉- 跨度完成(記錄跨度的結(jié)束時(shí)間)惠赫,如果跨度可導(dǎo)出,則它將有資格收集到Zipkin故黑。該跨度也從當(dāng)前線程中移除儿咱。
繼續(xù)- 將創(chuàng)建一個(gè)新的跨度實(shí)例,而它將是它繼續(xù)的一個(gè)副本场晶。
分離- 跨度不會(huì)停止或關(guān)閉混埠。它只從當(dāng)前線程中刪除。
使用顯式父項(xiàng)創(chuàng)建- 您可以創(chuàng)建一個(gè)新的跨度峰搪,并為其設(shè)置一個(gè)顯式父級(jí)
小費(fèi)
Spring為您創(chuàng)建了一個(gè)Tracer的實(shí)例岔冀。為了使用它凯旭,你需要的只是自動(dòng)連接它概耻。
創(chuàng)建和關(guān)閉spans
您可以使用Tracer界面手動(dòng)創(chuàng)建spans。
// 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);
}
在這個(gè)例子中罐呼,我們可以看到如何創(chuàng)建一個(gè)新的跨度實(shí)例鞠柄。假設(shè)這個(gè)線程中已經(jīng)存在跨度乾巧,那么它將成為該跨度的父代卤唉。
重要
創(chuàng)建跨度后始終清潔!如果要將其發(fā)送到Zipkin霹崎,請(qǐng)不要忘記關(guān)閉跨度计螺。
重要
如果您的span包含的名稱大于50個(gè)字符夯尽,則該名稱將被截?cái)酁?0個(gè)字符。你的名字必須是明確而具體的登馒。大名稱導(dǎo)致延遲問(wèn)題匙握,有時(shí)甚至引發(fā)異常。
繼續(xù)spans
有時(shí)你不想創(chuàng)建一個(gè)新的跨度陈轿,但你想繼續(xù)圈纺。這種情況的例子可能是(當(dāng)然這取決于用例):
AOP- 如果在達(dá)到方面之前已經(jīng)創(chuàng)建了一個(gè)跨度秦忿,則可能不想創(chuàng)建一個(gè)新的跨度。
Hystrix- 執(zhí)行Hystrix命令很可能是當(dāng)前處理的邏輯部分蛾娶。實(shí)際上灯谣,它只是一個(gè)技術(shù)實(shí)現(xiàn)細(xì)節(jié),你不一定要反映在跟蹤中作為一個(gè)單獨(dú)的存在蛔琅。
持續(xù)的跨度實(shí)例等于它繼續(xù)的范圍:
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
assertThat(continuedSpan).isEqualTo(spanToContinue);
要繼續(xù)跨度胎许,您可以使用Tracer界面。
// 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);
}
重要
創(chuàng)建跨度后始終清潔揍愁!如果在一個(gè)線程(例如線程X)中開(kāi)始了某些工作呐萨,并且正在等待其他線程(例如Y,Z)完成莽囤,請(qǐng)不要忘記分離跨距谬擦。那么線程Y,Z中的spans在工作結(jié)束時(shí)應(yīng)該被分離朽缎。當(dāng)收集結(jié)果時(shí)惨远,螺紋X中的跨度應(yīng)該被關(guān)閉。
用明確的父代創(chuàng)建spans
您可能想要開(kāi)始一個(gè)新的跨度话肖,并提供該跨度的顯式父級(jí)北秽。假設(shè)跨度的父項(xiàng)在一個(gè)線程中,并且要在另一個(gè)線程中啟動(dòng)一個(gè)新的跨度最筒。Tracer接口的startSpan方法是您要查找的方法贺氓。
// 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);
}
重要
創(chuàng)建這樣一個(gè)跨度后,記得關(guān)閉它床蜘。否則辙培,您將在您的日志中看到很多警告,其中有一個(gè)事實(shí)邢锯,即您在當(dāng)前線程中存在一個(gè)跨度扬蕊,而不是您要關(guān)閉的線程。更糟糕的是丹擎,您的spans不會(huì)正確關(guān)閉尾抑,因此不會(huì)收集到Zipkin。
命名spans
選擇一個(gè)跨度名稱不是一件小事蒂培。Span名稱應(yīng)該描述一個(gè)操作名稱再愈。名稱應(yīng)該是低基數(shù)(例如不包括標(biāo)識(shí)符)。
由于有很多儀器儀表在一些跨度名稱將是人為的:
controller-method-name當(dāng)控制器以方法名conrollerMethodName接收時(shí)
async通過(guò)包裝Callable和Runnable完成異步操作护戳。
@Scheduled注釋方法將返回類的簡(jiǎn)單名稱翎冲。
幸運(yùn)的是,對(duì)于異步處理灸异,您可以提供明確的命名府适。
@SpanName注釋
您可以通過(guò)@SpanName注釋顯式指定該跨度羔飞。
@SpanName("calculateTax")
class TaxCountingRunnable implements Runnable {
@Override public void run() {
// perform logic
}
}
在這種情況下,以下列方式處理時(shí):
Runnable runnable = new TraceRunnable(tracer, spanNamer, new TaxCountingRunnable());
Future future = executorService.submit(runnable);
// ... some additional logic ...
future.get();
該范圍將被命名為calculateTax檐春。
toString()方法
為Runnable或Callable創(chuàng)建單獨(dú)的課程很少見(jiàn)逻淌。通常马澈,創(chuàng)建這些類的匿名實(shí)例套硼。如果沒(méi)有@SpanName注釋,我們將檢查該類是否具有toString()方法的自定義實(shí)現(xiàn)全景。
所以執(zhí)行這樣的代碼:
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();
將導(dǎo)致創(chuàng)建一個(gè)名為calculateTax的跨度俐巴。
管理spans注釋
合理
這個(gè)功能的主要論據(jù)是
api-agnostic意味著與跨度進(jìn)行合作
使用注釋允許用戶添加到跨度api沒(méi)有庫(kù)依賴的跨度骨望。這允許Sleuth將其核心api的影響改變?yōu)閷?duì)用戶代碼的影響較小。
減少基礎(chǔ)跨度作業(yè)的表面積欣舵。
沒(méi)有這個(gè)功能擎鸠,必須使用span api,它具有不正確使用的生命周期命令缘圈。通過(guò)僅顯示范圍劣光,標(biāo)簽和日志功能,用戶可以協(xié)作糟把,而不會(huì)意外中斷跨度生命周期绢涡。
與運(yùn)行時(shí)生成的代碼協(xié)作
使用諸如Spring Data / Feign的庫(kù),在運(yùn)行時(shí)生成接口的實(shí)現(xiàn)遣疯,從而跨越對(duì)象的包裝是乏味的⌒劭桑現(xiàn)在,您可以通過(guò)這些接口的接口和參數(shù)提供注釋
創(chuàng)建新的spans
如果您真的不想手動(dòng)創(chuàng)建本地spans缠犀,您可以從@NewSpan注釋中獲利数苫。此外,我們還提供@SpanTag注釋夭坪,以自動(dòng)方式添加標(biāo)簽文判。
我們來(lái)看一些使用的例子过椎。
@NewSpan
void testMethod();
注釋沒(méi)有任何參數(shù)的方法將導(dǎo)致創(chuàng)建名稱將等于注釋方法名稱的新跨度室梅。
@NewSpan("customNameOnTestMethod4")
void testMethod4();
如果您在注釋中提供值(直接或通過(guò)name參數(shù)),則創(chuàng)建的范圍將具有提供的值的名稱疚宇。
// method declaration
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);
// and method execution
this.testBean.testMethod5("test");
您可以組合名稱和標(biāo)簽亡鼠。我們來(lái)關(guān)注后者。在這種情況下敷待,無(wú)論注釋方法的參數(shù)運(yùn)行時(shí)值的值如何 - 這將是標(biāo)記的值间涵。在我們的示例中,標(biāo)簽密鑰將為testTag榜揖,標(biāo)簽值為test勾哩。
@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}
您可以將@NewSpan注釋放在類和接口上抗蠢。如果覆蓋接口的方法并提供不同的@NewSpan注釋值,則最具體的一個(gè)獲勝(在這種情況下customNameOnTestMethod3將被設(shè)置)思劳。
繼續(xù)spans
如果您只想添加標(biāo)簽和注釋到現(xiàn)有的跨度迅矛,就可以使用如下所示的@ContinueSpan注釋。請(qǐng)注意潜叛,與@NewSpan注釋相反秽褒,您還可以通過(guò)log參數(shù)添加日志:
// method declaration
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);
// method execution
this.testBean.testMethod11("test");
這樣,跨越將繼續(xù)下去:
將創(chuàng)建名稱為testMethod11.before和testMethod11.after的日志
如果拋出異常威兜,也將創(chuàng)建一個(gè)日志testMethod11.afterFailure
將創(chuàng)建密鑰testTag11和值test的標(biāo)簽
更高級(jí)的標(biāo)簽設(shè)置
有三種不同的方法可以將標(biāo)簽添加到跨度销斟。所有這些都由SpanTag注釋控制。優(yōu)先級(jí)是:
嘗試使用TagValueResolver類型的bean椒舵,并提供名稱
如果沒(méi)有提供bean名稱蚂踊,請(qǐng)嘗試評(píng)估一個(gè)表達(dá)式。我們正在搜索一個(gè)TagValueExpressionResolverbean笔宿。默認(rèn)實(shí)現(xiàn)使用SPEL表達(dá)式解析悴势。
如果沒(méi)有提供任何表達(dá)式來(lái)評(píng)估只返回參數(shù)的toString()值
自定義提取器
以下方法的標(biāo)簽值將由TagValueResolver接口的實(shí)現(xiàn)來(lái)計(jì)算。其類名必須作為resolver屬性的值傳遞措伐。
有這樣一個(gè)注釋的方法:
@NewSpan
public void getAnnotationForTagValueResolver(@SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
}
和這樣一個(gè)TagValueResolverbean實(shí)現(xiàn)
@Bean(name = "myCustomTagValueResolver")
public TagValueResolver tagValueResolver() {
return parameter -> "Value from myCustomTagValueResolver";
}
將導(dǎo)致標(biāo)簽值的設(shè)置等于Value from myCustomTagValueResolver特纤。
解決表達(dá)式的價(jià)值
有這樣一個(gè)注釋的方法:
@NewSpan
public void getAnnotationForTagValueExpression(@SpanTag(key = "test", expression = "length() + ' characters'") String test) {
}
并且沒(méi)有自定義的TagValueExpressionResolver實(shí)現(xiàn)將導(dǎo)致對(duì)SPEL表達(dá)式的評(píng)估,并且將在span上設(shè)置值為4 characters的標(biāo)簽侥加。如果要使用其他表達(dá)式解析機(jī)制艳狐,您可以創(chuàng)建自己的bean實(shí)現(xiàn)扭倾。
使用toString方法
有這樣一個(gè)注釋的方法:
@NewSpan
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
}
如果使用值為15執(zhí)行,則將導(dǎo)致設(shè)置String值為"15"的標(biāo)記。
自定義
感謝SpanInjector和SpanExtractor佛吓,您可以自定義spans的創(chuàng)建和傳播方式。
目前有兩種在進(jìn)程之間傳遞跟蹤信息的內(nèi)置方式:
通過(guò)Spring Integration
通過(guò)HTTP
Span ids從Zipkin兼容(B3)頭(Message或HTTP頭)中提取崇渗,以啟動(dòng)或加入現(xiàn)有跟蹤旋廷。跟蹤信息被注入到任何出站請(qǐng)求中,所以下一跳可以提取它們狈网。
與以前版本的Sleuth相比宙搬,重要的變化是Sleuth正在實(shí)施Open Tracing的TextMap概念。在Sleuth拓哺,它被稱為SpanTextMap勇垛。基本上這個(gè)想法是通過(guò)SpanTextMap可以抽象出任何通信手段(例如消息士鸥,http請(qǐng)求等)闲孤。這個(gè)抽象定義了如何將數(shù)據(jù)插入到載體中以及如何從那里檢索數(shù)據(jù)。感謝這樣烤礁,如果您想要使用一個(gè)使用FooRequest作為發(fā)送HTTP請(qǐng)求的平均值的新HTTP庫(kù)讼积,那么您必須創(chuàng)建一個(gè)SpanTextMap的實(shí)現(xiàn)肥照,它將調(diào)用委托給FooRequest檢索和插入HTTP標(biāo)頭。
Spring Integration
對(duì)于Spring Integration勤众,有2個(gè)接口負(fù)責(zé)從Message創(chuàng)建Span建峭。這些是:
MessagingSpanTextMapExtractor
MessagingSpanTextMapInjector
您可以通過(guò)提供自己的實(shí)現(xiàn)來(lái)覆蓋它們。
HTTP
對(duì)于HTTP决摧,有2個(gè)接口負(fù)責(zé)從Message創(chuàng)建Span亿蒸。這些是:
HttpSpanExtractor
HttpSpanInjector
您可以通過(guò)提供自己的實(shí)現(xiàn)來(lái)覆蓋它們。
例
我們假設(shè)掌桩,而不是標(biāo)準(zhǔn)的Zipkin兼容的跟蹤HTTP頭名稱
for trace id -correlationId
for span id -mySpanId
這是SpanExtractor的一個(gè)例子
static class CustomHttpSpanExtractor implements HttpSpanExtractor {
@Override public Span joinTrace(SpanTextMap carrier) {
Map 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()));
}
}
你可以這樣注冊(cè):
@Bean
HttpSpanInjector customHttpSpanInjector() {
return new CustomHttpSpanInjector();
}
@Bean
HttpSpanExtractor customHttpSpanExtractor() {
return new CustomHttpSpanExtractor();
}
Spring Cloud為了安全起見(jiàn)边锁,Sleuth不會(huì)將跟蹤/跨度相關(guān)的標(biāo)頭添加到Http響應(yīng)。如果您需要標(biāo)題波岛,那么將標(biāo)題注入Http響應(yīng)的自定義SpanInjector茅坛,并且可以使用以下方式添加一個(gè)使用此標(biāo)簽的Servlet過(guò)濾器:
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> iterator() {
Map 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);
}
}
}
你可以這樣注冊(cè):
@Bean HttpSpanInjector customHttpServletResponseSpanInjector() {
return new CustomHttpServletResponseSpanInjector();
}
@Bean
HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) {
return new HttpResponseInjectingTraceFilter(tracer, customHttpServletResponseSpanInjector());
}
Zipkin中的自定義SA標(biāo)簽
有時(shí)你想創(chuàng)建一個(gè)手動(dòng)Span,將一個(gè)電話包裹到一個(gè)沒(méi)有被檢測(cè)的外部服務(wù)则拷。您可以做的是創(chuàng)建一個(gè)帶有peer.service標(biāo)簽的跨度贡蓖,其中包含要調(diào)用的服務(wù)的值。下面你可以看到一個(gè)調(diào)用Redis的例子煌茬,它被包裝在這樣一個(gè)跨度里斥铺。
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);
}
重要
記住不要添加peer.service標(biāo)簽和SA標(biāo)簽!您只需添加peer.service坛善。
自定義服務(wù)名稱
默認(rèn)情況下晾蜘,Sleuth假設(shè)當(dāng)您將跨度發(fā)送到Zipkin時(shí),您希望跨度的服務(wù)名稱等于spring.application.name值眠屎。這并不總是這樣剔交。在某些情況下,您希望為您應(yīng)用程序中的所有spans提供不同的服務(wù)名稱改衩。要實(shí)現(xiàn)這一點(diǎn)岖常,只需將以下屬性傳遞給應(yīng)用程序即可覆蓋該值(foo服務(wù)名稱的示例):
spring.zipkin.service.name: foo
主機(jī)定位器
為了定義與特定跨度對(duì)應(yīng)的主機(jī),我們需要解析主機(jī)名和端口葫督。默認(rèn)方法是從服務(wù)器屬性中獲取它竭鞍。如果由于某些原因沒(méi)有設(shè)置,那么我們正在嘗試從網(wǎng)絡(luò)接口檢索主機(jī)名候衍。
如果您啟用了發(fā)現(xiàn)客戶端笼蛛,并且更愿意從服務(wù)注冊(cè)表中注冊(cè)的實(shí)例檢索主機(jī)地址洒放,那么您必須設(shè)置屬性(適用于基于HTTP和Stream的跨度報(bào)告)蛉鹿。
spring.zipkin.locator.discovery.enabled: true
Span Data作為消息
您可以通過(guò)將spring-cloud-sleuth-streamjar作為依賴關(guān)系來(lái)累加并發(fā)送跨越Spring Cloud Stream的數(shù)據(jù),并為RabbitMQ或spring-cloud-starter-stream-kafka添加通道Binder實(shí)現(xiàn)(例如spring-cloud-starter-stream-rabbit)為Kafka)它褪。通過(guò)將spring-cloud-sleuth-streamjar作為依賴關(guān)系瘩例,并添加RabbitMQ或spring-cloud-starter-stream-kafka的Binder通道spring-cloud-starter-stream-rabbit來(lái)實(shí)現(xiàn){22 /} Stream的累積和發(fā)送范圍數(shù)據(jù)。Kafka)贫奠。這將自動(dòng)將您的應(yīng)用程序轉(zhuǎn)換為有效載荷類型為Spans的郵件的制作者他膳。
Zipkin消費(fèi)者
有一個(gè)特殊的便利注釋响逢,用于為Span數(shù)據(jù)設(shè)置消息使用者,并將其推入ZipkinSpanStore棕孙。這個(gè)應(yīng)用程序
@SpringBootApplication
@EnableZipkinStreamServer
public class Consumer {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
將通過(guò)Spring Cloud StreamBinder(例如RabbitMQ包含spring-cloud-starter-stream-rabbit)來(lái)收聽(tīng)您提供的任何運(yùn)輸?shù)腟pan數(shù)據(jù)舔亭,Redis和Kafka的類似起始者) 。如果添加以下UI依賴關(guān)系
io.zipkin.java
zipkin-autoconfigure-ui
然后蟀俊,您將有一個(gè)Zipkin服務(wù)器钦铺,您的應(yīng)用程序在端口9411上承載UI和API。
默認(rèn)SpanStore是內(nèi)存中的(適合演示肢预,快速入門)矛洞。對(duì)于更強(qiáng)大的解決方案,您可以將MySQL和spring-boot-starter-jdbc添加到類路徑中烫映,并通過(guò)配置啟用JDBCSpanStore沼本,例如:
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
注意
@EnableZipkinStreamServer還用@EnableZipkinServer注釋,因此該過(guò)程還將公開(kāi)標(biāo)準(zhǔn)的Zipkin服務(wù)器端點(diǎn)锭沟,以通過(guò)HTTP收集spans抽兆,并在Zipkin Web UI中進(jìn)行查詢。
定制消費(fèi)者
也可以使用spring-cloud-sleuth-stream并綁定到SleuthSink來(lái)輕松實(shí)現(xiàn)自定義消費(fèi)者族淮。例:
@EnableBinding(SleuthSink.class)
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@MessageEndpoint
public class Consumer {
@ServiceActivator(inputChannel = SleuthSink.INPUT)
public void sink(Spans input) throws Exception {
// ... process spans
}
}
注意
上面的示例消費(fèi)者應(yīng)用程序明確排除SleuthStreamAutoConfiguration郊丛,因此它不會(huì)向其自己發(fā)送消息,但這是可選的(您可能實(shí)際上想要將消息跟蹤到消費(fèi)者應(yīng)用程序中)瞧筛。
為了自定義輪詢機(jī)制厉熟,您可以創(chuàng)建名稱等于StreamSpanReporter.POLLER的PollerMetadata類型的bean。在這里可以找到這樣一個(gè)配置的例子较幌。
@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;
}
}
度量
目前Spring Cloud Sleuth注冊(cè)了與spans相關(guān)的簡(jiǎn)單指標(biāo)揍瑟。它使用Spring Boot的指標(biāo)支持來(lái)計(jì)算接受和刪除的數(shù)量spans。每次發(fā)送到Zipkin時(shí)乍炉,接受的spans的數(shù)量將增加绢片。如果出現(xiàn)錯(cuò)誤,那么刪除的數(shù)字spans將會(huì)增加岛琼。
集成
可運(yùn)行和可調(diào)用
如果你在Runnable或Callable中包含你的邏輯底循,就可以將這些類包裝在他們的Sleuth代表中。
Runnable的示例:
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);
Callable的示例:
Callable callable = new Callable() {
@Override
public String call() throws Exception {
return someLogic();
}
@Override
public String toString() {
return "spanNameFromToStringMethod";
}
};
// Manual `TraceCallable` creation with explicit "calculateTax" Span name
Callable 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 traceCallableFromTracer = tracer.wrap(callable);
這樣槐瑞,您將確保為每次執(zhí)行創(chuàng)建并關(guān)閉新的Span熙涤。
Hystrix
自定義并發(fā)策略
我們正在注冊(cè)HystrixConcurrencyStrategy將所有Callable實(shí)例包裝到他們的Sleuth代表 -TraceCallable中的定制。該策略是啟動(dòng)或繼續(xù)跨越,這取決于調(diào)用Hystrix命令之前跟蹤是否已經(jīng)進(jìn)行的事實(shí)祠挫。要禁用自定義Hystrix并發(fā)策略那槽,將spring.sleuth.hystrix.strategy.enabled設(shè)置為false。
手動(dòng)命令設(shè)置
假設(shè)您有以下HystrixCommand:
HystrixCommand hystrixCommand = new HystrixCommand(setter) {
@Override
protected String run() throws Exception {
return someLogic();
}
};
為了傳遞跟蹤信息等舔,您必須在HystrixCommand的HystrixCommand的Sleuth版本中包裝相同的邏輯:
TraceCommand traceCommand = new TraceCommand(tracer, traceKeys, setter) {
@Override
public String doRun() throws Exception {
return someLogic();
}
};
RxJava
我們正在注冊(cè)RxJavaSchedulersHook將所有Action0實(shí)例包裝到他們的Sleuth代表 -TraceAction中的定制骚灸。鉤子起動(dòng)或繼續(xù)一個(gè)跨度取決于跟蹤在Action被安排之前是否已經(jīng)進(jìn)行的事實(shí)。要禁用自定義RxJavaSchedulersHook慌植,將spring.sleuth.rxjava.schedulers.hook.enabled設(shè)置為false甚牲。
您可以定義線程名稱的正則表達(dá)式列表,您不希望創(chuàng)建一個(gè)Span蝶柿。只需在spring.sleuth.rxjava.schedulers.ignoredthreads屬性中提供逗號(hào)分隔的正則表達(dá)式列表鳖藕。
HTTP集成
可以通過(guò)提供值等于false的spring.sleuth.web.enabled屬性來(lái)禁用此部分的功能。
HTTP過(guò)濾器
通過(guò)TraceFilter所有采樣的進(jìn)入請(qǐng)求導(dǎo)致創(chuàng)建Span只锭。Span的名稱是http:+發(fā)送請(qǐng)求的路徑著恩。例如,如果請(qǐng)求已發(fā)送到/foo/bar蜻展,則該名稱將為http:/foo/bar喉誊。您可以通過(guò)spring.sleuth.web.skipPattern屬性配置要跳過(guò)的URI撒轮。如果您在類路徑上有ManagementServerProperties胁编,則其值contextPath將附加到提供的跳過(guò)模式拒炎。
的HandlerInterceptor
由于我們希望跨度名稱是精確的雕憔,我們使用的TraceHandlerInterceptor包裝現(xiàn)有的HandlerInterceptor,或直接添加到現(xiàn)有的HandlerInterceptors列表中廉赔。TraceHandlerInterceptor向給定的HttpServletRequest添加了一個(gè)特殊請(qǐng)求屬性础芍。如果TraceFilter沒(méi)有看到此屬性集低矮,它將創(chuàng)建一個(gè)“后備”跨度汉额,這是在服務(wù)器端創(chuàng)建的一個(gè)額外的跨度曹仗,以便在UI中正確顯示跟蹤∪渌眩看到最有可能意味著有一個(gè)缺失的儀器怎茫。在這種情況下,請(qǐng)?jiān)赟pring Cloud Sleuth中提出問(wèn)題妓灌。
異步Servlet支持
如果您的控制器返回Callable或WebAsyncTaskSpring Cloud轨蛤,Sleuth將繼續(xù)現(xiàn)有的跨度,而不是創(chuàng)建一個(gè)新的跨度虫埂。
HTTP客戶端集成
同步休息模板
我們注入一個(gè)RestTemplate攔截器祥山,確保所有跟蹤信息都傳遞給請(qǐng)求。每次呼叫都會(huì)創(chuàng)建一個(gè)新的Span掉伏。收到回應(yīng)后關(guān)閉缝呕。為了阻止將spring.sleuth.web.client.enabled設(shè)置為false的同步RestTemplate功能澳窑。
重要
你必須注冊(cè)RestTemplate作為一個(gè)bean,以便攔截器被注入岳颇。如果您使用new關(guān)鍵字創(chuàng)建RestTemplate實(shí)例照捡,那么該工具將不工作颅湘。
異步休息模板
重要
一個(gè)AsyncRestTemplatebean的跟蹤版本是為您開(kāi)箱即用的话侧。如果你有自己的bean,你必須用TraceAsyncRestTemplate表示來(lái)包裝它闯参。最好的解決方案是只定制ClientHttpRequestFactory和/或AsyncClientHttpRequestFactory瞻鹏。如果您有自己的AsyncRestTemplate,并且您不要包裝您的電話將不會(huì)被追蹤鹿寨。
定制儀器設(shè)置為在發(fā)送和接收請(qǐng)求時(shí)創(chuàng)建和關(guān)閉跨度新博。您可以通過(guò)注冊(cè)您的bean來(lái)自定義ClientHttpRequestFactory和AsyncClientHttpRequestFactory。記住使用跟蹤兼容的實(shí)現(xiàn)(例如脚草,不要忘記在TraceAsyncListenableTaskExecutor中包裝ThreadPoolTaskScheduler)赫悄。自定義請(qǐng)求工廠示例:
@EnableAutoConfiguration
@Configuration
public static class TestConfiguration {
@Bean
ClientHttpRequestFactory mySyncClientFactory() {
return new MySyncClientHttpRequestFactory();
}
@Bean
AsyncClientHttpRequestFactory myAsyncClientFactory() {
return new MyAsyncClientHttpRequestFactory();
}
}
將AsyncRestTemplate功能集spring.sleuth.web.async.client.enabled阻止為false。禁用TraceAsyncClientHttpRequestFactoryWrapper設(shè)置spring.sleuth.web.async.client.factory.enabled設(shè)置為false馏慨。如果您不想將所有spring.sleuth.web.async.client.template.enabledfalse的AsyncRestClient創(chuàng)建為false埂淮。
Feign
默認(rèn)情況下,Spring Cloud Sleuth通過(guò)TraceFeignClientAutoConfiguration提供與feign的集成写隶。您可以通過(guò)將spring.sleuth.feign.enabled設(shè)置為false來(lái)完全禁用它倔撞。如果這樣做,那么不會(huì)發(fā)生Feign相關(guān)的儀器慕趴。
Feign儀器的一部分是通過(guò)FeignBeanPostProcessor完成的痪蝇。您可以通過(guò)提供spring.sleuth.feign.processor.enabled等于false來(lái)禁用它。如果你這樣設(shè)置冕房,那么Spring Cloud Sleuth不會(huì)調(diào)整你的任何自定義Feign組件躏啰。然而,所有默認(rèn)的工具仍然存在耙册。
異步通信
@Async注釋方法
在Spring Cloud Sleuth中丙唧,我們正在調(diào)用異步相關(guān)組件,以便跟蹤信息在線程之間傳遞觅玻。您可以通過(guò)將spring.sleuth.async.enabled的值設(shè)置為false來(lái)禁用此行為想际。
如果您使用@Async注釋方法,那么我們將自動(dòng)創(chuàng)建一個(gè)具有以下特征的新的Span:
Span名稱將是注釋方法名稱
Span將被該方法的類名稱和方法名稱標(biāo)記
@Scheduled注釋方法
在Spring Cloud Sleuth中,我們正在調(diào)試計(jì)劃的方法執(zhí)行星爪,以便跟蹤信息在線程之間傳遞殊霞。您可以通過(guò)將spring.sleuth.scheduled.enabled的值設(shè)置為false來(lái)禁用此行為。
如果您使用@Scheduled注釋方法侧甫,那么我們將自動(dòng)創(chuàng)建一個(gè)具有以下特征的新的Span:
Span名稱將是注釋方法名稱
Span將被該方法的類名稱和方法名稱標(biāo)記
如果要跳過(guò)某些@Scheduled注釋類的Span創(chuàng)建,您可以使用與@Scheduled注釋類的完全限定名稱匹配的正則表達(dá)式來(lái)設(shè)置spring.sleuth.scheduled.skipPattern。
小費(fèi)
如果您一起使用spring-cloud-sleuth-stream和spring-cloud-netflix-hystrix-stream披粟,將為每個(gè)Hystrix指標(biāo)創(chuàng)建Span并發(fā)送到Zipkin咒锻。這可能是惱人的。您可以設(shè)置spring.sleuth.scheduled.skipPattern=org.springframework.cloud.netflix.hystrix.stream.HystrixStreamTask
Executor守屉,ExecutorService和ScheduledExecutorService
我們提供LazyTraceExecutor惑艇,TraceableExecutorService和TraceableScheduledExecutorService。每次提交拇泛,調(diào)用或調(diào)度新任務(wù)時(shí)滨巴,這些實(shí)現(xiàn)都將創(chuàng)建Spans。
在這里俺叭,您可以看到使用CompletableFuture使用TraceableExecutorService傳遞跟蹤信息的示例:
CompletableFuture 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"));
消息
Spring Cloud Sleuth與Spring Integration集成恭取。它創(chuàng)建spans發(fā)布和訂閱事件。要禁用Spring Integration檢測(cè)熄守,請(qǐng)將??spring.sleuth.integration.enabled設(shè)置為false蜈垮。
您可以提供spring.sleuth.integration.patterns模式,以明確提供要包括的用于跟蹤的通道的名稱裕照。默認(rèn)情況下攒发,所有通道都包含在內(nèi)。
重要
當(dāng)使用Executor構(gòu)建Spring IntegrationIntegrationFlow時(shí)牍氛,請(qǐng)記住使用Executor的未跟蹤版本晨继。用TraceableExecutorService裝飾Spring Integration執(zhí)行者頻道將導(dǎo)致spans被關(guān)閉。
Zuul
我們正在注冊(cè)Zuul過(guò)濾器來(lái)傳播跟蹤信息(請(qǐng)求標(biāo)頭豐富了跟蹤數(shù)據(jù))搬俊。要禁用Zuul支持紊扬,請(qǐng)將spring.sleuth.zuul.enabled屬性設(shè)置為false。
運(yùn)行示例
您可以在Pivotal Web Services中找到部署的運(yùn)行示例唉擂。在以下鏈接中查看它們:
Spring Cloud Consul
Dalston.RELEASE
該項(xiàng)目通過(guò)自動(dòng)配置并綁定到Spring環(huán)境和其他Spring編程模型成語(yǔ),為Spring Boot應(yīng)用程序提供Consul集成玩祟。通過(guò)幾個(gè)簡(jiǎn)單的注釋腹缩,您可以快速啟用和配置應(yīng)用程序中的常見(jiàn)模式,并使用基于Consul的組件構(gòu)建大型分布式系統(tǒng)空扎。提供的模式包括服務(wù)發(fā)現(xiàn)藏鹊,控制總線和配置。智能路由(Zuul)和客戶端負(fù)載平衡(Ribbon)转锈,斷路器(Hystrix)通過(guò)與Spring Cloud Netflix的集成提供盘寡。
安裝Consul
請(qǐng)參閱安裝文檔獲取有關(guān)如何安裝Consul指令。
Consul Agent
所有Spring Cloud Consul應(yīng)用程序必須可以使用Consul Agent客戶端撮慨。默認(rèn)情況下竿痰,代理客戶端預(yù)計(jì)位于localhost:8500脆粥。有關(guān)如何啟動(dòng)代理客戶端以及如何連接到Consul Agent服務(wù)器集群的詳細(xì)信息,請(qǐng)參閱代理文檔影涉。對(duì)于開(kāi)發(fā)变隔,安裝領(lǐng)事后,您可以使用以下命令啟動(dòng)Consul Agent:
./src/main/bash/local_run_consul.sh
這將啟動(dòng)端口8500上的服務(wù)器模式的代理蟹倾,其中ui可以在http:// localhost:8500上找到
服務(wù)發(fā)現(xiàn)與Consul
服務(wù)發(fā)現(xiàn)是基于微服務(wù)架構(gòu)的關(guān)鍵原則之一匣缘。嘗試配置每個(gè)客戶端或某種形式的約定可能非常困難,可以非常脆弱喊式。Consul通過(guò)HTTP API和DNS提供服務(wù)發(fā)現(xiàn)服務(wù)孵户。Spring Cloud Consul利用HTTP API進(jìn)行服務(wù)注冊(cè)和發(fā)現(xiàn)萧朝。這不會(huì)阻止非Spring Cloud應(yīng)用程序利用DNS界面岔留。Consul代理服務(wù)器在通過(guò)八卦協(xié)議進(jìn)行通信的集群中運(yùn)行,并使用Raft協(xié)議協(xié)議检柬。
如何激活
要激活Consul服務(wù)發(fā)現(xiàn),請(qǐng)使用組org.springframework.cloud和artifact idspring-cloud-starter-consul-discovery的啟動(dòng)器。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息通今,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面往枣。
注冊(cè)Consul
當(dāng)客戶端注冊(cè)Consul時(shí),它提供有關(guān)自身的元數(shù)據(jù)用爪,如主機(jī)和端口原押,ID,名稱和標(biāo)簽偎血。默認(rèn)情況下會(huì)創(chuàng)建一個(gè)HTTP檢查诸衔,每隔10秒,Consul命中/health端點(diǎn)颇玷。如果健康檢查失敗笨农,則服務(wù)實(shí)例被標(biāo)記為關(guān)鍵。
示例Consul客戶端:
@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);
}
}
(即完全正常的Spring Boot應(yīng)用程序)帖渠。如果Consul客戶端位于localhost:8500以外的位置谒亦,則需要配置來(lái)定位客戶端。例:
application.yml
spring:
cloud:
consul:
host: localhost
port: 8500
警告
如果您使用Spring Cloud Consul Config空郊,上述值將需要放在bootstrap.yml而不是application.yml中份招。
來(lái)自Environment的默認(rèn)服務(wù)名稱,實(shí)例ID和端口分別為${spring.application.name}狞甚,Spring上下文ID和${server.port}锁摔。
@EnableDiscoveryClient將應(yīng)用程序設(shè)為Consul“服務(wù)”(即注冊(cè)自己)和“客戶端”(即可以查詢Consul查找其他服務(wù))。
HTTP健康檢查
Consul實(shí)例的運(yùn)行狀況檢查默認(rèn)為“/ health”入愧,這是Spring Boot執(zhí)行器應(yīng)用程序中有用端點(diǎn)的默認(rèn)位置鄙漏。如果您使用非默認(rèn)上下文路徑或servlet路徑(例如server.servletPath=/foo)或管理端點(diǎn)路徑(例如management.context-path=/admin)嗤谚,則需要更改這些,即使是執(zhí)行器應(yīng)用程序怔蚌。也可以配置Consul用于檢查運(yùn)行狀況端點(diǎn)的間隔巩步。“10s”和“1m”分別表示10秒和1分鐘桦踊。例:
application.yml
spring:
cloud:
consul:
discovery:
healthCheckPath: ${management.context-path}/health
healthCheckInterval: 15s
元數(shù)據(jù)和Consul標(biāo)簽
Consul尚未支持服務(wù)元數(shù)據(jù)椅野。Spring Cloud的ServiceInstance有一個(gè)Map metadata字段。Spring Cloud Consul使用Consul標(biāo)簽來(lái)近似元數(shù)據(jù)籍胯,直到Consul正式支持元數(shù)據(jù)竟闪。使用key=value形式的標(biāo)簽將被分割并分別用作Map鍵和值。標(biāo)簽沒(méi)有相同的=符號(hào)杖狼,將被用作鍵和值兩者炼蛤。
application.yml
spring:
cloud:
consul:
discovery:
tags: foo=bar, baz
上述配置將導(dǎo)致具有foo→bar和baz→baz的映射。
使Consul實(shí)例ID唯一
默認(rèn)情況下蝶涩,一個(gè)領(lǐng)事實(shí)體注冊(cè)了一個(gè)等于其Spring應(yīng)用程序上下文ID的ID理朋。默認(rèn)情況下,Spring應(yīng)用程序上下文ID為${spring.application.name}:comma,separated,profiles:${server.port}绿聘。在大多數(shù)情況下嗽上,這將允許一個(gè)服務(wù)的多個(gè)實(shí)例在一臺(tái)機(jī)器上運(yùn)行。如果需要進(jìn)一步的唯一性熄攘,使用Spring Cloud兽愤,您可以通過(guò)在spring.cloud.consul.discovery.instanceId中提供唯一的標(biāo)識(shí)來(lái)覆蓋此。例如:
application.yml
spring:
cloud:
consul:
discovery:
instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
使用這個(gè)元數(shù)據(jù)和在localhost上部署的多個(gè)服務(wù)實(shí)例挪圾,隨機(jī)值將在那里進(jìn)行浅萧,以使實(shí)例是唯一的。在Cloudfoundry中洛史,vcap.application.instance_id將在Spring Boot應(yīng)用程序中自動(dòng)填充惯殊,因此不需要隨機(jī)值。
使用DiscoveryClient
Spring Cloud支持Feign(REST客戶端構(gòu)建器)也殖,SpringRestTemplate使用邏輯服務(wù)名稱而不是物理URL土思。
您還可以使用org.springframework.cloud.client.discovery.DiscoveryClient仗颈,它為Netflix不特定的發(fā)現(xiàn)客戶端提供了一個(gè)簡(jiǎn)單的API哭廉,例如
@Autowired
private DiscoveryClient discoveryClient;
public String serviceUrl() {
List list = discoveryClient.getInstances("STORES");
if (list != null && list.size() > 0 ) {
return list.get(0).getUri();
}
return null;
}
具有Consul的分布式配置
Consul提供了一個(gè)用于存儲(chǔ)配置和其他元數(shù)據(jù)的鍵/值存儲(chǔ)鸵隧。Spring Cloud Consul Config是Config Server和Client的替代方案染乌。在特殊的“引導(dǎo)”階段,配置被加載到Spring環(huán)境中栓撞。默認(rèn)情況下昵时,配置存儲(chǔ)在/config文件夾中尊搬〖保基于應(yīng)用程序的名稱和模擬解析屬性的Spring Cloud Config順序的活動(dòng)配置文件創(chuàng)建多個(gè)PropertySource實(shí)例途样。例如江醇,名為“testApp”的應(yīng)用程序和“dev”配置文件將創(chuàng)建以下屬性源:
config/testApp,dev/
config/testApp/
config/application,dev/
config/application/
最具體的物業(yè)來(lái)源位于頂部,底部最不具體何暇。Properties是config/application文件夾適用于使用consul進(jìn)行配置的所有應(yīng)用程序陶夜。config/testApp文件夾中的Properties僅適用于名為“testApp”的服務(wù)實(shí)例。
配置當(dāng)前在應(yīng)用程序啟動(dòng)時(shí)被讀取裆站。發(fā)送HTTP POST到/refresh將導(dǎo)致配置被重新加載条辟。觀看關(guān)鍵價(jià)值商店(Consul支持))目前不可能,但將來(lái)將是此項(xiàng)目的補(bǔ)充宏胯。
如何激活
要開(kāi)始使用Consul配置羽嫡,請(qǐng)使用組org.springframework.cloud和artifact idspring-cloud-starter-consul-config的啟動(dòng)器。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息肩袍,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面杭棵。
這將啟用自動(dòng)配置,將設(shè)置Spring Cloud Consul配置了牛。
定制
Consul可以使用以下屬性定制配置:
bootstrap.yml
spring:
cloud:
consul:
config:
enabled: true
prefix: configuration
defaultContext: apps
profileSeparator: '::'
enabled將此值設(shè)置為“false”禁用Consul配置
prefix設(shè)置配置值的基本文件夾
defaultContext設(shè)置所有應(yīng)用程序使用的文件夾名稱
profileSeparator設(shè)置用于使用配置文件在屬性源中分隔配置文件名稱的分隔符的值
配置觀察
Consul配置觀察功能可以利用領(lǐng)事看守鑰匙前綴的能力颜屠。Config Watch會(huì)阻止Consul HTTP API調(diào)用辰妙,以確定當(dāng)前應(yīng)用程序是否有任何相關(guān)配置數(shù)據(jù)發(fā)生更改鹰祸。如果有新的配置數(shù)據(jù),則會(huì)發(fā)布刷新事件密浑。這相當(dāng)于調(diào)用/refresh執(zhí)行器端點(diǎn)蛙婴。
要更改Config Watch調(diào)用的頻率changespring.cloud.consul.config.watch.delay。默認(rèn)值為1000尔破,以毫秒為單位街图。
禁用配置觀察集spring.cloud.consul.config.watch.enabled=false。
YAML或Properties配置
與單個(gè)鍵/值對(duì)相反懒构,可以更方便地將YBL或Properties格式的屬性塊存儲(chǔ)起來(lái)餐济。將spring.cloud.consul.config.format屬性設(shè)置為YAML或PROPERTIES。例如使用YAML:
bootstrap.yml
spring:
cloud:
consul:
config:
format: YAML
YAML必須在合適的data鍵中設(shè)置胆剧。使用鍵上面的默認(rèn)值將如下所示:
config/testApp,dev/data
config/testApp/data
config/application,dev/data
config/application/data
您可以將YAML文檔存儲(chǔ)在上述任何鍵中絮姆。
您可以使用spring.cloud.consul.config.data-key更改數(shù)據(jù)密鑰。
git2consul與配置
git2consul是一個(gè)Consul社區(qū)項(xiàng)目秩霍,將文件從git存儲(chǔ)庫(kù)加載到各個(gè)密鑰到Consul篙悯。默認(rèn)情況下,密鑰的名稱是文件的名稱铃绒。YAML和Properties文件分別支持.yml和.properties的文件擴(kuò)展名鸽照。將spring.cloud.consul.config.format屬性設(shè)置為FILES。例如:
bootstrap.yml
spring:
cloud:
consul:
config:
format: FILES
給定/config中的以下密鑰颠悬,development配置文件和應(yīng)用程序名稱為foo:
.gitignore
application.yml
bar.properties
foo-development.properties
foo-production.yml
foo.properties
master.ref
將創(chuàng)建以下屬性來(lái)源:
config/foo-development.properties
config/foo.properties
config/application.yml
每個(gè)鍵的值需要是一個(gè)格式正確的YAML或Properties文件矮燎。
快速失敗
在某些情況下(如本地開(kāi)發(fā)或某些測(cè)試場(chǎng)景)可能會(huì)方便定血,如果不能配置領(lǐng)事,則不會(huì)失敗诞外。在bootstrap.yml中設(shè)置spring.cloud.consul.config.failFast=false將導(dǎo)致配置模塊記錄一個(gè)警告而不是拋出異常糠悼。這將允許應(yīng)用程序繼續(xù)正常啟動(dòng)。
Consul重試
如果您希望您的應(yīng)用程序啟動(dòng)時(shí)可能偶爾無(wú)法使用代理商浅乔,則可以要求您在發(fā)生故障后繼續(xù)嘗試倔喂。您需要在您的類路徑中添加spring-retry和spring-boot-starter-aop战转。默認(rèn)行為是重試6次舶沿,初始退避間隔為1000ms,指數(shù)乘數(shù)為1.1捶朵,用于后續(xù)退避贤壁。您可以使用spring.cloud.consul.retry.*配置屬性配置這些屬性(和其他)悼枢。這適用于Spring Cloud Consul配置和發(fā)現(xiàn)注冊(cè)。
小費(fèi)
要完全控制重試脾拆,請(qǐng)使用id為“consulRetryInterceptor”添加RetryOperationsInterceptor類型的@Bean馒索。Spring重試有一個(gè)RetryInterceptorBuilder可以輕松創(chuàng)建一個(gè)。
Spring Cloud Bus與Consul
如何激活
要開(kāi)始使用Consul總線名船,使用組org.springframework.cloud和工件idspring-cloud-starter-consul-bus的起動(dòng)器绰上。有關(guān)使用當(dāng)前的Spring Cloud發(fā)布列表設(shè)置構(gòu)建系統(tǒng)的詳細(xì)信息,請(qǐng)參閱Spring Cloud項(xiàng)目頁(yè)面渠驼。
有關(guān)可用的執(zhí)行機(jī)構(gòu)端點(diǎn)以及如何發(fā)送自定義消息蜈块,請(qǐng)參閱Spring Cloud Bus文檔。
斷路器與Hystrix
應(yīng)用程序可以使用Spring Cloud Netflix項(xiàng)目提供的Hystrix斷路器將這個(gè)啟動(dòng)器包含在項(xiàng)目pom.xml:spring-cloud-starter-hystrix中迷扇。Hystrix不依賴于Netflix Discovery Client百揭。@EnableHystrix注釋應(yīng)放置在配置類(通常是主類)上。那么方法可以用@HystrixCommand注釋來(lái)被斷路器保護(hù)蜓席。有關(guān)詳細(xì)信息器一,請(qǐng)參閱文檔。
使用Turbine和Consul Hystrix指標(biāo)聚合
Turbine(由Spring Cloud Netflix項(xiàng)目提供))聚合多個(gè)實(shí)例Hystrix指標(biāo)流厨内,因此儀表板可以顯示聚合視圖祈秕。Turbine使用DiscoveryClient接口查找相關(guān)實(shí)例。要將Turbine與Spring Cloud Consul結(jié)合使用隘庄,請(qǐng)按以下示例配置Turbine應(yīng)用程序:
的pom.xml
org.springframework.cloud
spring-cloud-netflix-turbine
org.springframework.cloud
spring-cloud-starter-consul-discovery
請(qǐng)注意踢步,Turbine依賴不是起始者。渦輪啟動(dòng)器包括對(duì)Netflix Eureka的支持丑掺。
application.yml
spring.application.name: turbine
applications: consulhystrixclient
turbine:
aggregator:
clusterConfig: ${applications}
appConfig: ${applications}
clusterConfig和appConfig部分必須匹配获印,因此將逗號(hào)分隔的服務(wù)標(biāo)識(shí)列表放在單獨(dú)的配置屬性中是有用的。
Turbine。java的
@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class Turbine {
public static void main(String[] args) {
SpringApplication.run(DemoturbinecommonsApplication.class, args);
}
}
Spring Cloud Zookeeper
該項(xiàng)目通過(guò)自動(dòng)配置并綁定到Spring環(huán)境和其他Spring編程模型成語(yǔ)兼丰,為Spring Boot應(yīng)用程序提供Zookeeper集成玻孟。通過(guò)幾個(gè)簡(jiǎn)單的注釋,您可以快速啟用和配置應(yīng)用程序中的常見(jiàn)模式鳍征,并使用基于Zookeeper的組件構(gòu)建大型分布式系統(tǒng)黍翎。提供的模式包括服務(wù)發(fā)現(xiàn)和配置。智能路由(Zuul)和客戶端負(fù)載平衡(Ribbon)艳丛,斷路器(Hystrix)通過(guò)與Spring Cloud Netflix的集成提供匣掸。
安裝Zookeeper
請(qǐng)參閱安裝文檔獲取有關(guān)如何安裝Zookeeper指令。
服務(wù)發(fā)現(xiàn)與Zookeeper
服務(wù)發(fā)現(xiàn)是基于微服務(wù)架構(gòu)的關(guān)鍵原則之一氮双。嘗試配置每個(gè)客戶端或某種形式的約定可能非常困難碰酝,可以非常脆弱。策展人(一個(gè)用于Zookeeper的java庫(kù))通過(guò)服務(wù)發(fā)現(xiàn)擴(kuò)展提供服務(wù)發(fā)現(xiàn)服務(wù)戴差。Spring Cloud Zookeeper利用此擴(kuò)展功能進(jìn)行服務(wù)注冊(cè)和發(fā)現(xiàn)送爸。
如何激活
包括對(duì)org.springframework.cloud:spring-cloud-starter-zookeeper-discovery的依賴將啟用將設(shè)置Spring Cloud Zookeeper發(fā)現(xiàn)的自動(dòng)配置。
注意
您仍然需要包含org.springframework.boot:spring-boot-starter-web的網(wǎng)頁(yè)功能暖释。
注冊(cè)Zookeeper
當(dāng)客戶端注冊(cè)Zookeeper時(shí)袭厂,它提供有關(guān)自身的元數(shù)據(jù),如主機(jī)和端口球匕,ID和名稱纹磺。
示例Zookeeper客戶端:
@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);
}
}
(即完全正常的Spring Boot應(yīng)用程序)。如果Zookeeper位于localhost:2181以外的地方谐丢,則需要配置來(lái)定位服務(wù)器爽航。例:
application.yml
spring:
cloud:
zookeeper:
connect-string: localhost:2181
警告
如果您使用Spring Cloud Zookeeper配置,上述值將需要放置在bootstrap.yml而不是application.yml中乾忱。
來(lái)自Environment的默認(rèn)服務(wù)名稱,實(shí)例ID和端口分別為${spring.application.name}历极,Spring上下文ID和${server.port}。
@EnableDiscoveryClient將應(yīng)用程序同時(shí)進(jìn)入Zookeeper“服務(wù)”(即注冊(cè)自己)和“客戶端”(即可以查詢Zookeeper查找其他服務(wù))。
使用DiscoveryClient
Spring Cloud支持Feign(REST客戶端構(gòu)建器)侈离,還支持SpringRestTemplate使用邏輯服務(wù)名稱而不是物理URL峦筒。
您還可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它為Netflix不具體的發(fā)現(xiàn)客戶端提供簡(jiǎn)單的API锄列,例如
@Autowired
private DiscoveryClient discoveryClient;
public String serviceUrl() {
List list = discoveryClient.getInstances("STORES");
if (list != null && list.size() > 0 ) {
return list.get(0).getUri().toString();
}
return null;
}
使用Spring Cloud Zookeeper與Spring Cloud Netflix組件
Spring Cloud Netflix提供有用的工具图云,無(wú)論使用哪種DiscoveryClient實(shí)現(xiàn)。Feign邻邮,Turbine竣况,Ribbon和Zuul均與Spring Cloud Zookeeper合作。
Ribbon與Zookeeper
Spring Cloud Zookeeper提供Ribbon的ServerList的實(shí)現(xiàn)筒严。當(dāng)使用spring-cloud-starter-zookeeper-discovery時(shí)丹泉,Ribbon默認(rèn)情況下自動(dòng)配置為使用ZookeeperServerList情萤。
Spring Cloud Zookeeper和服務(wù)注冊(cè)表
Spring Cloud Zookeeper實(shí)現(xiàn)ServiceRegistry接口,允許開(kāi)發(fā)人員以編程方式注冊(cè)任意服務(wù)摹恨。
ServiceInstanceRegistration類提供builder()方法來(lái)創(chuàng)建可以由ServiceRegistry使用的Registration對(duì)象筋岛。
@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);
}
實(shí)例狀態(tài)
Netflix Eureka支持在服務(wù)器上注冊(cè)的實(shí)例是OUT_OF_SERVICE,而不是作為活動(dòng)服務(wù)實(shí)例返回晒哄。這對(duì)于諸如藍(lán)色/綠色部署之類的行為非常有用睁宰。策展人服務(wù)發(fā)現(xiàn)配方不支持此行為。利用靈活的有效載荷寝凌,讓Spring Cloud Zookeeper通過(guò)更新一些特定的元數(shù)據(jù)勋陪,然后對(duì)RibbonZookeeperServerList中的元數(shù)據(jù)進(jìn)行過(guò)濾來(lái)實(shí)現(xiàn)OUT_OF_SERVICE。ZookeeperServerList過(guò)濾出不等于UP的所有非空實(shí)例狀態(tài)硫兰。如果實(shí)例狀態(tài)字段為空诅愚,則向后兼容性被認(rèn)為是UP。將實(shí)例POSTOUT_OF_SERVICE的狀態(tài)更改為ServiceRegistry實(shí)例狀態(tài)執(zhí)行器端點(diǎn)劫映。
----
$ echo -n OUT_OF_SERVICE | http POST http://localhost:8081/service-registry/instance-status
----
NOTE: The above example uses the `http` command from https://httpie.org
Zookeeper依賴關(guān)系
使用Zookeeper依賴關(guān)系
Spring Cloud Zookeeper可以讓您提供應(yīng)用程序的依賴關(guān)系作為屬性违孝。作為依賴關(guān)系,您可以了解Zookeeper中注冊(cè)的其他應(yīng)用程序泳赋,您可以通過(guò)Feign(REST客戶端構(gòu)建器)以及SpringRestTemplate呼叫雌桑。
您還可以從Zookeeper依賴關(guān)系觀察者功能中受益,這些功能可讓您控制和監(jiān)視依賴關(guān)系的狀態(tài)祖今,并決定如何處理校坑。
如何激活Zookeeper依賴關(guān)系
包括對(duì)org.springframework.cloud:spring-cloud-starter-zookeeper-discovery的依賴將啟用將自動(dòng)配置Spring Cloud Zookeeper依賴關(guān)系的自動(dòng)配置。
如果您必須正確設(shè)置spring.cloud.zookeeper.dependencies部分 - 請(qǐng)查看后續(xù)部分以獲取更多詳細(xì)信息千诬,然后該功能處于活動(dòng)狀態(tài)
即使您在屬性中提供依賴關(guān)系耍目,也可以關(guān)閉依賴關(guān)系。只需將屬性spring.cloud.zookeeper.dependency.enabled設(shè)置為false(默認(rèn)為true)徐绑。
設(shè)置Zookeeper依賴關(guān)系
我們來(lái)仔細(xì)看一下依賴關(guān)系表示的例子:
application.yml
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
現(xiàn)在讓我們一個(gè)接一個(gè)地遍歷依賴的每個(gè)部分邪驮。根屬性名稱為spring.cloud.zookeeper.dependencies。
別名
在根屬性下面傲茄,由于Ribbon的限制毅访,必須通過(guò)別名來(lái)表示每個(gè)依賴關(guān)系(應(yīng)用程序ID必須放在URL中,因此您不能傳遞任何復(fù)雜的路徑盘榨,如/ foo / bar / name )喻粹。別名將是您將使用的名稱,而不是DiscoveryClient草巡,F(xiàn)eign或RestTemplate的serviceId守呜。
在上述例子中,別名是newsletter和mailing。使用newsletter的Feign使用示例為:
@FeignClient("newsletter")
public interface NewsletterService {
@RequestMapping(method = RequestMethod.GET, value = "/newsletter")
String getNewsletters();
}
路徑
代表pathyaml屬性弛饭。
Path是根據(jù)Zookeeper注冊(cè)依賴關(guān)系的路徑冕末。像Ribbon之前提交的URL,因此這個(gè)路徑不符合其要求侣颂。這就是為什么Spring Cloud Zookeeper將別名映射到正確的路徑档桃。
負(fù)載平衡器類型
代表loadBalancerTypeyaml屬性。
如果您知道在調(diào)用此特定依賴關(guān)系時(shí)必須應(yīng)用什么樣的負(fù)載平衡策略憔晒,那么您可以在yaml文件中提供它藻肄,并將自動(dòng)應(yīng)用剂买。您可以選擇以下負(fù)載平衡策略之一
STICKY - 一旦選擇了該實(shí)例將始終被調(diào)用
隨機(jī) - 隨機(jī)選擇一個(gè)實(shí)例
ROUND_ROBIN - 一遍又一遍地迭代實(shí)例
Content-Type模板和版本
代表contentTypeTemplate和versionyaml屬性溉知。
如果您通過(guò)Content-Type標(biāo)題版本您的api妖泄,那么您不想將此標(biāo)頭添加到您的每個(gè)請(qǐng)求中芝薇。另外如果你想調(diào)用一個(gè)新版本的API,你不想漫游你的代碼录别,以增加API版本速挑。這就是為什么您可以提供contentTypeTemplate特殊$version占位符的原因嘿悬。該占位符將由versionyaml屬性的值填充低零。我們來(lái)看一個(gè)例子婆翔。
擁有以下contentTypeTemplate:
application/vnd.newsletter.$version+json
和以下version:
v1
將導(dǎo)致為每個(gè)請(qǐng)求設(shè)置Content-Type標(biāo)題:
application/vnd.newsletter.v1+json
默認(rèn)標(biāo)題
由yaml代表headers映射
有時(shí)每次調(diào)用依賴關(guān)系都需要設(shè)置一些默認(rèn)標(biāo)頭。為了不在代碼中這樣做掏婶,您可以在yaml文件中設(shè)置它們啃奴。擁有以下headers部分:
headers:
Accept:
- text/html
- application/xhtml+xml
Cache-Control:
- no-cache
結(jié)果在您的HTTP請(qǐng)求中添加適當(dāng)?shù)闹盗斜淼腁ccept和Cache-Control標(biāo)頭。
強(qiáng)制依賴
在yaml中由required屬性表示
如果您的一個(gè)依賴關(guān)系在您的應(yīng)用程序啟動(dòng)時(shí)需要啟動(dòng)并運(yùn)行雄妥,則可以在yaml文件中設(shè)置required: true屬性最蕾。
如果您的應(yīng)用程序無(wú)法在引導(dǎo)期間本地化所需的依賴關(guān)系,則會(huì)拋出異常老厌,并且Spring上下文將無(wú)法設(shè)置瘟则。換句話說(shuō),如果Zookeeper中沒(méi)有注冊(cè)所需的依賴關(guān)系梅桩,則您的應(yīng)用程序?qū)o(wú)法啟動(dòng)壹粟。
您可以在以下部分閱讀有關(guān)Spring Cloud Zookeeper存在檢查器的更多信息。
存根
您可以為包含依賴關(guān)系的存根的JAR提供冒號(hào)分隔路徑宿百。例
stubs: org.springframework:foo:stubs
意味著對(duì)于特定的依賴關(guān)系可以在下面找到:
groupId:org.springframework
artifactId:foo
分類器:stubs- 這是默認(rèn)值
這實(shí)際上等于
stubs: org.springframework:foo
因?yàn)閟tubs是默認(rèn)分類器。
配置Spring Cloud Zookeeper依賴關(guān)系
有一些屬性可以設(shè)置為啟用/禁用Zookeeper依賴關(guān)系功能的部分洪添。
spring.cloud.zookeeper.dependencies- 如果您不設(shè)置此屬性垦页,則不會(huì)從Zookeeper依賴關(guān)系中受益
spring.cloud.zookeeper.dependency.ribbon.enabled(默認(rèn)情況下啟用) - Ribbon需要顯式的全局配置或特定的依賴關(guān)系。通過(guò)打開(kāi)此屬性干奢,運(yùn)行時(shí)負(fù)載平衡策略解決是可能的痊焊,您可以從Zookeeper依賴關(guān)系的loadBalancerType部分獲利。需要此屬性的配置具有LoadBalancerClient的實(shí)現(xiàn),委托給下一個(gè)子彈中的ILoadBalancer
spring.cloud.zookeeper.dependency.ribbon.loadbalancer(默認(rèn)情況下啟用) - 感謝這個(gè)屬性薄啥,自定義ILoadBalancer知道傳遞給Ribbon的URI部分實(shí)際上可能是必須被解析為Zookeeper辕羽。沒(méi)有此屬性,您將無(wú)法在嵌套路徑下注冊(cè)應(yīng)用程序垄惧。
spring.cloud.zookeeper.dependency.headers.enabled(默認(rèn)情況下啟用) - 此屬性注冊(cè)這樣的一個(gè)RibbonClient刁愿,它會(huì)自動(dòng)附加適當(dāng)?shù)念^文件和內(nèi)容類型,其中包含依賴關(guān)系配置中顯示的版本到逊。沒(méi)有這兩個(gè)參數(shù)的設(shè)置將不會(huì)運(yùn)行铣口。
spring.cloud.zookeeper.dependency.resttemplate.enabled(默認(rèn)情況下啟用) - 啟用時(shí)將修改@LoadBalanced注釋的RestTemplate的請(qǐng)求標(biāo)頭,以便它通過(guò)依賴關(guān)系配置中設(shè)置的版本的標(biāo)題和內(nèi)容類型觉壶。Wihtout這兩個(gè)參數(shù)的設(shè)置將無(wú)法運(yùn)行脑题。
Spring Cloud Zookeeper依賴關(guān)系觀察者
依賴關(guān)系觀察器機(jī)制允許您將偵聽(tīng)器注冊(cè)到依賴關(guān)系中。功能實(shí)際上是Observator模式的實(shí)現(xiàn)铜靶。當(dāng)依賴關(guān)系改變其狀態(tài)(UP或DOWN)時(shí)叔遂,可以應(yīng)用一些自定義邏輯。
如何激活
Spring Cloud Zookeeper依賴關(guān)系功能需要啟用從依賴關(guān)系觀察器機(jī)制中獲利争剿。
注冊(cè)聽(tīng)眾
為了注冊(cè)一個(gè)監(jiān)聽(tīng)器已艰,你必須實(shí)現(xiàn)一個(gè)接口org.springframework.cloud.zookeeper.discovery.watcher.DependencyWatcherListener,并將其注冊(cè)為一個(gè)bean秒梅。該界面為您提供了一種方法:
void stateChanged(String dependencyName, DependencyState newState);
如果要為特定的依賴關(guān)系注冊(cè)一個(gè)偵聽(tīng)器旗芬,那么dependencyName將是具體實(shí)現(xiàn)的鑒別器。newState將提供您的依賴關(guān)系是否已更改為CONNECTED或DISCONNECTED的信息捆蜀。
存在檢查
綁定與依賴關(guān)系觀察器是稱為存在檢查器的功能疮丛。它允許您在啟動(dòng)應(yīng)用程序時(shí)提供自定義行為,以根據(jù)您的依賴關(guān)系的狀態(tài)作出反應(yīng)辆它。
抽象org.springframework.cloud.zookeeper.discovery.watcher.presence.DependencyPresenceOnStartupVerifier類的默認(rèn)實(shí)現(xiàn)是org.springframework.cloud.zookeeper.discovery.watcher.presence.DefaultDependencyPresenceOnStartupVerifier誊薄,它以以下方式工作挂疆。
如果依賴關(guān)系標(biāo)記為我們r(jià)equired才漆,并且不在Zookeeper中,則在引導(dǎo)時(shí)鹏氧,您的應(yīng)用程序?qū)伋霎??常并關(guān)閉
如果依賴關(guān)系不是required飒筑,org.springframework.cloud.zookeeper.discovery.watcher.presence.LogMissingDependencyChecker將在WARN級(jí)別上記錄該應(yīng)用程序
功能可以被覆蓋片吊,因?yàn)橹挥挟?dāng)沒(méi)有DependencyPresenceOnStartupVerifier的bean時(shí)才會(huì)注冊(cè)DefaultDependencyPresenceOnStartupVerifier。
分布式配置與Zookeeper
Zookeeper提供了一個(gè)分層命名空間协屡,允許客戶端存儲(chǔ)任意數(shù)據(jù)俏脊,如配置數(shù)據(jù)。Spring Cloud Zookeeper Config是Config Server和Client的替代方案肤晓。在特殊的“引導(dǎo)”階段爷贫,配置被加載到Spring環(huán)境中认然。默認(rèn)情況下,配置存儲(chǔ)在/config命名空間中漫萄。根據(jù)應(yīng)用程序的名稱和模擬解析屬性的Spring Cloud Config順序的活動(dòng)配置文件卷员,創(chuàng)建多個(gè)PropertySource實(shí)例。例如腾务,名為“testApp”的應(yīng)用程序和“dev”配置文件將創(chuàng)建以下屬性源:
config/testApp,dev
config/testApp
config/application,dev
config/application
最具體的物業(yè)來(lái)源位于頂部毕骡,底部最不具體。Properties是config/application命名空間適用于使用zookeeper進(jìn)行配置的所有應(yīng)用程序窑睁。config/testApp命名空間中的Properties僅適用于名為“testApp”的服務(wù)實(shí)例挺峡。
配置當(dāng)前在應(yīng)用程序啟動(dòng)時(shí)被讀取。發(fā)送HTTP POST到/refresh將導(dǎo)致重新加載配置担钮。觀看配置命名空間(Zookeeper支持))目前尚未實(shí)現(xiàn)橱赠,但將來(lái)將會(huì)添加到此項(xiàng)目中。
如何激活
包括對(duì)org.springframework.cloud:spring-cloud-starter-zookeeper-config的依賴將啟用將配置Spring Cloud Zookeeper配置的自動(dòng)配置箫津。
定制
Zookeeper可以使用以下屬性自定義配置:
bootstrap.yml
spring:
cloud:
zookeeper:
config:
enabled: true
root: configuration
defaultContext: apps
profileSeparator: '::'
enabled將此值設(shè)置為“false”將禁用Zookeeper配置
root設(shè)置配置值的基本命名空間
defaultContext設(shè)置所有應(yīng)用程序使用的名稱
profileSeparator設(shè)置用于使用配置文件在屬性源中分隔配置文件名稱的分隔符的值
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
Spring Cloud Security提供了一組用于構(gòu)建安全應(yīng)用程序和服務(wù)的原語(yǔ),最小化苏遥”模可以從外部(或集中)高度配置的聲明式模型適用于通常使用中央契約管理服務(wù)的大型合作遠(yuǎn)程組件系統(tǒng)的實(shí)現(xiàn)。在像Cloud Foundry這樣的服務(wù)平臺(tái)上也很容易使用田炭∈Τ基于Spring Boot和Spring安全性O(shè)Auth2,我們可以快速創(chuàng)建實(shí)現(xiàn)常見(jiàn)模式的系統(tǒng)教硫,如單點(diǎn)登錄叨吮,令牌中繼和令牌交換。
注意
Spring Cloud根據(jù)非限制性Apache 2.0許可證發(fā)布瞬矩。如果您想為文檔的這一部分做出貢獻(xiàn)茶鉴,或者發(fā)現(xiàn)錯(cuò)誤,請(qǐng)?jiān)?a target="_blank" rel="nofollow">github中找到項(xiàng)目中的源代碼和問(wèn)題跟蹤器景用。
快速開(kāi)始
OAuth2單一登錄
這是一個(gè)具有HTTP基本身份驗(yàn)證和單個(gè)用戶帳戶的Spring Cloud“Hello World”應(yīng)用程序
app.groovy
@Grab('spring-boot-starter-security')
@Controller
class Application {
@RequestMapping('/')
String home() {
'Hello World'
}
}
您可以使用spring run app.groovy運(yùn)行它涵叮,并觀察日志的密碼(用戶名為“用戶”)。到目前為止伞插,這只是一個(gè)Spring Boot應(yīng)用程序的默認(rèn)設(shè)置割粮。
這是一個(gè)使用OAuth2 SSO的Spring Cloud應(yīng)用程序:
app.groovy
@Controller
@EnableOAuth2Sso
class Application {
@RequestMapping('/')
String home() {
'Hello World'
}
}
指出不同?這個(gè)應(yīng)用程序的行為實(shí)際上與之前的一樣媚污,因?yàn)樗恢浪荗Auth2的信譽(yù)穆刻。
您可以很容易地在github注冊(cè)一個(gè)應(yīng)用程序,所以如果你想要一個(gè)生產(chǎn)應(yīng)用程序在你自己的域上嘗試杠步。如果您很樂(lè)意在localhost:8080上測(cè)試氢伟,那么請(qǐng)?jiān)趹?yīng)用程序配置中設(shè)置這些屬性:
application.yml
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
運(yùn)行上面的應(yīng)用程序,它將重定向到github進(jìn)行授權(quán)幽歼。如果您已經(jīng)登錄github朵锣,您甚至不會(huì)注意到它已經(jīng)通過(guò)身份驗(yàn)證。只有您的應(yīng)用程序在8080端口上運(yùn)行甸私,這些憑據(jù)才會(huì)起作用诚些。
要限制客戶端在獲取訪問(wèn)令牌時(shí)要求的范圍,您可以設(shè)置security.oauth2.client.scope(逗號(hào)分隔或YAML中的數(shù)組)。默認(rèn)情況下,作用域?yàn)榭展墒跈?quán)服務(wù)器確定默認(rèn)值是什么忧换,通常取決于客戶端注冊(cè)中的設(shè)置。
注意
上面的例子都是Groovy腳本哗脖。如果要在Java(或Groovy)中編寫相同的代碼,則需要將Spring Security OAuth2添加到類路徑中(例如,請(qǐng)參閱此處的示例)家破。
OAuth2受保護(hù)資源
您要使用OAuth2令牌保護(hù)API資源?這是一個(gè)簡(jiǎn)單的例子(與上面的客戶端配對(duì)):
app.groovy
@Grab('spring-cloud-starter-security')
@RestController
@EnableResourceServer
class Application {
@RequestMapping('/')
def home() {
[message: 'Hello World']
}
}
和
application.yml
security:
oauth2:
resource:
userInfoUri: https://api.github.com/user
preferTokenInfo: false
更多詳情
單點(diǎn)登錄
注意
所有OAuth2 SSO和資源服務(wù)器功能在版本1.3中移動(dòng)到Spring Boot购岗。您可以在Spring Boot用戶指南中找到文檔汰聋。
令牌中繼
令牌中繼是OAuth2消費(fèi)者充當(dāng)客戶端,并將傳入令牌轉(zhuǎn)發(fā)到外發(fā)資源請(qǐng)求喊积。消費(fèi)者可以是純客戶端(如SSO應(yīng)用程序)或資源服務(wù)器烹困。
客戶端令牌中繼
如果您的應(yīng)用是面向OAuth2客戶端的用戶(即聲明為@EnableOAuth2Sso或@EnableOAuth2Client),那么它的請(qǐng)求范圍為spring security的OAuth2ClientContext乾吻。您可以從此上下文和自動(dòng)連線OAuth2ProtectedResourceDetails創(chuàng)建自己的OAuth2RestTemplate髓梅,然后上下文將始終向下轉(zhuǎn)發(fā)訪問(wèn)令牌,如果過(guò)期則自動(dòng)刷新訪問(wèn)令牌溶弟。(這些是Spring安全和Spring Boot的功能女淑。)
注意
如果您使用client_credentials令牌,則Spring Boot(1.4.1)不會(huì)自動(dòng)創(chuàng)建OAuth2ProtectedResourceDetails辜御。在這種情況下鸭你,您需要?jiǎng)?chuàng)建自己的ClientCredentialsResourceDetails并使用@ConfigurationProperties("security.oauth2.client")進(jìn)行配置。
客戶端令牌中繼在Zuul代理
如果您的應(yīng)用程式還有Spring Cloud Zuul嵌入式反向代理(使用@EnableZuulProxy)擒权,那么您可以要求將OAuth2訪問(wèn)令牌轉(zhuǎn)發(fā)到其正在代理的服務(wù)袱巨。因此,上述的SSO應(yīng)用程序可以簡(jiǎn)單地增強(qiáng):
app.groovy
@Controller
@EnableOAuth2Sso
@EnableZuulProxy
class Application {
}
并且(除了將用戶登錄并抓取令牌之外)將下載的身份驗(yàn)證令牌傳遞到/proxy/*服務(wù)碳抄。如果這些服務(wù)是用@EnableResourceServer實(shí)現(xiàn)的愉老,那么他們將在正確的標(biāo)題中獲得一個(gè)有效的標(biāo)記。
它是如何工作的剖效?@EnableOAuth2Sso注釋引入spring-cloud-starter-security(您可以在傳統(tǒng)應(yīng)用程序中手動(dòng)執(zhí)行)嫉入,而這又會(huì)觸發(fā)一個(gè)ZuulFilter的自動(dòng)配置焰盗,該屬性本身被激活,因?yàn)閆uul在classpath(通過(guò)@EnableZuulProxy)咒林。該過(guò)濾器僅從當(dāng)前已認(rèn)證的用戶提取訪問(wèn)令牌熬拒,并將其放入下游請(qǐng)求的請(qǐng)求頭中。
資源服務(wù)器令牌中繼
如果您的應(yīng)用有@EnableResourceServer垫竞,您可能希望將傳入令牌下載到其他服務(wù)澎粟。如果您使用RestTemplate聯(lián)系下游服務(wù),那么這只是如何使用正確的上下文創(chuàng)建模板的問(wèn)題欢瞪。
如果您的服務(wù)使用UserInfoTokenServices驗(yàn)證傳入令牌(即正在使用security.oauth2.user-info-uri配置))活烙,則可以使用自動(dòng)連線OAuth2ClientContext創(chuàng)建OAuth2RestTemplate(將由身份驗(yàn)證過(guò)程之前它遇到后端代碼)。相等(使用Spring Boot 1.4)遣鼓,您可以在配置中注入U(xiǎn)serInfoRestTemplateFactory并抓取其中的OAuth2RestTemplate啸盏。例如:
MyConfiguration.java
@Bean
public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) {
return factory.getUserInfoRestTemplate();
}
然后,此休息模板將具有由身份驗(yàn)證過(guò)濾器使用的OAuth2ClientContext(請(qǐng)求作用域)相同譬正,因此您可以使用它來(lái)發(fā)送具有相同訪問(wèn)令牌的請(qǐng)求宫补。
如果您的應(yīng)用沒(méi)有使用UserInfoTokenServices,但仍然是客戶端(即聲明@EnableOAuth2Client或@EnableOAuth2Sso)曾我,則使用Spring安全云任何OAuth2RestOperations粉怕,用戶從@Autowired@OAuth2Context也會(huì)轉(zhuǎn)發(fā)令牌。此功能默認(rèn)實(shí)現(xiàn)為MVC處理程序攔截器抒巢,因此它僅適用于Spring MVC贫贝。如果您不使用MVC,可以使用包含AccessTokenContextRelay的自定義過(guò)濾器或AOP攔截器來(lái)提供相同的功能蛉谜。
以下是一個(gè)基本示例稚晚,顯示了使用其他地方創(chuàng)建的自動(dòng)連線休息模板(“foo.com”是一個(gè)資源服務(wù)器,接受與周圍應(yīng)用程序相同的令牌):
MyController.java
@Autowired
private OAuth2RestOperations restTemplate;
@RequestMapping("/relay")
public String relay() {
ResponseEntity response =
restTemplate.getForEntity("https://foo.com/bar", String.class);
return "Success! (" + response.getBody() + ")";
}
如果您不想轉(zhuǎn)發(fā)令牌(這是一個(gè)有效的選擇型诚,因?yàn)槟赡芟M宰约旱纳矸荻皇窍蚰l(fā)送令牌的客戶端)客燕,那么您只需要?jiǎng)?chuàng)建自己的OAuth2Context的自動(dòng)裝配默認(rèn)值沸手。
Feign客戶端也會(huì)選擇使用OAuth2ClientContext的攔截器沼死,如果它是可用的,那么他們還應(yīng)該在RestTemplate將要執(zhí)行的令牌中繼巴刻。
配置Zuul代理下游的認(rèn)證
您可以通過(guò)proxy.auth.*設(shè)置控制@EnableZuulProxy下游的授權(quán)行為涵紊。例:
application.yml
proxy:
auth:
routes:
customers: oauth2
stores: passthru
recommendations: none
在此示例中傍妒,“客戶”服務(wù)獲取OAuth2令牌中繼,“存儲(chǔ)”服務(wù)獲取傳遞(授權(quán)頭只是通過(guò)下游)摸柄,“建議”服務(wù)已刪除其授權(quán)頭颤练。如果有令牌可用,則默認(rèn)行為是執(zhí)行令牌中繼驱负,否則為passthru嗦玖。
有關(guān)詳細(xì)信息患雇,請(qǐng)參閱ProxyAuthenticationProperties。
Spring Cloud為Cloud Foundry
Cloudfoundry的Spring Cloud可以輕松地在Cloud Foundry(平臺(tái)即服務(wù))中運(yùn)行Spring Cloud應(yīng)用程序踏揣。Cloud Foundry有一個(gè)“服務(wù)”的概念庆亡,它是“綁定”到應(yīng)用程序的中間件,本質(zhì)上為其提供包含憑據(jù)的環(huán)境變量(例如捞稿,用于服務(wù)的位置和用戶名)。
spring-cloud-cloudfoundry-web項(xiàng)目為Cloud Foundry中的webapps的一些增強(qiáng)功能提供基本支持:自動(dòng)綁定到單點(diǎn)登錄服務(wù)拼缝,并可選擇啟用粘性路由進(jìn)行發(fā)現(xiàn)娱局。
spring-cloud-cloudfoundry-discovery項(xiàng)目提供Spring Cloud CommonsDiscoveryClient的實(shí)施,因此您可以@EnableDiscoveryClient并將您的憑據(jù)提供為spring.cloud.cloudfoundry.discovery.[email,password]咧七,然后直接或通過(guò)LoadBalancerClient使用DiscoveryClient/}(如果您沒(méi)有連接到Pivotal Web Services衰齐,則也為*.url)。
第一次使用它時(shí)继阻,發(fā)現(xiàn)客戶端可能很慢耻涛,因?yàn)樗仨殢腃loud Foundry獲取訪問(wèn)令牌。
發(fā)現(xiàn)
以下是Cloud Foundry發(fā)現(xiàn)的Spring Cloud應(yīng)用程序:
app.groovy
@Grab('org.springframework.cloud:spring-cloud-cloudfoundry')
@RestController
@EnableDiscoveryClient
class Application {
@Autowired
DiscoveryClient client
@RequestMapping('/')
String home() {
'Hello from ' + client.getLocalServiceInstance()
}
}
如果您運(yùn)行它沒(méi)有任何服務(wù)綁定:
$ spring jar app.jar app.groovy
$ cf push -p app.jar
它將在主頁(yè)中顯示其應(yīng)用程序名稱瘟檩。
DiscoveryClient可以根據(jù)身份驗(yàn)證的憑據(jù)列出空間中的所有應(yīng)用程序抹缕,其中的空間默認(rèn)為客戶端運(yùn)行的空間(如果有的話)。如果組織和空間都不配置墨辛,則它們將根據(jù)Cloud Foundry中的用戶配置文件進(jìn)行默認(rèn)卓研。
單點(diǎn)登錄
注意
所有OAuth2 SSO和資源服務(wù)器功能在版本1.3中移動(dòng)到Spring Boot。您可以在Spring Boot用戶指南中找到文檔睹簇。
該項(xiàng)目提供從CloudFoundry服務(wù)憑據(jù)到Spring Boot功能的自動(dòng)綁定奏赘。如果您有一個(gè)稱為“sso”的CloudFoundry服務(wù),例如太惠,使用包含“client_id”磨淌,“client_secret”和“auth_domain”的憑據(jù),它將自動(dòng)綁定到您使用@EnableOAuth2Sso啟用的Spring OAuth2客戶端來(lái)自Spring Boot)凿渊×褐唬可以使用spring.oauth2.sso.serviceId對(duì)服務(wù)的名稱進(jìn)行參數(shù)化。
Spring Cloud Contract
文獻(xiàn)作者:Adam Dudczak嗽元,MathiasDüsterh?ft敛纲,Marcin Grzejszczak,Dennis Kieselhorst剂癌,JakubKubryński淤翔,Karol Lassak,Olga Maciaszek-Sharma佩谷,MariuszSmyku?a旁壮,Dave Syer
Dalston.RELEASE
Spring Cloud Contract
您始終需要的是將新功能推向分布式系統(tǒng)中的新應(yīng)用程序或服務(wù)的信心监嗜。該項(xiàng)目為Spring應(yīng)用程序中的消費(fèi)者驅(qū)動(dòng)Contracts和服務(wù)架構(gòu)提供支持,涵蓋了一系列用于編寫測(cè)試的選項(xiàng)抡谐,將其作為資產(chǎn)發(fā)布裁奇,聲稱生產(chǎn)者和消費(fèi)者保留合同用于HTTP和消息的交互。
Spring Cloud Contract WireMock
模塊讓您有可能使用WireMock使用嵌入在Spring Boot應(yīng)用的“環(huán)境”服務(wù)器不同的服務(wù)器麦撵。查看樣品了解更多詳情刽肠。
重要
Spring Cloud發(fā)布列表BOM導(dǎo)入spring-cloud-contract-dependencies,這反過(guò)來(lái)又排除了WireMock所需的依賴關(guān)系。這可能導(dǎo)致一種情況,即使你不使用Spring Cloud Contract潘酗,那么你的依賴將會(huì)受到影響。
如果您有一個(gè)使用Tomcat作為嵌入式服務(wù)器的Spring Boot應(yīng)用程序(默認(rèn)為spring-boot-starter-web))躺涝,那么您可以簡(jiǎn)單地將spring-cloud-contract-wiremock添加到類路徑中并添加@AutoConfigureWireMock,以便可以在測(cè)試中使用Wiremock扼雏。Wiremock作為存根服務(wù)器運(yùn)行坚嗜,您可以使用Java API或通過(guò)靜態(tài)JSON聲明來(lái)注冊(cè)存根行為,作為測(cè)試的一部分诗充。這是一個(gè)簡(jiǎn)單的例子:
@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!");
}
}
要使用@AutoConfigureWireMock(port=9999)(例如)啟動(dòng)不同端口上的存根服務(wù)器苍蔬,并且對(duì)于隨機(jī)端口使用值0.存根服務(wù)器端口將在測(cè)試應(yīng)用程序上下文中綁定為“wiremock.server.port”。使用@AutoConfigureWireMock將一個(gè)類型為WiremockConfiguration的bean添加到測(cè)試應(yīng)用程序上下文中其障,它將被緩存在具有相同上下文的方法和類之間银室,就像一般的Spring集成測(cè)試一樣。
自動(dòng)注冊(cè)存根
如果您使用@AutoConfigureWireMock励翼,則它將從文件系統(tǒng)或類路徑注冊(cè)WireMock JSON存根蜈敢,默認(rèn)情況下為file:src/test/resources/mappings。您可以使用注釋中的stubs屬性自定義位置汽抚,這可以是資源模式(ant-style)或目錄抓狭,在這種情況下,附加*/.json造烁。例:
@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!");
}
}
注意
實(shí)際上否过,WireMock總是從src/test/resources/mappings中加載映射以及stubs屬性中的自定義位置。要更改此行為惭蟋,您還必須如下所述指定文件根苗桂。
使用文件指定存根體
WireMock可以從類路徑或文件系統(tǒng)上的文件讀取響應(yīng)體。在這種情況下告组,您將在JSON DSL中看到響應(yīng)具有“bodyFileName”而不是(文字)“body”煤伟。默認(rèn)情況下,相對(duì)于根目錄src/test/resources/__files解析文件。要自定義此位置便锨,您可以將@AutoConfigureWireMock注釋中的files屬性設(shè)置為父目錄的位置(即围辙,位置__files是子目錄)。您可以使用Spring資源符號(hào)來(lái)引用file:…?或classpath:…?位置(但不支持通用URL)放案∫ǎ可以給出值列表,并且WireMock將在需要查找響應(yīng)體時(shí)解析存在的第一個(gè)文件吱殉。
注意
當(dāng)配置files根時(shí)掸冤,它會(huì)影響自動(dòng)加載存根(它們來(lái)自稱為“映射”的子目錄中的根位置)。files的值對(duì)從stubs屬性明確加載的存根沒(méi)有影響考婴。
替代方法:使用JUnit規(guī)則
對(duì)于更常規(guī)的WireMock體驗(yàn)贩虾,使用JUnit@Rules啟動(dòng)和停止服務(wù)器,只需使用WireMockSpring便利類來(lái)獲取Options實(shí)例:
@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!");
}
}
使用@ClassRule表示服務(wù)器將在此類中的所有方法后關(guān)閉沥阱。
WireMock和Spring MVC模擬器
Spring Cloud Contract提供了一個(gè)方便的類,可以將JSON WireMock存根加載到SpringMockRestServiceServer中伊群。以下是一個(gè)例子:
@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();
}
}
baseUrl前面是所有模擬調(diào)用考杉,stubs()方法將一個(gè)存根路徑資源模式作為參數(shù)。所以在這個(gè)例子中舰始,/stubs/resource.json定義的存根被加載到模擬服務(wù)器中崇棠,所以如果RestTemplate被要求訪問(wèn)http://example.org/,那么它將得到所聲明的響應(yīng)丸卷≌硐。可以指定多個(gè)存根模式,每個(gè)可以是一個(gè)目錄(對(duì)于所有“.json”的遞歸列表)或一個(gè)固定的文件名(如上例所示)或一個(gè)螞蟻樣式模式谜嫉。JSON格式是通常的WireMock格式萎坷,您可以在WireMock網(wǎng)站上閱讀。
目前沐兰,我們支持Tomcat哆档,Jetty和Undertow作為Spring Boot嵌入式服務(wù)器,而Wiremock本身對(duì)特定版本的Jetty(目前為9.2)具有“本機(jī)”支持住闯。要使用本地Jetty瓜浸,您需要添加本機(jī)線程依賴關(guān)系耳奕,并排除Spring Boot容器(如果有的話)倔约。
使用RestDocs生成存根
Spring RestDocs可用于為具有Spring MockMvc或RestEasy的HTTP API生成文檔(例如掌栅,asciidoctor格式)督禽。在生成API文檔的同時(shí)庇楞,還可以使用Spring Cloud Contract WireMock生成WireMock存根荐捻。只需編寫正常的RestDocs測(cè)試用例痰哨,并使用@AutoConfigureRestDocs在restdocs輸出目錄中自動(dòng)存儲(chǔ)存根甥材。例如:
@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"));
}
}
從此測(cè)試將在“target / snippets / stubs / resource.json”上生成一個(gè)WireMock存根。它將所有GET請(qǐng)求與“/ resource”路徑相匹配谢床。
沒(méi)有任何其他配置兄一,這將創(chuàng)建一個(gè)存根與HTTP方法的請(qǐng)求匹配器和除“主機(jī)”和“內(nèi)容長(zhǎng)度”之外的所有頭。為了更準(zhǔn)確地匹配請(qǐng)求识腿,例如要匹配POST或PUT的正文出革,我們需要明確創(chuàng)建一個(gè)請(qǐng)求匹配器。這將做兩件事情:1)創(chuàng)建一個(gè)只匹配您指定的方式的存根渡讼,2)斷言測(cè)試用例中的請(qǐng)求也匹配相同的條件骂束。
主要的入口點(diǎn)是WireMockRestDocs.verify(),可以替代document()便利方法成箫。例如:
@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"));
}
}
所以這個(gè)合同是說(shuō):任何有效的POST與“id”字段將得到與本測(cè)試相同的響應(yīng)展箱。您可以將來(lái)電鏈接到.jsonPath()以添加其他匹配器。如果您不熟悉JayWay文檔混驰,可以幫助您加快JSON路徑的速度。
您也可以使用WireMock API來(lái)驗(yàn)證請(qǐng)求是否與創(chuàng)建的存根匹配皂贩,而不是使用jsonPath和contentType方法栖榨。例:
@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"));
}
WireMock API是豐富的 - 您可以通過(guò)正則表達(dá)式以及json路徑來(lái)匹配頭文件,查詢參數(shù)和請(qǐng)求正文明刷,因此這可以用于創(chuàng)建具有更廣泛參數(shù)的存根婴栽。上面的例子會(huì)生成一個(gè)這樣的stub:
后resource.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"
}
}
}
注意
您可以使用wiremock()方法或jsonPath()和contentType()方法創(chuàng)建請(qǐng)求匹配器,但不能同時(shí)使用兩者辈末。
在消費(fèi)方面愚争,假設(shè)上面生成的resource.json可以在類路徑中使用,您可以使用WireMock以多種不同的方式創(chuàng)建一個(gè)存根挤聘,其中包括上述使用@AutoConfigureWireMock(stubs="classpath:resource.json")的描述轰枝。
使用RestDocs生成Contracts
可以使用Spring RestDocs生成的另一件事是Spring Cloud Contract DSL文件和文檔。如果您將其與Spring Cloud WireMock相結(jié)合檬洞,那么您將獲得合同和存根狸膏。
小費(fèi)
您可能會(huì)想知道為什么該功能在WireMock模塊中。來(lái)想一想添怔,它確實(shí)有道理湾戳,因?yàn)橹簧珊贤⑶也簧纱娓蜎](méi)有意義。這就是為什么我們建議做這兩個(gè)广料。
我們來(lái)想象下面的測(cè)試:
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()));
這將導(dǎo)致在上一節(jié)中介紹的存根的創(chuàng)建砾脑,合同將被生成和文檔文件。
合同將被稱為index.groovy艾杏,看起來(lái)更像是這樣韧衣。
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())
}
}
}
生成的文檔(Asciidoc的示例)將包含格式化的合同(此文件的位置將為index/dsl-contract.adoc)。
Spring Cloud Contract驗(yàn)證者
介紹
重要
1.1.0版中已棄用的Accurest項(xiàng)目的文檔可在此處獲取。
小費(fèi)
Accurest項(xiàng)目最初是由Marcin Grzejszczak和Jakub Kubrynski(codearte.io)
只是為了簡(jiǎn)短說(shuō)明 - Spring Cloud Contract驗(yàn)證程序是一種能夠啟用消費(fèi)者驅(qū)動(dòng)合同(CDC)開(kāi)發(fā)基于JVM的應(yīng)用程序的工具畅铭。它與合同定義語(yǔ)言(DSL)一起提供氏淑。合同定義用于生成以下資源:
在客戶端代碼(客戶端測(cè)試)上進(jìn)行集成測(cè)試時(shí),WireMock將使用JSON存根定義硕噩。測(cè)試代碼仍然需要手動(dòng)編寫假残,測(cè)試數(shù)據(jù)由Spring Cloud Contract驗(yàn)證器生成。
消息傳遞路由炉擅,如果你使用一個(gè)辉懒。我們正在與Spring Integration,Spring Cloud Stream,Spring AMQP和Apache Camel進(jìn)行整合耻瑟。然而,您可以設(shè)置自己的集成耿币,如果你想
驗(yàn)收測(cè)試(在JUnit或Spock中)用于驗(yàn)證API的服務(wù)器端實(shí)現(xiàn)是否符合合同(服務(wù)器測(cè)試)梳杏。完全測(cè)試由Spring Cloud Contract驗(yàn)證器生成。
Spring Cloud Contract驗(yàn)證者將TDD移動(dòng)到軟件體系結(jié)構(gòu)的層次淹接。
Spring Cloud Contract視頻
您可以查看華沙JUG關(guān)于Spring Cloud Contract的視頻:點(diǎn)擊此處查看視頻
為什么十性?
讓我們假設(shè)我們有一個(gè)由多個(gè)微服務(wù)組成的系統(tǒng):
測(cè)試問(wèn)題
如果我們想測(cè)試應(yīng)用程序在左上角,如果它可以與其他服務(wù)通信塑悼,那么我們可以做兩件事之一:
部署所有微服務(wù)器并執(zhí)行端到端測(cè)試
模擬其他微型服務(wù)單元/集成測(cè)試
兩者都有其優(yōu)點(diǎn)劲适,但也有很多缺點(diǎn)。我們來(lái)關(guān)注后者厢蒜。
部署所有微服務(wù)器并執(zhí)行端到端測(cè)試
優(yōu)點(diǎn):
模擬生產(chǎn)
測(cè)試服務(wù)之間的真實(shí)溝通
缺點(diǎn):
要測(cè)試一個(gè)微服務(wù)器霞势,我們將不得不部署6個(gè)微服務(wù)器,幾個(gè)數(shù)據(jù)庫(kù)等斑鸦。
將進(jìn)行測(cè)試的環(huán)境將被鎖定用于一套測(cè)試(即沒(méi)有人能夠在此期間運(yùn)行測(cè)試)愕贡。
長(zhǎng)跑
非常遲的反饋
非常難調(diào)試
模擬其他微型服務(wù)單元/集成測(cè)試
優(yōu)點(diǎn):
非常快的反饋
沒(méi)有基礎(chǔ)架構(gòu)要求
缺點(diǎn):
服務(wù)的實(shí)現(xiàn)者創(chuàng)建存根巷屿,因此它們可能與現(xiàn)實(shí)無(wú)關(guān)
您可以通過(guò)測(cè)試和生產(chǎn)不合格進(jìn)行生產(chǎn)
為了解決上述問(wèn)題固以,Spring Cloud Contract Stub Runner的驗(yàn)證器被創(chuàng)建。他們的主要思想是給你非持鼋恚快的反饋嘴纺,而不需要建立整個(gè)微服務(wù)的世界。
如果您在存根上工作浓冒,那么您需要的唯一應(yīng)用是應(yīng)用程序直接使用的應(yīng)用程序。
Spring Cloud Contract驗(yàn)證者確定您使用的存根是由您正在調(diào)用的服務(wù)創(chuàng)建的尖坤。此外稳懒,如果您可以使用它們,這意味著它們是針對(duì)生產(chǎn)者的一方進(jìn)行測(cè)試的慢味。換句話說(shuō) - 你可以信任這些存根场梆。
目的
Spring Cloud Contract驗(yàn)證器與Stub Runner的主要目的是:
確保WireMock / Messaging存根(在開(kāi)發(fā)客戶端時(shí)使用)正在完全實(shí)際執(zhí)行服務(wù)器端實(shí)現(xiàn),
推廣ATDD方法和微服務(wù)架構(gòu)風(fēng)格纯路,
提供一種發(fā)布雙方立即可見(jiàn)的合同變更的方法或油,
生成服務(wù)器端使用的樣板測(cè)試代碼。
重要
Spring Cloud Contract驗(yàn)證者的目的不是開(kāi)始在合同中編寫業(yè)務(wù)功能驰唬。我們假設(shè)我們有一個(gè)欺詐檢查的商業(yè)用例顶岸。如果一個(gè)用戶因?yàn)?00個(gè)不同的原因而被欺騙腔彰,我們假設(shè)你會(huì)創(chuàng)建2個(gè)合同。一個(gè)為正面辖佣,一個(gè)為負(fù)面欺詐案霹抛。合同測(cè)試用于測(cè)試應(yīng)用程序之間的合同,而不是模擬完整行為卷谈。
客戶端
在測(cè)試期間杯拐,您希望啟動(dòng)并運(yùn)行一個(gè)模擬服務(wù)Y的WireMock實(shí)例/消息傳遞路由。您希望為該實(shí)例提供適當(dāng)?shù)拇娓x世蔗。該存根定義將需要有效端逼,并且也應(yīng)在服務(wù)器端可重用。
總結(jié):在這方面污淋,在存根定義中顶滩,您可以使用模式進(jìn)行請(qǐng)求存根,并需要確切的響應(yīng)值芙沥。
服務(wù)器端
作為開(kāi)發(fā)您的存根的服務(wù)Y诲祸,您需要確保它實(shí)際上類似于您的具體實(shí)現(xiàn)。您不能以某種方式存在您的存根行為而昨,并且您的生產(chǎn)應(yīng)用程序以不同的方式運(yùn)行救氯。
這就是為什么會(huì)生成提供的存根驗(yàn)收測(cè)試,這將確保您的應(yīng)用程序的行為與您在存根中定義的相同歌憨。
總結(jié):在這方面着憨,在存根定義中,您需要精確的值作為請(qǐng)求,并可以使用模式/方法進(jìn)行響應(yīng)驗(yàn)證准谚。
逐步向CDC指導(dǎo)
舉一個(gè)欺詐檢測(cè)和貸款發(fā)行流程的例子。業(yè)務(wù)情景是這樣的动漾,我們想向人們發(fā)放貸款丁屎,但不希望他們從我們那里偷錢。目前我們的系統(tǒng)實(shí)施給大家貸款旱眯。
假設(shè)Loan Issuance是Fraud Detection服務(wù)器的客戶端晨川。在目前的沖刺中证九,我們需要開(kāi)發(fā)一個(gè)新的功能 - 如果客戶想要借到太多的錢,那么我們將他們標(biāo)記為欺詐共虑。
技術(shù)說(shuō)明 - 欺詐檢測(cè)將具有工件IDhttp-server愧怜,貸款發(fā)行http-client,兩者都具有組IDcom.example妈拌。
社會(huì)聲明 - 客戶端和服務(wù)器開(kāi)發(fā)團(tuán)隊(duì)都需要直接溝通拥坛,并在整個(gè)過(guò)程中討論變更。CDC是關(guān)于溝通的尘分。
小費(fèi)
在這種情況下,合同的所有權(quán)在生產(chǎn)者方面培愁。這意味著物理上所有的合同都存在于生產(chǎn)者存儲(chǔ)庫(kù)中
技術(shù)說(shuō)明
如果使用SNAPSHOT/里程碑/版本候選版本著摔,請(qǐng)將以下部分添加到您的
Maven的
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
spring-releases
Spring Releases
https://repo.spring.io/release
false
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
spring-releases
Spring Releases
https://repo.spring.io/release
false
搖籃
repositories {
mavenCentral()
mavenLocal()
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
maven { url "http://repo.spring.io/release" }
}
消費(fèi)方(貸款發(fā)行)
作為貸款發(fā)行服務(wù)(欺詐檢測(cè)服務(wù)器的消費(fèi)者)的開(kāi)發(fā)人員:
通過(guò)對(duì)您的功能進(jìn)行測(cè)試開(kāi)始做TDD
@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");
}
我們剛剛寫了一個(gè)關(guān)于我們新功能的測(cè)試。如果收到大額的貸款申請(qǐng)定续,我們應(yīng)該拒絕有一些描述的貸款申請(qǐng)谍咆。
寫入缺少的實(shí)現(xiàn)
在某些時(shí)間點(diǎn),您需要向欺詐檢測(cè)服務(wù)發(fā)送請(qǐng)求私股。我們假設(shè)我們要發(fā)送包含客戶端ID和要從我們借款的金額的請(qǐng)求摹察。我們想通過(guò)PUT方法將其發(fā)送到/fraudcheck網(wǎng)址。
ResponseEntity response =
restTemplate.exchange("http://localhost:" + port + "/fraudcheck", HttpMethod.PUT,
new HttpEntity<>(request, httpHeaders),
FraudServiceResponse.class);
為了簡(jiǎn)單起見(jiàn)庇茫,我們已將8080的欺詐檢測(cè)服務(wù)端口硬編碼港粱,我們的應(yīng)用程序正在8090上運(yùn)行。
如果我們開(kāi)始寫測(cè)試旦签,顯然會(huì)因?yàn)槎丝?080上沒(méi)有運(yùn)行而打斷查坪。
在本地克隆欺詐檢測(cè)服務(wù)存儲(chǔ)庫(kù)
我們將開(kāi)始玩服務(wù)器端的合同。這就是為什么我們需要先克隆它宁炫。
git clone https://your-git-server.com/server-side.git local-http-server-repo
在欺詐檢測(cè)服務(wù)的回購(gòu)中本地定義合同
作為消費(fèi)者偿曙,我們需要確定我們想要實(shí)現(xiàn)的目標(biāo)。我們需要制定我們的期望羔巢。這就是為什么我們寫下面的合同望忆。
重要
我們將合同放在src/test/resources/contract/fraud下。fraud文件夾是重要的竿秆,因?yàn)槲覀儗⒃谏a(chǎn)者的測(cè)試基類中引用該文件夾启摄。
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.*`
*/
合同使用靜態(tài)類型的Groovy DSL編寫。你可能想知道這些value(client(…?), server(…?))部分是什么幽钢。通過(guò)使用這種符號(hào)Spring Cloud Contract歉备,您可以定義動(dòng)態(tài)的JSON / URL /等的部分。在標(biāo)識(shí)符或時(shí)間戳的情況下匪燕,您不想硬編碼一個(gè)值蕾羊。你想允許一些不同的值范圍喧笔。這就是為什么對(duì)于消費(fèi)者端,您可以設(shè)置與這些值匹配的正則表達(dá)式龟再。您可以通過(guò)地圖符號(hào)或帶插值的String來(lái)提供身體书闸。有關(guān)更多信息,請(qǐng)參閱文檔利凑。我們強(qiáng)烈推薦使用地圖符號(hào)浆劲!
小費(fèi)
了解地圖符號(hào)設(shè)置合同非常重要。請(qǐng)閱讀有關(guān)JSON的Groovy文檔
上述合同是雙方達(dá)成的協(xié)議:
如果發(fā)送了HTTP請(qǐng)求
端點(diǎn)/fraudcheck上的方法PUT
與clientPesel的正則表達(dá)式[0-9]{10}和loanAmount等于99999的JSON體
并且標(biāo)題Content-Type等于application/vnd.fraud.v1+json
那么HTTP響應(yīng)將被發(fā)送給消費(fèi)者
狀態(tài)為200
包含JSON體截碴,其中包含值為FRAUD的fraudCheckStatus字段和值為Amount too high的rejectionReason字段
和一個(gè)值為application/vnd.fraud.v1+json的Content-Type標(biāo)頭
一旦我們準(zhǔn)備好在集成測(cè)試中實(shí)際檢查API梳侨,我們需要在本地安裝存根
添加Spring Cloud Contract驗(yàn)證程序插件
我們可以添加Maven或Gradle插件 - 在這個(gè)例子中,我們將展示如何添加Maven日丹。首先我們需要添加Spring Cloud ContractBOM走哺。
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud-dependencies.version}
pom
import
接下來(lái)拭荤,Spring Cloud Contract VerifierMaven插件
org.springframework.cloud
spring-cloud-contract-maven-plugin
${spring-cloud-contract.version}
true
com.example.fraud
自添加插件后蛹磺,我們將從提供的合同中獲得Spring Cloud Contract Verifier功能:
生成并運(yùn)行測(cè)試
生產(chǎn)并安裝存根
我們不想生成測(cè)試只厘,因?yàn)槲覀冏鳛橄M(fèi)者墓赴,只想玩短線标捺。這就是為什么我們需要跳過(guò)測(cè)試生成和執(zhí)行氛堕。當(dāng)我們執(zhí)行:
cd local-http-server-repo
./mvnw clean install -DskipTests
在日志中顾瞪,我們將看到如下:
[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
這條線是非常重要的
[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
確認(rèn)http-server的存根已安裝在本地存儲(chǔ)庫(kù)中掷邦。
運(yùn)行集成測(cè)試
為了從自動(dòng)存根下載的Spring Cloud Contract Stub Runner功能中獲利汪诉,您必須在我們的消費(fèi)者端項(xiàng)目(Loan Application service)中執(zhí)行以下操作废恋。
添加Spring Cloud ContractBOM
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud-dependencies.version}
pom
import
將依賴關(guān)系添加到Spring Cloud Contract Stub Runner
org.springframework.cloud
spring-cloud-starter-contract-stub-runner
test
用@AutoConfigureStubRunner標(biāo)注你的測(cè)試課程。在注釋中扒寄,提供Stub Runner下載協(xié)作者存根的組ID和工件ID鱼鼓。離線工作開(kāi)關(guān)還可以離線使用協(xié)作者(可選步驟)。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, workOffline = true)
@DirtiesContext
public class LoanApplicationServiceTests {
現(xiàn)在如果你運(yùn)行測(cè)試你會(huì)看到這樣的:
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}]
這意味著Stub Runner找到了您的存根该编,并為具有組ID為com.example迄本,artifact idhttp-server,版本為0.0.1-SNAPSHOT的存根和stubs分類器的端口8080课竣。
檔案公關(guān)
我們到現(xiàn)在為止是一個(gè)迭代的過(guò)程嘉赎。我們可以玩合同,安裝在本地于樟,在消費(fèi)者身邊工作公条,直到我們對(duì)合同感到滿意。
一旦我們對(duì)結(jié)果感到滿意迂曲,測(cè)試通過(guò)將PR發(fā)布到服務(wù)器端靶橱。目前消費(fèi)者方面的工作已經(jīng)完成。
生產(chǎn)者方(欺詐檢測(cè)服務(wù)器)
作為欺詐檢測(cè)服務(wù)器(貸款發(fā)行服務(wù)的服務(wù)器)的開(kāi)發(fā)人員:
初步實(shí)施
作為提醒,您可以看到初始實(shí)現(xiàn)
@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);
}
接管公關(guān)
git checkout -b contract-change-pr master
git pull https://your-git-server.com/server-side-fork.git contract-change-pr
您必須添加自動(dòng)生成測(cè)試所需的依賴關(guān)系
org.springframework.cloud
spring-cloud-starter-contract-verifier
test
在Maven插件的配置中抓韩,我們傳遞了packageWithBaseClasses屬性
org.springframework.cloud
spring-cloud-contract-maven-plugin
${spring-cloud-contract.version}
true
com.example.fraud
重要
我們決定使用“約定”命名,方法是設(shè)置packageWithBaseClasses屬性鬓长。這意味著最后的兩個(gè)包將被組合成基本測(cè)試類的名稱谒拴。在我們這個(gè)例子中,這些合約是src/test/resources/contract/fraud涉波。由于我們沒(méi)有從contracts文件夾開(kāi)始有2個(gè)包英上,我們只挑選一個(gè)是fraud。我們正在添加Base后綴啤覆,我們正在大寫fraud苍日。這給了我們FraudBase測(cè)試類名稱。
這是因?yàn)樗猩傻臏y(cè)試都會(huì)擴(kuò)展該類窗声。在那里你可以設(shè)置你的Spring上下文或任何必要的相恃。在我們的例子中,我們使用Rest Assured MVC來(lái)啟動(dòng)服務(wù)器端FraudDetectionController笨觅。
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;
}
}
現(xiàn)在拦耐,如果你運(yùn)行./mvnw clean install,你會(huì)得到這樣的sth:
Results :
Tests in error:
ContractVerifierTest.validate_shouldMarkClientAsFraud:32 ? IllegalState Parsed...
這是因?yàn)槟幸粋€(gè)新的合同见剩,從中生成測(cè)試杀糯,并且由于您尚未實(shí)現(xiàn)該功能而失敗。自動(dòng)生成測(cè)試將如下所示:
@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");
}
您可以看到value(consumer(…?), producer(…?))塊中存在的所有producer()部分合同注入測(cè)試苍苞。
重要的是在生產(chǎn)者方面固翰,我們也在做TDD。我們有一個(gè)測(cè)試形式的期望羹呵。此測(cè)試正在向我們自己的應(yīng)用程序拍攝一個(gè)在合同中定義的URL骂际,標(biāo)題和主體的請(qǐng)求。它也期待響應(yīng)中非常精確地定義的值担巩。換句話說(shuō)方援,您是redgreen和refactor的red部分。將red轉(zhuǎn)換為green的時(shí)間涛癌。
寫入缺少的實(shí)現(xiàn)
現(xiàn)在犯戏,由于我們現(xiàn)在預(yù)期的輸入和預(yù)期的輸出是什么杭措,我們來(lái)寫這個(gè)缺少的實(shí)現(xiàn)饲漾。
@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);
}
如果再次執(zhí)行./mvnw clean install,測(cè)試將通過(guò)你弦。由于Spring Cloud Contract Verifier插件將測(cè)試添加到generated-test-sources弃衍,您可以從IDE中實(shí)際運(yùn)行這些測(cè)試呀非。
部署你的應(yīng)用程序
完成工作后,現(xiàn)在是部署變更的時(shí)候了。首先合并分支
git checkout master
git merge --no-ff contract-change-pr
git push origin master
那么我們假設(shè)你的CI將像./mvnw clean deploy一樣運(yùn)行岸裙,它將發(fā)布應(yīng)用程序和存根工件猖败。
消費(fèi)方(貸款發(fā)行)最后一步
作為貸款發(fā)行服務(wù)(欺詐檢測(cè)服務(wù)器的消費(fèi)者)的開(kāi)發(fā)人員:
合并分支到主
git checkout master
git merge --no-ff contract-change-pr
在線工作
現(xiàn)在,您可以禁用Spring Cloud Contract Stub Runner廣告的離線工作降允,以提供存儲(chǔ)庫(kù)與存根的位置恩闻。此時(shí),服務(wù)器端的存根將自動(dòng)從Nexus / Artifactory下載剧董。您可以關(guān)閉注釋中的workOffline參數(shù)的值幢尚。在下面你可以看到一個(gè)通過(guò)改變屬性實(shí)現(xiàn)相同的例子。
stubrunner:
ids: 'com.example:http-server-dsl:+:stubs:8080'
repositoryRoot: http://repo.spring.io/libs-snapshot
就是這樣翅楼!
依賴
添加依賴關(guān)系的最佳方法是使用正確的starter依賴關(guān)系尉剩。
對(duì)于stub-runner使用spring-cloud-starter-stub-runner,當(dāng)您使用插件時(shí)毅臊,只需添加spring-cloud-starter-contract-verifier理茎。
附加鏈接
以下可以找到與Spring Cloud Contract驗(yàn)證器和Stub Runner相關(guān)的一些資源。注意褂微,有些可以過(guò)時(shí)功蜓,因?yàn)镾pring Cloud Contract驗(yàn)證程序項(xiàng)目正在不斷發(fā)展。
閱讀
來(lái)自Marcin Grzejszczak關(guān)于Accurest的演講
來(lái)自Marcin Grzejszczak的博客的Accurest相關(guān)文章
來(lái)自Marcin Grzejszczak博客的Spring Cloud Contract相關(guān)文章
樣品
在這里可以找到一些樣品宠蚂。
常問(wèn)問(wèn)題
為什么使用Spring Cloud Contract驗(yàn)證器而不是X式撼?
目前Spring Cloud Contract驗(yàn)證器是基于JVM的工具。因此求厕,當(dāng)您已經(jīng)為JVM創(chuàng)建軟件時(shí)著隆,可能是您的第一選擇。這個(gè)項(xiàng)目有很多非常有趣的功能呀癣,但特別是其中一些絕對(duì)讓Spring Cloud Contract Verifier在消費(fèi)者驅(qū)動(dòng)合同(CDC)工具的“市場(chǎng)”上脫穎而出美浦。許多最有趣的是:
CDC可以通過(guò)消息傳遞
清晰易用,靜態(tài)DSL
可以將當(dāng)前的JSON文件粘貼到合同中项栏,并且僅編輯其元素
從定義的合同自動(dòng)生成測(cè)試
Stub Runner功能 - 存根在運(yùn)行時(shí)自動(dòng)從Nexus / Artifactory下載
Spring Cloud集成 - 集成測(cè)試不需要發(fā)現(xiàn)服務(wù)
這個(gè)值是(consumer()浦辨,producer())?
與存根相關(guān)的最大挑戰(zhàn)之一是可重用性沼沈。只有如果他們能夠被廣泛使用流酬,他們是否會(huì)服務(wù)于他們的目的。通常使得難點(diǎn)是請(qǐng)求/響應(yīng)元素的硬編碼值列另。例如日期或ids芽腾。想象下面的JSON請(qǐng)求
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
和JSON響應(yīng)
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
想象一下,通過(guò)更改系統(tǒng)中的時(shí)鐘或提供數(shù)據(jù)提供者的存根實(shí)現(xiàn)页衙,設(shè)置time字段的正確值(我們假設(shè)這個(gè)內(nèi)容是由數(shù)據(jù)庫(kù)生成的)所需的痛苦摊滔。這同樣涉及到稱為id的字段阴绢。你會(huì)創(chuàng)建一個(gè)UUID發(fā)生器的stubbed實(shí)現(xiàn)?沒(méi)有意義
所以作為一個(gè)消費(fèi)者艰躺,你想發(fā)送一個(gè)匹配任何形式的時(shí)間或任何UUID的請(qǐng)求呻袭。這樣,您的系統(tǒng)將照常工作 - 將生成數(shù)據(jù)腺兴,您不必將任何東西存入棒妨。假設(shè)在上述JSON的情況下,最重要的部分是body字段含长。您可以專注于其他領(lǐng)域,并提供匹配伏穆。換句話說(shuō)拘泞,你想要的存根是這樣工作的:
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "foo"
}
就響應(yīng)作為消費(fèi)者而言,您需要具有可操作性的具體價(jià)值枕扫。所以這樣的JSON是有效的
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
從前面的部分可以看出指郁,我們從合同中產(chǎn)生測(cè)試畦戒。所以從生產(chǎn)者的角度看,情況看起來(lái)差別很大。我們正在解析提供的合同赏表,在測(cè)試中我們想向您的端點(diǎn)發(fā)送一個(gè)真正的請(qǐng)求。因此拉宗,對(duì)于請(qǐng)求的生產(chǎn)者來(lái)說(shuō)咖祭,我們不能進(jìn)行任何匹配。我們需要具體的價(jià)值觀砾赔,使制片人的后臺(tái)能夠工作蝌箍。這樣的JSON將是一個(gè)有效的:
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
另一方面,從合同的有效性的角度來(lái)看暴心,響應(yīng)不一定必須包含time或id的具體值妓盲。假設(shè)您在生產(chǎn)者方面產(chǎn)生這些 - 再次,您必須做很多樁专普,以確保始終返回相同的值悯衬。這就是為什么從生產(chǎn)者那邊你可能想要的是以下回應(yīng):
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "bar"
}
那么您如何才能為消費(fèi)者提供一次匹配,并為生產(chǎn)者提供具體的價(jià)值檀夹,反之亦然筋粗?在Spring Cloud Contract中,我們?cè)试S您提供動(dòng)態(tài)值击胜。這意味著通信雙方可能有所不同亏狰。你可以傳遞值:
可以通過(guò)value方法
value(consumer(...), producer(...))
value(stub(...), test(...))
value(client(...), server(...))
或使用$()方法
$(consumer(...), producer(...))
$(stub(...), test(...))
$(client(...), server(...))
您可以在Contract DSL部分閱讀更多信息。
調(diào)用value()或$()告訴Spring Cloud Contract您將傳遞一個(gè)動(dòng)態(tài)值偶摔。在consumer()方法中暇唾,傳遞消費(fèi)者端(在生成的存根)中應(yīng)該使用的值。在producer()方法中,傳遞應(yīng)在生產(chǎn)者端使用的值(在生成的測(cè)試中)策州。
小費(fèi)
如果一方面你已經(jīng)通過(guò)了正則表達(dá)式瘸味,而你沒(méi)有通過(guò)另一方,那么對(duì)方就會(huì)自動(dòng)生成够挂。
大多數(shù)情況下旁仿,您將使用該方法與regex輔助方法。例如consumer(regex('[0-9]{10}'))孽糖。
總而言之枯冈,上述情景的合同看起來(lái)或多或少是這樣的(正則表達(dá)式的時(shí)間和UUID被簡(jiǎn)化,很可能是無(wú)效的办悟,但是我們希望在這個(gè)例子中保持很簡(jiǎn)單):
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"
])
}
}
重要
請(qǐng)閱讀與JSON相關(guān)的Groovy文檔尘奏,以了解如何正確構(gòu)建請(qǐng)求/響應(yīng)實(shí)體。
如何做Stubs版本控制病蛉?
API版本控制
讓我們嘗試回答一個(gè)真正意義上的版本控制的問(wèn)題炫加。如果你指的是API版本,那么有不同的方法铺然。
使用超媒體俗孝,鏈接,不要通過(guò)任何方式版本您的API
通過(guò)標(biāo)題/網(wǎng)址傳遞版本
我不會(huì)試圖回答一個(gè)方法更好的問(wèn)題魄健。無(wú)論適合您的需求赋铝,并允許您創(chuàng)造商業(yè)價(jià)值應(yīng)被挑選。
假設(shè)你做你的API版本沽瘦。在這種情況下柬甥,您應(yīng)該提供與您支持的許多版本一樣多的合同。您可以為每個(gè)版本創(chuàng)建一個(gè)子文件夾其垄,或?qū)⑵涓郊拥胶贤Q - 無(wú)論如何適合您苛蒲。
JAR版本控制
如果通過(guò)版本控制是指包含存根的JAR的版本,那么基本上有兩種主要方法绿满。
假設(shè)您正在進(jìn)行連續(xù)交付/部署臂外,這意味著您每次通過(guò)管道生成新版本的jar時(shí),該jar可以隨時(shí)進(jìn)行生產(chǎn)喇颁。例如你的jar版本看起來(lái)像這樣(它建立在20.10.2016在20:15:21):
1.0.0.20161020-201521-RELEASE
在這種情況下漏健,您生成的存根jar將看起來(lái)像這樣。
1.0.0.20161020-201521-RELEASE-stubs.jar
在這種情況下橘霎,您應(yīng)該在application.yml或@AutoConfigureStubRunner內(nèi)引用存根提供最新版本的存根蔫浆。您可以通過(guò)傳遞+號(hào)來(lái)做到這一點(diǎn)浪耘。例
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
如果版本控制是固定的(例如1.0.4.RELEASE或2.1.1)之斯,則必須設(shè)置jar版本的具體值。示例2.1.1祸挪。
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})
開(kāi)發(fā)者或生產(chǎn)者存根
您可以操作分類器,以針對(duì)其他服務(wù)的存根或部署到生產(chǎn)的存根的當(dāng)前開(kāi)發(fā)版本來(lái)運(yùn)行測(cè)試原环。如果您在構(gòu)建生產(chǎn)部署之后挠唆,使用prod-stubs分類器來(lái)更改構(gòu)建部署,那么您可以在一個(gè)案例中使用dev stub運(yùn)行測(cè)試嘱吗,另一個(gè)則使用prod stub進(jìn)行測(cè)試玄组。
使用開(kāi)發(fā)版存根的測(cè)試示例
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
使用生產(chǎn)版本的存根的測(cè)試示例
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})
您也可以通過(guò)部署管道中的屬性傳遞這些值。
共同回購(gòu)合同
存儲(chǔ)合同以外的另一種方法是將它們保存在一個(gè)共同的地方谒麦。它可能與消費(fèi)者無(wú)法克隆生產(chǎn)者代碼的安全問(wèn)題相關(guān)俄讹。另外,如果您在一個(gè)地方保留合約绕德,那么作為生產(chǎn)者颅悉,您將知道有多少消費(fèi)者,以及您的本地變更會(huì)消費(fèi)哪些消費(fèi)者迁匠。
回購(gòu)結(jié)構(gòu)
假設(shè)我們有一個(gè)坐標(biāo)為com.example:server和3個(gè)消費(fèi)者的生產(chǎn)者:client1,client2驹溃,client3城丧。然后在具有常見(jiàn)合同的存儲(chǔ)庫(kù)中,您將具有以下設(shè)置(您可以在此處查看:
├── com
│?? └── example
│??? ? └── server
│??? ? ? ? ├── client1
│??? ? ? ? │?? └── expectation.groovy
│??? ? ? ? ├── client2
│??? ? ? ? │?? └── expectation.groovy
│??? ? ? ? ├── client3
│??? ? ? ? │?? └── expectation.groovy
│??? ? ? ? └── pom.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── assembly
└── contracts.xml
您可以看到下面的斜線分隔的groupid/工件id文件夾(com/example/server)豌鹤,您對(duì)3個(gè)消費(fèi)者(client1亡哄,client2和client3)有期望。期望是本文檔中描述的標(biāo)準(zhǔn)Groovy DSL合同文件布疙。該存儲(chǔ)庫(kù)必須生成一個(gè)將一對(duì)一映射到回收內(nèi)容的JAR文件蚊惯。
server文件夾內(nèi)的pom.xml示例。
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.example
server
0.0.1-SNAPSHOT
Server Stubs
POM used to install locally stubs for consumer side
org.springframework.boot
spring-boot-starter-parent
1.5.0.BUILD-SNAPSHOT
UTF-8
1.8
1.1.0.BUILD-SNAPSHOT
Dalston.BUILD-SNAPSHOT
true
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud-dependencies.version}
pom
import
org.springframework.cloud
spring-cloud-contract-maven-plugin
${spring-cloud-contract.version}
true
${project.basedir}
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
spring-releases
Spring Releases
https://repo.spring.io/release
false
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
spring-releases
Spring Releases
https://repo.spring.io/release
false
你可以看到除了Spring Cloud Contract Maven插件之外沒(méi)有依賴關(guān)系灵临。這些垃圾是消費(fèi)者運(yùn)行mvn clean install -DskipTests來(lái)本地安裝生產(chǎn)者項(xiàng)目的存根的必要條件截型。
根文件夾中的pom.xml可以如下所示:
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
com.example.standalone
contracts
0.0.1-SNAPSHOT
Contracts
Contains all the Spring Cloud Contracts, well, contracts. JAR used by the producers to generate tests and stubs
UTF-8
org.apache.maven.plugins
maven-assembly-plugin
contracts
prepare-package
single
true
${basedir}/src/assembly/contracts.xml
false
它正在使用程序集插件來(lái)構(gòu)建所有合同的JAR。此類設(shè)置的示例如下:
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">
project
jar
false
${project.basedir}
/
true
**/${project.build.directory}/**
mvnw
mvnw.cmd
.mvn/**
src/**
工作流程
工作流程將與Step by step guide to CDC中提供的工作流程類似儒溉。唯一的區(qū)別是生產(chǎn)者不再擁有合同宦焦。所以消費(fèi)者和生產(chǎn)者必須在共同的倉(cāng)庫(kù)中處理共同的合同。
消費(fèi)者
當(dāng)消費(fèi)者希望脫機(jī)工作顿涣,而不是克隆生產(chǎn)者代碼時(shí)波闹,消費(fèi)者團(tuán)隊(duì)克隆了公用存儲(chǔ)庫(kù),轉(zhuǎn)到所需的生產(chǎn)者的文件夾(例如com/example/server)涛碑,并運(yùn)行mvn clean install -DskipTests在本地安裝存根從合同轉(zhuǎn)換精堕。
小費(fèi)
制片人
作為一個(gè)生產(chǎn)者,足以改變Spring Cloud Contract驗(yàn)證器來(lái)提供包含合同的URL和依賴關(guān)系:
org.springframework.cloud
spring-cloud-contract-maven-plugin
http://link/to/your/nexus/or/artifactory/or/sth
com.example.standalone
contracts
使用此設(shè)置蒲障,將從http://link/to/your/nexus/or/artifactory/or/sth下載具有g(shù)roupidcom.example.standalone和artifactidcontracts的JAR歹篓。然后將在本地臨時(shí)文件夾中解壓縮瘫证,并將com/example/server下的合同作為用于生成測(cè)試和存根的選擇。由于這個(gè)慣例滋捶,生產(chǎn)者團(tuán)隊(duì)將會(huì)知道當(dāng)一些不兼容的更改完成時(shí)痛悯,哪些消費(fèi)者團(tuán)隊(duì)將被破壞。
其余的流程看起來(lái)是一樣的重窟。
我可以有多個(gè)基類進(jìn)行測(cè)試嗎载萌?
如何調(diào)試生成的測(cè)試客戶端發(fā)送的請(qǐng)求/響應(yīng)巡扇?
生成的測(cè)試都以某種形式或時(shí)尚的方式依賴于Apache HttpClient進(jìn)行RestAssured扭仁。HttpClient有一個(gè)名為wire logging的工具,它將整個(gè)請(qǐng)求和響應(yīng)記錄到HttpClient厅翔。Spring Boot有一個(gè)日志記錄通用應(yīng)用程序?qū)傩?/a>來(lái)做這種事情乖坠,只需將其添加到應(yīng)用程序?qū)傩灾屑纯?/p> logging.level.org.apache.http.wire=DEBUG 可以從響應(yīng)中引用請(qǐng)求嗎? 是!使用版本1.1.0刀闷,我們添加了這樣一種可能性熊泵。在HTTP存根服務(wù)器端,我們正在為WireMock提供支持甸昏。在其他HTTP服務(wù)器存根的情況下顽分,您必須自己實(shí)現(xiàn)該方法。 Spring Cloud Contract驗(yàn)證者HTTP 畢業(yè)項(xiàng)目 先決條件 為了在WireMock中使用Spring Cloud Contract驗(yàn)證器,您必須使用Gradle或Maven插件。 警告 如果您想在項(xiàng)目中使用Spock填抬,則必須單獨(dú)添加spock-core和spock-spring模塊。檢查Spock文檔以獲取更多信息 添加具有依賴關(guān)系的漸變插件 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' } Gradle的快照版本 將其他快照存儲(chǔ)庫(kù)添加到您的build.gradle以使用快照版本缸沃,每次成功構(gòu)建后都會(huì)自動(dòng)上傳: 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" } } } 添加存根 默認(rèn)情況下Spring Cloud Contract驗(yàn)證器正在src/test/resources/contracts目錄中查找存根。 包含存根定義的目錄被視為一個(gè)類名稱修械,每個(gè)存根定義被視為單個(gè)測(cè)試趾牧。我們假設(shè)它至少包含一個(gè)用作測(cè)試類名稱的目錄。如果有多個(gè)級(jí)別的嵌套目錄肯污,除了最后一個(gè)級(jí)別將被用作包名稱武氓。所以具有以下結(jié)構(gòu) src/test/resources/contracts/myservice/shouldCreateUser.groovy src/test/resources/contracts/myservice/shouldReturnUser.groovy Spring Cloud Contract驗(yàn)證程序?qū)⑹褂脙煞N方法創(chuàng)建測(cè)試類defaultBasePackage.MyService shouldCreateUser() shouldReturnUser() 運(yùn)行插件 插件注冊(cè)自己在check任務(wù)之前被調(diào)用。只要您希望它成為構(gòu)建過(guò)程的一部分仇箱,您就無(wú)所事事县恕。如果您只想生成測(cè)試,請(qǐng)調(diào)用generateContractTests任務(wù)剂桥。 默認(rèn)設(shè)置 默認(rèn)的Gradle插件設(shè)置創(chuàng)建了以下Gradle部分的構(gòu)建(它是一個(gè)偽代碼) 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 } } } 配置插件 要更改默認(rèn)配置忠烛,只需在您的Gradle配置中添加contracts代碼段即可 contracts { testMode = 'MockMvc' baseClassForTests = 'org.mycompany.tests' generatedTestSourcesDir = project.file('src/generatedContract') } 配置選項(xiàng) testMode- 定義接受測(cè)試的模式。默認(rèn)的基于Spring的MockMvc的MockMvc权逗。也可以將其更改為JaxRsClient或顯式為真實(shí)的HTTP調(diào)用美尸。 導(dǎo)入- 應(yīng)包含在生成的測(cè)試中的導(dǎo)入的數(shù)組(例如['org.myorg.Matchers'])冤议。默認(rèn)為空數(shù)組[] staticImports- 應(yīng)該包含在生成的測(cè)試中的靜態(tài)導(dǎo)入的數(shù)組(例如['org.myorg.Matchers。*'])师坎。默認(rèn)為空數(shù)組[] basePackageForTests- 為所有生成的測(cè)試指定基礎(chǔ)包恕酸。默認(rèn)設(shè)置為org.springframework.cloud.verifier.tests baseClassForTests- 所有生成的測(cè)試的基類。如果使用Spock測(cè)試胯陋,默認(rèn)為spock.lang.Specification蕊温。 packageWithBaseClasses- 而不是為基類提供固定值,您可以提供一個(gè)所有基類放置的包遏乔。優(yōu)先于baseClassForTests义矛。 baseClassMappings- 明確地將合約包映射到基類的FQN。優(yōu)先于packageWithBaseClasses和baseClassForTests盟萨。 ruleClassForTests- 指定應(yīng)該添加到生成的測(cè)試類的規(guī)則凉翻。 ignoredFiles- Ant匹配器,允許定義要跳過(guò)哪些處理的存根文件捻激。默認(rèn)為空數(shù)組[] contractsDslDir- 包含使用GroovyDSL編寫的合同的目錄制轰。默認(rèn)$rootDir/src/test/resources/contracts generatedTestSourcesDir- 應(yīng)該放置從Groovy DSL生成測(cè)試的測(cè)試源目錄。默認(rèn)$buildDir/generated-test-sources/contractVerifier stubsOutputDir- 應(yīng)該放置從Groovy DSL生成的WireMock存根的目錄 targetFramework- 要使用的目標(biāo)測(cè)試框架;JUnit作為默認(rèn)框架胞谭,目前支持Spock和JUnit 當(dāng)您希望提供合同所在JAR的位置時(shí)垃杖,將使用以下屬性 contractDependency- 提供groupid:artifactid:version:classifier坐標(biāo)的依賴關(guān)系。您可以使用contractDependency關(guān)閉來(lái)設(shè)置它 contractPath- 如果下載合同部分將默認(rèn)為groupid/artifactid韭赘,其中g(shù)roupid將被分隔。否則將掃描提供的目錄下的合同 contractsWorkOffline- 為了不下載依賴關(guān)系势就,每次下載一次泉瞻,然后離線工作(重用本地Maven repo) 所有測(cè)試的單一基類 在默認(rèn)的MockMvc中使用Spring Cloud Contract驗(yàn)證器時(shí),您需要為所有生成的驗(yàn)收測(cè)試創(chuàng)建一個(gè)基本規(guī)范苞冯。在這個(gè)類中袖牙,您需要指向應(yīng)驗(yàn)證的端點(diǎn)。 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 } } 在使用Explicit模式的情況下舅锄,您可以像普通集成測(cè)試一樣使用基類來(lái)初始化整個(gè)測(cè)試的應(yīng)用程序鞭达。在JAXRSCLIENT模式的情況下,這個(gè)基類也應(yīng)該包含protected WebTarget webTarget字段皇忿,現(xiàn)在測(cè)試JAX-RS API的唯一選項(xiàng)是啟動(dòng)Web服務(wù)器畴蹭。 不同的基礎(chǔ)類別的合同 如果您的基類在合同之間不同,您可以告訴Spring Cloud Contract插件哪個(gè)類應(yīng)該由自動(dòng)生成測(cè)試擴(kuò)展鳍烁。你有兩個(gè)選擇: 遵循約定叨襟,提供packageWithBaseClasses 通過(guò)baseClassMappings提供顯式映射 慣例 約定是這樣的,如果你有合同幔荒,例如src/test/resources/contract/foo/bar/baz/糊闽,并將packageWithBaseClasses屬性的值提供給com.example.base梳玫,那么我們將假設(shè)com.example.base下有一個(gè)BarBazBase類包。換句話說(shuō)右犹,如果它們存在并且形成具有Base后綴的類提澎,那么我們將使用最后兩個(gè)包的部分。優(yōu)先于baseClassForTests。contracts關(guān)閉中的使用示例: packageWithBaseClasses = 'com.example.base' 制圖 您可以手動(dòng)將合同包的正則表達(dá)式映射為匹配合同的基類的完全限定名稱。我們來(lái)看看下面的例子: baseClassForTests = "com.example.FooBase" baseClassMappings { baseClassMapping('.*/com/.*', 'com.example.ComBase') baseClassMapping('.*/bar/.*':'com.example.BarBase') } 我們假設(shè)你有合同 -src/test/resources/contract/com/-src/test/resources/contract/foo/ 通過(guò)提供baseClassForTests贤斜,我們有一個(gè)后備案例怕磨,如果映射沒(méi)有成功(您也可以提供packageWithBaseClasses作為備用)。這樣蒜焊,從src/test/resources/contract/com/合同產(chǎn)生的測(cè)試將擴(kuò)展com.example.ComBase,而其余的測(cè)試將擴(kuò)展com.example.FooBase。 調(diào)用生成的測(cè)試 為確保提供方對(duì)定義的合同進(jìn)行投訴服协,您需要調(diào)用: ./gradlew generateContractTests test Spring Cloud Contract消費(fèi)者驗(yàn)證者 在消費(fèi)者服務(wù)中,您需要以與提供商相同的方式配置Spring Cloud Contract驗(yàn)證器插件啦粹。如果您不想使用Stub Runner偿荷,則需要復(fù)制存儲(chǔ)在src/test/resources/contracts中的合同,并使用以下命令生成WireMock json存根: ./gradlew generateClientStubs 請(qǐng)注意唠椭,必須為存根生成設(shè)置stubsOutputDir選項(xiàng)才能正常工作跳纳。 當(dāng)存在時(shí),json存根可用于消費(fèi)者自動(dòng)測(cè)試贪嫂。 @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 } } 在LoanApplication下面調(diào)用FraudDetection服務(wù)寺庄。此請(qǐng)求由使用由Spring Cloud Contract驗(yàn)證器生成的存根配置的WireMock服務(wù)器處理。 在您的Maven項(xiàng)目中使用 添加maven插件 添加Spring Cloud Contract BOM org.springframework.cloud spring-cloud-dependencies ${spring-cloud-dependencies.version} pom import 接下來(lái)力崇,Spring Cloud Contract VerifierMaven插件 org.springframework.cloud spring-cloud-contract-maven-plugin ${spring-cloud-contract.version} true com.example.fraud 您可以在Spring Cloud Contract Maven插件文檔中閱讀更多內(nèi)容 Maven的快照版本 對(duì)于快照/里程碑版本斗塘,您必須將以下部分添加到您的pom.xml spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false spring-releases Spring Releases https://repo.spring.io/release false spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false spring-releases Spring Releases https://repo.spring.io/release false 添加存根 默認(rèn)情況下Spring Cloud Contract驗(yàn)證器正在src/test/resources/contracts目錄中查找存根。包含存根定義的目錄被視為一個(gè)類名稱亮靴,每個(gè)存根定義被視為單個(gè)測(cè)試馍盟。我們假設(shè)它至少包含一個(gè)用作測(cè)試類名稱的目錄。如果有多個(gè)級(jí)別的嵌套目錄茧吊,除了最后一個(gè)級(jí)別將被用作包名稱贞岭。所以具有以下結(jié)構(gòu) src/test/resources/contracts/myservice/shouldCreateUser.groovy src/test/resources/contracts/myservice/shouldReturnUser.groovy Spring Cloud Contract驗(yàn)證者將使用兩種方法創(chuàng)建測(cè)試類defaultBasePackage.MyService-shouldCreateUser()-shouldReturnUser() 運(yùn)行插件 插件目標(biāo)generateTests被分配為階段generate-test-sources。只要您希望它成為構(gòu)建過(guò)程的一部分搓侄,您就無(wú)所事事瞄桨。如果您只想生成測(cè)試,請(qǐng)調(diào)用generateTests目標(biāo)讶踪。 配置插件 要更改默認(rèn)配置讲婚,只需將configuration部分添加到插件定義或execution定義。 org.springframework.cloud spring-cloud-contract-maven-plugin convert generateStubs generateTests org.springframework.cloud.verifier.twitter.place org.springframework.cloud.verifier.twitter.place.BaseMockMvcSpec 重要配置選項(xiàng) testMode- 定義接受測(cè)試的模式俊柔。默認(rèn)MockMvc筹麸,它基于Spring的MockMvc活合。對(duì)于真正的HTTP呼叫,它也可以更改為JaxRsClient或Explicit物赶。 basePackageForTests- 為所有生成的測(cè)試指定基礎(chǔ)包白指。默認(rèn)設(shè)置為org.springframework.cloud.verifier.tests。 ruleClassForTests- 指定應(yīng)該添加到生成的測(cè)試類的規(guī)則酵紫。 baseClassForTests- 生成測(cè)試的基類告嘲。如果使用Spock測(cè)試,默認(rèn)為spock.lang.Specification奖地。 contractDir- 包含使用GroovyDSL編寫的合同的目錄橄唬。默認(rèn)/src/test/resources/contracts。 testFramework- 要使用的目標(biāo)測(cè)試框架;JUnit作為默認(rèn)框架参歹,目前支持Spock和JUnit packageWithBaseClasses- 而不是為基類提供固定值仰楚,您可以提供一個(gè)所有基類放置的包。約定是這樣的犬庇,如果你有合同src/test/resources/contract/foo/bar/baz/僧界,并提供這個(gè)屬性的值到com.example.base,那么我們將假設(shè)com.example.base包含com.example.base類臭挽。優(yōu)先于baseClassForTests baseClassMappings- 您必須提供contractPackageRegex的基類映射列表捂襟,該列表根據(jù)合同所在的包進(jìn)行檢查,并且baseClassFQN映射到匹配合同的基類的完全限定名稱欢峰。如果您有合同src/test/resources/contract/foo/bar/baz/并映射了屬性.*→com.example.base.BaseClass葬荷,則從這些合同生成的測(cè)試類將擴(kuò)展com.example.base.BaseClass。優(yōu)先于packageWithBaseClasses和baseClassForTests纽帖。 如果要從Maven存儲(chǔ)庫(kù)中下載合同定義宠漩,可以使用 contractsRepositoryUrl- 具有合同的工件的repo的URL(如果沒(méi)有提供)應(yīng)使用當(dāng)前的Maven contractDependency- 包含所有打包合同的合同依賴關(guān)系 contractPath- 通過(guò)打包合同在JAR中具體合同的路徑。默認(rèn)為groupid/artifactid抛计,其中g(shù)ropuid被斜杠分隔哄孤。 contractWorkOffline- 如果依賴關(guān)系應(yīng)該被下載照筑,或者本地Maven只能被重用 有關(guān)完整信息,請(qǐng)參閱插件文檔 所有測(cè)試的單一基類 在默認(rèn)的MockMvc中使用Spring Cloud Contract驗(yàn)證器時(shí),您需要為所有生成的驗(yàn)收測(cè)試創(chuàng)建一個(gè)基本規(guī)范秩命。在這個(gè)類中阔涉,您需要指向應(yīng)驗(yàn)證的端點(diǎn)。 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()) } } 在使用Explicit模式的情況下蛾默,您可以像常規(guī)集成測(cè)試一樣使用基類來(lái)初始化整個(gè)測(cè)試的應(yīng)用程序懦铺。在JAXRSCLIENT模式的情況下,這個(gè)基類也應(yīng)該包含protected WebTarget webTarget字段支鸡,現(xiàn)在測(cè)試JAX-RS API的唯一選項(xiàng)是啟動(dòng)Web服務(wù)器冬念。 不同的基礎(chǔ)類別的合同 如果您的基類在合同之間不同趁窃,您可以告訴Spring Cloud Contract插件哪個(gè)類應(yīng)該由自動(dòng)生成測(cè)試擴(kuò)展。你有兩個(gè)選擇: 遵循約定急前,提供packageWithBaseClasses 通過(guò)baseClassMappings提供顯式映射 慣例 約定是這樣的醒陆,如果你有合同,例如src/test/resources/contract/hello/v1/裆针,并將packageWithBaseClasses屬性的值提供給hello刨摩,那么我們將假設(shè)在hello下有一個(gè)HelloV1Base類包。換句話說(shuō)世吨,如果它們存在并且形成具有Base后綴的類澡刹,那么我們將使用最后兩個(gè)包的部分。優(yōu)先于baseClassForTests耘婚。使用示例: org.springframework.cloud spring-cloud-contract-maven-plugin hello 制圖 您可以手動(dòng)將合同包的正則表達(dá)式映射為匹配合同的基類的完全限定名稱罢浇。您必須提供baseClassMappingsbaseClassMapping的contractPackageRegex列表contractPackageRegex到baseClassFQN映射。我們來(lái)看看下面的例子: org.springframework.cloud spring-cloud-contract-maven-plugin com.example.FooBase .*com.* com.example.TestBase 我們假設(shè)你有合同 -src/test/resources/contract/com/-src/test/resources/contract/foo/ 通過(guò)提供baseClassForTests边篮,我們有一個(gè)后備程序己莺,如果映射沒(méi)有成功(你也可以提供packageWithBaseClasses作為備用)。這樣戈轿,從src/test/resources/contract/com/合同生成的測(cè)試將擴(kuò)展com.example.ComBase凌受,而其余的測(cè)試將擴(kuò)展com.example.FooBase。 調(diào)用生成的測(cè)試 Spring Cloud Contract Maven插件將驗(yàn)證碼生成到目錄/generated-test-sources/contractVerifier中思杯,并將此目錄附加到testCompile目標(biāo)胜蛉。 對(duì)于Groovy Spock代碼使用: org.codehaus.gmavenplus gmavenplus-plugin 1.5 testCompile ${project.basedir}/src/test/groovy **/*.groovy ${project.build.directory}/generated-test-sources/contractVerifier **/*.groovy 為了確保提供方對(duì)定義的合同進(jìn)行投訴,您需要調(diào)用mvn generateTest test Maven插件常見(jiàn)問(wèn)題 Maven插件和STS 如果在使用STS時(shí)看到以下異常 當(dāng)您點(diǎn)擊標(biāo)記時(shí)色乾,您應(yīng)該看到這樣的sth 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 為了解決這個(gè)問(wèn)題誊册,請(qǐng)?jiān)趐om.xml中提供以下部分 only. It has no influence on the Maven build itself. --> org.eclipse.m2e lifecycle-mapping 1.0.0 org.springframework.cloud spring-cloud-contract-maven-plugin [1.0,) convert Spring Cloud Contract消費(fèi)者驗(yàn)證者 您實(shí)際上也可以為消費(fèi)者使用Spring Cloud Contract驗(yàn)證器!您可以使用插件暖璧,以便只轉(zhuǎn)換合同并生成存根案怯。要實(shí)現(xiàn)這一點(diǎn),您需要以與提供程序相同的方式配置Spring Cloud Contract驗(yàn)證程序插件澎办。您需要復(fù)制存儲(chǔ)在src/test/resources/contracts中的合同嘲碱,并使用以下命令生成WireMock json存根:mvn generateStubs命令。默認(rèn)生成的WireMock映射存儲(chǔ)在目錄target/mappings中局蚀。您的項(xiàng)目應(yīng)該從此生成的映射創(chuàng)建附加工件與分類器stubs麦锯,以便輕松部署到maven存儲(chǔ)庫(kù)。 樣品配置: org.springframework.cloud spring-cloud-contract-maven-plugin ${verifier-plugin.version} convert generateStubs 當(dāng)存在時(shí)琅绅,json存根可用于消費(fèi)者自動(dòng)測(cè)試扶欣。 @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(); } } LoanApplication下方致電FraudDetection服務(wù)。此請(qǐng)求由使用Spring Cloud Contract驗(yàn)證器生成的存根配置的WireMock服務(wù)器進(jìn)行處理。 方案 可以使用Spring Cloud Contract驗(yàn)證程序處理場(chǎng)景料祠。所有您需要做的是在創(chuàng)建合同時(shí)堅(jiān)持正確的命名約定骆捧。公約要求包括后面是下劃線的訂單號(hào)。 my_contracts_dir\ scenario1\ 1_login.groovy 2_showCart.groovy 3_logout.groovy 這樣的樹(shù)將導(dǎo)致Spring Cloud Contract驗(yàn)證器生成名為scenario1的WireMock場(chǎng)景和三個(gè)步驟: 登錄標(biāo)記為Started髓绽,指向: showCart標(biāo)記為Step1指向: 注銷標(biāo)記為Step2凑懂,這將關(guān)閉場(chǎng)景。 有關(guān)WireMock場(chǎng)景的更多詳細(xì)信息梧宫,請(qǐng)參見(jiàn)http://wiremock.org/stateful-behaviour.html Spring Cloud Contract驗(yàn)證者還將生成具有保證執(zhí)行順序的測(cè)試接谨。 存根和傳遞依賴 我們創(chuàng)建的Maven和Gradle插件是為您添加創(chuàng)建存根jar的任務(wù)√料唬可能有問(wèn)題的是脓豪,當(dāng)重用存根時(shí),您可以錯(cuò)誤地導(dǎo)入所有這些存根依賴關(guān)系忌卤!即使你有幾個(gè)不同的罐子扫夜,建造一個(gè)Maven的工件悦荒,他們都有一個(gè)pom: ├── 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 ├── ... └── ... 使用這些依賴關(guān)系有三種可能性肃拜,以便不會(huì)對(duì)傳遞依賴性產(chǎn)生任何問(wèn)題。 將所有應(yīng)用程序依賴項(xiàng)標(biāo)記為可選 如果在github-webhook應(yīng)用程序中咆课,我們將所有的依賴項(xiàng)標(biāo)記為可選的棍厂,當(dāng)您將github-webhook存根包含在另一個(gè)應(yīng)用程序中(或者當(dāng)依賴關(guān)系由Stub Runner下載)時(shí)颗味,因?yàn)樗械囊蕾囮P(guān)系是可選的,它們不會(huì)被下載牺弹。 為存根創(chuàng)建一個(gè)單獨(dú)的artifactid 如果你創(chuàng)建一個(gè)單獨(dú)的artifactid浦马,那么你可以設(shè)置任何你想要的方式。例如通過(guò)沒(méi)有依賴關(guān)系张漂。 排除消費(fèi)者方面的依賴關(guān)系 作為消費(fèi)者晶默,如果將stub依賴關(guān)系添加到類路徑中,則可以顯式排除不需要的依賴關(guān)系航攒。 Spring Cloud Contract驗(yàn)證器消息 Spring Cloud Contract驗(yàn)證器允許您驗(yàn)證使用消息傳遞作為通信方式的應(yīng)用程序磺陡。我們所有的集成都使用Spring,但您也可以自己創(chuàng)建并使用它漠畜。 集成 您可以使用四種集成配置之一: Apache Camel Spring Integration Spring Cloud Stream Spring AMQP 由于我們使用Spring Boot币他,因此如果您已經(jīng)將上述的一個(gè)庫(kù)添加到類路徑中,那么將自動(dòng)設(shè)置所有的消息傳遞配置盆驹。 重要 記住將@AutoConfigureMessageVerifier放在生成的測(cè)試的基類上圆丹。否則Spring Cloud Contract驗(yàn)證器的消息傳遞部分將無(wú)法正常工作滩愁。 手動(dòng)集成測(cè)試 測(cè)試使用的主界面是org.springframework.cloud.contract.verifier.messaging.MessageVerifier躯喇。它定義了如何發(fā)送和接收消息。您可以創(chuàng)建自己的實(shí)現(xiàn)來(lái)實(shí)現(xiàn)相同的目標(biāo)。 在測(cè)試中廉丽,您可以注冊(cè)ContractVerifierMessageExchange發(fā)送和接收遵循合同的消息倦微。然后將@AutoConfigureMessageVerifier添加到您的測(cè)試中,例如 @RunWith(SpringTestRunner.class) @SpringBootTest @AutoConfigureMessageVerifier public static class MessagingContractTests { @Autowired private MessageVerifier verifier; ... } 注意 如果您的測(cè)試也需要存根正压,則@AutoConfigureStubRunner包括消息傳遞配置欣福,因此您只需要一個(gè)注釋。 發(fā)行人端測(cè)試一代 在您的DSL中擁有input或outputMessage部分將導(dǎo)致在發(fā)布商方面創(chuàng)建測(cè)試焦履。默認(rèn)情況下拓劝,將創(chuàng)建JUnit測(cè)試,但是也可以創(chuàng)建Spock測(cè)試嘉裤。 我們應(yīng)該考慮三個(gè)主要場(chǎng)景: 情況1:沒(méi)有輸入消息產(chǎn)生輸出消息郑临。輸出消息由應(yīng)用程序內(nèi)部的組件觸發(fā)(例如調(diào)度程序) 情況2:輸入消息觸發(fā)輸出消息 方案3:輸入消息被消耗,沒(méi)有輸出消息 情景1(無(wú)輸入訊息) 對(duì)于給定的合同: def contractDsl = Contract.make { label 'some_label' input { triggeredBy('bookReturnedTriggered()') } outputMessage { sentTo('activemq:output') body('''{ "bookName" : "foo" }''') headers { header('BOOK-NAME', 'foo') } } } 將創(chuàng)建以下JUnit測(cè)試: ''' // 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"); ''' 并且將創(chuàng)建以下Spock測(cè)試: ''' 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") ''' 情景2(輸入觸發(fā)輸出) 對(duì)于給定的合同: 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') } } } 將創(chuàng)建以下JUnit測(cè)試: ''' // 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"); ''' 并且將創(chuàng)建以下Spock測(cè)試: """\ 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") """ 情景3(無(wú)輸出訊息) 對(duì)于給定的合同: def contractDsl = Contract.make { label 'some_label' input { messageFrom('jms:delete') messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } assertThat('bookWasDeleted()') } } 將創(chuàng)建以下JUnit測(cè)試: ''' // given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( "{\\"bookName\\":\\"foo\\"}" , headers() .header("sample", "header")); // when: contractVerifierMessaging.send(inputMessage, "jms:delete"); // then: bookWasDeleted(); ''' 并且將創(chuàng)建以下Spock測(cè)試: ''' given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( \'\'\'{"bookName":"foo"}\'\'\', ['sample': 'header'] ) when: contractVerifierMessaging.send(inputMessage, 'jms:delete') then: noExceptionThrown() bookWasDeleted() ''' 消費(fèi)者存根側(cè)代 與HTTP部分不同 - 在消息傳遞中屑宠,我們需要使用存根發(fā)布JAR中的Groovy DSL厢洞。然后在消費(fèi)者端進(jìn)行解析,創(chuàng)建適當(dāng)?shù)膕tubbed路由典奉。 有關(guān)更多信息躺翻,請(qǐng)參閱Stub Runner消息部分。 Maven的 org.springframework.cloud spring-cloud-starter-stream-rabbit org.springframework.cloud spring-cloud-starter-contract-stub-runner test org.springframework.cloud spring-cloud-stream-test-support test org.springframework.cloud spring-cloud-dependencies Dalston.BUILD-SNAPSHOT pom import 搖籃 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 } } } Spring Cloud Contract Stub Runner 使用Spring Cloud Contract驗(yàn)證程序時(shí)可能遇到的一個(gè)問(wèn)題是將生成的WireMock JSON存根從服務(wù)器端傳遞到客戶端(或各種客戶端)卫玖。在消息傳遞的客戶端生成方面也是如此公你。 復(fù)制JSON文件/手動(dòng)設(shè)置客戶端進(jìn)行消息傳遞是不成問(wèn)題的。 這就是為什么我們會(huì)介紹可以為您自動(dòng)下載和運(yùn)行存根的Spring Cloud Contract Stub Runner假瞬。 快照版本 將其他快照存儲(chǔ)庫(kù)添加到您的build.gradle以使用快照版本省店,每次成功構(gòu)建后都會(huì)自動(dòng)上傳: Maven的 spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false spring-releases Spring Releases https://repo.spring.io/release false spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone false spring-releases Spring Releases https://repo.spring.io/release false 搖籃 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" } } 將存根發(fā)布為JAR 最簡(jiǎn)單的方法是集中保留存根的方式。例如笨触,您可以將它們作為JAR存儲(chǔ)在Maven存儲(chǔ)庫(kù)中懦傍。 小費(fèi) 對(duì)于Maven和Gradle來(lái)說(shuō),安裝程序都是開(kāi)箱即用的芦劣。但是如果你想要的話可??以自定義它粗俱。 Maven的 true org.apache.maven.plugins maven-assembly-plugin stub prepare-package single false true $/Users/sgibb/workspace/spring/spring-cloud-samples/scripts/docs/../src/assembly/stub.xml 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"> stubs jar false src/main/java / **com/example/model/*.* ${project.build.directory}/classes / **com/example/model/*.* ${project.build.directory}/snippets/stubs META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings **/* $/Users/sgibb/workspace/spring/spring-cloud-samples/scripts/docs/../src/test/resources/contracts META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts **/*.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 } } } 模塊 Stub Runner核心 為服務(wù)合作者運(yùn)行存根。作為服務(wù)合同處理存根允許使用stub-runner作為Consumer Driven Contracts的實(shí)現(xiàn)虚吟。 Stub Runner允許您自動(dòng)下載提供的依賴項(xiàng)的存根寸认,為其啟動(dòng)WireMock服務(wù)器,并為其提供適當(dāng)?shù)拇娓x串慰。對(duì)于消息傳遞偏塞,定義了特殊的存根路由。 運(yùn)行存根 限制 重要 StubRunner可能會(huì)在測(cè)試之間關(guān)閉端口時(shí)出現(xiàn)問(wèn)題。您可能會(huì)遇到您遇到端口沖突的情況。只要您在測(cè)試中使用相同的上下文寡痰,一切正常拇砰。但是當(dāng)上下文不同(例如不同的存根或不同的配置文件)時(shí)啊研,您必須使用@DirtiesContext關(guān)閉存根服務(wù)器货邓,否則在每個(gè)測(cè)試的不同端口上運(yùn)行它們怎茫。 運(yùn)行使用主應(yīng)用程序 您可以將以下選項(xiàng)設(shè)置為主類: -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 ? ? Maximum port value to be assigned to the WireMock instance. Defaults to 15000 (default: 15000) --minPort, --minp ? ? 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' HTTP存根 存根在JSON文檔中定義诊杆,其語(yǔ)法在WireMock文檔中定義 例: { "request": { "method": "GET", "url": "/ping" }, "response": { "status": 200, "body": "pong", "headers": { "Content-Type": "text/plain" } } } 查看注冊(cè)的映射 每個(gè)stubbed協(xié)作者公開(kāi)__/admin/端點(diǎn)下定義的映射列表捉腥。 消息存根 根據(jù)提供的Stub Runner依賴關(guān)系和DSL氓拼,消息路由將自動(dòng)設(shè)置。 Stub Runner JUnit規(guī)則 Stub Runner附帶一個(gè)JUnit規(guī)則抵碟,感謝您可以輕松地下載和運(yùn)行給定組和工件ID的存根: @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"); 該規(guī)則執(zhí)行后Stub Runner連接到您的Maven存儲(chǔ)庫(kù)桃漾,給定的依賴關(guān)系列表嘗試: 下載它們 在本地緩存 將它們解壓縮到臨時(shí)文件夾 從提供的端口/提供的端口范圍的隨機(jī)端口上為每個(gè)Maven依賴關(guān)系啟動(dòng)WireMock服務(wù)器 為WireMock服務(wù)器提供所有具有有效WireMock定義的JSON文件 Stub Runner使用Eclipse Aether機(jī)制下載Maven依賴關(guān)系。查看他們的文檔了解更多信息拟逮。 由于StubRunnerRule實(shí)現(xiàn)了StubFinder呈队,它允許您找到已啟動(dòng)的存根: 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> getContracts(); } Spock測(cè)試中使用示例: @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' } JUnit測(cè)試中的使用示例: @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"); } 有關(guān)如何應(yīng)用Stub Runner的全局配置的更多信息,請(qǐng)查看JUnit和Spring的公共屬性唱歧。 Maven設(shè)置 存根下載器為不同的本地存儲(chǔ)庫(kù)文件夾授予Maven設(shè)置宪摧。目前沒(méi)有考慮存儲(chǔ)庫(kù)和配置文件的身份驗(yàn)證詳細(xì)信息,因此您需要使用上述屬性進(jìn)行指定颅崩。 提供固定端口 您還可以在固定端口上運(yùn)行您的存根几于。你可以通過(guò)兩種不同的方法來(lái)實(shí)現(xiàn)。一個(gè)是在屬性中傳遞它沿后,另一個(gè)是通過(guò)JUnit規(guī)則的流暢API沿彭。 流暢的API 使用StubRunnerRule時(shí),您可以添加一個(gè)存根下載尖滚,然后通過(guò)上次下載的存根的端口喉刘。 @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"); 您可以看到,對(duì)于此示例漆弄,以下測(cè)試是有效的: then(rule.findStubUrl("loanIssuance")).isEqualTo(URI.create("http://localhost:12345").toURL()); then(rule.findStubUrl("fraudDetectionServer")).isEqualTo(URI.create("http://localhost:12346").toURL()); Stub Runner與Spring 設(shè)置Stub Runner項(xiàng)目的Spring配置睦裳。 通過(guò)在配置文件中提供存根列表,Stub Runner自動(dòng)下載并注冊(cè)WireMock中所選擇的存根撼唾。 如果要查找stubbed依賴關(guān)系的URL廉邑,您可以自動(dòng)連接StubFinder接口并使用其方法,如下所示: @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 {} } 對(duì)于以下配置文件: 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 您也可以使用@AutoConfigureStubRunner內(nèi)的屬性代替使用屬性倒谷。下面您可以通過(guò)設(shè)置注釋的值來(lái)找到實(shí)現(xiàn)相同結(jié)果的示例蛛蒙。 @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/") Stub Runner Spring為每個(gè)注冊(cè)的WireMock服務(wù)器以以下方式注冊(cè)環(huán)境變量。Stub Runner idscom.example:foo渤愁,com.example:bar的示例牵祟。 stubrunner.runningstubs.foo.port stubrunner.runningstubs.bar.port 你可以在你的代碼中引用它。 Stub Runner Spring Cloud Stub Runner可以與Spring Cloud整合抖格。 對(duì)于現(xiàn)實(shí)生活中的例子诺苹,你可以檢查 Stubbing服務(wù)發(fā)現(xiàn) Stub Runner Spring Cloud的最重要的特征就是它的存在 DiscoveryClient RibbonServerList 這意味著無(wú)論您是否使用Zookeeper咕晋,Consul,Eureka或其他任何事情筝尾,您都不需要在測(cè)試中。我們正在啟動(dòng)您的依賴項(xiàng)的WireMock實(shí)例办桨,只要您直接使用Feign筹淫,負(fù)載平衡RestTemplate或DiscoveryClient,我們會(huì)告訴您的應(yīng)用程序來(lái)調(diào)用這些stubbed服務(wù)器呢撞,而不是調(diào)用真實(shí)的服務(wù)發(fā)現(xiàn)工具损姜。 例如這個(gè)測(cè)試將通過(guò) 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' } 對(duì)于以下配置文件 spring.cloud: zookeeper.enabled: false consul.enabled: false eureka.client.enabled: false stubrunner: camel.enabled: false idsToServiceIds: ivyNotation: someValueInsideYourCode fraudDetectionServer: someNameThatShouldMapFraudDetectionServer 測(cè)試配置文件和服務(wù)發(fā)現(xiàn) 在集成測(cè)試中,您通常不想既不調(diào)用發(fā)現(xiàn)服務(wù)(例如Eureka)或調(diào)用服務(wù)器殊霞。這就是為什么你創(chuàng)建一個(gè)額外的測(cè)試配置摧阅,你要禁用這些功能。 由于spring-cloud-commons實(shí)現(xiàn)這一點(diǎn)的某些限制绷蹲,您可以通過(guò)下面的靜態(tài)塊來(lái)禁用這些屬性(例如Eureka) //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"); } 附加配置 您可以使用stubrunner.idsToServiceIds:地圖將存根的artifactId與應(yīng)用程序的名稱進(jìn)行匹配棒卷。提供:stubrunner.cloud.ribbon.enabled等于false,您可以禁用Stub Runner Ribbon支持祝钢。您可以通過(guò)提供stubrunner.cloud.enabled等于false來(lái)禁用Stub Runner支持 小費(fèi) 默認(rèn)情況下比规,所有服務(wù)發(fā)現(xiàn)都將被刪除。這意味著不管事實(shí)如果你有一個(gè)現(xiàn)有的DiscoveryClient拦英,它的結(jié)果將被忽略。但是,如果要重用它悯搔,只需將stubrunner.cloud.delegate.enabled設(shè)置為true照激,然后將現(xiàn)有的DiscoveryClient結(jié)果與已存在的結(jié)果合并。 Stub Runner啟動(dòng)應(yīng)用程序 Spring Cloud Contract驗(yàn)證者Stub Runner Boot是一個(gè)Spring Boot應(yīng)用程序铃拇,它暴露了REST端點(diǎn)來(lái)觸發(fā)郵件標(biāo)簽并訪問(wèn)啟動(dòng)的WireMock服務(wù)器钞瀑。 其中一個(gè)用例是在部署的應(yīng)用程序上運(yùn)行一些煙霧(端到端)測(cè)試。您可以在Too Much Coding博客的“Microservice部署”文章中閱讀更多信息慷荔。 如何使用它仔戈? 只需添加 compile "org.springframework.cloud:spring-cloud-starter-stub-runner" 用@EnableStubRunnerServer注釋一個(gè)課程,建一個(gè)胖子拧廊,你準(zhǔn)備好了监徘! 對(duì)于屬性,請(qǐng)檢查Stub Runner Spring部分吧碾。 端點(diǎn) HTTP GET/stubs- 返回ivy:integer表示法中所有運(yùn)行存根的列表 GET/stubs/{ivy}- 返回給定的ivy符號(hào)的端口(當(dāng)調(diào)用端點(diǎn)ivy也可以是artifactId) 消息 消息傳遞 GET/triggers- 返回ivy : [ label1, label2 …?]表示法中所有正在運(yùn)行的標(biāo)簽的列表 POST/triggers/{label}- 執(zhí)行l(wèi)abel的觸發(fā)器 POST/triggers/{ivy}/{label}- 對(duì)于給定的ivy符號(hào)(當(dāng)調(diào)用端點(diǎn)ivy也可以是artifactId)時(shí)凰盔,執(zhí)行具有l(wèi)abel的觸發(fā)器) 例 @ContextConfiguration(classes = StubRunnerBoot, loader = SpringBootContextLoader) @SpringBootTest(properties = "spring.cloud.zookeeper.enabled=false") @ActiveProfiles("test") class StubRunnerBootSpec extends Specification { @Autowired StubRunning stubRunning def setup() { RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning)) } def 'should return a list of running stub servers in "full ivy:port" notation'() { when: String response = RestAssuredMockMvc.get('/stubs').body.asString() then: def root = new JsonSlurper().parseText(response) root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs' instanceof Integer } def 'should return a port on which a [#stubId] stub is running'() { when: def response = RestAssuredMockMvc.get("/stubs/${stubId}") then: response.statusCode == 200 response.body.as(Integer) > 0 where: stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService:+', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService'] } def 'should return 404 when missing stub was called'() { when: def response = RestAssuredMockMvc.get("/stubs/a:b:c:d") then: response.statusCode == 404 } def 'should return a list of messaging labels that can be triggered when version and classifier are passed'() { when: String response = RestAssuredMockMvc.get('/triggers').body.asString() then: def root = new JsonSlurper().parseText(response) root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book","return_book_1","return_book_2"]) } def 'should trigger a messaging label'() { given: StubRunning stubRunning = Mock() RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning)) when: def response = RestAssuredMockMvc.post("/triggers/delete_book") then: response.statusCode == 200 and: 1 * stubRunning.trigger('delete_book') } def 'should trigger a messaging label for a stub with [#stubId] ivy notation'() { given: StubRunning stubRunning = Mock() RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning)) when: def response = RestAssuredMockMvc.post("/triggers/$stubId/delete_book") then: response.statusCode == 200 and: 1 * stubRunning.trigger(stubId, 'delete_book') where: stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService'] } def 'should throw exception when trigger is missing'() { when: RestAssuredMockMvc.post("/triggers/missing_label") then: Exception e = thrown(Exception) e.message.contains("Exception occurred while trying to return [missing_label] label.") e.message.contains("Available labels are") e.message.contains("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]") e.message.contains("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=") } } Stub Runner啟動(dòng)服務(wù)發(fā)現(xiàn) 使用Stub Runner Boot的可能性之一就是將其用作“煙霧測(cè)試”的存根。這是什么意思倦春?假設(shè)您不想將50個(gè)微服務(wù)部署到測(cè)試環(huán)境中户敬,以檢查您的應(yīng)用程序是否正常工作落剪。您在構(gòu)建過(guò)程中已經(jīng)執(zhí)行了一系列測(cè)試,但您也希望確保應(yīng)用程序的打包正常尿庐。您可以做的是將應(yīng)用程序部署到環(huán)境中忠怖,啟動(dòng)并運(yùn)行一些測(cè)試,以確定它是否正常工作抄瑟。我們可以將這些測(cè)試稱為煙霧測(cè)試凡泣,因?yàn)樗麄兊南敕ㄖ皇菣z查一些測(cè)試場(chǎng)景。 這種方法的問(wèn)題是皮假,如果您正在執(zhí)行微服務(wù)鞋拟,則很可能您正在使用服務(wù)發(fā)現(xiàn)工具。Stub Runner引導(dǎo)允許您通過(guò)啟動(dòng)所需的存根并將其注冊(cè)到服務(wù)發(fā)現(xiàn)工具中來(lái)解決此問(wèn)題惹资。讓我們來(lái)看看一個(gè)這樣一個(gè)設(shè)置的例子Eureka贺纲。假設(shè)Eureka已經(jīng)在運(yùn)行。 @SpringBootApplication @EnableStubRunnerServer @EnableEurekaClient @AutoConfigureStubRunner public class StubRunnerBootEurekaExample { public static void main(String[] args) { SpringApplication.run(StubRunnerBootEurekaExample.class, args); } } 如您所見(jiàn)褪测,我們希望啟動(dòng)一個(gè)Stub Runner引導(dǎo)服務(wù)器@EnableStubRunnerServer猴誊,啟用Eureka客戶端@EnableEurekaClient,并且我們想要使存根轉(zhuǎn)移功能打開(kāi)@AutoConfigureStubRunner侮措。 現(xiàn)在我們假設(shè)我們要啟動(dòng)這個(gè)應(yīng)用程序稠肘,以便自動(dòng)注冊(cè)存根。我們可以通過(guò)運(yùn)行應(yīng)用程序java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jar來(lái)執(zhí)行此操作萝毛,其中${SYSTEM_PROPS}將包含以下屬性列表 -Dstubrunner.repositoryRoot=http://repo.spring.io/snapshots (1) -Dstubrunner.cloud.stubbed.discovery.enabled=false (2) -Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.cloud.contract.verifier.stubs:bootService (3) -Dstubrunner.idsToServiceIds.fraudDetectionServer=someNameThatShouldMapFraudDetectionServer (4) (1) - we tell Stub Runner where all the stubs reside (2) - we don't want the default behaviour where the discovery service is stubbed. That's why the stub registration will be picked (3) - we provide a list of stubs to download (4) - we provide a list of artifactId to serviceId mapping 這樣您的部署應(yīng)用程序可以通過(guò)服務(wù)發(fā)現(xiàn)將請(qǐng)求發(fā)送到啟動(dòng)的WireMock服務(wù)器项阴。默認(rèn)情況下,application.yml可能會(huì)設(shè)置1-3笆包,因?yàn)樗鼈儾惶赡芨淖兓防俊_@樣,只要您啟動(dòng)Stub Runner引導(dǎo)庵佣,您只能提供要下載的存根列表歉胶。 JUnit和Spring的常用屬性 可以使用系統(tǒng)屬性或配置屬性(對(duì)于Spring)設(shè)置重復(fù)的某些屬性。以下是他們的名稱及其默認(rèn)值: 物業(yè)名稱默認(rèn)值描述 stubrunner.minPort 10000 具有存根的起始WireMock端口的最小值 stubrunner.maxPort 15000 具有存根的起始WireMock端口的最小值 stubrunner.repositoryRoot Maven repo網(wǎng)址如果空白巴粪,那么將調(diào)用本地的maven repo stubrunner.classifier stubs stub工件的默認(rèn)分類器 stubrunner.workOffline false 如果為true通今,則不會(huì)聯(lián)系任何遠(yuǎn)程存儲(chǔ)庫(kù)以下載存根 stubrunner.ids 數(shù)組的常春藤符號(hào)存根下載 stubrunner.username 可選的用戶名訪問(wèn)使用存根存儲(chǔ)JAR的工具 stubrunner.password 訪問(wèn)使用存根存儲(chǔ)JAR的工具的可選密碼 存根運(yùn)動(dòng)員短樁ids 您可以通過(guò)stubrunner.ids系統(tǒng)屬性提供存根下載。他們遵循以下模式: groupId:artifactId:version:classifier:port version肛根,classifier和port是可選的辫塌。 如果您不提供port,則會(huì)選擇一個(gè)隨機(jī)的 如果您不提供classifier派哲,那么將采用默認(rèn)值臼氨。(注意,你可以傳遞這樣一個(gè)空的分類器groupId:artifactId:version:) 如果您不提供version芭届,則將通過(guò)+储矩,最新的將被下載 其中port表示W(wǎng)ireMock服務(wù)器的端口感耙。 重要 從版本1.0.4開(kāi)始,您可以提供一系列您希望Stub Runner考慮的版本持隧。您可以在這里閱讀有關(guān)Aether版本控制范圍的更多信息即硼。 取自Aether文件: 該方案接受任何形式的版本,將版本解釋為數(shù)字和字母段的序列屡拨。字符' - '只酥,'_'和'阱表。'以及從數(shù)字到字母的轉(zhuǎn)換,反之亦然分隔版本段弯洗。分隔符被視為等同物免猾。 數(shù)字段在數(shù)學(xué)上進(jìn)行比較,字母段被字典和區(qū)分大小寫比較整陌。但是,以下限定字符串被特別識(shí)別和處理:“alpha”=“a”<“beta”=“b”<“milestone”=“m”<“cr”=“rc”<“snapshot”<“final “=”ga“<”sp“。所有這些知名的限定詞被認(rèn)為比其他字符串更小/更老她奥。空的段/字符串等于0怎棱。 除了上述限定符之外哩俭,令牌“min”和“max”可以用作最終版本段,以表示具有給定前綴的最小/最大版本拳恋。例如凡资,“1.2.min”表示1.2行中的最小版本,“1.2.max”表示1.2行中最大的版本谬运。形式“[MN *]”的版本范圍是“[MNmin隙赁,MNmax]”的縮寫。 數(shù)字和字符串被認(rèn)為是無(wú)法比擬的梆暖。在不同類型的版本段會(huì)相互沖突的情況下伞访,比較將假定以前的段分別以0或“ga”段的形式進(jìn)行填充,直到種類不一致被解決為止轰驳,例如“1-alpha”=“1.0.0-alpha “<”1.0.1-ga“=”1.0.1“厚掷。 Stub Runner用于消息傳遞 Stub Runner具有在內(nèi)存中運(yùn)行已發(fā)布存根的功能。它可以與開(kāi)箱即用的以下框架集成 Spring Integration Spring Cloud Stream Apache Camel Spring AMQP 它還提供了與市場(chǎng)上任何其他解決方案集成的入口點(diǎn)级解。 存根觸發(fā) 要觸發(fā)消息冒黑,只需使用StubTrigger接口即可: package org.springframework.cloud.contract.stubrunner; import java.util.Collection; import java.util.Map; public interface StubTrigger { /** * Triggers an event by a given label for a given {@code groupid:artifactid} notation. You can use only {@code artifactId} too. * * Feature related to messaging. * * @return true - if managed to run a trigger */ boolean trigger(String ivyNotation, String labelName); /** * Triggers an event by a given label. * * Feature related to messaging. * * @return true - if managed to run a trigger */ boolean trigger(String labelName); /** * Triggers all possible events. * * Feature related to messaging. * * @return true - if managed to run a trigger */ boolean trigger(); /** * Returns a mapping of ivy notation of a dependency to all the labels it has. * * Feature related to messaging. */ Map> labels(); } 為了方便起見(jiàn),StubFinder接口擴(kuò)展了StubTrigger勤哗,所以只需要在你的測(cè)試中使用一個(gè)薛闪。 StubTrigger提供以下選項(xiàng)來(lái)觸發(fā)郵件: 按標(biāo)簽觸發(fā) stubFinder.trigger('return_book_1') 按組和人工制品ids觸發(fā) stubFinder.trigger('org.springframework.cloud.contract.verifier.stubs:camelService', 'return_book_1') 通過(guò)人工制品ids觸發(fā) stubFinder.trigger('camelService', 'return_book_1') 觸發(fā)所有消息 stubFinder.trigger() Stub Runner Camel Spring Cloud Contract驗(yàn)證器Stub Runner的消息傳遞模塊為您提供了與Apache Camel集成的簡(jiǎn)單方法。對(duì)于提供的工件俺陋,它將自動(dòng)下載存根并注冊(cè)所需的路由豁延。 將其添加到項(xiàng)目中 在類路徑上同時(shí)擁有Apache Camel和Spring Cloud Contract Stub Runner就足夠了昙篙。記住使用@AutoConfigureMessageVerifier注釋你的測(cè)試類。 例子 樁結(jié)構(gòu) 讓我們假設(shè)我們擁有以下Maven資源庫(kù)诱咏,并為camelService應(yīng)用程序配置了一個(gè)存根苔可。 └── .m2 └── repository └── io └── codearte └── accurest └── stubs └── camelService ├── 0.0.1-SNAPSHOT │?? ├── camelService-0.0.1-SNAPSHOT.pom │?? ├── camelService-0.0.1-SNAPSHOT-stubs.jar │?? └── maven-metadata-local.xml └── maven-metadata-local.xml 并且存根包含以下結(jié)構(gòu): ├── META-INF │?? └── MANIFEST.MF └── repository ├── accurest │?? ├── bookDeleted.groovy │?? ├── bookReturned1.groovy │?? └── bookReturned2.groovy └── mappings 讓我們考慮以下合同(讓我們用1來(lái)表示): Contract.make { label 'return_book_1' input { triggeredBy('bookReturnedTriggered()') } outputMessage { sentTo('jms:output') body('''{ "bookName" : "foo" }''') headers { header('BOOK-NAME', 'foo') } } } 和2號(hào) Contract.make { label 'return_book_2' input { messageFrom('jms:input') messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } } outputMessage { sentTo('jms:output') body([ bookName: 'foo' ]) headers { header('BOOK-NAME', 'foo') } } } 情景1(無(wú)輸入訊息) 為了通過(guò)return_book_1標(biāo)簽觸發(fā)消息,我們將使用StubTigger接口袋狞,如下所示 stubFinder.trigger('return_book_1') 接下來(lái)焚辅,我們將要收聽(tīng)發(fā)送到j(luò)ms:output的消息的輸出 Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000) 接收到的消息將通過(guò)以下斷言 receivedMessage != null assertThatBodyContainsBookNameFoo(receivedMessage.in.body) receivedMessage.in.headers.get('BOOK-NAME') == 'foo' 情景2(輸入觸發(fā)輸出) 由于路由是為您設(shè)置的,只需向jms:output目的地發(fā)送消息即可苟鸯。 camelContext.createProducerTemplate().sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header']) 接下來(lái)我們將要收聽(tīng)發(fā)送到j(luò)ms:output的消息的輸出 Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000) 接收到的消息將通過(guò)以下斷言 receivedMessage != null assertThatBodyContainsBookNameFoo(receivedMessage.in.body) receivedMessage.in.headers.get('BOOK-NAME') == 'foo' 情景3(無(wú)輸出輸入) 由于路由是為您設(shè)置的同蜻,只需向jms:output目的地發(fā)送消息即可。 camelContext.createProducerTemplate().sendBodyAndHeaders('jms:delete', new BookReturned('foo'), [sample: 'header']) Stub Runner整合 Spring Cloud Contract驗(yàn)證器Stub Runner的消息傳遞模塊為您提供了一種簡(jiǎn)單的與Spring Integration集成的方法早处。對(duì)于提供的工件湾蔓,它將自動(dòng)下載存根并注冊(cè)所需的路由。 將其添加到項(xiàng)目中 在類路徑上同時(shí)擁有Apache Camel和Spring Cloud Contract Stub Runner就足夠了砌梆。記住使用@AutoConfigureMessageVerifier注釋測(cè)試類默责。 例子 樁結(jié)構(gòu) 讓我們假設(shè)我們擁有以下Maven倉(cāng)庫(kù),并為integrationService應(yīng)用程序配置了一個(gè)存根咸包。 └── .m2 └── repository └── io └── codearte └── accurest └── stubs └── integrationService ├── 0.0.1-SNAPSHOT │?? ├── integrationService-0.0.1-SNAPSHOT.pom │?? ├── integrationService-0.0.1-SNAPSHOT-stubs.jar │?? └── maven-metadata-local.xml └── maven-metadata-local.xml 并且存根包含以下結(jié)構(gòu): ├── META-INF │?? └── MANIFEST.MF └── repository ├── accurest │?? ├── bookDeleted.groovy │?? ├── bookReturned1.groovy │?? └── bookReturned2.groovy └── mappings 讓我們考慮以下合同(讓我們用1來(lái)表示): Contract.make { label 'return_book_1' input { triggeredBy('bookReturnedTriggered()') } outputMessage { sentTo('output') body('''{ "bookName" : "foo" }''') headers { header('BOOK-NAME', 'foo') } } } 和2號(hào) Contract.make { label 'return_book_2' input { messageFrom('input') messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } } outputMessage { sentTo('output') body([ bookName: 'foo' ]) headers { header('BOOK-NAME', 'foo') } } } 和以下Spring Integration路由: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd"> output-channel="outputTest"/> 情景1(無(wú)輸入訊息) 為了通過(guò)return_book_1標(biāo)簽觸發(fā)一條消息桃序,我們將使用StubTigger接口,如下所示 stubFinder.trigger('return_book_1') 接下來(lái)我們將要收聽(tīng)發(fā)送到output的消息的輸出 Message receivedMessage = messaging.receive('outputTest') 接收到的消息將通過(guò)以下斷言 receivedMessage != null assertJsons(receivedMessage.payload) receivedMessage.headers.get('BOOK-NAME') == 'foo' 情景2(輸入觸發(fā)輸出) 由于路由是為您設(shè)置的烂瘫,只需向output目的地發(fā)送一條消息即可媒熊。 messaging.send(new BookReturned('foo'), [sample: 'header'], 'input') 接下來(lái),我們將要收聽(tīng)發(fā)送到output的消息的輸出 Message receivedMessage = messaging.receive('outputTest') 接收到的消息將通過(guò)以下斷言 receivedMessage != null assertJsons(receivedMessage.payload) receivedMessage.headers.get('BOOK-NAME') == 'foo' 情景3(無(wú)輸出輸入) 由于路由是為您設(shè)置的坟比,只需向input目的地發(fā)送消息即可芦鳍。 messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete') Stub Runner流 Spring Cloud Contract驗(yàn)證器Stub Runner的消息傳遞模塊為您提供了與Spring Stream集成的簡(jiǎn)單方式。對(duì)于提供的工件温算,它將自動(dòng)下載存根并注冊(cè)所需的路由怜校。 警告 在Stub Runner與Stream的集成中,messageFrom或sentTo字符串首先被解析為一個(gè)destination的頻道注竿,然后如果沒(méi)有這樣的destination茄茁,它被解析為頻道名稱。 將其添加到項(xiàng)目中 在類路徑上同時(shí)擁有Apache Camel和Spring Cloud Contract Stub Runner就足夠了巩割。記住用@AutoConfigureMessageVerifier注釋你的測(cè)試類裙顽。 例子 樁結(jié)構(gòu) 讓我們假設(shè)我們擁有以下Maven倉(cāng)庫(kù)箕母,并為streamService應(yīng)用程序配置了一個(gè)存根绑雄。 └── .m2 └── repository └── io └── codearte └── accurest └── stubs └── streamService ├── 0.0.1-SNAPSHOT │?? ├── streamService-0.0.1-SNAPSHOT.pom │?? ├── streamService-0.0.1-SNAPSHOT-stubs.jar │?? └── maven-metadata-local.xml └── maven-metadata-local.xml 并且存根包含以下結(jié)構(gòu): ├── META-INF │?? └── MANIFEST.MF └── repository ├── accurest │?? ├── bookDeleted.groovy │?? ├── bookReturned1.groovy │?? └── bookReturned2.groovy └── mappings 讓我們考慮以下合同(讓我們用1來(lái)表示): Contract.make { label 'return_book_1' input { triggeredBy('bookReturnedTriggered()') } outputMessage { sentTo('returnBook') body('''{ "bookName" : "foo" }''') headers { header('BOOK-NAME', 'foo') } } } 和2號(hào) Contract.make { label 'return_book_2' input { messageFrom('bookStorage') messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } } outputMessage { sentTo('returnBook') body([ bookName: 'foo' ]) headers { header('BOOK-NAME', 'foo') } } } 和以下Spring配置: stubrunner.repositoryRoot: classpath:m2repo/repository/ stubrunner.ids: org.springframework.cloud.contract.verifier.stubs:streamService:0.0.1-SNAPSHOT:stubs spring: cloud: stream: bindings: output: destination: returnBook input: destination: bookStorage server: port: 0 debug: true 情景1(無(wú)輸入訊息) 為了通過(guò)return_book_1標(biāo)簽觸發(fā)一條消息,我們將使用StubTrigger接口阻荒,如下所示 stubFinder.trigger('return_book_1') 接下來(lái),我們將要收聽(tīng)發(fā)送到destination為returnBook的頻道的消息的輸出 Message receivedMessage = messaging.receive('returnBook') 接收到的消息將通過(guò)以下斷言 receivedMessage != null assertJsons(receivedMessage.payload) receivedMessage.headers.get('BOOK-NAME') == 'foo' 情景2(輸入觸發(fā)輸出) 由于路由是為您設(shè)置的漩怎,只需向bookStoragedestination發(fā)送消息即可勋颖。 messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage') 接下來(lái)我們將要收聽(tīng)發(fā)送到returnBook的消息的輸出 Message receivedMessage = messaging.receive('returnBook') 接收到的消息將通過(guò)以下斷言 receivedMessage != null assertJsons(receivedMessage.payload) receivedMessage.headers.get('BOOK-NAME') == 'foo' 情景3(無(wú)輸出輸入) 由于路由是為您設(shè)置的,只需向output目的地發(fā)送消息即可勋锤。 messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete') Stub Runner Spring AMQP Spring Cloud Contract驗(yàn)證器Stub Runner的消息傳遞模塊提供了一種簡(jiǎn)單的方法來(lái)與Spring AMQP的Rabbit模板集成饭玲。對(duì)于提供的工件,它將自動(dòng)下載存根并注冊(cè)所需的路由叁执。 集成嘗試獨(dú)立運(yùn)行茄厘,即不與運(yùn)行的RabbitMQ消息代理交互。它期望在應(yīng)用程序上下文中使用RabbitTemplate谈宛,并將其用作spring boot測(cè)試@SpyBean次哈。因此,它可以使用mockito間諜功能來(lái)驗(yàn)證和內(nèi)省應(yīng)用程序發(fā)送的消息吆录。 在消費(fèi)消費(fèi)者方面窑滞,它考慮了所有@RabbitListener注釋端點(diǎn)以及應(yīng)用程序上下文中的所有“SimpleMessageListenerContainer”。 由于消息通常發(fā)送到AMQP中的交換機(jī)径筏,消息合同中包含交換機(jī)名稱作為目標(biāo)葛假。另一方的消息偵聽(tīng)器綁定到隊(duì)列障陶。綁定將交換機(jī)連接到隊(duì)列滋恬。如果觸發(fā)消息合約,Spring AMQP存根轉(zhuǎn)移器集成將在與該交換機(jī)匹配的應(yīng)用程序上下文中查找綁定抱究。然后它從Spring交換機(jī)收集隊(duì)列恢氯,并嘗試查找綁定到這些隊(duì)列的消息偵聽(tīng)器。消息被觸發(fā)到所有匹配的消息監(jiān)聽(tīng)器鼓寺。 將其添加到項(xiàng)目中 在類路徑上同時(shí)擁有Spring AMQP和Spring Cloud Contract Stub Runner就足夠了勋拟,并設(shè)置屬性stubrunner.amqp.enabled=true。記住用@AutoConfigureMessageVerifier注釋你的測(cè)試類妈候。 例子 樁結(jié)構(gòu) 讓我們假設(shè)我們擁有以下Maven資源庫(kù)敢靡,并為spring-cloud-contract-amqp-test應(yīng)用程序配置了一個(gè)存根。 └── .m2 └── repository └── com └── example └── spring-cloud-contract-amqp-test ├── 0.4.0-SNAPSHOT │?? ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT.pom │?? ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT-stubs.jar │?? └── maven-metadata-local.xml └── maven-metadata-local.xml 并且存根包含以下結(jié)構(gòu): ├── META-INF │?? └── MANIFEST.MF └── contracts └── shouldProduceValidPersonData.groovy 讓我們考慮下列合約: Contract.make { // Human readable description description 'Should produce valid person data' // Label by means of which the output message can be triggered label 'contract-test.person.created.event' // input to the contract input { // the contract will be triggered by a method triggeredBy('createPerson()') } // output message of the contract outputMessage { // destination to which the output message will be sent sentTo 'contract-test.exchange' headers { header('contentType': 'application/json') header('__TypeId__': 'org.springframework.cloud.contract.stubrunner.messaging.amqp.Person') } // the body of the output message body ([ id: $(consumer(9), producer(regex("[0-9]+"))), name: "me" ]) } } 和以下Spring配置: stubrunner: repositoryRoot: classpath:m2repo/repository/ ids: org.springframework.cloud.contract.verifier.stubs.amqp:spring-cloud-contract-amqp-test:0.4.0-SNAPSHOT:stubs amqp: enabled: true server: port: 0 觸發(fā)消息 因此苦银,為了觸發(fā)使用上述合同的消息啸胧,我們將使用StubTrigger界面如下。 stubTrigger.trigger("contract-test.person.created.event") 消息的目的地為contract-test.exchange幔虏,所以Spring AMQP存根轉(zhuǎn)移器集成查找與此交換相關(guān)的綁定纺念。 @Bean public Binding binding() { return BindingBuilder.bind(new Queue("test.queue")).to(new DirectExchange("contract-test.exchange")).with("#"); } 綁定定義綁定隊(duì)列test.queue。因此想括,以下監(jiān)聽(tīng)器定義是一個(gè)匹配陷谱,并使用合同消息進(jìn)行調(diào)用。 @Bean public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.setQueueNames("test.queue"); container.setMessageListener(listenerAdapter); return container; } 此外瑟蜈,以下注釋的監(jiān)聽(tīng)器表示一個(gè)匹配并將被調(diào)用烟逊。 @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "test.queue"), exchange = @Exchange(value = "contract-test.exchange", ignoreDeclarationExceptions = "true"))) public void handlePerson(Person person) { this.person = person; } 注意 該消息直接交給MessageListener與SimpleMessageListenerContainer匹配的MessageListener方法渣窜。 Spring AMQP測(cè)試配置 為了避免Spring AMQP在測(cè)試期間嘗試連接到運(yùn)行的代理,我們配置了一個(gè)模擬ConnectionFactory宪躯。 要禁用嘲弄的ConnectionFactory設(shè)置屬性stubrunner.amqp.mockConnection=false stubrunner: amqp: mockConnection: false Contract DSL 重要 請(qǐng)記住图毕,在合同文件中,您必須向Contract類和make靜態(tài)導(dǎo)入ieorg.springframework.cloud.spec.Contract.make { …? }提供完全限定名稱眷唉。您還可以向Contract類import org.springframework.cloud.spec.Contract提供導(dǎo)入予颤,然后調(diào)用Contract.make { …? } Contract DSL是用Groovy寫的,但是如果以前沒(méi)有使用Groovy冬阳,不要驚慌蛤虐。語(yǔ)言的知識(shí)并不是真正需要的,因?yàn)槲覀兊腄SL只使用它的一小部分(即文字肝陪,方法調(diào)用和閉包)驳庭。DSL還被設(shè)計(jì)為程序員可讀,而不需要DSL本身的知識(shí) - 它是靜態(tài)類型的氯窍。 小費(fèi) Spring Cloud Contract支持在單個(gè)文件中定義多個(gè)合同饲常! 合同存在于Spring Cloud Contract驗(yàn)證器存儲(chǔ)庫(kù)的spring-cloud-contract-spec模塊中。 我們來(lái)看一下合同定義的完整例子狼讨。 org.springframework.cloud.contract.spec.Contract.make { request { method 'PUT' url '/api/12' headers { header 'Content-Type': 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json' } body '''\ [{ "created_at": "Sat Jul 26 09:38:57 +0000 2014", "id": 492967299297845248, "id_str": "492967299297845248", "text": "Gonna see you at Warsaw", "place": { "attributes":{}, "bounding_box": { "coordinates": [[ [-77.119759,38.791645], [-76.909393,38.791645], [-76.909393,38.995548], [-77.119759,38.995548] ]], "type":"Polygon" }, "country":"United States", "country_code":"US", "full_name":"Washington, DC", "id":"01fbe706f872cb32", "name":"Washington", "place_type":"city", "url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json" } }] ''' } response { status 200 } } 不是DSL的所有功能都在上面的例子中使用贝淤。如果您找不到您想要的內(nèi)容,請(qǐng)查看本頁(yè)下面的段落政供。 您可以使用獨(dú)立的maven命令mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert輕松地將Contracts編譯為WireMock存根映射厂庇。 限制 警告 Spring Cloud Contract驗(yàn)證器不正確支持XML般又。請(qǐng)使用JSON或幫助我們實(shí)現(xiàn)此功能胀滚。 警告 對(duì)JSON數(shù)組的大小的驗(yàn)證的支持是實(shí)驗(yàn)性的澎迎。如果要打開(kāi)它,請(qǐng)?zhí)峁┑扔趖rue的系統(tǒng)屬性spring.cloud.contract.verifier.assert.size的值衅檀。默認(rèn)情況下招刨,此功能設(shè)置為false。您還可以在插件配置中提供assertJsonSize屬性哀军。 警告 由于JSON結(jié)構(gòu)可以有任何形式沉眶,因此在GString中使用時(shí)使用value(consumer(…?), producer(…?))符號(hào)時(shí),有時(shí)無(wú)法正確解析它排苍。這就是為什么我們強(qiáng)烈推薦使用Groovy Map符號(hào)沦寂。 常見(jiàn)的頂級(jí)元素 描述 您可以添加一個(gè)description到您的合同,除了一個(gè)任意的文本淘衙。例: org.springframework.cloud.contract.spec.Contract.make { description(''' given: An input when: Sth happens then: Output ''') } 名稱 您可以提供您的合同名稱传藏。假設(shè)您提供了一個(gè)名稱should register a user。如果這樣做,則自動(dòng)生成測(cè)試的名稱將等于validate_should_register_a_user毯侦。如果是WireMock存根哭靖,存根的名稱也將為should_register_a_user.json。 重要 請(qǐng)確保該名稱不包含任何會(huì)使生成的測(cè)試無(wú)法編譯的字符侈离。還要記住试幽,如果您為多個(gè)合同提供相同的名稱,那么您的自動(dòng)生成測(cè)試將無(wú)法編譯卦碾,并且生成的存根將會(huì)相互覆蓋铺坞。 忽略合同 如果您想忽略合同,您可以在插件配置中設(shè)置忽略合同的值洲胖,或者僅在合同本身設(shè)置ignored屬性: org.springframework.cloud.contract.spec.Contract.make { ignored() } HTTP頂級(jí)元素 可以在合同定義的頂層關(guān)閉中調(diào)用以下方法济榨。請(qǐng)求和響應(yīng)是強(qiáng)制性的,優(yōu)先級(jí)是可選的绿映。 org.springframework.cloud.contract.spec.Contract.make { // Definition of HTTP request part of the contract // (this can be a valid request or invalid depending // on type of contract being specified). request { //... } // Definition of HTTP response part of the contract // (a service implementing this contract should respond // with following response after receiving request // specified in "request" part above). response { //... } // Contract priority, which can be used for overriding // contracts (1 is highest). Priority is optional. priority 1 } 請(qǐng)求 HTTP協(xié)議只需要在請(qǐng)求中指定方法和地址擒滑。在合同的請(qǐng)求定義中,相同的信息是強(qiáng)制性的叉弦。 org.springframework.cloud.contract.spec.Contract.make { request { // HTTP request method (GET/POST/PUT/DELETE). method 'GET' // Path component of request URL is specified as follows. urlPath('/users') } response { //... } } 可以指定整個(gè)url而不是路徑丐一,但是urlPath是測(cè)試與主機(jī)無(wú)關(guān)的推薦方法。 org.springframework.cloud.contract.spec.Contract.make { request { method 'GET' // Specifying `url` and `urlPath` in one contract is illegal. url('http://localhost:8888/users') } response { //... } } 請(qǐng)求可能包含查詢參數(shù)淹冰,這些參數(shù)在嵌套在urlPath或url的調(diào)用中的閉包中指定库车。 org.springframework.cloud.contract.spec.Contract.make { request { //... urlPath('/users') { // Each parameter is specified in form // `'paramName' : paramValue` where parameter value // may be a simple literal or one of matcher functions, // all of which are used in this example. queryParameters { // If a simple literal is used as value // default matcher function is used (equalTo) parameter 'limit': 100 // `equalTo` function simply compares passed value // using identity operator (==). parameter 'filter': equalTo("email") // `containing` function matches strings // that contains passed substring. parameter 'gender': value(consumer(containing("[mf]")), producer('mf')) // `matching` function tests parameter // against passed regular expression. parameter 'offset': value(consumer(matching("[0-9]+")), producer(123)) // `notMatching` functions tests if parameter // does not match passed regular expression. parameter 'loginStartsWith': value(consumer(notMatching(".{0,2}")), producer(3)) } } //... } response { //... } } 它可能包含其他請(qǐng)求標(biāo)頭... org.springframework.cloud.contract.spec.Contract.make { request { //... // Each header is added in form `'Header-Name' : 'Header-Value'`. // there are also some helper methods headers { header 'key': 'value' contentType(applicationJson()) } //... } response { //... } } ...和請(qǐng)求機(jī)構(gòu)。 org.springframework.cloud.contract.spec.Contract.make { request { //... // Currently only JSON format of request body is supported. // Format will be determined from a header or body's content. body '''{ "login" : "john", "name": "John The Contract" }''' } response { //... } } 響應(yīng) 最小響應(yīng)必須包含HTTP狀態(tài)代碼榄棵。 org.springframework.cloud.contract.spec.Contract.make { request { //... } response { // Status code sent by the server // in response to request specified above. status 200 } } 除了狀態(tài)響應(yīng)可能包含標(biāo)頭和正文之外凝颇,它們與請(qǐng)求中的方式相同(參見(jiàn)前一段)潘拱。 動(dòng)態(tài)屬性 合同可以包含一些動(dòng)態(tài)屬性 - 時(shí)間戳/ ids等疹鳄。您不想強(qiáng)制使用者將其時(shí)鐘保留為始終返回相同的時(shí)間值,以便與存根匹配芦岂。這就是為什么我們?cè)试S您以兩種方式在合同中提供動(dòng)態(tài)部分瘪弓。一個(gè)是將它們直接傳遞到體內(nèi),一個(gè)將它們?cè)O(shè)置在另一部分禽最,稱為testMatchers和stubMatchers腺怯。 體內(nèi)動(dòng)態(tài)屬性 您可以通過(guò)value方法設(shè)置體內(nèi)的屬性 value(consumer(...), producer(...)) value(c(...), p(...)) value(stub(...), test(...)) value(client(...), server(...)) 或者如果您正在使用Groovy地圖符號(hào),您可以使用$()方法 $(consumer(...), producer(...)) $(c(...), p(...)) $(stub(...), test(...)) $(client(...), server(...)) 所有上述方法都是相同的川无。這意味著stub和client方法是consumer方法的別名呛占。我們來(lái)仔細(xì)看看我們可以在后續(xù)章節(jié)中對(duì)這些值做些什么。 正則表達(dá)式 您可以使用正則表達(dá)式在Contract DSL中寫入請(qǐng)求懦趋。當(dāng)您想要指出給定的響應(yīng)應(yīng)該被提供給遵循給定模式的請(qǐng)求時(shí)晾虑,這是特別有用的。此外,當(dāng)您需要使用模式帜篇,而不是測(cè)試和服務(wù)器端測(cè)試時(shí)糙捺,您可以使用它。 請(qǐng)看下面的例子: org.springframework.cloud.contract.spec.Contract.make { request { method('GET') url $(consumer(~/\/[0-9]{2}/), producer('/12')) } response { status 200 body( id: $(anyNumber()), surname: $( consumer('Kowalsky'), producer(regex('[a-zA-Z]+')) ), name: 'Jan', created: $(consumer('2014-02-02 12:23:43'), producer(execute('currentDate(it)'))), correlationId: value(consumer('5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827'), producer(regex('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')) ) ) headers { header 'Content-Type': 'text/plain' } } } 您還可以使用正則表達(dá)式僅提供通信的一方笙隙。如果這樣做洪灯,那么我們將自動(dòng)提供與提供的正則表達(dá)式匹配的生成的字符串。例如: org.springframework.cloud.contract.spec.Contract.make { request { method 'PUT' url value(consumer(regex('/foo/[0-9]{5}'))) body([ requestElement: $(consumer(regex('[0-9]{5}'))) ]) headers { header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*')))) } } response { status 200 body([ responseElement: $(producer(regex('[0-9]{7}'))) ]) headers { contentType("application/vnd.fraud.v1+json") } } } 在該示例中竟痰,對(duì)于請(qǐng)求和響應(yīng)签钩,通信的相對(duì)側(cè)將具有生成的相應(yīng)數(shù)據(jù)。 Spring Cloud Contract附帶一系列預(yù)定義的正則表達(dá)式坏快,您可以在合同中使用边臼。 protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/) protected static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/) protected static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?') protected static final Pattern IP_ADDRESS = Pattern.compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])') protected static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?') protected static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}'); protected static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])') protected static final Pattern UUID = Pattern.compile('[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}') protected static final Pattern ANY_DATE = Pattern.compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])') protected static final Pattern ANY_DATE_TIME = Pattern.compile('([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') protected static final Pattern ANY_TIME = Pattern.compile('(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') protected static final Pattern NON_EMPTY = Pattern.compile(/.+/) protected static final Pattern NON_BLANK = Pattern.compile(/.*(\S+|\R).*|!^\R*$/) protected static final Pattern ISO8601_WITH_OFFSET = Pattern.compile(/([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.\d{3})?(Z|[+-][01]\d:[0-5]\d)/) protected static Pattern anyOf(String... values){ return Pattern.compile(values.collect({"^$it\$"}).join("|")) } String onlyAlphaUnicode() { return ONLY_ALPHA_UNICODE.pattern() } String number() { return NUMBER.pattern() } String anyBoolean() { return TRUE_OR_FALSE.pattern() } String ipAddress() { return IP_ADDRESS.pattern() } String hostname() { return HOSTNAME_PATTERN.pattern() } String email() { return EMAIL.pattern() } String url() { return URL.pattern() } String uuid(){ return UUID.pattern() } String isoDate() { return ANY_DATE.pattern() } String isoDateTime() { return ANY_DATE_TIME.pattern() } String isoTime() { return ANY_TIME.pattern() } String iso8601WithOffset() { return ISO8601_WITH_OFFSET.pattern() } String nonEmpty() { return NON_EMPTY.pattern() } String nonBlank() { return NON_BLANK.pattern() } 所以在你的合同中你可以這樣使用它 Contract dslWithOptionalsInString = Contract.make { priority 1 request { method POST() url '/users/password' headers { contentType(applicationJson()) } body( email: $(consumer(optional(regex(email()))), producer('abc@abc.com')), callback_url: $(consumer(regex(hostname())), producer('http://partners.com')) ) } response { status 404 headers { contentType(applicationJson()) } body( code: value(consumer("123123"), producer(optional("123123"))), message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]" ) } } 傳遞可選參數(shù) 可以在您的合同中提供可選參數(shù)梯醒。只能有可選參數(shù): STUB側(cè)請(qǐng)求 響應(yīng)的TEST側(cè) 例: org.springframework.cloud.contract.spec.Contract.make { priority 1 request { method 'POST' url '/users/password' headers { contentType(applicationJson()) } body( email: $(consumer(optional(regex(email()))), producer('abc@abc.com')), callback_url: $(consumer(regex(hostname())), producer('http://partners.com')) ) } response { status 404 headers { header 'Content-Type': 'application/json' } body( code: value(consumer("123123"), producer(optional("123123"))) ) } } 通過(guò)使用optional()方法包裝身體的一部分宾茂,您實(shí)際上正在創(chuàng)建一個(gè)應(yīng)該存在0次或更多次的正則表達(dá)式桨菜。 如果您選擇Spock连茧,那么上述示例將會(huì)生成以下測(cè)試: """ given: def request = given() .header("Content-Type", "application/json") .body('''{"email":"abc@abc.com","callback_url":"http://partners.com"}''') when: def response = given().spec(request) .post("/users/password") then: response.statusCode == 404 response.header('Content-Type')? == 'application/json' and: DocumentContext parsedJson = JsonPath.parse(response.body.asString()) assertThatJson(parsedJson).field("code").matches("(123123)?") """ 和以下存根: ''' { "request" : { "url" : "/users/password", "method" : "POST", "bodyPatterns" : [ { "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]" }, { "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]" } ], "headers" : { "Content-Type" : { "equalTo" : "application/json" } } }, "response" : { "status" : 404, "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}", "headers" : { "Content-Type" : "application/json" } }, "priority" : 1 } ''' 在服務(wù)器端執(zhí)行自定義方法 也可以在測(cè)試期間定義要在服務(wù)器端執(zhí)行的方法調(diào)用虹菲。這樣的方法可以添加到在配置中定義為“baseClassForTests”的類中粟矿。例: 合同 org.springframework.cloud.contract.spec.Contract.make { request { method 'PUT' url $(consumer(regex('^/api/[0-9]{2}$')), producer('/api/12')) headers { header 'Content-Type': 'application/json' } body '''\ [{ "text": "Gonna see you at Warsaw" }] ''' } response { body ( path: $(consumer('/api/12'), producer(regex('^/api/[0-9]{2}$'))), correlationId: $(consumer('1223456'), producer(execute('isProperCorrelationId($it)'))) ) status 200 } } 基礎(chǔ)班 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 } } 重要 您不能同時(shí)使用String和execute來(lái)執(zhí)行連接侣颂。例如呼叫header('Authorization', 'Bearer ' + execute('authToken()'))將導(dǎo)致不正確的結(jié)果胯甩。要使此工作只需調(diào)用header('Authorization', execute('authToken()'))啃沪,并確保authToken()方法返回您需要的所有內(nèi)容粘拾。 從響應(yīng)引用請(qǐng)求 最好的情況是提供固定值,但有時(shí)您需要在響應(yīng)中引用請(qǐng)求创千。為了做到這一點(diǎn)缰雇,您可以從fromRequest()方法中獲利,從而允許您從HTTP請(qǐng)求中引用一堆元素追驴。您可以使用以下選項(xiàng): fromRequest().url()- 返回請(qǐng)求URL fromRequest().query(String key)- 返回具有給定名稱的第一個(gè)查詢參數(shù) fromRequest().query(String key, int index)- 返回具有給定名稱的第n個(gè)查詢參數(shù) fromRequest().header(String key)- 返回具有給定名稱的第一個(gè)標(biāo)題 fromRequest().header(String key, int index)- 返回具有給定名稱的第n個(gè)標(biāo)題 fromRequest().body()- 返回完整的請(qǐng)求體 fromRequest().body(String jsonPath)- 從與JSON路徑匹配的請(qǐng)求中返回元素 我們來(lái)看看下面的合同 Contract contractDsl = Contract.make { request { method 'GET' url('/api/v1/xxxx') { queryParameters { parameter("foo", "bar") parameter("foo", "bar2") } } headers { header(authorization(), "secret") header(authorization(), "secret2") } body(foo: "bar", baz: 5) } response { status 200 headers { header(authorization(), "foo ${fromRequest().header(authorization())} bar") } body( url: fromRequest().url(), param: fromRequest().query("foo"), paramIndex: fromRequest().query("foo", 1), authorization: fromRequest().header("Authorization"), authorization2: fromRequest().header("Authorization", 1), fullBody: fromRequest().body(), responseFoo: fromRequest().body('$.foo'), responseBaz: fromRequest().body('$.baz'), responseBaz2: "Bla bla ${fromRequest().body('$.foo')} bla bla" ) } } 運(yùn)行JUnit測(cè)試代碼將導(dǎo)致創(chuàng)建一個(gè)或多或少這樣的測(cè)試 // given: MockMvcRequestSpecification request = given() .header("Authorization", "secret") .header("Authorization", "secret2") .body("{\"foo\":\"bar\",\"baz\":5}"); // when: ResponseOptions response = given().spec(request) .queryParam("foo","bar") .queryParam("foo","bar2") .get("/api/v1/xxxx"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Authorization")).isEqualTo("foo secret bar"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("url").isEqualTo("/api/v1/xxxx"); assertThatJson(parsedJson).field("fullBody").isEqualTo("{\"foo\":\"bar\",\"baz\":5}"); assertThatJson(parsedJson).field("paramIndex").isEqualTo("bar2"); assertThatJson(parsedJson).field("responseFoo").isEqualTo("bar"); assertThatJson(parsedJson).field("authorization2").isEqualTo("secret2"); assertThatJson(parsedJson).field("responseBaz").isEqualTo(5); assertThatJson(parsedJson).field("responseBaz2").isEqualTo("Bla bla bar bla bla"); assertThatJson(parsedJson).field("param").isEqualTo("bar"); assertThatJson(parsedJson).field("authorization").isEqualTo("secret"); 您可以看到請(qǐng)求中的元素在響應(yīng)中已被正確引用械哟。 生成的WireMock存根將看起來(lái)或多或少是這樣的: { "request" : { "urlPath" : "/api/v1/xxxx", "method" : "POST", "headers" : { "Authorization" : { "equalTo" : "secret2" } }, "queryParameters" : { "foo" : { "equalTo" : "bar2" } }, "bodyPatterns" : [ { "matchesJsonPath" : "$[?(@.baz == 5)]" }, { "matchesJsonPath" : "$[?(@.foo == 'bar')]" } ] }, "response" : { "status" : 200, "body" : "{\"url\":\"{{{request.url}}}\",\"param\":\"{{{request.query.foo.[0]}}}\",\"paramIndex\":\"{{{request.query.foo.[1]}}}\",\"authorization\":\"{{{request.headers.Authorization.[0]}}}\",\"authorization2\":\"{{{request.headers.Authorization.[1]}}}\",\"fullBody\":\"{{{escapejsonbody}}}\",\"responseFoo\":\"{{{jsonpath this '$.foo'}}}\",\"responseBaz\":{{{jsonpath this '$.baz'}}} ,\"responseBaz2\":\"Bla bla {{{jsonpath this '$.foo'}}} bla bla\"}", "headers" : { "Authorization" : "{{{request.headers.Authorization.[0]}}}" }, "transformers" : [ "response-template" ] } } 因此,發(fā)送請(qǐng)求作為合同request部分提出的請(qǐng)求將導(dǎo)致發(fā)送以下響應(yīng)主體 { "url" : "/api/v1/xxxx?foo=bar&foo=bar2", "param" : "bar", "paramIndex" : "bar2", "authorization" : "secret", "authorization2" : "secret2", "fullBody" : "{\"foo\":\"bar\",\"baz\":5}", "responseFoo" : "bar", "responseBaz" : 5, "responseBaz2" : "Bla bla bar bla bla" } 重要 此功能僅適用于版本大于或等于2.5.1的WireMock殿雪。我們正在使用WireMock的response-template響應(yīng)變壓器暇咆。它使用Handlebars將Mustache{{{ }}}模板轉(zhuǎn)換成正確的值。另外我們正在注冊(cè)2個(gè)幫助函數(shù)丙曙。escapejsonbody- 以可嵌入JSON的格式轉(zhuǎn)義請(qǐng)求正文爸业。另一個(gè)是jsonpath對(duì)于給定的參數(shù)知道如何在請(qǐng)求體中查找對(duì)象。 匹配部分的動(dòng)態(tài)屬性 如果您一直在使用Pact亏镰,這似乎很熟悉扯旷。很多用戶習(xí)慣于在身體和設(shè)定合約的動(dòng)態(tài)部分之間進(jìn)行分隔。 這就是為什么你可以從兩個(gè)不同的部分獲利索抓。一個(gè)稱為stubMatchers钧忽,您可以在其中定義應(yīng)該存在于存根中的動(dòng)態(tài)值某抓。您可以在合同的request或inputMessage部分設(shè)置。另一個(gè)稱為testMatchers惰瓜,它存在于合同的response或outputMessage方面否副。 目前,我們僅支持具有以下匹配可能性的基于JSON路徑的匹配器崎坊。對(duì)于stubMatchers: byEquality()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要等于合同中提供的值 byRegex(…?)- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要與正則表達(dá)式匹配 byDate()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要與ISO Date的正則表達(dá)式匹配 byTimestamp()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要與ISO DateTime的正則表達(dá)式匹配 byTime()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要匹配ISO時(shí)間的正則表達(dá)式 對(duì)于testMatchers: byEquality()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要等于合同中提供的值 byRegex(…?)- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要與正則表達(dá)式匹配 byDate()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要與ISO Date的正則表達(dá)式匹配 byTimestamp()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要匹配ISO DateTime的正則表達(dá)式 byTime()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要匹配ISO時(shí)間的正則表達(dá)式 byType()- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值需要與合同中的響應(yīng)正文中定義的類型相同备禀。byType可以關(guān)閉,您可以設(shè)置minOccurrence和maxOccurrence奈揍。這樣你可以斷定集合的大小曲尸。 byCommand(…?)- 通過(guò)提供的JSON路徑從響應(yīng)中獲取的值將作為您提供的自定義方法的輸入傳遞。例如byCommand('foo($it)')將導(dǎo)致調(diào)用匹配JSON路徑的值將被通過(guò)的foo方法男翰。 我們來(lái)看看下面的例子: Contract contractDsl = Contract.make { request { method 'GET' urlPath '/get' body([ duck: 123, alpha: "abc", number: 123, aBoolean: true, date: "2017-01-01", dateTime: "2017-01-01T01:23:45", time: "01:02:34", valueWithoutAMatcher: "foo", valueWithTypeMatch: "string" ]) stubMatchers { jsonPath('$.duck', byRegex("[0-9]{3}")) jsonPath('$.duck', byEquality()) jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number())) jsonPath('$.aBoolean', byRegex(anyBoolean())) jsonPath('$.date', byDate()) jsonPath('$.dateTime', byTimestamp()) jsonPath('$.time', byTime()) } headers { contentType(applicationJson()) } } response { status 200 body([ duck: 123, alpha: "abc", number: 123, aBoolean: true, date: "2017-01-01", dateTime: "2017-01-01T01:23:45", time: "01:02:34", valueWithoutAMatcher: "foo", valueWithTypeMatch: "string", valueWithMin: [ 1,2,3 ], valueWithMax: [ 1,2,3 ], valueWithMinMax: [ 1,2,3 ], valueWithMinEmpty: [], valueWithMaxEmpty: [], ]) testMatchers { // asserts the jsonpath value against manual regex jsonPath('$.duck', byRegex("[0-9]{3}")) // asserts the jsonpath value against the provided value jsonPath('$.duck', byEquality()) // asserts the jsonpath value against some default regex jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number())) jsonPath('$.aBoolean', byRegex(anyBoolean())) // asserts vs inbuilt time related regex jsonPath('$.date', byDate()) jsonPath('$.dateTime', byTimestamp()) jsonPath('$.time', byTime()) // asserts that the resulting type is the same as in response body jsonPath('$.valueWithTypeMatch', byType()) jsonPath('$.valueWithMin', byType { // results in verification of size of array (min 1) minOccurrence(1) }) jsonPath('$.valueWithMax', byType { // results in verification of size of array (max 3) maxOccurrence(3) }) jsonPath('$.valueWithMinMax', byType { // results in verification of size of array (min 1 & max 3) minOccurrence(1) maxOccurrence(3) }) jsonPath('$.valueWithMinEmpty', byType { // results in verification of size of array (min 0) minOccurrence(0) }) jsonPath('$.valueWithMaxEmpty', byType { // results in verification of size of array (max 0) maxOccurrence(0) }) // will execute a method `assertThatValueIsANumber` jsonPath('$.duck', byCommand('assertThatValueIsANumber($it)')) } headers { contentType(applicationJson()) } } } 在這個(gè)例子中另患,我們?cè)谄ヅ淦鞑糠痔峁┖贤膭?dòng)態(tài)部分。對(duì)于請(qǐng)求部分蛾绎,您可以看到對(duì)于所有字段昆箕,但是valueWithoutAMatcher我們正在明確地設(shè)置我們希望存根包含的正則表達(dá)式的值。對(duì)于valueWithoutAMatcher租冠,驗(yàn)證將以與不使用匹配器相同的方式進(jìn)行 - 在這種情況下鹏倘,測(cè)試將執(zhí)行相等檢查。 對(duì)于testMatchers部分的響應(yīng)方面顽爹,我們以類似的方式定義所有的動(dòng)態(tài)部分纤泵。唯一的區(qū)別是我們也有byType匹配器。在這種情況下镜粤,我們正在檢查4個(gè)字段捏题,我們正在驗(yàn)證測(cè)試的響應(yīng)是否具有一個(gè)值,其JSON路徑與給定字段匹配的類型與響應(yīng)主體中定義的相同肉渴, 對(duì)于$.valueWithTypeMatch- 我們只是檢查類型是否相同 對(duì)于$.valueWithMin- 我們正在檢查類型公荧,并聲明大小是否大于或等于最小出現(xiàn)次數(shù) 對(duì)于$.valueWithMax- 我們正在檢查類型,并聲明大小是否小于或等于最大值 對(duì)于$.valueWithMinMax- 我們正在檢查類型黄虱,并確定大小是否在最小和最大值之間 所得到的測(cè)試或多或少會(huì)看起來(lái)像這樣(請(qǐng)注意稚矿,我們將自動(dòng)生成的斷言與匹配器與and部分分開(kāi)): // given: MockMvcRequestSpecification request = given() .header("Content-Type", "application/json") .body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}"); // when: ResponseOptions response = given().spec(request) .get("/get"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo"); // and: assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}"); assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123); assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*"); assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc"); assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?"); assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)"); assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"); assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class); assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class); assertThat(parsedJson.read("$.valueWithMin", java.util.Collection.class)).hasSizeGreaterThanOrEqualTo(1); assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class); assertThat(parsedJson.read("$.valueWithMax", java.util.Collection.class)).hasSizeLessThanOrEqualTo(3); assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class); assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class)).hasSizeBetween(1, 3); assertThat((Object) parsedJson.read("$.valueWithMinEmpty")).isInstanceOf(java.util.List.class); assertThat(parsedJson.read("$.valueWithMinEmpty", java.util.Collection.class)).hasSizeGreaterThanOrEqualTo(0); assertThat((Object) parsedJson.read("$.valueWithMaxEmpty")).isInstanceOf(java.util.List.class); assertThat(parsedJson.read("$.valueWithMaxEmpty", java.util.Collection.class)).hasSizeLessThanOrEqualTo(0); assertThatValueIsANumber(parsedJson.read("$.duck")); 和WireMock這樣的stub: ''' { "request" : { "urlPath" : "/get", "method" : "GET", "headers" : { "Content-Type" : { "matches" : "application/json.*" } }, "bodyPatterns" : [ { "matchesJsonPath" : "$[?(@.valueWithoutAMatcher == 'foo')]" }, { "matchesJsonPath" : "$[?(@.valueWithTypeMatch == 'string')]" }, { "matchesJsonPath" : "$.list.some.nested[?(@.anothervalue == 4)]" }, { "matchesJsonPath" : "$.list.someother.nested[?(@.anothervalue == 4)]" }, { "matchesJsonPath" : "$.list.someother.nested[?(@.json == 'with value')]" }, { "matchesJsonPath" : "$[?(@.duck =~ /([0-9]{3})/)]" }, { "matchesJsonPath" : "$[?(@.duck == 123)]" }, { "matchesJsonPath" : "$[?(@.alpha =~ /([\\\\p{L}]*)/)]" }, { "matchesJsonPath" : "$[?(@.alpha == 'abc')]" }, { "matchesJsonPath" : "$[?(@.number =~ /(-?\\\\d*(\\\\.\\\\d+)?)/)]" }, { "matchesJsonPath" : "$[?(@.aBoolean =~ /((true|false))/)]" }, { "matchesJsonPath" : "$[?(@.date =~ /((\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))/)]" }, { "matchesJsonPath" : "$[?(@.dateTime =~ /(([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))/)]" }, { "matchesJsonPath" : "$[?(@.time =~ /((2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))/)]" }, { "matchesJsonPath" : "$.list.some.nested[?(@.json =~ /(.*)/)]" } ] }, "response" : { "status" : 200, "body" : "{\\"duck\\":123,\\"alpha\\":\\"abc\\",\\"number\\":123,\\"aBoolean\\":true,\\"date\\":\\"2017-01-01\\",\\"dateTime\\":\\"2017-01-01T01:23:45\\",\\"time\\":\\"01:02:34\\",\\"valueWithoutAMatcher\\":\\"foo\\",\\"valueWithTypeMatch\\":\\"string\\",\\"valueWithMin\\":[1,2,3],\\"valueWithMax\\":[1,2,3],\\"valueWithMinMax\\":[1,2,3]}", "headers" : { "Content-Type" : "application/json" } } } ''' JAX-RS支持 我們支持JAX-RS 2 Client API∧砥郑基類需要定義protected WebTarget webTarget和服務(wù)器初始化,現(xiàn)在唯一的選擇如何測(cè)試JAX-RS API是啟動(dòng)一個(gè)Web服務(wù)器桥爽。 使用身體的請(qǐng)求需要設(shè)置內(nèi)容類型朱灿,否則將使用application/octet-stream。 為了使用JAX-RS模式钠四,請(qǐng)使用以下設(shè)置: testMode === 'JAXRSCLIENT' 生成測(cè)試API的示例: ''' // when: Response response = webTarget .path("/users") .queryParam("limit", "10") .queryParam("offset", "20") .queryParam("filter", "email") .queryParam("sort", "name") .queryParam("search", "55") .queryParam("age", "99") .queryParam("name", "Denis.Stepanov") .queryParam("email", "bob@email.com") .request() .method("GET"); String responseAsString = response.readEntity(String.class); // then: assertThat(response.getStatus()).isEqualTo(200); // and: DocumentContext parsedJson = JsonPath.parse(responseAsString); assertThatJson(parsedJson).field("property1").isEqualTo("a"); ''' 異步支持 如果您在服務(wù)器端使用異步通信(您的控制器正在返回Callable,DeferredResult等等要门,然后在合同中您必須在response部分中提供async()方法获茬。 : org.springframework.cloud.contract.spec.Contract.make { request { method GET() url '/get' } response { status 200 body 'Passed' async() } } 使用上下文路徑 Spring Cloud Contract支持上下文路徑。 重要 為了完全支持上下文路徑甸祭,唯一改變的是在PRODUCER端的切換。自動(dòng)生成測(cè)試需要使用EXPLICIT模式褥影。 消費(fèi)者方面保持不變池户,為了讓生成的測(cè)試通過(guò),您必須切換EXPLICIT模式凡怎。 Maven的 org.springframework.cloud spring-cloud-contract-maven-plugin ${spring-cloud-contract.version} true EXPLICIT 搖籃 contracts { testMode = 'EXPLICIT' } 這樣就可以生成不使用MockMvc的測(cè)試校焦。這意味著您正在生成真實(shí)的請(qǐng)求,您需要設(shè)置生成的測(cè)試的基類以在真正的套接字上工作统倒。 讓我們想象下面的合同: org.springframework.cloud.contract.spec.Contract.make { request { method 'GET' url '/my-context-path/url' } response { status 200 } } 以下是一個(gè)如何設(shè)置基類和Rest Assured的示例寨典,以使所有操作都正常工作。 import com.jayway.restassured.RestAssured; import org.junit.Before; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = ContextPathTestingBaseClass.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ContextPathTestingBaseClass { @LocalServerPort int port; @Before public void setup() { RestAssured.baseURI = "http://localhost"; RestAssured.port = this.port; } } 這樣一來(lái): 您自動(dòng)生成測(cè)試中的所有請(qǐng)求都將發(fā)送到包含上下文路徑的實(shí)際端點(diǎn)(例如/my-context-path/url) 您的合同反映出您具有上下文路徑房匆,因此您生成的存根也將具有該信息(例如耸成,在存根中您將看到您也調(diào)用了/my-context-path/url) 消息傳遞頂級(jí)元素 消息傳遞的DSL與重點(diǎn)在HTTP上的DSL有點(diǎn)不同。 由方法觸發(fā)的輸出 可以通過(guò)調(diào)用方法來(lái)觸發(fā)輸出消息(例如浴鸿,調(diào)度程序啟動(dòng)并發(fā)送消息) def dsl = Contract.make { // Human readable description description 'Some description' // Label by means of which the output message can be triggered label 'some_label' // input to the contract input { // the contract will be triggered by a method triggeredBy('bookReturnedTriggered()') } // output message of the contract outputMessage { // destination to which the output message will be sent sentTo('output') // the body of the output message body('''{ "bookName" : "foo" }''') // the headers of the output message headers { header('BOOK-NAME', 'foo') } } } 在這種情況下墓猎,如果將執(zhí)行一個(gè)稱為bookReturnedTriggered的方法,輸出消息將被發(fā)送到output赚楚。在消息發(fā)布者的一方毙沾,我們將生成一個(gè)測(cè)試,該測(cè)試將調(diào)用該方法來(lái)觸發(fā)該消息宠页。在消費(fèi)者端左胞,您可以使用some_label觸發(fā)消息。 由消息觸發(fā)的輸出 可以通過(guò)接收消息來(lái)觸發(fā)輸出消息举户。 def dsl = Contract.make { description 'Some Description' label 'some_label' // input is a message input { // the message was received from this destination messageFrom('input') // has the following body messageBody([ bookName: 'foo' ]) // and the following headers messageHeaders { header('sample', 'header') } } outputMessage { sentTo('output') body([ bookName: 'foo' ]) headers { header('BOOK-NAME', 'foo') } } } 在這種情況下烤宙,如果input目的地收到正確的消息,則輸出消息將被發(fā)送到output俭嘁。在消息發(fā)布者的一方躺枕,我們將生成一個(gè)測(cè)試,它將輸入消息發(fā)送到定義的目的地供填。在消費(fèi)者端拐云,您可以向輸入目的地發(fā)送消息,也可以使用some_label觸發(fā)消息近她。 消費(fèi)者/生產(chǎn)者 在HTTP中叉瘩,您有一個(gè)概念client/stub and `server/test符號(hào)。您也可以在消息中使用它們粘捎,但是我們還提供了下面提供的consumer和produer方法(請(qǐng)注意薇缅,您可以使用$或value方法來(lái)提供consumer和producer部分) Contract.make { label 'some_label' input { messageFrom value(consumer('jms:output'), producer('jms:input')) messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } } outputMessage { sentTo $(consumer('jms:input'), producer('jms:output')) body([ bookName: 'foo' ]) } } 一個(gè)文件中的多個(gè)合同 可以在一個(gè)文件中定義多個(gè)合同危彩。這樣的合同的例子可以這樣看 import org.springframework.cloud.contract.spec.Contract [ Contract.make { name("should post a user") request { method 'POST' url('/users/1') } response { status 200 } }, Contract.make { request { method 'POST' url('/users/2') } response { status 200 } } ] 在這個(gè)例子中,一個(gè)合同有name字段泳桦,另一個(gè)沒(méi)有汤徽。這將導(dǎo)致生成兩個(gè)或多或少這樣的測(cè)試: package org.springframework.cloud.contract.verifier.tests.com.hello; import com.example.TestBase; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; import com.jayway.restassured.module.mockmvc.specification.MockMvcRequestSpecification; import com.jayway.restassured.response.ResponseOptions; import org.junit.Test; import static com.jayway.restassured.module.mockmvc.RestAssuredMockMvc.*; import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; public class V1Test extends TestBase { @Test public void validate_should_post_a_user() throws Exception { // given: MockMvcRequestSpecification request = given(); // when: ResponseOptions response = given().spec(request) .post("/users/1"); // then: assertThat(response.statusCode()).isEqualTo(200); } @Test public void validate_withList_1() throws Exception { // given: MockMvcRequestSpecification request = given(); // when: ResponseOptions response = given().spec(request) .post("/users/2"); // then: assertThat(response.statusCode()).isEqualTo(200); } } 請(qǐng)注意,對(duì)于具有name字段的合同灸撰,生成的測(cè)試方法名為validate_should_post_a_user谒府。對(duì)于沒(méi)有名稱的人validate_withList_1。它對(duì)應(yīng)于文件WithList.groovy的名稱和列表中的合同索引梧奢。 生成的存根將看起來(lái)像這樣 should post a user.json 1_WithList.json 您可以看到第一個(gè)文件從合同中獲取了name參數(shù)狱掂。第二個(gè)獲得了以索引為前綴的合同文件WithList.groovy的名稱(在這種情況下,合同在文件中的合同列表中具有索引1)亲轨。 小費(fèi) 正如你可以看到趋惨,如果您的合同名稱更好,那么您的測(cè)試更有意義惦蚊。 定制 擴(kuò)展DSL 可以向DSL提供自己的功能器虾。此功能的關(guān)鍵要求是保持靜態(tài)兼容性。下面你可以看到一個(gè)例子: 創(chuàng)建具有可重用類的JAR 在DSL中引用這些類 這里可以找到完整的例子蹦锋。 普通JAR 下面你可以找到我們將在DSL中重用的三個(gè)類兆沙。 PatternUtils包含消費(fèi)者和制作者使用的功能。 package com.example; import java.util.regex.Pattern; /** * If you want to use {@link Pattern} directly in your tests * then you can create a class resembling this one. It can * contain all the {@link Pattern} you want to use in the DSL. * * * {@code * request { *? ? body( *? ? ? ? [ age: $(c(PatternUtils.oldEnough()))] *? ? ) * } * * * Notice that we're using both {@code $()} for dynamic values * and {@code c()} for the consumer side. * * @author Marcin Grzejszczak */ public class PatternUtils { public static String tooYoung() { return "[0-1][0-9]"; } public static Pattern oldEnough() { return Pattern.compile("[2-9][0-9]"); } public static Pattern anyName() { return Pattern.compile("[a-zA-Z]+"); } /** * Makes little sense but it's just an example ;) */ public static Pattern ok() { return Pattern.compile("OK"); } } ConsumerUtils包含由使用功能的消費(fèi)者莉掂。 package com.example; import org.springframework.cloud.contract.spec.internal.ClientDslProperty; import org.springframework.cloud.contract.spec.internal.DslProperty; /** * DSL Properties passed to the DSL from the consumer's perspective. * That means that on the input side {@code Request} for HTTP * or {@code Input} for messaging you can have a regular expression. * On the {@code Response} for HTTP or {@code Output} for messaging * you have to have a concrete value. * * @author Marcin Grzejszczak */ public class ConsumerUtils { /** * Consumer side property. By using the {@link ClientDslProperty} * you can omit most of boilerplate code from the perspective * of dynamic values. Example * * * {@code * request { *? ? body( *? ? ? ? [ age: $(ConsumerUtils.oldEnough())] *? ? ) * } * * * That way the consumer side value of age field will be * a regular expression and the producer side will be generated. * * @author Marcin Grzejszczak */ public static ClientDslProperty oldEnough() { return new ClientDslProperty(PatternUtils.oldEnough()); } /** * Consumer side property. By using the {@link ClientDslProperty} * you can omit most of boilerplate code from the perspective * of dynamic values. Example * * * {@code * request { *? ? body( *? ? ? ? [ name: $(ConsumerUtils.anyName())] *? ? ) * } * * * That way the consumer will be a regular expression and the * producer side value will be equal to {@code marcin} */ public static DslProperty anyName() { return new DslProperty<>(PatternUtils.anyName(), "marcin"); } } ProducerUtils包含由使用的功能制片人葛圃。 package com.example; import org.springframework.cloud.contract.spec.internal.ServerDslProperty; /** * DSL Properties passed to the DSL from the producer's perspective. * That means that on the input side {@code Request} for HTTP * or {@code Input} for messaging you have to have a concrete value. * On the {@code Response} for HTTP or {@code Output} for messaging * you can have a regular expression. * * @author Marcin Grzejszczak */ public class ProducerUtils { /** * Producer side property. By using the {@link ProducerUtils} * you can omit most of boilerplate code from the perspective * of dynamic values. Example * * * {@code * response { *? ? body( *? ? ? ? [ status: $(ProducerUtils.ok())] *? ? ) * } * * * That way the producer side value of age field will be * a regular expression and the consumer side will be generated. */ public static ServerDslProperty ok() { return new ServerDslProperty(PatternUtils.ok()); } } 將依賴項(xiàng)添加到項(xiàng)目中 為了使插件和IDE能夠引用常見(jiàn)的JAR類,您需要將依賴關(guān)系傳遞給您的項(xiàng)目憎妙。 測(cè)試依賴項(xiàng)目的依賴關(guān)系 首先將常見(jiàn)的jar依賴項(xiàng)添加為測(cè)試依賴關(guān)系库正。這樣,由于您的合同文件在測(cè)試資源路徑中可用厘唾,所以公用的jar類將自動(dòng)顯示在您的Groovy文件中褥符。 Maven的 com.example beer-common ${project.version} test 搖籃 testCompile("com.example:beer-common:0.0.1-SNAPSHOT") 測(cè)試插件依賴關(guān)系 現(xiàn)在你必須添加插件的依賴關(guān)系,以便在運(yùn)行時(shí)重用抚垃。 Maven的 org.springframework.cloud spring-cloud-contract-maven-plugin ${spring-cloud-contract.version} true com.example org.springframework.cloud spring-cloud-contract-verifier ${spring-cloud-contract.version} com.example beer-common ${project.version} compile 搖籃 classpath "com.example:beer-common:0.0.1-SNAPSHOT" 在DSL中引用類 現(xiàn)在您可以參考DSL中的課程什燕。例: package contracts.beer.rest import org.springframework.cloud.contract.spec.Contract import static com.example.ConsumerUtils.oldEnough import static com.example.ProducerUtils.ok Contract.make { request { description(""" Represents a successful scenario of getting a beer given: client is old enough when: he applies for a beer then: we'll grant him the beer """) method 'POST' url '/check' body( age: $(oldEnough()) ) headers { contentType(applicationJson()) } } response { status 200 body(""" { "status": "${value(ok())}" } """) headers { contentType(applicationJson()) } } } 可插拔架構(gòu) 在某些情況下屋吨,您將合同定義為其他格式徙瓶,如YAML椰拒,RAML或PACT。另一方面魂迄,您希望從測(cè)試和存根生成中獲利粗截。添加自己的任何一個(gè)實(shí)現(xiàn)是很容易的。此外捣炬,您還可以自定義測(cè)試生成的方式(例如熊昌,您可以為其他語(yǔ)言生成測(cè)試),并且可以對(duì)存根生成執(zhí)行相同操作(可為其他存根http服務(wù)器實(shí)現(xiàn)生成存根)湿酸。 定制合同轉(zhuǎn)換器 我們假設(shè)您的合同是用YAML文件寫成的: request: url: /foo method: PUT headers: foo: bar body: foo: bar response: status: 200 headers: foo2: bar body: foo2: bar 感謝界面 package org.springframework.cloud.contract.spec /** * Converter to be used to convert FROM {@link File} TO {@link Contract} * and from {@link Contract} to {@code T} * * @param - type to which we want to convert the contract * * @author Marcin Grzejszczak * @since 1.1.0 */ interface ContractConverter { /** * Should this file be accepted by the converter. Can use the file extension * to check if the conversion is possible. * * @param file - file to be considered for conversion * @return - {@code true} if the given implementation can convert the file */ boolean isAccepted(File file) /** * Converts the given {@link File} to its {@link Contract} representation * * @param file - file to convert * @return - {@link Contract} representation of the file */ Collection convertFrom(File file) /** * Converts the given {@link Contract} to a {@link T} representation * * @param contract - the parsed contract * @return - {@link T} the type to which we do the conversion */ T convertTo(Collection contract) } 您可以注冊(cè)自己的合同結(jié)構(gòu)轉(zhuǎn)換器的實(shí)現(xiàn)婿屹。您的實(shí)現(xiàn)需要說(shuō)明開(kāi)始轉(zhuǎn)換的條件。此外推溃,您必須定義如何以兩種方式執(zhí)行轉(zhuǎn)換昂利。 重要 創(chuàng)建實(shí)施后,您必須創(chuàng)建一個(gè)/META-INF/spring.factories文件铁坎,您可以在其中提供實(shí)施的完全限定名稱蜂奸。 spring.factories文件的示例 # Converters org.springframework.cloud.contract.spec.ContractConverter=\ org.springframework.cloud.contract.verifier.converter.YamlContractConverter 和YAML實(shí)現(xiàn) package org.springframework.cloud.contract.verifier.converter import groovy.transform.CompileStatic import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.ContractConverter import org.springframework.cloud.contract.spec.internal.Headers import org.yaml.snakeyaml.Yaml /** * Simple converter from and to a {@link YamlContract} to a collection of {@link Contract} */ @CompileStatic class YamlContractConverter implements ContractConverter> { @Override public boolean isAccepted(File file) { String name = file.getName() return name.endsWith(".yml") || name.endsWith(".yaml") } @Override public Collection convertFrom(File file) { try { YamlContract yamlContract = new Yaml().loadAs(new FileInputStream(file), YamlContract.class) return [Contract.make { request { method(yamlContract?.request?.method) url(yamlContract?.request?.url) headers { yamlContract?.request?.headers?.each { String key, Object value -> header(key, value) } } body(yamlContract?.request?.body) } response { status(yamlContract?.response?.status) headers { yamlContract?.response?.headers?.each { String key, Object value -> header(key, value) } } body(yamlContract?.response?.body) } }] } catch (FileNotFoundException e) { throw new IllegalStateException(e) } } @Override public List convertTo(Collection contracts) { return contracts.collect { Contract contract -> YamlContract yamlContract = new YamlContract() yamlContract.request.with { method = contract?.request?.method?.clientValue url = contract?.request?.url?.clientValue headers = (contract?.request?.headers as Headers)?.asStubSideMap() body = contract?.request?.body?.clientValue as Map } yamlContract.response.with { status = contract?.response?.status?.clientValue as Integer headers = (contract?.response?.headers as Headers)?.asStubSideMap() body = contract?.response?.body?.clientValue as Map } return yamlContract } } } 契約轉(zhuǎn)換器 Spring Cloud Contract提供了協(xié)議代表合同的開(kāi)箱即用支持。換句話說(shuō)硬萍,而不是使用Groovy DSL扩所,您可以使用Pact文件。在本節(jié)中朴乖,我們將介紹如何為您的項(xiàng)目添加此類支持祖屏。 契約契約 我們將在下面的一個(gè)契約契約的例子中工作。我們將此文件放在src/test/resources/contracts文件夾下买羞。 { "provider": { "name": "Provider" }, "consumer": { "name": "Consumer" }, "interactions": [ { "description": "", "request": { "method": "PUT", "path": "/fraudcheck", "headers": { "Content-Type": "application/vnd.fraud.v1+json" }, "body": { "clientId": "1234567890", "loanAmount": 99999 }, "matchingRules": { "$.body.clientId": { "match": "regex", "regex": "[0-9]{10}" } } }, "response": { "status": 200, "headers": { "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8" }, "body": { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }, "matchingRules": { "$.body.fraudCheckStatus": { "match": "regex", "regex": "FRAUD" } } } } ], "metadata": { "pact-specification": { "version": "2.0.0" }, "pact-jvm": { "version": "2.4.18" } } } 生產(chǎn)者契約 在生產(chǎn)者方面袁勺,您可以添加兩個(gè)附加依賴關(guān)系的插件配置。一個(gè)是Spring Cloud Contract Pact支持畜普,另一個(gè)表示您正在使用的當(dāng)前Pact版本期丰。 Maven的 org.springframework.cloud spring-cloud-contract-maven-plugin ${spring-cloud-contract.version} true com.example.fraud org.springframework.cloud spring-cloud-contract-spec-pact ${spring-cloud-contract.version} au.com.dius pact-jvm-model 2.4.18 搖籃 classpath "org.springframework.cloud:spring-cloud-contract-spec-pact:${findProperty('verifierVersion') ?: verifierVersion}" classpath 'au.com.dius:pact-jvm-model:2.4.18' 當(dāng)您執(zhí)行應(yīng)用程序的構(gòu)建時(shí),將會(huì)產(chǎn)生一個(gè)或多或少的這樣的測(cè)試 @Test public void validate_shouldMarkClientAsFraud() throws Exception { // given: MockMvcRequestSpecification request = given() .header("Content-Type", "application/vnd.fraud.v1+json") .body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}"); // when: ResponseOptions response = given().spec(request) .put("/fraudcheck"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).isEqualTo("application/vnd.fraud.v1+json;charset=UTF-8"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("rejectionReason").isEqualTo("Amount too high"); // and: assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD"); } 并且這樣的存根看起來(lái)像這樣 { "uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62", "request" : { "url" : "/fraudcheck", "method" : "PUT", "headers" : { "Content-Type" : { "equalTo" : "application/vnd.fraud.v1+json" } }, "bodyPatterns" : [ { "matchesJsonPath" : "$[?(@.loanAmount == 99999)]" }, { "matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]" } ] }, "response" : { "status" : 200, "body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}", "headers" : { "Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8" } } } 消費(fèi)者契約 在生產(chǎn)者方面吃挑,您可以添加項(xiàng)目依賴關(guān)系兩個(gè)附加依賴關(guān)系钝荡。一個(gè)是Spring Cloud Contract Pact支持,另一個(gè)表示您正在使用的當(dāng)前Pact版本儒鹿。 Maven的 org.springframework.cloud spring-cloud-contract-spec-pact test au.com.dius pact-jvm-model 2.4.18 test 搖籃 testCompile "org.springframework.cloud:spring-cloud-contract-spec-pact" testCompile 'au.com.dius:pact-jvm-model:2.4.18' 定制測(cè)試發(fā)生器 如果您想為Java生成不同語(yǔ)言的測(cè)試化撕,或者您不滿意我們?yōu)槟ava測(cè)試的方式,那么您可以注冊(cè)自己的實(shí)現(xiàn)來(lái)做到這一點(diǎn)约炎。 感謝界面 package org.springframework.cloud.contract.verifier.builder import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.file.ContractMetadata /** * Builds a single test. * * @since 1.1.0 */ interface SingleTestGenerator { /** * Creates contents of a single test class in which all test scenarios from * the contract metadata should be placed. * * @param properties - properties passed to the plugin * @param listOfFiles - list of parsed contracts with additional metadata * @param className - the name of the generated test class * @param classPackage - the name of the package in which the test class should be stored * @param includedDirectoryRelativePath - relative path to the included directory * @return contents of a single test class */ String buildClass(ContractVerifierConfigProperties properties, Collection listOfFiles, String className, String classPackage, String includedDirectoryRelativePath) /** * Extension that should be appended to the generated test class. E.g. {@code .java} or {@code .php} * * @param properties - properties passed to the plugin */ String fileExtension(ContractVerifierConfigProperties properties) } 您可以注冊(cè)自己的生成測(cè)試的實(shí)現(xiàn)植阴。再次提供一個(gè)合適的spring.factories文件就足夠了。例: org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/ com.example.MyGenerator 自定義存根發(fā)生器 如果要為WireMock生成其他存根服務(wù)器的存根圾浅,就可以插入您自己的此接口的實(shí)現(xiàn): package org.springframework.cloud.contract.verifier.converter import groovy.transform.CompileStatic import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.verifier.file.ContractMetadata /** * Converts contracts into their stub representation. * * @since 1.1.0 */ @CompileStatic interface StubGenerator { /** * Returns {@code true} if the converter can handle the file to convert it into a stub. */ boolean canHandleFileName(String fileName) /** * Returns the collection of converted contracts into stubs. One contract can * result in multiple stubs. */ Map convertContents(String rootName, ContractMetadata content) /** * Returns the name of the converted stub file. If you have multiple contracts * in a single file then a prefix will be added to the generated file. If you * provide the {@link Contract#name} field then that field will override the * generated file name. * * Example: name of file with 2 contracts is {@code foo.groovy}, it will be * converted by the implementation to {@code foo.json}. The recursive file * converter will create two files {@code 0_foo.json} and {@code 1_foo.json} */ String generateOutputFileNameForInput(String inputFileName) } 您可以注冊(cè)自己的生成存根的實(shí)現(xiàn)掠手。再次提供一個(gè)合適的spring.factories文件就足夠了。例: # Stub converters org.springframework.cloud.contract.verifier.converter.StubGenerator=\ org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter 默認(rèn)實(shí)現(xiàn)是WireMock存根生成狸捕。 小費(fèi) 您可以提供多個(gè)存根生成器實(shí)現(xiàn)喷鸽。這樣,例如從單個(gè)DSL作為輸入灸拍,您可以例如生成WireMock存根和Pact文件做祝! 自定義Stub Runner 如果您決定使用自定義存根生成器砾省,則還需要使用不同的存根提供程序來(lái)運(yùn)行存根的自定義方式。 讓我們假設(shè)您正在使用Moco來(lái)構(gòu)建您的存根混槐。你寫了一個(gè)正確的存根生成器编兄,你的存根被放在一個(gè)JAR文件中。 為了Stub Runner知道如何運(yùn)行存根声登,您必須定義一個(gè)自定義的HTTP Stub服務(wù)器實(shí)現(xiàn)狠鸳。它可以看起來(lái)像這樣: package org.springframework.cloud.contract.stubrunner.provider.moco import com.github.dreamhead.moco.bootstrap.arg.HttpArgs import com.github.dreamhead.moco.runner.JsonRunner import org.springframework.cloud.contract.stubrunner.HttpServerStub import org.springframework.util.SocketUtils class MocoHttpServerStub implements HttpServerStub { private boolean started private JsonRunner runner private int port @Override int port() { if (!isRunning()) { return -1 } return port } @Override boolean isRunning() { return started } @Override HttpServerStub start() { return start(SocketUtils.findAvailableTcpPort()) } @Override HttpServerStub start(int port) { this.port = port return this } @Override HttpServerStub stop() { if (!isRunning()) { return this } this.runner.stop() return this } @Override HttpServerStub registerMappings(Collection stubFiles) { List streams = stubFiles.collect { it.newInputStream() } this.runner = JsonRunner.newJsonRunnerWithStreams(streams, HttpArgs.httpArgs().withPort(this.port).build()) this.runner.run() this.started = true return this } @Override boolean isAccepted(File file) { return file.name.endsWith(".json") } } 并將其注冊(cè)到您的spring.factories文件中 # Example of a custom HTTP Server Stub org.springframework.cloud.contract.stubrunner.HttpServerStub=\ org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub 這樣你就可以使用Moco來(lái)運(yùn)行存根。 重要 如果您不提供任何實(shí)現(xiàn)悯嗓,那么將選擇默認(rèn)的 - 基于WireMock的件舵。如果您提供多個(gè),那么列表中的第一個(gè)將被選中脯厨。 自定義存根下載器 您可以自定義存根的下載方式铅祸。如果您不想以默認(rèn)方式從Nexus / Artifactory下載JAR,您可以設(shè)置自己的實(shí)現(xiàn)俄认。下面您可以找到一個(gè)Stub Downloader Provider示例个少,它從classpath的測(cè)試資源獲取json文件,將它們復(fù)制到臨時(shí)文件眯杏,然后將該臨時(shí)文件夾作為存根的根傳遞夜焦。 package org.springframework.cloud.contract.stubrunner.provider.moco import org.springframework.cloud.contract.stubrunner.StubConfiguration import org.springframework.cloud.contract.stubrunner.StubDownloader import org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder import org.springframework.cloud.contract.stubrunner.StubRunnerOptions import org.springframework.core.io.DefaultResourceLoader import org.springframework.core.io.Resource import org.springframework.core.io.support.PathMatchingResourcePatternResolver import java.nio.file.Files /** * Poor man's version of taking stubs from classpath. It needs much more * love and attention to go to the main sources. * * @author Marcin Grzejszczak */ class ClasspathStubProvider implements StubDownloaderBuilder { private static final int TEMP_DIR_ATTEMPTS = 10000 @Override public StubDownloader build(StubRunnerOptions stubRunnerOptions) { final StubConfiguration configuration = stubRunnerOptions.getDependencies().first() PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver( new DefaultResourceLoader()) try { String rootFolder = repoRoot(stubRunnerOptions) ?: "**/" + separatedArtifact(configuration) + "/**/*.json" Resource[] resources = resolver.getResources(rootFolder) final File tmp = createTempDir() tmp.deleteOnExit() // you'd have to write an impl to maintain the folder structure // this is just for demo resources.each { Resource resource -> Files.copy(resource.getInputStream(), new File(tmp, resource.getFile().getName()).toPath()) } return new StubDownloader() { @Override public Map.Entry downloadAndUnpackStubJar( StubConfiguration stubConfiguration) { return new AbstractMap.SimpleEntry(configuration, tmp) } } } catch (IOException e) { throw new IllegalStateException(e) } } private String repoRoot(StubRunnerOptions stubRunnerOptions) { switch (stubRunnerOptions.stubRepositoryRoot) { case { !it }: return "" case { String root -> root.endsWith("**/*.json") }: return stubRunnerOptions.stubRepositoryRoot default: return stubRunnerOptions.stubRepositoryRoot + "/**/*.json" } } private String separatedArtifact(StubConfiguration configuration) { return configuration.getGroupId().replace(".", File.separator) + File.separator + configuration.getArtifactId() } // Taken from Guava private File createTempDir() { File baseDir = new File(System.getProperty("java.io.tmpdir")) String baseName = System.currentTimeMillis() + "-" for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { File tempDir = new File(baseDir, baseName + counter) if (tempDir.mkdir()) { return tempDir } } throw new IllegalStateException( "Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + ( TEMP_DIR_ATTEMPTS - 1) + ")") } } 并將其注冊(cè)到您的spring.factories文件中 # Example of a custom Stub Downloader Provider org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\ org.springframework.cloud.contract.stubrunner.provider.moco.ClasspathStubProvider 這樣你就可以選擇一個(gè)文件夾與你的存根的來(lái)源。 重要 如果您沒(méi)有提供任何實(shí)現(xiàn)岂贩,那么將選擇從遠(yuǎn)程備份中下載存根的默認(rèn)Aether辨嗽。如果您提供多個(gè)九孩,那么列表中的第一個(gè)將被選中。 鏈接 在這里,您可以找到有關(guān)Spring Cloud Contract驗(yàn)證器的有趣鏈接: Spring Cloud Contract Github Repository Spring Cloud Contract Stub Runner文檔 Spring Cloud Contract Stub Runner消息傳遞文檔 附錄:配置綱要Properties 名稱默認(rèn)描述 encrypt.fail-on-error true 標(biāo)記說(shuō)律胀,如果存在加密或解密錯(cuò)誤锌妻,進(jìn)程將失敗继阻。 encrypt.key 對(duì)稱密鑰办龄。作為一個(gè)更強(qiáng)大的替代方案,考慮使用密鑰庫(kù)颈渊。 encrypt.key-store.alias 商店中的鑰匙別名 encrypt.key-store.location 密鑰存儲(chǔ)文件的位置遂黍,例如classpath:/keystore.jks。 encrypt.key-store.password 鎖定密鑰庫(kù)的密碼俊嗽。 encrypt.key-store.secret 秘密保護(hù)密鑰(默認(rèn)為密碼相同)雾家。 encrypt.rsa.algorithm 使用RSA算法(DEFAULT或OEAP)。一旦設(shè)置不改變它(或現(xiàn)有的密碼將不可解密)绍豁。 encrypt.rsa.salt deadbeef Salt用于加密密文的隨機(jī)秘密芯咧。一旦設(shè)置不改變它(或現(xiàn)有的密碼將不可解密)。 encrypt.rsa.strong false 標(biāo)志表示應(yīng)該在內(nèi)部使用“強(qiáng)”AES加密。如果為真敬飒,則將GCM算法應(yīng)用于AES加密字節(jié)邪铲。默認(rèn)值為false(在這種情況下使用“標(biāo)準(zhǔn)”CBC代替)。一旦設(shè)置不改變它(或現(xiàn)有的密碼將不可解密)驶拱。 endpoints.bus.enabled endpoints.bus.id endpoints.bus.sensitive endpoints.consul.enabled endpoints.consul.id endpoints.consul.sensitive endpoints.env.post.enabled true 通過(guò)POST將環(huán)境更改為/ env霜浴。 endpoints.features.enabled endpoints.features.id endpoints.features.sensitive endpoints.pause.enabled true 啟用/暫停端點(diǎn)(發(fā)送Lifecycle.stop())晶衷。 endpoints.pause.id endpoints.pause.sensitive endpoints.refresh.enabled true 啟用/ refresh端點(diǎn)刷新配置并重新初始化刷新作用域bean蓝纲。 endpoints.refresh.id endpoints.refresh.sensitive endpoints.restart.enabled true 啟用/ restart端點(diǎn)重新啟動(dòng)應(yīng)用程序上下文。 endpoints.restart.id endpoints.restart.pause-endpoint.enabled endpoints.restart.pause-endpoint.id endpoints.restart.pause-endpoint.sensitive endpoints.restart.resume-endpoint.enabled endpoints.restart.resume-endpoint.id endpoints.restart.resume-endpoint.sensitive endpoints.restart.sensitive endpoints.restart.timeout 0 endpoints.resume.enabled true 啟用/ resume端點(diǎn)(發(fā)送Lifecycle.start())晌纫。 endpoints.resume.id endpoints.resume.sensitive endpoints.zookeeper.enabled true 啟用/ zookeeper端點(diǎn)來(lái)檢查zookeeper的狀態(tài)税迷。 eureka.client.allow-redirects false 指示服務(wù)器是否可以將客戶端請(qǐng)求重定向到備份服務(wù)器/集群。如果設(shè)置為false锹漱,服務(wù)器將直接處理請(qǐng)求箭养,如果設(shè)置為true,則可能會(huì)向客戶端發(fā)送HTTP重定向哥牍,并具有新的服務(wù)器位置毕泌。 eureka.client.availability-zones 獲取此實(shí)例所在區(qū)域的可用性區(qū)域列表(用于AWS數(shù)據(jù)中心)。 更改在運(yùn)行時(shí)在registryFetchIntervalSeconds指定的下一個(gè)注冊(cè)表提取周期中有效嗅辣。 eureka.client.backup-registry-impl 獲取執(zhí)行BackupRegistry的實(shí)現(xiàn)的名稱撼泛,以便僅在eureka客戶端啟動(dòng)時(shí)首次將注冊(cè)表信息作為回退選項(xiàng)提取。 對(duì)于需要額外的彈性的注冊(cè)表信息的應(yīng)用程序可能需要這一點(diǎn)澡谭,而無(wú)法運(yùn)行它們愿题。 eureka.client.cache-refresh-executor-exponential-back-off-bound 10 緩存刷新執(zhí)行者指數(shù)退出相關(guān)屬性。在發(fā)生超時(shí)序列的情況下蛙奖,它是重試延遲的最大乘數(shù)值潘酗。 eureka.client.cache-refresh-executor-thread-pool-size 2 cacheRefreshExecutor初始化的線程池大小 eureka.client.client-data-accept EurekaAccept客戶端數(shù)據(jù)接受名稱 eureka.client.decoder-name 這是一個(gè)瞬態(tài)配置,一旦最新的編解碼器穩(wěn)定雁仲,可以刪除(因?yàn)橹挥幸粋€(gè)) eureka.client.disable-delta false 指示eureka客戶端是否應(yīng)該禁用提取delta仔夺,而應(yīng)該訴諸于獲取完整的注冊(cè)表信息。 請(qǐng)注意攒砖,增量獲取可以極大地減少流量缸兔,因?yàn)橛壤ǚ?wù)器的更改速率通常遠(yuǎn)低于提取速率。 更改在運(yùn)行時(shí)在registryFetchIntervalSeconds指定的下一個(gè)注冊(cè)表提取周期中有效 eureka.client.dollar-replacement _- 在eureka服務(wù)器的序列化/反序列化信息期間祭衩,獲取Dollar符號(hào) eureka.client.enabled true 標(biāo)記以指示啟用Eureka客戶端。 eureka.client.encoder-name 這是一個(gè)瞬態(tài)配置掐暮,一旦最新的編解碼器穩(wěn)定蝎抽,可以刪除(因?yàn)橹挥幸粋€(gè)) eureka.client.escape-char-replacement __ 在eureka服務(wù)器的序列化/反序列化信息期間獲取下劃線符號(hào) eureka.client.eureka-connection-idle-timeout-seconds 30 表示到eureka服務(wù)器的HTTP連接可以在關(guān)閉之前保持空閑狀態(tài)的時(shí)間(以秒為單位)。 在AWS環(huán)境中樟结,建議值為30秒或更短养交,因?yàn)榉阑饓υ趲追昼妰?nèi)清除連接信息,將連接掛在空中 eureka.client.eureka-server-connect-timeout-seconds 5 指示在連接到eureka服務(wù)器需要超時(shí)之前等待(以秒為單位)的時(shí)間瓢宦。請(qǐng)注意碎连,客戶端中的連接由org.apache.http.client.HttpClient匯集,此設(shè)置會(huì)影響實(shí)際的連接創(chuàng)建以及從池中獲取連接的等待時(shí)間驮履。 eureka.client.eureka-server-d-n-s-name 獲取要查詢的DNS名稱以獲取eureka服務(wù)器的列表鱼辙。如果合同通過(guò)實(shí)現(xiàn)serviceUrls返回服務(wù)URL,則不需要此信息玫镐。 當(dāng)useDnsForFetchingServiceUrls設(shè)置為true時(shí)倒戏,使用DNS機(jī)制,而eureka客戶端希望DNS以某種方式配置恐似,以便可以動(dòng)態(tài)獲取更改的eureka服務(wù)器杜跷。 更改在運(yùn)行時(shí)有效页徐。 eureka.client.eureka-server-port 獲取用于構(gòu)建服務(wù)url的端口妖胀,以在eureka服務(wù)器列表來(lái)自DNS時(shí)聯(lián)系eureka服務(wù)器。如果合同返回服務(wù)url eurekaServerServiceUrls(String)和蚪,則不需要此信息双藕。 當(dāng)useDnsForFetchingServiceUrls設(shè)置為true時(shí)淑趾,使用DNS機(jī)制,而eureka客戶端希望DNS以某種方式配置蔓彩,以便可以動(dòng)態(tài)獲取更改的eureka服務(wù)器治笨。 更改在運(yùn)行時(shí)有效。 eureka.client.eureka-server-read-timeout-seconds 8 指示從eureka服務(wù)器讀取之前需要等待(秒)多久才能超時(shí)赤嚼。 eureka.client.eureka-server-total-connections 200 獲取從eureka客戶端到所有eureka服務(wù)器允許的總連接數(shù)旷赖。 eureka.client.eureka-server-total-connections-per-host 50 獲取從eureka客戶端到eureka服務(wù)器主機(jī)允許的總連接數(shù)。 eureka.client.eureka-server-u-r-l-context 獲取用于構(gòu)建服務(wù)網(wǎng)址的URL上下文更卒,以便在eureka服務(wù)器列表來(lái)自DNS時(shí)聯(lián)系eureka服務(wù)器等孵。如果合同從eurekaServerServiceUrls返回服務(wù)網(wǎng)址,則不需要此信息蹂空。 當(dāng)useDnsForFetchingServiceUrls設(shè)置為true時(shí)俯萌,使用DNS機(jī)制,而eureka客戶端希望DNS以某種方式配置上枕,以便可以動(dòng)態(tài)獲取更改的eureka服務(wù)器咐熙。更改在運(yùn)行時(shí)有效。 eureka.client.eureka-service-url-poll-interval-seconds 0 表示輪詢對(duì)eureka服務(wù)器信息進(jìn)行更改的頻率(以秒為單位)辨萍∑迥眨可以添加或刪除Eureka服務(wù)器,此設(shè)置控制eureka客戶端應(yīng)該知道的時(shí)間。 eureka.client.fetch-registry true 指示該客戶端是否應(yīng)從eureka服務(wù)器獲取eureka注冊(cè)表信息爪飘。 eureka.client.fetch-remote-regions-registry 逗號(hào)分隔將獲取eureka注冊(cè)表信息的區(qū)域列表义起。必須為availabilityZones返回的每個(gè)區(qū)域定義可用性區(qū)域。否則师崎,將導(dǎo)致發(fā)現(xiàn)客戶端啟動(dòng)失敗默终。 eureka.client.filter-only-up-instances true 指示是否在僅具有InstanceStatus UP狀態(tài)的實(shí)例的過(guò)濾應(yīng)用程序之后獲取應(yīng)用程序。 eureka.client.g-zip-content true 指示從服務(wù)器支持時(shí)犁罩,是否必須壓縮從eureka服務(wù)器提取的內(nèi)容齐蔽。來(lái)自eureka服務(wù)器的注冊(cè)表信息被壓縮以獲得最佳的網(wǎng)絡(luò)流量。 eureka.client.heartbeat-executor-exponential-back-off-bound 10 心跳執(zhí)行者指數(shù)回撤相關(guān)財(cái)產(chǎn)昼汗。在發(fā)生超時(shí)序列的情況下肴熏,它是重試延遲的最大乘數(shù)值。 eureka.client.heartbeat-executor-thread-pool-size 2 heartbeat執(zhí)行器初始化的線程池大小 eureka.client.initial-instance-info-replication-interval-seconds 40 指示將實(shí)例信息復(fù)制到eureka服務(wù)器的開(kāi)始時(shí)間(以秒為單位) eureka.client.instance-info-replication-interval-seconds 30 指示復(fù)制要復(fù)制到eureka服務(wù)器的實(shí)例更改的頻率(以秒為單位)顷窒。 eureka.client.log-delta-diff false 指示在注冊(cè)表信息方面是否記錄eureka服務(wù)器和eureka客戶端之間的差異。 Eureka客戶端嘗試僅從歐萊雅服務(wù)器檢索增量更改以最小化網(wǎng)絡(luò)流量源哩。收到三角形后鞋吉,eureka客戶端將從服務(wù)器的信息進(jìn)行協(xié)調(diào),以驗(yàn)證它是否已經(jīng)沒(méi)有漏掉一些信息励烦。當(dāng)客戶端發(fā)生網(wǎng)絡(luò)問(wèn)題與服務(wù)器通信時(shí)谓着,可能會(huì)發(fā)生調(diào)解失敗。如果對(duì)帳失敗坛掠,eureka客戶端將獲得完整的注冊(cè)表信息赊锚。 在獲取完整的注冊(cè)表信息的同時(shí),eureka客戶端可以記錄客戶端和服務(wù)器之間的差異屉栓,并且此設(shè)置控制它舷蒲。 更改在運(yùn)行時(shí)在registryFetchIntervalSecondsr指定的下一個(gè)注冊(cè)表提取周期中有效 eureka.client.on-demand-update-status-change true 如果設(shè)置為true,則通過(guò)ApplicationInfoManager進(jìn)行的本地狀態(tài)更新將觸發(fā)對(duì)遠(yuǎn)程eureka服務(wù)器的按需(但限速)注冊(cè)/更新 eureka.client.prefer-same-zone-eureka true 指示此實(shí)例是否應(yīng)嘗試在同一區(qū)域中使用尤里卡服務(wù)器延遲和/或其他原因友多。 理想情況下牲平,eureka客戶端配置為與同一區(qū)域中的服務(wù)器通信 更改在運(yùn)行時(shí)在registryFetchIntervalSeconds指定的下一個(gè)注冊(cè)表提取周期中有效 eureka.client.property-resolver eureka.client.proxy-host 獲取代理主機(jī)到eureka服務(wù)器(如果有的話)。 eureka.client.proxy-password 獲取代理密碼(如果有)域滥。 eureka.client.proxy-port 獲取代理端口到eureka服務(wù)器(如果有的話)纵柿。 eureka.client.proxy-user-name 獲取代理用戶名(如果有)。 eureka.client.region us-east-1 獲取此實(shí)例所在的區(qū)域(用于AWS數(shù)據(jù)中心)启绰。 eureka.client.register-with-eureka true 指示此實(shí)例是否應(yīng)將其信息注冊(cè)到eureka服務(wù)器以供其他人發(fā)現(xiàn)昂儒。 在某些情況下,您不希望發(fā)現(xiàn)實(shí)例委可,而您只想發(fā)現(xiàn)其他實(shí)例渊跋。 eureka.client.registry-fetch-interval-seconds 30 指示從eureka服務(wù)器獲取注冊(cè)表信息的頻率(以秒為單位)。 eureka.client.registry-refresh-single-vip-address 指示客戶端是否只對(duì)單個(gè)VIP的注冊(cè)表信息感興趣。 eureka.client.service-url 可用性區(qū)域映射到與eureka服務(wù)器通信的完全限定URL的列表刹枉。每個(gè)值可以是單個(gè)URL或逗號(hào)分隔的替代位置列表叽唱。 通常,尤里卡服務(wù)器URL攜帶協(xié)議微宝,主機(jī)棺亭,端口,上下文和版本信息(如果有的話)蟋软。示例:http://ec2-256-156-243-129.compute-1.amazonaws.com:7001/eureka/ 更改在運(yùn)行時(shí)在eurekaServiceUrlPollIntervalSeconds指定的下一個(gè)服務(wù)網(wǎng)址刷新周期中有效镶摘。 eureka.client.transport eureka.client.use-dns-for-fetching-service-urls false 指示eureka客戶端是否應(yīng)該使用DNS機(jī)制來(lái)獲取要與之通信的eureka服務(wù)器列表哮独。當(dāng)DNS名稱更新為具有其他服務(wù)器時(shí)介时,該信息將在eureka客戶端輪詢?cè)揺urka??ServiceUrlPollIntervalSeconds中指定的信息之后立即使用其屏。 或者疙驾,服務(wù)urls可以返回serviceUrls悦陋,但用戶應(yīng)該實(shí)現(xiàn)自己的機(jī)制來(lái)返回更新的列表慕爬,以防發(fā)生更改畅铭。 更改在運(yùn)行時(shí)有效淡溯。 eureka.dashboard.enabled true 標(biāo)志以啟用Eureka儀表板譬重。默認(rèn)值為true拒逮。 eureka.dashboard.path / 到Eureka儀表板(相對(duì)于servlet路徑)的路徑。默認(rèn)為“/”臀规。 eureka.instance.a-s-g-name 獲取與此實(shí)例關(guān)聯(lián)的AWS自動(dòng)縮放組名稱滩援。該信息在AWS環(huán)境中專門用于在實(shí)例啟動(dòng)后自動(dòng)將實(shí)例停止運(yùn)行,并且已將其禁用塔嬉。 eureka.instance.app-group-name 獲取要在eureka中注冊(cè)的應(yīng)用程序組的名稱玩徊。 eureka.instance.appname unknown 獲取要在eureka注冊(cè)的應(yīng)用程序的名稱。 eureka.instance.data-center-info 返回此實(shí)例部署的數(shù)據(jù)中心谨究。如果實(shí)例部署在AWS中恩袱,則此信息用于獲取一些AWS特定實(shí)例信息。 eureka.instance.default-address-resolution-order [] eureka.instance.environment eureka.instance.health-check-url 獲取此實(shí)例的絕對(duì)運(yùn)行狀況檢查頁(yè)面URL记盒。如果運(yùn)行狀況檢查頁(yè)面駐留在與eureka通話的同一個(gè)實(shí)例中憎蛤,用戶可以提供healthCheckUrlPath,否則在實(shí)例是其他服務(wù)器的代理的情況下纪吮,用戶可以提供完整的URL俩檬。如果提供完整的URL,則優(yōu)先碾盟。 它通常用于根據(jù)實(shí)例的健康狀況做出有根據(jù)的決策 - 例如棚辽,它可用于確定是否繼續(xù)部署到整個(gè)場(chǎng),或者停止部署而不會(huì)造成進(jìn)一步的損壞冰肴。完整的URL應(yīng)遵循格式http:// $ {eureka.hostname}:7001 /其中值$ {eureka.hostname}在運(yùn)行時(shí)被替換屈藐。 eureka.instance.health-check-url-path /health 獲取此實(shí)例的相對(duì)運(yùn)行狀況檢查URL路徑榔组。然后,健康檢查頁(yè)面URL由主機(jī)名和通信類型構(gòu)建联逻,如securePort和nonSecurePort中指定的安全或不安全搓扯。 它通常用于根據(jù)實(shí)例的健康狀況做出有根據(jù)的決策 - 例如,它可用于確定是否繼續(xù)部署到整個(gè)場(chǎng)包归,或者停止部署而不會(huì)造成進(jìn)一步的損壞锨推。 eureka.instance.home-page-url 獲取此實(shí)例的絕對(duì)主頁(yè)URL。如果主頁(yè)位于與eureka通話的同一個(gè)實(shí)例中公壤,用戶可以提供homePageUrlPath换可,否則在實(shí)例是其他服務(wù)器的代理的情況下,用戶可以提供完整的URL厦幅。如果提供完整的URL沾鳄,則優(yōu)先。 它通常用于其他服務(wù)的信息目的确憨,以將其用作著陸頁(yè)译荞。完整的URL應(yīng)遵循格式http:// $ {eureka.hostname}:7001 /其中值$ {eureka.hostname}在運(yùn)行時(shí)被替換。 eureka.instance.home-page-url-path / 獲取此實(shí)例的相對(duì)主頁(yè)URL路徑缚态。然后磁椒,主頁(yè)URL由hostName和通信類型構(gòu)建 - 安全或不安全。 它通常用于其他服務(wù)的信息目的玫芦,以將其用作著陸頁(yè)。 eureka.instance.host-info eureka.instance.hostname 如果可以在配置時(shí)確定主機(jī)名(否則將從操作系統(tǒng)原語(yǔ)中猜出)本辐。 eureka.instance.inet-utils eureka.instance.initial-status 使用rmeote Eureka服務(wù)器注冊(cè)的初始狀態(tài)桥帆。 eureka.instance.instance-enabled-onit false 指示在eureka注冊(cè)后,實(shí)例是否應(yīng)該啟用流量慎皱。有時(shí)候老虫,應(yīng)用程序可能需要做一些預(yù)處理,才能準(zhǔn)備好交通茫多。 eureka.instance.instance-id 獲取要在eureka注冊(cè)的此實(shí)例的唯一ID(在appName的范圍內(nèi))祈匙。 eureka.instance.ip-address 獲取實(shí)例的IPAdress。此信息僅用于學(xué)術(shù)目的天揖,因?yàn)閬?lái)自其他實(shí)例的通信主要發(fā)生在使用{@link #getHostName(boolean)}中提供的信息夺欲。 eureka.instance.lease-expiration-duration-in-seconds 90 指示eureka服務(wù)器在接收到最后一個(gè)心跳之后等待的時(shí)間(秒),然后才能從此視圖中刪除此實(shí)例今膊,并禁止此實(shí)例的流量些阅。 將此值設(shè)置得太長(zhǎng)可能意味著流量可以路由到實(shí)例,即使實(shí)例不存在斑唬。設(shè)置此值太小可能意味著市埋,由于臨時(shí)網(wǎng)絡(luò)故障黎泣,實(shí)例可能會(huì)被取消流量仰禀。此值將設(shè)置為至少高于leaseRenewalIntervalInSeconds中指定的值勘究。 eureka.instance.lease-renewal-interval-in-seconds 30 指示eureka客戶端需要向eureka服務(wù)器發(fā)送心跳以指示它仍然存在的頻率(以秒為單位)。如果在leaseExpirationDurationInSeconds中指定的時(shí)間段內(nèi)未收到心跳線判莉,則eureka服務(wù)器將從其視圖中刪除該實(shí)例坷澡,因此不允許此實(shí)例的流量托呕。 請(qǐng)注意,如果該實(shí)例實(shí)現(xiàn)HealthCheckCallback洋访,然后決定使其本身不可用镣陕,則該實(shí)例仍然可能無(wú)法訪問(wèn)流量。 eureka.instance.metadata-map 獲取與此實(shí)例關(guān)聯(lián)的元數(shù)據(jù)名稱/值對(duì)姻政。該信息發(fā)送到eureka服務(wù)器呆抑,可以被其他實(shí)例使用。 eureka.instance.namespace eureka 獲取用于查找屬性的命名空間汁展。忽略Spring Cloud鹊碍。 eureka.instance.non-secure-port 80 獲取實(shí)例應(yīng)該接收流量的非安全端口。 eureka.instance.non-secure-port-enabled true 指示是否應(yīng)啟用非安全端口的流量食绿。 eureka.instance.prefer-ip-address false 標(biāo)示說(shuō)侈咕,當(dāng)猜測(cè)主機(jī)名時(shí),服務(wù)器的IP地址應(yīng)該在操作系統(tǒng)報(bào)告的主機(jī)名中使用器紧。 eureka.instance.registry.default-open-for-traffic-count 1 用于確定租賃期間取消的價(jià)值耀销,獨(dú)立時(shí)默認(rèn)為1。應(yīng)該為對(duì)等復(fù)制的eurekas設(shè)置為0 eureka.instance.registry.expected-number-of-renews-per-min 1 eureka.instance.secure-health-check-url 獲取此實(shí)例的絕對(duì)安全運(yùn)行狀況檢查頁(yè)面URL铲汪。如果健康檢查頁(yè)面駐留在與eureka通話的同一個(gè)實(shí)例中熊尉,用戶可以提供secureHealthCheckUrl,否則在實(shí)例是其他服務(wù)器的代理的情況下掌腰,用戶可以提供完整的URL狰住。如果提供完整的URL,則優(yōu)先齿梁。 它通常用于根據(jù)實(shí)例的健康狀況做出有根據(jù)的決策 - 例如催植,它可用于確定是否繼續(xù)部署到整個(gè)場(chǎng),或者停止部署而不會(huì)造成進(jìn)一步的損壞勺择。完整的URL應(yīng)遵循格式http:// $ {eureka.hostname}:7001 /其中值$ {eureka.hostname}在運(yùn)行時(shí)被替換创南。 eureka.instance.secure-port 443 獲取實(shí)例應(yīng)該接收流量的安全端口。 eureka.instance.secure-port-enabled false 指示安全端口是否應(yīng)啟用流量酵幕。 eureka.instance.secure-virtual-host-name unknown 獲取為此實(shí)例定義的安全虛擬主機(jī)名扰藕。 這通常是其他實(shí)例通過(guò)使用安全虛擬主機(jī)名找到此實(shí)例的方式。這與完全限定域名相似芳撒,您的服務(wù)的用戶將需要找到此實(shí)例邓深。 eureka.instance.status-page-url 獲取此實(shí)例的絕對(duì)狀態(tài)頁(yè)面URL路徑未桥。如果狀態(tài)頁(yè)面駐留在與eureka通話的同一個(gè)實(shí)例中,用戶可以提供statusPageUrlPath芥备,否則在實(shí)例是其他服務(wù)器的代理的情況下冬耿,用戶可以提供完整的URL。如果提供完整的URL萌壳,則優(yōu)先亦镶。 它通常用于其他服務(wù)的信息目的,以查找此實(shí)例的狀態(tài)袱瓮。用戶可以提供一個(gè)簡(jiǎn)單的HTML來(lái)指示實(shí)例的當(dāng)前狀態(tài)缤骨。 eureka.instance.status-page-url-path /info 獲取此實(shí)例的相對(duì)狀態(tài)頁(yè)面URL路徑。然后尺借,狀態(tài)頁(yè)面URL由安全端口和非安全端口中指定的hostName和通信類型構(gòu)建 - 安全或不安全绊起。 它通常用于其他服務(wù)的信息目的,以查找此實(shí)例的狀態(tài)燎斩。用戶可以提供一個(gè)簡(jiǎn)單的HTML來(lái)指示實(shí)例的當(dāng)前狀態(tài)虱歪。 eureka.instance.virtual-host-name unknown 獲取為此實(shí)例定義的虛擬主機(jī)名。 這通常是其他實(shí)例通過(guò)使用虛擬主機(jī)名找到此實(shí)例的方式栅表。這與完全限定域名相似笋鄙,您的服務(wù)的用戶將需要找到此實(shí)例。 eureka.server.a-s-g-cache-expiry-timeout-ms 0 eureka.server.a-s-g-query-timeout-ms 300 eureka.server.a-s-g-update-interval-ms 0 eureka.server.a-w-s-access-id eureka.server.a-w-s-secret-key eureka.server.batch-replication false eureka.server.binding-strategy eureka.server.delta-retention-timer-interval-in-ms 0 eureka.server.disable-delta false eureka.server.disable-delta-for-remote-regions false eureka.server.disable-transparent-fallback-to-other-region false eureka.server.e-i-p-bind-rebind-retries 3 eureka.server.e-i-p-binding-retry-interval-ms 0 eureka.server.e-i-p-binding-retry-interval-ms-when-unbound 0 eureka.server.enable-replicated-request-compression false eureka.server.enable-self-preservation true eureka.server.eviction-interval-timer-in-ms 0 eureka.server.g-zip-content-from-remote-region true eureka.server.json-codec-name eureka.server.list-auto-scaling-groups-role-name ListAutoScalingGroups eureka.server.log-identity-headers true eureka.server.max-elements-in-peer-replication-pool 10000 eureka.server.max-elements-in-status-replication-pool 10000 eureka.server.max-idle-thread-age-in-minutes-for-peer-replication 15 eureka.server.max-idle-thread-in-minutes-age-for-status-replication 10 eureka.server.max-threads-for-peer-replication 20 eureka.server.max-threads-for-status-replication 1 eureka.server.max-time-for-replication 30000 eureka.server.min-threads-for-peer-replication 5 eureka.server.min-threads-for-status-replication 1 eureka.server.number-of-replication-retries 5 eureka.server.peer-eureka-nodes-update-interval-ms 0 eureka.server.peer-eureka-status-refresh-time-interval-ms 0 eureka.server.peer-node-connect-timeout-ms 200 eureka.server.peer-node-connection-idle-timeout-seconds 30 eureka.server.peer-node-read-timeout-ms 200 eureka.server.peer-node-total-connections 1000 eureka.server.peer-node-total-connections-per-host 500 eureka.server.prime-aws-replica-connections true eureka.server.property-resolver eureka.server.rate-limiter-burst-size 10 eureka.server.rate-limiter-enabled false eureka.server.rate-limiter-full-fetch-average-rate 100 eureka.server.rate-limiter-privileged-clients eureka.server.rate-limiter-registry-fetch-average-rate 500 eureka.server.rate-limiter-throttle-standard-clients false eureka.server.registry-sync-retries 0 eureka.server.registry-sync-retry-wait-ms 0 eureka.server.remote-region-app-whitelist eureka.server.remote-region-connect-timeout-ms 1000 eureka.server.remote-region-connection-idle-timeout-seconds 30 eureka.server.remote-region-fetch-thread-pool-size 20 eureka.server.remote-region-read-timeout-ms 1000 eureka.server.remote-region-registry-fetch-interval 30 eureka.server.remote-region-total-connections 1000 eureka.server.remote-region-total-connections-per-host 500 eureka.server.remote-region-trust-store eureka.server.remote-region-trust-store-password changeit eureka.server.remote-region-urls eureka.server.remote-region-urls-with-name eureka.server.renewal-percent-threshold 0.85 eureka.server.renewal-threshold-update-interval-ms 0 eureka.server.response-cache-auto-expiration-in-seconds 180 eureka.server.response-cache-update-interval-ms 0 eureka.server.retention-time-in-m-s-in-delta-queue 0 eureka.server.route53-bind-rebind-retries 3 eureka.server.route53-binding-retry-interval-ms 0 eureka.server.route53-domain-t-t-l 30 eureka.server.sync-when-timestamp-differs true eureka.server.use-read-only-response-cache true eureka.server.wait-time-in-ms-when-sync-empty 0 eureka.server.xml-codec-name feign.compression.request.mime-types [text/xml, application/xml, application/json] 支持的MIME類型列表怪瓶。 feign.compression.request.min-request-size 2048 最小閾值內(nèi)容大小萧落。 health.config.enabled false 標(biāo)記以指示應(yīng)安裝配置服務(wù)器運(yùn)行狀況指示器。 health.config.time-to-live 0 生成緩存結(jié)果的時(shí)間洗贰,以毫秒為單位铐尚。默認(rèn)300000(5分鐘)。 hystrix.metrics.enabled true 啟用Hystrix指標(biāo)輪詢哆姻。默認(rèn)為true。 hystrix.metrics.polling-interval-ms 2000 后續(xù)輪詢度量之間的間隔玫膀。默認(rèn)為2000 ms矛缨。 management.health.refresh.enabled true 啟用刷新范圍的運(yùn)行狀況端點(diǎn)。 management.health.zookeeper.enabled true 啟用zookeeper的健康端點(diǎn)帖旨。 netflix.atlas.batch-size 10000 netflix.atlas.enabled true netflix.atlas.uri netflix.metrics.servo.cache-warning-threshold 1000 當(dāng)ServoMonitorCache達(dá)到這個(gè)大小時(shí)箕昭,會(huì)記錄一個(gè)警告。如果您在RestTemplate url中使用字符串連接原在,這將非常有用乍迄。 netflix.metrics.servo.registry-class com.netflix.servo.BasicMonitorRegistry Servo使用的監(jiān)視器注冊(cè)表的完全限定類名折晦。 proxy.auth.load-balanced proxy.auth.routes 每個(gè)路由的認(rèn)證策略。 spring.cloud.bus.ack.destination-service 想要聽(tīng)ack的服務(wù)述召。默認(rèn)為null(表示所有服務(wù))朱转。 spring.cloud.bus.ack.enabled true 標(biāo)志關(guān)閉acks(默認(rèn)打開(kāi))。 spring.cloud.bus.destination springCloudBus 名稱Spring Cloud消息的流目的地积暖。 spring.cloud.bus.enabled true 標(biāo)志表示總線已啟用藤为。 spring.cloud.bus.env.enabled true 標(biāo)志關(guān)閉環(huán)境變化事件(默認(rèn)為開(kāi))。 spring.cloud.bus.refresh.enabled true 關(guān)閉刷新事件的標(biāo)志(默認(rèn)為開(kāi))夺刑。 spring.cloud.bus.trace.enabled false 打開(kāi)acks跟蹤的標(biāo)志(默認(rèn)關(guān)閉)缅疟。 spring.cloud.cloudfoundry.discovery.enabled true 標(biāo)記以指示啟用發(fā)現(xiàn)。 spring.cloud.cloudfoundry.discovery.heartbeat-frequency 5000 心跳次數(shù)以毫秒為單位的頻率遍愿〈嬉客戶端將輪詢?cè)擃l率并廣播服務(wù)ID列表。 spring.cloud.cloudfoundry.discovery.org 要進(jìn)行身份驗(yàn)證的組織名稱(默認(rèn)為用戶默認(rèn)值)沼填。 spring.cloud.cloudfoundry.discovery.password 用戶驗(yàn)證和獲取令牌的密碼桅咆。 spring.cloud.cloudfoundry.discovery.space 要進(jìn)行身份驗(yàn)證的空間名稱(默認(rèn)為用戶默認(rèn)值)。 spring.cloud.cloudfoundry.discovery.url Cloud Foundry API(云控制器)的URL倾哺。 spring.cloud.cloudfoundry.discovery.username 驗(yàn)證用戶名(通常是電子郵件地址)轧邪。 spring.cloud.config.allow-override true 標(biāo)記以指示可以使用{@link #isSystemPropertiesOverride()systemPropertiesOverride}。設(shè)置為false以防止用戶意外更改默認(rèn)值羞海。默認(rèn)值為true忌愚。 spring.cloud.config.authorization 客戶端使用的授權(quán)令牌連接到服務(wù)器。 spring.cloud.config.discovery.enabled false 標(biāo)記以指示啟用配置服務(wù)器發(fā)現(xiàn)(配置服務(wù)器URL將通過(guò)發(fā)現(xiàn)查找)却邓。 spring.cloud.config.discovery.service-id configserver 服務(wù)ID來(lái)定位配置服務(wù)器硕糊。 spring.cloud.config.enabled true 標(biāo)記說(shuō)遠(yuǎn)程配置啟用。默認(rèn)為true; spring.cloud.config.fail-fast false 標(biāo)記表示無(wú)法連接到服務(wù)器是致命的(默認(rèn)為false)腊徙。 spring.cloud.config.label 用于拉取遠(yuǎn)程配置屬性的標(biāo)簽名稱简十。默認(rèn)設(shè)置在服務(wù)器上(通常是基于git的服務(wù)器的“主”)。 spring.cloud.config.name 用于獲取遠(yuǎn)程屬性的應(yīng)用程序名稱撬腾。 spring.cloud.config.override-none false 標(biāo)志表示當(dāng){@link #setAllowOverride(boolean)allowOverride}為true時(shí)螟蝙,外部屬性應(yīng)該采用最低優(yōu)先級(jí),并且不覆蓋任何現(xiàn)有的屬性源(包括本地配置文件)民傻。默認(rèn)為false胰默。 spring.cloud.config.override-system-properties true 標(biāo)記以指示外部屬性應(yīng)覆蓋系統(tǒng)屬性。默認(rèn)值為true漓踢。 spring.cloud.config.password 聯(lián)系遠(yuǎn)程服務(wù)器時(shí)使用的密碼(HTTP Basic)牵署。 spring.cloud.config.profile default 獲取遠(yuǎn)程配置時(shí)使用的默認(rèn)配置文件(逗號(hào)分隔)。默認(rèn)為“默認(rèn)”喧半。 spring.cloud.config.retry.initial-interval 1000 初始重試間隔(以毫秒為單位)奴迅。 spring.cloud.config.retry.max-attempts 6 最大嘗試次數(shù)。 spring.cloud.config.retry.max-interval 2000 退避的最大間隔 spring.cloud.config.retry.multiplier 1.1 下一個(gè)間隔的乘數(shù)挺据。 spring.cloud.config.server.bootstrap false 表示配置服務(wù)器應(yīng)使用遠(yuǎn)程存儲(chǔ)庫(kù)中的屬性初始化其自己的環(huán)境取具。默認(rèn)情況下關(guān)閉脖隶,因?yàn)樗鼤?huì)延遲啟動(dòng),但在將服務(wù)器嵌入到另一個(gè)應(yīng)用程序中時(shí)很有用者填。 spring.cloud.config.server.default-application-name application 傳入請(qǐng)求沒(méi)有特定的默認(rèn)應(yīng)用程序名稱浩村。 spring.cloud.config.server.default-label 傳入請(qǐng)求沒(méi)有特定標(biāo)簽時(shí)的默認(rèn)存儲(chǔ)庫(kù)標(biāo)簽。 spring.cloud.config.server.default-profile default 傳入請(qǐng)求沒(méi)有特定的默認(rèn)應(yīng)用程序配置文件時(shí)占哟。 spring.cloud.config.server.encrypt.enabled true 在發(fā)送給客戶端之前啟用對(duì)環(huán)境屬性的解密心墅。 spring.cloud.config.server.git.basedir 庫(kù)的本地工作副本的基本目錄。 spring.cloud.config.server.git.clone-on-start 標(biāo)記以表明應(yīng)該在啟動(dòng)時(shí)克隆存儲(chǔ)庫(kù)(不是按需)榨乎。通常會(huì)導(dǎo)致啟動(dòng)速度較慢怎燥,但??第一次查詢更快。 spring.cloud.config.server.git.default-label spring.cloud.config.server.git.environment spring.cloud.config.server.git.force-pull 標(biāo)記表示存儲(chǔ)庫(kù)應(yīng)該強(qiáng)制拉蜜暑。如果真的丟棄任何本地更改并從遠(yuǎn)程存儲(chǔ)庫(kù)獲取铐姚。 spring.cloud.config.server.git.git-factory spring.cloud.config.server.git.password 使用遠(yuǎn)程存儲(chǔ)庫(kù)驗(yàn)證密碼。 spring.cloud.config.server.git.repos 存儲(chǔ)庫(kù)標(biāo)識(shí)符映射到位置和其他屬性肛捍。 spring.cloud.config.server.git.search-paths 在本地工作副本中使用的搜索路徑隐绵。默認(rèn)情況下只搜索根。 spring.cloud.config.server.git.timeout 用于獲取HTTP或SSH連接的超時(shí)(以秒為單位)(如果適用)拙毫。默認(rèn)5秒依许。 spring.cloud.config.server.git.uri 遠(yuǎn)程存儲(chǔ)庫(kù)的URI。 spring.cloud.config.server.git.username 用于遠(yuǎn)程存儲(chǔ)庫(kù)的身份驗(yàn)證用戶名缀蹄。 spring.cloud.config.server.health.repositories spring.cloud.config.server.native.fail-on-error false 標(biāo)識(shí)以確定在解密期間如何處理異常(默認(rèn)為false)峭跳。 spring.cloud.config.server.native.search-locations [] 搜索配置文件的位置。默認(rèn)與Spring Boot應(yīng)用程序相同缺前,因此[classpath:/蛀醉,classpath:/ config /,file:./衅码,file:./ config /]拯刁。 spring.cloud.config.server.native.version 為本地存儲(chǔ)庫(kù)報(bào)告的版本字符串 spring.cloud.config.server.overrides 無(wú)條件發(fā)送給所有客戶的資源的額外地圖芽突。 spring.cloud.config.server.prefix 配置資源路徑的前綴(默認(rèn)為空)界斜。當(dāng)您不想更改上下文路徑或servlet路徑時(shí)嵌入其他應(yīng)用程序時(shí)很有用售躁。 spring.cloud.config.server.strip-document-from-yaml true 標(biāo)記為指示作為文本或集合(而不是映射)的YAML文檔應(yīng)以“本機(jī)”形式返回荔睹。 spring.cloud.config.server.svn.basedir 庫(kù)的本地工作副本的基本目錄。 spring.cloud.config.server.svn.default-label trunk 用于環(huán)境屬性請(qǐng)求的默認(rèn)標(biāo)簽咨察。 spring.cloud.config.server.svn.environment spring.cloud.config.server.svn.password 使用遠(yuǎn)程存儲(chǔ)庫(kù)驗(yàn)證密碼。 spring.cloud.config.server.svn.search-paths 在本地工作副本中使用的搜索路徑。默認(rèn)情況下只搜索根巫糙。 spring.cloud.config.server.svn.uri 遠(yuǎn)程存儲(chǔ)庫(kù)的URI。 spring.cloud.config.server.svn.username 用于遠(yuǎn)程存儲(chǔ)庫(kù)的身份驗(yàn)證用戶名颊乘。 spring.cloud.config.token 安全令牌通過(guò)到底層環(huán)境庫(kù)参淹。 spring.cloud.config.uri 遠(yuǎn)程服務(wù)器的URI(默認(rèn)http:// localhost:8888)醉锄。 spring.cloud.config.username 聯(lián)系遠(yuǎn)程服務(wù)器時(shí)使用的用戶名(HTTP Basic)。 spring.cloud.consul.config.acl-token spring.cloud.consul.config.data-key data 如果格式為Format.PROPERTIES或Format.YAML浙值,則使用以下字段來(lái)查找協(xié)調(diào)配置恳不。 spring.cloud.consul.config.default-context application spring.cloud.consul.config.enabled true spring.cloud.consul.config.fail-fast true 在配置查找期間拋出異常,如果為true开呐,否則為日志警告烟勋。 spring.cloud.consul.config.format spring.cloud.consul.config.prefix config spring.cloud.consul.config.profile-separator , spring.cloud.consul.config.watch.delay 1000 手表的固定延遲的價(jià)值在毫秒。默認(rèn)為1000筐付。 spring.cloud.consul.config.watch.enabled true 如果手表啟用默認(rèn)為true卵惦。 spring.cloud.consul.config.watch.wait-time 55 等待(或阻止)觀看查詢的秒數(shù),默認(rèn)為55.需要小于默認(rèn)的ConsulClient(默認(rèn)為60)瓦戚。要增加ConsulClient超時(shí)沮尿,使用自定義的HttpClient創(chuàng)建一個(gè)帶有自定義ConsulRawClient的ConsulClient bean。 spring.cloud.consul.discovery.acl-token spring.cloud.consul.discovery.catalog-services-watch-delay 10 spring.cloud.consul.discovery.catalog-services-watch-timeout 2 spring.cloud.consul.discovery.default-query-tag 如果沒(méi)有在serverListQueryTags中列出较解,請(qǐng)?jiān)诜?wù)列表中查詢標(biāo)簽畜疾。 spring.cloud.consul.discovery.default-zone-metadata-name zone 服務(wù)實(shí)例區(qū)域來(lái)自元數(shù)據(jù)。這允許更改元數(shù)據(jù)標(biāo)簽名稱印衔。 spring.cloud.consul.discovery.enabled true 是否啟用服務(wù)發(fā)現(xiàn)啡捶? spring.cloud.consul.discovery.fail-fast true 在服務(wù)注冊(cè)期間拋出異常,如果為true当编,否則届慈,記錄警告(默認(rèn)為true)。 spring.cloud.consul.discovery.health-check-interval 10s 執(zhí)行健康檢查的頻率(例如10s) spring.cloud.consul.discovery.health-check-path /health 調(diào)用健康檢查的備用服務(wù)器路徑 spring.cloud.consul.discovery.health-check-timeout 健康檢查超時(shí)(例如10s) spring.cloud.consul.discovery.health-check-url 自定義健康檢查網(wǎng)址覆蓋默認(rèn)值 spring.cloud.consul.discovery.heartbeat.enabled false spring.cloud.consul.discovery.heartbeat.heartbeat-interval spring.cloud.consul.discovery.heartbeat.interval-ratio spring.cloud.consul.discovery.heartbeat.ttl-unit s spring.cloud.consul.discovery.heartbeat.ttl-value 30 spring.cloud.consul.discovery.host-info spring.cloud.consul.discovery.hostname 訪問(wèn)服務(wù)器時(shí)使用的主機(jī)名 spring.cloud.consul.discovery.instance-id 唯一的服務(wù)實(shí)例ID spring.cloud.consul.discovery.instance-zone 服務(wù)實(shí)例區(qū) spring.cloud.consul.discovery.ip-address 訪問(wèn)服務(wù)時(shí)使用的IP地址(還必須設(shè)置preferIpAddress才能使用) spring.cloud.consul.discovery.lifecycle.enabled true spring.cloud.consul.discovery.management-port 端口注冊(cè)管理服務(wù)(默認(rèn)為管理端口) spring.cloud.consul.discovery.management-suffix management 注冊(cè)管理服務(wù)時(shí)使用后綴 spring.cloud.consul.discovery.management-tags 注冊(cè)管理服務(wù)時(shí)使用的標(biāo)簽 spring.cloud.consul.discovery.port 端口注冊(cè)服務(wù)(默認(rèn)為偵聽(tīng)端口) spring.cloud.consul.discovery.prefer-agent-address false 我們將如何確定使用地址的來(lái)源 spring.cloud.consul.discovery.prefer-ip-address false 在注冊(cè)時(shí)使用ip地址而不是主機(jī)名 spring.cloud.consul.discovery.query-passing false 將“pass”參數(shù)添加到/ v1 / health / service / serviceName忿偷。這會(huì)將健康檢查推送到服務(wù)器金顿。 spring.cloud.consul.discovery.register true 注冊(cè)為領(lǐng)事服務(wù)。 spring.cloud.consul.discovery.register-health-check true 注冊(cè)領(lǐng)事健康檢查鲤桥。在開(kāi)發(fā)服務(wù)期間有用揍拆。 spring.cloud.consul.discovery.scheme http 是否注冊(cè)http或https服務(wù) spring.cloud.consul.discovery.server-list-query-tags 服務(wù)器列表中要查詢的serviceId的→標(biāo)簽的映射。這允許通過(guò)單個(gè)標(biāo)簽過(guò)濾服務(wù)茶凳。 spring.cloud.consul.discovery.service-name 服務(wù)名稱 spring.cloud.consul.discovery.tags 注冊(cè)服務(wù)時(shí)使用的標(biāo)簽 spring.cloud.consul.enabled true 啟用了spring cloud consul spring.cloud.consul.host localhost Consul代理主機(jī)名嫂拴。默認(rèn)為“l(fā)ocalhost”。 spring.cloud.consul.port 8500 Consul代理端口贮喧。默認(rèn)為'8500'筒狠。 spring.cloud.consul.retry.initial-interval 1000 初始重試間隔(以毫秒為單位)。 spring.cloud.consul.retry.max-attempts 6 最大嘗試次數(shù)箱沦。 spring.cloud.consul.retry.max-interval 2000 退避的最大間隔 spring.cloud.consul.retry.multiplier 1.1 下一個(gè)間隔的乘數(shù)辩恼。 spring.cloud.hypermedia.refresh.fixed-delay 5000 spring.cloud.hypermedia.refresh.initial-delay 10000 spring.cloud.inetutils.default-hostname localhost 默認(rèn)主機(jī)名。用于發(fā)生錯(cuò)誤的情況。 spring.cloud.inetutils.default-ip-address 127.0.0.1 默認(rèn)ipaddress灶伊。用于發(fā)生錯(cuò)誤的情況疆前。 spring.cloud.inetutils.ignored-interfaces 將被忽略的網(wǎng)絡(luò)接口的Java正則表達(dá)式列表。 spring.cloud.inetutils.preferred-networks 將被忽略的網(wǎng)絡(luò)地址的Java正則表達(dá)式列表聘萨。 spring.cloud.inetutils.timeout-seconds 1 計(jì)算主機(jī)名的超時(shí)秒數(shù)竹椒。 spring.cloud.inetutils.use-only-site-local-interfaces false 僅使用與站點(diǎn)本地地址的接口。有關(guān)詳細(xì)信息米辐,請(qǐng)參閱{@link InetAddress#isSiteLocalAddress()}胸完。 spring.cloud.loadbalancer.retry.enabled false spring.cloud.stream.binders spring.cloud.stream.bindings spring.cloud.stream.consul.binder.event-timeout 5 spring.cloud.stream.consumer-defaults spring.cloud.stream.default-binder spring.cloud.stream.dynamic-destinations [] spring.cloud.stream.ignore-unknown-properties true spring.cloud.stream.instance-count 1 spring.cloud.stream.instance-index 0 spring.cloud.stream.producer-defaults spring.cloud.stream.rabbit.binder.admin-adresses [] spring.cloud.stream.rabbit.binder.compression-level 0 spring.cloud.stream.rabbit.binder.nodes [] spring.cloud.stream.rabbit.bindings spring.cloud.zookeeper.base-sleep-time-ms 50 重試之間等待的初始時(shí)間 spring.cloud.zookeeper.block-until-connected-unit 與Zookeeper連接時(shí)阻止的時(shí)間單位 spring.cloud.zookeeper.block-until-connected-wait 10 等待時(shí)間阻止連接到Zookeeper spring.cloud.zookeeper.connect-string localhost:2181 連接字符串到Zookeeper集群 spring.cloud.zookeeper.default-health-endpoint 將檢查以驗(yàn)證依賴關(guān)系是否存在的默認(rèn)運(yùn)行狀況端點(diǎn) spring.cloud.zookeeper.dependencies 將別名映射到ZookeeperDependency。從Ribbon的角度看儡循,別名實(shí)際上是serviceID舶吗,因?yàn)镽ibbon不能接受serviceID中的嵌套結(jié)構(gòu) spring.cloud.zookeeper.dependency-configurations spring.cloud.zookeeper.dependency-names spring.cloud.zookeeper.discovery.enabled true spring.cloud.zookeeper.discovery.instance-host 預(yù)定義的主機(jī)可以在Zookeeper中注冊(cè)自己的服務(wù)。對(duì)應(yīng)于URI規(guī)范中的{code address}择膝。 spring.cloud.zookeeper.discovery.instance-port 端口注冊(cè)服務(wù)(默認(rèn)為偵聽(tīng)端口) spring.cloud.zookeeper.discovery.metadata 獲取與此實(shí)例關(guān)聯(lián)的元數(shù)據(jù)名稱/值對(duì)誓琼。該信息被發(fā)送到動(dòng)物管理員,可以被其他實(shí)例使用肴捉。 spring.cloud.zookeeper.discovery.register true 在動(dòng)物園管理員中注冊(cè)為服務(wù)腹侣。 spring.cloud.zookeeper.discovery.root /services 所有實(shí)例都被注冊(cè)的根Zookeeper文件夾 spring.cloud.zookeeper.discovery.uri-spec {scheme}://{address}:{port} 在Zookeeper服務(wù)注冊(cè)期間解決的URI規(guī)范 spring.cloud.zookeeper.enabled true 啟用了Zookeeper spring.cloud.zookeeper.max-retries 10 最多重試次數(shù) spring.cloud.zookeeper.max-sleep-ms 500 每次重試最多可以以ms為單位睡眠 spring.cloud.zookeeper.prefix 將應(yīng)用于所有Zookeeper依賴關(guān)系的路徑的公共前綴 spring.integration.poller.fixed-delay 1000 修復(fù)默認(rèn)輪詢器的延遲。 spring.integration.poller.max-messages-per-poll 1 默認(rèn)輪詢器每輪詢的最大消息齿穗。 spring.sleuth.integration.enabled true 啟用Spring Integration偵察器傲隶。 spring.sleuth.integration.patterns * 一組簡(jiǎn)單的模式,通道名稱將與之匹配窃页。默認(rèn)值為*(所有通道)跺株。請(qǐng)參閱org.springframework.util.PatternMatchUtils.simpleMatch(String,String)脖卖。 spring.sleuth.keys.async.class-name-key class 具有使用{@code @Async}注釋的方法的類的簡(jiǎn)單名稱煮剧,從異步進(jìn)程開(kāi)始 @see org.springframework.scheduling.annotation.Async spring.sleuth.keys.async.method-name-key method 使用{@code @Async}注釋的方法的名稱 @see org.springframework.scheduling.annotation.Async spring.sleuth.keys.async.prefix 如果標(biāo)題名稱被添加為標(biāo)簽罐韩,則使用前綴距淫。 spring.sleuth.keys.async.thread-name-key thread 執(zhí)行異步方法的線程的名稱 @see org.springframework.scheduling.annotation.Async spring.sleuth.keys.http.headers 額外的標(biāo)題應(yīng)該作為標(biāo)簽添加申窘,如果它們存在。如果頭值是多值的十籍,則標(biāo)簽值將是逗號(hào)分隔的單引號(hào)列表蛆封。 spring.sleuth.keys.http.host http.host URL或主機(jī)頭的域部分。示例:“mybucket.s3.amazonaws.com”勾栗。用于過(guò)濾主機(jī)而不是ip地址惨篱。 spring.sleuth.keys.http.method http.method HTTP方法或動(dòng)詞,如“GET”或“POST”围俘。用于過(guò)濾http路由妒蛇。 spring.sleuth.keys.http.path http.path 絕對(duì)的http路徑机断,沒(méi)有任何查詢參數(shù)。示例:“/ objects / abcd-ff”绣夺。用于過(guò)濾http路由,可以與zipkin v1一起移植欢揖。在zipkin v1中陶耍,只支持等于過(guò)濾器。刪除查詢參數(shù)使不同URI的數(shù)量減少她混。例如烈钞,無(wú)論查詢行中編碼的簽名參數(shù)如何,都可以查詢相同的資源坤按。這不會(huì)降低HTTP單路由的基數(shù)毯欣。例如,通常將路由表示為http URI模板臭脓,如“/ resource / {resource_id}”酗钞。在只有等量查詢可用的系統(tǒng)中,如果實(shí)際請(qǐng)求是“/ resource / abcd-ff”来累,則搜索{@code http.uri = / resource}將不匹配砚作。歷史記錄:這通常在拉鏈中被表示為“http.uri”,但最常見(jiàn)的只是一條路嘹锁。 spring.sleuth.keys.http.prefix http. 如果標(biāo)題名稱被添加為標(biāo)簽葫录,則使用前綴。 spring.sleuth.keys.http.request-size http.request.size 非空HTTP請(qǐng)求體的大辛旎(以字節(jié)為單位)米同。防爆。"16384" 大上傳可能會(huì)超出限制或直接影響延遲摔竿。 spring.sleuth.keys.http.response-size http.response.size 非空HTTP響應(yīng)體的大忻媪浮(以字節(jié)為單位)。防爆拯坟。"16384" 大量下載可能會(huì)超出限制或直接影響延遲但金。 spring.sleuth.keys.http.status-code http.status_code 當(dāng)HTTP響應(yīng)代碼不在2xx范圍內(nèi)。防爆郁季±淅#“503”用于過(guò)濾錯(cuò)誤狀態(tài)。2xx范圍不會(huì)被記錄梦裂,因?yàn)槌晒Υa對(duì)延遲故障排除不那么有趣似枕。省略節(jié)省每個(gè)跨度至少20個(gè)字節(jié)。 spring.sleuth.keys.http.url http.url 整個(gè)URL年柠,包括方案凿歼,主機(jī)和查詢參數(shù)(如果可用)。防爆〈疸荆“https://mybucket.s3.amazonaws.com/objects/abcd-ff?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Algorithm=AWS4-HMAC-SHA256...”結(jié)合{@link #method }味赃,您可以了解完全限定的請(qǐng)求行。這是可選的虐拓,因?yàn)樗赡馨ㄋ饺藬?shù)據(jù)或相當(dāng)長(zhǎng)的長(zhǎng)度心俗。 spring.sleuth.keys.hystrix.command-group commandGroup 命令組的名稱Hystrix使用命令組密鑰將諸如報(bào)告,警報(bào)蓉驹,儀表板或團(tuán)隊(duì)/庫(kù)所有權(quán)的命令分組在一起城榛。 @see com.netflix.hystrix.HystrixCommandGroupKey spring.sleuth.keys.hystrix.command-key commandKey 命令鍵的名稱描述給定命令的名稱。代表用于監(jiān)視态兴,斷路器狠持,指標(biāo)發(fā)布,緩存和其他此類用途的{@link com.netflix.hystrix.HystrixCommand}的關(guān)鍵瞻润。 @see com.netflix.hystrix.HystrixCommandKey spring.sleuth.keys.hystrix.prefix 如果標(biāo)題名稱被添加為標(biāo)簽喘垂,則使用前綴。 spring.sleuth.keys.hystrix.thread-pool-key threadPoolKey 線程池密鑰的名稱敢订。線程池密鑰表示用于監(jiān)視王污,指標(biāo)發(fā)布,緩存和其他此類用途的{@link com.netflix.hystrix.HystrixThreadPool}楚午。甲{@link com.netflix.hystrix.HystrixCommand}與單個(gè)相關(guān)的{@link com.netflix.hystrix.HystrixThreadPool}如由檢索{@link com.netflix.hystrix.HystrixThreadPoolKey}注入它昭齐,或者它的默認(rèn)值使用{@link com.netflix.hystrix.HystrixCommandGroupKey}創(chuàng)建的創(chuàng)建它。 @see com.netflix.hystrix.HystrixThreadPoolKey spring.sleuth.keys.message.headers 額外的標(biāo)題應(yīng)該作為標(biāo)簽添加,如果它們存在。如果頭值不是String恨统,它將使用其toString()方法轉(zhuǎn)換為String。 spring.sleuth.keys.message.payload.size message/payload-size 估計(jì)有效載荷的大欣锔病(如果有的話)。 spring.sleuth.keys.message.payload.type message/payload-type 有效載荷的類型缆瓣。 spring.sleuth.keys.message.prefix message/ 如果標(biāo)題名稱被添加為標(biāo)簽喧枷,則使用前綴。 spring.sleuth.keys.mvc.controller-class mvc.controller.class 小寫弓坞,連字符分隔處理請(qǐng)求的類的名稱隧甚。防爆。名為“BookController”的類將導(dǎo)致“book-controller”標(biāo)簽值渡冻。 spring.sleuth.keys.mvc.controller-method mvc.controller.method 小寫戚扳,連字符分隔處理請(qǐng)求的類的名稱。防爆族吻。名為“l(fā)istOfBooks”的方法將導(dǎo)致“l(fā)ist-of-books”標(biāo)簽值帽借。 spring.sleuth.metric.span.accepted-name counter.span.accepted spring.sleuth.metric.span.dropped-name counter.span.dropped spring.sleuth.sampler.percentage 0.1 應(yīng)采樣的請(qǐng)求百分比珠增。例如1.0 - 100%的請(qǐng)求應(yīng)該被抽樣。精度僅為全數(shù)(即不支持0.1%的痕跡)砍艾。 spring.sleuth.trace-id128 false 如果為true蒂教,則生成128位跟蹤ID,而不是64位跟蹤ID脆荷。 zuul.add-host-header false 標(biāo)識(shí)以確定代理是否轉(zhuǎn)發(fā)主機(jī)頭悴品。 zuul.add-proxy-headers true 標(biāo)識(shí)以確定代理是否添加X(jué)-Forwarded- *標(biāo)頭。 zuul.host.max-per-route-connections 20 單個(gè)路由可以使用的最大連接數(shù)简烘。 zuul.host.max-total-connections 200 代理可以容納到后端的總連接數(shù)。 zuul.ignore-local-service true zuul.ignore-security-headers true 標(biāo)記說(shuō)定枷,如果spring security在類路徑上孤澎,則將SECURITY_HEADERS添加到忽略的標(biāo)頭。通過(guò)將ignoreSecurityHeaders設(shè)置為false欠窒,我們可以關(guān)閉此默認(rèn)行為覆旭。這應(yīng)該與禁用默認(rèn)的spring security標(biāo)頭一起使用,請(qǐng)參見(jiàn)https://docs.spring.io/spring-security/site/docs/current/reference/html/headers.html#default-security-headers zuul.ignored-headers HTTP標(biāo)頭的名稱完全忽略(即將其從下游請(qǐng)求中刪除岖妄,并將其從下游響應(yīng)中刪除)型将。 zuul.ignored-patterns zuul.ignored-services 一組服務(wù)名稱不考慮代理自動(dòng)。默認(rèn)情況下荐虐,發(fā)現(xiàn)客戶端中的所有服務(wù)都將被代理七兜。 zuul.prefix 所有路由的公共前綴。 zuul.remove-semicolon-content true 標(biāo)記說(shuō)福扬,可以刪除超過(guò)第一個(gè)分號(hào)的路徑元素腕铸。 zuul.retryable 默認(rèn)情況下是否支持重試的標(biāo)志(假設(shè)路由本身支持)。 zuul.ribbon-isolation-strategy zuul.routes 將路線名稱映射到屬性铛碑。 zuul.s-e-c-u-r-i-t-y-h-e-a-d-e-r-s 一般預(yù)期由Spring安全性添加的標(biāo)頭狠裹,因此如果代理和后端使用Spring保護(hù),則通常會(huì)重復(fù)汽烦。默認(rèn)情況下涛菠,如果存在Spring安全性,并且ignoreSecurityHeaders = true撇吞,它們將被添加到忽略的標(biāo)頭俗冻。 zuul.semaphore.max-semaphores 100 Hystrix的總信號(hào)量的最大數(shù)量。 zuul.sensitive-headers 不傳遞到下游請(qǐng)求的敏感標(biāo)頭列表梢夯。默認(rèn)為通常包含用戶憑據(jù)的“安全”標(biāo)題集言疗。如果下游服務(wù)是與代理相同的系統(tǒng)的一部分,那么從列表中刪除它們是正確的颂砸,所以他們正在共享認(rèn)證數(shù)據(jù)噪奄。如果在您自己的域之外使用物理URL死姚,那么一般來(lái)說(shuō)泄漏用戶憑據(jù)將是一個(gè)壞主意。 zuul.servlet-path /zuul 安裝Zuul作為servlet的路徑(不是Spring MVC的一部分)勤篮。對(duì)于具有大型機(jī)構(gòu)的請(qǐng)求都毒,例如文件上傳,servlet對(duì)于更高的內(nèi)存效率更高碰缔。 zuul.ssl-hostname-validation-enabled true 標(biāo)記以說(shuō)明是否應(yīng)驗(yàn)證ssl連接的主機(jī)名账劲。默認(rèn)值為true。這只應(yīng)用于測(cè)試設(shè)置金抡! zuul.strip-prefix true 在轉(zhuǎn)發(fā)之前標(biāo)記是否從路徑中刪除前綴瀑焦。 zuul.trace-request-body true 標(biāo)記說(shuō)可以跟蹤請(qǐng)求機(jī)構(gòu)。 $ 的替換字符串灶体。
_ 的替換字符串。