thrift 大list序列化过慢引发的客户端耗时记录和服务端不一致问题
一、问题背景
最近在工作上遇到了一个问题,客户端说我们服务的返回长尾太长了,经常40+ms。找了一个trace发现,我们服务端记录这个trace从开始到返回,总共才花了6ms。多试了几个trace发现有同样的现象,这就很奇怪了。
二、问题分析
1. 请求链路
首先思考一下一个请求的整个链路都包含哪些阶段
如图所示,对于客户端来说,他们的耗时记录是在最上面两个过程中,但是服务端是最下面函数处理前后,两者差着序列化和tcp传输,所以猜测是不是这两个地方导致的问题。
2. tcp传输
我们的rpc框架用的是thrift 0.9.2,比较成熟了,所以优先考虑的是不是tcp传输过程中特别慢。如果是tcp的话最好的方法就是抓包了,在服务器上使用命令:
sudo tcpdump host 100.69.93.36 -i any -Xvv -w dump.pcap
获取到超时的请求和返回,用本地wireshark的Flow Graph下面的tcp flows来查看tcp的过程:
第二部分,省略了中间一部分数据传输:
可以看到,虽然传输的报文段很多,开始直接达到了客户端告诉我们的最大窗口值,但是网络传输时花费的时间并不长。只有两个地方时间较长(图中红框中的),比较诡异,分别是
- 服务端返回请求响应ACK之后,到第二个ACK过程(19ms)
- FIN之前的ACK到FIN(74ms)
分别分析,第一个情况其实就是服务端中函数处理时间,长点就长点吧。第二个是什么情况,花了这么久?我们知道FIN请求是在断开连接之后才会启动的,我们的例子中客户端是在请求服务端后立即就释放了,那么很有可能就是客户端在socket到反序列化过程中时间特别长。
2. 反序列化
我们用的是thrift,他的反序列化其实就是在recv_Func()中,截取一部分代码:
if (ftype == ::apache::thrift::protocol::T_LIST) {{this->coords.clear();uint32_t _size0;::apache::thrift::protocol::TType _etype3;xfer += iprot->readListBegin(_etype3, _size0);this->coords.resize(_size0);uint32_t _i4;for (_i4 = 0; _i4 < _size0; ++_i4){xfer += this->coords[_i4].read(iprot);}xfer += iprot->readListEnd();}isset_coords = true;} else {xfer += iprot->skip(ftype);}
发现针对list的情况,他是for循环顺序解析的,每个coords又是一个结构体,会继续递归进行read。在这前后我们加一个时间戳打印函数,发现每次coord的解析都需要0.1ms!!那我们返回500个coord的时候,顺序反序列化岂不是就消耗了50ms。
客户端请求的时候是批量请求的,可以理解为二维数组,那么外围又是一个顺序解析的list,里面顺序解析coord,必然会很慢,至此原因就找到了。
3. 问题验证
既然是大list引发的问题,那么我们就先减少一下list的数量,发现果然耗时降到了20ms。
4. 更多疑问
- 为什么只有反序列化慢呢?
后面经过验证,发现之前序列化也很慢,只是上面的例子中可能是客户端性能较差(docker容器),导致反序列化慢的情况放大了。而且经过网上的benchmark测试,thrift序列化的时间更久。 - 为什么会慢?
我们使用的是TBinaryProtocol通过查看thrift源码,每次调用自带数据类型时,read的时候会调用:
template <class Transport_>
uint32_t TBinaryProtocolT<Transport_>::readI64(int64_t& i64) {union bytes {uint8_t b[8];int64_t all;} theBytes;this->trans_->readAll(theBytes.b, 8);i64 = (int64_t)ntohll(theBytes.all);return 8;
}
我们使用的是TFramedTransport,trans_->readAll()会调用下面的代码:
/*** Shortcutted version of readAll.*/uint32_t readAll(uint8_t* buf, uint32_t len) {uint8_t* new_rBase = rBase_ + len;if (TDB_LIKELY(new_rBase <= rBound_)) {std::memcpy(buf, rBase_, len);rBase_ = new_rBase;return len;}return apache::thrift::transport::readAll(*this, buf, len);}
// return中readAll代码
/*** Helper template to hoist readAll implementation out of TTransport*/
template <class Transport_>
uint32_t readAll(Transport_ &trans, uint8_t* buf, uint32_t len) {uint32_t have = 0;uint32_t get = 0;while (have < len) {get = trans.read(buf+have, len-have);if (get <= 0) {throw TTransportException(TTransportException::END_OF_FILE,"No more data to read.");}have += get;}return have;
}return have;
}//trans.read函数
/*** Fast-path read.** When we have enough data buffered to fulfill the read, we can satisfy it* with a single memcpy, then adjust our internal pointers. If the buffer* is empty, we call out to our slow path, implemented by a subclass.* This method is meant to eventually be nonvirtual and inlinable.*/uint32_t read(uint8_t* buf, uint32_t len) {uint8_t* new_rBase = rBase_ + len;if (TDB_LIKELY(new_rBase <= rBound_)) {std::memcpy(buf, rBase_, len);rBase_ = new_rBase;return len;}return readSlow(buf, len);}
可以看到,这里有个memcpy内存拷贝,外面还有一个while循环,应该就是这个地方比较耗时了。
三、解决方案
thrift协议我们没法直接改动,这样我们可以通过如下几种方案处理:
- 通过缩小返回的大小来处理,但是有的时候业务不允许。
- 客户端批量请求改为并行请求,不把时间耗费在list、map等数据结构的串行反序列化上。
- 既然是list和map引发的问题,那我们可以不用list和map,把list和map先使用pb等方式序列化成二进制流或者字符串,保证反序列化的时候不用串行遍历。
thrift 大list序列化过慢引发的客户端耗时记录和服务端不一致问题相关推荐
- 大淘宝服务端技术干货沉淀和总结
网络基础 TCP三次握手 三次握手过程 客户端--发送带有SYN标志的数据包--服务端 一次握手 Client进入syn_sent状态 服务端--发送带有SYN/ACK标志的数据包--客户端 二次握手 ...
- C# Socket服务端与客户端通信(包含大文件的断点传输)
步骤: 一.服务端的建立 1.服务端的项目建立以及页面布局 2.各功能按键的事件代码 1)传输类型说明以及全局变量 2)Socket通信服务端具体步骤: (1)建立一个Socket (2)接收 ...
- 知物由学 | 从应用端到服务端,设备指纹生成算法大变革
设备指纹是用来标识手机或者浏览器的唯一 ID.基于这个 ID,我们能够精确定位一个设备,将使用该设备的全部数据进行关联.结合更加多样化的数据,黑产设备的识别准确性也将大幅提升. 设备指纹从何而来?一般 ...
- java服务端开发 php_PHP使用thrift做服务端开发
php中文网最新课程 每日17点准时技术干货分享 php使用thrift做服务端开发 thrift采用接口描述语言定义和创建服务,用二进制格式传输数据,体积更小.效率更高,对于高并发.数据量大和多语言 ...
- jQuery中的ajax、jquery中ajax全局事件、load实现页面无刷新局部加载、ajax跨域请求jsonp、利用formData对象向服务端异步发送二进制数据,表单序列化(异步获取表单内容)
jQuery中使用ajax: 在jQuery中使用ajax首先需要引入jQuery包,其引入方式可以采用网络资源,也可以下载包到项目文件中,这里推荐下载包到文件中:市面上有多个版本的jQuery库,这 ...
- PostgreSQL 30天 培训视频(SQL基础,备份恢复,HA,服务端编程,大数据,内核,应用案例)
Postgres2015全国用户大会将于11月20至21日在北京丽亭华苑酒店召开.本次大会嘉宾阵容强大,国内顶级PostgreSQL数据库专家将悉数到场,并特邀欧洲.俄罗斯.日本.美国等国家和地区的数 ...
- 服务端thrift 使用非阻塞式IO报异常Got an IOException in internalRead!
参考:https://blog.csdn.net/z13192905903/article/details/103181204 参考:https://blog.csdn.net/xmtblog/art ...
- postgresql最全整理资料,PostgreSQL 30天 培训视频(SQL基础,备份恢复,HA,服务端编程,大数据,内核,应用案例)
转载自:http://blog.163.com/digoal@126/blog/static/16387704020141229159715/ 希望通过这些视频帮到一些朋友, 同时对视频中的错误点烦请 ...
- 客快物流大数据项目(一百零七):物流信息查询服务接口开发解决方案
文章目录 物流信息查询服务接口开发解决方案 一.业务需求 二.系统架构演变 1.集中式架构 2.垂直拆分 3.分布式服务 4.面向服务架构(SO ...
最新文章
- session存储在redis/memcache/mysql
- 在游戏运营行业,Serverless 如何解决数据采集分析痛点?
- hazelcast入门教程_Hazelcast入门指南第5部分
- android sdk 8.1.0,OneAPM版本更新:Android SDK 1.0.8
- Flink-org.apache.flink.streaming.api.watermark.Watermark
- 微信小程序商城完整代码
- SCSI代码分析(5)SCSI驱动编程模式
- FreeMarker模板引擎实现页面静态化
- 高等数学教材上册复习
- 中国最令人崩溃的25个姓氏,排名第1位的,打死都想不到
- 作为零基础的新手,如何自学Java和JavaEE开发技术?
- 视频号计划,撑得起微博下一个十年?
- WT588F02B-8S(芯片代码C001_01)语音芯片在化妆品/保健品/食品行业保质期和使用期得应用解决方案
- 如何将pdf转换成ppt文件
- excel图表美化:用散点标记制作不一样的折线图
- java count sql_SQL优化之SELECT COUNT(*)
- 解析计算机科学与技术论文引言,计算机科学与技术专业毕业论文写作指导
- 微信小程序使用wx.downloadFile和wx.saveFile在真机调试和体验版没问题,线上却下载失败
- js 前端实现国际化配置
- 圣朱妮佩洛| San Junipero(4)