likes
comments
collection
share

SpringCloud实践系列(二):Ribbon负载均衡

作者站长头像
站长
· 阅读数 19

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第7篇文章,点击查看活动详情

SpringCloud实践系列(一):Nacos注册中心

Nacos: 注册中心,解决服务注册与发现

SpringCloud实践系列(二):Ribbon负载均衡

Ribbon: 客户端的负载均衡器,解决服务集群的负载均衡

SpringCloud实践系列(三):OpenFeign服务调用(待更新)

OpenFeign:声明式的HTTP客户端,服务远程调用

SpringCloud实践系列(四):Nacos配置中心(待更新)

Nacos:配置中心,中心化管理配置文件

SpringCloud实践系列(五):Sentinel流控(待更新)

Sentinel:微服务流量卫兵,以流量为入口,保护微服务,防止出现服务雪崩

SpringCloud实践系列(六):Gateway网关(待更新)

Gateway: 微服务网关,服务集群的入口,路由转发以及负载均衡(结合Sentinel)

SpringCloud实践系列(七):Sleuth链路追踪(待更新)

Sleuth: 链路追踪,链路快速梳理、故障定位等

SpringCloud实践系列(八):Seata分布式事务(待更新)

Seata: 分布式事务解决方案

一、概述

1.1、什么是Ribbon

Ribbon是负载均衡器,用于解决服务集群的负载均衡

1.2、常见负载均衡

  • 集中式LB 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;

  • 进程内LB 将LB逻辑到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

  • Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

1.3、Ribbon工作流程

SpringCloud实践系列(二):Ribbon负载均衡

二、快速使用

  • 导入依赖

    springcloud alibaba对Ribbon做了兼容,无需单独引用

    SpringCloud实践系列(二):Ribbon负载均衡

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.0.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    
  • 启动类加注解

    // 启动类上开启服务注册与发现功能
    @EnableDiscoveryClient 
    
  • 配置文件

    可以不写,直接使用默认的

    ribbon: 
    	eager-load: 
    		enabled: true # 是否立即加载(项目启动是,是否拉取实例列表),默认是false(项目启动不拉取,第一次请求时拉取).
    		clients: # 指定哪些服务需要立即加载,数组形式
    			- cloud-goods
    			- cloud-goods2
    
  • 使用

    在RestTemplate配置文件中,加@LoadBalanced注解

    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.client.ClientHttpRequestFactory;
    import org.springframework.http.client.SimpleClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    
    @Configuration
    public class ApiConfig {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
            return new RestTemplate(factory);
        }
    
        @Bean
        public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setReadTimeout(5000);
            factory.setConnectTimeout(5000);
            return factory;
        }
    }
    

三、源码简单追踪

3.1、源码简单追踪

LoadBalancerAutoConfiguration类中有个ribbonInterceptor方法,该方法调了LoadBalancerInterceptor类

public class LoadBalancerAutoConfiguration {
    ......
    static class LoadBalancerInterceptorConfig {
        ......
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }
        ......
    }    
}

LoadBalancerInterceptor类中有个关键的方法intercept,调用RibbonLoadBalancerClient的execute方法,获取实例列表

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    ......

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
        // 获得url
        URI originalUri = request.getURI();
        // 拿到serviceName
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        // 获取实例列表,实现负载均衡
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
}

RibbonLoadBalancerClient类的execute方法中,获取实例列表,实现负载均衡

public class RibbonLoadBalancerClient implements LoadBalancerClient {
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
        // 获取实例列表(先从本地缓存取,没有则从注册中心取)
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        // 实现负载均衡(选取负载均衡策略)
        Server server = this.getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
            return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
        }
    }
}

3.2、Ribbon核心组件

接口作用默认值
IClientConfig读取配置DefaultClientConfigImpl
IRule负载均衡规则,选择实例ZoneAvoidanceRule
IPing筛选掉ping不通的实例DummyPing
ServerList交给Ribbon的实例列表Ribbon:ConfigurationBasedServerList Spring Cloud Alibaba:NacosServerList
ServerListFilter过滤掉不符合条件的实例ZonePreferenceServerListFilter
ILoadBalanceRibbon的入口ZoneAwareLoadBalance
ServerListUpdater更新交给Ribbon的List的策略PollingServerListUpdater

四、负载均衡策略

