Spring Cloud集中環境中開發如何避免服務衝突
使用中央環境開發Spring Cloud微服務,同時避免服務衝突。開發人員如何在同一個中央彈簧雲環境中同時工作並且仍然不會互相干擾?
使用 spring boot 和 spring cloud 時,開發基於微服務架構的軟體非常容易。只需輸入幾行程式碼就可以啟動並執行微服務。但是,如何在這樣的環境中開發真實世界的應用程式呢?從理論上講,每個微服務都是孤立的,可以單獨開發,但實際上並非如此。要在使用它的應用程式的上下文中開發和測試您的服務,不僅需要您的微服務啟動和執行。那麼,如何在多微服務環境中方便地開發呢?
好吧,如果您只需要兩到三個服務,那麼您可以在本地執行它們,因此設定這樣的環境並不是什麼大問題。但是,如果您的應用程式由許多服務(大型應用程式的常見情況)組成,那麼啟動這樣的環境,保持最新狀態等等可能會變成一個真正令人頭痛的問題。
另一個極端,在本地只執行一個微服務,其餘的在一些中央伺服器上也是一個問題:
例如,如果我正在開發'MyService'微服務,並且中央環境也有一個“MyService”服務正在執行(所有開發人員都在使用這個環境,因此它運行了所有服務,公共發現服務會同時引用這兩個服務,當許多開發人員正在使用這樣的環境時,問題當然會變得更糟。
我們找到了一種享受這兩個世界的優雅方式 - 每個開發人員只在本地執行他或她當前正在處理的服務,而所有其他服務都在某個中央環境中執行,我們設法避免例項之間的衝突和混淆那個服務!
這種魔力是如何發生的?好吧,我們最初問題的根源是開發人員正在處理的服務及其在中央環境(以及其他開發人員的機器上)上的匹配例項在發現服務上使用相同的名稱註冊(我們使用Eureka,順便說說)。如果每個例項都使用不同的名稱註冊自己並且仍然可以被需要它的任何其他服務使用,該怎麼辦?嗯,有可能!但這有點棘手。這些是必要的步驟:
1.對於每個服務(或在所有服務正在使用的一個基礎架構jar中),我們定義一個@Configuration bean(RemoteEurekaConfig)來調整對Eureka的註冊。在這個類中,我們返回一個EurekaInstanceConfigBean,它通過將主機名新增到已註冊的服務來覆蓋超類行為。這樣'MyService'將被註冊為' MyHostName.MyService',因此我和我的開發人員每個人都有這個服務的唯一名稱,允許我們同時處理它(而不是與' MyService衝突)'中央環境的例項,所以其他人可以在開發其他服務時使用它)。
我們通過將application屬性值設定是否為true來確定是否要執行此行為(在我的示例中名為devDiscovery)。您還可以使用當前配置檔案(dev / prod)或任何其他所需標誌來確定您的服務是否應更改預設發現服務註冊。此外,您可以將newAppName設定為您想要的任何唯一值(開發人員名稱,您使用的版本等),只要它在開發人員中是唯一的並且足夠有意義。
RemoteEurekaConfig:
@Value(“${dev.discovery:false}”) <b>private</b> Boolean devDiscovery; @Bean @Autowired @Profile(“development”) <b>public</b> EurekaInstanceConfigBean eurekaInstanceConfigBean(<b>final</b> InetUtils inetUtils) { String newAppName = getHostname() + “.” + appName; config = <b>new</b> EurekaInstanceConfigBean(inetUtils) { @Override <b>public</b> <b>void</b> setEnvironment(Environment environment) { <b>super</b>.setEnvironment(environment); <b>if</b> (devDiscovery != <b>null</b> && devDiscovery == <b>true</b>) { setAppname(newAppName); setVirtualHostName(newAppName); setSecureVirtualHostName(newAppName); } } }; config.setNonSecurePort(port); config.setIpAddress(getHostAddress()); config.getMetadataMap().put(“instanceId”, config.getHostname() + “:” + config.getAppname() + “:” + port); <b>return</b> config; }
2.現在,我們的服務是登記在遠端尤里卡一個獨特的名字其實是不夠的,因為我們的閘道器仍會路由UI(或其他)請求的“MyService ”例項,而不是“ MyHostName.MyService”,因為這些路由是在Gateway定義的。
每當我們在本地執行服務時,我們都可以在application.properties檔案中修改這些路由,但這可能容易出錯且繁瑣。
我們可以做得更好 - 我們可以通過定義一個新的bean DynamicRouting來動態處理它,在初始化時將遍歷所有已註冊的服務,並將本地路由更新為本地執行的服務。
它如何知道哪些服務在本地執行?簡單 - 這樣的服務將有我們獨特的字首,當然:)。我們應該迭代所有已註冊的服務,因為我們可能在本地執行多個服務。
不過,使用此解決方案,本地執行的服務應該在Gateway之前啟動。如果需要更動態的行為,我們可以每隔X秒應用此邏輯以始終保持最新狀態(儘管在大多數情況下我發現它是一種過度殺傷)。
DynamicRouting:
@Autowired <b>private</b> ZuulProperties zuulProperties; @Autowired DiscoveryClient discoveryClient @PostConstruct <b>public</b> <b>void</b> init() { <font><i>// Get all services from Eureka</i></font><font> List<String> allServices = discoveryClient.getServices(); String prefix = getHostname()+”.”; <b>for</b>(String service : allServices) { </font><font><i>// If a service starts with my designated prefix, replace the original route to it</i></font><font> <b>if</b> (service.startsWith(prefix)) { String originalService = service.substring(service.indexOf(“.”)+1); <b>for</b>(ZuulProperties.ZuulRoute route : zuulProperties.getRoutes().values()) { <b>if</b> (route.getServiceId().equals(originalService)) { </font><font><i>// Change original route to ‘my’ service id</i></font><font> route.setServiceId(service); } } } } } </font>
3.我們差不多完成了!最後一件事 - 如果你直接從其他服務而不是通過閘道器使用REST來呼叫服務,你也必須要處理它。例如,如果您使用Spring的RestTemplate,則必須將其包裝並應用與上面相同的邏輯,這意味著:
- 確定這是服務呼叫還是對實際URL的呼叫(並且不對後者執行任何操作)。例如,服務呼叫將類似於 http://MyService/sth/1
- 通過查詢Eureka檢查服務呼叫是否是對本地執行服務的呼叫,並檢查字首。
- 如果是這樣,請將URL的主機部分更改為本地執行的服務名稱,如前所述。例如 http://myHostName.MyService/sth/1 並且應該呼叫本地服務!
您現在可以在一箇中央環境中快速執行數百個微服務的系統,開發人員可以在本地僅執行一個服務的同時進行開發,節省資源和時間,同時始終自動同步。
要記住兩件事:
- 此處顯示的相關bean應該是@Profile(“development”)的註釋,並且不應該在開發development之外處於active 狀態,以避免混淆。
- 由於我們在工作場所編寫完整堆疊,因此我們始終在本地執行Gateway。如果您只需要後端,則無需在本地執行Gateway,只需使用Swagger或Postman(或類似)來呼叫服務API。如果您確實想要使用應用程式的UI並且不想在 本地執行Gateway ,也可以通過嚮應用程式的URL新增一些資訊(本地服務字首和本地執行的服務的通用名稱)來完成此操作。 。然後,UI可以輕鬆地將REST呼叫地址替換為本地服務名稱(就像我們在伺服器中所做的那樣),並且它們將被路由到開發人員的計算機。