1. 程式人生 > >spring cloud連載第三篇之Spring Cloud Netflix

spring cloud連載第三篇之Spring Cloud Netflix

1. Service Discovery: Eureka Server(服務發現:eureka伺服器)

1.1 依賴

1         <dependency>
2             <groupId>org.springframework.cloud</groupId>
3             <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
4         </dependency>    

1.2 How to Run a Eureka Server(怎樣啟動eureka伺服器)

下面是一個小型的eureka伺服器:

1 @SpringBootApplication
2 @EnableEurekaServer
3 public class Application {
4 
5     public static void main(String[] args) {
6         new SpringApplicationBuilder(Application.class).web(true).run(args);
7     }
8 
9 }

啟動後通過http://localhost:{port}來檢視主頁。

1.3 High Availability, Zones and Regions(高可用,zones和regions)

eureka服務沒有後端儲存系統,但是服務例項需要不停傳送心跳檢測來保持他們的註冊資訊是最新的,所以這些註冊資訊是儲存在記憶體中的。

客戶端也是使用記憶體來快取從伺服器獲取的服務註冊資訊的。(這樣他們就不用每次請求一個服務時都要經過eureka伺服器)。

預設情況下,eureka伺服器也是一個eureka客戶端,並且需要至少一個service URL來定位其他節點。如果你不提供這個URL也可以工作,但是你的日誌會出現很多無用的報錯資訊(它無法註冊自己到叢集節點中)。

這個問題可以用下面介紹的單節點模式來解決^_^。

1.4 Standalone Mode(單節點模式)

兩個快取(客戶端和伺服器)和心跳機制的組合使得單節點的Eureka伺服器對故障具有相當的彈性,只要有某種監視器或彈性執行時(如Cloud Foundry)保持它的活力。

在單節點模式下需要關閉伺服器的客戶端行為(不斷的嘗試連線其他叢集節點),如下(application.yml):

 1 server:
 2   port: 8761
 3 
 4 eureka:
 5   instance:
 6     hostname: localhost
 7   client:
 8     registerWithEureka: false
 9     fetchRegistry: false
10     serviceUrl:
11       defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

注意:其中serviceUrl是指向本身的。

1.5 Peer Awareness(節點感知)

Eureka伺服器可以使用啟動多個例項並且相互註冊的方法來達到高可用。實際上這是預設行為,你需要做的就是新增節點的serviceUrl。如下(application.yml):

 1 ---
 2 spring:
 3   profiles: peer1
 4 eureka:
 5   instance:
 6     hostname: peer1
 7   client:
 8     serviceUrl:
 9       defaultZone: http://peer2/eureka/
10 
11 ---
12 spring:
13   profiles: peer2
14 eureka:
15   instance:
16     hostname: peer2
17   client:
18     serviceUrl:
19       defaultZone: http://peer1/eureka/

在上面的例子中我們可以以不同的host來啟動同一個應用。啟動時指定引數--spring.profiles.active=peer1或者peer2。通過修改作業系統的hosts檔案(windows位置C:\Windows\System32\drivers\etc\hosts,linux位置/etc/hosts)就可以解決hostname問題。這樣我們就可以在一臺主機上測試eureka伺服器的節點感知功能了。

你可以新增多個節點到一個系統中,只要它們至少在一端是彼此相連的。它們在自己內部同步註冊資訊。如果節點在物理上是分離的(在資料中心內部或多個數據中心之間),那麼系統原則上可以在“腦裂”型別的故障中存活。(腦裂其實就是在高可用(HA)系統中,當聯絡2個節點的“心跳線”斷開時,本來為一整體、動作協調的HA系統,就分裂成為2個獨立的個體。由於相互失去了聯絡,都以為是對方出了故障。兩個節點上的HA軟體像“裂腦人”一樣,爭搶“共享資源”、爭起“應用服務”,就會發生嚴重後果——或者共享資源被瓜分、2邊“服務”都起不來了;或者2邊“服務”都起來了,但同時讀寫“共享儲存”,導致資料損壞)。

