置顶随笔

 

 

最近给自己换了个老板,忙了一段时间,所以有几个月没写博客,今后还是要争取多写啊,呵呵。

 

换来新地方,第一件大的事情就是修改后端架构和通信协议,架构也设计得很普通,因为这边的业务不需要太过复杂的后端,所以就简单设计了一下,基本是参照web的模型,符合我一贯的向web学习的思想,弄了个gate管理入口,相当于web下的webserver,后端其他服务器挂在该gate下,相当于web模型下的appserver,或者fastcgi模型的fastcgi进程,gate上管理连接、合法性检测、登录、加密、压缩、缓存。Gate和后端通信本来想参照fastcgi协议,但看了之后觉得fastcgi协议还是复杂了,所以就设计了一个更简单的协议,gate和后端server之间可传递key:value型数据对,value不局限于字符串,可以是任意数据,这样基本满足了当前的需求,第一版放上去之后也运行良好,到今天也基本持续稳定运行快一个月了,没出过什么事情。由于在gate这边缓冲了job管理,所以后端server升级很方便,随时可关闭更新,gate会在窗口时间内将未执行完成的任务重新提交,有此功能可放心大胆的升级后端,这个月这样的工作做了几次,在架构修改之前这样的事情几乎是不敢做的,因为一旦升级所有用户全部断开连接,而现在用户则基本无感觉。Gate上的缓存层为后端减少了一些压力,这个缓存是按照请求的md5key做的,并根据协议配置时效,有此cache后端大多数服务可不设计缓存或降低缓存设计的复杂度。Gate上针对敏感数据统一做了加密处理,主要是辛辛苦苦整理的数据不能轻易让竞争对手窃去了,呵呵。Gate也做了压缩,现在是针对>=128长度的包进行压缩,使用了qlz,压缩效率还是很不错的,速度很快。目前gate后端挂接的既有win上的server也有linux上的server,这是一开始就这么规划的,现在看来当初的目的达到了,混合发挥各自的优势,有的项目在原有系统上跑得好好的,没必要重新开发嘛。

 

协议设计上本来我是计划二进制混合json格式,以二进制为主,但尝试了一个协议之后发现,这边的小伙子们对直接操纵内存普遍技术不过关,他们大多是从java开始的,后来才学习c,对字符串用得很熟练,权衡之下采用了json为主,混合二进制为辅的方案,这样修改之后的协议和他们之前使用的xml类似,就是更小更紧凑一点,使用方法上很类似,从现在的效果看还行,使用json格式为主的协议当然不能跟使用pb之类的相比,解析效率上大约单线程每秒解析20来万10obj的对象,速度上不算太快但也不算太慢,对付一秒至多几万数据包的应用来说还是够的,因为现在cpu计算能力普遍过剩,使用json的另个好处就是增删字段很方便,各个版本之间不需要太考虑版本的问题,要是全用二进制格式就要麻烦很多了,在使用压缩之后,目前的json格式协议比之前的xml协议减少了2/3的带宽使用,总体效果还是可以的。使用json调试也很方便,我提供了一个工具,写后端的就直接用该工具按照json格式收发数据,无需等client开发好了再去做后端,之后做client也很方便,请求发过去之后返回来的就是标准的json格式数据,同样的解析方法,每个不同的应用就按照不同的格式处理下即可,和web等模块交互也很方便,这可算是额外的好处了。

 

总之,虽然json格式存储效率和解析效率跟二进制方式还差半个量级到一个量级,但合理使用还是可以的,特别是跟xml相比优势很明显,权衡使用吧,当然追求极致效率可能还是用pb之类的更合适一些,或者自己设计tlv格式。

 

posted @ 2011-01-11 13:33 袁斌 阅读(2490) | 评论 (3)编辑 收藏

