上文介绍了服务如何通过Eureka实现注册,以及如何从Eureka获取已经注册的服务列表。那么拿到注册服务列表后, 如何进行服务调用?一个简单的实现是可以从被调用服务的实例列表中选择一个服务实例,通过其hostname(或IP),端口,及API的路径拼接成完整的url,通过http client来完成调用。但生产环境中,为了高性能、高可用等要素,服务的调用一般涉及负载均衡、故障转移、失败重试等实现,因此引入实现这些功能的客户端组件也成为了微服务架构中的必备要素。Spring Cloud中可通过Ribbon与Feign来实现服务间的调用。
本系列文章与示例均基于最新的Spring Cloud Hoxton版编写。
Ribbon是一个可实现负载均衡的Web客户端。我们一般理解的负载均衡是在服务端实现的,如Nginx(但这都是相对的,如果相对后端服务来说,也可以把Nginx当做一个实现了负载均衡的客户端), 而Ribbon是客户端的负载均衡实现。
Ribbon的核心概念是命名的客户端(named client),Spring Cloud会为每个命名客户端创建一个子应用上下文(ApplicationContext),在该上下文中,通过RibbonClientConfiguration创建ILoadBalancer,RestClient,ServerListFilter等Bean。
Spring Cloud Netflix提供的默认的Ribbon bean及说明
Bean类型 | 默认实现类 | 说明 |
---|---|---|
IClientConfig | DefaultClientConfigImpl | Ribbon客户端配置加载实现,加载各实现bean及客户端连接超时、通讯超时等配置 |
IRule | ZoneAvoidanceRule | 基于zone与可用性来过滤服务器的规则实现 |
IPing | DummyPing | 判断服务器是否存活的实现,默认总是返回true |
ServerList
|
ConfigurationBasedServerList | 获取服务器列表的实现,默认基于配置 |
ServerListFilter
|
ZonePreferenceServerListFilter | 服务器过滤实现,默认过滤出与客户端在同一个zone中的服务器列表 |
ILoadBalancer | ZoneAwareLoadBalancer | 负载均衡实现,默认根据zone的请求负载量排除掉负载最高的zone,从剩下的zone中选择一个根据给定的Rule选择其中一个服务器 |
ServerListUpdater | PollingServerListUpdater | 动态的服务器列表更新器 |
Spring Cloud允许我们通过声明一个configuration来对客户端进行自定义,来调整或覆盖上述默认实现,如
@Configuration @RibbonClient(name = "custom", configuration = CustomConfiguration.class) public class TestConfiguration { }
这样,客户端将由RibbonClientConfiguration 与 CustomConfiguration中定义的组件一起组成,且CustomConfiguration 中的组件会覆盖前者。
注意CustomConfiguration 必须是@Configuration 修饰的类,且不能被main application context的 @ComponentScan 扫描,否则会被所有@RibbonClients 共享
如果要为所有Ribbon Clients定制默认配置,则可使用@RibbonClients 注解
@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class) public class RibbonClientDefaultConfigurationTestsConfig { }
也可以通过配置属性来定制Ribbon Client,支持的配置属性
<clientName>.ribbon.NFLoadBalancerClassName: ILoadBalancer接口实现类 <clientName>.ribbon.NFLoadBalancerRuleClassName: IRule接口实现类 <clientName>.ribbon.NFLoadBalancerPingClassName: IPing接口实现类 <clientName>.ribbon.NIWSServerListClassName: ServerList接口实现类 <clientName>.ribbon.NIWSServerListFilterClassName: ServerListFilter接口实现类
比如对于一个服务名称为users的配置
users: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
配置属性的优先级 > configuration指定配置类的优先级 > 默认RibbonClientConfiguration的优先级, 即同样的实现,前者覆盖后者。
当Eureka与Ribbon同时存在时,ribbonServerList会被 DiscoveryEnabledNIWSServerList覆盖,从Eureka来获取server list,同时 NIWSDiscoveryPing也会替换IPing接口,代理Eureka来确定服务器是否处于运行状态。
Ribbon的超时与重试配置
当项目中添加了Spring Retry的依赖,则会启用重试机制。当请求失败时,会再尝试访问当前服务器(次数由MaxAutoRetries配置),如果不行,就换一个服务器进行访问,如果还是不行,再换服务器访问(更换次数由MaxAutoRetriesNextServer配置),如果还是不行,则返回请求失败。
前文提到Ribbon的负载均衡默认实现为ZoneAwareLoadBalancer,那么Ribbon提供的负载均衡策略还有哪些? 罗列如下
可通过如下配置修改Ribbon的负载均衡策略
client-name: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
本文案例演示基于上文搭建的springcloud-eureka 与 springcloud-eureka-client 两个示例项目 ( 源码 ),依次启动两个项目,然后将springcloud-eureka-client项目的端口 server.port改为8081,新开一个springboot运行配置,如图
以8081端口再起一个springcloud-eureka-client的服务实例。这是查看Eureka页面 http://localhost:8761/ , 可以看到hello-service服务注册了两个实例
新建springcloud-ribbon项目 ( 源码 )
pom.xml中引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
编写测试接口, LoadBalanceClient 是Ribbon的API
@RestController public class RibbonTestController { @Autowired private LoadBalancerClient loadBalancer; @GetMapping("ribbon") public String testRibbon(){ ServiceInstance instance = loadBalancer.choose("hello-service"); return String.format("http://%s:%s", instance.getHost(), instance.getPort()); } }
启动springcloud-ribbon, 调用测试接口 http://localhost:8082/ribbon, 可以看到返回结果交替显示 http://CN-201911061714:8080, http://CN-201911061714:8081 (CN-201911061714是我电脑的hostname,你的可能不一样),可见Ribbon实现了客户端的负载均衡。
Ribbon如果对所有请求进行重试,则需要保证接口的幂等性(多次调用产生的结果是一致的)
每一个命名的Ribbon客户端都有一个相应的由Spring cloud维护的子应用上下文,默认是lazy load的(第一次请求客户端时才load),可以通过如下配置更改为启动立即加载
ribbon: eager-load: enabled: true clients: client1, client2, client3
client.ribbon.* 针对单个客户端进行配置,针对所有客户端默认配置,则使用ribbon.*
当结合断路器使用时, 断路器的超时时间要大于Ribbon的超时时间,不然不会触发重试(还没重试就触发断路器打开了)
除了Ribbon,能做负载均衡访问的Web客户端还有@LoadBalance 注解的RestTemplate, 与Feign
本文示例代码
认真生活,快乐分享
欢迎关注微信公众号:空山新雨的技术空间