1.6 When to Prefer IP Address(何時使用ip地址)

在有些情況下,Eureka推薦使用ip地址而不是hostname。通過設定eureka.instance.preferIpAddress=true。

注意:如果java決定不了hostname那麼就會把ip地址傳送給eureka。通過eureka.instance.hostname可以顯式的設定hostname。或者設定環境變數。

1.7 Securing The Eureka Server(保護eureka伺服器)

新增spring-boot-starter-security到你的classpath。預設情況下,當spring security在classpath中時,它要求每個請求都必須含有CSRF token。不過一般Eureka客戶端都沒有CSRF token,所以需要將路徑/eureka/**放開。

如下:

1 @EnableWebSecurity
2 class WebSecurityConfig extends WebSecurityConfigurerAdapter {
3 
4     @Override
5     protected void configure(HttpSecurity http) throws Exception {
6         http.csrf().ignoringAntMatchers("/eureka/**");
7         super.configure(http);
8     }
9 }

2 Service Discovery: Eureka Clients(服務發現:eureka客戶端)

2.1 依賴

1         <dependency>
2             <groupId>org.springframework.cloud</groupId>
3             <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
4         </dependency>  

2.2 Registering with Eureka(通過eureka註冊)

當一個客戶端註冊到Eureka時,它會提供自身的一些元資料(主機,埠,健康狀態指示器url,主頁,和其他一些細節)。Eureka會接受到來自服務例項的心跳資訊。如果心跳在一段時間沒有接收到,則Eureka將會移除對應的例項。

下面是一個簡單的eureka客戶端:

 1 @SpringBootApplication
 2 @RestController
 3 public class Application {
 4 
 5     @RequestMapping("/")
 6     public String home() {
 7         return "Hello world";
 8     }
 9 
10     public static void main(String[] args) {
11         new SpringApplicationBuilder(Application.class).web(true).run(args);
12     }
13 
14 }

application.yml:

1 eureka:
2   client:
3     serviceUrl:
4       defaultZone: http://localhost:8761/eureka/

在classpath上存在spring-cloud-starter-netflix-eureka-client會是你的app變成eureka“例項”(將自身註冊到eureka)和eureka“客戶端”(從eureka獲取其他服務的資訊)。其中例項的行為可以使用eureka.instance.*來設定。

一般來說預設值就夠用了,你只需要設定一下spring.application.name,因為它是eureka預設的service ID。

如果你只對外提供服務而不需要從eureka獲取其他服務資訊,那麼你可以關閉對應的“客戶端”行為。使用eureka.client.enabled=false。

2.3 Authenticating with the Eureka Server(使用eureka伺服器進行身份驗證)

如果eureka.client.serviceUrl.defaultZone的URL中含有使用者憑證資訊(http://user:[email protected]:8761/eureka),HTTP基本驗證將會自動新增到eureka客戶端中。但是如果有更復雜的需求的話,需要定義一個

DiscoveryClientOptionalArgs型別的bean,並且在其中注入一個ClientFilter例項,它會應用到客戶端到服務端的所有請求上。

2.4 Using the EurekaClient(使用EurekaClient)

只要你的app是一個eureka客戶端,你就可以從eureka伺服器獲取其他服務的資訊。其中一種方法就是使用原生的com.netflix.discovery.EurekaClient,如下:

1 @Autowired
2 private EurekaClient discoveryClient;
3 
4 public String serviceUrl() {
5     InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false);
6     return instance.getHomePageUrl();
7 }

注意:別在@PostConstruct或者@Scheduled方法裡使用EurekaClient,或者任何其他ApplicationContext還沒啟動的地方。

2.5 Alternatives to the Native Netflix EurekaClient(原生Netflix EurekaClient的替代方法)

可以使用org.springframework.cloud.client.discovery.DiscoveryClient,提供了一些簡單的API來獲取其他註冊服務資訊。例如:

 1 @Autowired
 2 private DiscoveryClient discoveryClient;
 3 
 4 public String serviceUrl() {
 5     List<ServiceInstance> list = discoveryClient.getInstances("STORES");
 6     if (list != null && list.size() > 0 ) {
 7         return list.get(0).getUri();
 8     }
 9     return null;
10 }

2.6 Why Is It so Slow to Register a Service?(為啥註冊一個服務這麼慢?)

註冊成為一個例項需要傳送週期性的心跳到註冊中心,預設值為30秒。直到例項,伺服器,客戶端在他們本地的快取中有同樣的元資料(可能需要3次心跳)一個服務才能被其他客戶端發現。

可以通過設定eureka.instance.leaseRenewalIntervalInSeconds來改變週期。把它設定成一個小於30的值會使客戶端更快的連線到其他服務上。但是在生產環境中最好使用預設值,因為伺服器內部的計算假設了租賃續期。

2.7 Zones

如果你把客戶端部署到不同的區域,你可以通過設定來使客戶端優先使用同一區域中的服務。

第一步,確保你的eureka伺服器部署到了各個區域,並且是彼此的節點。

第二步,你需要告訴eureka伺服器你的服務處於哪個區域,使用metadataMap來設定。下面的例子中service 1被部署到了zone1和zone2.

Service 1 in Zone 1:

1 eureka.instance.metadataMap.zone = zone1
2 eureka.client.preferSameZoneEureka = true

Service 1 in Zone 2:

eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true

3. Circuit Breaker: Hystrix Clients(斷路器:Hystrix客戶端)

3.1 依賴

1         <dependency>
2             <groupId>org.springframework.cloud</groupId>
3             <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
4         </dependency>

下面是一個簡單的含有Hystrix斷路器的eureka伺服器:

 1 @SpringBootApplication
 2 @EnableCircuitBreaker
 3 public class Application {
 4 
 5     public static void main(String[] args) {
 6         new SpringApplicationBuilder(Application.class).web(true).run(args);
 7     }
 8 
 9 }
10 
11 @Component
12 public class StoreIntegration {
13 
14     @HystrixCommand(fallbackMethod = "defaultStores")
15     public Object getStores(Map<String, Object> parameters) {
16         //do stuff that might fail
17     }
18 
19     public Object defaultStores(Map<String, Object> parameters) {
20         return /* something useful */;
21     }
22 }

