老司机乱谈『代码之美』 ——不要束缚了自己前进的脚步

Seastar是一个优秀的c++网络框架,代码量低,注释详细,可读性高,在一些基础部门已经开始应用。框架之中有很多美的地方值得我们学习,本文主要介绍了Seastar框架的代码之美和一些关键特性。对于代码之美是站在C++程序员的角度来看的,比如haskell程序员看到这样的代码,也许会感叹:“Pieceofsh**!”。学习不同的语言,不同的框架,用不同的方法解决问题,可以让我们扩宽思路,打破思维局限,丰富自己思考问题的方式,提高自己解决问题的效率。比如用haskell实现一个快速排序,是这样的:

q_sort n=case n of

[]->[]

(x:xs)->q_sort [a|a<-xs,a<=x]++[x]++q_sort [a|a<-xs,a>x]

再对比一下你桌子上C语言版的《数据结构》,对,蓝色的那本。所以再次强调本文中提到的美,请从C++语言的角度去看,不要跟其他语言比☺。

《程序员修炼之道》中有一段话“Invest Regularly in Your Knowledge Portfolio”。对于技术人员来说,如果长期停留在自己的舒适区间你的优势将变成你的劣势。随时保持自己对技术的敏锐,停留在学习区间,光阴荏苒,日月如梭,多年之后,蓦然回首,你将发现自己已经蜕变。保持学习的态度,切莫辜负了岁月。

代码之美

先看一下这个框架以简驭繁的美。用一个EchoServer举例(省略了main函数)。
这里写图片描述

可以看到代码很简洁清晰。该代码监听了一个1234的端口,只要accept,就发送一个固定字符串,然后关闭连接。这里面使用里future/promise模式还作为代码的胶合剂,把整个代码组织起来。同样整个框架也是用这个模式组织起来的。Future/promise(可以理解为常用的Callback,只是换了种方式重新组织了起来)模式后面再详细介绍一下,公司的taf框架里也有这个模式可以参照对比一下。另外这里出现的do_with 和 keep_doing都是配合该模式的一些函数(看名字也可以大概猜出来干啥的),有兴趣可以参考源文件future-util.hh(业界良心有注释)。

测试一下,注意这只是一个测试的例子,所以限定使用单线程否则会出错,如果正式使用应该使用sharded模板,使得实例运行在每一个core上面,这个后面再讲。

使用这个框架当然是可以提高开发效率,但是这是一把大威力的加特林机枪,并自带了炸膛功能,如果对框架理解的不够对C++理解不够,那后果真是,不可限量。当然好的一面是,这个框架的代码量不大,注释比较详细,短时间就可以阅读所有核心代码。

性能之美

Seastar框架在代码上是美的,在性能方面也毫不含糊。对基于Seastar 的httpd来测试,已经具备在单节点(服务器)上每秒处理700万请求的能力,突破千万也指日可待。鱼(开发效率)和熊掌(运行效率)可以兼得了。
这里写图片描述

硬件配置

CPU:2x Xeon E5-2695v3
网卡:2x Intel Ethernet CNA XL710-QDA1 (Transfer rate :40 Gbps,每cpu对应一个网卡)

使用Seastar开发的应用层代码和底层网络模式是隔离的,应用无需编译,可以通过配置选择使用Linux的tcp协议栈,还是使用Seastar native协议栈(用户态的tcp协议栈,zero-copy, zero-lock, and zero-context-switch,通过DPDK直接存取物理网络设备。DPDK处理一个包只需要不多于80个cpu时钟周期)

架构之美
对于多线程程序,理论是完美的,现实是残酷的,下图就体现了理论和现实的差别。

Seastar在架构的上都是一些成熟经验,每个core运行一个线程(称之为shard),每个core上运行的线程只处理自己所负责的数据,把异步事件通过Promises的模式调度起来。这样的设计达到了shared-nothing的效果,所以避免了多核之间的锁,理论等于现实。当然这样设计当然避免不了core之间的交互,这个后面会提到。

下面给一个官方的配图。
这里写图片描述

我们看一下关键代码,每个线程运行的主循环,是reactor::run(),是一个典型的reactor事件驱动模式,这个模块称之为引擎。
这里写图片描述

我们写的一个应用比如httpd,通过sharded模板,然后跑到不同的core上去,这个模块称之为服务。
这里写图片描述

Parallel_for_each 循环smp::count(可以通过启动参数进行重置少于物理core的数量)次,通过smp::submit_to 把服务创建到各个core的实例上去。这段代码也很美。

