[重磅] 如何更好地实现服务调用和消息推送

第四届阿里中间件性能挑战赛是由阿里巴巴集团发起,阿里巴巴中间(Aliware)、阿里云天池联合举办,是集团少有的工程性品牌赛事。大赛的初衷是为热爱技术的年轻人提供一个挑战世界级技术问题的舞台,希望选手在追求性能极致的同时,能深刻体会技术人的匠心精神,用技术为全社会创造更大的价值。


本文是亚军选手“做作业”的解题思路,来自南京理工大学的95后开发者

 

初赛部分


一、赛题重述及理解


初赛主要的考察点是实现一个高性能的http服务器,一套优秀的负载均衡算法。而协议转换仅需要对协议进行详细了解即可实现,属于基本技能考察。而服务的注册和发现参考demo中的实现即可,属于比较常用的处理逻辑。


针对高性能的http服务器,想要在竞赛环境下取得较好的成绩,使用常用通用http框架是较难取得高分的,因为consumer agent和consumer共享一个docker运行环境,在512连接测试环境下,系统资源就已经很紧张了,需要在舍弃一部分http特性的情况下才能将consumer性能发挥到极致。所以自己造轮子是一个比较合适的方案。Consumeragent的总体结构(所有逻辑基本集中在这)如图1所示。


640?wx_fmt=png

图1 Consumer Agent结构


二、网络编程模型的选择


高并发网络模型首选epoll,在连接数增多的情况下,其性能不会像select和poll一样明显下降,是由于内核对其进行了特殊优化,不是线性遍历所有连接。实际测试中发现,因为请求是在收到响应后才会继续发出,所以不仅要求高并发,同时要求低延迟,因为延迟叠加的原因也会导致QPS下降。


而我们使用的常见的异步网络库libuv、libevent等都是one loop per thread的,即一个线程一个事件循环,这种设计具有较高的吞吐量,但请求只能绑定到一个事件循环上,一旦请求不均匀,则会导致一核有难众核围观的情况,对于动态均衡线程负载很不适合。


基于此原因我决定自己造轮子,让多个线程处理一个epoll循环,让操作系统的线程调度均衡请求,同时所有连接均绑定到一个事件循环上,也便于处理。最终我选择使用多线程的et模式的epoll模型,整体框架代码如图2所示。


640?wx_fmt=jpeg

图2 多线程epoll框架


整体框架非常简洁,所有网络处理类仅需继承CepollCallback完成网络数据的读取和写入即可。


640?wx_fmt=png

图3 epoll回调抽象类


三、程序整体流程


Consumer端agent负责解析http请求转换为dubbo请求,以udp形式发给provider agent处理,provider agent收到udp后直接传给provider dubbo服务处理,将结果以udp形式回传,consumer agent处理后返回http形式结果。


640?wx_fmt=png


640?wx_fmt=png

图4、5 CA/PA处理流程


程序初始化的时候provider agent调用etcd接口将自己的服务注册,consumer agent调用etcd接口获取到注册的provider agent地址,由于采用udp协议通信,故不用提前建立连接。Provider agent端也仅在第一个请求到达时建立到provider的tcp连接,保证provider已经启动完毕可以接受请求。


四、中间协议设计


考虑到高吞吐量,低延迟,采用了udp协议,请求为单包单请求,数据格式为dubbo请求格式,相应包为单包多相应,数据格式为dubbo响应。即consumer agent端实现协议转换,provider agent端实现udp转tcp。


由于请求仅存在于一个包中,所以无需考虑乱序情况,直接将收到的数据包进行重组发到dubbo的tcp连接上,提供最大的吞吐量和最低的延迟。具体处理操作如图3。


640?wx_fmt=png

图3 udp重组转发tcp


同时为了减少系统调用,可以一直取udp包,当缓冲满后一次性写入tcp的缓冲中。同理在tcp转udp时,可以将数据凑成接近MTU,减少网络压力。如图4所示。


640?wx_fmt=jpeg

图4 udp单包多相应


还有很多工程上的技巧,比如调整缓冲区大小等,保证每次异步调用都能成功,减少系统调用次数。


五、负载均衡算法


负载均衡采用计数方法实现,每次请求发送到和配置负载比例差距最大的Provider上,如果3台都超出200负载,则进行请求排队,当有请求返回时,优先调度排队请求。整体如图5所示。


640?wx_fmt=jpeg

图5 负载均衡算法


六、创新点及工程价值


  • 1、consumer agent实现负载均衡和请求控制,能够有效在consumer使用排队机制端遏制过载,不把过载传递到网络传输上。

  • 2、两个agent间使用udp传输,利用udp的低损耗,因为是单包单请求,即使乱序也能随意合并重组,丢包不会造成协议出错,仅造成超时出错,非常适合这种实时性和吞吐量要求高的情况。

  • 3、在请求id中存储了调用者和时间戳,能正确相应合适的http调用者的同时,能够统计性能数据,作为调参依据。

  • 4、多线程等待epoll的et模式,能够充分挖掘网卡通信潜力,不存在单线程拷贝速度比网络传输慢的情况,同时省去了任务队列,每个处理线程具备一定的CPU计算处理能力,也能最小化响应时间,使用自旋锁后保证一个连接的消息不会被多个线程处理,健壮性有保障。

  • 5、设置合适的tcp缓冲区大小,在不阻塞情况下保证每次write调用都会成功。


复赛部分


一、  赛题重述及理解


复赛的主要考察点是单机实现100W规模的队列信息存储与读取,具体考察如何在单进程空间中的实现1M个队列的信息存储,如何设计合理的数据存储及索引结构,在特定硬盘的情况下完成高性能的顺序写、随机读和顺序读功能。同时由于测评环境是4C8G的,如何合理分配利用系统资源也是很重要的(任何对队列存储的东西都会放大100W倍,同时维护这么多数据需要占用更多的系统资源)。


针对上述考察点,本队提出了一套完整的队列消息数据存储方案,稀疏索引存储方案。程序开发迭代中的总共提出了3套写入方案,2套读方案,分别在Java和C++平台上进行了实现。


二、  整体系统架构


系统的整体结构较为简单和清晰,主要分为以下几个部分:每个队列对应一个对象,用于维护buffer等必要结构;一个容器负责将队列名映射到队列对象;文件管理对象负责维护读写文件;负责整体读写状态切换和管理的控制模块。如图6所示。


640?wx_fmt=jpeg

图6 复赛系统整体架构


三、  消息存储方案


考虑到顺序写、随机读、顺序读的使用场景,参考文件系统以簇为单位的文件存储方式,设计了以块为单位的队列消息存储方式。


640?wx_fmt=png

图7 队列消息存储方式


考虑到不定长消息及超长消息的情况,设计了变长整形+消息体的结构记录单条消息,同时支持跨块的消息。


640?wx_fmt=png

图8 消息数据存储方式


每个块开头都是变长整形,每个VarInt存储的都是目前还剩的消息体长度(便于快速跳过或判断消息是否继续跨块),这种结构支持任意长度的消息,如果块剩余空间不足写VarInt,则padding。


考虑到节省磁盘空间,在CPU充足的情况下,可以采用消息数据压缩,但是线上CPU紧张,考虑到通用性并未使用特化压缩算法。实际测试中由于消息生成性能及CPU性能的问题,未开启压缩功能,但在程序中预留了压缩接口,以适应各种场合。图9所示为预留的压缩接口。


640?wx_fmt=png

图9 预留的压缩接口


四、  队列写入方案


设计好了消息存储方案,剩下的就是合适的消息读写方案了,由于读取方案比较固定,而写入由于系统资源的匮乏(4C8G),需要合理设计。


1、mmap方案(Java实现)(淘汰)


本方案使用引用计数机制控制mmap生命周期,同时mmap的块存储在hash cache(定长slot基于hash冲突淘汰的cache算法)中,cache也会增加引用计数,这样在连续写文件时保证了较好cache命中率。实现结构如图10所示。


640?wx_fmt=png

图10 mmap方案


  • 优点:完全不用flush操作,因为读写都是引用mmap实现的。

  • 缺点:由于page cache脏超过20%会block写入线程,为了不阻塞线程,队列块的大小必须非常小(512bytes,1M队列512MB),性能较差。


2、整体内存引用方案(Java实现)(淘汰)


考虑到mmap直接作用在page cache上,受脏页比例影响,难以提升缓冲大小,故考虑使用整体内存方案,机制和mmap类似。同样使用引用计数机制控制写入缓冲区内存块的生命周期,额外使用独立线程负责将缓冲区刷盘。该方法能够等待新缓冲区内存块有效保障不写入过载,同时使用环形缓冲实现内存块及等待对象复用。使用2K大小block的java程序线上成绩为167w。方案整体流程如图11所示。


640?wx_fmt=png

图11整体内存引用方案


  • 优点:没有额外拷贝,异步写入不会阻塞。

  • 缺点:由于内存限制,存在写入线程等待刷盘数据释放,block大小受限制(2K,如果使用4K则会存在写入等待内存交换出来,写入有波动,性能不是最优)。


3、分片内存池方案(C++实现)(最优成绩)


综合前2种方案后,提出采用分片内存池+iovec内存重组写入的方案。具体实现为将block分为更小的slice,每次缓冲区申请一个slice,写满一个block后用iovec提交。


写入线程将提交的slice用iovec拼接成更大的part,整体写入磁盘。写入后将slice释放回内存池便于后续队列缓冲使用。虽然均匀写入是一种糟糕的写入方式,存在缓冲申请激增的情况,但由于分片机制的存在,每次激增为1M*slice的大小,可以通过slice大小控制。具体流程如图12,13所示。


640?wx_fmt=png

图12分片内存池方案


  • 优点:更细粒度的内存分配和释放,4K大小block情况下缓冲区能均匀移动,不会阻塞,写入和落盘同步进行;能在有限内存中实现更大的block。


640?wx_fmt=png

图13 分片内存方案细节


内部使用的ring buffer方案及代码如图14所示,所有对象均实现了复用。


640?wx_fmt=png

图14 ring buffer实现细节


五、  索引方案


由于内存空间限制,索引记录所有消息的偏移并不是个好方案。本系统方案采用了稀疏索引,因为设计的消息存储结构保证块内消息能够自行区分,仅记录块信息即可。只要能知道消息所在的块的位置和消息是该块的第几个即可定位到该消息。


每个消息块仅需要记录3个信息:块所在文件偏移(块号);块内消息数;上个块消息是否跨块。通过索引定位到具体消息数据的方法也十分简单。


  • 1、通过从前往后累加每个块的消息数量,快速定位到消息所在块;

  • 2、通过索引查找到块所在文件偏移,并完整读出块(4K读);

  • 3、通过是否跨块信息和目标消息ID,确定需要跳过的消息数,快速跳过无用消息。

  • 4、读取目标消息。具体流程如图15所示。


640?wx_fmt=png

图15 消息数据定位流程


定位完成后可以连续读,一旦发现长度超出当前块,则说明读到跨块消息,继续切到下一块读取即可。


六、  随机读与连续读


随机读取的消息落到一个块内有较大的概率,故即使是读一个小范围的数据,大部分情况能一次IO读取完成。针对连续读,记录每次读取到的位置,如果下次访问是从0或上次读取的位置,则认为是连续消费,对队列的下一个块发起readahead操作,将其读入到page cache中,实测可以提升40%性能。由于块的大小是4K,连续读时不会读入任何无效数据,操作系统的page cache能提供非常高的读取性能。


七、  Map容器优化


由于消息写入接口是以队列名对队列进行区分的,队列名到队列对象的映射也是系统的瓶颈所在。针对数字后缀队列名进行特殊hash计算处理,可以降低冲突概率。对于后缀计算hash冲突的情况,使用正常哈希计算,离散到32个加锁的unordered map中进行存储,保证鲁棒性和性能。由于后缀的区分度高,spin lock基本没有冲突,100W插入比传统map快几百ms,100W查找快几十ms。Suffix map结构如图16所示。


640?wx_fmt=png

图16 suffix map结构


八、  读写状态切换


程序一开始就设计有内存和磁盘协同工作的代码,但由于缓冲占用内存过大,使得后续随机读和连续读(依赖page cache)存在性能下降的问题,采用了读之前将缓冲全部落盘的策略。


在第一次读的时候,block所有读线程,落盘,释放所有缓冲内存(~5.5G)然后开始读取操作。由于空余缓冲是全部填充padding的,即使再写入队列,数据也不会出错。同时整体状态可以切换回写状态(需重新申请缓冲内存)。