3.2 Propagating the Security Context or Using Spring Scopes(傳播安全上下文或者使用spring scopes)

如果你想傳遞執行緒本地上下文到@HystrixCommand中,預設配置是不行的,因為它是線上程池中執行命令的。你可以通過配置檔案或者直接在註解中配置來使Hystrix使用呼叫者的執行緒。即使用一個不同的“Isolation Strategy”。

下面的例子展示瞭如果在註解中設定執行緒:

1 @HystrixCommand(fallbackMethod = "stubMyService",
2     commandProperties = {
3       @HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
4     }
5 )
6 ...

你也可以選擇使用hystrix.shareSecurityContext=true。這麼做將會自動配置一個Hystrix併發性策略外掛鉤子來將SecurityContext從主執行緒中傳遞到Hystrix執行緒中。Hystrix不允許多個併發性策略被註冊,所以你可以配一個你自己的

HystrixConcurrencyStrategy bean。Spring Cloud會在spring上下文中查詢你的實現並且將它包裝到它自己的外掛中去。

4. Hystrix Timeouts And Ribbon Clients(Hystrix超時和Ribbon客戶端)

當使用包含Ribbon客戶端的Hystrix命令時,你要保證Hystrix的超時時間比Ribbon的超時時間長,包含潛在的重試機制。舉個例子:Ribbon的超時時間是1秒,而且會在超時時重試3次,那麼你的Hystrix的超時時間應該設定大於3秒。

