Spring Cloud Ribbon源码解析

一、什么是Ribbon

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列的完善的配置,如超时,重试等。

通过Load Balancer(LB)获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。

1.1 什么是客户端的负载均衡

进程内的LB,他是一个类库集成到消费端,通过消费端进行获取提供者的地址。

生活中:类似于你去火车站排队进站(有三条通道),只要是正常人,都会排队到人少的队伍中去。

程序中:我们消费端能获取到服务提供者地址列表,然后根据某种策略去获取一个地址进行调用。

 

 1.2 什么是服务端的负载均衡

生活中:类似于你去火车站排队进站的时候,有一个火车站的引导员告诉你说三号通道人少,你去三号通道排队。

程序中:就是你消费者调用的是nginx的地址,由nginx来选择请求的转发(轮询,权重,ip_hash等)

 二、快速整合Ribbon

本文采用nacos进行整合测试,具体源码见gitee链接:Ribbon源码Debug环境

2.1 搭建生产者-订单服务

2.1.1 pom

    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- SpringCloud ailibaba nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

2.1.2 yaml

server:
  port: 3333 #启动端口
spring:
  application:
    name: provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

 2.1.3 启动类

@SpringBootApplication
@EnableDiscoveryClient
public class NacosProviderApplication {
	public static void main(String[] args)
	{
		SpringApplication.run(NacosProviderApplication.class, args);
	}
}

2.1.4 业务类

@RestController
@RequestMapping("/order")
public class OrderController {

    @Value("${server.port}")
    String port;

    @GetMapping("/find/instance")
    public String queryOrderInstanceId() {

        String applicationName = "provider";
        String result = applicationName + "-" +  port;

        System.out.println("**********" + result);

        return result;
    }
}

2.2 搭建消费者-用户服务

2.2.1 pom

    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- SpringCloud alibaba nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

2.2.2 yaml

server:
  port: 5555 #启动端口
spring:
  application:
    name: consumer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

consumer:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    NFLoadBalancerPingClassName: com.netflix.loadbalancer.PingUrl

2.2.3 配置类

@Configuration
public class AppConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

2.2.4 启动类

@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {
	public static void main(String[] args) {
		SpringApplication.run(NacosConsumerApplication.class, args);
	}
}

 2.2.5 业务类

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/find/instance")
    public String queryOrderInstanceId() {
        // provider: 生产者服务名->替换-> 127.0.0.1:3333
        String url = "http://provider/order/find/instance";
        return restTemplate.getForObject(url, String.class);
    }

}

2.3 启动测试

2.3.1 启动Nacos

startup.cmd -m standalone 

 2.3.2 启动生产者

 2.3.3 启动消费者

三、Ribbon内核原理

3.1 Ribbon调用流程

 3.2 Ribbon负载均衡策略

1.RandomRule:随机选择一个Server。

2.RetryRule:对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。

3.RoundRobinRule:轮询选择,轮询index,选择index对应位置的Server。

4.AvailabilityFilteringRule:过滤掉一直连接失败的被标记为circuit tripped的后端Server,

并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。

5.BestAvailableRule:选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。

6. WeightedResponseTimeRule:根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。

7.ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择Server

public interface IRule{

    // 选择一个server
    public Server choose(Object key);
    
    // 设置负载均衡器
    public void setLoadBalancer(ILoadBalancer lb);
    
    // 获取负载均衡器
    public ILoadBalancer getLoadBalancer();    
}

 

 

 3.3 修改默认负载均衡策略

 

 3.4 判断服务存活状态

 

3.5 自定义负载均衡策略

3.5.1 自定义负载算法

参考自定义负载均衡策略

// 自定义的负载均衡算法
public class MyRandomRule extends AbstractLoadBalancerRule {
    
    // total = 0 // 当total==5以后,我们指针才能往下走,
    // index = 0 // 当前对外提供服务的服务器地址,
    // total需要重新置为零,但是已经达到过一个5次,我们的index = 1
    // 分析:我们5次,但是微服务只有8001 8002 8003 三台,OK?
    