07年我写了一篇文章叫《我的网络模块设计》,姑且叫那个为第一版吧,由于持续对网络模块进行改进,所以现在的实现和当时有很大改变,加上上层应用越来越多,又经过了几年时间考验,现在的实现方式比之前的更灵活更有效率,也因为最近看了一些人做网络程序多年竟毫无建树,一直要用别人写的网络模块,所以有感而写此文,为了使得此文不受上一篇《我的网络模块设计》的影响,我决定写之前不看原来的文章,所以此文跟原文那篇文章可能没有太多相似性。
 一个基本的网络模块,无非就是管理N个连接,快速处理每个连接的收发数据、消息等,所谓好的网路模块,无非就是稳定、高效、灵活,下面分几部分来写:
 一、 连接管理
 之所以首先写连接管理,是因为连接管理是核心,也是最难的地方,我写第一个网络库之前,搜索过很多当时可以找到的例子工程,当时几乎找不到可稳定运行的工程,当然更找不到好的,于是摸索前进,期间对连接管理使用了各种方法,从最早一个cs(临界区CriticalSection,我简称cs),recv send都用这个cs,到后来send用一个cs,recv用一个cs,用多个的时候还出过错,最后使用一个cs+一个原子值ref管理一个连接,每个连接send的时候用cs,recv的时候用ref,如果该连接的消息要跨线程异步执行,也使用ref,如此较简单的解决了连接管理的问题。
 同样使用生存期管理方法,也有人用智能指针,虽然原理和我直接操纵生存期一样,但实现方法毕竟不同,不过我为了让实现依赖少一些没有引入智能指针。
 当然我后来也发现很多人不是用这种方法,如有些人就id来管理连接,每个连接分个id,其他操作全部用id,每次对连接的调用先翻译一下,如果id找得到映射目标就调用,否则就说明该连接不存在了,这种方法简单只是不直接,多了个查找过程,另外查找的时候可能还需要全局锁(这依赖于连接数据组织)。
 也有人使用一个线程管理连接,其他所有与该连接有关的生存期问题全部到该线程处理,这样也是可行的,只是需要做一个较好的包装,如果包装好上层调用方便,如果包装不好,可能上层调用就有一些约束。
 虽然各种方法都有人使用,但我一直选择直接的生存期管理方法,其实内部实现的时候还是有很多优化措施的,减少了大量addref、release的调用,进一步提高了效率。
 二、 线程组
 我最初做网络库的时候还不是很清楚上层如何使用这个库,后来在上面做了几个应用之后慢慢有了更多想法,最近的网络库是设计了这么几组线程:io线程组、同步线程组、异步线程组、时钟线程组、log线程组,每组线程都可开可关,就算io线程组也是可关的,这只是为了整个库更灵活适用性更广泛,如只用同步线程组或异步线程组仅将这个线程组当一个消息队列使用。
 Io线程组就是处理io收发的,listen recv send 以及解密解压缩都是在这组线程,一般这组线程会开2个或2*cpu个。
 同步线程组,一般这组线程开1个,用来处理logic。
 异步线程组,这组线程根据需要开0个或n个,简单应用无db等慢速操作的应用不开,有很多db等慢速操作的可以开很多个。
 时钟线程组,一般不开或开1个。
 Log线程组,一般开1个,主要为了避免其他线程调用WriteLog的时候被磁盘io阻塞,所以弄了一个log线程。
 其实还有一个主线程,我的每组线程(包括主线程)都支持事件和定时器,io线程、同步线程、异步线程组、时钟线程组、甚至log线程组都支持事件和定时器,到去年我还只是让每组线程都支持事件,今年为了更好的使用时钟我给每组线程设计了定时器,现在定时器线程组有点鸡肋的味道,一般是用不上专门的定时器线程组,不过我还没有将它删掉,主要在我的设计里面,它和同步异步线程组一样,都只是一组线程,如果必要的时候可以将它用作同步线程或者异步线程组,所以继续保留了它的存在。
 这几组线程之间都是可互发消息的,所以一个逻辑要异步到别的线程执行是非常方便的,只要调用一下PostXXEvent(TlsInfo *ptls, DWORD dwEvent, WPARAM wParam, LPARAM lParam);我凭借这个设计使得这套网络库几乎可以适用上层各种应用,不管是非常简单的网络应用还是复杂的,一框打尽。对最简单的,一个io线程搞定,其他线程全关,对于复杂的io线程+同步+异步+log全开。
 三、 内存池
 内存池其实没有想象中的那么神秘,当然如果要让一个网络程序持续7*24小时稳定高效运行,内存池几乎必不可少的,内存池的作用首先是减少内存碎片,其次是为了提高速度,我想这两点很容易想明白的,关于内存池我之前写了系列文章,可参考我的博客:
 