九、  创新性与工程价值


  • 1、针对内存不足使用内存分片+iovec重组写技术,达到在小内存的情况下写内存和写硬盘同时进行的目的,极大提升了写磁盘性能。线上测试写时存在CPU idle>0现象,说明生产已大于写盘速度。

  • 2、针对队列名称改进了map,提出suffix map,针对后缀进行hash(现实情况下也大量存在编号结尾的队列名),极大降低了hash碰撞几率(测评数据中没有碰撞),同时用自旋锁和多个细粒度的unordered_map处理了碰撞情况,提升性能的时候保证了map的正确性鲁棒性。

  • 3、先写缓冲,缓冲满了排队,最后填满一个大block后刷盘,极大利用了SSD顺序写入性能,同时在队列不均匀时也能提供同样的高性能。

  • 4、变长整形+消息存储,及跨块消息,超长消息的处理机制保证任意长度消息都能正确处理,并有同样的高性能。

  • 5、稀疏索引,只对块的偏移和消息数量做记录,整个索引大小仅29*(4(blockId)+2(number))*1000000≈166MB,完全可以存储在内存中。

  • 6、使用ring buffer,分散写入和等待对象,采用引用机制(最后释放引用的线程负责提交该块到写入队列中),工作期间没有任何对象创建和销毁,建立在数组上,充分利用cpu cache,提升了性能。

  • 7、整体内存分配,然后分片,使用完成后整体释放,降低了内存管理的cpu消耗。

  • 8、设计时考虑了缓冲和磁盘协同工作读,不用将缓冲刷盘仅仅只用等待当前io完成,正确性可以保证,但由于占用太多内存,性能不好,故后期改为全刷盘操作。

  • 9、设计时候考虑了队列索引自增涨算法,但由于tcmalloc分配内存不释放,后面改为定长的了(有宏控制开关)。

  • 10、所有长度,池大小等参数都可以拿宏修改,可以修改适应大内存等各种情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/525614.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开发怼产品,天经地义?大惊小怪?

最近,又有一件轰动程序员界的事情发生了,想必大家伙都已经奔走相告了。来回顾下事情的经过,1张图就能说明白了骚不?反正有句话叫「从技术层面出发,总归有办法实现的」,还有这么一句话叫「从技术角度出发&am…

php实现数据排序算法,PHP实现排序堆排序算法

这篇文章主要为大家详细介绍了PHP实现排序堆排序(Heap Sort)算法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下算法引进:在这里我直接引用《大话数据结构》里面的开头:在前面讲到 简单选择排序 ,它在待排序的 n 个…

谷歌Edge TPU:将机器学习引入边缘,撬动边缘计算/IOT大“地球”

近期,谷歌在Cloud Next会议上推出其最新产品,Edge TPU芯片和Cloud IOT Edge软件,并将于10月推出Edge TPU开发套件。作为Cloud TPU的补充,目前Edge TPU仅用于推理,专为在边缘运行TensorFlow Lite ML模型而设计。Edge TP…

详解云计算、大数据和人工智能的区别与联系

今天跟大家讲讲云计算、大数据和人工智能。为什么讲这三个东西呢?因为这三个东西现在非常火,并且它们之间好像互相有关系:一般谈云计算的时候会提到大数据、谈人工智能的时候会提大数据、谈人工智能的时候会提云计算……感觉三者之间相辅相成…

关于腾讯云丢数据事件的一些看法

事件回顾:创业公司“前沿数控”8月5日发文称,公司存放在腾讯云上的精准注册用户以及内容数据全部丢失,并且不能恢复,造成公司平台全部停运的状态。前沿数控表示,公司丢失的数据近千万元级,对此索赔1000余万…

Navicat for mysql备份与恢复

文章目录 一、Navicat for mysql备份1.打开navicat,找到备份2.点击新建备份,直接点备份3.备份完成 二、恢复数据1.删除表2.点击备份,选中备份文件,点击还原备份3.还原完成 三、其他命令四、视频演示总结 一、Navicat for mysql备份…

一文详解微服务架构的数据设计

微服务是一个软件架构模式,对微服务的讨论大多集中在容器或其他技术是否能很好的实施微服务这些方面。本文将从以下几个角度来和大家分享在微服务架构下进行数据设计需要关注的地方,旨在帮助大家在构建微服务架构时,提供一个数据方面的视角:什…

干货 | 数据分析的 7 个关键步骤是什么?

“数据科学家” 这个名号总让人联想到一个孤独的天才独自工作,将深奥的公式应用于大量的数据,从而探索出有用的见解。但这仅仅是数据分析过程中的一步。数据分析本身不是目标,目标是使企业能够做出更好的决策。数据科学家构建出的产品&#x…