Ribbon的负载均衡策略、原理和扩展

4.1、内置的负载均衡策略

Ribbon核心组件IRule: 根据特定算法从服务列表中选取一个需要访问的服务

IRule是一个接口,有7个自带的实现类,可以实现不同的负载均衡算法规则

规则名称特点
AvailabilityFilteringRule过滤掉一直连接失败的被标记为circuit tripped的后端Server,并 过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate 来包含过滤server的逻辑,其实就是检查status里记录的各个server 的运行状态
BestAvailableRule选择一个最小的并发请求的server,逐个考察server, 如果Server被tripped了,则跳过
RandomRule随机选择一个Server
ResponseTimeWeightedRule已废弃,作用同WeightedResponseTimeRule
WeightedResponseTimeRule根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低
RetryRule对选定的负载均衡策略加上重试机制,在一个配置时间段内当 选择Server不成功,则一直尝试使用subRule的方式选择一个 可用的Server
RoundRobinRule轮询选择,轮询index,选择index对应位置的Server
ZoneAvoidanceRule(默认)默认的负载均衡策略,即复合判断Server所在区域的性能和Server的可用性 选择Server,在没有区域的环境下,类似于轮询(RandomRule)【优中选优,再轮询】

4.2、切换ribbon负载均衡策略

  • 编写配置

    在客户端配置

    注意:不能在启动类的同级/子集下配置

    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RoundRobinRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MyRibbonRule {
        @Bean
        public IRule getRule() {
            return new RoundRobinRule();
        }
    }
    
  • 配置启动类

    // 启动类上加该注解即可
    @RibbonClient(name = "cloud-goods", configuration = {MyRibbonRule.class})
    

4.3、自定义策略(使用Nacos权重)

直接在页面上修改权重,是无效的。因为负载均衡中引入了ribbon,没有改ribbon配置,还是使用的ribbon的默认负载均衡策略。

使用nacos的权重,需要自定义负载均衡策略

  • 配置权重

    • 在页面上配置

      SpringCloud实践系列(二):Ribbon负载均衡

    • 在代码中配置

      spring:
        application:
          name: cloud-goods # 服务名
        cloud:
          nacos:
            discovery:
              server-addr: localhost:8848 # 指定nacos-server的地址
              username: nacos
              password: nacos
              weight: 10
      server:
        port: 9001
      
  • 自定义负载均衡策略

    拿到nacos的配置,进行自定义的负载均衡策略

    注意:不能在启动类的同级/子集下配置

    import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
    import com.alibaba.cloud.nacos.ribbon.NacosServer;
    import com.alibaba.nacos.api.exception.NacosException;
    import com.alibaba.nacos.api.naming.NamingService;
    import com.alibaba.nacos.api.naming.pojo.Instance;
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.BaseLoadBalancer;
    import com.netflix.loadbalancer.Server;
    import org.springframework.beans.factory.annotation.Autowired;
    
    
    public class NacosWeightedRule extends AbstractLoadBalancerRule {
        /**
         * NacosDiscoveryProperties内置了基于权重的负载均衡算法
         */
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
    
        /**
         * 读取配置文件并初始化NacosWeightedRule
         * @param iClientConfig
         */
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
        }
    
        /**
         * 实现基于权重的负载均衡算法
         * @param o
         */
        @Override
        public Server choose(Object o) {
            try {
                BaseLoadBalancer loadBalancer = (BaseLoadBalancer)this.getLoadBalancer();
                //想要请求的微服务名称
                String name = loadBalancer.getName();
                //拿到服务发现新的相关的api
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
                //Nacos client自动通过基于权重的负载均衡算法,给我们选择一个实例
                Instance instance = namingService.selectOneHealthyInstance(name);
                return new NacosServer(instance);
            } catch (NacosException e) {
                return null;
            }
        }
    }
    
  • 引用自定义的负载均衡策略

    • 引用配置类

      import com.netflix.loadbalancer.IRule;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class MyRibbonRule {
          @Bean
          public IRule getRule() {
              return new NacosWeightedRule();
          }
      }
      
    • 启动类

      // 启动类上加该注解即可
      @RibbonClient(name = "cloud-goods", configuration = {MyRibbonRule.class})
      
转载自:https://juejin.cn/post/7149053391652519967
评论
请登录