《内存池之引言》 http://blog.csdn.net/oldworm/archive/2010/02/04/5288985.aspx
 《单线程内存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289003.aspx
 《多线程内存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289006.aspx
 《dlmalloc、nedmalloc》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289010.aspx
 《线程关联内存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289015.aspx
 《线程关联内存池再提速》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289018.aspx
 
四、 定时器
 关于定时器,上面讲线程组的时候已经讲过,我现在的设计是每个线程(包括主线程)都支持定时器,调用方法都是一样的,回调函数形式也是一样的,由于定时器放到各组线程里面,所以减少了线程之间的切换,提高了效率。
 关于定时器,可参考《定时器模块改造》 http://blog.csdn.net/oldworm/archive/2010/09/11/5877425.aspx
 
五、 包格式
 关于包格式可参考《常用cs程序自定义数据包描述》 http://blog.csdn.net/oldworm/archive/2010/03/24/5413013.aspx
 
六、 Buffer
 之前的文章其实我一直没有提过我的buffer,其实我的buffer设计是很灵活的,现在它和pool也是有些关联的,我的poolset其实底下就是按照各种不同大小的buffer预设的尺寸。Buffer我设计为循环式,不允许回绕,包含
 Char *pbase 块基址
 Char *pread 当前读指针
 Char *pwrite 当前写指针
 DWORD tag;
 Buffer *next;
 Capacity 总分配尺寸,上面分配的时候可能只是指定了19,但实际可能分配的是32个字节,所以内部用的时候要根据capacity来最大限度的利用缓冲区。
 Buffer分配还利用了一个技巧,事实上分配的时候是一次分配一个需要的大缓冲,前面为Buffer自身的数据,后面为数据部分,pbase指向数据部分,这样处理减少了一次分配,我估计很多人都在用这个技巧。
 Pwrite总是不会小于pread的,但pread可能和pbase不一样,仅当后面空余空间不够用的时候才可能会移动数据,否则数据不会移动。
 WSARecv的时候我是这么处理的,如果首次获取了一个包的一部分,但buffer中还有足够的空间放下包的剩余部分,我不会再分配一个buffer去recv,而是直接用原buffer指定一个合适的偏移和size去WSARecv,这样可以最大限度的减少复制。
 刚才还有朋友问到我recv的层次组织,我的网络库里面是这样组织的,OnRecv是个虚函数,最基础的IocpClient的OnRecv只处理数据而不解析格式,IocpClientMsg就会认识默认的一种包格式,这个类的OnRecv会将m_recvbuf中的数据组织为msg,并尽可能的一次返回更多个msg,回调OnMsg函数,由上层决定该消息在哪个线程处理,这样我认为是最灵活的,如果是个很小的server,可能直接就在io线程里面处理了,也可postevent到同步线程处理,亦可PostEvent到异步线程处理。
 
七、 TLSINFO
 TlsInfo顾名思义就是每个线程关联的一组数据,暂时我还没有看到别人这么设计,也许我设计得有些复杂了,在这个数据里面有一些常用的和该线程相关的数据,如该线程的分配基、步长,用这两个参数可让每个线程制造出唯一序列,还有常用pool的地址,如tm_pool *p1k; tm_pool *p2k;… 这样设计使得要分配的时候直接取tm_pool,最大限度的发挥了分配速度,还有一些常规参量long c; long d; DWORD a; DWORD b;… 这几个值可理解为栈内值,其实为了减少上层调用复杂度的,如我将一个连接的包从io线程PostEvent到同步线程处理,PostEvent首参数就是tlsinfo,PostEvent会根据tlsinfo里面的一个内部值决定是不是要调用addref,因为我有个地方预增了2,所以大多数情况下在io发到其他线程的时候是无需调用addref的,提高了效率,tlsinfo里的其他一些值上层应用可使用,用在逻辑处理等情况下。
 
