[享学Netflix] 三十八、Ribbon核心API源码解析:ribbon-core(一)IClient请求客户端

软件设计有两种方式:一种方式是,使软件过于简单,明显没有缺陷;另一种方式是,使软件过于复杂,没有明显的缺陷

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

上篇文章整体上对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>${ribbon.version}</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-coreribbon-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("YourBatman");

    // 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("YourBatman");
    IClientConfigKey key2 = CommonClientConfigKey.valueOf("YourBatman");
    System.out.println(key1.key());
    System.out.println(key1 == key2);
}

控制台输出:

YourBatman
false

可见,同一个String类型的key构建两次,得到的是两个不同的IClientConfigKey实例,这么处理大多数时候对内存是不够友好的,这就是为何上面我认为它这是个小“bug”的原因。


总结

本文牛刀小试,介绍了ribbon-core核心包里面的最核心API:IClient,以及搭建好了本地测试环境,这对后续加入负载均衡逻辑提供基础支持。另外我们知道Ribbon它对key的管理使用的是IClientConfigKey接口抽象,并且使用CommonClientConfigKey管理着内部可识别的40+个常用key供以方便使用。

关于ribbon-core的核心API第一部分就先介绍到这,下文继续。。。
分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了362 篇原创文章 · 获赞 531 · 访问量 48万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104806463