SpringCloud——Ribbon使用和原理

SpringCloud中的Ribbon开源项目,提供了客户端的负载均衡算法。这篇文章,我们来介绍下它是如何实现的。为了方便理解,我们以客户端调用的流程来介绍,其中会穿插介绍相关源代码。

简单回顾下Ribbon的使用,这里强调两点:

1、在启动类Application中,添加@LoadBalanced注解。

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

2、结合RestTemplate发起调用,调用时采用服务名称(如:COMPUTE-SERVICE)来实现。

return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();

先从拦截器LoadBalancerInterceptor开始介绍:

从请求中获取服务名称,即上文说到的COMPUTE-SERVICE,然后执行LoadBalancerClient的execute方法。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
 
	private LoadBalancerClient loadBalancer;
 
	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		this.loadBalancer = loadBalancer;
	}
 
	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		return this.loadBalancer.execute(serviceName,
			new LoadBalancerRequest<ClientHttpResponse>() {
				@Override
				public ClientHttpResponse apply(final ServiceInstance instance)
						throws Exception {
					HttpRequest serviceRequest = new ServiceRequestWrapper(request,
							instance, loadBalancer);
					return execution.execute(serviceRequest, body);
				}
			});
	}
}

这里,先简单介绍下这几个类。
1)顶层接口
实现该接口的类,会使用一个负载均衡器来选择一个server来转发请求。

public interface ServiceInstanceChooser {
	ServiceInstance choose(String serviceId);
}

2)继承接口
LoadBalancerClient
提供了两种不同的参数的execute()执行方法。

public interface LoadBalancerClient extends ServiceInstanceChooser {
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
	
	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
	
	URI reconstructURI(ServiceInstance instance, URI original);
}

3)实现类
在RibbonLoadBalancerClient类中,可以看到具体的执行方法。
主要做了以下几件事:

  • 根据serviceId获取负载均衡器;
  • 根据负载均衡器获取server;
  • 将请求转到具体的服务实例。
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
	ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
	Server server = getServer(loadBalancer);
	if (server == null) {
		throw new IllegalStateException("No instances available for " + serviceId);
	}
	RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
			serviceId), serverIntrospector(serviceId).getMetadata(server));

	return execute(serviceId, ribbonServer, request);
}

@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
	Server server = null;
	if(serviceInstance instanceof RibbonServer) {
		server = ((RibbonServer)serviceInstance).getServer();
	}
	if (server == null) {
		throw new IllegalStateException("No instances available for " + serviceId);
	}

	RibbonLoadBalancerContext context = this.clientFactory
			.getLoadBalancerContext(serviceId);
	RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

	try {
		T returnVal = request.apply(serviceInstance);
		statsRecorder.recordStats(returnVal);
		return returnVal;
	}
	// catch IOException and rethrow so RestTemplate behaves correctly
	catch (IOException ex) {
		statsRecorder.recordStats(ex);
		throw ex;
	}
	catch (Exception ex) {
		statsRecorder.recordStats(ex);
		ReflectionUtils.rethrowRuntimeException(ex);
	}
	return null;
}

其中,ServiceInstance——服务实例接口

public interface ServiceInstance {
	String getServiceId();
 	String getHost();
  	int getPort();
 	boolean isSecure();
	URI getUri();
	Map<String, String> getMetadata();
}

RibbonServer——服务实例实现类

protected static class RibbonServer implements ServiceInstance {
	private final String serviceId;
	private final Server server;
	private final boolean secure;
	private Map<String, String> metadata;

	protected RibbonServer(String serviceId, Server server) {
		this(serviceId, server, false, Collections.<String, String> emptyMap());
	}

	protected RibbonServer(String serviceId, Server server, boolean secure,
			Map<String, String> metadata) {
		this.serviceId = serviceId;
		this.server = server;
		this.secure = secure;
		this.metadata = metadata;
	}

	//省去getter和setter
	@Override
	public String toString() {
		final StringBuffer sb = new StringBuffer("RibbonServer{");
		sb.append("serviceId='").append(serviceId).append('\'');
		sb.append(", server=").append(server);
		sb.append(", secure=").append(secure);
		sb.append(", metadata=").append(metadata);
		sb.append('}');
		return sb.toString();
	}
}

getServer()中,默认使用的ZoneAwareLoadBalancer负载均衡器。

public Server chooseServer(Object key) {		        
	Server server = null;
   	try {
       //获取可用区域Zone
       LoadBalancerStats lbStats = getLoadBalancerStats();
       Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
             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 (Throwable e) {
       logger.error("Unexpected exception when choosing server using zone aware logic", e);
   	}
   	if (server != null) {
       return server;
   	}
}

具体的分配算法:

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

原文链接:
https://blog.csdn.net/u010066934/article/details/56671675

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值