4.1 Hystrix Dashboard依賴

1         <dependency>
2             <groupId>org.springframework.cloud</groupId>
3             <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
4         </dependency>

在spring boot的啟動類上新增@EnableHystrixDashboard註解。然後訪問/hystrix,並且指向一個Hystrix客戶端的/hystrix.stream端點。

4.2 Turbine

檢視一個單獨例項的Hystrix的資料對於整個系統整體而言通常意義不大。Turbine可以將所有相關的/hystrix.stream關聯到/turbine.stream來供Hystrix Dashboard使用。每個單獨的應用例項可以使用eureka來定位。

啟動Turbine需要在啟動類上添加註解@EnableTurbine,不同的是turbine.instanceUrlSuffix不需要提供埠,因為它會自動新增,除非設定了turbine.instanceInsertPort=false。

注意:預設情況下,Turbine查詢/hystrix.stream是通過註冊例項在eureka中的hostName和port資訊並且在後面新增/hystrix.stream。如果註冊例項的元資料中存在management.port屬性的話,那麼/hystrix.stream端點將會使用

它代替之前的port。預設,元資料中的management.port和配置屬性的management.port是相同的。可以通過下面的設定來覆蓋:

1 eureka:
2   instance:
3     metadata-map:
4       management.port: ${management.port:8081}

turbine.appConfig配置是一個eureka serviceId的列表,Turbine用它來查詢例項。Turbine stream在Hystrix Dashboard中使用的url如下:

1 http://my.turbine.server:8080/turbine.stream?cluster=CLUSTERNAME

其中cluster引數可以省略,如果名稱是預設的話。cluster引數必須匹配turbine.aggregator.clusterConfig。從Eureka返回的值都是大寫的。

1 turbine:
2   aggregator:
3     clusterConfig: CUSTOMERS
4   appConfig: customers

你可以通過定義一個TurbineClustersProvider的bean來定製化cluster名稱。

cluster name還可以使用SPEL在turbine.clusterNameExpression配置。cluster name預設就是例項在eureka註冊的serviceId。下面使用一個不一樣的例子:

1 turbine:
2   aggregator:
3     clusterConfig: SYSTEM,USER
4   appConfig: customers,stores,ui,admin
5   clusterNameExpression: metadata['cluster']

在上面的例子中,四個服務的cluster name是從他們在註冊中心例項資訊的metadata map中獲取的。

如果將所有服務都歸納到一個cluster name下,則可以使用“default”,如下(注意單引號和雙引號):

1 turbine:
2   appConfig: customers,stores
3   clusterNameExpression: "'default'"

4.2.1 Clusters Endpoint(Clusters端點)

通過/clusters路徑可以檢視當前Turbine存在哪些clusters:

 1 [
 2   {
 3     "name": "RACES",
 4     "link": "http://localhost:8383/turbine.stream?cluster=RACES"
 5   },
 6   {
 7     "name": "WEB",
 8     "link": "http://localhost:8383/turbine.stream?cluster=WEB"
 9   }
10 ]

如果要禁用這個端點可以使用turbine.endpoints.clusters.enabled=false。

5. Client Side Load Balancer: Ribbon(客戶端負載均衡器:Ribbon)

Ribbon是一個端客戶端的負載均衡器,它給了HTTP或者TCP客戶端行為的很多控制。Feign用的就是Ribbon。如果你使用了Feign那麼本節也值得一看。

5.1 How to Include Ribbon(依賴)

1         <dependency>
2             <groupId>org.springframework.cloud</groupId>
3             <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
4         </dependency>

5.2 Customizing the Ribbon Client(自定義Ribbon客戶端)

可以通過外部配置<client>.ribbon.*來配置一些Ribbon客戶端屬性。Spring Cloud也允許你使用@RibbonClient來申明額外的配置來完全掌控Ribbon客戶端。如下:

1 @Configuration
2 @RibbonClient(name = "custom", configuration = CustomConfiguration.class)
3 public class TestConfiguration {
4 }