这里Cpu可以看作工厂,每一个core是一个车间,Reactor可以看作为引擎,我们写的服务(例如httpd)看作为机床,引擎驱动机床运转,每个车间互不干扰。

我们看一下httpd,限定为3个车间(shard),可以看到有3个同样的引擎在处理同样的事情,互不干扰。这里底层使用了SO_REUSEPORT(注意需要3.9以上的内核支持)。
这里写图片描述

Future/promise

这是框架的胶合剂。Future,一个数据结构代表了尚未决议的结果,并且可以绑定一个Continuations(理解为回调函数)。Promise,就是这个结果的Provider。

这里需要强调的是,各种框架里实现的Promise包括标准库里提供的,细节上都是有所不同的,需要注意其中的差别。

我们先看一个示例,第11行我们创建了一个promise,并且它的future绑定了一个回调函数。当第16行对这个promise进行赋值的时候(比如异步结果从服务器返回),我们绑定的回调函数将会被调度,比如这里就跟公司的taf框架的实现有些不同,taf中的回调函数会在对promisesetvalue的时候立即调用,seastar还存在一个统一调度的过程(在第19行后才被调度)。同样后面的sleep函数也会返回一个future,也绑定了一个回调函数。
这里写图片描述

执行结果,注意秩序顺序。
这里写图片描述
虽然只是一个小小的机制,但是带来的收益确是巨大的,在整个框架中这种机制把各个模块连接成了一个整体。回调那种丑陋的代码到处都有就不再举例了。

Programs must be written for people to read, and only incidentally for machines to execute.
— Harold Abelson and Gerald Jay Sussman

Message passing

前面说到了shared-nothing,但是没有跨核通讯是不太可能的,对于跨核的通讯,框架中使用了无锁队列来实现。要达到无锁,不能出现多读多写,所以这里任意2核心之间都需要一个队列,对于16 core的cpu,就需要16*16-16个队列(自己无需跟自己通讯)。
这里写图片描述

队列的定义与初始化
这里写图片描述

队列的轮询处理
这里写图片描述

smp_message_queue的定义

这里看到了内存洁癖,工匠精神。这里有2块数据,发送端的统计(Acore使用),和接收端的统计(Bcore使用),中间插入了一个其他的数据结构。作者解释为了避免cpu的prefecther造成A core的Cacheline和 B core的Cacheline加载了相同的内容 。这里多解释一下,如果多加载了会造成什么现象。我们以Intel处理器来举例,如果prefecth失败了,数据块2(这里已经按照cacheline的大小64字节对齐)只存在于B core的cacheline中,状态可能为E(Exclusive),如果中间没有其他数据结构A coreprefecth成功了。数据块2还存在于Acore中,cacheline的状态可能为 S(Shared)。S状态的写需要操作到InvalidateQueue,必然在硬件层面上造成了性能上的负担。如果想了解相关细节,可以阅读《Memory Barriers: a Hardware View for Software Hackers》,注意这里不必深究,不同cpu的实现都不太一样。

后记

回到《程序员修炼之道》这句话“Invest Regularly in Your Knowledge Portfolio”,有一个关键的问题,如何持之以恒的呢?其实这根本不是一个问题,如果你学习一个东西的过程感觉跟看《天龙八部》一样很精彩,那这会是一个问题吗?如果你的学习过程味同嚼蜡,那这也不是一个问题,因为可能已经走错路了。另外学习的过程中肯定有些东西无法完全理解,学会囫囵吞枣,不妨先放下,过一段时间回来看看,可能有不同的认识。学习是一个持续的过程,在这个过程中没有终点,但在过程之中自己会慢慢变得强大。

腾讯WeTest是腾讯游戏官方推出的一站式游戏测试平台,用十年腾讯游戏测试经验帮助广大开发者对游戏开发全生命周期进行质量保障。 腾讯WeTest提供:兼容适配测试;云端真机调试;安全测试;耗电量测试;服务器压力测试;舆情监控等服务。点击链接:http://wetest.qq.com/gaps/立即体验

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
主要讲述了计算机系统的开发领域。在每章中的漂亮代码都是来自独特解决方案的发现,而这种发现是来源于作者超越既定边界的远见卓识,并且识别出被多数人忽视的需求以及找出令人叹为观止的问题解决方案。 本书介绍了人类在一个奋斗领域中的创造性和灵活性:计算机系统的开发领域。在每章中的漂亮代码都是来自独特解决方案的发现,而这种发现是来源于作者超越既定边界的远见卓识,并且识别出被多数人忽视的需求以及找出令人叹为观止的问题解决方案。 本书33章,有33位作者,每位作者贡献一章。每位作者都将自己心目中对于“美丽的代码”的认识浓缩在一章当中,张力十足。33位大师,每个人对代码之美都 有自己独特的认识,现在一览无余的放在一起,对于热爱程序的每个人都不啻一场盛宴。 虽然本书的涉猎范围很广,但也只能代表一小部分在这个软件开发这个最令人兴奋领域所发生的事情。 本书收录的是软件设计领域中的一组大师级作品。每一章都是由一位或几位著名程序员针对某个问题给出的完美的解决方案,并且细述了这些解决方案的巧妙之处。 本书既不是一本关于设计模式的书,也不是一本关于软件工程的书,它告诉你的不仅仅是一些正确的方式或者错误的方式。它让你站在那些优秀软件设计师的肩膀上,从他们的角度来看待问题。 本书给出了38位大师级程序员在项目设计中的思路、在开发工作中的权衡,以及一些打破成规的决策。 第1章 正则表达式匹配器 。 1.1 编程实践 1.2 实现 1.3 讨论 1.4 其他的方法 1.5 构建 1.6 小结 第2章 Subversion中的增量编辑器:像本体一样的接口 2.1 版本控制与目录树的转换 2.2 表达目录树的差异 2.3 增量编辑器接口 2.4 但这是不是艺术? 2.5 像体育比赛一样的抽象 2.6 结论 第3章 我编写过的最漂亮代码 3.1 我编写过的最漂亮代码 3.2事倍功半 3.3 观点 3.4 本章的中心思想是什么? 3.5 结论 3.6致谢 第4章 查找 4.1. 耗时 4.2. 问题:博客数据 4.3. 问题:时间,人物,以及对象? 4.4. 大规模尺度的搜索 4.5. 结论 第5章 正确、优美、迅速(按重要性排序):从设计XML验证器中学到的经验 5.1 XML验证器的作用 5.2 问题所在 5.3 版本1:简单的实现 5.4 版本2:模拟BNF语法——复杂度O(N) 5.5 版本3:第一个复杂度O(log N)的优化 5.6 版本4:第二次优化:避免重复验证 5.7 版本5:第三次优化:复杂度 O(1) 5.8 版本 6:第四次优化:缓存(Caching) 5.9 从故事中学到的 第6章 集成测试框架:脆弱之美 6.1. 三个类搞定一个验收测试框架 6.2. 框架设计的挑战 6.3. 开放式框架 6.4. 一个HTML解析器可以简单到什么程度? 6.5. 结论 第7章 美丽测试 7.1 讨厌的二分查找 7.2 JUnit简介 7.3将二分查找进行到底 7.4 结论 第8章 图像处理中的即时代码生成 第9章 自顶向下的运算符优先级 9.1. JavaScript 9.2. 符号表 9.3. 语素 9.4. 优先级 9.5. 表达式 9.6. 中置运算符 9.7. 前置操作符 9.8. 赋值运算符 9.9. 常数 9.10. Scope 9.11. 语句 9.12. 函数 9.13. 数组和对象字面量 9.14. 要做和要思考的事 第 10章 追求加速的种群计数 10.1. 基本方法 10.2. 分治法 10.3. 其他方法 10.4. 两个字种群计数的和与差 10.5. 两个字的种群计数比较 10.6. 数组中的1位种群计数 10.7. 应用 第11章 安全通信:自由的技术 11.1 项目启动之前 11.2剖析安全通信的复杂性 11.3 可用性是关键要素 11.4 基础 11.5 测试集 11.6 功能原型 11.7 清理,插入,继续…… 11.8 在喜马拉雅山的开发工作 11.9 看不到的改动 11.10 速度确实重要 11.11 人权中的通信隐私 11.12 程序员与文明 第12章 在BioPerl里培育漂亮代码 12.1. BioPerl和Bio::Graphics模块 12.2. Bio::Graphics的设计流程 12.3. 扩展Bio::Graphics 12.4. 结束语和教训 第13章 基因排序器的设计 13.1 基因排序器的用户界面 13.2 通过Web跟用户保持对话 13.3. 多态的威力 13.4 滤除无关的基因 13.5 大规模美丽代码理论 13.6 结论 第14章 优雅代码随硬件发展的演化 14.1. 计算机体系结构对矩阵算法的影响 14.2 一种基于分解的方法 14.3 一个简单版本 14.4 LINPACK库中的DGEFA子程序 14.5 LAPACK DGETRF 14.6递归LU 14.7 ScaLAPACK PDGETRF 14.8 针对多核系统的多线程设计 14.9 误差分析与操作计数浅析 14.10 未来的研究方向 14.11 进一步阅读 第15章 漂亮的设计会给你带来长远的好处 15.1. 对于漂亮代码的个人看法 15.2. 对于CERN库的介绍 15.3. 外在美(Outer Beauty) 15.4. 内在美(Inner Beauty ) 15.5. 结论 第16章,Linux内核驱动模型:协作的好处 16.1 简单的开始 16.2 进一步简化 16.3 扩展到上千台设备 16.4 小对象的松散结合 第17章 额外的间接层 17.1. 从直接代码操作到通过函数指针操作 17.2. 从函数参数到参数指针 17.3. 从文件系统到文件系统层 17.4. 从代码到DSL(Domain-Specific Language) 17.5. 复用与分离 17.6.分层是永恒之道? 第18章 Python的字典类:如何打造全能战士 18.1. 字典类的内部实现 18.2. 特殊调校 18.3. 冲突处理 18.4. 调整大小 18.5. 迭代和动态变化 18.6. 结论 18.7. 致谢 第19章 NumPy中的多维迭代器 19.1 N维数组操作中的关键挑战 19.2 N维数组的内存模型 19.3NumPy迭代器的起源 19.4 迭代器的设计 19.5 迭代器的接口 19.6 迭代器的使用 19.7 结束语 第20章 NASA火星漫步者任务中的高可靠企业系统 20.1 任务与CIP 20.2 任务需求 20.3 系统架构 20.4 案例分析:流服务 20.5 可靠性 20.6 稳定性 20.7 结束语 第21章 ERP5:最大可适性的设计 21.1 ERP的总体目标 21.2 ERP5 21.3 Zope基础平台 21.4 ERP5 Project中的概念 21.5 编码实现ERP5 Project 21.6 结束语 第22章 一匙污水 第23章 MapReduce分布式编程 23.1 激动人心的示例 23.2 MapReduce编程模型 23.3 其他MapReduce示例 23.4 分布式MapReduce的一种实现 23.5 模型扩展 23.6 结论 23.7 进阶阅读 23.8 致谢 23.9 附录:单词计数解决方案 第24章 美丽的并发 24.2 软件事务内存 24.3 圣诞老人问题 24.4 对Haskell的一些思考 24.6 致谢 第25章 句法抽象:syntax-case 展开器 25.1. syntax-case简介 25.2. 展开算法 25.3. 例子 25.4. 结论 第26章 节省劳动的架构:一个面向对象的网络化软件框架 26.1 示例程序:日志服务 26.2 日志服务器框架的面向对象设计 26.3 实现串行化日志服务器 26.4 实现并行日志服务器 26.5 结论 第27章 以REST方式集成业务伙伴 27.1 项目背景 27.2 把服务开放给外部客户 27.3 使用工厂模式转发服务 27.4 用电子商务协议来交换数据 27.5 结束语 第28章 漂亮的调试 28.1 对调试器进行调试 28.2 系统化的过程 28.3 关于查找的问题 28.4 自动找出故障起因 28.5 增量调试 28.6 最小化输入 28.7 查找缺陷 28.8 原型问题 28.9 结束语 28.10 致谢 28.11 进一步阅读 第29章 把代码当作文章 第30章 当你与世界的联系只有一个按钮 30.1 基本的设计模型 30.2 输入界面 30.3 用户界面的效率 30.4 下载 30.5 未来的发展方向 第31章 Emacspeak:全功能音频桌面 31.1 产生语音输出 31.2 支持语音的Emacs 31.3 对于在线信息的简单访问 31.4 小结 31.5 致谢 第32章 变动的代码 32.1 像书本一样 32.2 功能相似的代码在外观上也保持相似 32.3 缩进带来的危险 32.4 浏览代码 32.5 我们使用的工具 32.6 DiffMerge的曲折历史 32.7 结束语 32.8 致谢 32.9 进一步阅读 第33章 为“The Book”编写程序 33.1 没有捷径 33.2 给Lisp初学者的提示 33.3 三点共线 33.4 不可靠的斜率 33.5 三角不等性 33.6 河道弯曲模型 33.7 “Duh!”——我的意思是“Aha!” 33.8 结束语 33.9 进一步阅读 后记
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值