RPC的故事
今天我跟几个RPC框架之间发生了一些事,情节跌宕起伏一波三折,不吐不快,以至于我这个从来不写博客的人也忍不住写下来分享一下。
背景
主系统部署在Windows上(.NET 4.5),子系统(.NET CORE)部署在各种Linux上(Ubuntu/CentOS/RHEL等),两者之间通信用RPC框架。由于涉及到文件的读写(目前最大是4M,不过不排除增大的可能),因此选用了基于Protocol Buffers的GRPC框架,毕竟PB的序列化效率在请求比较大的时候还是很重要的。在功能已经完成之后,发现如果项目中包含了Google.ProtocolBuffers这样的依赖的话,有可能会被公司内部的工具扫描到然后被challenge(之前我就觉得友商的名字好扎眼哈哈),为了避免不必要的麻烦,最好还是不要用它了。由于项目中别处已经采用了thrift,所以首选还是用thrift。有人要说了既然之前就用了thrift,你这次怎么还用GRPC你是不是傻?其实用GRPC是因为真的很适合这个场景,而且2个thrift又必须版本不同(历史遗留问题)会有一个很恶心的坑,就是为了避免这个坑才用的GRPC。结果现在还是要改回thrift,又要用一个workaround 去解决双thrift版本的坑……头疼!话不多说,LET'S GO。
THRIFT从入门到放弃
第一个拦路虎就是现在nuget上的THRIFT版本真是五花八门,官方那个package已经3年没更新了,不少热心网友自己编的版本不过都不支持NETCORE,只有一个叫 apache-thrift-netcore的版本可用,并且上传者是官方package的维护者之一,感觉是个尝鲜版。该版本的thrift compiler依然只支持-gen csharp而不是官网上的-gen netcore。其实不管哪个,生成出来的要么是同步方法,要么就是原始的BeginXXX EndXXX,因为他们压根就没打算支持async(这个issue开了4年了啊我的哥)。这样的话我得在调用的地方包上一层Task.Run,因为之前实现的代码都是异步,真是恶心……效率低点代码难看点,算了忍吧。
改了一会发现传输压缩没找到实现方式,心想没道理应该有的啊,查了下果然有TZlibTransport ,但是只支持Java Python等(貌似就C#没有),这个功能不是netcore版本阉割的而是从来就没有,没有压缩的话传输效率会低很多,只能先加个TODO,回头有空自己实现一个压缩的子类了。挺生气的感觉C#对于THRIFT真是二等公民,各种缺功能各种不更新。
其实server挺快就改完了,然后开始改client,虽然有上面说的不完美,但是都不是block issue。Client的话之前GRPC的client是thread safe的所以都是复用的,thrift的client查了下是线程不安全,只能给每次会话创建一个新的client……等等,每次创建一个新的?然后构造函数里每次还都创建一个socket?那调用方的端口还不爆炸?简单测试了一下,如果真的这么干的话会跟HttpClient不复用一样有端口TIME_WAIT问题(即使正确Dispose),在高峰时期端口会被用完然后崩盘(我估计现在项目里另外一个用thrift的地方就有这个问题)所以必须得用一个连接池……对不起C#又没有,只能自己写!掀桌!
再看GRPC
冷静了下来,要么还是用原来的方案算了,上面这么多问题就算都解决了或者忍了,还要经受稳定性考验。毕竟apache-thrift-netcore的package description里作者自己都写着 “use with caution…” 这省略号我真是无比的虚,检查了下GRPC的几台服务器已经run了好几个星期了一点问题都没。或者换个RPC框架吧,反正不是thrift就行。等等为什么要换RPC框架,我们只是不要protocol buffers而已,所以只要不用它做序列化就可以了。但是GRPC能脱离PB吗?确实有可能可以,一个是RPC,一个是序列化,完全可以把PB设计成RPC的一个功能模块或者说插件,而不是依赖绑定的关系。看了一眼GRPC,发现了新世界……GRPC居然不依赖PB!是的,我没有看错,GRPC不依赖PB!虽然跟我刚才想的一样,但是你怎么能不依赖呢,你对得起官网上的描述吗 Define your service using Protocol Buffers, a powerful binary serialization toolset and language?你对得起IDL compiler生成的文件吗,里面全是Google.Protobuf这个namespace下的类?那就把PB依赖删掉吧……果然编译不过,能过才见鬼了……可能,GRPC不依赖PB只是因为手抖搞错了?不应该啊,从设计角度来讲,他们也确实不应该绑定,不过移除了PB也确实会编译不过是事实……等等!编译不过的其实都是IDL compiler生成的文件,这些文件里包含了序列化的行为所以是依赖PB的,移除PB必然会编译错误!所以说如果有其他的序列化组件的话,应该有自己的IDL compiler才对(甚至有自己的IDL语法),这样的话就说的通了,GRPC应该是这种设计思路。问题是……谁没事去给你做一个序列化组件啊,你都已经有PB了啊!
Bond拯救世界
抱着不见棺材不落泪的心态我随便搜了下,我去还真有个叫 Bond 的项目,看了下IDL,简直良心!各种贴心小棉袄,简直就是为C#量身定做的!尝试了几个demo,立刻就爱上了它!不仅更新很及时昨天还发布了一版,而且到处都能感受到C#是被放在第一位的,之前thrift带来的不爽一扫而光!顺便说一下Bond 也带一个RPC框架叫 Bond Comm,不过官方说Comm已经废弃了推荐配合GRPC试用。所以我把原来PB的几个IDL文件用Bond的规范重写一遍,其他什么都不用改就可以了……完美啊完美!
结语
其实我主要是想说,thrift真心不适合C#,如果你还在用它,赶紧去看看有没有我上面说的那几个问题。就算没有,还是早日改成GRPC吧,从C#角度看GRPC各方面体验都好很多。序列化的话可以用PB,但是我知道Bond以后我都会选择Bond,因为它的IDL更适合C#(毕竟微软家产品肯定.NET优先)。光从IDL来说,thrift还是比PB更好用一些,尤其是PB现在都用proto3了,proto2正在淘汰中一些功能马上就被砍掉(比如optional field),PB的IDL是简单至上,大道至简的感觉(可能也正是这个原因它的多语言支持很好吧)。Bond的IDL光从C#的角度来讲是比PB和thrift要强大的(居然还支持attribute你敢信),但是有得必有失,可能因为这些它对一些其他语言的支持就未必友好(这个是我猜的)。
说完IDL再说说RPC,这方面GRPC真心完爆Thrift。Thrfit的各种 Transport/Protocol 以及你能看到的任何 Txxx 类,看起来可以高度自定义实则是过度设计,因为很少有人去实现自己的 Transport和Protocol,RPC框架应当把这类细节对使用者隐藏掉。GRPC这方面做的很好,HTTP2足够标准,足够效率,足够应付可见的未来出现的其他可能,使用者只要关注自己的实现就可以,传输协议什么的,RPC框架会帮我做的很好,至少肯定比我自己做的好,我不需要关心,我只要相信它就可以。还有一个我印象很深的就是Error handling。GRPC中只有一种Exception就是RpcException,用StatusCode进行分类。服务器端无论过程中是什么Exception,在出口处catch然后根据类型赋予不同的StatusCode和Message。就算请求根本没有到服务器端,client端也会有对应的RpcException,这样在client端handle起来就很舒服很统一。Thrift允许自定义Exception,看起来很强大,但是为了client端处理起来容易,通常还是得有一个GeneralException(或者间接达到这个效果),往往最终这个GeneralException就是GRPC里的RpcException的作用。当请求无法达到服务器时,Thrift client端的异常就是五花八门了,完全取决于transport,各种各样的花式Exception,然而client端其实只需要知道是connection failure就可以了。
总的来说thrift给我的用户体验很差,就如同几年前一样,这么多年一点长进都没(事实上确实也没有新版本,无奈~) GRPC+PB 是更好的选择,对于C#来说GRPC+Bond可能会更适合。以上结论只针对C#/.NET,对于其他语言的开发体验不做评价。
RPC的故事的更多相关文章
- Redola.Rpc 的一个小目标
Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Complete requests: 20000 ...
- 你应该知道的 RPC 原理
作者:伯乐在线 - meituanalibaba 网址:http://blog.jobbole.com/92290/ 在校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用 ...
- 一个故事讲清楚NIO
转载请引用:一个故事讲清楚NIO 假设某银行只有10个职员.该银行的业务流程分为以下4个步骤: 1) 顾客填申请表(5分钟): 2) 职员审核(1分钟): 3) 职员叫保安去金库取钱(3分钟): 4) ...
- 你应该知道的RPC原理
你应该知道的RPC原理 在学校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服务消费方和服务提供方是本地调用关系. 而一旦踏入公司尤其是大型互 ...
- 基于RPC原理的dubbo
在校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服务消费方和服务提供方是本地调用关系. 而一旦踏入公司尤其是大型互联网公司就会发现,公司的系 ...
- RPC 原理的前生今世
(如果感觉有帮助,请帮忙点推荐,添加关注,谢谢!你的支持是我不断更新文章的动力.本博客会逐步推出一系列的关于大型网站架构.分布式应用.设计模式.架构模式等方面的系列文章) 在校期间大家都写过不少程序, ...
- C# 的轻量级 RPC 框架
Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Comple ...
- 一个故事讲清楚NIO(转)
转载请引用:一个故事讲清楚NIO 假设某银行只有10个职员.该银行的业务流程分为以下4个步骤: 1) 顾客填申请表(5分钟): 2) 职员审核(1分钟): 3) 职员叫保安去金库取钱(3分钟): 4) ...
- 那些年,我们追过的RPC
1974年冬,互联网大师 Jon Postel发表了RFC674:“Procedure Call Protocol Documents,Version 2”,尝试定义一种在包含70个节点的网络中共享资 ...
随机推荐
- [Delphi]接口认识
Delphi中的接口用 interface 进行声明.接口是针对行为方法的描述,而不管他实现这种行为方法的是对象还是别的什么东西.因此,接口和类的出发点是不一样的,是在不同的角度看问题. 接口通过GU ...
- MyBatis 一级缓存避坑
MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选: package org.apache.ibatis.session; /** * @autho ...
- 菜鸟的Xamarin.Forms前行之路——实现按钮的字体图标(可扩展)
在实际的APP中,带有图标的按钮用到地方还是蛮多的,字体图标往往能更快更生动的传达信息,并且相对于背景图片,字体图标也有着绝对的优势,所以实现按钮的字体图标是值得尝试的. 实现方法:各平台自定义渲染按 ...
- CentOS下Docker与.netcore(一) 之 安装
CentOS下Docker与.netcore(一) 之 安装 CentOS下Docker与.netcore(二) 之 Dockerfile CentOS下Docker与.netcore(三)之 三剑客 ...
- [HNOI2004]宠物收养所
题目链接:戳我 其实也就是一个splay而已了. 但是一定要注意这种需要计算的,刚开始insert的时候插入极大值极小值的时候不要让它爆掉int.......(比如我刚开始就写了一个214748364 ...
- 语法解析 rs.next()
ResultSet.next()方法将指针从当前位置下移一行.ResultSet 指针最初位于第一行之前:第一次调用 next 方法使第一行成为当前行:第二次调用使第二行成为当前行,依此类推. 如果新 ...
- 接口测试:添加header信息
一.获取所有学生信息的接口文档内容 二.使用postman进行测试 选择请求方式.填写URL.填写Headers下的参数值(key是Referer.value是接口文档中的value值) 三.使用jm ...
- linux安装报错之:ifconfig command not found解决
问题描述: 用虚拟机VMware安装linux系统(镜像文件是从官网下载的CentOS-7.0-1406-x86_64-DVD.iso), 在安装完成之后,输入ifconfig命令报错:ifconfi ...
- mongodb获取具体某一天的查询语句
比如我要查询截止日期为2010-12-21这一天的数据记录.方法一. db.集合名.find({con_date:new Date("2010/12/21")})1方法二. db. ...
- 现代 JavaScript 框架存在的主要原因
简评:现代 JavaScript 框架的出现最主要是解决哪个问题?这篇文章很好的解释了这个问题. 我见过许多人盲目地使用像 React,Angular 或 Vue.js 这样的现代框架.这些框架提供了 ...