所有文章

https://www.cnblogs.com/lay2017/p/11908715.html

正文

ribbon是springcloud封装的一个基于http客户端负载均衡的组件。springcloud的openfeign集成使用了ribbon。所以如果你使用openfeign,那么也会很轻易得使用到ribbon。本文将从openfeign切入,看看它是怎么来使用到ribbon这个客户端负载均衡组件的。

LoadBalancerFeignClient提供openfeign的负载均衡实现

在讲openfeign的时候我们说到,最后代理类其实就是发起http请求,并解码返回的字节码内容。回顾一下代码

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template); Response response; try {
response = client.execute(request, options);
} catch (IOException e) {
} boolean shouldClose = true;
try {
if (Response.class == metadata.returnType()) {
//
}
if (response.status() >= 200 && response.status() < 300) {
// 返回空
if (void.class == metadata.returnType()) {
return null;
} else {
// 返回解码结果对象
Object result = decode(response); return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
//
} else {
//
}
} catch (IOException e) {
//
} finally {
//
}
}

可以看到,client提交了一个http的request,然后获得了response响应对象,处理后并返回。ribbon的接入将从这里开始,我们看看client接口

public interface Client {

  Response execute(Request request, Options options) throws IOException;
}

接口很简单,就是一个请求响应模型。那么,我们再向下看看Client关于负载均衡这个方面有什么实现呢?

LoadBalancerFeignClient作为Client在负载均衡方面的实现类,我们跟进它的execute方法,看看做了些啥

public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 构造ribbon的request对象
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName);
// 执行ribbon的request对象
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
} catch (ClientException e) {
//
}
}

可以看到,关于负载均衡方面openfeign直接构造了ribbon的请求,并执行。

构造ribbon的IClient

lbClient(clientName)构造了一个ribbon的http客户端实现,打开该方法

private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}

一个简单工厂模式,跟进create方法

public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
// 默认返回FeignLoadBalancer
client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}

这里瞄一眼FeignLoadBalancer的类图吧

FeignLoadBalancer实现了IClient接口,所以它会负责提交并执行Ribbon的request请求

提交执行ribbon的request请求

lbClient方法构建了FeignLoadBalancer,下面该调用它的executeWithLoadBalancer方法了,跟进方法(方法在父类AbstractLoadBalancerAwareClient中)

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
// 回调返回选择好的Server对象,并重新构造uri地址
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 执行ribbon的request请求
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
//
} }

如果不关心负载均衡的情况,这里其实就是直接执行ribbon的request请求了。也就是IClient这个接口类定义的内容。不过FeignLoadBalancer需要进行一次负载选择Server,然后才回调这里的call来发起请求。

跟进submit方法看看,submit方法先是进行了一次选择获得了一个Server对象,然后回调了上面说的ribbon的request执行

public Observable<T> submit(final ServerOperation<T> operation) {
// ... // Use the load balancer
Observable<T> o =
// 选择Server
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
context.setServer(server);
// // Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(final Server server) {
// ...
// 回调ribbon的request请求
return operation.call(server).doOnEach(
// ...
);
}
}); if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
}); // ...
}

ILoadBalancer负载均衡器

继续跟进selectServer,看看如何选择服务的

private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
// 从上下文中获取
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}

托付给了getServerFromLoadBalancer来实现,继续跟进

public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
// ... ILoadBalancer lb = getLoadBalancer();
if (host == null) {
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
// ... return svc;
} else {
// ...
}
} else {
// ...
} // ...
}

getLoadBalancer方法先是获取了一个ILoadBalancer接口的实现,然后调用了chooseServer来选择一个Server。

先跟进getLoadBalancer方法,直接返回了上下文中的设置的ILoadBalancer负载均衡器

private ILoadBalancer lb;

public ILoadBalancer getLoadBalancer() {
return lb;
}

我们看一下ILoadBalancer的的类图,RibbonClientConfiguration配置ILoadBalancer的时候配置的是ZoneAwareLoadBalancer的Bean

getLoadBalancer返回的ILoadBalancer负载均衡器也将是ZoneAwareLoadBalancer的实例对象

IRule负载均衡算法

有了ILoadBalancer负载均衡器,再看看chooseServer方法。这里忽略一些细节,直接看BaseLoadBalancer的chooseServer这个核心的实现

public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}

可以看看,直接调用了IRule接口的choose方法。IRule接口则负责相关的负载均衡算法实现,我们看看IRule接口有哪些实现吧

常见的随机算法、轮询算法...等

总结

到这里,本文就结束了。我们再回顾一下文章的接口和流程

1、先是openfeign开放了一个Client接口用于http请求,并且LoadBalancerFeignClient作为负载均衡的实现类

2、LoadBalancerFeignClient则直接构造了一个ribbon的IClient接口的实现FeignLoadBalancer

3、执行ribbon的request之前,先委托ILoadBalancer负载均衡器选择一个Server,然后回调执行request请求

4、ILoadBalancer会选择IRule实现的负载均衡算法来获取一个Server,并返回。

