SpringCloud--Ribbon 负载均衡(三)

一、Ribbon简介

  Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。

  1. 客户端负载均衡
      负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。
    硬件负载均衡主要通过在服务器节点之间按照专门用于负载均衡的设备,比如F5等;
    而软件负载均衡则是通过在服务器上安装一些用于负载均衡功能或模块等软件来完成请求分发工作,比如Nginx等。不论采用硬件负载均衡还是软件负载均衡,只要是服务端都能以类似下图的架构方式构建起来:
负载均衡架构图

   而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端端清单来自于服务注册中心,比如Eureka服务端。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,默认会创建针对各个服务治理框架的Ribbon自动化整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。
  在实际使用的时候,我们可以通过查看这两个类的实现,以找到它们的配置详情来帮助我们更好地使用它。
  通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
▪️服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
▪️服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
   这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。

文档地址:
https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html

二、Maven依赖

<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>

三、application.properties配置

server.port=8002
eureka.register.port=8761
#服务实例名
#eureka.instance.hostname=goods-service
#服务名,如果没有则为 unknown
spring.application.name=goods-service
eureka.register.host=localhost
eureka.client.serviceUrl.defaultZone=http\://${eureka.register.host}\:${eureka.register.port}/eureka/
#显示IP
eureka.instance.prefer-ip-address=true
# 注2.0 必须为ip-address
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则)
eureka.instance.lease-renewal-interval-in-seconds =3

四、服务调用

  1. Ribbon配置
@Configuration
public class RibbonConfig {
    @Bean   // 初始化 Bean
    @LoadBalanced   // 实现负载均衡
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
  1. 创建控制器
@RestController
@Slf4j
public class GoodsController {
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/getGoods")
    public String getGoods() {
        String message = restTemplate.getForObject("http://USER-SERVICE/getUser", String.class);
        log.info(message);
        return message;
    }
}

五、负载均衡测试

  1. 启动多个服务实例,端口分别为8001 8005 8006
    启动之前先将服务提供,启动设置单例关掉


    去掉单实例选择
启动多个服务提供者
  1. 消费测试


    刷新访问
刷新访问
刷新访问

六、负载均衡器

虽然Spring Cloud 中定义了LoadBalancerClient作为负载均衡器的通用接口,并且针对Ribbon实现了RibbonLoadBalancerClient,但是它在具体体实现客户端负载均衡时,是通过Ribbon的ILoadBalancer接口实现的。

  1. AbstractLoadBalancer 抽象类
    AbstractLoadBalancer是ILoadBalancer接口的抽象实现。在该抽象类中定义了一个关于服务实例的分组枚举类ServerGroup,它包含三种不同类型。
    ALL :所有服务实例。
    SATUS_UP:正常服务的实例。
    SATUS_NOT_UP:停止服务的实例。
    另外,还实现了一个chooseServer()函数,该函数通过调用接口中的chooseServer(Object key)实现,其中参数key为null,表示在选择具体服务实例时忽略key的条件判断。
    最后,还定义了两个抽象函数。
    getServerList(ServerGroup serverGroup):定义了根据分组类型来获取不同的服务实例的列表。
    getLoadBalancerStats对象存储负载均衡器中各个服务实例当前的属性和统计信息。
  2. BaseLoadBalancer 实现类
    BaseLoadBalancer类是Ribbon负载均衡器的基础实现类,在该类中定义了很多关于负载均衡器相关的基础内容。
    定义并维护了两个存储服务实例Server对象的列表。一个用户存储所有服务实例的清单,一个用户存储正常服务的实例清单。
@Monitor(name=PREFIX + "AllServerList",type=DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>());
@Monitor(name=PREFIX + "UpServerList",type=DataSurceType.INFORMATIONAL)
protected volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<Server>());

