krpc-rust
一个最像RPC框架的Rust-RPC框架https://github.com/kwsc98/krpc-rust
刚刚学习Rust语言或者没怎么了解Rust-RPC框架的同学,可能以为又是一个标题党了,但实际上了解过这部分的同学都知道,目前来说主流的Rust-RPC框架和实际定义的RPC框架还是有着很大的差别。我们先看一下隔壁Java是如何实现的,就拿本项目Java版本 krpc-java 举例,有兴趣学习Java-RPC框架的同学别忘了点个Star~
实现一个基于netty单路复用网络模型的rpc框架,支持spring-boot启动,支持zookeeper,nacos注册中心。
public interface ExampeService {
ResponseDTO doRun(RequestDTO requestDTO);
}
配置application.properties krpc.registeredPath = nacos://114.116.3.221:8848
@KrpcResource(version = "1.0.1",timeout = 1000)
private ExampeService exampeService;
配置application.properties krpc.registeredPath = nacos://114.116.3.221:8848 krpc.port = 8082
@KrpcService(version = "1.0.1")
public class ExampeServiceImpl implements ExampeService {
@Override
public ResponseDTO doRun(RequestDTO requestDTO) {
ResponseDTO responseDTO = new ResponseDTO();
responseDTO.setDate(new Date(requestDTO.getDate().getTime() + (long) requestDTO.getNum() * 60 * 60 * 1000));
return responseDTO;
}
}
我们看到只需要定义一个接口,然后Server端来实现这个接口,Client端给接口加一个注解,就可以进行RPC的调用了,这是因为Java拥有一个大杀器就是运行时反射,可以很轻松的在运行时对类进行增强,但是同样这也是Java的一大缺点就是因为运行时存在导致程序执行降低,那么以高性能著称Rust当然不存在运行时,但因此也缺少了运行时反射这一功能,那么目前主流的Rust-RPC框架是怎么解决这个问题的?目前市面上有两大产品分别是阿里的Dubbo和字节的Volo,首先我们看 Dubbo 怎么做的吧。
Dubbo 快速入门章节中介绍了Dubbo Rust的使用方法,其实主要是分为三个部分
第一部分定义接口,dubbo目前支持很多协议,其中还支持gRPC协议,其中Rust版本就是通过ProtoBuf协议来实现接口
avatar
第二部则是通过定义文件实现相关的Rust代码,因为Rust没有运行时,所以Client调用时没有办法通过动态代理的方式生成client类,而dubbo的解决方法就是通过定义接口内容生成相关的Client调用代码,来"降低"使用者的使用成本。
avatar
第三部分则是编写相关的Server端代码逻辑,然后通过生成的Client代码进行RPC调用
字节的Volo其实大体也是这个思路,通过IDL定义接口,然后通过脚手架脚本生成调用相关代码。有兴趣的同学可以看一下 Volo-grpc 的快速开始
avatar
总结下来就是通过接口定义使用脚本生成调用代码和服务接口,然后进行Server端业务实现和Client调用。
这样看下来Dubbo和Volo的实现,尤其对比Java版本的实现来说,是不是离真正的RPC框架还有很大的差距,包括还存在很多的问题,比如
// #[async_trait]
#[async_trait]
impl Greeter for GreeterServerImpl {
async fn greet(
&self,
request: Request<GreeterRequest>,
) -> Result<Response<GreeterReply>, dubbo::status::Status> {
println!("GreeterServer::greet {:?}", request.metadata);
Ok(Response::new(GreeterReply {
message: "hello, dubbo-rust".to_string(),
}))
}
}
let req = volo_gen::proto_gen::hello::HelloRequest {
name: FastStr::from_static_str("Volo"),
};
那么Rust没有运行时反射是不是也是个"缺点"?目前来看确实是这样的,两大厂都只能交出这么一个不令我们满意的答案,Java有反射这个大杀器才在微服务领域独领风骚,那Rust有什么办法可以在微服务领域也挑战Java呢?那就不得不提Rust宏这个核弹级武器了。
Rust宏大家都戏称可以通过宏来实现另一种编程语言,可见宏的强大之处,我们都知道宏是作用于编译期,那么我们就拿宏来实现一个编译期的反射不就行了吗?事实也的确可以,说了上面那么大一段废话,下面接入正题,看一看 krpc-rust
是如果进行RPC调用的
use krpc_core::server::KrpcServer;
use krpc_macro::krpc_server;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug)]
struct ReqDto {
str: String,
}
#[derive(Serialize, Deserialize, Default)]
struct ResDto {
str: String,
}
#[derive(Clone)]
struct TestServer {
_db: String,
}
krpc_server! {
TestServer,
"1.0.0",
async fn do_run1(&self,res : ReqDto) -> ResDto {
println!("{:?}" ,res);
return ResDto { str : "TestServer say hello 1".to_string()};
}
async fn do_run2(&self,res : ReqDto) -> ResDto {
println!("{:?}" ,res);
return ResDto { str : "TestServer say hello 2".to_string()};
}
}
#[tokio::main(worker_threads = 512)]
async fn main() {
let server: TestServer = TestServer {
_db: "我是一个DB数据库".to_string(),
};
KrpcServer::build()
.set_port("8081")
.add_rpc_server(Box::new(server))
.run()
.await;
}
use krpc_core::client::KrpcClient;
use krpc_macro::krpc_client;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
lazy_static! {
static ref CLI: KrpcClient = KrpcClient::build("http://127.0.0.1:8081".to_string());
}
#[derive(Serialize, Deserialize, Default, Debug)]
struct ReqDto {
str: String,
}
#[derive(Serialize, Deserialize, Default,Debug)]
struct ResDto {
str: String,
}
struct TestServer;
krpc_client! {
CLI,
TestServer,
"1.0.0",
async fn do_run1(&self,res : ReqDto) -> ResDto
async fn do_run2(&self,res : ReqDto) -> ResDto
}
#[tokio::main(worker_threads = 512)]
async fn main() {
let client = TestServer;
let res = client.do_run1(ReqDto{str : "client say hello 1".to_string()}).await;
println!("{:?}",res);
let res = client.do_run2(ReqDto{str : "client say hello 2".to_string()}).await;
println!("{:?}",res);
}
我们直接运行一下来看
avatar
这是不是才是RPC框架因有的样子?看到这里的同学是不是得本项目点个Star感谢支持,这个项目是一个很好的学习项目,同时也希望通过这个项目能让Rust在微服务领域同样有所发展。得益于Rust零抽象成本的概念,本项目当然也以高性能为目标,那我们就简单做个压力测试呗,因为Dubbo目前开源的版本示例我弄了一会儿没跑起来...那么我们就和Volo比一下。
https://github.com/kwsc98/krpc-rust
本次压测机器是MacBook Pro M1 16 + 512 压测内容是四百万请求,异步线程数client端和server端各512,因为RPC调用时IO密集型所有多开一些线程。下面是测试脚本
krpc-rust
测试结果 四百万请求,平均47秒跑完,每秒8.5w+QTS!!!而且内存占用也比较稳定
只能说不愧是Rust,Java表示实名制羡慕... 接着我们看Volo的表现。 额。。。出现点状况,测试100并发的时候还挺好好使,但是压测时内存和耗时异常高,因为为了压测关掉了日志打印,那么打开日志再看一下,结果
设置到500并发时,socket连接就出现了错误,500个请求只成功了139个,可能目前Volo还存在一些问题,不过影响不大,我们已经证明了Rust在微服务领域其实是有机会干掉Java的。
如果感兴趣的同学可以一起讨论学习,目前本项目还有需要工作需要做,比如说异常的处理,多组件的支持,服务的注册和发现,但是框架骨架已搭建并验证完毕,未来可期~