Ribbon是一个客户端负载均衡器,是Netflix公司开源的项目,运行在客户端上,feign默认使用了Ribbon。
Ribbon在Spring中三种使用方式:
①RestTemplate集成
@Bean
@LoadBalanced
RestTemplate RestTemplate() {
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setReadTimeout(2000);
simpleClientHttpRequestFactory.setConnectTimeout(2000);//设置超时时间
return new RestTemplate(simpleClientHttpRequestFactory);
}
//通过以下方式调用
String body = restTemplate.getForObject("http://servicename/", String.class);
②LoadBalancerClient
@Autowired
LoadBalancerClient loadbalancerClient;
//使用方法:
loadbalancerClient.choose("serviceId").getUri();//获取url
//用请求客户端请求url
③feign
在https://blog.csdn.net/lgq2626/article/details/80392914文章中已经介绍了feign,feign中默认集成了ribbon。
Nginx是服务端负载均衡器,nginx得负载均衡器具有这么几个特点:
ribbon服务列表有两种方式来获取:
方式1:在配置文件中配置
ribbon.eureka.enabled=false
test-service.ribbon.listOfServers=http://www.baidu.com,http://www.jd.com,http://www.taobao.com
//使用方式:下面只说明使用feign也可以使用RestTemplate或者LoadBalancerClient去调用
//申明客户端,在bean里注入Test 直接调用即可
@FeignClient(name="test-service")
public interface Test {
@RequestMapping("/")
public String eee();
}
方式2:和eureka集成:
在eureka客户端中初始化一个EurekaRibbonClientConfiguration类,中的ribbonServerList()方法,就是初始化服务列表的方法。每一个ribbon客户端都有一个RibbonClientConfiguration配置,初始化RibbonClientConfiguration配置的时候初始化了一些信息,包括是否ping,ribbonclient,还初始化了一个ILoadBalancer对象,调用到了DynamicServerListLoadBalancer方法的enableAndInitLearnNewServersFeature()开启了每30秒去更新一次eureka服务器的定时任务定时更新BaseLoadBalancer#upServerList对象。
在ribbon负载均衡器中,提供了ping机制,每隔一段时间,就会去ping服务器,由 com.netflix.loadbalancer.IPing 接口去实现。单独使用ribbon,不会激活ping机制,默认采用DummyPing(在RibbonClientConfiguration中实例化),isAlive()方法直接返回true。ribbon和eureka集成,默认采用NIWSDiscoveryPing(在EurekaRibbonClientConfiguration中实例化的),只有服务器列表的实例状态为up的时候才会为Alive。
IPing的接口实现类图如下:
配置方式:
#配置Ping操作的间隔
test-service.ribbon.NFLoadBalancerPingInterval=2
#配置IPing的实现类
test-service.ribbon.NFLoadBalancerPingClassName=com.test.PingTest
public class PingTest implements IPing{
@Override
public boolean isAlive(Server server) {
System.out.println("我是ping机制");
return false;
}
}
在ribbon负载机制中包括随机/轮询/连接数最少…
final ServerStats stats = loadBalancerContext.getServerStats(server);
是自增, loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);
是自减。 在上一篇文章中介绍了feign的使用,但是仅仅只是介绍了feign请求带url的解析,并没有解释和ribbon的集成。下面介绍ribbon和feign的集成请求方式:
在FeignClientFactoryBean#getObject()中:
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));//如果请求没有带url,而是带了name
}
protected T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget target) {
Client client = getOptional(context, Client.class);//这里获取的Client是LoadBalancerFeignClient,在DefaultFeignLoadBalancedConfiguration/FeignRibbonClientAutoConfiguration(版本不同初始化类不同)类中初始化。
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
//这里不往下跟了,在上篇中都跟过了,直接跟到LoadBalancerFeignClient#execute()方法中
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
比较简单的代码就直接过了,直接看重点LoadBalancerCommand#submit();
public Observable<T> submit(final ServerOperation<T> operation) {
...省略代码...
// Use the load balancer
Observable<T> o =
//选择server,和feign默认的选择策略是ZoneAwareLoadBalancer
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
// Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1T>>() {
@Override
public Observable<T> call(final Server server) {
context.incAttemptCount();
loadBalancerContext.noteOpenConnection(stats);//做一些记录,包括把服务器的连接数+1,设置请求的开始时间,方便上文中讲到的策略选择
if (listenerInvoker != null) {
try {
listenerInvoker.onStartWithServer(context.toExecutionInfo());
} catch (AbortExecutionException e) {
return Observable.error(e);
}
}
final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start();
//在这个call里面调用服务,调用到了FeignLoadBalancer#execute()/RetryableFeignLoadBalancer#execute(重试)去发起请求,并且返回值
return operation.call(server).doOnEach(new Observer<T>() {
...省略代码...//处理返回值,包括把服务器连接数-1,状态变化,统计请求时间,方便策略选择
});
}
});
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
...省略代码...
}