Ribbon和Feign的原理
1.Ribbon
1.1 Ribbon的简介
Ribbon也是微服务治理框架中的一员,主要功能是负载均衡。负责从多个服务实例中根据某些规则选取一台进行调用,在跟nacos整合后,通过nacos获取服务列表,再根据负载均衡算法选取具体服务进行调用。
1.2 Ribbon整合原理
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
我们在使用Ribbon时会向上面代码声明一个RestTemplate添加@LoadBalanced,在Ribbon的依赖中有一个自动配置类,就会对这个这个RestTemplate增加添加一个拦截器LoadBalancerInterceptor,在使用RestTemplate访问具体服务时就会被拦截,将url中的服务名替换成真正的IP和Port。
流程图:
1.2 Ribbon整合关键代码
- 1.自动配置类向restTemplate 添加拦截器
LoadBalancerAutoConfiguration
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
#LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#restTemplateCustomizer
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 给restTemplate 添加拦截器
restTemplate.setInterceptors(list);
- 2.执行请求进入拦截器
LoadBalancerInterceptor#intercept
// 使用restTemplate发送请求就会被拦截
restTemplate.getForObject(url,String.class);
// 执行请求进入拦截器逻辑
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,
this.requestFactory.createRequest(request, body, execution));
}
- 3.执行负载均衡器的execute方法
RibbonLoadBalancerClient#execute()
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
// 初始化loadBalancer
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
// 根据负载均衡规则选择server
Server server = this.getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
- 4.初始化loadBalancer相关逻辑
this.getLoadBalancer(serviceId)
# DynamicServerListLoadBalancer#restOfInit
// 实例化默认ZoneAwareLoadBalancer
new ZoneAwareLoadBalancer
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
# 实例化父类
restOfInit(clientConfig);
# DynamicServerListLoadBalancer#updateListOfServers
// 更新服务列表
updateListOfServers()
servers = serverListImpl.getUpdatedListOfServers();
# NacosServerList#getServers
// nacos中的实现获取微服务列表
getServers()
discoveryProperties.namingServiceInstance().selectInstances(serviceId, group, true)
2.Feign
2.1 Feign简介
Feign主要是微服务之间的远程调用,传统服务间调用需要建立远程连接调用,使用Feign跨服务间接口调用就像是调用本地的方法一样。Feign整合了Ribbon,通过定义的feign接口使用Ribbon去选择具体服务调用,Ribbon又会将服务名替换成真正的IP和Port调用。
2.2 Feign原理
1.springboot的启动类上添加注解 @EnableFeignClients
2.定义接口如下OrderFeignService
// 代理类中拼接方式:"http://mall-order/order/findOrderByUserId/"+id
@FeignClient(value = "mall-order",path = "/order")
public interface OrderFeignService {
@RequestMapping("/findOrderByUserId/{userId}")
R findOrderByUserId(@PathVariable("userId") Integer userId);
}
我们在使用Feign时需要在启动类上启用Feign,这个注解会引入一个类,这个类会专门扫描添加了@FeignClient注解的接口,通过spring的FactoryBean原理注入到spring容器中,同时生成代理。最后通过整合Ribbon替换路径中的服务名。
流程图:
2.3 Feign关键代码
- 1.将添加了@FeignClient的类注册为FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
- 2.bean实例化时执行的是FeignClientFactoryBean#getObject
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
} else {
this.url = this.name;
}
this.url = this.url + this.cleanPath();
// 负载均衡调用
return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url));
}
- 3.获取Client
protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) {
Client client = (Client)this.getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = (Targeter)this.get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
}
- 4.执行Client.execute方法
LoadBalancerFeignClient#execute
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
- 5.执行Ribbon中的chooseServer
- com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer