1 Star 0 Fork 4

pepsiss / T_RPC_Framework

forked from Ticsmyc / T_RPC_Framework 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
历史问题与优化记录.md 6.54 KB
一键复制 编辑 原始数据 按行查看 历史
Ticsmyc 提交于 2021-02-28 11:46 . 优化代码、更新文档和注释

[TOC]

对性能的优化

优化了一次容器扫描

与Spring整合后, 服务端的服务注册想用注解实现。

最开始使用的是applicationContext.getBeansWithAnnotation 这个方法。 在容器中的bean都实例化完毕后,又调用ApplicationContext的相关方法, 获取到所有标识了指定注解的bean,做服务发布。 无形中导致bean的两次遍历。

后来优化成使用BeanPostProcessor,在Bean实例化时就进行发布。

TCP粘包和拆包

通过定义协议,完美避免了。。。

一些设计错误

客户端的失败重连机制

fun.ticsmyc.rpc.client.transport.netty.NettyRpcClient的 45-53 行

客户端发送请求的方式: 先根据请求方法和所在的组,从nacos获取到服务提供者的ip和端口。 然后使用netty发起网络请求。

最初重试机制放在了拿到ip端口之后, 如果连接不成功,会重复连接。感觉也没什么问题。

测试时发现, 由于nacos的延迟,当服务提供者频繁上下线时,nacos中的信息不会及时更新,导致客户端拿到的ip和端口是过期的,多次重试仍连接不上。

最后修改为,每次重连都重新从nacos拉取一次服务提供者ip。

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

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

nacos服务注销的回调

给服务端提供了一个钩子函数,每次下线都注销nacos的信息(但是异常下线的时候仍然会有延迟)。

同时使用以下两个机制实现这个回调

Runtime.getRuntime().addShutdownHook DisposableBean

不知道哪个会生效...

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

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

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

  • 如果使用Object类型 接收反序列化后的Object,会变成String。 、
  • 所以只能在请求体里面加上原始的参数的类型,在反序列化之后判断是否反序列化正确。如果未正确反序列化,就序列化成二进制,根据原始类型再反序列化一次。

【这种场景下,使用基于二进制的序列化器更好】

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】。

RoundRobin轮询算法中,AtomicInteger越界的问题

每次cas交换AtomicInteger的值, 而不是简单的incrementAndGet(参考了Ribbon)

一些调试时的错误

idea自动调用toString的问题

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

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

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

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

RPC调用中特殊情况的处理

一个接口多个实现类

服务分组(类似dubbo)

一个实现类多个接口

增加了针对接口的注解@TRPCInterface

早期一些过度设计

static滥用和单例模式滥用

  1. 提供了多重序列化器供用户使用。 但是考虑到序列化器在系统中只需要一个,不需要每次都new, 就把他们都设计成static了
  2. 后来因为对static的一些误解,认为static常驻内存。为了避免几种序列化器都加载一次,又把static改成懒加载的单例模式。
  3. (似乎可以使用SPI机制)
    • 加入SPI之前: 使用枚举类+方法类的get方法,通过传入枚举类参数来拿出一个序列化器
    • 加入SPI之后, 通过读取配置文件中的序列化器名称,通过类加载器动态加载。

对于什么时候用static 什么时候用单例 还是没有结论。

Java
1
https://gitee.com/pepsiss/t-rpc-framework.git
git@gitee.com:pepsiss/t-rpc-framework.git
pepsiss
t-rpc-framework
T_RPC_Framework
master

搜索帮助