总体逻辑比较简单,本文忽略了一些细节内容,比如一些自动配置的东西、如果从Eureka中获取服务列表等,有兴趣可以自己看看。

一、ribbon如何集成在openfeign中使用的更多相关文章

  1. 一、hystrix如何集成在openfeign中使用

    所有文章 https://www.cnblogs.com/lay2017/p/11908715.html 正文 HystrixInvocationHandler hystrix是开源的一个熔断组件,s ...

  2. 【笔记】android sdk集成的eclipse中导入项目

    android sdk集成的eclipse中导入项目 想要把旧的ADT项目,一模一样的导入进来,需要: 1.把项目放到,非当前ADT的workspace目录下: 2.从Project中Import,选 ...

  3. Spark Streaming揭秘 Day28 在集成开发环境中详解Spark Streaming的运行日志内幕

    Spark Streaming揭秘 Day28 在集成开发环境中详解Spark Streaming的运行日志内幕 今天会逐行解析一下SparkStreaming运行的日志,运行的是WordCountO ...

  4. 持续集成:TestNG中case之间的关系

    持续集成:TestNG中case之间的关系   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq: ...

  5. 第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中

    第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详 ...

  6. 第三百五十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—将selenium操作谷歌浏览器集成到scrapy中

    第三百五十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—将selenium操作谷歌浏览器集成到scrapy中 1.爬虫文件 dispatcher.connect()信号分发器,第一个参数信 ...

  7. 如何将SLIC集成到ESXi中

    如何将SLIC集成到ESXi中 参考 http://forums.mydigitallife.info/threads/12982-ESX-ESXi-Bios-Tools/page34?p=72183 ...

  8. 解决duilib使用zip换肤卡顿的问题(附将资源集成到程序中的操作方法)

    转载请说明原出处,谢谢~~ 今天在做单子是.客户要求做换肤功能,为此我专门写了一个换肤函数,而且把各种皮肤资源压缩为各个zip文件来换肤.可是客户反映程序执行缓慢,我測试后发现的确明显能够看出慢了不少 ...

  9. 三十七 Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中

    Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详解 基本概念 如 ...

随机推荐

  1. lintcode 394. Coins in a Line 、leetcode 292. Nim Game 、lintcode 395. Coins in a Line II

    变型:如果是最后拿走所有石子那个人输,则f[0] = true 394. Coins in a Line dp[n]表示n个石子,先手的人,是必胜还是必输.拿1个石子,2个石子之后都是必胜,则当前必败 ...

  2. kinova roslaunch kinova_bringup kinova_robot.launch kinova_robotType:=j2s7s300

    luo@luo-ThinkPad-W530:~$ luo@luo-ThinkPad-W530:~$ luo@luo-ThinkPad-W530:~$ luo@luo-ThinkPad-W530:~$ ...

  3. Laya的调试,调试面板,断点调试

    参考: 性能统计面板介绍 版本2.1.1.1 调试面板 Laya有两个调试选项,编辑模式F9. 第一个调试模式,除了调试面板,还有一个查看当前舞台对象的面板.类似白鹭的Egret Inspector. ...

  4. 宣化上人:大佛顶首楞严经四种清净明诲浅释(1)(转自学佛网:http://www.xuefo.net/nr/article23/230609.html)

    唐天竺·沙门般剌密帝译 宣化上人主讲 一九八三年四月十七日晚讲于万佛圣城 四种清净明诲,真实不虚 楞严经里的四种清净明诲:断淫.断杀.断偷.断妄,是息息相关的.若犯淫戒,就容易犯杀戒,也容易犯盗戒.妄 ...

  5. 单机prometheus vs 集群kube-prometheus+prometheus-operator

    prometheus 组件: node-exporter:9100端口 https://segmentfault.com/a/1190000017959127

  6. 【端口转发】k8s port-forward端口转发 和 ssh -NfL端口转发

    kubectl port-forward端口转发 将远程pod端口转发到本地端口 kubectl port-forward monitoring-grafana-695c545f46-rhtwc -- ...

  7. 【grpc proto】python使用proto文件生成简易的服务端和客户端

    1.安装python-grpc(注意,是grpcio包,不是grpc包!) pip install grpcio 2.编写.proto文件 grpc教程:http://doc.oschina.net/ ...

  8. mysql日期加一个天数获得新的日期

    原文地址:https://476057266-qq-com.iteye.com/blog/2047671 在当前的日期上加三天,天数随便改: SELECT date_add(CURRENT_DATE( ...

  9. 2019年Java面试题基础系列228道,题目汇总,可以先看会多少

    Java面试题(一) 1.面向对象的特征有哪些方面? 2.访问修饰符 public,private,protected,以及不写(默认)时的区别? 3.String 是最基本的数据类型吗? 4.flo ...

  10. pt-kill MySQL会话杀灭神器

    废话不多说,直接上例子: pt-kill --host=127.0.0.1 --user=xxx --password=xxxxxx --port=xxxx --busy-time 10 --matc ...