定义了之前我们提到的用来存储负载均衡器各服务实例属性和统计信息的LoadBalancerStats对象。
定义了检查服务实例是否正常服务的IPing对象,在BaseLoadBalancer中默认为null,需要在构造时注入它的具体实现。
定义了检查服务实例操作的执行策略对象IPingStrategy,在BaseLoadBalancer中默认使用了该类中定义的静态内部类SerialPingStrategy实现。
定义了负载均衡的处理规则IRule对象。从BaseLoadBalancer中chooseServer(Object key)的实现源码,可以知道,负载均衡实际将服务实例选择人物委托给了IRule实例中的choose函数来实现。在这里,默认初始化了RoundRobinRule为IRule的实现对象。RouleRobinRule实现了最基本且常用的线性负载均衡规则。
启动ping任务。
实现了ILoadBalancer接口定义的负载均衡器应具备的一系列操作。

  1. DynamicServerListLoadBalancer
    DynamicServerListLoadBalancer类继承于BaseLoadBalancer类,它是对基础负载均衡器的扩展。在该负载均衡器中,实现了服务清单在运行期的动态更新功能;同时,它还具备了对服务实例清单的过滤功能,也就是说,我们可以通过过滤器来选择性获取一批服务实例清单。

七、负载均衡策略

Ribbon中实现了非常多的选择策略(负载均衡中的服务实例选择策略),其中包括的RoundRobinRule和ZoneAvoidanceRule。

  1. 自动化配置:
    由于Ribbon中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,使得Ribbon开发者很难上手。Spring Cloud Ribbon中的自动化配置能够解决这种问题。就能自动化构架下面这些接口的实现。
    IClientConfig:Ribbon的客户端配置,默认采用com.netflix.client.config.DefaultClientConfigImpl实现。
    IRule:Ribbon的负载均衡策略,默认采用com.netflix.loadbalancer.ZoneAvoidanceRule实,该策略能够在多区域环境下选出最佳区域的实例进行访问。
    IPing:Ribbon的实例检查策略,默认采用com.netflix.loadbalancer.NoOpPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
    ServerList:服务实例清单的维护机制,默认采用com.netflix.loadbalancer.ConfigurationBasedServerList实现。
    ServerListFilter:服务实例清单过滤机制,默认采用org.springframework.cloud.netflix.ribbon.ZonePrefenceServerListFilter实现,该策略能够优先过滤出与请求调用方处于同区域的服务实例。
    ILoadBalancer:负载均衡器,默认采用com.netflix.loadbalancer.ZoneAwareLoadBalancer实现,它具备了区域感知的能力。
    通过自动化配置的实现,可以轻松地实现客户端负载均衡。同时,针对一些个性化需求,也可以方面替换上面的默认实现。只需要在Spring Boot应用中创建对应的实现实例就能覆盖这些默认的配置实现。
    另外,也可以通过使用@RibbonClient注解来实现更细粒度的客户端配置。

com.netflix.client.config.IClientConfig:Ribbon的客户端配置,默认采用com.netflix.client.config.DefaultClientConfigImpl实现。
com.netflix.loadbalancer.IRule:Ribbon的负载均衡策略,默认采用com.netflix.loadbalancer.ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
com.netflix.loadbalancer.IPing:Ribbon的实例检查策略,默认采用com.netflix.loadbalancer.NoOpPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
com.netflix.loadbalancer.ServerList:服务实例清单的维护机制,默认采用com.netflix.loadbalancer.ConfigurationBasedServerList实现。
com.netflix.loadbalancer.ServerListFilter:服务实例清单过滤机制,默认采E用org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。
com.netflix.loadbalancer.ILoadBalancer:负载均衡器,默认采用com.netflix.loadbalancer.ZoneAwareLoadBalancer实现,它具备了区域感知的能力。

  1. Ribbon提供的主要负载均衡策略
    简单轮询负载均衡(RoundRobin)
    以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
    加权响应时间负载均衡 (WeightedResponseTime)
    随机负载均衡 (Random)
    Ribbon自带负载均衡策略比较:
