1 Star 4 Fork 4

Ticsmyc / T_RPC_Framework

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-2.0

T_RPC_Framework

一个rpc远程过程调用的框架。

使用方法

见项目 : T_RPC_Framework_Demo

步骤(基于3.8版本):

  1. 启动nacos

  2. 引入依赖

    <dependency>
        <groupId>fun.ticsmyc.rpc</groupId>
        <artifactId>t-rpc-all</artifactId>
        <version>3.8</version>
    </dependency>
  3. 在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 #注册中心地址
  4. 在配置类声明

    @EnableTRPC
  5. 提供服务:

    在想要提供的服务接口上声明

    @TRPCInterface //这一步是为了解决某个实现类实现了多个接口的情况 

    在想要提供的服务实现类上声明

    @TRPCService(group="t")   // group属性是为了处理一个接口想要注册多个实现类的情况消费服务
  6. 消费服务:

    通过@RpcClient注解即可注入rpc服务

    @RpcClient(group = "t")
    private HelloService helloService;

原理图

image-20201109152554001

目录说明

└─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

v3.8

  • 优化代码结构
  • 修复:SingletonFactory中错误的双重判断
  • 修复:对线程不安全的Kryo序列化器错误的单例使用方式
  • 优化:服务注销时,使用更粗粒度的锁
  • 新增:增加Nacos服务列表本地缓存机制
  • 新增:外部配置文件新增对Nacos注册中心地址的配置
  • 优化:static初始化顺序
  • 优化:rpc服务端Netty启动时间调整为IOC容器完全启动后,通过Listener实现
  • 修复:如果有多个网卡,可能导致ip地址获取错误的问题
  • 修复:打成jar包后,读取不到properties配置文件的问题

v3.7

  • 优化代码结构
  • 使用 Runtime.getRuntime().addShutdownHook 机制 和DisposableBean机制来保证系统正常退出时,从nacos中注销服务
  • 修改nacos中注销服务的方法为线程安全
  • 优化了使用方式,用户只需要在配置类中声明@EnableTRPC,即可自动注册发布服务、自动为接口注入实现类
  • 可以使用trpc.properties配置文件来配置端口、覆盖均衡器、序列化器
  • 修复:bootstrap.connect().sync()监听器线程同步错误,导致多次连接重试,最终与同一个服务器建立多个连接的情况。
  • 修复:json序列化器对心跳包进行序列化时的空指针问题
  • 修复:当Spring容器使用cglib为bean生成代理时,RpcClient不能正常注入的问题。
  • 修复:当Spring容器使用jdk动态代理为bean生成代理时,RpcClient不能正常注入的问题。
  • 优化:根据代理对象找到原始对象,进行RpcClient注入。
  • 修复:轮询负载均衡策略 int溢出 的问题
  • 修复:当调用代理类的Object方法 或者 代理类特有的方法时,不触发rpc逻辑

一些令人骄傲的设计和bug修复

还有一些不太骄傲的设计,在 历史问题与优化记录.md

一个接口对应多个实现类的处理

从AOP代理对象中找被代理对象

fun.ticsmyc.rpc.common.util.AopTargetUtils 这个工具类。 用于从代理对象中找到原始对象。

  • 在服务端发布服务时使用: 为了获取代理对象上的自定义注解,从而知道这个Service是哪个分组 。
  • 在客户端:为接口注入服务代理类时使用。遇到的问题之一是因为使用了@Transactional,导致这个Bean使用cglib代理,从而导致扫描不到@RpcClient注解
  • 为什么要用到这个

    • 在一个接口对应多个实现类的情况下,Service端注册服务时,使用的名称是【接口名称_所属服务分组】,需要区分这几个服务。 Client端生成代理类时,也需要明确得知指出调用的是这个接口的哪个实现类。
    • Service端的服务实现类所属服务分组 使用自定义注解在实现类上标注。 Client端的接口调用的实现类所属分组在字段上使用自定义注解标注。
    • 对于复杂的实现类,如使用了@Transactional注解的实现类,加入IOC容器时,会被Spring生成代理类,IOC容器中放的是代理类。 代理类上没有被代理类的注解。
    • 所以需要根据代理类找到被代理类,然后再用反射的方式读出代理类上的注解,才能获取到这个服务的分组。
  • 具体实现

    • 共有三种情况 ,可以通过AopUtils这个工具类中的方法判断。
      1. 未被代理 : 直接反射读注解即可。
      2. 使用JDK动态代理:根据代理类的生成规律,找到内部的被代理类,反射读取被代理类中的注解
      3. 使用cglib代理:同上
    • 这种方法其实并不可靠, 一般写动态代理,都在内部保存被代理类,但是Spring的动态代理内部保存的是TargetSource类型
      • TargetSource有多种实现方式, 如果是SingletonTargetSource,则内部就存一个单例的被代理类,但如果是其他类型,则代理类的结构会发生变化, 这种根据代理类找被代理类的方法就会失效。

