0 bug:C/C++商用工程之道
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.3 基本开发思路

在商用数据传输工程中,需要的基本开发思路,也就是基本的开发原则,系统分析时的关注要点。这些要点,不仅仅是资源上的关注,需求上的关注,也是思路上的方向。

这里,给大家提示一些商用工程常规的思考方向。

1.3.1 边界

任何系统,其资源都不是无穷无尽的,因此,任何不涉及边界的系统设计都是失败的设计。笔者工作这么多年,每次接到需求的第一要务就是落实边界。

什么是边界?一个系统最多有多少台设备?多少个用户?平时的工作流程,并发访问有多少?每台服务器、每台客户端,最多可以有多大内存,多快的CPU,硬盘多大?这些是大边界,必须第一时间和客户确定清楚。

原因很简单,边界决定数据结构,边界决定算法

比如,一个只有255用户的小型局域网IM,我们在管理用户时,一个磁盘文件就可以了,启动服务器时,可以直接把这个文件一次读入内存中的一个静态数组,然后使用。

但一个100万用户的大型运营系统,可不能这么干,内存中显然放不下这么多信息,肯定要用数据库来保存用户数据。一旦涉及数据库,就要根据具体业务设计各种表、查询等数据库相关细节,必要时,查询的信息甚至要做Cache系统,以保证效率,等等。

二者的开发成本存在巨大差别。

举个例子,以前笔者接到军方一个项目,对方说的很笼统,要一个系统,他们内部可以互相传文件,嗯,还要考虑保密,不能乱传,传输的每一份文档都要有监控;并且还要直接收发短消息,因为军方网络内部不允许使用QQ;嗯,还要一个网站,有些文档可以放上去,网站上常规的那些新闻什么的都要;文档上去,还要有权限,不是每个人都可以看的;对了,还要一个聊天室,大家可以打字开会。

很乱吧?刚开始笔者都想拒绝了这个项目,这个软件的规模,大概需要一个Exchange+IIS+QQ+一套网站+一套论坛,笔者不是神仙,做不出来的。

可是不对,对方给的开发费用只有几万元,和系统规模根本不相称。

于是笔者就专门找到对方的领导,仔细询问了边界。原来对方单位总共才100多人,他们的文档也不多,最多每天1篇、每年300篇,他们单位总共才50多台设备。这个系统主要是应对上级的信息化建设检查,因此网站上功能要全,但对性能要求不大。

呵呵,知道了边界,就好做了,VC开发一个IM软件,最多256用户,内部设置一个公告板和聊天室,这个软件包括文件传输功能,传输文件不使用P2P模式,全部到Server上做Relay,Server同时拷贝一份备份,供事后泄密追溯。

找朋友拿个企业网站修改一下,企业改为单位,包括新闻、论坛、公告等简单几个项目。文件分发,在Server上开辟FTP服务,IM客户端可以上传文件,文件自动显示在网站中,然后大家可以点击下载。做个简单数据库,记录上传文件名字和上传者,供事后追溯。

就这么简单!大家可以看到,边界一旦界定,很多时候,方案马上就出来了。

1.3.2 “细分”的分析方法

在这么多年的开发经验中,笔者深深感觉系统分析中最重要的就是细分。这个概念本来是学习营销时的“细分市场”得来的,但发现在系统分析中很有用。

我们接到一个客户需求,通常都是笼统的、概括的、模糊的。客户不是专业人士,很难把自己的需求清晰地、有条理地说清楚,这就要求程序员有一个细分的思路,要能把客户需求不断拆分,一直拆分到每个小功能,再落实到程序可实现的地步,而这个过程,其实就是系统分析的过程。

简单的细分,大家可能都会。上面关于边界的例子,其实也是细分的例子,但是,这里强调一个概念,“一切皆可细分”,这才是最重要的。

还是用例子说话:

所有的公网服务的IM软件,如QQ,一般都有用户管理系统,即每个用户需要凭用户名和密码登录。