策略名 策略声明 策略描述 实现说明
BestAvailableRule public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule 选择一个最小的并发请求的server 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRule public class AvailabilityFilteringRule extends PredicateBasedRule 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
WeightedResponseTimeRule public class WeightedResponseTimeRule extends RoundRobinRule 根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择server。
RetryRule public class RetryRule extends AbstractLoadBalancerRule 对选定的负载均衡策略机上重试机制。 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRule public class RoundRobinRule extends AbstractLoadBalancerRule roundRobin方式轮询选择server 轮询index,选择index对应位置的server
RandomRule public class RandomRule extends AbstractLoadBalancerRule 随机选择一个server 在index上随机,选择index对应位置的server
ZoneAvoidanceRule public class ZoneAvoidanceRule extends PredicateBasedRule 复合判断server所在区域的性能和server的可用性选择server 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。
  1. 手动配置示例
    通过自动化配置的实现,可以轻松的实现客户端的负载均衡。同时,针对一些个性化需求,我们可以方便的替换上面的这些默认实现,只需要在springboot应用中创建对应的实现实例就能覆盖这些默认的配置实现。
@Configuration
public class MyRibbonConfiguration {

    @Bean
    public IRule ribbonRule(){
        return new RandomRule();
    }
}

这样就会使用P使用了RandomRule实例替代了默认的com.netflix.loadbalancer.ZoneAvoidanceRule。
也可以使用@RibbonClient注解实现更细粒度的客户端配置

八、参数配置

对于Ribbon的参数通常有二种方式:全局配置以及指定客户端配置
全局配置的方式很简单
只需要使用ribbon.<key>=<value>格式进行配置即可。其中,<key>代表了Ribbon客户端配置的参数名,<value>则代表了对应参数的值。比如,我们可以想下面这样配置开启Ribbon的饥饿加载模式:

#开启Ribbon的饥饿加载模式
ribbon.eager-load.enabled=true
# 指定需要饥饿加载的客户端名称、服务名
ribbon.eager-load.clients=goods-service, user-service

通过上面的配置完成之后,我们尝试重启一下服务消费者,这个时候我们会发现,我们没有开始调用服务接口,但是上面初始化负载均衡的日志就已经打印出来了。这就说明我们对ribbon的饥饿加载模块设置已经生效了。
全局配置可以作为默认值进行设置,当指定客户端配置了相应的key的值时,将覆盖全局配置的内容

指定客户端的配置方式
<client>.ribbon.<key>=<value>的格式进行配置.<client>表示服务名,比如没有服务治理框架的时候(如Eureka),我们需要指定实例清单,可以指定服务名来做详细的配置。

# 2.01.5 已取消
user-service.ribbon.listOfServers=localhost:8080,localhost:8081,localhost:8082

九、常见错误:

  1. 找不到服务提供实例
    检查服务提供者的配置:
spring.application.name=user-service
  1. 不能启动多个实例
    1)启动之前先将服务提供,启动设置单例关掉
    2)去掉依赖包
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
</dependency>

十、详解 RestTemplate

RestTemplate定义了36个与REST资源交互的方法,其中的大多数都对应于HTTP的方法。
其实,这里面只有11个独立的方法,其中有十个有三种重载形式,而第十一个则重载了六次,这样一共形成了36个方法。
delete() 在特定的URL上对资源执行HTTP DELETE操作
exchange()
在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中
映射得到的
execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
getForObject() 发送一个HTTP GET请求,返回的请求体将映射为一个对象
postForEntity()
POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得
到的
postForObject() POST 数据到一个URL,返回根据响应体匹配形成的对象
headForHeaders() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头
optionsForAllow() 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息
postForLocation() POST 数据到一个URL,返回新创建资源的URL
put() PUT 资源到特定的URL

RestTemplate 的get方法有以上几个,可以分为两类: getForEntity() 和 getForObject()

  1. getForEntity
    服务提供:

服务消费:

  1. getForObject
    服务提供:

服务消费:

测试:

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/get/balance")
    public String getBalance() {
        ServiceInstance instance = loadBalancerClient.choose("USER-SERVICE");
        log.info(instance.getScheme());
        log.info("主机:", instance.getHost());
        log.info("实例ID:", instance.getInstanceId());
        log.info("服务ID:", instance.getServiceId());
        log.info("端口号:{}", instance.getPort());
        log.info("地址:{}", instance.getUri());
        return instance.getPort()+"";
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容