客户端负载均衡与Ribbon

客户端负载均衡与Ribbon

一、什么是客户端负载均衡

  • 负载均衡

首先,用一个图来说明什么是负载均衡。

在这里插入图片描述

以上就是负载均衡(服务端负载均衡或者是一台负载均衡服务器)的图示,我们使用的软件通常称为客户端,客户端发送请求到服务器端,通过添加一个负载均衡器,就能实现客户端发送的请求能够均衡地下发给不同的服务器,从而减少单台服务器的压力。

  • 客户端负载均衡

那么,客户端负载均衡基于负载均衡的原理,实现了与服务器端完全不同的实现思路。它是基于客户端的,因此,负载均衡策略也是由客户端设定的,无需依赖负载均衡器(可以理解为一台专门用于负载均衡的服务器),更加灵活,且减少了数据的传播时延,使得响应延时更低。

以下是客户端负载均衡的图示:

在这里插入图片描述

客户端负载均衡的分析:

  • 维护一个可用服务器的列表,称为 availabe server list。
  • 根据既定的负载均衡策略,实现对应的均衡请求。
  • 优点
    • 降低网络时延
    • 无需依赖额外的负载均衡服务器
  • 缺点
    • 客户端的开发难度相对较高
    • 会占用小部分客户端的资源

那么,客户端负载均衡要解决的问题是什么呢?

  • 如何获取可用服务列表

解决方案如下:一般使用负载均衡的系统都是庞大的分布式系统,因此,依赖于服务治理中心。如下:

在这里插入图片描述

  • 如何更新可用服务列表以及实时检测可用服务的状态

客户端发送Ping检测或者心跳检测,从而获取服务端的状态。

二、Ribbon客户端负载均衡的使用

讲完了客户端负载均衡,接下来要讲如何使用Ribbon客户端负载均衡了。我将基于Spring Cloud项目讲解如何使用,如果读者对微服务不感兴趣,那么,可以跳过。

使用方式:

  • 添加Pom依赖
   <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
   </dependency>
  • 注册负载均衡Bean
//在启动类中注册一个负载均衡的bean
 @Bean
 @LoadBalanced
  RestTemplate restTemplate() {
     return new RestTemplate();
  }
  • 基于RestTemplate发送请求
@Service
public class ConsumerService {
    /**
     * 注入负载均衡的rest模板
     */
    @Autowired
    private RestTemplate restTemplate;
    public String hiService() {
      //http://provider/zone?中,provider是可用服务的名称,这个provider可以是一个服务提供者集群(这时候就会实现负载均衡请求)
        return restTemplate.getForObject("http://provider/zone?",String.class);
    }
}
//注意,如果请求的URL是一个IP地址或者一个域名,而非微服务集群中的服务名,就只会正常的发送请求,而没有负载均衡。

使用也是很简单的,对于微服务架构熟悉的开发者来说。

三、Ribbon客户端负载均衡的实现原理

下面讲讲Ribbon客户端负载均衡的实现原理。这里我就不作过多的源码分析,只列举出重要的部分。

1、贴了标签的RestTemplate

从上面的使用中,可以看到,我们注册RestTemplate的bean的时候,多加了一个注解 @LoadBalanced,这个注解实际上的作用是:标记这个bean,扫描的时候,把这个标记的bean添加进负载均衡请求则的列表,方便后续进行拦截。好了,先贴一下负载均衡的接口源码:

public interface ILoadBalancer {
    void addServers(List<Server> var1);
    Server chooseServer(Object var1);
    void markServerDown(Server var1);
    List<Server> getReachableServers();
    List<Server> getAllServers();
}

分析:

  • 添加服务列表
  • 选择服务
  • 对服务的状态标记为Down
  • 获取可达的服务以及获取全部服务

2、一丝不苟的拦截者

利用拦截器,在被贴了标签的restTemplate发送请求时进行拦截,判断是否是微服务内部的服务地址,如果不是,则是正常请求,如果是,则调用对应的负载均衡策略,进行负载均衡请求。

以下是把贴了标签的restTemplate自定义化,目的是给这个bean添加拦截器

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

	@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) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

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

	@Configuration
	@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.setInterceptors(list);
			};
		}

	}

	/**
	 * Auto configuration for retry mechanism.以下是尝试部分。
	 */
	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {
			};
		}

	}

	/**
	 * Auto configuration for retry intercepting mechanism.
	 */
	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

}