八、 性能分析
 *nix下有很多知名的网络库,但在win下特别是使用iocp的库里面,一直就没有一个能作为基准的库,即使asio也因为出来太晚不为大多数人熟悉而不能成为基准库,libevent接iocp由于采用0 buffer模拟所以也没有发挥出足够的性能,对比spserver我比它快70%左右,我总在想要是微软能将他那个iocp的例子写得更好一点就好了,至少学的人有一个更高一点的基础,而不至于让http://www.codeproject.com/KB/IP/iocp_server_client.aspx这样的垃圾代码都能成为很多人的样板。
 
九、 杂谈
 为了写好一个win下稳定高效的网络库,我07年的时候几乎搜遍了那个时间段之前所有能找到的iocp例子,还包括通过朋友等途径看到的如snda等网络库,可惜真没找到好的,大多数例子是只要多线程发起几千个连接不断发送数据马上就死了,偶尔几个不死的(包括snda的)只要随机连接并断开就会产生句柄泄漏,关闭所有连接之后句柄并不关闭等,也就是说这些例子连基本的生存期管理都没搞定,能通过生存期管理并且不死的只有有限的几个,可惜性能又太差,杯具啊。
 早年写网络库的时候也加入了sodme在google上建的那个群,当时群还是很热闹的,可惜大多数人都是摸索,所以很多问题只是讨论却从无定论,没有谁能说服别人,也没有人可轻易被说服,要是现在或许有一些很有经验的人,可惜那个群由于GFW现在虽能访问也不大活跃了。
 最近看到有些写网络程序7年甚至更久的人还在用libevent、ace等感想很复杂,可悲的是那些人还没意识到用一个库和写一个库有多大的区别,可能那些人一辈子也认识不到写一个库比用一个库难多少,那些人以为这些库基本会用了,让他自己去写也基本是照这个模式,不会有什么突破,就无需自己动手了,悲哀啊。当然,要写一个稳定的网络库需要耗费很多时间,特别是要写一个能和知名库性能接近或更好的库,更是要费神费力,没点耐心和持久力是不可能做好的。在中文领域随便查什么稍有些名气的代码,总是能找到很多剖析类文章,可原创的东西总是很少,也不知道那些大侠怎么搞的,什么都能剖析可怎么总写不出什么像样的东西呢。
 其实本来没有打算写这篇文章,可能是看了陈硕的muduo才使得我有了写出来的冲动,大概是受到他的开源鼓励吧。
 谨以此文记录本人最近3年对网络模块的修改并简短总结。

 

posted @ 2010-10-03 14:25 袁斌 阅读(3214) | 评论 (5)编辑 收藏

实用云计算环境简述

 

如今it领域没听说过云计算的绝对是out了,虽然大家都知道云计算,虽然很多高校很多专业都开设了云计算专业,虽然很多人都在讨论云计算,虽然也有少数人走在了应用云计算的前列,然而,可悲的是,大多数人对云计算的认识仅限于amazongooglemicrosoftibm有能力架设云计算环境,其他公司都靠边,甚至唯他们的云计算才叫云计算,别的企业根本不可能做云计算,各级政府部门最搞笑了,动不动花多少钱引进某某云计算环境,填补某某空白,多少cpu多少机器每秒多少万亿次计算,最终是不是一堆浪费电力的摆设也没有人知道,也没人去过问。

略感欣慰的是,很多企业都在务实地部署自己的云计算环境,大如腾讯、淘宝、百度、小如我们这样刚成立的小公司,其实要部署一个私有云计算环境并没有那么难,以我个人的经验来看,如果有一个精干的小团队,几个人一个月部署一个私有云计算环境是完全可能可行的。在我看来,所谓云计算就是分布式存储+分布式计算,不局限于底下oswin还是*nix,也不局限于是局域网环境还是广域网环境,也不管上面跑的是c++的程序还是javascript的程序,下面简单介绍下我设计的一个即时查询价格的云计算体系:

