简介
负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。 因为负载均衡是对系统的高可用、 网络压力的缓解和处理能力扩容的重要手段之一。 我们通常所说的负载均衡都指的是服务端负载均衡。Eureka使用的是客户端发现,它的负载均衡是软负载,客户端先请求Eureka服务端获取可以访问的服务清单,然后使用轮询或者随机等负载均衡机制去访问目标。这些都是在客户端完成的不需要服务端,Spring Cloud中的客户端负载均衡就是Ribbon组件。
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,基于Netflix Ribbon实现,通过Spring Cloud封装,可以轻松的将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。
服务端负载均衡都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的节点。而客户端负载均衡和服务端负载均衡最大的不同就是清单的存储位置。在客户端负载均衡中,所有客户端节点都维护着一个自己要访问的服务端清单,而这些服务端清单都来自与服务注册中心。在客户端负载均衡中同样也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需如下两步:
1.服务提供者只需要启动多个实例并注册到一个注册中心或是多个相关联的服务注册中心。
2.服务消费者直接通过调用被@LoadBalance注解修饰过的ResTemplate来实现面向服务的接口调用。
实现可以看下一章《应用通信》中RestTemplate的使用
Ribbon使用软负载均衡有三点:
服务发现:其实就是根据名称获取服务清单
服务选择规则
服务监听
主要组件:
ServerList
IRule
ServerListFilter
流程:通过ServerList获取所有可用的服务列表,通过ServerListFilter过滤掉一部分地址,最后从剩下的地址中使用IRule获取最后地址
RestTemplate、Feign、Zuul都使用到了Ribbon
添加@LoadBalanced注解和LoadBalancedClient使用的就是Ribbon的组件,添加@LoadBalanced Ribbon会通过LoadBalancedClient帮助你基于轮询 或者随机等一些规则去连接目标服务,从而很容易使用ribbon实现自定义的负载均衡算法。
源码分析
第一步获取所有可用的服务列表,unmodifiableList把这个list作为不能修改的List返回去
默认的负载均衡规则为轮询
如果需要修改默认的负载均衡规则,那么直接在客户端配置文件修改,修改成随机
product:ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
product是服务的名称,那么怎么去看有那些规则呢?查询IRule这个接口
从@LoadBalanced注解源码的注释中可以知道,该注解用来给RestTemplate做标记,以使用负载均衡客户端(LoadBalancerClient)来配置它。LoadBalancerClient是SpringCloud中定义的一个接口。
LoadBalancerClient接口的三个方法:
顺着LoadBalancerClient接口所属包,对其内容进行整理
从类的命名上可初步判断 LoadBalancerAutoConfiguration 为实现客户端负载均衡器的自动化配置类。
那么LoadBalancerinterceptor 拦截器是如何将 一个普通的RestTemplate变成客户端负载均衡的:
当一个@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会被LoadBalancerInterceptor类的Intercept函数拦截。由于我们使用RestTemplate时采用服务名作为host,所以直接从HttpRequest的URL对象中通过getHost()就可以拿到服务名,然后调用execute方法去根据服务名来选择实例并发起实际请求。而从LoadBalancerClient接口的实现类中,可以看到execute方法第一步做的就是通过 getServer 根据传入的服务名 serviceId 去获得具体的服务实例
自动化配置
由于Ribbon中定义的每 一 个接口都有多种不同的策略实现,同时这些接口之间又有一定的 依赖关系,这使得第 一 次使用ribbon的开发者很难上手,不知道如何选择具体的实现策略以及如何组织它们 的关系。Spring Cloud Ribbon中的自动化恰能够解决这样的痛点,在引入Spring Cloud Ribbon的 依赖之后, 就能够自动化构建下面这些接口的实现。
1.IClientConfig: Ribbon 的 客户端配置 , 默认采用 com.netfilx.client.config.DefaultClientConfigimpl实现。
2.IRule: Ribbon 的负载均衡策略, 默认采用 com.netflix.loadbalancer.ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
3.IPing: ribbon的实例检查策略,默认采用com.netflix.loadbalancer.NoOpPing实现, 该 检查策略是 一 个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true, 默认认为所有服务实例都是可用的 。
4. ServerList: 服务实例清单的维护机制, 默认采用 com.netflix.loadbalancer.ConfigurationBasedServerList实现。
5. ServerListFilter: 服务实例清单过滤机制, 默认采用org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter实现, 该策略能够优先过滤出与请求调用 方处于同区域的服务实例。
6.ILoadBalancer: 负载均衡器, 默认采用 com.netflix.loadbalancer.ZoneAwareLoadBalancer实现,它具备了区域感知的 能力。
通过自动化配置 的实现,我们可以轻松地实现客户端负载均衡 。 同时 ,针对 一 些个性
化需求,我们也可以方便地替换上面的这些默认实现。 只需在Spring Boot应用中创建对应
的实现实例就能覆盖这些默认的配置实现。 比如下面 的配置内容, 由于创建了PingUrl
实例, 所以默认 的 NoOpPing 就不会被创建。
@Configuration
public class MyRibbonConfiguration {@Beanpublic IPing ribbonPing(IClientConfig config) {return new PingUrl();}
}
与Eureka结合
当在Spring Cloud的应用中同时引入Spring Cloud Ribbon和Spring Cloud Eureka依赖时,会触发Eureka中 实现的对Ribbon的自动化配置。这时 ServerList的维护机制实现将被com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList的实例所覆盖, 该实现会将服务清单列表交给Eureka的服务治理机制来进行维护;IPing的实现将被com.neflix.niws.loadbalancer.NIWSDiscoveryPing 的实例所覆盖,该实现也将实例检查的任务交给了服务治理框架来行维护。 默认情况下,用 于获取实例请求的ServerList接口实现将采用Spring Cloud Eureka中封装的org.springframework.
cloud.netfilx.ribbon.eureka.DomainExtractingServerList,其目的是为了让实例维护策略更加通用, 所以将使用物理元数据来进行负载均衡, 而不 是使用原生的AWSAMI元数据。
在与SpringCloud Eureka结合使用的时候, 我们的配置将会变得更加简单。 不再需要通过类似hello-service.ribbon. listOfServers的参数来指定具体的服务实例清单, 因为Eureka将会为我们维护所有服务的实例清单。 而对于Ribbon的参数配置, 我们依然可以采用之前的两种配置方式来实现, 而指定客户端的配置方式可以直接使用Eureka中的服务名作为来完成针对各个微服务的个性化配置。
此外,由于SpringCloud Ribbon默认实现了区域亲和策略,所以,我们可以通过Eureka实例的元数据配置来实现区域化的实例配置方案。 比如, 可以将处于不同机房的实例配置成不同的区域值, 以作为跨区域的容错机制实现。 而实现的方式非常简单, 只需在服务实例的元数据中增加zone参数来指定自己所在的区域, 比如
eureka.instance.metadataMap.zone=shanghai
在SpringCloud Ribbon与SpringCloud Eureka结合的工程中, 我们也可以通过参数配置的方式来禁用Eureka对Ribbon服务实例的维护实现。 只需在配置文件中加入如下参数,这时我们对于服务实例的维护就又将回归到使用. ribbon.listOfServers参数配置的方式来实现了。
ribbon.eureka.enabled=false
重试机制
由于SpringCloud Eureka实现的服务治理机制强调了CAP原理中的AP, 即可用性与可靠性,它与ZooKeeper这类强调CP(一致性、可靠性)的服务治理框架最大的区别就是,Eureka为了实现更高的服务可用性, 牺牲了一定的一致性, 在极端情况下它宁愿接受故障实例也不要丢掉“健康”实例, 比如, 当服务注册中心的网络发生故障断开时, 由于所有的服务实例无法维持续约心跳, 在不是强调AP的服务治理中 将会把所有服务实例都剔除掉,而Eureka则会因为心跳失败比例超过85%而会触发保护机制,注册中心将会保留此时的所有节点, 以实现服务间依然可以进行互相调用的场景, 即使其中有部分故障节点, 但这样做可以继续保障大多数的服务正常消费。
由于SpringCloud Eureka在可用性与一致性上的取舍, 不论是由于触发了保护机制还是服务剔除的延迟, 引起服务调用到故障实例的时候, 我们还是希望能够增强对这类问题的容错。 所以 我们在实现服务调用的时候通常会加入一些重试机制。 在目前我们使用的Brixton版本中, 对于重试机制的实现需要我们自己来扩展完成。 而从CamdenSR2版本开始,SpringCloud整合了SpringRetry来增强RestTemplate 的重试能力, 对于开发者来说只需通过简单的配置, 原来那些通过RestTemplate 实现的服务访问就会自动根据配置来实现重试策略。
#开启重试,默认关闭
spring.cloud.loadbalancer.retry.enabled=true
#断路器的超时时间需要大于Ribbon的超时时间, 不然不会触发重试。
hystrix.command.default.execution.isolation.thread.timeoutinMilliseconds = l000
#请求连接的超时时间
hello-service.ribbon.ConnectTimeout = 250
#请求处理的超时时间
hello-service.ribbon.ReadTimeout = l000
#对所有操作请求都进行重试
hello-service.ribbon.OkToRetryOnAllOperations = true
#切换实例的重试次数
hello-service.ribbon.MaxAutoRetriesNextServer = 2
#对当前实例的重试次数
hello-service.ribbon.MaxAutoRetries = 1