Netflix之後,如何用Spring Cloud 新組件構建微服務架構?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"本文最初發表於作者Piotr Mińkowski的"},{"type":"link","attrs":{"href":"https:\/\/piotrminkowski.com\/2020\/05\/01\/a-new-era-of-spring-cloud\/","title":"","type":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"個人站點"}]},{"type":"text","marks":[{"type":"strong"}],"text":",經作者許可由InfoQ中文站編譯分享。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2020年12月22日,"},{"type":"link","attrs":{"href":"https:\/\/spring.io\/blog\/2020\/12\/22\/spring-cloud-2020-0-0-aka-ilford-is-available","title":"","type":null},"content":[{"type":"text","text":"Spring在官方博客正式發佈2020.0.0版本(即Ilford)"}]},{"type":"text","text":",這是一個採用新命名規範的版本,但是,更引人關注的是該版本移除了多個之前處於維護模式的Netflix組件,如Ribbon、Hystrix和Zuul。唯一剩餘的模塊是Eureka。這些變化對Spring Cloud來說是很重要的,因爲從誕生之初,Spring Cloud就因爲與Netflix組件的集成得到廣泛認可。此外,Spring Cloud Netflix仍然是GitHub上最受歡迎的Spring Cloud項目(約有4200 star)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在將Netflix組件轉入維護模式的同時,Spring 團隊已經開始着手準備替代方案了。因此,Ribbon將被Spring Cloud Load Balancer取代,Hystrix將被建立在Resilience4J庫之上的Spring Cloud Circuit Breaker取代。Spring Cloud Gateway是Zuul的競爭解決方案,目前已是一個很受歡迎的項目,在Ilford版本後,它將是API網關方面的唯一方案。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的主要目的是指導你使用新的Spring Cloud組件構建微服務架構,替換廢棄的Netflix項目。示例應用的源碼可以在"},{"type":"link","attrs":{"href":"https:\/\/github.com\/piomin\/course-spring-microservices.git","title":"","type":null},"content":[{"type":"text","text":"GitHub上的倉庫中找到"}]},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"架構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖展現了我們樣例系統的架構。在這裏,我們包含了微服務的特徵元素,如API網關、服務發現server以及配置server。接下來,我會展示如何使用提供了這些模式的Spring Cloud組件。目前,向系統中添加API網關的主要組件是"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Gateway"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/14\/14baa701320293074ed656cf90deaf7d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud提供了與多個可用作服務發現服務器的方案的集成,包括Netflix Eureka、HashiCorp Consul、Alibaba Nacos和Apache ZooKeeper。其中,最流行的是前兩者。"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Netflix Eureka"},{"type":"text","text":"專門用於服務發現,而"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Consul"},{"type":"text","text":"可同時通過Consul Services實現服務發現,通過Consul Key\/Value引擎實現分佈式跟蹤特性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud Config只負責提供一個配置管理的機制。但是,它也可以與第三方工具集成,如來自HashiCorp的Vault。以兩個簡單的Spring Boot應用"},{"type":"text","marks":[{"type":"italic"}],"text":"callme-service"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"italic"}],"text":"caller-service"},{"type":"text","text":"爲例,我們闡述如何將應用與服務發現和配置服務器進行集成。我們還會在"},{"type":"text","marks":[{"type":"italic"}],"text":"caller-service"},{"type":"text","text":"上使用Spring Cloud Load Balancer啓用客戶端負載均衡,並使用基於Resilience4J構建的"},{"type":"text","marks":[{"type":"strong"}],"text":"Spring Cloud Circuit Breaker"},{"type":"text","text":"啓用斷路器功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"服務發現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藉助Spring Cloud的DiscoveryClient抽象,在客戶端切換不同的服務發現服務器非常容易。這種切換隻需要替換Maven "},{"type":"codeinline","content":[{"type":"text","text":"pom.xml"}]},{"type":"text","text":"文件中的一個依賴項。如果你想要使用Eureka的話,只需要添加如下的starter到微服務中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-client\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而如果你想要使用Consul的話,那麼需要添加如下的starter到微服務中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-consul-discovery\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你想要爲服務發現的客戶端定義一些非默認的配置的話,那情況就有點複雜了。在這種情況下,你需要使用特定於Eureka或Consul的屬性。例如,如果你想要在同一個主機上運行同一個應用的多個實例,並啓用動態HTTP服務器端口(選項"},{"type":"codeinline","content":[{"type":"text","text":"server.port=0"}]},{"type":"text","text":"),那麼你需要爲每個實例設置一個唯一的id。如下是Eureka客戶端中所使用的屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"eureka:\n instance:\n instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${random.value}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對Consul客戶端來說,相同的配置如下所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n cloud:\n consul:\n discovery:\n instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${random.value}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在微服務架構中,我們可以很容易地通過Spring Cloud Netflix Eureka Server模塊配置和運行Eureka服務發現。只需要創建包含該模塊的Spring Boot應用即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-server\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們還需要爲應用啓用Eureka,只需在主類上添加@EnableEurekaServer註解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@SpringBootApplication\n@EnableEurekaServer\nclass DiscoveryServerApplication\nfun main(args: Array) {\n runApplication(*args)\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本地機器上運行Consul的最簡便方式是使用它的Docker鏡像。我們可以通過執行如下命令以開發模式在Docker容器中運行Consul:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"$ docker run -d --name=consul -e CONSUL_BIND_INTERFACE=eth0 -p 8500:8500 consul:1.7.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"使用Spring cloud進行分佈式配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我們的架構中,下一個重要的元素就是配置服務器。在Spring Cloud中,能提供分佈式配置機制的最流行方案就是Spring Cloud Config。Spring Cloud Config爲分佈式系統中的外部化配置提供了服務器端和客戶端的支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藉助配置服務器,我們能有一箇中心化的位置管理應用在所有環境下的外部屬性。有一些其他的方案可用作基於微服務的架構的配置服務器,如Consul、ZooKeeper和Alibaba Nacos。但是,從嚴格意義上來講,所有的這些方案都不是專門的分佈式配置,它們也可以用作服務發現的服務器。Spring Cloud Config可能會集成不同的工具來存儲數據。服務器存儲後端的默認實現是使用git,但是我們也能使用像HashiCorp Vault這樣的工具來管理secret和保護敏感數據,也可以使用簡單的文件系統。在一個配置服務器應用中,可能會將不同的後端組合在一起。我們只需要在應用屬性文件中通過"},{"type":"codeinline","content":[{"type":"text","text":"spring.profiles.active"}]},{"type":"text","text":"激活對應的profile即可。我們可以覆蓋一些默認值,比如修改Vault服務器的地址或設置認證token。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n application:\n name: config-server\n profiles:\n active: native,vault\n cloud:\n config:\n server:\n native:\n searchLocations: classpath:\/config-repo\n vault:\n host: 192.168.99.100\n authentication: TOKEN\n token: spring-microservices-course"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對Consul來講,同樣如此,在開發模式下,我們應該使用Docker來運行Vault實例。我們可以使用環境變量"},{"type":"codeinline","content":[{"type":"text","text":"VAULT_DEV_ROOT_TOKEN_ID"}]},{"type":"text","text":"設置一個靜態的根token用於進行認證:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"$ docker run -d --name vault --cap-add=IPC_LOCK -e 'VAULT_DEV_ROOT_TOKEN_ID=spring-microservices-course' -p 8200:8200 vault:1.4.0"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當Spring Cloud Config與服務發現一起使用時,我們可以在兩種可用的方式間做出選擇,即"},{"type":"text","marks":[{"type":"italic"}],"text":"Config First Bootstrap"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"italic"}],"text":"Discovery First Bootstrap"},{"type":"text","text":"。在"},{"type":"text","marks":[{"type":"italic"}],"text":"Discovery First Bootstrap"},{"type":"text","text":"中,配置服務器會將自身註冊到發現服務中。藉助這一點,每個微服務都能基於配置服務器的註冊id找到它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲配置是在引導階段注入的,我們需要使用"},{"type":"codeinline","content":[{"type":"text","text":"bootstrap.yml"}]},{"type":"text","text":"在客戶端設置屬性。爲了在客戶端啓用配置服務器的“發現”功能,我們需要將"},{"type":"codeinline","content":[{"type":"text","text":"spring.cloud.config.discovery.enabled"}]},{"type":"text","text":"屬性設置爲"},{"type":"codeinline","content":[{"type":"text","text":"true"}]},{"type":"text","text":"。如果配置服務器的註冊服務id與自動配置的"},{"type":"codeinline","content":[{"type":"text","text":"configserver"}]},{"type":"text","text":"(在我們的樣例中是"},{"type":"codeinline","content":[{"type":"text","text":"config-server"}]},{"type":"text","text":")不同的話,我們還應該覆蓋它。當然,還可以使用Consul作爲配置屬性源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n application:\n name: callme-service\n cloud:\n config:\n discovery:\n enabled: true\n serviceId: config-server\n consul:\n host: 192.168.99.100\n config:\n format: YAML"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"服務間通信"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前,有三種基於HTTP的Spring組件可用於服務間的通信,它們都與服務發現進行了集成:同步的"},{"type":"codeinline","content":[{"type":"text","text":"RestTemplate"}]},{"type":"text","text":"、反應式的"},{"type":"codeinline","content":[{"type":"text","text":"WebClient"}]},{"type":"text","text":"以及聲明式的REST客戶端OpenFeign。"},{"type":"codeinline","content":[{"type":"text","text":"RestTemplate"}]},{"type":"text","text":"組件可以通過Spring Web模塊獲取,"},{"type":"codeinline","content":[{"type":"text","text":"WebClient"}]},{"type":"text","text":"能通過Spring WebFlux模塊獲取。要包含Spring Cloud OpenFeign的話,我們需要一個專門的starter。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.boot\n spring-boot-starter-webflux\n\n\n org.springframework.cloud\n spring-cloud-starter-openfeign\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要使用"},{"type":"codeinline","content":[{"type":"text","text":"RestTemplate"}]},{"type":"text","text":"或"},{"type":"codeinline","content":[{"type":"text","text":"WebClient"}]},{"type":"text","text":"進行支持服務發現的通信,我們需要註冊bean併爲它們添加"},{"type":"codeinline","content":[{"type":"text","text":"@LoadBalanced"}]},{"type":"text","text":"註解。我們最好還要爲這樣的通信設置恰當的超時時間,當不使用斷路器的時候,更應如此。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@SpringBootApplication\n@EnableFeignClients\nclass InterCallerServiceApplication {\n @Bean\n @LoadBalanced\n fun template(): RestTemplate = RestTemplateBuilder()\n .setReadTimeout(Duration.ofMillis(100))\n .setConnectTimeout(Duration.ofMillis(100))\n .build()\n @Bean\n @LoadBalanced\n fun clientBuilder(): WebClient.Builder {\n val tcpClient: TcpClient = TcpClient.create()\n .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 100)\n .doOnConnected { conn ->\n conn.addHandlerLast(ReadTimeoutHandler(100, TimeUnit.MILLISECONDS))\n }\n val connector = ReactorClientHttpConnector(HttpClient.from(tcpClient))\n return WebClient.builder().clientConnector(connector)\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Cloud LoadBalancer提供了自己的抽象和實現。爲了實現負載均衡機制,Spring Cloud添加了"},{"type":"codeinline","content":[{"type":"text","text":"ReactiveLoadBalancer"}]},{"type":"text","text":"接口,並支持基於Round-Robin和Random的實現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前,對於負載均衡的定製化並沒有太多的選項。不過,選項之一就是配置客戶端緩存的能力。默認情況下,每個客戶端緩存目標服務的列表並且每隔30秒刷新一次。在你的場景下,這樣的間隔可能會有些長。在配置中,我們可以很容易地修改它,如下面的樣例,我們將其設置成1秒。如果你的負載均衡器與Eureka服務發現進行集成的話,還需要減少獲取註冊表的時間間隔,默認它是30秒鐘。在修改配置之後,這兩個對客戶端的變更能幾乎立即刷新當前運行的服務的列表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n cloud:\n loadbalancer:\n cache:\n ttl: 1s\n ribbon:\n enabled: false\neureka:\n client:\n registryFetchIntervalSeconds: 1\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"斷路器"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"斷路器是微服務架構中一個很流行的設計模式。它被設計用來探測失敗並封裝阻止失敗不斷重複出現的邏輯。Spring Cloud提供了一個使用不同斷路器的實現。針對Resilience4J,有兩個實現,分別用於反應式應用和非反應式應用。要啓用非反應式的實現,我們要包含如下的依賴。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-circuitbreaker-resilience4j\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下是負責註冊"},{"type":"codeinline","content":[{"type":"text","text":"Customizer"}]},{"type":"text","text":" bean的代碼,這個bean配置了斷路器的行爲。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@Bean\nfun defaultCustomizer(): Customizer {\n return Customizer { factory: Resilience4JCircuitBreakerFactory ->\n factory.configureDefault { id: String? ->\n Resilience4JConfigBuilder(id)\n .timeLimiterConfig(TimeLimiterConfig.custom()\n .timeoutDuration(Duration.ofMillis(500))\n .build())\n .circuitBreakerConfig(CircuitBreakerConfig.custom()\n .slidingWindowSize(10)\n .failureRateThreshold(33.3F)\n .slowCallRateThreshold(33.3F)\n .build())\n .build()\n }\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"斷路器的設置通過如下圖片進行了可視化。滑動窗口的大小設置了用於計算錯誤率的請求數。如果我們在大小爲10的窗口中出現了3個以上的錯誤,那麼斷路器就會打開。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/81\/8167dcc5456d1651a88051d17094285d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下一步,我們需要使用"},{"type":"codeinline","content":[{"type":"text","text":"Resilience4JCircuitBreakerFactory"}]},{"type":"text","text":"創建一個斷路器實例併爲HTTP客戶端啓用它,如下所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@RestController\n@RequestMapping(\"\/caller\")\nclass CallerController(private val template: RestTemplate, private val factory: Resilience4JCircuitBreakerFactory) {\n private var id: Int = 0\n @PostMapping(\"\/random-send\/{message}\")\n fun randomSend(@PathVariable message: String): CallmeResponse? {\n val request = CallmeRequest(++id, message)\n val circuit = factory.create(\"random-circuit\")\n return circuit.run { template.postForObject(\"http:\/\/inter-callme-service\/callme\/random-call\",\n request, CallmeResponse::class.java) }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Spring Cloud API網關"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我們微服務架構中,缺失的最後一個元素就是API網關。Spring Cloud Gateway能幫助我們實現這一組件。目前,它是Spring Cloud中僅次於Spring Cloud Netflix,歡迎程度排在第二名的項目。在GitHub上它有2800+star。它構建在Spring WebFlux和Reactor項目之上,它以反應式的方式運行,需要Netty作爲運行時框架。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"API網關的主要目標是提供一個有效的方式路由至API,從而爲外部客戶端隱藏微服務系統的複雜性,但是它也能解決一些安全性和可靠性相關的問題。用來配置Spring Cloud Gateway的主要組件是路由。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它由一個ID、一個目標URI、一個斷言的集合和一個過濾器的集合組成。如果斷言聚合爲true的話,則會匹配路由。通過過濾器,我們則可以在發送下游請求之前或之後修改請求和響應。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過預定義的網關過濾器集合,我們可以實現路徑重寫、速率限制、發現客戶端、斷路器、fallback或路由指標等機制。爲了在網關上啓用所有這些功能,我們首先需要包含以下依賴關係。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n org.springframework.cloud\n spring-cloud-starter-gateway\n\n\n org.springframework.boot\n spring-boot-starter-actuator\n\n\n org.jetbrains.kotlin\n kotlin-reflect\n\n\n org.jetbrains.kotlin\n kotlin-stdlib\n\n\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-client\n\n\n org.springframework.boot\n spring-boot-starter-data-redis-reactive\n\n\n org.springframework.cloud\n spring-cloud-starter-circuitbreaker-reactor-resilience4j\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲實現上述所列的所有特性,我們並不需要編寫太多的代碼。幾乎所有的內容都是通過應用屬性配置的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"spring:\n application:\n name: api-gateway\n cloud:\n gateway:\n discovery:\n locator:\n enabled: true \n lowerCaseServiceId: true\n routes:\n - id: inter-callme-service\n uri: lb:\/\/inter-callme-service\n predicates:\n - Path=\/api\/callme\/**\n filters:\n - RewritePath=\/api(?\/?.*), $\\{path}\n - name: RequestRateLimiter\n args:\n redis-rate-limiter.replenishRate: 20\n redis-rate-limiter.burstCapacity: 40\n - name: CircuitBreaker\n args:\n name: sampleSlowCircuitBreaker\n fallbackUri: forward:\/fallback\/test\n - id: inter-caller-service\n uri: lb:\/\/inter-caller-service\n predicates:\n - Path=\/api\/caller\/**\n filters:\n - StripPrefix=1\n - name: RequestRateLimiter\n args:\n redis-rate-limiter.replenishRate: 20\n redis-rate-limiter.burstCapacity: 40\n loadbalancer:\n ribbon:\n enabled: false\n redis:\n host: 192.168.99.100\nmanagement:\n endpoints.web.exposure.include: '*'\n endpoint:\n health:\n show-details: always\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有些設置依然需要在代碼中進行配置,也就是斷路器的配置,它基於Resilience4J項目,我們需要註冊"},{"type":"codeinline","content":[{"type":"text","text":"Customizer"}]},{"type":"text","text":" bean。此外,我們還需要定義一個速率限制的key,它用來設置爲限制計數選擇請求的策略。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@SpringBootApplication\nclass ApiGatewayApplication {\n @Bean\n fun keyResolver(): KeyResolver = KeyResolver { _ -> Mono.just(\"1\") }\n @Bean\n fun defaultCustomizer(): Customizer {\n return Customizer { factory: ReactiveResilience4JCircuitBreakerFactory ->\n factory.configureDefault { id: String? ->\n Resilience4JConfigBuilder(id)\n .timeLimiterConfig(TimeLimiterConfig.custom()\n .timeoutDuration(Duration.ofMillis(500))\n .build())\n .circuitBreakerConfig(CircuitBreakerConfig.custom()\n .slidingWindowSize(10)\n .failureRateThreshold(33.3F)\n .slowCallRateThreshold(33.3F)\n .build())\n .build()\n }\n }\n }\n}\nfun main(args: Array) {\n runApplication(*args)\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,我們快速瞭解瞭如何使用最新的Spring Cloud組件構建微服務架構。關於這些組件的更多詳情,讀者可以閱讀"},{"type":"link","attrs":{"href":"https:\/\/docs.spring.io\/spring-cloud\/docs\/2020.0.0\/reference\/html\/","title":"","type":null},"content":[{"type":"text","text":"Spring Cloud的最新文檔"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/piotrminkowski.com\/2020\/05\/01\/a-new-era-of-spring-cloud\/","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/piotrminkowski.com\/2020\/05\/01\/a-new-era-of-spring-cloud\/"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章