    private int total = 0;            // 总共被调用的次数,目前要求每台被调用5次
    
    private int currentIndex = 0;    // 当前提供服务的机器号
    
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;
        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();
            List<Server> allList = lb.getAllServers();
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
            //private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
            //private int currentIndex = 0; // 当前提供服务的机器号
            if (total < 5) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex >= upList.size()) {
                    currentIndex = 0;
                }
            }
            if (server == null) {
                Thread.yield();
                continue;
            }
            if (server.isAlive()) {
                return (server);
            }
            server = null;
            Thread.yield();
        }
        return server;
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

3.5.2 在SpringBoot主程序扫描的包外定义配置类

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule() {
        return new MyRandomRule();
    }
}

 注意:自定义的负载均衡策略不能写在@SpringbootApplication注解的@CompentScan扫描得到的地方,否则自定义的配置类就会被所有的RibbonClients共享。

 3.5.3 启动类配置@RibbonClient

使用@RibbonClient指定负载均衡策略,在SpringBoot主程序添加@RibbonClient引入配置类

3.5.4 测试

3.6 Ribbon相关接口

参考: org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration

lClientConfig: Ribbon的客户端配置,默认采用DefaultClientConfiglmpl实现。

lRule: Ribbon的负载均衡策略,默认采用ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。

IPing: Ribbon的实例检查策略,默认采用DummyPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,

默认认为所有服务实例都是可用的。

ServerList:服务实例清单的维护机制,默认采用ConfigurationBasedServerList实现

ServerListFilter:服务实例清单过滤机制,默认采ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。

lLoadBalancer:负载均衡器,默认采用ZoneAwareLoadBalancer实现,它具备了区域感知的能力。

3.6.1 修改配置

 

四. Ribbon源码分析

processonprocesson流程图

4.1 @RibbonAutoConfiguration配置类

 

4.2 LoadBalancerAutoConfiguration配置类

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    // 将标记有@LoadBalanced注解的restTemplate都封装到这个集合中
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
                    // 用RestTemplateCustomizer定制所有的restTemplate
					customizer.customize(restTemplate);
				}
			}
		});
	}

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
                // 定制RestTemplate,将LoadBalancerIntercepotr封装到RestTemplate的拦截器集合中
                // 被@LoadBalanced注解的restTemplates(装了所有被@LoadBalanced注解的restTemplate)
				restTemplate.setInterceptors(list);
			};
		}

	}
}

4.2.1 LoadBalanced注解测试demo

1.定义User类

public class User {

}

 2.定义Jak类

public class Jak {

    // 模拟LoadBalanced注解,此位置必须要加@LoadBalance注解,否则看不到效果
    @LoadBalance
    @Autowired(required = false)
    private List<User> userList = new ArrayList<User>();

    public List<User> getUserList() {
        return userList;
    }

}

3.自定义LoadBalance注解

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface LoadBalance {
    
}

 4.定义配置类AppConfig

@Configuration
public class AppConfig {

    @Bean
    @LoadBalance
    public User user() {
        return new User();
    }

    @Bean
    public User user2() {
        return new User();
    }

    @Bean
    public Jak jak() {
        return new Jak();
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        Jak jak = (Jak) applicationContext.getBean("jak");
        System.out.println(jak.getClass());
        System.out.println(jak.getUserList().size());
        System.out.println(jak.getUserList());
    }
}

5.测试不加@LoadBalance

 

 6.测试加@LoadBalance

 7. 原理

 4.3 调用逻辑

 

 

 

 

 

4.3.1 获取服务列表逻辑

 

 

 

 

 

 

 

 

 定时任务每隔10s钟判断是否存活

 

 

 

 

 

 

 

 

 

 从nacos获取实例列表

 

 

 

4.3.2 挑选服务逻辑

 

 

 

 

 

视频教程

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值