Python 爬取了马蜂窝的出行数据,告诉你这个夏天哪里最值得去!

文章由数据森麟出品作者徐麟正值火辣的暑假,朋友圈已经被大家的旅行足迹刷屏了,真的十分惊叹于那些把全国所有省基本走遍的朋友们。与此同时,也就萌生了写篇旅行相关的内容,本次数据来源于一个对于爬虫十分友好的旅行攻略类网站&a…

如何让笨重的系统架构变灵巧?

图片来源:Unsplash作者丨徐贤军来源丨徐贤军 架构师技术联盟如需转载,请联系原作者授权随着业务的复杂性增大、系统吞吐量增长,所有功能统一部署难度加大,各个功能模块相互影响使系统变的笨重且脆弱,因此需要对业务进行…

透过日播放量超过6亿的《延禧攻略》,看2018视频网站格局

作者介绍徐麟目前就职于上海唯品会产品技术中心,哥大统计数据狗,从事数据挖掘&分析工作,喜欢用R&Python玩一些不一样的数据文章来源数据森麟如需转载,请联系原作者授权前言随着《延禧攻略》的播出,魏璎珞、富察…

oracle怎么以时间排序,oracle指定数据排序在前面怎么处理

最近工作碰到客户的特殊要求,需要将特定的数据排序在前面,然后才按时间顺序排序,这个之前还真没有碰到过,好在有万能的度娘,搜索了一下,发现可以实现,使用order by decode语句即可。对于order b…

如何优雅使用Docker?请收下这15个小技巧

图片来源:Unsplash作者介绍ElNinoT文章来源Java架构沉思录原文链接www.cnblogs.com/elnino/p/3899136.html如需转载,请联系原作者授权1获取最近运行容器的id 这是我们经常会用到的一个操作,按照官方示例,你可以这样做(…

supervisor监控php进程程序,详解Supervisor进程守护监控(转)

Supervisor的配置2.0 创建目录,初始化配置文件mkdir /usr/supervisorecho_supervisord_conf > /usr/supervisor/supervisord.conf12mkdir/usr/supervisorecho_supervisord_conf>/usr/supervisor/supervisord.confecho_supervisord_conf详解:echo_s…

H264/ACC数据使用librtmp推流到服务器

这是本人第一次发表这个,首先声明本人也是个菜鸟!都说使用librtmp很简单,但是在网上找了很久,还是被各种大神坑了。 其实我也还是有很多东西不懂,如果下面有什么问题的地方,还请各位大神指点纠正。 1.视频数…

从容器到微服务,技术架构、网络和生态详解

图片来源:Unsplash作者晗狄文章来源架构师技术联盟如需转载,请联系原作者授权谈起容器技术,不得不提Docker技术。Docker 是 PaaS 提供商 DotCloud 开源的一个高级容器引擎,源代码托管在 Github 上,基于Go语言并遵从Apa…

彻底搞懂 python 中文乱码问题

前言 曾几何时 Python 中文乱码的问题困扰了我很多很多年,每次出现中文乱码都要去网上搜索答案,虽然解决了当时遇到的问题但下次出现乱码的时候又会懵逼,究其原因还是知其然不知其所以然。现在有的小伙伴为了躲避中文乱码的问题甚至代码中不使…

如何让Kubernetes集群生产可用?

图片来源:veer本文作者Steven Wong (VMware)Michael Gasch (VMware)文章翻译Karen Lee文章来源K8S技术社区原文链接https://kubernetes.io/blog/2018/08/03/out-of-the-clouds-onto-the-ground-how-to-make-kubernetes-production-grade-anywhere如需转载&#xff0…

北京房租到底有多高? | 爬取北京海淀区一居室租房信息

图片来源:花瓣网文章来源人工智能与大数据生活如需转载,请联系原作者授权最近北京房租成了热门话题,到底北京的房租有多高?本次实战是爬取北京海淀区一居室的租房信息,共爬取了300套房源信息,看一下北京的房…

租房有深坑?手把手教你如何用R速读评论+科学选房

图片来源:网络编译Hope、臻臻、CoolBoy文章来源大数据文摘出品如有转载,请联系原作者。最近,租房这事儿成了北漂族的一大bug,要想租到称心如意的房子,不仅要眼明手快,还得看清各类“前辈”的评价避开大坑。…