博客中代码地址:https://github.com/farliu/farpc.git
dubbo架构

项目结构介绍

项目结构
本节涉及博客中代码的三个module,farpc-registry(服务治理)、farpc-cluster(集群管理),farpc-common。

本章重点就是farpc-common。而SPI具体实现方案就是ExtensionLoader。本章大部分代码都从dubbo源码中提取。

我们在贴代码之前还是讲讲涉及到的类前后推理的逻辑。ExtensionLoader为SPI重要实现类,本章实现的简单的SPI其实有这个类就够了。但是在dubbo中还提供了两个辅助类,我觉得有必要就也加进来一起聊聊,一个是FarSPI注解,一个是Holder。

SPI实现

本章代码量有点大,下面贴代码会导致篇幅过长,我们针对涉及的几个都分别解析一下,具体代码大家可以上github自己clone。我们对照项目结构一个个分析。

项目结构

FarSPI

FarSPI注解用来标注接口的默认配置对象。

Holder

Holder用来包装对象实例,上两章中有提到dubbo中很多地方用到了double-check-lock,它虽然能保证并发,但是出现重排序时,调用者拿到的对象可能是一个未初始化的地址。所以使用Holder对其包装,利用volatile保证可见性

ExtensionLoader

ExtensionLoader做SPI重点,代码中都有标注有注释,应该很清楚。这里看一下怎么使用。

1
ILoadbalance extension = ExtensionLoader.getExtensionLoader(ILoadbalance.class).getExtension(loadbalance);

Property、PropertyUtil

这两个类是为了读取用户的配置写的,dubbo会解析xml,我这里由用户在application.properties中指定。

使用

SPI其实就靠上面几个类就能运行了,现在说说怎么使用,我在ExtensionLoader中读取META-INF/farpc/下面的文件,我们需要将SPI的配置放在该文件夹下,文件名字为接口的全路径。

例如:让SPI创建ILoadbalance的对象。在之前我们实现了两个ILoadbalance的方案RoundLoadBalanceImpl、RandomLoadbalanceImpl。
项目结构

以及在之前我们实现了两种注册中心,也可以交由SPI初始化对象

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void spiTest(){
ILoadbalance round = ExtensionLoader.getExtensionLoader(ILoadbalance.class)
.getExtension("round");
ILoadbalance random = ExtensionLoader.getExtensionLoader(ILoadbalance.class)
.getExtension("random");
System.out.println(round.getClass().getName());
System.out.println(random.getClass().getName());
}

------------------------------------
com.ofcoder.farpc.cluster.loadbalance.RoundLoadBalanceImpl
com.ofcoder.farpc.cluster.loadbalance.RandomLoadbalanceImpl

在项目中优雅使用

SPI扩展负载均衡

在项目启动时,Property类会加载用户的配置。我在代码中是由一个LoadbalanceFactory去调用SPI的。就是类似dubbo SPI的自适应机制,在dubbo中自适应机制是由dubbo生成代理类去完成对实现类调用的路由,我这里是直接写死由Factory去管理。

在之前讲述服务发现的代码时,我也留下了伏笔AbstractRegistrar,那么这一节,我们可以在AbstractRegistrar中调用LoadbalanceFactory。例如

1
2
3
4
5
6
7
8
9
10
public abstract class AbstractRegistrar implements IRegistrar {
public String discover(String service) {
List<String> providers = lookup(service);
ILoadbalance loadbalance = LoadbalanceFactory.getLoadbalance();
String select = loadbalance.select(providers);
return select;
}

public abstract List<String> lookup(String service);
}

然后在配置文件application.properties中,指定loadbalance方式,就可以由Factory路由要你需要的负载均衡算法。如下:

1
farpc.cluster.loadbalance=random

SPI扩展注册中心

按照上面的设计模式,我们其实还可以管理注册中心、调用协议、序列化方式,等等等等….例如,我们在使用SPI加载注册中心。

注册中心的地址和端口,一般也是不会变的,我也把它放在配置文件中,在AbstractRegistrar的构造中将注册中心的地址传给ZookeeperRegistrarImpl和RedisRegistrarImpl,交由他们各自去实现,并AbstractRegistrar来调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class AbstractRegistrar implements IRegistrar {
protected static final String FOLDER = "/faregistrys";
protected static final String SEPARATOR = "/";

public AbstractRegistrar() {
String address = Property.Registry.address;
init(address);
}

public String discover(String service) {
List<String> providers = lookup(service);
ILoadbalance loadbalance = LoadbalanceFactory.getLoadbalance();
String select = loadbalance.select(providers);
return select;
}

protected abstract void init(String address);

protected abstract List<String> lookup(String service);
}

application.properties

1
2
3
farpc.registry.protocol=zookeeper
farpc.registry.address=127.0.0.1:6379
farpc.cluster.loadbalance=random

完成上面的优化,我们调用时就可以极其简洁,并且新拓展其他注册中心也只要实现AbstractRegistrar的抽象接口就行,对已有的代码没有任何侵入。

1
2
3
4
5
6
@Test
public void spiTest() throws IOException {
IRegistrar registrar = RegistrarFactory.getRegistrar();
registrar.register("127.0.0.1:62880", IWelcome.class.getName());
System.in.read();
}

这一节没有对各个类的代码讲述,可能有点跳跃。不过代码很简单,稍微瞥一眼就行。这个系列,我们实现的RPC调用,意义在于了解整个调用过程。