在上面這個例子中,客戶端是由RibbonClientConfiguration中的元件和CustomConfiguration中的所有元件組成(後者會覆蓋前者)。

注意:CustomConfiguration必須是一個@Configuration類,並且要保證它不在@ComponentScan掃描範圍內。否則它就會被所有Ribbon客戶端共享。

下表是Spring Cloud Netflix為Ribbon提供的預設值:

Bean Type Bean Name Class Name
IClientConfig ribbonClientConfig DefaultClientConfigImpl
IRule ribbonRule ZoneAvoidanceRule
IPing ribbonPing DummyPing
ServerList<Server> ribbonServerList ConfigurationBasedServerList
ServerListFilter<Server> ribbonServerListFilter ZonePreferenceServerListFilter
ILoadBalancer ribbonLoadBalancer ZoneAwareLoadBalancer
ServerListUpdater ribbonServerListUpdater PollingServerListUpdater

建立上面型別的bean並且將它放在@RibbonClient配置中可以覆蓋預設的配置。如下:

 1 @Configuration
 2 protected static class FooConfiguration {
 3     @Bean
 4     public ZonePreferenceServerListFilter serverListFilter() {
 5         ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
 6         filter.setZone("myTestZone");
 7         return filter;
 8     }
 9 
10     @Bean
11     public IPing ribbonPing() {
12         return new PingUrl();
13     }
14 }

5.3 Customizing the Default for All Ribbon Clients(為所有Ribbon客戶端自定義預設配置)

使用@RibbonClients註解並且註冊一個預設配置。如下:

 1 @RibbonClients(defaultConfiguration = DefaultRibbonConfig.class)
 2 public class RibbonClientDefaultConfigurationTestsConfig {
 3 
 4     public static class BazServiceList extends ConfigurationBasedServerList {
 5         public BazServiceList(IClientConfig config) {
 6             super.initWithNiwsConfig(config);
 7         }
 8     }
 9 }
10 
11 @Configuration
12 class DefaultRibbonConfig {
13 
14     @Bean
15     public IRule ribbonRule() {
16         return new BestAvailableRule();
17     }
18 
19     @Bean
20     public IPing ribbonPing() {
21         return new PingUrl();
22     }
23 
24     @Bean
25     public ServerList<Server> ribbonServerList(IClientConfig config) {
26         return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config);
27     }
28 
29     @Bean
30     public ServerListSubsetFilter serverListFilter() {
31         ServerListSubsetFilter filter = new ServerListSubsetFilter();
32         return filter;
33     }
34 
35 }

5.4 Customizing the Ribbon Client by Setting Properties(使用配置檔案自定義Ribbon客戶端)

從版本1.2.0後Spring Cloud Netflix允許通過屬性設定來自定義Ribbon客戶端。下面是支援的屬性設定:

1 <clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer
2 <clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule
3 <clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing
4 <clientName>.ribbon.NIWSServerListClassName: Should implement ServerList
5 <clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter

