今天我跟几个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的故事的更多相关文章

  1. Redola.Rpc 的一个小目标

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Complete requests: 20000 ...

  2. 你应该知道的 RPC 原理

    作者:伯乐在线 - meituanalibaba   网址:http://blog.jobbole.com/92290/ 在校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用 ...

  3. 一个故事讲清楚NIO

    转载请引用:一个故事讲清楚NIO 假设某银行只有10个职员.该银行的业务流程分为以下4个步骤: 1) 顾客填申请表(5分钟): 2) 职员审核(1分钟): 3) 职员叫保安去金库取钱(3分钟): 4) ...

  4. 你应该知道的RPC原理

    你应该知道的RPC原理 在学校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服务消费方和服务提供方是本地调用关系. 而一旦踏入公司尤其是大型互 ...

  5. 基于RPC原理的dubbo

    在校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服务消费方和服务提供方是本地调用关系. 而一旦踏入公司尤其是大型互联网公司就会发现,公司的系 ...

  6. RPC 原理的前生今世

    (如果感觉有帮助,请帮忙点推荐,添加关注,谢谢!你的支持是我不断更新文章的动力.本博客会逐步推出一系列的关于大型网站架构.分布式应用.设计模式.架构模式等方面的系列文章) 在校期间大家都写过不少程序, ...

  7. C# 的轻量级 RPC 框架

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Comple ...

  8. 一个故事讲清楚NIO(转)

    转载请引用:一个故事讲清楚NIO 假设某银行只有10个职员.该银行的业务流程分为以下4个步骤: 1) 顾客填申请表(5分钟): 2) 职员审核(1分钟): 3) 职员叫保安去金库取钱(3分钟): 4) ...

  9. 那些年,我们追过的RPC

    1974年冬,互联网大师 Jon Postel发表了RFC674:“Procedure Call Protocol Documents,Version 2”,尝试定义一种在包含70个节点的网络中共享资 ...

随机推荐

  1. BZOJ 2038 小z的袜子(莫队)

    Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……具体来说,小Z把这N只袜 ...

  2. 12、Semantic-UI之输入框

    12.1 基础输入框   在Semantic-UI中可以定义多个样式的输入框,可以将图片与输入框结合,输入提示信息文字,设置输入框的状态. 示例:定义基础输入框 用户名: <div class= ...

  3. Acrobat_8_Pro_SC 激活老是提示你输入的授权码无效

    假如安装了Adobe Acrobat Professional 8 的时候无法激活, 或在恢复安装时 Adobe Acrobat Professional 8 需要重新激活, 激活的时候,总是提示你输 ...

  4. C++派生类在构造和析构过程中做的事

    (一)构造时: (1)首先调用继承关系中第一个基类(最靠左边的)的构造函数,然后第二个,第三个,以此类推 (2)然后调用成员对象的构造函数,这个顺序按照定义的顺序,与构造函数初始化列表的顺序无关. ( ...

  5. php的数组汉字符串常用函数

    <?php// function add($a,$b,$func){// if(!is_callable($func)){// return false;// }// $m=$a+$b+$fun ...

  6. [linux] 查看SATA速度和具体设备

    查看SATA速度和具体设备 SATA 速度确认 方法一 dmesg |grep SATA 输出 [ 2.977661] ahci 0000:00:17.0: AHCI 0001.0301 32 slo ...

  7. Window 服务启动出错 14001

    在安装windows服务时,没有异常情况,但是在启动的过程中出现 14001错误. 错误 14001 应用程序无法启动 因为应用程序的并行配置不正确 有关详细信息 请参阅应用程序事件日志 或使用命令行 ...

  8. 慎用uniapp开发商业级应用

    官方的社区反馈问题只给解决简单的前端问题,涉及到IDE的问题长期没人回复没人认领 官方公布的各渠道联系方式都得不到回复,先后出现了两个无法解决的问题 第一个问题(现在你都可以去他们社区搜索,没人回复没 ...

  9. Asp.Net Core下的两种路由配置方式

    与Asp.Net Mvc创建区域的时候会自动为你创建区域路由方式不同的是,Asp.Net Core下需要自己手动做一些配置,但更灵活了. 我们先创建一个区域,如下图 然后我们启动访问/Manage/H ...

  10. 多线程DP

    Matrix Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...