借鉴:https://github.com/Snailclimb/guide-rpc-framework
├─rpc-api
rpc的接口
├─rpc-client
rpc客户端
├─rpc-server
rpc服务端
├─rpc-common
核心实现
│--└─src
│------├─main
│------│--├─java
│------│--│--└─pro
│------│--│------└─gugg
│------│--│----------├─base
│------│--│----------│--├─extension
扩展类加载
│------│--│----------│--├─factory
单例工厂
│------│--│----------│--└─utils
│------│--│----------│------├─concurrent
线程池
│------│--│----------│------│--└─threadpool
│------│--│----------│------└─file
文件读取
│------│--│----------├─common
工具实体类
│------│--│----------├─config
配置(比如 zk的地址)
│------│--│----------├─hooks
钩子🐕
│------│--│----------├─remote
远程方法(例如服务注册/发现)
│------│--│----------├─rpcclient
⭐客户端方法
│------│--│----------│--├─loadbalance
负载均衡
│------│--│----------│--├─proxy
rpc接口代理
│------│--│----------│--└─socket
远程的socket实现
│------│--│----------└─rpcserver
⭐服务端方法
│------│--│--------------├─handler
请求处理(反射执行)
│------│--│--------------└─provider
服务治理(服务的储存/注册)
│------│--└─resources
│------│------└─META-INF
└─-----│----------└─extensions
扩展类
- 找个zookeeper 可以远程 也可以本机
- 要修改客户端和服务端的配置文件properties中的zk地址
- 逐个启动server和client即可
服务端的调用实现 首先服务端要注册通过 SocketRpcServer 执行 注册和 socket的监听
-
关于注册 注册通过 ServiceProvider (单例)来实现
- 先将 <服务的properties序列化结果,要调用的方法所属类的实例>,放入我们的map中
- 调用Curator实现的zookeeper的客户端实现zookeeper的服务注册。
-
关于监听 监听通过tcp/udp层面的socket来监听(while死循环),启动前执行钩子函数清除所有线程池
- 一旦监听到,就新建一个自己创建的自定义的Runnable线程,传入socket,并在线程中传入RpcRequestHandler(单例)
- 在实现的run()中: 在RpcRequestHandler中,使用反射完成给定对象和方法/参数对方法的调用。
- 需要注意的是 ,这里使用了单例工厂的写法,而且很多地方写的单例不安全。
- 统一的参数没写好,比如socket本地暴露的接口定义不规范
- 对多线程的处理,是值得借鉴的
- 对扩展类的处理,那边看不懂
主要是通过写服务发现,到zookeeper上找服务名<组名,版本号,方法名>对应的服务地址<ip,port>, 然后通过动态代理接口类,实现在调用接口后远程调用(socket/netty)来返回数据;
- 关于服务发现 发现是通过发现 ServiceRecovery 来实现
- 首先根据调用的类的类名,组名 ,版本号 生成zookeeper的键值,
- 然后获取对应的子节点(一堆socket三元组),通过一致性hash算法或者随机算法,选取一个作为目标地址
- 关于远程调用,使用 RpcRequestTransport 接口对应的实现类(socket/netty,取决于服务端的监听方式)来发送request,并返回
- 在服务发现时-> 选取服务-> 负载均衡—> 一致性hash
- 调用服务时,通过动态代理服务接口生成代理类实现调用
- 需要注意序列化方式!
- 对于
一致性hash
(含虚拟节点)的实现
static class ConsistentHashSelector {
// 使用Treemap作为哈希环的数据结构
private final TreeMap<Long, String> virtualInvokers;
private final int identityHashCode;
ConsistentHashSelector(List<String> invokers, int replicaNumber, int identityHashCode) {
this.virtualInvokers = new TreeMap<>();
this.identityHashCode = identityHashCode;
for (String invoker : invokers) {
for (int i = 0; i < replicaNumber / 4; i++) {
// 每四个一组获取摘要
//根据md5算法为每4个结点生成一个消息摘要,摘要长为16字节128位。
byte[] digest = md5(invoker + i);
for (int h = 0; h < 4; h++) {
// 设置4个虚拟节点,对摘要进行hash计算
// 随后将128位分为4部分,0-31,32-63,64-95,95-128,
// 并生成4个32位数,存于long中,long的高32位都为0
long m = hash(digest, h);
virtualInvokers.put(m, invoker);
}
}
}
}
static long hash(byte[] digest, int idx) {
return ((long) (digest[3 + idx * 4] & 255) << 24 | (long) (digest[2 + idx * 4] & 255) << 16 | (long) (digest[1 + idx * 4] & 255) << 8 | (long) (digest[idx * 4] & 255)) & 4294967295L;
}
public String select(String rpcServiceName) {
byte[] digest = md5(rpcServiceName);
return selectForKey(hash(digest, 0));
}
public String selectForKey(long hashCode) {
Map.Entry<Long, String> entry = virtualInvokers.tailMap(hashCode, true).firstEntry();
if (entry == null) {
entry = virtualInvokers.firstEntry();
}
return entry.getValue();
}
}