注意:在上面這些屬性中定義的類比@RibbonClient(configuration=MyRibbonConfig.class和Spring Cloud Netflix提供的預設值具有更高的優先順序。

例如(application.yml):

1 users:
2   ribbon:
3     NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
4     NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

5.5 Using Ribbon with Eureka(在Eureka中使用Ribbon)

當Eureka和Ribbon一起使用時,ribbonServerList會被DiscoveryEnabledNIWSServerList的擴充套件覆蓋,NIWSDiscoveryPing覆蓋IPing介面授權Eureka來決定服務是否可用。ServerList預設是DomainExtractingServerList。

它的目的是在不使用AWS AMI 元資料的情況下,使負載均衡器可以使用元資料。預設情況下,服務列表是由“zone” 資訊組成的(eureka.instance.metadataMap.zone)。如果“zone”不存在並且approximateZoneFromHostname

被設定了,那麼將會使用服務主機的域名作為zone的代理。只要當zone資訊存在時,就可以在ServerListFilter使用它。

注意:設定客戶端區域的傳統“archaius”方法是通過一個名為“@zone”的配置屬性。如果它是可用的,Spring Cloud優先使用它而不是所有其他設定(注意,鍵必須在YAML配置中引用)。

5.6 Example: How to Use Ribbon Without Eureka(在沒有eureka的情況下使用ribbon)

Eureka是一個抽象遠端服務發現的簡便方法,這樣你就不用在客戶端中硬編碼他們的URL了。但是如果你不使用Eureka, Ribbon 和 Feign也是可以的。

假設你為"stores"聲明瞭一個@RibbonClient配置類,並且沒有使用Eureka。你可以使用如下配置(application.yml):

1 stores:
2   ribbon:
3     listOfServers: example.com,google.com

5.7 Example: Disable Eureka Use in Ribbon(在Ribbon中禁用Eureka)

顯示禁用Eureka(application.yml):

1 ribbon:
2   eureka:
3    enabled: false

5.8 Using the Ribbon API Directly(直接使用Ribbon API)

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
    }
}

5.9 Caching of Ribbon Configuration(Ribbon配置快取)

每個Ribbon客戶端都對應有一個spring cloud的子上下文,這個應用上下文是在第一次請求到Ribbon客戶端時才載入的。這種懶載入行為可以通過下面的例子來改變成在啟動時載入(application.yml):

ribbon:
  eager-load:
    enabled: true
    clients: client1, client2, client3

5.10 How to Configure Hystrix Thread Pools(如何配置Hystrix執行緒池)

如果將zuul.ribbonIsolationStrategy設定為THREAD,Hystrix的執行緒隔離策略將應用於所有路由。在這種情況下,HystrixThreadPoolKey預設被設定為RibbonCommand。這意味著,所有路由的HystrixCommands會在

同一個Hystrix執行緒池中執行。可以通過如下配置來改變這種行為(application.yml):

1 zuul:
2   threadPool:
3     useSeparateThreadPools: true

上面的例子將會使每個路由的HystrixCommands在不同的執行緒池中執行。

這種情況下,預設HystrixThreadPoolKey和每個路由的service ID是一樣的。設定zuul.threadPool.threadPoolKeyPrefix可以為HystrixThreadPoolKey新增一個字首。如下(application.yml):

1 zuul:
2   threadPool:
3     useSeparateThreadPools: true
4     threadPoolKeyPrefix: zuulgw

5.11 How to Provide a Key to Ribbon’s IRule(如何給Ribbon IRule提供一個鍵)

如果你想要實現自定義的IRule來處理一些特別的路由,傳遞一些資訊到IRule的choose方法。

com.netflix.loadbalancer.IRule.java

1 public interface IRule{
2     public Server choose(Object key);
3          :

可以提供一些資訊給IRule的實現來選擇一臺目標伺服器。如下:

1 RequestContext.getCurrentContext()
2               .set(FilterConstants.LOAD_BALANCER_KEY, "canary-test");

如果你向RequestContext中設定鍵為FilterConstants.LOAD_BALANCER_KEY的值,那麼它會傳遞給IRule的choose方法。上面例子中的程式碼必須在RibbonRoutingFilter之前執行。Zuul的pre filter是最好的地方。

在pre filte中你可以通過RequestContext獲取HTTP頭部資訊和查詢引數資訊,因此它可以來決定傳遞給Ribbon的LOAD_BALANCER_KEY值。如果你沒有在RequestContext中設定LOAD_BALANCER_KEY的值,那麼

null將會傳遞給choose方法。

關於Spring Cloud Netflix還剩下一個路由閘道器zuul,我將會在下一篇部落格中給大家講解。如果看了我的文章後覺得有點幫助的,希望大家多給點點推薦。謝謝大家!寫部落格也很耗時間的。謝謝大家的支援!