我们知道,IM软件有好友,一个用户总有个几十个好友,这就有一笔业务需要处理。每次用户上线,需要查询这几十个好友的在线状态,然后显示到界面上。而且这还没完,好友可能会动态上下线的,因此,每分钟需要刷新一下好友状态。

就这么一个看似不起眼的业务,让所有做IM软件的程序员痛苦不堪。原因很简单,业务量太大了。

一个同时在线100万用户的系统,每个人上线后,每分钟需要提出一个查询请求,查询几十个人的在线状态,1分钟60秒,100万除以60秒,每秒差不多接近17000笔交易,不要说数据库,传统的apache连接能力根本无法承受。

笔者在思考这个问题时,几乎什么技术都想了,Cache、MySQL事务,等等,但是效果就是不好。

最后,笔者突然发现一个问题,我们的思路,都局限到中心数据库的星型模型上了。另外,还有个思维误区,认为用户验证管理的CS服务器,理所当然应该负担好友查询。

不要忘了,一切皆可细分!

笔者就思考,为什么一定要把这个业务放到中心数据库完成?用户在线查询和用户验证,明明是两笔业务嘛。

思路一旦确定,解决方案就很好解决了。在中心数据库集群开发一个服务,就叫做用户状态查询(GetUserStatus),每个客户端上线,中心数据库在验证通过后,向这个服务发送一个User上线的通知,下线也发一个下线通知,然后,客户端所有查询好友状态的请求,从中心数据库转移到这个服务上来完成,自己不再响应。

一个用户的在线状态最多几十个字节,算64B吧,那么,100万用户也才64MB,完全可以放在内存里面,因为这是用户在线的动态信息,也没有磁盘保存的必要。

一旦数据放到内存里面,优化方案就太多了,哈希Map,平衡二叉树,等等,其效率远高于数据库的磁盘IO查询。

客户端根本看不见,一切都在服务器集群内部透明完成。系统Loading可望下降1000倍,大家想是不是这么回事?

可以看到,简单的一个细分思维,把原来我们认为理所当然应该在一起的业务,拆分开,就带来大量的成本节约,大家是不是可以体会到点什么?

1.3.3 灵活,逆向思维

这其实可以算作细分思维的延伸。

上文的例子,大家可以看到,其实这也是逆向思维的一种体现,细分和逆向思维,本来就密不可分。

笔者这里提出来,主要是提醒大家,不要固执己见,不要墨守成规,很多创新,都是从对大家公认的事务中找出不合理的地方加以改进而实现的。

工作多年,笔者经常给团队成员讲的一句话是:“在数据传输领域,你亲眼看见的,都不是真的”。这个话怎么理解呢?还是举个例子。

网络上两个角色建立起了一个socket链路,工作一切正常。那是不是万事大吉了?

公网传输会有多级网关,我们和另外一台计算机进行传输的时候,中间不知道经过了多少路由器或者网关,如图1.4所示。

图1.4 网络多级传输模型

每个路由器又同时在为不知道多少客户服务,任务有轻重、系统有繁忙程度,因此,每个路由器的工作实际上都是不保证的,如果中间有某一台路由器在这一个瞬间发生繁忙,使得你的这笔交易失败,这是完全有可能的,而这一切,永远没有办法预测,只能是事后分析。

因此,如果中间的某一台路由器,如图中网关3,在某个时刻,因为繁忙或者其他什么原因,导致传输失败,是完全有可能的。而此时,UA1、UA2由于没有和网关3发生直接交互,是不可能知道本次传输时可能失败的,如图1.5所示。

图1.5 网络多级传输中间故障模型

这类情况还很多,因此,在数据传输这个领域,没有什么状态是恒定的,这一秒通过这个链路传输成功,仅仅是表示这一秒链路是好的,在你得到这个结果的时候,这个信息就已经过时了,没有可参照性了。

所以,网络状况,永远只能分析,不能被观察。