以下是拦截器的实现:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;

	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
			LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
      //通过originalUri.getHost()可以获取服务名。
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

}

3、定时检测者与策略选择

  • Ping检测
public class PingUrl implements IPing {
		public boolean isAlive(Server server) {
				String urlStr   = "";
				if (isSecure){
					urlStr = "https://";
				}else{
					urlStr = "http://";
				}
				urlStr += server.getId();
				urlStr += getPingAppendString();

				boolean isAlive = false;

				HttpClient httpClient = new DefaultHttpClient();
				HttpUriRequest getRequest = new HttpGet(urlStr);
				String content=null;
				try {
					HttpResponse response = httpClient.execute(getRequest);
					content = EntityUtils.toString(response.getEntity());
					isAlive = (response.getStatusLine().getStatusCode() == 200);
					if (getExpectedContent()!=null){
						LOGGER.debug("content:" + content);
						if (content == null){
							isAlive = false;
						}else{
							if (content.equals(getExpectedContent())){
								isAlive = true;
							}else{
								isAlive = false;
							}
						}
					}
				} catch (IOException e) {
					e.printStackTrace();
				}finally{
					// Release the connection.
					getRequest.abort();
				}
				return isAlive;
		}
}
  • 均衡策略
//以下是基本的轮询策略;省略了部分源码
public class RoundRobinRule extends AbstractLoadBalancerRule {
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();

            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }
}
//以下是区域有效检测策略;当zone区域大于1时,才会执行zone选择策略。
public class ZoneAvoidancePredicate extends  AbstractServerPredicate {
    private volatile DynamicDoubleProperty triggeringLoad = new DynamicDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer.triggeringLoadPerServerThreshold", 0.2d);

    private volatile DynamicDoubleProperty triggeringBlackoutPercentage = new DynamicDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer.avoidZoneWithBlackoutPercetage", 0.99999d);
    
    private static final Logger logger = LoggerFactory.getLogger(ZoneAvoidancePredicate.class);
    
    private static final DynamicBooleanProperty ENABLED = DynamicPropertyFactory
            .getInstance().getBooleanProperty(
                    "niws.loadbalancer.zoneAvoidanceRule.enabled", true);


    public ZoneAvoidancePredicate(IRule rule, IClientConfig clientConfig) {
        super(rule, clientConfig);
        initDynamicProperties(clientConfig);
    }

    public ZoneAvoidancePredicate(LoadBalancerStats lbStats,
            IClientConfig clientConfig) {
        super(lbStats, clientConfig);
        initDynamicProperties(clientConfig);
    }

    ZoneAvoidancePredicate(IRule rule) {
        super(rule);
    }
    
    private void initDynamicProperties(IClientConfig clientConfig) {
        if (clientConfig != null) {
  //
            triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer." + clientConfig.getClientName() + ".triggeringLoadPerServerThreshold", 0.2d);
//区域故障率(断开次数/该区域下的实例数)>=0.99999;则选择剔除掉这个zone。
            triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer." + clientConfig.getClientName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
        }
        
    }

    @Override
    public boolean apply(@Nullable PredicateKey input) {
        if (!ENABLED.get()) {
            return true;
        }
        String serverZone = input.getServer().getZone();
        if (serverZone == null) {
            // there is no zone information from the server, we do not want to filter
            // out this server
            return true;
        }
        LoadBalancerStats lbStats = getLBStats();
        if (lbStats == null) {
            // no stats available, do not filter
            return true;
        }
        if (lbStats.getAvailableZones().size() <= 1) {
          //只有一个zone,无需挑选。
            return true;
        }
      //获取区域的快照数据
        Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
        if (!zoneSnapshot.keySet().contains(serverZone)) {
            // The server zone is unknown to the load balancer, do not filter it out 
            return true;
        }
        logger.debug("Zone snapshots: {}", zoneSnapshot);
      //根据快照数据、心跳信息、故障率,获取有效的zone。
        Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
        logger.debug("Available zones: {}", availableZones);
        if (availableZones != null) {
            return availableZones.contains(input.getServer().getZone());
        } else {
            return false;
        }
    }    
}
//zone挑选策略
public class ZoneAvoidanceRule extends PredicateBasedRule {