在服务消费方为什么不能像MyBatis一样直接使用IOC容器进行注入,而要自定义注解手动注入

MyBatis用的是ImportBeanDefinitionRegistrar (被BeanFactoryPostProcessor处理), 获取注解上配置的元数据(basepackage路径)。Spring整合Mybatis的原理如下:

  1. 间接使用BeanFactoryPostProcessor,在IOC容器初始化第一阶段(注册BeanDefinition)结束后,进行扩展:
    1. 扫描所有的Mapper,拿到BeanDefinition。 这个BeanDefinition的id是对应的Mapper(可以注入)
    2. 修改BeanDefinition中保存的对象类型,为MyBatis内部定义的一个FactoryBean。
      • 这个FactoryBean重写getObject方法为: 调用SqlSession的getMapper方法
    3. 这时,对Mapper接口进行注入时,实际时调用那个FactoryBean的getObject方法,就可以获取到相应的Mapper。

不难发现,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。

json序列化时反序列化失败的情况

fun.ticsmyc.rpc.common.serializer.impl.JsonSerializer的 90-107行

json是文本序列化器,反序列化时如果不知道原始类型, 可能会导致反序列化失败。

  • 如果使用Object类型接收反序列化后的Object,无法识别原始的类型,会变成String或者其他奇怪的类型。
    • 如Date对象会反序列化为Long,嵌套的RpcRequest对象会被反序列化为LinkedHashMap。。。。
  • 所以只能在请求体里面带上参数的Class对象,在反序列化之后判断是否反序列化正确。如果未正确反序列化,就序列化成二进制,根据原始类型再反序列化一次。

【这种场景下,使用基于二进制的序列化器更好,以下是几种二进制序列化器的优劣测评】

  1. protobuf序列化器
    • 使用方式
      • 可以自己写.proto文件(每个类都要对应一个proto文件),然后使用他提供的代码生成器生成scheme(也是一个.java文件),加入项目中,性能高一点,但很麻烦,而且类的结构一变,就得重写proto文件
      • 引入protobuf-runtime,在运行时根据传入的类,使用字节码生成技术,动态生成该类。
        • 在本地使用并发Map存储,使用懒加载单例的双重验证机制存储生成过的schema。避免多次重复生成shcema。 初次性能低,但使用方便。
  2. Kryo序列化器
    • 只能在java中使用。性能很好。比kyro更高效的序列化库就只有google的protobuf了
    • 本身不是线程安全的,所以要存在ThreadLocal中。 不能做成单例的。
      • 遇到了ThreadLocal的内存泄漏问题!!!
  3. Hession序列化器
    • 编码后长度短,但性能低
    • 拿来凑数 不太了解

客户端连接同步错误,导致的一个服务端多个心跳包现象

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,会触发重连操作。 最终系统中会与这一个服务器维持多个连接,导致每次发送多个心跳。

BeanPostProcessor导致@Value失效

fun.ticsmyc.rpc.Config 这个类的static代码块

场景: 想要将服务端配置文件从static改成@Component。 使用properties文件编写配置,使用@Value进行注入。

使用InitializingBean进行赋值。 发现属性还处在配置文件引用阶段("${}"这样),没有替换成配置文件的内容。

  • @Value获取不到值的场景:

    • 将properties的值 使用@Value("${}")注入到 RpcProperties类中。
    • 将RpcProperties注入到另一个类中。 注入Config类时,只能获取到${}字符串。。。,注入其他类却可以正常获取
    • 最后发现,是BeanPostProcessor导致@Value失效 。 当把使用了@Value的Bean直接或者间接的注入到BeanPostProcessor中时,会导致@Value失效。即使BeanPostProcessor中根本没有用到@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】。

idea自动调用toString的问题

fun.ticsmyc.rpc.client.proxy.ServiceProxy 的 45-60 行

idea在debug时,会自动对类中属性调用toString,显示在界面上。

如果不做特殊处理,在对客户端的根据rpc服务接口生成的代理类调用toString时,也会触发rpc逻辑,导致发送了一个java.lang.Object_t的调用请求。 自然就请求错误了。

解决方法: 在动态代理的invoke方法中加入短路逻辑。 如果调用的是Object类的方法或者代理类特有的方法,就本地调用,不执行rpc逻辑。(MyBatis中MapperProxy的解决方案)

木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

一个基于Netty的RPC框架,使用Java实现,与Spring整合,提供SpringBoot Starter。是一个运行在传输层,不需要额外配置,基于注解开箱即用的rpc框架,结合了Dubbo的高性能OpenFeign简单易用的优点,提供rpc框架的第三种解决方案 展开 收起
Java
MulanPSL-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/TicsmycL/t-rpc-framework.git
git@gitee.com:TicsmycL/t-rpc-framework.git
TicsmycL
t-rpc-framework
T_RPC_Framework
master

搜索帮助