商用程序员,永远不会相信一条链路、一个模块会永远完好,也不会相信一次调用一定会成功,他们会设置很多校验机制,会仔细检查每次调用返回的结果,随时判定服务的成功与否,并处理每一个错误的情况,书写具有较高自恢复性的程序逻辑,保证系统在任何错误下都能自动恢复,持续服务。

这要求商用程序员要具有极其灵活的头脑,善于逆向思维。很多时候,即使程序本身没有bug,但某个时间点的一次传输失败了,但过了会儿又恢复正常,这很可能被QA部门作为bug报出来,此时的程序员要冷静,仔细分析,看究竟是程序bug还是系统本身的质量问题。

同时,商用工程系统一般要求自恢复性非常好,绝对不能因为某一次bug而造成崩溃,应有足够的自我防范和恢复机制,故障出现后第一时间恢复现场,重新开启服务,使系统继续工作。

1.3.4 小内核,大外延,工程库思维

商用系统工程,一般建议模块化开发,商用程序员可以考虑将一些成熟的代码模块做成工程程序库,以API层与上层业务层交互。以达到较高的重用率,降低开发成本,提升程序员的产能。

为了实现这一目标,要求商用程序员一开始就要有建立工程库的意识,在代码实施中始终保持高度的抽象性,遵循一个逻辑只写一次的开发理念,每一段代码的编写均按照库代码的高标准实现编写,同时加以高强度测试,力争完成一块、成熟一块、入库一块,不断提升自己的产能。

由于工程程序库为多个应用工程使用,库内包含的程序模块、库提供的API接口,原则上一旦确定不能再更改,只能做增量补充,绝对不允许删改,以避免新工程的实施导致旧工程的崩溃。

另外一方面,程序员尽量仔细把握,进入工程程序库的代码,原则上凡是与某个工程具体业务相关的代码不要入库,因为通用性不够好。库内代码尽量短小精干,每个模块功能尽量少,保持最大的应用灵活性。

在开发中,笔者个人习惯使用一个叫share的目录,所有的库代码均放于此处,一个工程,不管是服务器还是客户端,甚至是n个工程,均直接共享该目录下的库代码;一个逻辑,一旦被库代码实现,没有特殊理由,原则上以后的应用工程不允许自行实现,必须调用库代码。

这样做还有一个好处,就是在团队开发中比较容易实现标准化开发。整个团队基于一个成熟的库开发,配合严格的代码规范,可以有效降低bug率,提升团队的效率和产能。

笔者以前带团队完成的一个服务器集群,所有的C、C++程序员均是基于笔者的底层库在开发,最后,十几万行代码写下来,9轮测试总共只测出51个bug,其中属于C和C++部分的只有7个。上线后直接就是运行一年,没有任何bug。

工程程序库的威力,大家是不是能体会到什么?

1.3.5 单笔交易失败不算失败

这是笔者长期工作的经验,在商用工程中,很多时候涉及网络数据传输的开发,并且一般面临的是基于公网互联网7×24小时不间断服务的运营需求。

在系统上线后漫长的服务过程中,很可能会因为某一次网络的异常或者黑客、病毒的攻击,导致服务程序本身的异常,使某笔客户交易失败。而这类故障,在实验室中很难通过模拟得到,因此,商用数据传输工程的测试,很难做到真正的100%测试,简单说,就是不存在完美的系统。

这是正常现象,笔者建议商用工程的开发人员和QA测试人员,要充分认识这种不完美性。商用软件系统工程,一般强调的是智能化的异常处理,异常之后的自恢复特性,任何一次异常造成的某一次客户服务失败,都不算bug,只要程序能自动恢复,无资源泄漏,无崩溃,无累积性逻辑错误,系统就是成功的。

这在性能上看也很合理,一台服务器可能同时为几万甚至几十万客户服务,一笔或几笔服务失败,在比例上看其实并不大,系统长期运行过程中,某个时间点可能出现性能瓶颈,但只要自动恢复,大多数时间都能正常服务,系统就是成功的。

因此,商用数据传输工程开发有个很重要的思维,请各位读者体会:“单笔交易失败不算失败!