《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》

摘要: 原创出处 http://thoreauz.com/2017/08/11/language/java/spring%20cloud/Ribbon%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/ 「二道涯」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

Ribbon是一个客户端负载均衡软件,通过注册到Eureka上的服务名,获取服务列表,缓存到本地,选择负载均衡算法,发送http请求。 在spring cloud可以通过简单配置,即可完成客户端负载均衡,用法如下:配置,注入,请求

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

}
@Autowired
private RestTemplate restTemplate;

public Object getUser(Long id) {
return restTemplate.getForEntity("http://CLOUD-SERVICE-USER/user/persionalInfo?id=" + id, Object.class).getBody();
}

带着问题看源码:

  1. Ribbon怎么和RestTemplate整合
  2. 怎么样自定义负载均衡算法
  3. 底层http客户端怎么修改

common 包

在spring-cloud-common中提供了这样一个接口LoadBalancerClient,注释说明是客户端负载均衡器。

public interface LoadBalancerClient extends ServiceInstanceChooser {
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
* 使用来自LoadBalancer的ServiceInstance,对起执行请求
* @param serviceId
* @param 服务实例
* @param Request。允许在执行前后添加metric
* @return 回调结果
*/
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
/**
* 创建一个真正的URL包含主机和端口:
* http://myservice/path/to/service --> http://host:port/path/to/service
* @param 服务实例
* @param 源url,是一个包含serviceId或者dns的URL
* @return 重新构造的URL
*/
URI reconstructURI(ServiceInstance instance, URI original);
}

此接口提供了三个方法,继承ServiceInstanceChooser,我们再来看下这个接口:

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

这个接口只提供了一个方法:通过serviceId获取需要访问的实例。而Ribbon则实现了LoadBalancerClient。

img

下面分析这个类怎么具体实现接口中的choose方法:

choose实现

@Override
public ServiceInstance choose(String serviceId) {
Server server = getServer(serviceId);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}

第一行返回一个server,server包含了服务的地址端口和状态区域等信息,通过serviceId获取这个服务是关键。

Server{
public static final String UNKNOWN_ZONE = "UNKNOWN";
private String host;
private int port = 80;
private volatile String id;
private volatile boolean isAliveFlag;
private String zone = "UNKNOWN";
private volatile boolean readyToServe = true;
}

再看获取服务的方法:

protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}

获取ILoadBalancer对象,再传递给另一个同名方法。下面分析ILoadBalancer及其实现成了重点。

img

追查ILoadBalancer的实现,最终落实到DynamicServerListLoadBalancer,下面从接口开始分析这个实现类。 先看几个抽象类和接口中需要实现的方法:

public interface ILoadBalancer {
void addServers(List<Server> var1); // 初始化服务列表
Server chooseServer(Object var1); // 从列表中选择一个服务
void markServerDown(Server var1); // 标记服务为down
List<Server> getReachableServers(); // 获取up和reachable的服务
List<Server> getAllServers(); //获取所有服务(reachable and unreachable)
}
public abstract List<Server> getServerList(ServerGroup serverGroup);
public abstract LoadBalancerStats getLoadBalancerStats();
public void primeCompleted(Server s, Throwable lastException);
public abstract void initWithNiwsConfig(IClientConfig clientConfig);

接着再看下面调用链:下面列出的代码中省略了很多逻辑。

public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}

//类:DynamicServerListLoadBalancer
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}

//类:DynamicServerListLoadBalancer
protected Server getServer(ILoadBalancer loadBalancer) {
return loadBalancer.chooseServer("default");
}

//类:ZoneAwareLoadBalancer
@Override
public Server chooseServer(Object key) {
return zoneLoadBalancer.chooseServer(key);
}

//类:BaseLoadBalancer
public Server chooseServer(Object key) {
return rule.choose(key); // 重要方法 rule = new RoundRobinRule()
}

总之,逻辑是这样的:

  1. 获取负载均衡器(LoadBalancer)的实例(getInstance)。
  2. 负载均衡器通过IRule实现的算法逻辑实现选择。
  3. 最后获得一个Server(服务)

负载均衡的算法主要通过ribbon提供的一些列算法实现IRule,默认是轮询。 img Ribbon提供了一些其他算法:

  • RetryRule:根据轮询的方式重试
  • RandomRule: 随机
  • WeightedResponseTimeRule:根据响应时间去分配权重
  • ZoneAvoidanceRule:区域可用性

至于切换算法的配置,主要集中在初始化时的配置类里面IClientConfig,而这个bean的信息通过对restTemplate添加注解@LoadBalanced完成。具体的负载均衡算法可以通过bean注入,如下。

@Bean
public IRule ribbonRule() {
return new RandomRule();//这里配置策略,和配置文件对应
}

这就解决了我在文章开头的第二个问题。如果需要自定义算法,实现IRule,通过bean的配置完成注入。至于服务列表维护,此文跳过。

restTemplate整合

上文提到通过注解即可开启restTemplate,那是如何做到的?

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

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
}

这是一个自动配置类,首先维护了一个restTemplates列表,并通过RestTemplateCustomizer对这些他们添加一个拦截器,如下。

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}

而拦截器的主要就是把serviceId取出来,再使用负载均衡器发起http请求。

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

又回到了common中提到的四个方法中的第一个。至此,很明了,就是通过注入给restTemplate注入一个拦截器达到使用loadBalancer的目的。

RestTemplate 本身并没有做 HTTP 底层的实现,而是利用了现有的第三方库:

  1. HttpURLConnection
  2. Apache httpclient
  3. ok http
  4. netty

不同的底层实现,只需要定义直接的RequestFactory,实现ClientHttpRequestFactory。在spring cloud给出了http client切换成okhttp的配置。

666. 彩蛋

如果你对 Ribbon 感兴趣,欢迎加入我的知识星球一起交流。

知识星球

文章目录
  1. 1. common 包
    1. 1.1. choose实现
  2. 2. restTemplate整合
  • 666. 彩蛋