字节用啥RPC框架?| 青训营

947 阅读6分钟

Kitex简介

上面一篇关于RPC框架的文章让我们了解了RPC是远程调用服务,相比于网络传输,RPC框架可以帮助我们实现高性能的,负载均衡的简化远程调用服务。同时也了解了RPC框架的分层设计和主要性能指标。那么平时大家刷抖音的时候用的是啥RPC框架呢,下面让我们一起来了解一下字节内部使用的RPC框架,Kitex。

Kitex的整体架构

  • Kitex Core是Kitex的核心组件,定义了框架的层次结构,接口,还有接口的默认实现。

如图所示,在Kitex Core中,最上面的client和server是对用户暴露的,client/server option的配置都在这两个package里提供。还有client/server的初始化。在之前介绍kitex_gen生成代码时,可以看到里面有client.go和server.go,虽然在初始化client时调用的是kitex_gen中的方法,其实就是对这里client/server的封装。

client/server下面是框架治理层面的功能模块和交互元信息,remote是对端交互的模块,包括编解码和网络通信。

  • Kitex Byted是对字节内部的扩展,继承了内部的二方库和与字节相关的非通用的实现。

在之前介绍高级特性中关于如何拓展Kitex中提过,byted部分是在生成代码中初始化client和server时通过对suite集成进来的,这样实现的好处是与字节的内部特性解耦,方便后续开源拆分。

  • Kitex Tool是与生成代码相关的实现。

包括了id解析、校验、代码生成、插件支持、自更新等,未来生成代码的逻辑还会进行一定的拆分,便于给用户提供更友好的扩展。

image.png

字节自研网络库 - Netpoll

背景

  • Go语言RPC框架的原生库无法感知链接的状态,在使用链接池时,池中存在失效链接,影响链接池的复用。
  • Go语言原生网络库使用一个链接占用一个goroutine,链接效率低下,影响性能。

对比

  • Go Net使用边缘触发模式(ET),而Netpoll使用水平触发模式(LT)

ET模式(边缘触发): 在ET模式下,当一个文件描述符变为"就绪"状态(可读或可写)时,epoll会通知进程一次,而且只会通知一次,无论数据是否被读取或写入。这意味着如果进程没有完全处理就绪的数据,下次就绪时不会再次通知。ET模式要求应用程序持续读取或写入数据,直到出现非阻塞I/O的EAGAIN错误,以确保数据的完全处理。ET模式通常需要更多的处理逻辑,但也可以更高效地处理大量I/O事件。

LT模式(水平触发): 在LT模式下,当一个文件描述符变为"就绪"状态时,epoll会持续通知进程,直到数据不再就绪。这意味着只要文件描述符处于就绪状态,epoll会持续通知进程,无论是否已经处理了就绪的数据。这种模式下,进程可以在每次通知中只处理一小部分数据,然后继续等待下次通知。LT模式相对来说逻辑较简单,但可能会产生更多的通知次数

  • Netpoll在大包场景下会占用更多的内存。
  • Go Net只有一个epoll事件循环(因为ET模式被唤醒的少,且事件循环内无需读写,所以干的活少),而Netpoll允许有多个事件循环(循环内需要读写,干的活多,读写越重,越需要开多个Loops)
  • Netpoll链接数和Groutinue数量没有关系,和请求数有一定关系,但是有Gopool重用。
  • Go Net不支持Zero Copy技术,Netpoll支持管理一个Buffer池直接交给用户,且上层用户可以不使用Read接口而使用零拷贝技术读取接口对Buffer进行管理,实现零拷贝能力的传递。

零拷贝(Zero-Copy)是一种计算机编程中的优化技术,旨在减少数据在内存之间复制的次数,从而提高数据传输的效率和性能。在传统的数据传输中,比如从磁盘到内存,或者通过网络发送数据,通常涉及多次数据复制操作,这些复制操作会消耗CPU资源和内存带宽。零拷贝技术的目标是尽量减少或避免这些不必要的数据复制,从而提高数据传输的速度和效率。常见实现方法有:DMA,MMAP,共享内存等。

优势

  • Netpoll引入epoll主动监听机制,解决无法感知连接状态的问题。
  • Netpoll建立groutinue池,复用groutinue。

Netpoll基于epoll,同时采用Reactor模型,对于服务端使用主从Reactor模型。服务端的主reactor用于接收调用端的连接,然后将建立好的连接注册到某个从Reactor上,从Reactor负责监听连接上的读写时间,然后将读写事件分发到协程池中进行处理。

Reactor模式是一种事件驱动的编程模式,用于处理多个并发的I/O事件。在Reactor模式中,存在一个"Reactor",它负责监听多个I/O事件,如网络连接、数据可读或可写等。当某个事件就绪时,Reactor会调用相应的处理器(也称为"事件处理器")来处理这个事件。这种模式下,事件的分发和处理都是同步的,通常是通过多路复用(如select、poll、epoll)来实现的。

Proactor模式也是一个事件驱动的编程模式,但与Reactor模式有所不同。在Proactor模式中,存在一个"Proactor",它负责监听I/O事件,并在事件就绪后主动发起处理请求,然后将I/O操作的执行交给相应的处理器。换句话说,Proactor负责I/O的发起和完成,而处理器负责实际的事件处理。这种模式下,事件的分发和处理都是异步的,通常在操作系统提供的异步I/O机制(如Windows的IOCP、Linux的AIO)的基础上实现的。

  • Netpoll引入了Nocopy Buffer,实现了编解码层面零拷贝。

扩展性设计

目前,Kitex支持多协议,也支持灵活的自定义扩展。

image.png

image.png

性能优化

  • Codegen

预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝。 inline减少函数调用次数和避免不必要的反射操作等 自研了Go语言实现的Thrift IDL解析和代码生成器,支持完善的Thrift IDL语法和语义检查,并支持插件机制-Thriftgo

  • JIT

使用JIT编译技术(即时编译技术)改善用户体验的同时带来更强的编解码性能,减轻用户维护生成代码的负担。 *