一个直观简单、高可配置的 RPC 框架。编程语言为 Java,目前网络编程框架是 Netty,注册中心采用 zookeeper 实现。
api
服务消费端要调用的服务的接口,服务提供端存在实现接口的服务
common
多个模块可能会用到的 异常、实体、SPI、工具类 等
consumer
动态代理实现,利用注册中心进行服务发现
loadbalance
负载均衡
provider
服务发布,将服务注册到注册中心,针对服务请求通过反射调用实现类
registry
注册中心,实现将服务注册到注册中心,提供已注册服务发现功能
serialization
序列化反序列化
transport
网络传输,实现服务提供端和服务消费端通信,eg:发送 服务请求 和 解析返回 服务响应
test-client
测试用的 服务消费端,通过 consumer 中的代理调用想要调用的服务
test-server
测试用的 服务提供端,通过 provider 进行服务发布和服务的使用
由于在客户端这一侧我们并没有接口的具体实现类,就没有办法直接生成实例对象。这时,我们可以通过动态代理的方式生成实例,并且调用方法时生成需要的 RpcRequest 对象并且发送给服务端。目的是屏蔽调用过程中的 通信、负载均衡、容错 等。
服务端提供通过反射调用请求的方法,返回方法的返回值。
Java:1.8.0_291
zookeeper:apache-zookeeper-3.5.10、curator 4.2.0
netty:4.1.39.Final
序列化:jackson:2.11.0、kryo 4.0.2、protostuff 1.7.2
其他:lombok 1.18.24、logback 1.2.3
服务提供端
public class Server {
public static void main(String[] args) {
String host = null;
try {
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
int port = 9000;
AddService addService = new AddServiceImpl();
ServiceProvider serviceProvider = SingletonFactory.getInstance(
ServiceProviderImpl.class,
new Class[]{String.class, int.class},
new Object[]{host, port});
serviceProvider.register(addService);
RpcServer rpcServer = new NettyRpcServer();
rpcServer.start(port);
}
}
服务消费端
public class Client {
public static void main(String[] args) {
RpcProxyFactory rpcClientProxy = new RpcProxyFactoryJdkImpl();
AddService addService = rpcClientProxy.getProxy(AddService.class);
int addRes = addService.add(1, 2);
int addRes2 = addService.add(2, 2);
System.out.println("addRes : " + addRes);
System.out.println("addRes2 : " + addRes2);
}
}
通过 SPI,几乎所有组件均可使用配置替换,并且扩展方便。
目前可配置的项及可选字段:
- 网络编程框架(
rpc-consumer/src/main/resources/consumer.properties
中,client.transport
字段可选:netty) - 序列化算法(
rpc-consumer/src/main/resources/consumer.properties
中,serialization.algorithm
字段可选:jdk、json、kryo、protostuff) - 注册中心(
rpc-provider/src/main/resources/provider.properties
中,provider.registry
字段可选:zookeeper) - 负载均衡算法(
rpc-consumer/src/main/resources/consumer.properties
中,load.balance
字段可选:random、roundrobin)
若要对上述组件扩展其他框架或算法,均可 通过实现 他们的模块根目录下的接口来扩展,然后将 SPI 文件中添加对应的全类名,并修改配置文件对应字段即可使用。比如序列化算法,若添加新的序列化算法:
- 新建
rpc-serialization/src/main/java/cn/krone/rpc/serialization/Serializer.java
的实现类(eg:yyy
) - 然后将实现类
yyy
的全类名添加到接口命名的文件rpc-serialization/src/main/resources/META-INF/extensions/cn.krone.rpc.serialization.Serializer
中,这个文件里的内容就是这个接口的具体的实现类,格式为:xxx=cn.krone.rpc.serialization.yyy
- 修改配置文件
rpc-consumer/src/main/resources/consumer.properties
中的serialization.algorithm
字段为xxx
,serialization.algorithm=xxx
public class RpcRequest {
// 接口名
private String interfaceName;
// 方法名
private String methodName;
// 参数类型
private Class<?>[] paramTypes;
// 参数实际值
private Object[] args;
}
public class RpcResponse {
// 状态码
private int statusCode;
// 出错的说明信息
private String message;
// 执行完成得到的返回结果数据,泛型
private Object data;
}
+------------------------------------+---------+---------+---------+---------+------------------------------------+
| Magic Number | version | msg type| ser type| com type| Data Length |
| 4 bytes | 1 byte | 1 byte | 1 byte | 1 byte | 4 bytes |
+-----------------------------------------------------------------------------------------------------------------+
| Data Bytes |
| Length: ${Data Length} |
+-----------------------------------------------------------------------------------------------------------------+
消息头 6个部分 共 12 bytes
魔数 4 bytes
版本号 1 byte
消息类型 1 byte;eg:request、response、heart
序列化类型 1 byte
压缩类型 1 byte
消息体长度 4 bytes
解决 客户端怎么知道服务端的地址 的问题。
服务端提供把 service Object 放入Map 的时候,注册到 zookeeper,节点的层级目录举例:
/rpc/cn.krone.rpc.api.AddService/127.0.0.1:9000
服务消费端 在代理的 invoke 方法中基于上述命名格式,到 zookeeper 中找服务的通信地址
- 服务消费端 动态代理
- 服务提供端 反射
- 自定义协议
- Netty 网络传输
- SPI
- 可配置 网络传输(暂只有 Netty)
- 可配置多种序列化(至少三种)算法(JDK、JSON、Kryo克里奥、protostuff、Hessian)
- 客户端 Channel 复用
- 可配置注册中心(暂只有 zookeeper)
- 可配置负载均衡策略(至少两种)(Random、轮询、一致性哈希)
- 可配置压缩算法(有时间暂定一种)
- +心跳机制,避免重连
- 容错
- spring 基于注解的自动服务注册