一个rpc远程过程调用的框架。
见项目 : T_RPC_Framework_Demo
步骤(基于3.8版本):
启动nacos
引入依赖
<dependency>
<groupId>fun.ticsmyc.rpc</groupId>
<artifactId>t-rpc-all</artifactId>
<version>3.8</version>
</dependency>
在resources目录下,放入trpc.properties 配置文件 (可选)
port=8999 # 作为服务提供方,使用的端口号 (默认是8888)
loadbalancer=round # 作为服务消费方,使用的负载均衡机制,可选random(随机)和round(轮询),默认是随机
serializer=json # 作为服务消费方,发送rpc请求时使用的负载均衡器,可选kryo(默认)、json、protobuf、hessian
networkIO=netty #可用netty和socket 。 亲测netty更快
nameServiceAddress=127.0.0.1:8848 #注册中心地址
在配置类声明
@EnableTRPC
提供服务:
在想要提供的服务接口上声明
@TRPCInterface //这一步是为了解决某个实现类实现了多个接口的情况
在想要提供的服务实现类上声明
@TRPCService(group="t") // group属性是为了处理一个接口想要注册多个实现类的情况消费服务
消费服务:
通过@RpcClient注解即可注入rpc服务
@RpcClient(group = "t")
private HelloService helloService;
└─src
└─main
├─java
│ └─fun
│ └─ticsmyc
│ └─rpc
│ ├─client :客户端
│ │ ├─annotation:供客户端使用的注解
│ │ ├─proxy :动态代理
│ │ └─transport :网络传输层
│ │ ├─bio :基于socket实现的传输
│ │ └─netty :基于netty实现的传输
│ │ ├─codec :编码、解码器
│ │ └─handler :自定义的处理器
│ │ └─util:客户端使用的工具类
│ ├─common :通用
│ │ ├─entity :网络通信使用的实体类
│ │ ├─enumeration :枚举类
│ │ ├─exception :异常类
│ │ ├─factory :工厂
│ │ └─serializer :序列化器
│ │ └─impl
│ ├─nacos
│ │ ├─loadbalance 负载均衡器
│ │ │ └─impl
│ │ └─registry 注册中心
│ │ └─impl
│ ├─server :服务端
│ │ ├─annotation:客户端使用的注解
│ │ ├─handler :业务:根据收到的信息调用相应服务
│ │ ├─provider:服务端本地使用的服务注册
│ │ │ └─impl
│ │ └─transport :网络传输层
│ │ ├─bio
│ │ └─netty
│ │ ├─codec
│ │ └─handler
│ └─test
└─resources
其他版本记录见目录下 历史版本记录.md
还有一些不太骄傲的设计,在 历史问题与优化记录.md
fun.ticsmyc.rpc.common.util.AopTargetUtils 这个工具类。 用于从代理对象中找到原始对象。
- 在服务端发布服务时使用: 为了获取代理对象上的自定义注解,从而知道这个Service是哪个分组 。
- 在客户端:为接口注入服务代理类时使用。遇到的问题之一是因为使用了@Transactional,导致这个Bean使用cglib代理,从而导致扫描不到@RpcClient注解
为什么要用到这个
具体实现
MyBatis用的是ImportBeanDefinitionRegistrar (被BeanFactoryPostProcessor处理), 获取注解上配置的元数据(basepackage路径)。Spring整合Mybatis的原理如下:
不难发现,MyBatis的每个Mapper接口,都有且仅有一种确定的实现类, 所以对于每个Mapper接口都可以创建一个FactoryBean用于创建Bean。
而这个Rpc框架中,因为考虑到一个接口对应多种实现类的情况,每个接口可能需要多个FactoryBean,情况很复杂。所以干脆自定义注解,然后在BeanPostProcessor中扫描注解,然后根据接口名和分组,生成代理类,手动使用反射进行注入。
fun.ticsmyc.rpc.client.transport.netty.NettyRpcClient的 45-53 行
客户端发送请求的方式: 先根据请求方法和所在的组,从nacos获取到服务提供者的ip和端口。 然后使用netty发起网络请求。
最初重试机制放在了拿到ip端口之后, 如果连接不成功,会重复连接。感觉也没什么问题。
测试时发现, 由于nacos的延迟,当服务提供者频繁上下线时,nacos中的信息不会及时更新,导致客户端拿到的ip和端口是过期的,多次重试仍连接不上。
最后修改为,每次重连都重新从nacos拉取一次服务提供者ip。
fun.ticsmyc.rpc.common.serializer.impl.JsonSerializer的 90-107行
json是文本序列化器,反序列化时如果不知道原始类型, 可能会导致反序列化失败。
【这种场景下,使用基于二进制的序列化器更好,以下是几种二进制序列化器的优劣测评】
fun.ticsmyc.rpc.client.transport.netty.RpcRequestSender 的 42-49 行
该bug表现为: 【只与一个服务器建立了连接, 每次却有若干个心跳包发送】
bootstrap.connect(xxxx).addListener( ()->{
//代码1
this.channel = sync.channel();
}).sync();
//代码2
连接建立之后,代码1和代码2在两个线程中同步执行,无法保证代码1和代码2执行的先后顺序。
在代码1区域为channel赋值的操作可能晚于代码2发生,导致线程同步错误。 应该等到代码1执行完毕后,代码2再执行。
如果代码2先于代码1执行,因为此时channel还未赋值,检测为null,会触发重连操作。 最终系统中会与这一个服务器维持多个连接,导致每次发送多个心跳。
fun.ticsmyc.rpc.Config 这个类的static代码块
场景: 想要将服务端配置文件从static改成@Component。 使用properties文件编写配置,使用@Value进行注入。
使用InitializingBean进行赋值。 发现属性还处在配置文件引用阶段("${}"这样),没有替换成配置文件的内容。
@Value获取不到值的场景:
原因: BeanPostProcesser的实例化按照优先级分批进行,优先级高的先于优先级低的进行实例化。 在实例化时,内部依赖的Bean也会实例化。这些被依赖的Bean因为实例化太早,无法享受同等优先级以及更低优先级BeanPostProcesser的处理,所以@Value不会替换。
@Value被AutowiredAnnotationBeanPostProcessor处理,这个BeanPostProcessor也是PriorityOrdered级别的。
详细信息在PriorityOrdered接口的注释中有提到
* <p>Note: {@code PriorityOrdered} post-processor beans are initialized in
* a special phase, ahead of other post-processor beans. This subtly
* affects their autowiring behavior: they will only be autowired against
* beans which do not require eager initialization for type matching.
如果在bean启动的过程中需要通过BeanPostProcessor注册服务,所以必须保证在bean容器初始化的过程之前就读取好了配置文件的内容,所以还是用static比较合适,【但是static乱序初始化也容易造成nullptr】。
fun.ticsmyc.rpc.client.proxy.ServiceProxy 的 45-60 行
idea在debug时,会自动对类中属性调用toString,显示在界面上。
如果不做特殊处理,在对客户端的根据rpc服务接口生成的代理类调用toString时,也会触发rpc逻辑,导致发送了一个java.lang.Object_t
的调用请求。 自然就请求错误了。
解决方法: 在动态代理的invoke方法中加入短路逻辑。 如果调用的是Object类的方法或者代理类特有的方法,就本地调用,不执行rpc逻辑。(MyBatis中MapperProxy的解决方案)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
1. 开源生态
2. 协作、人、软件
3. 评估模型