Ribbon笔记

ribbon是客户端的负载均衡

客户端把所有的服务列表拉取到本地,然后使用策略算法,最终连接那个服务

eureka客户端从eureka服务器获得服务列表,ribbon使用策略获得一个服务,RestTemplate向这个服务发送http请求

 

 

ribbon默认是区域轮寻策略

 

策略

默认实现:

ZoneAvoidanceRule(区域权衡策略):复合判断Server所在区域的性能和Server的可用性,轮询选择服务器。

其他规则:

BestAvailableRule(最低并发策略):会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略。

RoundRobinRule(轮询策略):以简单轮询选择一个服务器。按顺序循环选择一个server。

RandomRule(随机策略):随机选择一个服务器。

AvailabilityFilteringRule(可用过滤策略):会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问。

WeightedResponseTimeRule(响应时间加权策略):据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间。

RetryRule(重试策略):先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务。如多次获取某个服务失败,就不会再次获取该服务。主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时。

 

测试代码:

前提是,启动一个eureka服务端服务,启动两个eureka客户端服务,端口分别为8080、8081,同时,客户端中的两个服务中,都提供getPort方法,返回当前服务的端口号

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MainController {

	@Autowired
	HealthStatusService healthStatusSrv;
	
	@Value("${server.port}")
	private String port;
	
	@GetMapping("getHi")
	public String getHi() {
		return "Hi";
	}
	
	@GetMapping("getPort")
	public String getPort() {
		return "my port: " + port;
	}
	
	@GetMapping("/health")
	public String health(@RequestParam("status") Boolean status) {
		
		healthStatusSrv.setStatus(status);
		return healthStatusSrv.getStatus();
	}
}

 

ribbon测试代码:

EurekaClientApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@Configuration
public class EurekaClientApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaClientApplication.class, args);
	}
	
	@Bean
//	@LoadBalanced // 添加ribbon
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}
	/**
	 * 代码中注入ribbon的负载均衡策略
	 */
//	@Bean
//	public IRule myRule() {
//		return new RoundRobinRule();
//		return new RandomRule();
//	}
} 

RibbonController.java   测试代码

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/ribbon")
public class RibbonController {

	@Autowired
	private DiscoveryClient client;
	
	@Autowired
	private LoadBalancerClient lb;

	@Autowired
	private RestTemplate restTemplate;

	
	/**
	 * ribbon负载均衡
	 * 默认策略、自定义配制策略测试
	 * @return
	 */
	@GetMapping("client1")
	public String client1() {
		// ribbon 完成客户端的负载均衡,过滤掉down了的节点
		ServiceInstance instances = lb.choose("client");
		String url = "http://" + instances.getHost() + ":" + instances.getPort() + "/getPort";
		return restTemplate.getForObject(url, String.class);
	}


	private AtomicInteger atomicInteger = new AtomicInteger();
	
	/**
	 * ribbon负载均衡
	 * 自定义策略测试
	 * @return
	 */
	@GetMapping("client2")
	public String client2() {
		List<ServiceInstance> instances = client.getInstances("client");
		
		// 自定义的算法,随机
//		int nextInt = new Random().nextInt(instances.size());

		// 自定义的算法,轮巡
		int andIncrement = atomicInteger.getAndIncrement();
		int nextInt = andIncrement % instances.size();
		
		ServiceInstance serviceInstance = instances.get(nextInt);
		
		String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/getPort";
		return restTemplate.getForObject(url, String.class);
	}
}

 

配制文件

application.properties

ribbon配制策略

Ribbon脱离Eureka

#续约发送间隔默认30秒,心跳间隔
#eureka.instance.lease-renewal-interval-in-seconds=5
#表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
#eureka.client.registry-fetch-interval-seconds=5
# 续约到期时间(默认90秒)
#eureka.instance.lease-expiration-duration-in-seconds=60

#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://admin:123@euk1.com:7001/eureka/

server.port=8090

spring.application.name=client2

#配制某个服务的ribbon策略,名称为{服务名称}.ribbon.NFLoadBalancerRuleClassName
client.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

#配制所有服务的ribbon策略
#ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

#Ribbon脱离Eureka
#ribbon.eureka.enabled=false
#Ribbon脱离Eureka后的服务列表集合
#ribbon.listOfServers=localhost:8080,localhost:8081

 

关于ribbon策略的代码跟踪

注:此代码跟踪使用Spring Cloud Hoxton版本

<spring-cloud.version>Hoxton.SR11</spring-cloud.version>

 

lb.choose("client")

点进choose方法,找实现类RibbonLoadBalancerClient

@Override
	public ServiceInstance choose(String serviceId) {
		return choose(serviceId, null);
	}

点击choose方法

/**
	 * New: Select a server using a 'key'.
	 * @param serviceId of the service to choose an instance for
	 * @param hint to specify the service instance
	 * @return the selected {@link ServiceInstance}
	 */
	public ServiceInstance choose(String serviceId, Object hint) {
		Server server = getServer(getLoadBalancer(serviceId), hint);
		if (server == null) {
			return null;
		}
		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
	}

此处查找getServer方法

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		if (loadBalancer == null) {
			return null;
		}
		// Use 'default' on a null hint, or just pass it on?
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}

查找 loadBalancer.chooseServer实现方法,选择ZoneAwareLoadBalancer类

 @Override
    public Server chooseServer(Object key) {
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
            }

            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
            }
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        if (server != null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key);
        }
    }

查找super.chooseServer方法

最后进入 BaseLoadBalancer类中

 

从这三个类中,一路找来

 

 

查看rule初始化

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很高兴能够为你提供关于Spring Cloud的笔记。Spring Cloud是一套基于Spring Boot的微服务框架,它提供了一系列开箱即用的工具和组件,帮助开发者快速构建和部署分布式系统。下面是一些关键的笔记要点: 1. 服务注册与发现:Spring Cloud提供了Eureka、Consul和Zookeeper等注册中心,用于服务的注册与发现,实现了服务之间的自动发现和负载均衡。 2. 服务调用:通过Spring Cloud的RestTemplate或Feign客户端,可以轻松实现服务之间的远程调用。Feign还支持声明式的服务调用,简化了代码的编写。 3. 负载均衡:Spring Cloud提供了Ribbon和LoadBalancer等组件,用于实现负载均衡。通过配置负载均衡策略,可以将请求分发到不同的服务实例上。 4. 服务容错:通过Hystrix或Sentinel等组件,可以实现服务的容错和熔断。当某个服务不可用时,可以快速失败或者返回默认值,保证整个系统的稳定性。 5. 配置中心:Spring Cloud Config提供了集中式的配置管理,可以将配置文件集中存储在Git、SVN等版本控制系统中,并实现配置文件的动态刷新。 6. 链路追踪:通过Spring Cloud Sleuth或Zipkin等工具,可以实现分布式系统的链路追踪和监控。可以了解每个请求经过的各个服务节点,便于排查和解决问题。 7. 消息驱动:Spring Cloud Stream和Spring Cloud Bus等组件,提供了消息驱动的方式来实现服务之间的解耦和异步通信。 这只是对Spring Cloud的一些简单介绍,希望能够帮助到你。如果有具体的问题或者需要进一步的资料,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值