我一直在win下开发,win用得非常熟练,所以我把云计算环境部署在windows之上,当然也考虑到windows的机器众多,tasknode可轻易找到非常多的目标机器,我部署的云计算环境主要分两类节点,jobservertasknodejobserver主管任务切割、任务调度,tasknode是计算节点。另外还有一些节点,jobowner可连接jobserver并提交任务,并可查询该任务的执行情况,admin可连接jobserver查询jobserver的状态。

 

其实这些上篇博客已经写过,我再讲的详细一点,看具体的执行情况,首先jobownerjobserver提交package,这个package是一个zip文件,包含一组文件,jobowner提交package之后jobserver会根据约定的规则管理package,并在jobserver展开该package,如下:

 

 

Jobowner连到jobserver之后,发出如下的命令到jobserver

0x49 0x0 0x0 0x0 0x2 0x0 0xb 0x0 127.0.0.1 0x0 ppsget.dll 0x0

{type:[0,1,2,3,4],rmax:5,wb:"pc",text:"诺基亚 e63"} 0x0

上面是用我设计的一种混合显示格式显示的包数据,可以看到里面带上了ppsget.dll,这就是指定包内部名,其实还可以这样ppsget.dll:getpage,如此一个dll就可支持多个IJobTask输出,getpage只是获得其中一个IJobTask接口(关于IJobTask接口参考上一篇云计算实践2的文章)。具体命令是json格式,主要是为了方便信息传输和解析。Jobserver接收到该命令之后,调用ppsget.dllIJobTask接口中的split函数,将该任务分解,之后调度Tasknode执行,tasknode收到jobserver发过来的任务之后,检查包名称,如果缺少就会主动向jobserver要求发送相应的包,并进行部署,待部署完成之后从包获取指定的IJobTask接口,执行该接口的map函数,将结果按照约定的格式发给jobserver,最后由jobserver调用IJobTask中的reduce函数进行打包,最后将结果发给jobowner并记录相关Log

上图中还可看到一个HashCrackCloud.dll,这是另一个云计算环境下破解md5密码的dll,这个上篇文章也写了一下,这里就不详述了。

 

为使得tasknode可适应各种机器环境,我把tasknode设计为一个dll,该dll内部自己管理消息及任务执行,该dll可被加载到各种容器进程(如gui进程、console进程、service进程)等执行,看下我的tasknode和它的容器进程:

 

这也算是我的得意设计吧,这样设计的tasknodewindows系统下的确具有很高的灵活性。

这样的tasknode甚至可直接加载在jobserver进程,也可被任意win系列机器的任意进程加载参与运算,用主动加载或被动加载都很方便,极大的方便了云计算环境的部署,反正具体执行的任务都由package完成,tasknode只要按照约定的规则部署 package即可,所以这种云计算环境是非常轻量级又非常灵活的,开发一个新的任务只要做一个新的IJobTask即可,目前我这套体系除了没有考虑太多安全性之外,这个云计算环境的实施还是非常容易的,实际上我们这个价格查询的后台云计算环境只用了不到2周的时间就开发完成。

再看下jobserver记录的每个joblog

 

log中可很容易的分析出一个job每个task的执行情况,并可根据这些数据进行相应的优化处理。

之所以把jobservertasknode以及package都写出来,主要是为了表达一个看法,要实现一个简单的云计算环境其实并不难,有经验的团队很容易就能做出来,参考下googlemap/reduce论文,按照自己的需要简化实现,真理在实践中,如果只是仰望googleamazon,那就真的是在云中雾里,另一个想要表达的就是云的形式是多种多样的,并不一定amazonegoogle的云计算环境才是标准的,对实用派来说,形式都是次要的,实用才是关键的。

posted @ 2010-10-03 14:23 袁斌 阅读(1766) | 评论 (1)编辑 收藏

仅列出标题  下一页