- casio-core【核心代码模块】
- boostrap:启动辅助类(启动框架时,代码整洁)
- common:公用包
- annotation:注解(服务注册、消费、SPI)
- exception:自定义异常
RemotingException.class
:远程连接异常RpcException.class
:RPC调用异常
- extension:SPI,参照Dubbo实现,没有dubbo那么复杂
- utils:通用工具类
- cache:缓存实现(LRU和LFU)
- config:
- context:启动上下文相关(注册中心、服务提供者、服务消费者等等)
- parser:xml和yaml的解析
- domain
RpcMessage.class
:消息传递的数据通用类型,数据通过protocol包装后的类RpcRequest.class
:服务消费者请求RpcResponse.class
:服务提供者调用服务后返回结果RpcStatus
:状态和异常
- monitor:监控
- registry:注册中心相关
- cache:注册中心相关信息缓存,不需要重复调用注册中心
- nacos(未实现)
- zookeeper
ServiceDiscovery.class
:服务发现接口ServiceRegistry.class
: 服务注册接口
- remoting:远程连接
- http:http形式调用,类似Spring cloud
- netty:netty连接
- rpc:与远程连接模块区分,是远程连接前置和后置工作
- cluster:集群相关,负载均衡等
- filter:前置过滤
- protocol:协议,
com.report.casio.remoting.transport.netty.codec
调用 - proxy:代理,客户端和服务端
- casio-embed:内嵌其他应用(例:内嵌Zookeeper,不需要额外安装启动zk)
- casio-spring:结合Spring模块
- casio-spring-test:casio-spring模块的测试类
- casio-test:casio-core模块的测试类
RPC分为服务提供者和消费者两部分。
RPC调用流程:消费者通过类生成对应的URL,访问注册中心,拿到提供者所在的IP地址,通过负载均衡选择其中一个IP地址,进行连接(Netty),将服务名、方法名、参数等信息传递给提供者,提供者调用实际的实现类,将结果包装后返回给消费者,消费者将结果输出。
启动时扫描项目中 @Register
的类,将类注册进注册中心,这里就需要几个部分:注册中心配置类、注册中心连接工具类,扫描类、已经服务注册类。
目前只实现zookeeper注册中心,工具类采用 org.apache.curator
。
注册仿照dubbo的方式,但功能相对少了很多,因此路径也优化了很多。路径格式 casio/serviceName/provider
。
扫描类 AnnScanUtils
,没有使用 Spring 结合实现,这个扫描类通过路径递归查找判断扫描,可能存在问题。
NettyServer:等待消费者发送消息,调用后返回结果
服务发现类 ServiceDiscovery
:通过方法名生成对应路径,去注册中心获取ip,每次调用都需要访问注册中心,会对注册中心造成比较大的压力,因此第一次调用获取后将结果缓存起来,通过添加watcher,如果该节点发生变化,删除对应的缓存。缓存需要考虑超过cacheSize后的删除机制,这里参考dubbo
NettyClient:消费者比较复杂,首先你需要拿到对应IP的 Channel,进行消息发送,其次需要等待提供者将消息返回,这里的实现采用 Future 模式实现(不用通过阻塞的方式实现 wait、notify,多线程会出现问题)。
仿照 dubbo 实现,只实现了扫描文件获取对应的类,源码还有类属性的注入等实现。
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| BYTE | BYTE | BYTE | int | ........ | |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| magic | version| type | content length | content byte[] |
+--------+--------------------------------------------+--------+--------+--------+--------+--------+
magic
:固定为 0x27(00011011)
version
:区分协议解析器版本
type
:请求类型
content length
:消息长度
content byte[]
:消息内容
重试的设置最好是在提供者,因为提供者对业务熟悉,能够合理的设置重试次数。
dubbo的超时针对消费者,消费者轮询Future发现超时时,不再等待该返回结果,并不意味着该线程结束执行。
- Rpc涉及到类在网络上的传输,因此需要注意几点:序列化;不需要传输,但是本地需要用到的属性添加
transient
修饰符;类的长度丢失问题,涉及协议 - 在Rpc中会涉及到需要多线程的问题,因此
ConcurrentMap
的使用非常必要,在累加的时候需要使用AtomicInteger
。 CompletableFuture
类的使用,这个类在异步中使用非常强大。- LRU和LFU算法:一定程度上参考了dubbo。我的LRU是将所有被调用的service全部添加,这样会有频繁切换的缓存的问题,dubbo有一个Map记录service访问次数,达到某个阈值才会被添加到缓存。LFU算法通过Frequency table + 链表实现,具体参考
com.report.casio.common.cache.LFUCache
- 服务发现的缓存存在问题,测试时发现服务提供者断线之后,注册中心的节点存在延迟,这个时候调用会发现服务提供者并不存在,需要添加重试机制。
- 目前消息传递使用序列化和对象转byte数组实现,可以考虑其他,例:kyro、protobuff(GRPC)
- 启动的信息不知道该如何加载和放置,目前全部堆积在 config 下,存在一定问题。
- 配置文件的读取也没有实现,目前zookeeper等配置信息都是写死的
参考阿里文章:https://zhuanlan.zhihu.com/p/388848964
dubbo项目:https://github.com/apache/dubbo
几位大佬自己实现的RPC框架(相比我的完善许多,给我提供了许多思路):https://github.com/Snailclimb/guide-rpc-framework
联系方式:QQ1758619238