多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] # 简介 Ribbon客户端启动时,从注册中心获取服务实例列表并初始化, 对获取的服务实例列表根据指定的算法进行过滤 定时更新服务实例列表的状态 Ribbon客户端要访问`http://authority/api/xxx` 根据算法从服务实例列表中选择一个服务实例 使用服务实例真实的IP和端口并替换authority部分 向真正的服务发送请求,并完成请求 ~~~ <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> ~~~ ~~~ @Autowired private LoadBalancerClient loadBalancerClient; ~~~ ~~~ //让负载均衡器找 ServiceInstance serviceInstance = loadBalancerClient.choose("user-service"); String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id; ~~~ 让负载均衡器拦截整个 ~~~ public class ConsumerApplication { @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } ~~~ 直接在上面加注解,然后 ~~~ @Autowired private RestTemplate restTemplate; ~~~ 这样就全局用,都是负载均衡帮你拦截了,这边只能用服务的名称了 ~~~ String url = "http://user-service/user/" + id; String user = restTemplate.getForObject(url, String.class); ~~~ springboot也为我们提供了修改负载均衡规则的配置入口 格式是:`{服务名称}.ribbon.NPLoadBalancerRuleclassName`,值就是IRule的实现类 ~~~ user-service: ribbon: NPLoadBalancerRuleclassName: com.netflix.loadbalancer.RandomRule ~~~ # 主要组件 * ServerList:定义获取服务器列表 * ServerListFilter:对ServerList服务器列表进行二次过滤 * ServerListUpdater: 定义服务更新策略 * IPing: 检查服务列表是否存活 * IRule :根据算法中从服务列表中选取一个要访问的服务 * ILoadBalancer:软件负载平衡器入口,整合以上所有的组件实现负载功能 **Ribbon组件之间的关系简单整理如下:** ServerList和ServerListFilter生成客户端可以访问的服务列表 ServerListUpdater和IPing:根据服务的状态更新服务列表 IRule:服务的选择策略 ILoadBalancer:将以上组件组合到这个类中一起工作 ## ServerList: 获取服务器列表 > 存储服务列表。分为静态和动态。如果是动态的,后台有个线程会定时刷新和过滤服务列表 常用的规则有以下几种: * ConfigurationBasedServerList > 从配置文件中获取所有服务列表,配置举例: ~~~ sample-client: ribbon.listOfServers: www.microsoft.com:80,www.yahoo.com:80,www.google.com:80 ~~~ * DiscoveryEnabledNIWSServerList > 从Eureka Client中获取服务列表 ~~~ myClient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList # the server must register itself with Eureka server with VipAddress "myservice" myClient.ribbon.DeploymentContextBasedVipAddresses=myservice ~~~ * DomainExtractingServerList > 代理类,根据 ServerList 的值实现具体的逻辑 ## IPing : 检查服务列表是否存活 > 在后台运行的一个组件,用于检查服务列表是否存活 这里也有不同的策略,分别有以下策略: * NIWSDiscoveryPing > 不执行真正的ping。如果服务实例在本地的Eureka缓存中存在,则返回true(默认配置)。ribbon-eureka包中提供的类,结合eureka使用时,如果Discovery Client在线,则认为心跳检测通过 * PingUrl > ribbon-httpclient包中提供的类,采用此方式,会使用httpclient调用服务的一个url,如果调用成功则认为本次心跳检测通过,即服务存活可用 * NoOpPing > 什么都不做,永远返回true,即认为服务永远活着 * DummyPing > 默认实现,默认返回true,即认为服务永远活着 * PingConstant > 通过配置参数设置指定服务器存活状态 ## ServerListFilter: 对ServerList服务器列表进行二次过滤 > 该接口允许过滤配置或动态获取的具有所需特性的服务器列表。ServerListFilter 是 DynamicServerListLoadBalancer 用于过滤从 ServerList 实现返回的服务器的组件。 > 常用 ServerListFilter 实现有以下几种: * ZoneAffinityServerListFilter > 过滤掉所有的不和客户端在相同zone的服务,如果和客户端相同的zone不存在,才不过滤不同zone有服务。 ~~~ myclient.ribbon.EnableZoneAffinity=true ~~~ * ServerListSubsetFilter > ZoneAffinityServerListFilter的子类。此过滤器确保客户端仅看到由ServerList实现返回的整个服务器的固定子集。 它还可以定期用新服务器替代可用性差的子集中的服务器 ~~~ myClient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList # 指定要负载均衡服务实例的服务应用名称 myClient.ribbon.DeploymentContextBasedVipAddresses=myservice myClient.ribbon.NIWSServerListFilterClassName=com.netflix.loadbalancer.ServerListSubsetFilter # 指定返回服务器子集的数量,默认20 myClient.ribbon.ServerListSubsetFilter.size=5 ~~~ * ZonePreferenceServerListFilter > ZoneAffinityServerListFilter的子类。和ZoneAffinityServerListFilter相似,但是比较的zone是发布环境里面的zone。根据消费者配置预设的区域Zone来进行过滤(默认配置). 过滤掉所有和客户端环境里的配置的zone的不同的服务,如果和客户端相同的zone不存在,才不进行过滤。 ## ServerListUpdater : 定义服务更新策略 > 被DynamicServerListLoadBalancer用于动态的更新服务列表。 * PollingServerListUpdater > 默认的实现策略。此对象会启动一个定时线程池,定时执行更新策略 * EurekaNotificationServerListUpdater > 当收到缓存刷新的通知,会更新服务列表,由Eureka的事件监听来驱动服务列表的更新操作 * * * ## IClientConfig > 定义各种配置信息,用来初始化ribbon客户端和负载均衡器 > 常用IClientConfig实现有以下几种: * DefaultClientConfigImpl > IClientConfig的默认实现,配置文件里的部分值为ribbon ## IRule :根据算法中从服务列表中选取一个要访问的服务 一共有7种负载均衡策略: ![](https://img.kancloud.cn/14/41/1441839138e44fcc95972f1ddb22556b_1009x967.png) * RandomRule:随机,使用Random对象从服务列表中随机选择一个服务 * RoundRobinRule:轮训策略。默认策略,同时也是更高级rules的回退策略 * RetryRule: 轮询 + 重试 先使用RoundRobinRule进行服务实例选择,如果选择服务实例失败,则在指定时间不断进行重试直至找到服务或超时 * WeightedResponseTimeRule: 优先选择响应时间快 此策略会根据每个实例的平均响应时间,计算出每个服务的权重,响应时间越快,服务权重越重、被选中的概率越高。此类有个DynamicServerWeightTask的定时任务,默认情况下每隔30秒会计算一次各个服务实例的权重。 刚启动时,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到WeightedResponseTimeRule。 * BestAvailableRule: 优先选择并发请求最小的 先过滤到断路器处于打开的服务,然后选择并发请求最小的服务实例来使用。 刚启动时,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到BestAvailableRule。 * PredicateBasedRule 抽象类。 PredicateBasedRule是ClientConfigEnabledRoundRobinRule的一个子类,它先通过内部定义的一个过滤器过滤出一部分服务实例清单,然后再采用轮询的方式从过滤出来的结果中选取一个服务实例 * AvailabilityFilteringRule: (默认实现) 这个负载均衡器规则,会先过滤掉以下服务: a. 由于多次访问故障而处于断路器打开的服务 b. 并发的连接数量超过阈值 然后对剩余的服务列表按照RoundRobinRule策略进行访问 如果RestClient最近3次连接服务实例都失败,则对应的服务的断路器打开。断路器打开的状态默认会持续30s,然后再关闭。如果再次连接又失败,则断路器又打开,并且等待的时间随着连续失败的次数,成指数值增加,但是等待的时间不能超过最长的等待时间 * ZoneAvoidanceRule 根据以下的规则过滤服务: a. 如果一个ZONE不可用,则丢弃这个zone里的所有服务实例 b. 过滤以下服务实例:”由于多次访问故障而处于断路器打开的服务”和”并发的连接数量超过阈值”然后再使用轮询从过滤后的服务列表中选择一个服务 **配置举例:** 服务名开头 ~~~ users: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule ~~~ ## ILoadBalancer:将以上组件组合到这个类中一起工作 > 定义软件负载平衡器操作的接口。动态更新一组服务列表及根据指定算法从现有服务器列表中选择一个服务。 * DynamicServerListLoadBalancer > DynamicServerListLoadBalancer组合Rule、IPing、ServerList、ServerListFilter、ServerListUpdater 实现类,实现动态更新和过滤更新服务列表.是BaseLoadBalancer的一个子类,对基础负载均衡器的功能做了进一步的扩展。增加了服务实例列表动态更新的功能,同时增加对服务实例列表过滤的功能,此类内部依靠DomainExtractingServerList从EurekaClient从注册中心获取服务实例列表,将状态为UP的服务实例组成新的服务列表,使用ZoneAffinityServerListFilter再对这个列表进行过滤。此类内部默认使用PollingServerListUpdater对服务实例列表进行定时更新,保证服务的有效性. * ZoneAwareLoadBalancer > 默认,这是DynamicServerListLoadBalancer的子类,主要加入zone的因素。统计每个zone的平均请求的情况,保证从所有zone选取对当前客户端服务最好的服务组列表.DynamicServerListLoadBalancer默认使用轮询策略,但是此策略在进行跨区域调用时,可能会产生高延迟。此类使用ZoneStats存储每个Zone的状态和平均请求情况,当一个zone的平均请求达到阈值或请求超时的比例达到阈值或zone不可用,则将该zone的服务实例中删除。此类使用AvailabilityFilteringRule选择一个服务实例。 * NoOpLoadBalancer > 什么都没做 * BaseLoadBalancer > 负载均衡器的基本实现。 > 此类内部维护一个存储所有服务实例列表和一个当前活着的服务实例列表。默认使用轮询策略选择一个服务实例做为请求对象。定义一个定时器,根据IPingStrategy定时轮询ping服务实例,用于判断服务列表是否活着。默认的ping策略为SerialPingStrategy ## Springcloud中的以上组件的默认配置 ~~~ # 负载均衡类,默认为om.netflix.loadbalancer.ZoneAwareLoadBalance NFLoadBalancerClassName=ZoneAwareLoadBalancer # 负载均衡规则类,默认为com.netflix.loadbalancer.AvailabilityFilteringRule NFLoadBalancerRuleClassName=AvailabilityFilteringRule # 心跳检测类,ribbon默认为com.netflix.loadbalancer.DummyPing,结合eureka使用时默认值为NIWSDiscoveryPing NFLoadBalancerPingClassName=NIWSDiscoveryPing # 服务列表类,ribbon默认为com.netflix.loadbalancer.ConfigurationBasedServerList,结合eureka使用时默认值为com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList NIWSServerListClassName=DiscoveryEnabledNIWSServerList # 服务过滤类,ribbon默认为com.netflix.loadbalancer.ZoneAffinityServerListFilter。结合spring cloud eureka使用时默认为org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter NIWSServerListFilterClassName=ZonePreferenceServerListFilter ~~~ # 属性配置 配置的位置: Ribbon属性可以定义在属性文件中,也可以定义在系统变量(system properties)中 属性格式: ~~~ <clientName>.<nameSpace>.<propertyName>=<value> ~~~ 说明: ~~~ clientName :ribbon的客户端的名称。如果此值为没有配置,则此条属性会作用到所有的客户端 nameSpace:ribbon的命令空间,默认值为ribbon,一般不建议修改 propertyName:属性命令。所有的可用的属性都在com.netflix.client.conf.CommonClientConfigKey类中定义。若属性没有配置,则会使用DefaultClientConfigImpl类中的默认配置 ~~~ **配置文件demo** 除了第一个是配置为全局属性外,其他的属性都是为一个名称为”sample-client”的ribbon客户端配置的属性 ~~~ # 设置全局默认的ribbon的连接超时 ribbon.ConnectionTimeout=500 # 设置全局默认的ribbon的读超时 ribbon.ReadTimeout=1000 # 同一服务器上重试的最大次数(不包括第一次尝试) sample-client.ribbon.MaxAutoRetries=1 # 要重试的下一个服务器的最大数量(不包括第一个服务器) sample-client.ribbon.MaxAutoRetriesNextServer=1 # 是否可以为此客户端重试所有操作 sample-client.ribbon.OkToRetryOnAllOperations=true # 从源刷新服务器列表的时间间隔 sample-client.ribbon.ServerListRefreshInterval=2000 # Apache HttpClient使用的连接超时 sample-client.ribbon.ConnectTimeout=3000 # 读取Apache HttpClient使用的超时 sample-client.ribbon.ReadTimeout=3000 # 服务器的初始列表,可以在运行时通过Archaius动态属性进行更改 sample-client.ribbon.listOfServers=www.microsoft.com:80,www.yahoo.com:80,www.google.com:80 ~~~ # 自定义RibbonClient 如何为服务消费者自定义Ribbon Client? ① 代码自定义RibbonClient 所谓的自定义Ribbon Client的主要作用就是使用自定义配置替代Ribbon默认的负载均衡策略,注意:自定义的Ribbon Client是有针对性的,一般一个自定义的Ribbon Client是对一个服务提供者(包括服务名相同的一系列副本)而言的。自定义了一个Ribbon Client 它所设定的负载均衡策略只对某一特定服务名的服务提供者有效,但不能影响服务消费者与别的服务提供者通信所使用的策略。根据官方文档的意思,推荐在 springboot主程序扫描的包范围之外进行自定义配置类。其实纯代码自定义RibbonClient的话有两种方式: 方式一:在springboot主程序扫描的包外定义配置类,然后为springboot主程序添加@RibbonClient注解引入配置类  ~~~ @Configuration public class TestConfiguration { @Autowired private IClientConfig config; @Bean public IRule ribbonRule(IClientConfig config) { // 自定义为随机规则 return new RandomRule(); } } ~~~ 注意:@RibbonClient注解中的name属性是指服务提供者的服务名(即当前消费者使用自定义配置与其通信的服务提供者的spring.application.name的属性) ~~~ @RibbonClient(name = "microservice-provider-user",configuration = TestConfiguration.class) ~~~ 方式二:在与springboot主程序的同一级目录新建RibbonClient的配置类,但是必须在springboot扫描的包范围内排除掉,方法是自定义注解标识配置类,然后在springboot的添加@ComponentScan根据自定义注解类型过滤掉配置类 自定义注解 ~~~ public @interface ExcludeFromComponentScan { } ~~~ 自定义配置类 ~~~ @Configuration @ExcludeFromComponentScan public class TestConfiguration1 { @Autowired private IClientConfig config; @Bean public IRule ribbonRule(IClientConfig config) { // 自定义为随机规则 return new RandomRule(); } } ~~~ 在springboot主程序上添加注解 ~~~ @RibbonClient(name = "microservice-provider-user",configuration = TestConfiguration1.class) @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = ExcludeFromComponentScan.class)}) ~~~ ②  通过配置文件自定义RibbonClient 官方文档地址:`http://cloud.spring.io/spring-cloud-static/Camden.SR7/#_customizing_the_ribbon_client_using_properties` 意思就是:配置RibbonClient规则是  `<服务名>.ribbon.<类型>=与类型对应的类名(也可以自定义) ` 类型可以为一下几个:  ~~~ NFLoadBalancerClassName: 应该实现 ILoadBalancer接口 NFLoadBalancerRuleClassName: 应该实现 IRule接口 NFLoadBalancerPingClassName: 应该实现 IPing接口 NIWSServerListClassName: 应该实现ServerList接口 NIWSServerListFilterClassName: 应该实现ServerListFilter接口 ~~~ # Ribbon脱离Eureka使用 官方描述地址:`http://cloud.spring.io/spring-cloud-static/Camden.SR7/#spring-cloud-ribbon-without-eureka` Eureka是用于服务发现和服务注册、以及使用服务名来解决服务消费者和服务提供者通信时地址的硬编码问题的。如果Ribbon脱离了Eureka,那么在服务消费者端就无法根据服务名通过心跳机制从EurekaServer端获取对应服务提供者的IP以及端口号。这时就需要在服务消费者端配置对应服务提供者的地址列表,然后Ribbon才能通过配置文件或者自定义的RibbonClient或者默认的配置获取负载均衡的轮询策略进行请求分发。 配置方式: 第一步:检查是否引入了Eureka。如果服务在依赖中添加了spring-cloud-starter-eureka,这种情况下如果想使Ribbon脱离Eureka使用的话就需要将Eureka禁用掉。仅仅需要添加以下配置,如果没有引入Eureka就不需要禁用。  ~~~ ribbon: eureka: enabled: false ~~~ 第二步:配置某服务提供者的地址列表以及均衡策略(默认是轮询) ~~~ <服务提供者名称>: ribbon: listOfServers: localhost:7901,localhost:7902 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule ~~~ 因为我的Demo中引入了Eureka,所以我的配置如下所示: ~~~ ribbon: eureka: enabled: false microservice-provider-user: ribbon: listOfServers: localhost:7901,localhost:7902 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule ~~~