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: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,我将会在下一篇博客中给大家讲解。如果看了我的文章后觉得有点帮助的,希望大家多给点点推荐。谢谢大家!写博客也很耗时间的。谢谢大家的支持!