    private static final Random random = new Random();
    
    private CompositePredicate compositePredicate;
    
    public ZoneAvoidanceRule() {
        super();
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
    
    private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
        return CompositePredicate.withPredicates(p1, p2)
                             .addFallbackPredicate(p2)
                             .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
                             .build();
        
    }
    
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this, clientConfig);
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this, clientConfig);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }

    static Map<String, ZoneSnapshot> createSnapshot(LoadBalancerStats lbStats) {
        Map<String, ZoneSnapshot> map = new HashMap<String, ZoneSnapshot>();
        for (String zone : lbStats.getAvailableZones()) {
            ZoneSnapshot snapshot = lbStats.getZoneSnapshot(zone);
            map.put(zone, snapshot);
        }
        return map;
    }

    static String randomChooseZone(Map<String, ZoneSnapshot> snapshot,
            Set<String> chooseFrom) {
        if (chooseFrom == null || chooseFrom.size() == 0) {
            return null;
        }
        String selectedZone = chooseFrom.iterator().next();
        if (chooseFrom.size() == 1) {
            return selectedZone;
        }
        int totalServerCount = 0;
        for (String zone : chooseFrom) {
            totalServerCount += snapshot.get(zone).getInstanceCount();
        }
        int index = random.nextInt(totalServerCount) + 1;
        int sum = 0;
        for (String zone : chooseFrom) {
            sum += snapshot.get(zone).getInstanceCount();
            if (index <= sum) {
                selectedZone = zone;
                break;
            }
        }
        return selectedZone;
    }

    public static Set<String> getAvailableZones(
            Map<String, ZoneSnapshot> snapshot, double triggeringLoad,
            double triggeringBlackoutPercentage) {
        if (snapshot.isEmpty()) {
            return null;
        }
        Set<String> availableZones = new HashSet<String>(snapshot.keySet());
        if (availableZones.size() == 1) {
            return availableZones;
        }
        Set<String> worstZones = new HashSet<String>();
        double maxLoadPerServer = 0;
        boolean limitedZoneAvailability = false;

        for (Map.Entry<String, ZoneSnapshot> zoneEntry : snapshot.entrySet()) {
            String zone = zoneEntry.getKey();
            ZoneSnapshot zoneSnapshot = zoneEntry.getValue();
            int instanceCount = zoneSnapshot.getInstanceCount();
            if (instanceCount == 0) {
                availableZones.remove(zone);
                limitedZoneAvailability = true;
            } else {
                double loadPerServer = zoneSnapshot.getLoadPerServer();
                if (((double) zoneSnapshot.getCircuitTrippedCount())
                        / instanceCount >= triggeringBlackoutPercentage
                        || loadPerServer < 0) {
                    availableZones.remove(zone);
                    limitedZoneAvailability = true;
                } else {
                    if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
                        // they are the same considering double calculation
                        // round error
                        worstZones.add(zone);
                    } else if (loadPerServer > maxLoadPerServer) {
                        maxLoadPerServer = loadPerServer;
                        worstZones.clear();
                        worstZones.add(zone);
                    }
                }
            }
        }

        if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
            // zone override is not needed here
            return availableZones;
        }
        String zoneToAvoid = randomChooseZone(snapshot, worstZones);
        if (zoneToAvoid != null) {
            availableZones.remove(zoneToAvoid);
        }
        return availableZones;

    }

    public static Set<String> getAvailableZones(LoadBalancerStats lbStats,
            double triggeringLoad, double triggeringBlackoutPercentage) {
        if (lbStats == null) {
            return null;
        }
        Map<String, ZoneSnapshot> snapshot = createSnapshot(lbStats);
        return getAvailableZones(snapshot, triggeringLoad,
                triggeringBlackoutPercentage);
    }

    @Override
    public AbstractServerPredicate getPredicate() {
        return compositePredicate;
    }    
}

四、总结

  • 客户端负载均衡的原理
  • Ribbon客户端的使用
  • Ribbon客户端的实现原理
    • 贴标签
    • 拦截器
    • 定时检测
    • 策略选择
发布了57 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/rekingman/article/details/95048609
今日推荐