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:password@localhost: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,我將會在下一篇博客中給大家講解。如果看了我的文章后覺得有點幫助的,希望大家多給點點推薦。謝謝大家!寫博客也很耗時間的。謝謝大家的支持!