Ribbon核心API源码解析:ribbon-coreIClient请求客户端 - 02
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ribbon核心API源码解析:ribbon-coreIClient请求客户端 - 02相关的知识,希望对你有一定的参考价值。
Ribbon核心API源码解析:ribbon-core(一)IClient请求客户端 - 02
前言
上篇文章整体上对Ribbon做了介绍,可能有小伙伴的有和我一样的感觉:知道Ribbon它是做什么大,仅只是略懂略懂状态,一种不踏实之感。Java库的好处是它开源,大大降低了学习的难度(不用纯凭记忆,可以从设计脉络上整体把握)。
从本文起将对Ribbon从API源码出发,附以示例讲解,逐个击破的方式,一步步对Ribbon进行全面剖析。因Ribbon一时半会还找不到替代的技术,并且国内学习它的资料比较少,希望此系列文章帮助到你。
正文
ribbon-core
它是Ribbon的核心包,其它任何包均依赖于此。该Jar包内只定义了公共API,自己并不能单独work,所以你可以理解为它就是一公共抽象即可。
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
<!-- 版本号为上文约定的2.3.0版本,保持和Spring Cloud的依赖一致 -->
<version>2.3.0</version>
</dependency>
从图中可以看出:core核心包里并没有任何loadbalance负载均衡的概念,并且也没有任何http的概念在里面,所以说core是一个高度抽象的包:和lb无关,和协议亦无关。
说明:Ribbon的源码对类的命名有个规范 -> 接口一定是以
I
开头的,如IClient、以及后面的IRule、IPing等
IClient
Ribbon
要想负载均衡,那必然需要有发送请求的能力,而该接口就是它最最最最为核心接口喽,其它的一切组件均围绕它来设计和打造,包括LB。
该接口表示可以执行单个请求的客户端:发送请求Request,获得响应Response,注意并没有绑定任何协议哦(http、tcp、udp、文件协议、本地调用都是阔仪的)。
public interface IClient<S extends ClientRequest, T extends IResponse>
// 执行请求并返回响应
public T execute(S request, IClientConfig requestConfig) throws Exception;
core包里没提供此接口的任何实现,在Spring Cloud
下有如下实现:
ClientRequest
表示适用于所有通信协议的通用客户端请求对象。该对象是immutable
不可变的。
public class ClientRequest implements Cloneable
// 请求的URI
protected URI uri;
protected Object loadBalancerKey = null;
// 是否是可重试。true:该请求可重试 false:该请求不可重试
protected Boolean isRetriable = null;
// 外部传进来的配置,可以覆盖内置的IClientConfig配置哦
protected IClientConfig overrideConfig;
... // 省略各种构造器
... // 生路各种get方法。注意:没有set方法,因为该实例不可变
// 判断该请求是否可以重试(重要)
public boolean isRetriable()
return (Boolean.TRUE.equals(isRetriable));
...
// 使用新的URI创建一个**新的**ClientRequest
// 它会先用clone方法去克隆一个,若抛错那就new ClientRequest(this) new一个实例
// 推荐子类复写此方法,提供更多、更有效的实施。
public ClientRequest replaceUri(URI newURI)
ClientRequest req;
try
req = (ClientRequest) this.clone();
catch (CloneNotSupportedException e)
req = new ClientRequest(this);
req.uri = newURI;
return req;
core包里无子类。Spring Cloud
下继承图谱如下:
IResponse
客户端框架的响应接口,请注意它是一个接口,而Request请求是类哦。
public interface IResponse extends Closeable
// 从响应中获得实体。若是Http协议,那就是Body体
// 因为和协议无关,所以这里只能取名叫Payload
public Object getPayload() throws ClientException;
public boolean hasPayload();
// 如果认为响应成功,则为真,例如,http协议的200个响应代码。
public boolean isSuccess();
public URI getRequestedURI();
// 响应头们
public Map<String, ?> getHeaders();
小细节:该接口并没有形如getStatus
获取响应状态码的方法,是因为它和协议无关,而响应状态码是Http的专属。
core包内并无此接口的实现,Spring Cloud
下的情况如下:
本地测试环境搭建
本着main方法是一切程序的入口,任何组件均可本地测试的原则,再加上Ribbon核心的设计本就是和协议无关的,所以关于Ribbon核心的讲解内容使用单元测试(非集成测试)的方式来完成,相信会更加有助于你对负载均衡的学习。
说明:本环境搭建聚焦于内核部分的测试,也就是ribbon-core
和ribbon-loadbalancer
,因为他俩均为协议无惯性、网络无惯性设计,因此均可通过本地方式达到测试目的,让一切均可测试。
- 自定义MyClient实现
/**
* 不具有负载均衡功能的一个Client
*
* @author yourbatman
* @date 2020/3/14 22:09
*/
public class MyClient implements IClient<ClientRequest, MyResponse>
@Override
public MyResponse execute(ClientRequest request, IClientConfig requestConfig) throws Exception
MyResponse response = new MyResponse();
response.setRequestUri(request.getUri());
return response;
- 自定义MyResponse实现
public class MyResponse implements IResponse
private URI requestUri;
public void setRequestUri(URI requestUri)
this.requestUri = requestUri;
@Override
public Object getPayload() throws ClientException
return "ResponseBody";
@Override
public boolean hasPayload()
return true;
// 永远成功
@Override
public boolean isSuccess()
return true;
@Override
public URI getRequestedURI()
return requestUri;
@Override
public Map<String, ?> getHeaders()
return null;
@Override
public void close() throws IOException
复制
- 最基础测试用例展示:
@Test
public void fun1() throws Exception
// client配置
IClientConfig clientConfig = DefaultClientConfigImpl.getClientConfigWithDefaultValues("DHY");
// Client客户端,用于发送请求(使用ClientConfig配置)
// 因为木有LB功能,所以要不要IClientConfig没什么关系
MyClient client = new MyClient();
// 执行请求,获得响应
MyResponse response = client.execute(createClientRequest(), null);
System.out.println(response.isSuccess());
复制
控制台输出:true
。这边是一个最简的本地测试环境,后面会基于此展开更多测试case
配置key管理
作为一个开源的库,需要使用配置来提高其弹性,因此Ribbon也有一套属于自己的配置管理,core包里记录着它的核心API。它需要管理大量的可识别的配置key
,这就是将要介绍的Ribbon对key的管理方式。
IClientConfigKey
用于定义用于IClientConfig
使用的key,注意它仅是key,而非k-v。
// 注意:泛型接口
public interface IClientConfigKey<T>
// 用于做Hash的字符串表现形式
public String key();
// key的类型,比如Integer.class
// 若不指定,会根据泛型类型自动判断出来
public Class<T> type();
复制
对该接口简单粗暴的理解:含义同普通的Object key
。它有一个子类:CommonClientConfigKey
,记录着通用的一些key们(太多了,40+个)。
CommonClientConfigKey
它是一个抽象类,所以IClientConfigKey
均以它的匿名子类的形式出现。
public abstract class CommonClientConfigKey<T> implements IClientConfigKey<T>
public static final IClientConfigKey<String> AppName = new CommonClientConfigKey<>("AppName");
public static final IClientConfigKey<String> Version = new CommonClientConfigKey<>("Version");
public static final IClientConfigKey<Integer> Port = new CommonClientConfigKey<>("Port");
public static final IClientConfigKey<Integer> MaxAutoRetries = new CommonClientConfigKey<Integer>("MaxAutoRetries");
public static final IClientConfigKey<Integer> MaxAutoRetriesNextServer = new CommonClientConfigKey<Integer>("MaxAutoRetriesNextServer");
... // 因为实在太多了,略
public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout");
public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout");
... // 因为实在太多了,略
// 此key是使用得最最最多的:通过配置的形式指定Server地址(可指定多个)
public static final IClientConfigKey<String> ListOfServers = new CommonClientConfigKey<String>("listOfServers") ;
说明:配置的含义是任何程序都不可忽视的一部分,所以关于每个key到底什么意思?默认值是什么?有何作用?这在后面配置章节专题里专门讲解
本类内部维护着非常多个public static
的常量,这样外面便可直接使用。但是再怎么多,也不可能涵盖所有,所以本类也提供了自定义构造key的方法:
CommonClientConfigKey:
// 根据字符串name,创建出一个IClientConfigKey实例
public static IClientConfigKey valueOf(final String name)
// 先从keys缓存里看看是否已经有了,若已经存在就直接返回
for (IClientConfigKey key: keys())
if (key.key().equals(name))
return key;
// 若没有现成的,那就创建一个新的返回
return new IClientConfigKey()
@Override
public String key()
return name;
@Override
public Class type()
return String.class;
;
支出这里的一个小"Bug":新建出来的key并没有放进缓存里,其实放进去是否会更好呢???
示例
@Test
public void fun1()
IClientConfigKey key1 = CommonClientConfigKey.valueOf("dhy");
IClientConfigKey key2 = CommonClientConfigKey.valueOf("dhy");
System.out.println(key1.key());
System.out.println(key1 == key2);
控制台输出:
dhy
false
可见,同一个String类型的key构建两次,得到的是两个不同的IClientConfigKey
实例,这么处理大多数时候对内存是不够友好的,这就是为何上面我认为它这是个小“bug”的原因。
总结
本文牛刀小试,介绍了ribbon-core
核心包里面的最核心API:IClient
,以及搭建好了本地测试环境,这对后续加入负载均衡逻辑提供基础支持。另外我们知道Ribbon它对key的管理使用的是IClientConfigKey
接口抽象,并且使用CommonClientConfigKey
管理着内部可识别的40+个常用key供以方便使用。
关于ribbon-core
的核心API第一部分就先介绍到这,下文继续。。。
以上是关于Ribbon核心API源码解析:ribbon-coreIClient请求客户端 - 02的主要内容,如果未能解决你的问题,请参考以下文章