• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            置頂隨筆

             

             

            最近給自己換了個(gè)老板,忙了一段時(shí)間,所以有幾個(gè)月沒(méi)寫博客,今后還是要爭(zhēng)取多寫啊,呵呵。

             

            換來(lái)新地方,第一件大的事情就是修改后端架構(gòu)和通信協(xié)議,架構(gòu)也設(shè)計(jì)得很普通,因?yàn)檫@邊的業(yè)務(wù)不需要太過(guò)復(fù)雜的后端,所以就簡(jiǎn)單設(shè)計(jì)了一下,基本是參照web的模型,符合我一貫的向web學(xué)習(xí)的思想,弄了個(gè)gate管理入口,相當(dāng)于web下的webserver,后端其他服務(wù)器掛在該gate下,相當(dāng)于web模型下的appserver,或者fastcgi模型的fastcgi進(jìn)程,gate上管理連接、合法性檢測(cè)、登錄、加密、壓縮、緩存。Gate和后端通信本來(lái)想?yún)⒄?/span>fastcgi協(xié)議,但看了之后覺(jué)得fastcgi協(xié)議還是復(fù)雜了,所以就設(shè)計(jì)了一個(gè)更簡(jiǎn)單的協(xié)議,gate和后端server之間可傳遞key:value型數(shù)據(jù)對(duì),value不局限于字符串,可以是任意數(shù)據(jù),這樣基本滿足了當(dāng)前的需求,第一版放上去之后也運(yùn)行良好,到今天也基本持續(xù)穩(wěn)定運(yùn)行快一個(gè)月了,沒(méi)出過(guò)什么事情。由于在gate這邊緩沖了job管理,所以后端server升級(jí)很方便,隨時(shí)可關(guān)閉更新,gate會(huì)在窗口時(shí)間內(nèi)將未執(zhí)行完成的任務(wù)重新提交,有此功能可放心大膽的升級(jí)后端,這個(gè)月這樣的工作做了幾次,在架構(gòu)修改之前這樣的事情幾乎是不敢做的,因?yàn)橐坏┥?jí)所有用戶全部斷開連接,而現(xiàn)在用戶則基本無(wú)感覺(jué)。Gate上的緩存層為后端減少了一些壓力,這個(gè)緩存是按照請(qǐng)求的md5key做的,并根據(jù)協(xié)議配置時(shí)效,有此cache后端大多數(shù)服務(wù)可不設(shè)計(jì)緩存或降低緩存設(shè)計(jì)的復(fù)雜度。Gate上針對(duì)敏感數(shù)據(jù)統(tǒng)一做了加密處理,主要是辛辛苦苦整理的數(shù)據(jù)不能輕易讓競(jìng)爭(zhēng)對(duì)手竊去了,呵呵。Gate也做了壓縮,現(xiàn)在是針對(duì)>=128長(zhǎng)度的包進(jìn)行壓縮,使用了qlz,壓縮效率還是很不錯(cuò)的,速度很快。目前gate后端掛接的既有win上的server也有linux上的server,這是一開始就這么規(guī)劃的,現(xiàn)在看來(lái)當(dāng)初的目的達(dá)到了,混合發(fā)揮各自的優(yōu)勢(shì),有的項(xiàng)目在原有系統(tǒng)上跑得好好的,沒(méi)必要重新開發(fā)嘛。

             

            協(xié)議設(shè)計(jì)上本來(lái)我是計(jì)劃二進(jìn)制混合json格式,以二進(jìn)制為主,但嘗試了一個(gè)協(xié)議之后發(fā)現(xiàn),這邊的小伙子們對(duì)直接操縱內(nèi)存普遍技術(shù)不過(guò)關(guān),他們大多是從java開始的,后來(lái)才學(xué)習(xí)c,對(duì)字符串用得很熟練,權(quán)衡之下采用了json為主,混合二進(jìn)制為輔的方案,這樣修改之后的協(xié)議和他們之前使用的xml類似,就是更小更緊湊一點(diǎn),使用方法上很類似,從現(xiàn)在的效果看還行,使用json格式為主的協(xié)議當(dāng)然不能跟使用pb之類的相比,解析效率上大約單線程每秒解析20來(lái)萬(wàn)10個(gè)obj的對(duì)象,速度上不算太快但也不算太慢,對(duì)付一秒至多幾萬(wàn)數(shù)據(jù)包的應(yīng)用來(lái)說(shuō)還是夠的,因?yàn)楝F(xiàn)在cpu計(jì)算能力普遍過(guò)剩,使用json的另個(gè)好處就是增刪字段很方便,各個(gè)版本之間不需要太考慮版本的問(wèn)題,要是全用二進(jìn)制格式就要麻煩很多了,在使用壓縮之后,目前的json格式協(xié)議比之前的xml協(xié)議減少了2/3的帶寬使用,總體效果還是可以的。使用json調(diào)試也很方便,我提供了一個(gè)工具,寫后端的就直接用該工具按照json格式收發(fā)數(shù)據(jù),無(wú)需等client開發(fā)好了再去做后端,之后做client也很方便,請(qǐng)求發(fā)過(guò)去之后返回來(lái)的就是標(biāo)準(zhǔn)的json格式數(shù)據(jù),同樣的解析方法,每個(gè)不同的應(yīng)用就按照不同的格式處理下即可,和web等模塊交互也很方便,這可算是額外的好處了。

             

            總之,雖然json格式存儲(chǔ)效率和解析效率跟二進(jìn)制方式還差半個(gè)量級(jí)到一個(gè)量級(jí),但合理使用還是可以的,特別是跟xml相比優(yōu)勢(shì)很明顯,權(quán)衡使用吧,當(dāng)然追求極致效率可能還是用pb之類的更合適一些,或者自己設(shè)計(jì)tlv格式。

             

            posted @ 2011-01-11 13:33 袁斌 閱讀(2532) | 評(píng)論 (3)編輯 收藏

            07年我寫了一篇文章叫《我的網(wǎng)絡(luò)模塊設(shè)計(jì)》,姑且叫那個(gè)為第一版吧,由于持續(xù)對(duì)網(wǎng)絡(luò)模塊進(jìn)行改進(jìn),所以現(xiàn)在的實(shí)現(xiàn)和當(dāng)時(shí)有很大改變,加上上層應(yīng)用越來(lái)越多,又經(jīng)過(guò)了幾年時(shí)間考驗(yàn),現(xiàn)在的實(shí)現(xiàn)方式比之前的更靈活更有效率,也因?yàn)樽罱戳艘恍┤俗鼍W(wǎng)絡(luò)程序多年竟毫無(wú)建樹,一直要用別人寫的網(wǎng)絡(luò)模塊,所以有感而寫此文,為了使得此文不受上一篇《我的網(wǎng)絡(luò)模塊設(shè)計(jì)》的影響,我決定寫之前不看原來(lái)的文章,所以此文跟原文那篇文章可能沒(méi)有太多相似性。
             一個(gè)基本的網(wǎng)絡(luò)模塊,無(wú)非就是管理N個(gè)連接,快速處理每個(gè)連接的收發(fā)數(shù)據(jù)、消息等,所謂好的網(wǎng)路模塊,無(wú)非就是穩(wěn)定、高效、靈活,下面分幾部分來(lái)寫:
             一、 連接管理
             之所以首先寫連接管理,是因?yàn)檫B接管理是核心,也是最難的地方,我寫第一個(gè)網(wǎng)絡(luò)庫(kù)之前,搜索過(guò)很多當(dāng)時(shí)可以找到的例子工程,當(dāng)時(shí)幾乎找不到可穩(wěn)定運(yùn)行的工程,當(dāng)然更找不到好的,于是摸索前進(jìn),期間對(duì)連接管理使用了各種方法,從最早一個(gè)cs(臨界區(qū)CriticalSection,我簡(jiǎn)稱cs),recv send都用這個(gè)cs,到后來(lái)send用一個(gè)cs,recv用一個(gè)cs,用多個(gè)的時(shí)候還出過(guò)錯(cuò),最后使用一個(gè)cs+一個(gè)原子值ref管理一個(gè)連接,每個(gè)連接send的時(shí)候用cs,recv的時(shí)候用ref,如果該連接的消息要跨線程異步執(zhí)行,也使用ref,如此較簡(jiǎn)單的解決了連接管理的問(wèn)題。
             同樣使用生存期管理方法,也有人用智能指針,雖然原理和我直接操縱生存期一樣,但實(shí)現(xiàn)方法畢竟不同,不過(guò)我為了讓實(shí)現(xiàn)依賴少一些沒(méi)有引入智能指針。
             當(dāng)然我后來(lái)也發(fā)現(xiàn)很多人不是用這種方法,如有些人就id來(lái)管理連接,每個(gè)連接分個(gè)id,其他操作全部用id,每次對(duì)連接的調(diào)用先翻譯一下,如果id找得到映射目標(biāo)就調(diào)用,否則就說(shuō)明該連接不存在了,這種方法簡(jiǎn)單只是不直接,多了個(gè)查找過(guò)程,另外查找的時(shí)候可能還需要全局鎖(這依賴于連接數(shù)據(jù)組織)。
             也有人使用一個(gè)線程管理連接,其他所有與該連接有關(guān)的生存期問(wèn)題全部到該線程處理,這樣也是可行的,只是需要做一個(gè)較好的包裝,如果包裝好上層調(diào)用方便,如果包裝不好,可能上層調(diào)用就有一些約束。
             雖然各種方法都有人使用,但我一直選擇直接的生存期管理方法,其實(shí)內(nèi)部實(shí)現(xiàn)的時(shí)候還是有很多優(yōu)化措施的,減少了大量addref、release的調(diào)用,進(jìn)一步提高了效率。
             二、 線程組
             我最初做網(wǎng)絡(luò)庫(kù)的時(shí)候還不是很清楚上層如何使用這個(gè)庫(kù),后來(lái)在上面做了幾個(gè)應(yīng)用之后慢慢有了更多想法,最近的網(wǎng)絡(luò)庫(kù)是設(shè)計(jì)了這么幾組線程:io線程組、同步線程組、異步線程組、時(shí)鐘線程組、log線程組,每組線程都可開可關(guān),就算io線程組也是可關(guān)的,這只是為了整個(gè)庫(kù)更靈活適用性更廣泛,如只用同步線程組或異步線程組僅將這個(gè)線程組當(dāng)一個(gè)消息隊(duì)列使用。
             Io線程組就是處理io收發(fā)的,listen recv send 以及解密解壓縮都是在這組線程,一般這組線程會(huì)開2個(gè)或2*cpu個(gè)。
             同步線程組,一般這組線程開1個(gè),用來(lái)處理logic。
             異步線程組,這組線程根據(jù)需要開0個(gè)或n個(gè),簡(jiǎn)單應(yīng)用無(wú)db等慢速操作的應(yīng)用不開,有很多db等慢速操作的可以開很多個(gè)。
             時(shí)鐘線程組,一般不開或開1個(gè)。
             Log線程組,一般開1個(gè),主要為了避免其他線程調(diào)用WriteLog的時(shí)候被磁盤io阻塞,所以弄了一個(gè)log線程。
             其實(shí)還有一個(gè)主線程,我的每組線程(包括主線程)都支持事件和定時(shí)器,io線程、同步線程、異步線程組、時(shí)鐘線程組、甚至log線程組都支持事件和定時(shí)器,到去年我還只是讓每組線程都支持事件,今年為了更好的使用時(shí)鐘我給每組線程設(shè)計(jì)了定時(shí)器,現(xiàn)在定時(shí)器線程組有點(diǎn)雞肋的味道,一般是用不上專門的定時(shí)器線程組,不過(guò)我還沒(méi)有將它刪掉,主要在我的設(shè)計(jì)里面,它和同步異步線程組一樣,都只是一組線程,如果必要的時(shí)候可以將它用作同步線程或者異步線程組,所以繼續(xù)保留了它的存在。
             這幾組線程之間都是可互發(fā)消息的,所以一個(gè)邏輯要異步到別的線程執(zhí)行是非常方便的,只要調(diào)用一下PostXXEvent(TlsInfo *ptls, DWORD dwEvent, WPARAM wParam, LPARAM lParam);我憑借這個(gè)設(shè)計(jì)使得這套網(wǎng)絡(luò)庫(kù)幾乎可以適用上層各種應(yīng)用,不管是非常簡(jiǎn)單的網(wǎng)絡(luò)應(yīng)用還是復(fù)雜的,一框打盡。對(duì)最簡(jiǎn)單的,一個(gè)io線程搞定,其他線程全關(guān),對(duì)于復(fù)雜的io線程+同步+異步+log全開。
             三、 內(nèi)存池
             內(nèi)存池其實(shí)沒(méi)有想象中的那么神秘,當(dāng)然如果要讓一個(gè)網(wǎng)絡(luò)程序持續(xù)7*24小時(shí)穩(wěn)定高效運(yùn)行,內(nèi)存池幾乎必不可少的,內(nèi)存池的作用首先是減少內(nèi)存碎片,其次是為了提高速度,我想這兩點(diǎn)很容易想明白的,關(guān)于內(nèi)存池我之前寫了系列文章,可參考我的博客:
             
            《內(nèi)存池之引言》 http://blog.csdn.net/oldworm/archive/2010/02/04/5288985.aspx
             《單線程內(nèi)存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289003.aspx
             《多線程內(nèi)存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289006.aspx
             《dlmalloc、nedmalloc》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289010.aspx
             《線程關(guān)聯(lián)內(nèi)存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289015.aspx
             《線程關(guān)聯(lián)內(nèi)存池再提速》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289018.aspx
             
            四、 定時(shí)器
             關(guān)于定時(shí)器,上面講線程組的時(shí)候已經(jīng)講過(guò),我現(xiàn)在的設(shè)計(jì)是每個(gè)線程(包括主線程)都支持定時(shí)器,調(diào)用方法都是一樣的,回調(diào)函數(shù)形式也是一樣的,由于定時(shí)器放到各組線程里面,所以減少了線程之間的切換,提高了效率。
             關(guān)于定時(shí)器,可參考《定時(shí)器模塊改造》 http://blog.csdn.net/oldworm/archive/2010/09/11/5877425.aspx
             
            五、 包格式
             關(guān)于包格式可參考《常用cs程序自定義數(shù)據(jù)包描述》 http://blog.csdn.net/oldworm/archive/2010/03/24/5413013.aspx
             
            六、 Buffer
             之前的文章其實(shí)我一直沒(méi)有提過(guò)我的buffer,其實(shí)我的buffer設(shè)計(jì)是很靈活的,現(xiàn)在它和pool也是有些關(guān)聯(lián)的,我的poolset其實(shí)底下就是按照各種不同大小的buffer預(yù)設(shè)的尺寸。Buffer我設(shè)計(jì)為循環(huán)式,不允許回繞,包含
             Char *pbase 塊基址
             Char *pread 當(dāng)前讀指針
             Char *pwrite 當(dāng)前寫指針
             DWORD tag;
             Buffer *next;
             Capacity 總分配尺寸,上面分配的時(shí)候可能只是指定了19,但實(shí)際可能分配的是32個(gè)字節(jié),所以內(nèi)部用的時(shí)候要根據(jù)capacity來(lái)最大限度的利用緩沖區(qū)。
             Buffer分配還利用了一個(gè)技巧,事實(shí)上分配的時(shí)候是一次分配一個(gè)需要的大緩沖,前面為Buffer自身的數(shù)據(jù),后面為數(shù)據(jù)部分,pbase指向數(shù)據(jù)部分,這樣處理減少了一次分配,我估計(jì)很多人都在用這個(gè)技巧。
             Pwrite總是不會(huì)小于pread的,但pread可能和pbase不一樣,僅當(dāng)后面空余空間不夠用的時(shí)候才可能會(huì)移動(dòng)數(shù)據(jù),否則數(shù)據(jù)不會(huì)移動(dòng)。
             WSARecv的時(shí)候我是這么處理的,如果首次獲取了一個(gè)包的一部分,但buffer中還有足夠的空間放下包的剩余部分,我不會(huì)再分配一個(gè)buffer去recv,而是直接用原buffer指定一個(gè)合適的偏移和size去WSARecv,這樣可以最大限度的減少?gòu)?fù)制。
             剛才還有朋友問(wèn)到我recv的層次組織,我的網(wǎng)絡(luò)庫(kù)里面是這樣組織的,OnRecv是個(gè)虛函數(shù),最基礎(chǔ)的IocpClient的OnRecv只處理數(shù)據(jù)而不解析格式,IocpClientMsg就會(huì)認(rèn)識(shí)默認(rèn)的一種包格式,這個(gè)類的OnRecv會(huì)將m_recvbuf中的數(shù)據(jù)組織為msg,并盡可能的一次返回更多個(gè)msg,回調(diào)OnMsg函數(shù),由上層決定該消息在哪個(gè)線程處理,這樣我認(rèn)為是最靈活的,如果是個(gè)很小的server,可能直接就在io線程里面處理了,也可postevent到同步線程處理,亦可PostEvent到異步線程處理。
             
            七、 TLSINFO
             TlsInfo顧名思義就是每個(gè)線程關(guān)聯(lián)的一組數(shù)據(jù),暫時(shí)我還沒(méi)有看到別人這么設(shè)計(jì),也許我設(shè)計(jì)得有些復(fù)雜了,在這個(gè)數(shù)據(jù)里面有一些常用的和該線程相關(guān)的數(shù)據(jù),如該線程的分配基、步長(zhǎng),用這兩個(gè)參數(shù)可讓每個(gè)線程制造出唯一序列,還有常用pool的地址,如tm_pool *p1k; tm_pool *p2k;… 這樣設(shè)計(jì)使得要分配的時(shí)候直接取tm_pool,最大限度的發(fā)揮了分配速度,還有一些常規(guī)參量long c; long d; DWORD a; DWORD b;… 這幾個(gè)值可理解為棧內(nèi)值,其實(shí)為了減少上層調(diào)用復(fù)雜度的,如我將一個(gè)連接的包從io線程PostEvent到同步線程處理,PostEvent首參數(shù)就是tlsinfo,PostEvent會(huì)根據(jù)tlsinfo里面的一個(gè)內(nèi)部值決定是不是要調(diào)用addref,因?yàn)槲矣袀€(gè)地方預(yù)增了2,所以大多數(shù)情況下在io發(fā)到其他線程的時(shí)候是無(wú)需調(diào)用addref的,提高了效率,tlsinfo里的其他一些值上層應(yīng)用可使用,用在邏輯處理等情況下。
             
            八、 性能分析
             *nix下有很多知名的網(wǎng)絡(luò)庫(kù),但在win下特別是使用iocp的庫(kù)里面,一直就沒(méi)有一個(gè)能作為基準(zhǔn)的庫(kù),即使asio也因?yàn)槌鰜?lái)太晚不為大多數(shù)人熟悉而不能成為基準(zhǔn)庫(kù),libevent接iocp由于采用0 buffer模擬所以也沒(méi)有發(fā)揮出足夠的性能,對(duì)比spserver我比它快70%左右,我總在想要是微軟能將他那個(gè)iocp的例子寫得更好一點(diǎn)就好了,至少學(xué)的人有一個(gè)更高一點(diǎn)的基礎(chǔ),而不至于讓http://www.codeproject.com/KB/IP/iocp_server_client.aspx這樣的垃圾代碼都能成為很多人的樣板。
             
            九、 雜談
             為了寫好一個(gè)win下穩(wěn)定高效的網(wǎng)絡(luò)庫(kù),我07年的時(shí)候幾乎搜遍了那個(gè)時(shí)間段之前所有能找到的iocp例子,還包括通過(guò)朋友等途徑看到的如snda等網(wǎng)絡(luò)庫(kù),可惜真沒(méi)找到好的,大多數(shù)例子是只要多線程發(fā)起幾千個(gè)連接不斷發(fā)送數(shù)據(jù)馬上就死了,偶爾幾個(gè)不死的(包括snda的)只要隨機(jī)連接并斷開就會(huì)產(chǎn)生句柄泄漏,關(guān)閉所有連接之后句柄并不關(guān)閉等,也就是說(shuō)這些例子連基本的生存期管理都沒(méi)搞定,能通過(guò)生存期管理并且不死的只有有限的幾個(gè),可惜性能又太差,杯具啊。
             早年寫網(wǎng)絡(luò)庫(kù)的時(shí)候也加入了sodme在google上建的那個(gè)群,當(dāng)時(shí)群還是很熱鬧的,可惜大多數(shù)人都是摸索,所以很多問(wèn)題只是討論卻從無(wú)定論,沒(méi)有誰(shuí)能說(shuō)服別人,也沒(méi)有人可輕易被說(shuō)服,要是現(xiàn)在或許有一些很有經(jīng)驗(yàn)的人,可惜那個(gè)群由于GFW現(xiàn)在雖能訪問(wèn)也不大活躍了。
             最近看到有些寫網(wǎng)絡(luò)程序7年甚至更久的人還在用libevent、ace等感想很復(fù)雜,可悲的是那些人還沒(méi)意識(shí)到用一個(gè)庫(kù)和寫一個(gè)庫(kù)有多大的區(qū)別,可能那些人一輩子也認(rèn)識(shí)不到寫一個(gè)庫(kù)比用一個(gè)庫(kù)難多少,那些人以為這些庫(kù)基本會(huì)用了,讓他自己去寫也基本是照這個(gè)模式,不會(huì)有什么突破,就無(wú)需自己動(dòng)手了,悲哀啊。當(dāng)然,要寫一個(gè)穩(wěn)定的網(wǎng)絡(luò)庫(kù)需要耗費(fèi)很多時(shí)間,特別是要寫一個(gè)能和知名庫(kù)性能接近或更好的庫(kù),更是要費(fèi)神費(fèi)力,沒(méi)點(diǎn)耐心和持久力是不可能做好的。在中文領(lǐng)域隨便查什么稍有些名氣的代碼,總是能找到很多剖析類文章,可原創(chuàng)的東西總是很少,也不知道那些大俠怎么搞的,什么都能剖析可怎么總寫不出什么像樣的東西呢。
             其實(shí)本來(lái)沒(méi)有打算寫這篇文章,可能是看了陳碩的muduo才使得我有了寫出來(lái)的沖動(dòng),大概是受到他的開源鼓勵(lì)吧。
             謹(jǐn)以此文記錄本人最近3年對(duì)網(wǎng)絡(luò)模塊的修改并簡(jiǎn)短總結(jié)。

             

            posted @ 2010-10-03 14:25 袁斌 閱讀(3253) | 評(píng)論 (5)編輯 收藏

            實(shí)用云計(jì)算環(huán)境簡(jiǎn)述

             

            如今it領(lǐng)域沒(méi)聽說(shuō)過(guò)云計(jì)算的絕對(duì)是out了,雖然大家都知道云計(jì)算,雖然很多高校很多專業(yè)都開設(shè)了云計(jì)算專業(yè),雖然很多人都在討論云計(jì)算,雖然也有少數(shù)人走在了應(yīng)用云計(jì)算的前列,然而,可悲的是,大多數(shù)人對(duì)云計(jì)算的認(rèn)識(shí)僅限于amazon、googlemicrosoft、ibm有能力架設(shè)云計(jì)算環(huán)境,其他公司都靠邊,甚至唯他們的云計(jì)算才叫云計(jì)算,別的企業(yè)根本不可能做云計(jì)算,各級(jí)政府部門最搞笑了,動(dòng)不動(dòng)花多少錢引進(jìn)某某云計(jì)算環(huán)境,填補(bǔ)某某空白,多少cpu多少機(jī)器每秒多少萬(wàn)億次計(jì)算,最終是不是一堆浪費(fèi)電力的擺設(shè)也沒(méi)有人知道,也沒(méi)人去過(guò)問(wèn)。

            略感欣慰的是,很多企業(yè)都在務(wù)實(shí)地部署自己的云計(jì)算環(huán)境,大如騰訊、淘寶、百度、小如我們這樣剛成立的小公司,其實(shí)要部署一個(gè)私有云計(jì)算環(huán)境并沒(méi)有那么難,以我個(gè)人的經(jīng)驗(yàn)來(lái)看,如果有一個(gè)精干的小團(tuán)隊(duì),幾個(gè)人一個(gè)月部署一個(gè)私有云計(jì)算環(huán)境是完全可能可行的。在我看來(lái),所謂云計(jì)算就是分布式存儲(chǔ)+分布式計(jì)算,不局限于底下oswin還是*nix,也不局限于是局域網(wǎng)環(huán)境還是廣域網(wǎng)環(huán)境,也不管上面跑的是c++的程序還是javascript的程序,下面簡(jiǎn)單介紹下我設(shè)計(jì)的一個(gè)即時(shí)查詢價(jià)格的云計(jì)算體系:

            我一直在win下開發(fā),win用得非常熟練,所以我把云計(jì)算環(huán)境部署在windows之上,當(dāng)然也考慮到windows的機(jī)器眾多,tasknode可輕易找到非常多的目標(biāo)機(jī)器,我部署的云計(jì)算環(huán)境主要分兩類節(jié)點(diǎn),jobservertasknode,jobserver主管任務(wù)切割、任務(wù)調(diào)度,tasknode是計(jì)算節(jié)點(diǎn)。另外還有一些節(jié)點(diǎn),jobowner可連接jobserver并提交任務(wù),并可查詢?cè)撊蝿?wù)的執(zhí)行情況,admin可連接jobserver查詢jobserver的狀態(tài)。

             

            其實(shí)這些上篇博客已經(jīng)寫過(guò),我再講的詳細(xì)一點(diǎn),看具體的執(zhí)行情況,首先jobownerjobserver提交package,這個(gè)package是一個(gè)zip文件,包含一組文件,jobowner提交package之后jobserver會(huì)根據(jù)約定的規(guī)則管理package,并在jobserver展開該package,如下:

             

             

            Jobowner連到jobserver之后,發(fā)出如下的命令到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

            上面是用我設(shè)計(jì)的一種混合顯示格式顯示的包數(shù)據(jù),可以看到里面帶上了ppsget.dll,這就是指定包內(nèi)部名,其實(shí)還可以這樣ppsget.dll:getpage,如此一個(gè)dll就可支持多個(gè)IJobTask輸出,getpage只是獲得其中一個(gè)IJobTask接口(關(guān)于IJobTask接口參考上一篇云計(jì)算實(shí)踐2的文章)。具體命令是json格式,主要是為了方便信息傳輸和解析。Jobserver接收到該命令之后,調(diào)用ppsget.dllIJobTask接口中的split函數(shù),將該任務(wù)分解,之后調(diào)度Tasknode執(zhí)行,tasknode收到jobserver發(fā)過(guò)來(lái)的任務(wù)之后,檢查包名稱,如果缺少就會(huì)主動(dòng)向jobserver要求發(fā)送相應(yīng)的包,并進(jìn)行部署,待部署完成之后從包獲取指定的IJobTask接口,執(zhí)行該接口的map函數(shù),將結(jié)果按照約定的格式發(fā)給jobserver,最后由jobserver調(diào)用IJobTask中的reduce函數(shù)進(jìn)行打包,最后將結(jié)果發(fā)給jobowner并記錄相關(guān)Log

            上圖中還可看到一個(gè)HashCrackCloud.dll,這是另一個(gè)云計(jì)算環(huán)境下破解md5密碼的dll,這個(gè)上篇文章也寫了一下,這里就不詳述了。

             

            為使得tasknode可適應(yīng)各種機(jī)器環(huán)境,我把tasknode設(shè)計(jì)為一個(gè)dll,該dll內(nèi)部自己管理消息及任務(wù)執(zhí)行,該dll可被加載到各種容器進(jìn)程(如gui進(jìn)程、console進(jìn)程、service進(jìn)程)等執(zhí)行,看下我的tasknode和它的容器進(jìn)程:

             

            這也算是我的得意設(shè)計(jì)吧,這樣設(shè)計(jì)的tasknodewindows系統(tǒng)下的確具有很高的靈活性。

            這樣的tasknode甚至可直接加載在jobserver進(jìn)程,也可被任意win系列機(jī)器的任意進(jìn)程加載參與運(yùn)算,用主動(dòng)加載或被動(dòng)加載都很方便,極大的方便了云計(jì)算環(huán)境的部署,反正具體執(zhí)行的任務(wù)都由package完成,tasknode只要按照約定的規(guī)則部署 package即可,所以這種云計(jì)算環(huán)境是非常輕量級(jí)又非常靈活的,開發(fā)一個(gè)新的任務(wù)只要做一個(gè)新的IJobTask即可,目前我這套體系除了沒(méi)有考慮太多安全性之外,這個(gè)云計(jì)算環(huán)境的實(shí)施還是非常容易的,實(shí)際上我們這個(gè)價(jià)格查詢的后臺(tái)云計(jì)算環(huán)境只用了不到2周的時(shí)間就開發(fā)完成。

            再看下jobserver記錄的每個(gè)joblog

             

            log中可很容易的分析出一個(gè)job每個(gè)task的執(zhí)行情況,并可根據(jù)這些數(shù)據(jù)進(jìn)行相應(yīng)的優(yōu)化處理。

            之所以把jobservertasknode以及package都寫出來(lái),主要是為了表達(dá)一個(gè)看法,要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的云計(jì)算環(huán)境其實(shí)并不難,有經(jīng)驗(yàn)的團(tuán)隊(duì)很容易就能做出來(lái),參考下googlemap/reduce論文,按照自己的需要簡(jiǎn)化實(shí)現(xiàn),真理在實(shí)踐中,如果只是仰望googleamazon,那就真的是在云中霧里,另一個(gè)想要表達(dá)的就是云的形式是多種多樣的,并不一定amazone、google的云計(jì)算環(huán)境才是標(biāo)準(zhǔn)的,對(duì)實(shí)用派來(lái)說(shuō),形式都是次要的,實(shí)用才是關(guān)鍵的。

            posted @ 2010-10-03 14:23 袁斌 閱讀(1812) | 評(píng)論 (1)編輯 收藏

            2011年2月1日

            Windows下兩種iocp實(shí)現(xiàn)的差距

             

             

            之前幾天說(shuō)過(guò),因?yàn)榻?jīng)典iocp實(shí)現(xiàn)(以下簡(jiǎn)稱經(jīng)典實(shí)現(xiàn))多個(gè)io線程綁定在一個(gè)iocp上,這樣內(nèi)部管理了iocp隊(duì)列的處理,內(nèi)部決定是不是需要線程切換,我上次修改的一個(gè)版本(以下簡(jiǎn)稱實(shí)現(xiàn)2),用了多個(gè)io線程,每個(gè)iocp隊(duì)列僅綁定一個(gè)io線程,一組用戶共享一個(gè)io線程,這和經(jīng)典的多線程epoll模型的做法是很相似的,這樣每個(gè)io線程是可以獨(dú)立控制了,但理論上這種做法沒(méi)有發(fā)揮iocp自動(dòng)管理線程切換的優(yōu)勢(shì),昨晚沒(méi)事用這兩種實(shí)現(xiàn)分別做了個(gè)echoserver測(cè)試了一下,這兩套實(shí)現(xiàn)代碼僅40行左右不同,其他完全一樣,效果真的是差很多,測(cè)試僅用一個(gè)進(jìn)程模擬了4000個(gè)客戶端,每秒1個(gè)包,先看實(shí)現(xiàn)2的,cpu14%,2個(gè)io線程,1個(gè)accept線程,1個(gè)主線程,其他線程都沒(méi)干活閑置。

             

            Cpu

            Memory

            Threads

            handles

            14

            40088k

            8

            4236

             

            再看經(jīng)典實(shí)現(xiàn),cpu幾乎一直是0%,2個(gè)io線程,accept也是在io線程里面處理,其他跟實(shí)現(xiàn)2一樣,測(cè)試客戶端也一樣。

            Cpu

            Memory

            Threads

            handles

            0

            39244k

            7

            4336

             

            說(shuō)實(shí)話,在測(cè)試之前我也沒(méi)想到有這么大的差距,經(jīng)典實(shí)現(xiàn)就是1.2w個(gè)連接連上來(lái)還是這樣,就是內(nèi)存占用多一點(diǎn):

            Cpu

            Memory

            Threads

            handles

            0

            112068k

            7

            12280

             

            習(xí)慣上總有人喜歡拿epolliocp來(lái)對(duì)比,我到現(xiàn)在也沒(méi)看到真正公平的對(duì)比,就算是相對(duì)公平的也沒(méi)見到,因?yàn)樵谖铱磥?lái),要對(duì)比硬件應(yīng)該是一樣的,os都應(yīng)該是最新的,最重要的是,server端程序應(yīng)該都是發(fā)揮了各自優(yōu)勢(shì)的,如果拿我這里的實(shí)現(xiàn)2去代表iocp的水平和epoll對(duì)比,勢(shì)必造成比epoll差很多的結(jié)果,然而這顯然是不正確的。

             

            epoll經(jīng)典多線程模式實(shí)際實(shí)現(xiàn)和實(shí)現(xiàn)2很相似,理論上也有類似的線程切換問(wèn)題,不知道效率怎樣。

             

             

            posted @ 2011-02-01 10:48 袁斌 閱讀(11954) | 評(píng)論 (3)編輯 收藏

            2011年1月30日

            回調(diào)函數(shù)的常見實(shí)現(xiàn)方式及速度比較

             

             

            回調(diào)函數(shù)實(shí)在是用得太廣泛,回調(diào)函數(shù)又有多種實(shí)現(xiàn)方式,如:

            1、  靜態(tài)函數(shù)

            2、  虛函數(shù)

            3、  函數(shù)對(duì)象

            4、  傳統(tǒng)c函數(shù),通過(guò)一個(gè)void *傳遞對(duì)象地址,內(nèi)部強(qiáng)制轉(zhuǎn)換

            5、  fastdelegate

            6、  Tr1::function + bind

            7、  Boost::Function + bind

            基本上速度是按照由快到慢的順序排列的,就是

            1 > 2 > 3 > 4 > 5 > 6 > 7

            其實(shí)234速度很接近,有的時(shí)候函數(shù)對(duì)象效率更高一點(diǎn),基本上越是高級(jí)的方法使用起來(lái)越方便,但速度越慢,越是傳統(tǒng)的方法速度越快,呵呵,看來(lái)做server端程序要綜合考慮效率太新的東西還是要少用啊,還是用傳統(tǒng)的方法比較靠譜一點(diǎn),當(dāng)然如果調(diào)用次數(shù)不多的地方,使用更方便的方法還是好一些,畢竟我們要綜合權(quán)衡,而不能死板恪守教條。

             

             

            posted @ 2011-01-30 11:19 袁斌 閱讀(4589) | 評(píng)論 (3)編輯 收藏

            2011年1月26日

            一套網(wǎng)絡(luò)框架的杯具

            之前設(shè)計(jì)了一套網(wǎng)絡(luò)框架,持續(xù)改進(jìn)了很多年,使用在很多項(xiàng)目上,綜合效率還行,也很穩(wěn)定,一直以來(lái)對(duì)這套東西信心滿滿,總以為啥問(wèn)題都好解決,但最近就有個(gè)需求讓我選擇還是改了下這個(gè)框架。

            之前的框架是這樣的,可以開一組N個(gè)io線程,可以開一組N個(gè)同步線程(默認(rèn)1個(gè)),可以開一組N個(gè)異步線程(默認(rèn)1個(gè)),可以開一組N個(gè)timer線程(默認(rèn)1個(gè)),可以開一組N個(gè)異步線程(默認(rèn)cpu個(gè)),每組可獨(dú)立受控,每組可支持自定義消息,可支持timer,一組N個(gè)如果N大于1則無(wú)法直接給這組里面的特定線程發(fā)消息,只能給一組發(fā)消息,這個(gè)組里面會(huì)選擇某個(gè)合適的線程處理這個(gè)消息,這也是iocp高效和典型的用法了,但這也正是問(wèn)題的結(jié)癥所在。

            Linux下的多線程服務(wù)器更常見的做法跟這個(gè)不大相似,一般都是將某些socket分配到某些線程epoll,分好之后就是固定的,不再變化,跟Iocpsocket綁定到一組線程的做法不同,由于某個(gè)socket直接綁定到了某個(gè)線程,所以有些問(wèn)題就變得簡(jiǎn)單了,如同一個(gè)連接的在同一個(gè)線程內(nèi)消息進(jìn)行了同步,要跟io線程綁定私有化數(shù)據(jù)也簡(jiǎn)單了,而且每個(gè)線程可獨(dú)立受控,所以很容易實(shí)現(xiàn)一組io各自掛tls(線程局部存儲(chǔ))數(shù)據(jù),而我現(xiàn)在做的這套框架就是這方面不好控了,其實(shí)也很難說(shuō)這兩種意義上的框架到底誰(shuí)更優(yōu),如用在web型應(yīng)用上這種socket被一組io線程管理的模式很方便效率也高,但我現(xiàn)在的需求需要某個(gè)socket使用線程相關(guān)數(shù)據(jù),以避免數(shù)據(jù)之間的鎖,我用內(nèi)存換時(shí)間,由于在原來(lái)的框架上增邏輯難以實(shí)現(xiàn)可直接控制io線程的框架的,所以花了一個(gè)晚上重新改寫了一套框架,在原來(lái)iocpframe的基礎(chǔ)上派生了一組帶2名稱的類,除替換類名之外只修改了幾十行代碼就做好了,總的來(lái)說(shuō)花的時(shí)間還是比較少的。修改后io線程一組,但獨(dú)立受控,外部可對(duì)這組線程中的某一個(gè)直接發(fā)消息,基本滿足了需求,現(xiàn)在要給每個(gè)io線程綁定私有數(shù)據(jù)并觸發(fā)特定消息比之前簡(jiǎn)單多了,而且絕對(duì)無(wú)鎖。

             

            posted @ 2011-01-26 16:14 袁斌 閱讀(2778) | 評(píng)論 (1)編輯 收藏

            2011年1月23日

             

            由于原先的appserver功能不斷增多,最近又增了兩個(gè)功能,需要不斷從后端memcached中提取數(shù)據(jù)并進(jìn)行計(jì)算,由于提取數(shù)據(jù)量大且頻繁,導(dǎo)致效率很低,粗測(cè)了一下,獲取數(shù)據(jù)和格式化等操作花了90%以上的時(shí)間,由此設(shè)想將memcached改寫或重寫一個(gè)支持memcached的服務(wù)器,將計(jì)算功能和memcached做到一起,讓獲取數(shù)據(jù)的路徑最短,也就最大限度減少了數(shù)據(jù)傳輸和格式化等操作,就是類似存儲(chǔ)過(guò)程一樣啦,這部分可以考慮使用插件來(lái)實(shí)現(xiàn),甚至可考慮使用腳本語(yǔ)言來(lái)實(shí)現(xiàn)。

            網(wǎng)上搜了一下,果然發(fā)現(xiàn)早有人這么干了,正所謂英雄所見啊,呵呵。具體方法倒很多,自定義key命名,根據(jù)特殊keyget、set、replace上做特殊操作,或者根據(jù)命令中的flag等做特殊處理,或者擴(kuò)充stat命令等,都是可以的,我們暫時(shí)就考慮修改特殊的鍵值做特殊處理。

            要做一個(gè)完備的既支持ascii命令又支持binary命令的兼容memcached還是有一點(diǎn)點(diǎn)麻煩的,我暫時(shí)也沒(méi)有太多需求,所以就僅支持了ascii命令,其實(shí)也是考慮支持ascii的客戶端更多,各種語(yǔ)言的支持mc的客戶端數(shù)不勝數(shù),但大多只支持ascii命令。由于我之前為了測(cè)試服務(wù)器框架效率,做過(guò)一個(gè)支持ascii命令的memcached兼容版本,因此拿過(guò)來(lái)直接使用太方便了,這個(gè)版本的實(shí)現(xiàn)其實(shí)很容易,如果有一個(gè)較好的框架代碼的話基本上在一天之內(nèi)可做完,當(dāng)然要做到很好可能需要多花一些時(shí)間,我現(xiàn)在做的也不是特好,要完全取代memcached使用還是有些差距,主要是一些過(guò)期機(jī)制等沒(méi)完全實(shí)現(xiàn),雖然速度上比標(biāo)準(zhǔn)mc版本還要快一點(diǎn),呵呵,因?yàn)闀簳r(shí)的確是不需要這些過(guò)期機(jī)制,所以也沒(méi)打算這個(gè)版本實(shí)現(xiàn),其他功能基本上都有。

            以后準(zhǔn)備將這個(gè)memcached解碼部分作為一個(gè)單獨(dú)的解析器,和支持其他協(xié)議一樣,換上這個(gè)解析那就支持mc協(xié)議了,還是很方便的,以后有空還是要做個(gè)支持binary協(xié)議的,以便可以更高效的解決問(wèn)題。

            想到server能支持Memcached協(xié)議真是好啊,客戶端基本只要用個(gè)libmemcached就好了,多服務(wù)器分布,容錯(cuò),多份數(shù)據(jù)啥的都有現(xiàn)成的解決方案,只要把server做穩(wěn)定了就基本ok了,對(duì)咱這種小團(tuán)隊(duì)來(lái)說(shuō)再合適不過(guò)了,節(jié)省了很多開發(fā)維護(hù)成本啊,現(xiàn)在內(nèi)存這么便宜,部署幾個(gè)點(diǎn)實(shí)在是很easy的問(wèn)題。

             

             

            posted @ 2011-01-23 17:13 袁斌 閱讀(2063) | 評(píng)論 (1)編輯 收藏

            2011年1月21日

             

            關(guān)于內(nèi)存數(shù)據(jù)庫(kù)

             

            最近要將一些數(shù)據(jù)放到內(nèi)存里面做很高的并發(fā)操作,考慮了很多方案,

            1、 簡(jiǎn)單點(diǎn)使用map hash_map等自己管理。

            2、 sqlite內(nèi)存表。

            3、 fastdb內(nèi)存數(shù)據(jù)庫(kù)。

            4、 ExtremeDb,TimesTen等。

            比較測(cè)試了一下123,發(fā)現(xiàn)還是自己實(shí)現(xiàn)速度最快,比fastdb模式快3-5倍,fastdb模式比sqlite內(nèi)存表模式快10倍左右,由于自己實(shí)現(xiàn)不具有典型通用性,多線程下訪問(wèn)效率會(huì)下降,要管理多線程下各種更新查找等還是比較麻煩的,所以在13方案之間糾結(jié)。

            為了使得決策更好一些,暫時(shí)還沒(méi)做決定,順便到萬(wàn)方等上面搜索了一些論文來(lái)看,看來(lái)看去看得真來(lái)氣啊,雖然都叫內(nèi)存數(shù)據(jù)庫(kù)但各種實(shí)現(xiàn)的都有,有用gdbm來(lái)做的,有直接map管理的,有hash管理數(shù)據(jù)的,有t樹管理的,有數(shù)組隊(duì)列管理的,有的明顯就是個(gè)不大變的東西還弄個(gè)啥事務(wù)的,靠,剛剛居然還看到一篇鳥文《電網(wǎng)監(jiān)控系統(tǒng)實(shí)時(shí)數(shù)據(jù)庫(kù)的設(shè)計(jì)與實(shí)現(xiàn)》里面的測(cè)試居然是1000條,插入時(shí)間80毫秒,真可笑啊,區(qū)區(qū)這么點(diǎn)數(shù)據(jù)也好意思測(cè),還要花80毫秒,還自以為很快,這個(gè)速度至少可提高1000倍以上啊,這幫垃圾,寫的啥鳥文章,研究個(gè)屁啊。

            看完這十來(lái)篇論文,俺的思緒又回到1999年,當(dāng)年我給別人優(yōu)化過(guò)一個(gè)電信計(jì)費(fèi)的軟件(看的論文里面有好幾篇講電信計(jì)費(fèi)的),當(dāng)時(shí)有個(gè)朋友的朋友拿了個(gè)需求過(guò)來(lái),7000萬(wàn)條記錄,原來(lái)計(jì)算費(fèi)單要花十幾個(gè)小時(shí)吧,我?guī)退牧讼拢畞?lái)分鐘就算完了,朋友很滿意,當(dāng)時(shí)的做法很簡(jiǎn)單,就是弄了個(gè)mmtable,大體就是跟map類似的東西吧,那個(gè)時(shí)候map還沒(méi)流行起來(lái),俺也不知道,所以就自己弄了個(gè)內(nèi)存表,內(nèi)部基本就是二分查找了,那個(gè)時(shí)候我對(duì)hash都不大熟悉,B樹之類的算法剛接觸也不會(huì)用,就這么個(gè)東西當(dāng)時(shí)的電腦也只要花十來(lái)分鐘,我估計(jì)就算是那個(gè)老程序放在現(xiàn)在的普通臺(tái)式機(jī)上要不了幾秒鐘就可算完。也不知道這么幾千萬(wàn)條記錄的小需求怎么在這幫人眼里就成了什么海量數(shù)據(jù),對(duì)俺來(lái)說(shuō)跟玩似的,區(qū)區(qū)幾千萬(wàn)嘛,不過(guò)是俺拿來(lái)測(cè)試用的。

            去年中做了個(gè)md5 hash反查的東西,數(shù)據(jù)都是幾百億到幾萬(wàn)億的,后來(lái)的效果就是一個(gè)文件可存萬(wàn)億記錄,一次查詢平均1.2IO,即使全放在SATA磁盤上也就十來(lái)毫秒而已。

            區(qū)區(qū)幾千萬(wàn)條記錄咋就叫什么海量數(shù)據(jù)呢,海量個(gè)毛啊,內(nèi)存都放得下的叫什么海量,現(xiàn)在服務(wù)器動(dòng)不動(dòng)都是幾十G內(nèi)存,區(qū)區(qū)千萬(wàn)根本算不上什么,查詢定位都可到微妙了,1秒插入至少千萬(wàn)條了,居然還看到1000條插入的測(cè)試,真是不得不佩服國(guó)內(nèi)這幫垃圾研究生的水平,也不知道這種論文咋就能通過(guò)審查,只能得出結(jié)論他們的老師也都是豬。

                     罵歸罵自己的問(wèn)題還需要繼續(xù)努力,對(duì)咱目前的需求來(lái)說(shuō)自己管理數(shù)據(jù),即使一個(gè)線程都搞得定,因?yàn)椴贿^(guò)區(qū)區(qū)幾個(gè)表,幾十萬(wàn)條記錄而已,不過(guò)這種10年前咱就會(huì)的技術(shù)還真是拿不出手,怎么的也得做得更好一點(diǎn),呵呵,繼續(xù)研究吧,多線程下內(nèi)存數(shù)據(jù)庫(kù),從概念上看的確是個(gè)很有吸引力的東西,要是性能跟得上,其實(shí)在很多地方可以取代普通的數(shù)據(jù)結(jié)構(gòu)用法了,可以大大減少編程難度,甚至我在想如果有個(gè)支持事務(wù)的內(nèi)存數(shù)據(jù)庫(kù),之前設(shè)計(jì)的cad類軟件的undo/redo都可以用事務(wù)來(lái)實(shí)現(xiàn),完全可以拋棄先前設(shè)計(jì)的復(fù)雜結(jié)構(gòu),其實(shí)這種東西即使不用內(nèi)存數(shù)據(jù)庫(kù)就算是用個(gè)sqlite都完全能搞定,唉,往事不堪回首啊,看來(lái)數(shù)據(jù)庫(kù)方面的確得多花功夫,特別是多線程和分布式模式下的內(nèi)存數(shù)據(jù)庫(kù)。

             

             

            posted @ 2011-01-21 13:37 袁斌 閱讀(8916) | 評(píng)論 (8)編輯 收藏

            2011年1月11日

             

             

            最近給自己換了個(gè)老板,忙了一段時(shí)間,所以有幾個(gè)月沒(méi)寫博客,今后還是要爭(zhēng)取多寫啊,呵呵。

             

            換來(lái)新地方,第一件大的事情就是修改后端架構(gòu)和通信協(xié)議,架構(gòu)也設(shè)計(jì)得很普通,因?yàn)檫@邊的業(yè)務(wù)不需要太過(guò)復(fù)雜的后端,所以就簡(jiǎn)單設(shè)計(jì)了一下,基本是參照web的模型,符合我一貫的向web學(xué)習(xí)的思想,弄了個(gè)gate管理入口,相當(dāng)于web下的webserver,后端其他服務(wù)器掛在該gate下,相當(dāng)于web模型下的appserver,或者fastcgi模型的fastcgi進(jìn)程,gate上管理連接、合法性檢測(cè)、登錄、加密、壓縮、緩存。Gate和后端通信本來(lái)想?yún)⒄?/span>fastcgi協(xié)議,但看了之后覺(jué)得fastcgi協(xié)議還是復(fù)雜了,所以就設(shè)計(jì)了一個(gè)更簡(jiǎn)單的協(xié)議,gate和后端server之間可傳遞key:value型數(shù)據(jù)對(duì),value不局限于字符串,可以是任意數(shù)據(jù),這樣基本滿足了當(dāng)前的需求,第一版放上去之后也運(yùn)行良好,到今天也基本持續(xù)穩(wěn)定運(yùn)行快一個(gè)月了,沒(méi)出過(guò)什么事情。由于在gate這邊緩沖了job管理,所以后端server升級(jí)很方便,隨時(shí)可關(guān)閉更新,gate會(huì)在窗口時(shí)間內(nèi)將未執(zhí)行完成的任務(wù)重新提交,有此功能可放心大膽的升級(jí)后端,這個(gè)月這樣的工作做了幾次,在架構(gòu)修改之前這樣的事情幾乎是不敢做的,因?yàn)橐坏┥?jí)所有用戶全部斷開連接,而現(xiàn)在用戶則基本無(wú)感覺(jué)。Gate上的緩存層為后端減少了一些壓力,這個(gè)緩存是按照請(qǐng)求的md5key做的,并根據(jù)協(xié)議配置時(shí)效,有此cache后端大多數(shù)服務(wù)可不設(shè)計(jì)緩存或降低緩存設(shè)計(jì)的復(fù)雜度。Gate上針對(duì)敏感數(shù)據(jù)統(tǒng)一做了加密處理,主要是辛辛苦苦整理的數(shù)據(jù)不能輕易讓競(jìng)爭(zhēng)對(duì)手竊去了,呵呵。Gate也做了壓縮,現(xiàn)在是針對(duì)>=128長(zhǎng)度的包進(jìn)行壓縮,使用了qlz,壓縮效率還是很不錯(cuò)的,速度很快。目前gate后端掛接的既有win上的server也有linux上的server,這是一開始就這么規(guī)劃的,現(xiàn)在看來(lái)當(dāng)初的目的達(dá)到了,混合發(fā)揮各自的優(yōu)勢(shì),有的項(xiàng)目在原有系統(tǒng)上跑得好好的,沒(méi)必要重新開發(fā)嘛。

             

            協(xié)議設(shè)計(jì)上本來(lái)我是計(jì)劃二進(jìn)制混合json格式,以二進(jìn)制為主,但嘗試了一個(gè)協(xié)議之后發(fā)現(xiàn),這邊的小伙子們對(duì)直接操縱內(nèi)存普遍技術(shù)不過(guò)關(guān),他們大多是從java開始的,后來(lái)才學(xué)習(xí)c,對(duì)字符串用得很熟練,權(quán)衡之下采用了json為主,混合二進(jìn)制為輔的方案,這樣修改之后的協(xié)議和他們之前使用的xml類似,就是更小更緊湊一點(diǎn),使用方法上很類似,從現(xiàn)在的效果看還行,使用json格式為主的協(xié)議當(dāng)然不能跟使用pb之類的相比,解析效率上大約單線程每秒解析20來(lái)萬(wàn)10個(gè)obj的對(duì)象,速度上不算太快但也不算太慢,對(duì)付一秒至多幾萬(wàn)數(shù)據(jù)包的應(yīng)用來(lái)說(shuō)還是夠的,因?yàn)楝F(xiàn)在cpu計(jì)算能力普遍過(guò)剩,使用json的另個(gè)好處就是增刪字段很方便,各個(gè)版本之間不需要太考慮版本的問(wèn)題,要是全用二進(jìn)制格式就要麻煩很多了,在使用壓縮之后,目前的json格式協(xié)議比之前的xml協(xié)議減少了2/3的帶寬使用,總體效果還是可以的。使用json調(diào)試也很方便,我提供了一個(gè)工具,寫后端的就直接用該工具按照json格式收發(fā)數(shù)據(jù),無(wú)需等client開發(fā)好了再去做后端,之后做client也很方便,請(qǐng)求發(fā)過(guò)去之后返回來(lái)的就是標(biāo)準(zhǔn)的json格式數(shù)據(jù),同樣的解析方法,每個(gè)不同的應(yīng)用就按照不同的格式處理下即可,和web等模塊交互也很方便,這可算是額外的好處了。

             

            總之,雖然json格式存儲(chǔ)效率和解析效率跟二進(jìn)制方式還差半個(gè)量級(jí)到一個(gè)量級(jí),但合理使用還是可以的,特別是跟xml相比優(yōu)勢(shì)很明顯,權(quán)衡使用吧,當(dāng)然追求極致效率可能還是用pb之類的更合適一些,或者自己設(shè)計(jì)tlv格式。

             

            posted @ 2011-01-11 13:33 袁斌 閱讀(2532) | 評(píng)論 (3)編輯 收藏

            2010年10月3日

            昨天去見兩個(gè)老鄉(xiāng),多年的朋友同學(xué),也是搞技術(shù)的,大家都在上海,只是交流不是太多,聊起我做過(guò)的一些東西,他覺(jué)得不大相信,我說(shuō)我寫的遠(yuǎn)程控制程序全dll組成,所有模塊可熱升級(jí),包括主模塊,主模塊小于20k,他似乎難于相信,我跟他說(shuō)這個(gè)程序還是2001年做的,他就更難相信了。后來(lái)又說(shuō)起我最近做的那個(gè)云計(jì)算的價(jià)格查詢,他也很難相信底下是云計(jì)算,由于沒(méi)帶機(jī)器也沒(méi)法給他看后臺(tái)服務(wù)器,所以我估計(jì)他最后還是半信半疑吧。上周另一個(gè)朋友說(shuō)他們老板有個(gè)項(xiàng)目十來(lái)個(gè)人做了3年,一直做不穩(wěn)定,我說(shuō)給我一段時(shí)間我肯定能把他整穩(wěn)定,后來(lái)給他看了我之前做的一些東西,游戲等,似乎他還在懷疑我的能力,這幾個(gè)其實(shí)都算是對(duì)我有些了解的朋友了,看來(lái)我還是宣傳得太少啊。我知道大家都對(duì)出身大公司的人有種崇拜,我等一直在小公司混的人沒(méi)什么人瞧得上,可是我又不能跟他們說(shuō)哪年哪月,我到某個(gè)公司轉(zhuǎn)了下,看了某某寫的代碼,你所崇拜的人不過(guò)如此,哪個(gè)工程里面寫了個(gè)敗筆 等等

            可能我身邊也就合作伙伴、曾經(jīng)的老板、同事、敵人知道我到底什么水平,2000年的時(shí)候就帶隊(duì)做幾十萬(wàn)行的項(xiàng)目,連續(xù)做了幾個(gè),為他們申請(qǐng)軟件企業(yè)奠定基礎(chǔ)。云計(jì)算的價(jià)格查詢,3周完成,帶2個(gè)客戶端的棋牌游戲帶了兩個(gè)朋友一起半年完成,全部模塊接口化,模塊可單獨(dú)升級(jí)。Netdongle,一個(gè)master、slave多重保護(hù)的網(wǎng)絡(luò)驗(yàn)證系統(tǒng),支持由控制端上傳dlldb等,也就一個(gè)月完成。這些程序上線之后就幾乎不用修改,一直穩(wěn)定運(yùn)行哦,一般的程序要做到第一版版本出去就幾乎不出錯(cuò)是很難的,沒(méi)有一定功力的人是做不到的。

            昨天吹了下牛,我說(shuō)windows下應(yīng)用層的軟件基本沒(méi)有做不出來(lái)的,或許牛吹得有點(diǎn)大,那朋友驚訝了一下。

            今年濕疹治好之后發(fā)現(xiàn)自己戰(zhàn)斗力提升很多,之前做事情總覺(jué)得差一口氣,精力不濟(jì),現(xiàn)在覺(jué)得精力充沛,酒也能喝了,活也能干了,速度也快了,也敢出去跟別人交流了,之前一直自卑,沒(méi)病真好啊。

            以后要多宣傳,多吹牛啊!

            posted @ 2010-10-03 14:26 袁斌 閱讀(658) | 評(píng)論 (0)編輯 收藏

            07年我寫了一篇文章叫《我的網(wǎng)絡(luò)模塊設(shè)計(jì)》,姑且叫那個(gè)為第一版吧,由于持續(xù)對(duì)網(wǎng)絡(luò)模塊進(jìn)行改進(jìn),所以現(xiàn)在的實(shí)現(xiàn)和當(dāng)時(shí)有很大改變,加上上層應(yīng)用越來(lái)越多,又經(jīng)過(guò)了幾年時(shí)間考驗(yàn),現(xiàn)在的實(shí)現(xiàn)方式比之前的更靈活更有效率,也因?yàn)樽罱戳艘恍┤俗鼍W(wǎng)絡(luò)程序多年竟毫無(wú)建樹,一直要用別人寫的網(wǎng)絡(luò)模塊,所以有感而寫此文,為了使得此文不受上一篇《我的網(wǎng)絡(luò)模塊設(shè)計(jì)》的影響,我決定寫之前不看原來(lái)的文章,所以此文跟原文那篇文章可能沒(méi)有太多相似性。
             一個(gè)基本的網(wǎng)絡(luò)模塊,無(wú)非就是管理N個(gè)連接,快速處理每個(gè)連接的收發(fā)數(shù)據(jù)、消息等,所謂好的網(wǎng)路模塊,無(wú)非就是穩(wěn)定、高效、靈活,下面分幾部分來(lái)寫:
             一、 連接管理
             之所以首先寫連接管理,是因?yàn)檫B接管理是核心,也是最難的地方,我寫第一個(gè)網(wǎng)絡(luò)庫(kù)之前,搜索過(guò)很多當(dāng)時(shí)可以找到的例子工程,當(dāng)時(shí)幾乎找不到可穩(wěn)定運(yùn)行的工程,當(dāng)然更找不到好的,于是摸索前進(jìn),期間對(duì)連接管理使用了各種方法,從最早一個(gè)cs(臨界區(qū)CriticalSection,我簡(jiǎn)稱cs),recv send都用這個(gè)cs,到后來(lái)send用一個(gè)cs,recv用一個(gè)cs,用多個(gè)的時(shí)候還出過(guò)錯(cuò),最后使用一個(gè)cs+一個(gè)原子值ref管理一個(gè)連接,每個(gè)連接send的時(shí)候用cs,recv的時(shí)候用ref,如果該連接的消息要跨線程異步執(zhí)行,也使用ref,如此較簡(jiǎn)單的解決了連接管理的問(wèn)題。
             同樣使用生存期管理方法,也有人用智能指針,雖然原理和我直接操縱生存期一樣,但實(shí)現(xiàn)方法畢竟不同,不過(guò)我為了讓實(shí)現(xiàn)依賴少一些沒(méi)有引入智能指針。
             當(dāng)然我后來(lái)也發(fā)現(xiàn)很多人不是用這種方法,如有些人就id來(lái)管理連接,每個(gè)連接分個(gè)id,其他操作全部用id,每次對(duì)連接的調(diào)用先翻譯一下,如果id找得到映射目標(biāo)就調(diào)用,否則就說(shuō)明該連接不存在了,這種方法簡(jiǎn)單只是不直接,多了個(gè)查找過(guò)程,另外查找的時(shí)候可能還需要全局鎖(這依賴于連接數(shù)據(jù)組織)。
             也有人使用一個(gè)線程管理連接,其他所有與該連接有關(guān)的生存期問(wèn)題全部到該線程處理,這樣也是可行的,只是需要做一個(gè)較好的包裝,如果包裝好上層調(diào)用方便,如果包裝不好,可能上層調(diào)用就有一些約束。
             雖然各種方法都有人使用,但我一直選擇直接的生存期管理方法,其實(shí)內(nèi)部實(shí)現(xiàn)的時(shí)候還是有很多優(yōu)化措施的,減少了大量addref、release的調(diào)用,進(jìn)一步提高了效率。
             二、 線程組
             我最初做網(wǎng)絡(luò)庫(kù)的時(shí)候還不是很清楚上層如何使用這個(gè)庫(kù),后來(lái)在上面做了幾個(gè)應(yīng)用之后慢慢有了更多想法,最近的網(wǎng)絡(luò)庫(kù)是設(shè)計(jì)了這么幾組線程:io線程組、同步線程組、異步線程組、時(shí)鐘線程組、log線程組,每組線程都可開可關(guān),就算io線程組也是可關(guān)的,這只是為了整個(gè)庫(kù)更靈活適用性更廣泛,如只用同步線程組或異步線程組僅將這個(gè)線程組當(dāng)一個(gè)消息隊(duì)列使用。
             Io線程組就是處理io收發(fā)的,listen recv send 以及解密解壓縮都是在這組線程,一般這組線程會(huì)開2個(gè)或2*cpu個(gè)。
             同步線程組,一般這組線程開1個(gè),用來(lái)處理logic。
             異步線程組,這組線程根據(jù)需要開0個(gè)或n個(gè),簡(jiǎn)單應(yīng)用無(wú)db等慢速操作的應(yīng)用不開,有很多db等慢速操作的可以開很多個(gè)。
             時(shí)鐘線程組,一般不開或開1個(gè)。
             Log線程組,一般開1個(gè),主要為了避免其他線程調(diào)用WriteLog的時(shí)候被磁盤io阻塞,所以弄了一個(gè)log線程。
             其實(shí)還有一個(gè)主線程,我的每組線程(包括主線程)都支持事件和定時(shí)器,io線程、同步線程、異步線程組、時(shí)鐘線程組、甚至log線程組都支持事件和定時(shí)器,到去年我還只是讓每組線程都支持事件,今年為了更好的使用時(shí)鐘我給每組線程設(shè)計(jì)了定時(shí)器,現(xiàn)在定時(shí)器線程組有點(diǎn)雞肋的味道,一般是用不上專門的定時(shí)器線程組,不過(guò)我還沒(méi)有將它刪掉,主要在我的設(shè)計(jì)里面,它和同步異步線程組一樣,都只是一組線程,如果必要的時(shí)候可以將它用作同步線程或者異步線程組,所以繼續(xù)保留了它的存在。
             這幾組線程之間都是可互發(fā)消息的,所以一個(gè)邏輯要異步到別的線程執(zhí)行是非常方便的,只要調(diào)用一下PostXXEvent(TlsInfo *ptls, DWORD dwEvent, WPARAM wParam, LPARAM lParam);我憑借這個(gè)設(shè)計(jì)使得這套網(wǎng)絡(luò)庫(kù)幾乎可以適用上層各種應(yīng)用,不管是非常簡(jiǎn)單的網(wǎng)絡(luò)應(yīng)用還是復(fù)雜的,一框打盡。對(duì)最簡(jiǎn)單的,一個(gè)io線程搞定,其他線程全關(guān),對(duì)于復(fù)雜的io線程+同步+異步+log全開。
             三、 內(nèi)存池
             內(nèi)存池其實(shí)沒(méi)有想象中的那么神秘,當(dāng)然如果要讓一個(gè)網(wǎng)絡(luò)程序持續(xù)7*24小時(shí)穩(wěn)定高效運(yùn)行,內(nèi)存池幾乎必不可少的,內(nèi)存池的作用首先是減少內(nèi)存碎片,其次是為了提高速度,我想這兩點(diǎn)很容易想明白的,關(guān)于內(nèi)存池我之前寫了系列文章,可參考我的博客:
             
            《內(nèi)存池之引言》 http://blog.csdn.net/oldworm/archive/2010/02/04/5288985.aspx
             《單線程內(nèi)存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289003.aspx
             《多線程內(nèi)存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289006.aspx
             《dlmalloc、nedmalloc》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289010.aspx
             《線程關(guān)聯(lián)內(nèi)存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289015.aspx
             《線程關(guān)聯(lián)內(nèi)存池再提速》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289018.aspx
             
            四、 定時(shí)器
             關(guān)于定時(shí)器,上面講線程組的時(shí)候已經(jīng)講過(guò),我現(xiàn)在的設(shè)計(jì)是每個(gè)線程(包括主線程)都支持定時(shí)器,調(diào)用方法都是一樣的,回調(diào)函數(shù)形式也是一樣的,由于定時(shí)器放到各組線程里面,所以減少了線程之間的切換,提高了效率。
             關(guān)于定時(shí)器,可參考《定時(shí)器模塊改造》 http://blog.csdn.net/oldworm/archive/2010/09/11/5877425.aspx
             
            五、 包格式
             關(guān)于包格式可參考《常用cs程序自定義數(shù)據(jù)包描述》 http://blog.csdn.net/oldworm/archive/2010/03/24/5413013.aspx
             
            六、 Buffer
             之前的文章其實(shí)我一直沒(méi)有提過(guò)我的buffer,其實(shí)我的buffer設(shè)計(jì)是很靈活的,現(xiàn)在它和pool也是有些關(guān)聯(lián)的,我的poolset其實(shí)底下就是按照各種不同大小的buffer預(yù)設(shè)的尺寸。Buffer我設(shè)計(jì)為循環(huán)式,不允許回繞,包含
             Char *pbase 塊基址
             Char *pread 當(dāng)前讀指針
             Char *pwrite 當(dāng)前寫指針
             DWORD tag;
             Buffer *next;
             Capacity 總分配尺寸,上面分配的時(shí)候可能只是指定了19,但實(shí)際可能分配的是32個(gè)字節(jié),所以內(nèi)部用的時(shí)候要根據(jù)capacity來(lái)最大限度的利用緩沖區(qū)。
             Buffer分配還利用了一個(gè)技巧,事實(shí)上分配的時(shí)候是一次分配一個(gè)需要的大緩沖,前面為Buffer自身的數(shù)據(jù),后面為數(shù)據(jù)部分,pbase指向數(shù)據(jù)部分,這樣處理減少了一次分配,我估計(jì)很多人都在用這個(gè)技巧。
             Pwrite總是不會(huì)小于pread的,但pread可能和pbase不一樣,僅當(dāng)后面空余空間不夠用的時(shí)候才可能會(huì)移動(dòng)數(shù)據(jù),否則數(shù)據(jù)不會(huì)移動(dòng)。
             WSARecv的時(shí)候我是這么處理的,如果首次獲取了一個(gè)包的一部分,但buffer中還有足夠的空間放下包的剩余部分,我不會(huì)再分配一個(gè)buffer去recv,而是直接用原buffer指定一個(gè)合適的偏移和size去WSARecv,這樣可以最大限度的減少?gòu)?fù)制。
             剛才還有朋友問(wèn)到我recv的層次組織,我的網(wǎng)絡(luò)庫(kù)里面是這樣組織的,OnRecv是個(gè)虛函數(shù),最基礎(chǔ)的IocpClient的OnRecv只處理數(shù)據(jù)而不解析格式,IocpClientMsg就會(huì)認(rèn)識(shí)默認(rèn)的一種包格式,這個(gè)類的OnRecv會(huì)將m_recvbuf中的數(shù)據(jù)組織為msg,并盡可能的一次返回更多個(gè)msg,回調(diào)OnMsg函數(shù),由上層決定該消息在哪個(gè)線程處理,這樣我認(rèn)為是最靈活的,如果是個(gè)很小的server,可能直接就在io線程里面處理了,也可postevent到同步線程處理,亦可PostEvent到異步線程處理。
             
            七、 TLSINFO
             TlsInfo顧名思義就是每個(gè)線程關(guān)聯(lián)的一組數(shù)據(jù),暫時(shí)我還沒(méi)有看到別人這么設(shè)計(jì),也許我設(shè)計(jì)得有些復(fù)雜了,在這個(gè)數(shù)據(jù)里面有一些常用的和該線程相關(guān)的數(shù)據(jù),如該線程的分配基、步長(zhǎng),用這兩個(gè)參數(shù)可讓每個(gè)線程制造出唯一序列,還有常用pool的地址,如tm_pool *p1k; tm_pool *p2k;… 這樣設(shè)計(jì)使得要分配的時(shí)候直接取tm_pool,最大限度的發(fā)揮了分配速度,還有一些常規(guī)參量long c; long d; DWORD a; DWORD b;… 這幾個(gè)值可理解為棧內(nèi)值,其實(shí)為了減少上層調(diào)用復(fù)雜度的,如我將一個(gè)連接的包從io線程PostEvent到同步線程處理,PostEvent首參數(shù)就是tlsinfo,PostEvent會(huì)根據(jù)tlsinfo里面的一個(gè)內(nèi)部值決定是不是要調(diào)用addref,因?yàn)槲矣袀€(gè)地方預(yù)增了2,所以大多數(shù)情況下在io發(fā)到其他線程的時(shí)候是無(wú)需調(diào)用addref的,提高了效率,tlsinfo里的其他一些值上層應(yīng)用可使用,用在邏輯處理等情況下。
             
            八、 性能分析
             *nix下有很多知名的網(wǎng)絡(luò)庫(kù),但在win下特別是使用iocp的庫(kù)里面,一直就沒(méi)有一個(gè)能作為基準(zhǔn)的庫(kù),即使asio也因?yàn)槌鰜?lái)太晚不為大多數(shù)人熟悉而不能成為基準(zhǔn)庫(kù),libevent接iocp由于采用0 buffer模擬所以也沒(méi)有發(fā)揮出足夠的性能,對(duì)比spserver我比它快70%左右,我總在想要是微軟能將他那個(gè)iocp的例子寫得更好一點(diǎn)就好了,至少學(xué)的人有一個(gè)更高一點(diǎn)的基礎(chǔ),而不至于讓http://www.codeproject.com/KB/IP/iocp_server_client.aspx這樣的垃圾代碼都能成為很多人的樣板。
             
            九、 雜談
             為了寫好一個(gè)win下穩(wěn)定高效的網(wǎng)絡(luò)庫(kù),我07年的時(shí)候幾乎搜遍了那個(gè)時(shí)間段之前所有能找到的iocp例子,還包括通過(guò)朋友等途徑看到的如snda等網(wǎng)絡(luò)庫(kù),可惜真沒(méi)找到好的,大多數(shù)例子是只要多線程發(fā)起幾千個(gè)連接不斷發(fā)送數(shù)據(jù)馬上就死了,偶爾幾個(gè)不死的(包括snda的)只要隨機(jī)連接并斷開就會(huì)產(chǎn)生句柄泄漏,關(guān)閉所有連接之后句柄并不關(guān)閉等,也就是說(shuō)這些例子連基本的生存期管理都沒(méi)搞定,能通過(guò)生存期管理并且不死的只有有限的幾個(gè),可惜性能又太差,杯具啊。
             早年寫網(wǎng)絡(luò)庫(kù)的時(shí)候也加入了sodme在google上建的那個(gè)群,當(dāng)時(shí)群還是很熱鬧的,可惜大多數(shù)人都是摸索,所以很多問(wèn)題只是討論卻從無(wú)定論,沒(méi)有誰(shuí)能說(shuō)服別人,也沒(méi)有人可輕易被說(shuō)服,要是現(xiàn)在或許有一些很有經(jīng)驗(yàn)的人,可惜那個(gè)群由于GFW現(xiàn)在雖能訪問(wèn)也不大活躍了。
             最近看到有些寫網(wǎng)絡(luò)程序7年甚至更久的人還在用libevent、ace等感想很復(fù)雜,可悲的是那些人還沒(méi)意識(shí)到用一個(gè)庫(kù)和寫一個(gè)庫(kù)有多大的區(qū)別,可能那些人一輩子也認(rèn)識(shí)不到寫一個(gè)庫(kù)比用一個(gè)庫(kù)難多少,那些人以為這些庫(kù)基本會(huì)用了,讓他自己去寫也基本是照這個(gè)模式,不會(huì)有什么突破,就無(wú)需自己動(dòng)手了,悲哀啊。當(dāng)然,要寫一個(gè)穩(wěn)定的網(wǎng)絡(luò)庫(kù)需要耗費(fèi)很多時(shí)間,特別是要寫一個(gè)能和知名庫(kù)性能接近或更好的庫(kù),更是要費(fèi)神費(fèi)力,沒(méi)點(diǎn)耐心和持久力是不可能做好的。在中文領(lǐng)域隨便查什么稍有些名氣的代碼,總是能找到很多剖析類文章,可原創(chuàng)的東西總是很少,也不知道那些大俠怎么搞的,什么都能剖析可怎么總寫不出什么像樣的東西呢。
             其實(shí)本來(lái)沒(méi)有打算寫這篇文章,可能是看了陳碩的muduo才使得我有了寫出來(lái)的沖動(dòng),大概是受到他的開源鼓勵(lì)吧。
             謹(jǐn)以此文記錄本人最近3年對(duì)網(wǎng)絡(luò)模塊的修改并簡(jiǎn)短總結(jié)。

             

            posted @ 2010-10-03 14:25 袁斌 閱讀(3253) | 評(píng)論 (5)編輯 收藏

            我的IOCP網(wǎng)絡(luò)模塊設(shè)計(jì)

             

            為了設(shè)計(jì)一個(gè)穩(wěn)定易用高效的iocp網(wǎng)絡(luò)模塊,我前前后后花了好幾個(gè)月的時(shí)間,也曾閱讀過(guò)網(wǎng)上很多資料和代碼,但是非常遺憾,能找到的資料一般都說(shuō)得很含糊,很少有具體的,能找到的代碼離真正能商用的網(wǎng)絡(luò)模塊差得太遠(yuǎn),大多只是演示一下最基本的功能,而且大多是有很多問(wèn)題的,主要問(wèn)題如下:

            1、 很多代碼沒(méi)有處理一次僅發(fā)送成功部分?jǐn)?shù)據(jù)的情況。

            2、 幾乎沒(méi)有找到能正確管理所有資源的代碼。

            3、 大多沒(méi)有采用用pool,有的甚至畫蛇添足用什么map查找對(duì)應(yīng)客戶端,沒(méi)有充分使用perhandle, perio。

            4、 接收發(fā)送數(shù)據(jù)大多拷貝太多次數(shù)。

            5、 接收管理大多很低效,沒(méi)有充分發(fā)揮iocp能力。

            6、 幾乎都沒(méi)有涉及上層如何處理邏輯,也沒(méi)有提供相應(yīng)解決方案(如合并io線程處理或單獨(dú)邏輯線程)。

            7、 大多沒(méi)有分離流數(shù)據(jù)和包數(shù)據(jù)。

            問(wèn)題還有很多,就不一一列出來(lái)了,有一定設(shè)計(jì)經(jīng)驗(yàn)的人應(yīng)該有同感。要真正解決這些問(wèn)題也不是那么容易的,特別是在win下用iocp的時(shí)候資源釋放是個(gè)麻煩的問(wèn)題,我在資源管理上花了很多時(shí)間,起初也犯了很多錯(cuò)誤,后來(lái)在減少同步對(duì)象上又花了不少時(shí)間(起初client用了兩個(gè)同步對(duì)象,后來(lái)減少為1個(gè))。下面我就我所設(shè)計(jì)的網(wǎng)絡(luò)模塊的各個(gè)部分進(jìn)行簡(jiǎn)單的講解

            一、內(nèi)存管理。

            內(nèi)存管理是采用池模式,設(shè)計(jì)了一個(gè)基礎(chǔ)池類,可以管理某固定大小的池

            class CBufferPool

            {

                   

                    void *newobj();

                    void delobj(void *pbuf);

                   

            };

            在基礎(chǔ)池類上提供了一個(gè)模板的對(duì)象池

            template <class T>

            class CObjPool : public CBufferPool

            {

            public:

                    T *newobj()

                    {

                            void *p = CBufferPool::newobj();

                            T *pt = new(p) T;

                            return pt;

                    }

                    void delobj(T* pt)

                    {

                            pt->~T();

                            CBufferPool::delobj(pt);

                    }

            };

             

            在基礎(chǔ)池的基礎(chǔ)上定義了一個(gè)簡(jiǎn)單的通用池

            class CMemoryPool

            {

            private:

                    CBufferPool bp[N];

            };

            通用池是由N個(gè)不同大小的基礎(chǔ)池組成的,分配的時(shí)候圓整到合適的相近基礎(chǔ)池并由基礎(chǔ)池分配。

            最后還提供了一個(gè)內(nèi)存分配適配器類,從該類派生的類都支持內(nèi)存池分配。

            class t_alloc

            {

            public:

                    static void *operator new(size_t size)

                    {

                            return CMemoryPool::instance().newobj(size);

                    }

                    static void operator delete(void *p, size_t size)

                    {

                            CMemoryPool::instance().delobj(p, size);

                    }

            };

            根據(jù)測(cè)試CMempool分配速度比CObjpool<>稍微慢一點(diǎn)點(diǎn),所以我在用的時(shí)候就直接用t_alloc類派生,而不是用對(duì)象池,這是個(gè)風(fēng)格問(wèn)題,也許有很多人喜歡用更高效一點(diǎn)的objpool方式,但這個(gè)并不大礙。

             

            在網(wǎng)絡(luò)模塊中OVERLAPPED派生類就要用池進(jìn)行分配,還有CIocpClient也要用池分配,再就是CBlockBuffer也是從池分配的。

            如下定義:

            struct IOCP_ACCEPTDATA : public IOCP_RECVDATA, public t_alloc

            class CIocpClient : public t_alloc

             

            二、數(shù)據(jù)緩沖區(qū)。

            數(shù)據(jù)緩沖區(qū)CBlockBuffer為環(huán)形,大小不固定,隨便分配多少,主要有以下幾個(gè)元素:

            Char *pbase;           //環(huán)形首部

            Char *pread;           //當(dāng)前讀指針

            Char *pwrite;          //當(dāng)前寫指針

            Int nCapacity;         //緩沖區(qū)大小

            Long nRef;              //關(guān)聯(lián)計(jì)數(shù)器

            用這種形式管理緩沖區(qū)有很多好處,發(fā)送數(shù)據(jù)的時(shí)候如果只發(fā)送了部分?jǐn)?shù)據(jù)只要修改pread指針即可,不用移動(dòng)數(shù)據(jù),接收數(shù)據(jù)并處理的時(shí)候如果只處理了部分?jǐn)?shù)據(jù)也只要修改pread指針即可,有新數(shù)據(jù)到達(dá)后直接寫到pwrite并修改pwrite指針,不用多次拷貝數(shù)據(jù)。nRef關(guān)聯(lián)計(jì)數(shù)還可處理一個(gè)包發(fā)給N個(gè)人的問(wèn)題,如果要給N個(gè)人發(fā)送相同的包,只要分配一個(gè)緩沖區(qū),并設(shè)置nRefN就可以不用復(fù)制N份。

             

            三、收發(fā)緩沖區(qū)管理

            發(fā)送緩沖區(qū)

            我把CIocpClient的發(fā)送數(shù)據(jù)設(shè)計(jì)為一個(gè)CBlockBuffer 的隊(duì)列,如果隊(duì)列內(nèi)有多個(gè)則WSASend的時(shí)候一次發(fā)送多個(gè),如果只有一個(gè)則僅發(fā)送一個(gè),CIocpClient發(fā)送函數(shù)提供了兩個(gè),分別是:

            Bool SendData(char *pdata, int len);

            Bool SendData(CBlockBuffer *pbuffer);

            第一個(gè)函數(shù)會(huì)檢測(cè)發(fā)送鏈的最后一個(gè)數(shù)據(jù)塊能否容納發(fā)送數(shù)據(jù),如果能復(fù)制到最后一個(gè)塊,如果不能則分配一個(gè)CBlockBuffer掛到發(fā)送鏈最后面,當(dāng)然這個(gè)里面要處理同步。

             

            接收緩沖區(qū)

            接收管理是比較簡(jiǎn)單的,只有一個(gè)CBlockBuffer,WSARecv的時(shí)候直接指向CBlockBuffer->pwrite,所以如果塊大小合適的話基本上是不用拼包的,如果一次沒(méi)有收到一個(gè)完整的數(shù)據(jù)包,并且塊還有足夠空間容納剩余空間,那么再提交一個(gè)WSARecv讓起始緩沖指向CBlockBuffer->pwrite如此則收到一個(gè)完整數(shù)據(jù)包的過(guò)程都不用重新拼包,收到一個(gè)完整數(shù)據(jù)包之后可以調(diào)用虛函數(shù)讓上層進(jìn)行處理。

            IocpClient層其實(shí)是不支持?jǐn)?shù)據(jù)包的,在這個(gè)層次只有流的概念,這個(gè)后面會(huì)專門講解。

             

            四、IocpServer的接入部分管理

            我把IocpServer設(shè)計(jì)為可以支持打開多個(gè)監(jiān)聽端口,對(duì)每個(gè)監(jiān)聽端口接入用戶后調(diào)用IocpServer的虛函數(shù)分配客戶端:

                    virtual CIocpClient *CreateNewClient(int nServerPort)

            分配客戶端之后會(huì)調(diào)用IocpClient的函數(shù) virtual void OnInitialize();分配內(nèi)部接收和發(fā)送緩沖區(qū),這樣就可以根據(jù)來(lái)自不同監(jiān)聽端口的客戶端分配不同的緩沖區(qū)和其他資源。

             

            Accept其實(shí)是個(gè)可以有很多選擇的,最簡(jiǎn)單的做法可以用一個(gè)線程+accept,當(dāng)然這個(gè)不是高效的,也可以采用多個(gè)線程的領(lǐng)導(dǎo)者-追隨者模式+accept實(shí)現(xiàn),還可以是一個(gè)線程+WSAAccept,或者多個(gè)線程的領(lǐng)導(dǎo)者-追隨者模式+WSAAccept模式,也可以采用AcceptEx模式,我是采用AcceptEx模式做的,做法是有接入后投遞一個(gè)AcceptEx,接入后重復(fù)利用此OVERLAPPED再投遞,這樣即使管理大量連接也只有起初的幾十個(gè)連接會(huì)分配 OVERLAPPED后面的都是重復(fù)利用前面分配的結(jié)構(gòu),不會(huì)導(dǎo)致再度分配。

             

            IocpServer還提供了一個(gè)虛函數(shù)

                    virtual bool CanAccept(const char *pip, int port){return true;}

            來(lái)管理是否接入某個(gè)ip:port 的連接,如果不接入直接會(huì)關(guān)閉該連接并重復(fù)利用此前分配的WSASocket。

             

            五、資源管理

            Iocp網(wǎng)絡(luò)模塊最難的就是這個(gè)了,什么時(shí)候客戶端關(guān)閉或服務(wù)器主動(dòng)關(guān)閉某個(gè)連接并收回資源,這是最難處理的問(wèn)題,我嘗試了幾種做法,最后是采用計(jì)數(shù)器管理模式,具體做法是這樣的:

            CIocpClient2個(gè)計(jì)數(shù)變量

                    volatile long m_nSending;              //是否正發(fā)送中

                    volatile long m_nRef;                     //發(fā)送接收關(guān)聯(lián)字

            m_nSending表示是否有數(shù)據(jù)已WSASend中沒(méi)有返回

            m_nRef表示WSASendWSARecv有效調(diào)用未返回和

            在合適的位置調(diào)用

                    inline void AddRef(const char *psource);

                    inline void Release(const char *psource);

            增引用計(jì)數(shù)和釋放引用計(jì)數(shù)

                    if(InterlockedDecrement(&m_nRef)<=0)

                    {

                            //glog.print("iocpclient %p Release %s ref %d\r\n", this, psource, m_nRef);

                            m_server->DelClient(this);

                    }

            當(dāng)引用計(jì)數(shù)減少到0的時(shí)候刪除客戶端(其實(shí)是將內(nèi)存返回給內(nèi)存池)。

             

            六、鎖使用

            鎖的使用至關(guān)重要,多了效率低下,少了不能解決問(wèn)題,用多少個(gè)鎖在什么粒度上用鎖也是這個(gè)模塊的關(guān)鍵所在。

            IocpClient有一個(gè)鎖      DECLARE_SIGNEDLOCK_NAME(send);        //發(fā)送同步鎖

            這個(gè)鎖是用來(lái)控制發(fā)送數(shù)據(jù)鏈管理的,該鎖和前面提到的volatile long m_nSending;共同配合管理發(fā)送數(shù)據(jù)鏈。

            可能有人會(huì)說(shuō)recv怎么沒(méi)有鎖同步,是的,recv的確沒(méi)有鎖,recv不用鎖是為了最大限度提高效率,如果和發(fā)送共一個(gè)鎖則很多問(wèn)題可以簡(jiǎn)化,但沒(méi)有充分發(fā)揮iocp的效率。Recv接收數(shù)據(jù)后就調(diào)用OnReceive虛函數(shù)進(jìn)行處理。可以直接io線程內(nèi)部處理,也可以提交到某個(gè)隊(duì)列由獨(dú)立的邏輯線程處理。具體如何使用完全由使用者決定,底層不做任何限制。

             

            七、服務(wù)器定時(shí)器管理

            服務(wù)器定義了如下定時(shí)器函數(shù),利用系統(tǒng)提供的時(shí)鐘隊(duì)列進(jìn)行管理。

                    bool AddTimer(int uniqueid, DWORD dueTime, DWORD period, ULONG nflags=WT_EXECUTEINTIMERTHREAD);

                    bool ChangeTimer(int uniqueid, DWORD dueTime, DWORD period);

                    bool DelTimer(int uniqueid);

                    //獲取Timers數(shù)量

                    int GetTimerCount() const;

                    TimerIterator GetFirstTimerIterator();

                    TimerNode *GetNextTimer(TimerIterator &it);

                    bool IsValidTimer(TimerIterator it)

            設(shè)計(jì)思路是給每個(gè)定時(shí)器分配一個(gè)獨(dú)立的id,根據(jù)id可修改定時(shí)器的首次觸發(fā)時(shí)間和后續(xù)每次觸發(fā)時(shí)間,可根據(jù)id刪除定時(shí)器,也可遍歷定時(shí)器。定時(shí)器時(shí)間單位為毫秒。

             

            八、模塊類結(jié)構(gòu)

            模塊中最重要的就是兩個(gè)類CIocpClientCIocpServer,其他有幾個(gè)類從這兩個(gè)類派生,圖示如下:

             

            圖表 1

             

            圖表 2

             

            CIocpClient是完全流式的,沒(méi)有包概念。CIocpMsgClientCIocpClient派生,內(nèi)部支持包概念:

            class CIocpMsgClient : public CIocpClient

            {

                    virtual void OnDataTooLong(){};

                    virtual void OnMsg(PKHEAD *ph){};

             

                    bool SendMsg(WORD mtype, WORD stype, const char *pdata, int length);

            };

             

            template <class TYPE>

            class CIocpMsgClientT : public CIocpMsgClient

            {

                    void AddMsg(DWORD id, CBFN pfn);

                    BOOL DelMsg(DWORD id);

            };

            CIocpMsgClientT模板類支持內(nèi)嵌入式定義,如在

            CMyDoc中可這樣定義

            CIocpMsgClientT<CMyDoc> client;

            后面可以調(diào)用client.AddMsg(UMSG_LOGIN, OnLogin);關(guān)聯(lián)一個(gè)類成員函數(shù)作為消息處理函數(shù),使用很方便。

             

            CIocpServerT定義很簡(jiǎn)單,從CIocpServer派生,重載了CreateNewClient函數(shù)

            template <class TClient>

            class CIocpServerT : public CIocpServer

            {

            public:

                    //如果CIocpClient派生了則也需要重載下面的函數(shù),這里可以根據(jù)nServerPort分配不同的CIocpClient派生類

                    virtual CIocpClient *CreateNewClient(int nServerPort)

                    {

                            CIocpClient *pclient = new TClient;

                            return pclient;

                    }

            };

             

            八、應(yīng)用舉例

             

            class CMyClient : public CIocpMsgClient

            {

            public:

                    CMyClient() : CIocpMsgClient()

                    {

                    }

                    virtual ~CMyClient()

            {

            }

                    virtual void OnConnect()

            {

            Printf(“用戶連接%s:%d連接到服務(wù)器\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());

            }

                    virtual void OnClose()

            {

            Printf(“用戶%s:%d關(guān)閉連接\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());

            }

                    virtual void OnMsg(PKHEAD *phead)

            {

                    SendData((const char *)phead, phead->len+PKHEADLEN);

            }

                    virtual void OnSend(DWORD dwbyte)

            {

            Printf(“成功發(fā)送%d個(gè)字符\r\n”, dwbyte);

            }

                    virtual void OnInitialize()

            {

                    m_sendbuf = newbuf(1024);

                    m_recvbuf = newbuf(4096);

            }

             

                    friend class CMyServer;

             

            };

             

            class CMyServer : public CIocpServer

            {

            public:

                    CMyServer() : CIocpServer

                    {

                    }

             

                    virtual void OnConnect(CIocpClient *pclient)

            {

                            printf("%p : %d 遠(yuǎn)端用戶%s:%d連接到本服務(wù)器.\r\n", pclient, pclient->m_socket,

                                    pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());

            }

                    virtual void OnClose(CIocpClient *pclient)

            {

                    printf("%p : %d 遠(yuǎn)端用戶%s:%d退出.\r\n", pclient, pclient->m_socket,

                                    pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());

            }

                    virtual void OnTimer(int uniqueid)

            {

            If(uniqueid == 10)

            {

            }

            Else if(uniqueid == 60)

            {

            }

            }

            //這里可以根據(jù)nServerPort分配不同的CIocpClient派生類

                    virtual CIocpClient *CreateNewClient(int nServerPort)

                    {

                    //      If(nServerPort == ?)

            //             

                            CIocpClient *pclient = new CMyClient;

                            return pclient;

                    }

             

            };

             

            Int main(int argc, char *argv[])

            {

                    CMyServer server;

             

                    server.AddTimer(60, 10000, 60000);

                    server.AddTimer(10, 10000, 60000);

             

                    //第二個(gè)參數(shù)為0表示使用默認(rèn)cpu*2個(gè)io線程,>0表示使用該數(shù)目的io線程。

            //第三個(gè)參數(shù)為0表示使用默認(rèn)cpu*4個(gè)邏輯線程,如果為-1表示不使用邏輯線程,邏輯在io線程內(nèi)計(jì)算。>0則表示使用該數(shù)目的邏輯線程

                    server.StartServer("1000;2000;4000", 0, 0);

            }

             

            從示例可看出,對(duì)使用該網(wǎng)絡(luò)模塊的人來(lái)說(shuō)非常簡(jiǎn)單,只要派生兩個(gè)類,集中精力處理消息函數(shù)即可,其他內(nèi)容內(nèi)部全部包裝了。

             

            九、后記

            我研究iocp大概在2005年初,前一個(gè)版本的網(wǎng)絡(luò)模塊是用多線程+異步事件來(lái)做的,iocp網(wǎng)絡(luò)模塊基本成型在2005年中,后來(lái)又持續(xù)進(jìn)行了一些改進(jìn),2005底進(jìn)入穩(wěn)定期,2006年又做了一些大的改動(dòng),后來(lái)又持續(xù)進(jìn)行了一些小的改進(jìn),目前該模塊作為服務(wù)程序框架已經(jīng)在很多項(xiàng)目中穩(wěn)定運(yùn)行了1年半左右的時(shí)間。在此感謝大寶、Chost ChengSunway等眾多網(wǎng)友,是你們的討論給了我靈感和持續(xù)改進(jìn)的動(dòng)力,也是你們的討論給了我把這些寫出來(lái)的決心。若此文能給后來(lái)者們一點(diǎn)點(diǎn)啟示我將甚感欣慰,若有錯(cuò)誤歡迎批評(píng)指正。

             

            oldworm

            oldworm@21cn.com

            2007.9.24

            posted @ 2010-10-03 14:25 袁斌 閱讀(918) | 評(píng)論 (0)編輯 收藏

            It行業(yè)從業(yè)十幾年,雖然接觸的人并不多,但算上網(wǎng)絡(luò)上有點(diǎn)交往的人,也不算太少,閱讀過(guò)無(wú)數(shù)代碼,很容易得出這樣的感悟,國(guó)內(nèi)it行業(yè)能說(shuō)的人太多,能做的人太少,能說(shuō)大意就是能說(shuō)會(huì)道,說(shuō)起來(lái)頭頭是道,從架構(gòu)到體系到模塊到接口都能說(shuō)得很專業(yè),但實(shí)施起來(lái)就不行,不但架構(gòu)做不好,接口定義不清,就連小小模塊也未見得可搞定,這樣的人實(shí)在是太多,之前一直以為一個(gè)項(xiàng)目組多幾個(gè)人總能加快點(diǎn)速度,現(xiàn)在終于明白,一個(gè)項(xiàng)目組加幾個(gè)不合適的人不但不能加速反而要降速,甚至直接導(dǎo)致項(xiàng)目開發(fā)失控、失敗。關(guān)鍵模塊如果讓一個(gè)不稱職的人去負(fù)責(zé),最終該模塊可能需要耗費(fèi)核心人員更多時(shí)間去修改,甚至要重寫,輕則導(dǎo)致項(xiàng)目延時(shí),重則導(dǎo)致項(xiàng)目失敗。

            實(shí)施一個(gè)成功的it項(xiàng)目(只說(shuō)技術(shù)不說(shuō)市場(chǎng)),概括起來(lái)就是一句話:找合適的人做合適的項(xiàng)目。說(shuō)起來(lái)容易做起來(lái)難啊,每個(gè)人都有他的領(lǐng)域,如果找來(lái)一個(gè)擅長(zhǎng)a領(lǐng)域的讓他做b領(lǐng)域的項(xiàng)目也未必做得好,雖然有的人學(xué)習(xí)能力超強(qiáng),但總歸是需要時(shí)間積累經(jīng)驗(yàn)的。見過(guò)聽說(shuō)過(guò)很多開發(fā)失敗的例子,莫不如此,曾幫人家優(yōu)化一個(gè)電信計(jì)費(fèi)項(xiàng)目,原實(shí)施的人只會(huì)用數(shù)據(jù)庫(kù),所有的計(jì)算全用數(shù)據(jù)庫(kù)實(shí)現(xiàn),速度比其對(duì)手慢一個(gè)數(shù)量級(jí)以上,將計(jì)算需要的數(shù)據(jù)預(yù)裝入內(nèi)存,之后全在內(nèi)存里面查找計(jì)算,速度一下提高了上百倍,修改后速度領(lǐng)先其對(duì)手好幾倍,其實(shí)這個(gè)修改很容易,只要幾天時(shí)間就搞定了,還包括熟悉他們的數(shù)據(jù)及規(guī)則。 還幫別人看過(guò)一個(gè)棋牌的項(xiàng)目,原項(xiàng)目組十幾個(gè)人搞了1年多,整了40多萬(wàn)行代碼,結(jié)果bug不斷,一直不能穩(wěn)定運(yùn)行,項(xiàng)目組無(wú)人能搞得定,我看了之后下的結(jié)論是重寫,他們傻眼了,還以為改一個(gè)項(xiàng)目的時(shí)間肯定要少,畢竟寫了那么多代碼,他們那些外行哪里知道,修改一個(gè)漏洞無(wú)數(shù)的工程哪有重寫快啊,這是典型的找了一群不恰當(dāng)?shù)娜俗隽艘粋€(gè)不恰當(dāng)?shù)捻?xiàng)目,幾百萬(wàn)投入打了水漂,要是讓一個(gè)有能力的人設(shè)計(jì)把關(guān),他那棋牌項(xiàng)目100萬(wàn)足夠開發(fā)得出來(lái)了。

            剛畢業(yè)那會(huì)做項(xiàng)目的時(shí)候帶過(guò)幾個(gè)水平較差的手下(都是俺領(lǐng)導(dǎo)招的人),他們最常說(shuō)的話是“優(yōu)化”,我把某某地方“優(yōu)化”了一下,呵呵,外行要聽到這個(gè)還以為真的是優(yōu)化呢,其實(shí)很多時(shí)候他只是改寫了一下,是不是算優(yōu)化還值得商量,大多數(shù)時(shí)候都算不上優(yōu)化,有的時(shí)候還改錯(cuò)引入了更多bug。對(duì)高手來(lái)說(shuō)更喜歡說(shuō)重構(gòu),我把某某模塊重新設(shè)計(jì)了一下,以前模塊有哪些缺陷,重新設(shè)計(jì)一下之后這些缺陷就不存在了,還有某某好處,等等,高手著眼于大局,低手只能看到一個(gè)小角落,高手優(yōu)化關(guān)鍵之處,低手優(yōu)化無(wú)關(guān)緊要之處??炊拱昃W(wǎng)技術(shù)發(fā)展,就是一個(gè)不斷重構(gòu)的過(guò)程,看qq98年開始到現(xiàn)在的蜂鳥內(nèi)核,大的重構(gòu)就3次了,完全重寫3次了。最近迅雷終于官方承認(rèn)v6開發(fā)失敗,我之前在群里表達(dá)過(guò)我的觀點(diǎn),我估計(jì)他們v6是開發(fā)失敗了,所以一直以v5頂著,現(xiàn)在等到v7出來(lái)終于承認(rèn)了,這種自詡市值超100億美刀的公司這么長(zhǎng)時(shí)間的開發(fā)還能失敗,就不要說(shuō)小公司小團(tuán)隊(duì)了,見光死的項(xiàng)目很多,未見光死的項(xiàng)目更多。

            國(guó)內(nèi)真正懂得開發(fā)的老板很少,大多數(shù)老板覺(jué)得一個(gè)人開5w 10w的月薪太高了,這個(gè)待遇找1w的能找好幾個(gè)呢,其實(shí)他們不知道,it行業(yè)一個(gè)重要的人能頂100個(gè)普通的人,甚至不可比較,因?yàn)橐粋€(gè)普通的人去做一個(gè)項(xiàng)目可能根本不能完成,成功為0,10這個(gè)比值是無(wú)窮大啊,可惜等老板們失敗了幾個(gè)項(xiàng)目之后才能悟出這個(gè)道理,it項(xiàng)目做得好和做不好的差別不是差一點(diǎn)的問(wèn)題,而是10的問(wèn)題。俗人總是忙忙碌碌,天天維護(hù)自己前一天制造的bug,看起來(lái)很敬業(yè),高手總是懶懶散散,因?yàn)?/span>100天后可能出現(xiàn)的問(wèn)題都已經(jīng)了然于胸,于是整天看起來(lái)無(wú)所事事時(shí),不懂的老板很可能青睞于前者而打壓后者。也正因?yàn)槟苷f(shuō)的人太多,懂行的老板又太少,所以使得整個(gè)行業(yè)充斥著浮躁和急功近利,很多關(guān)鍵職位其實(shí)只是個(gè)鸚鵡在頂著,有能力的人被壓制,悲哀啊。千里馬難找,伯樂(lè)更難找啊。

            posted @ 2010-10-03 14:24 袁斌 閱讀(852) | 評(píng)論 (2)編輯 收藏

            僅列出標(biāo)題  下一頁(yè)
            蜜臀久久99精品久久久久久| 美女写真久久影院| 国产午夜福利精品久久2021| 色综合久久中文综合网| 亚洲国产成人久久精品99| 72种姿势欧美久久久久大黄蕉| 免费观看久久精彩视频| 一级女性全黄久久生活片免费| 色婷婷综合久久久中文字幕| 无码八A片人妻少妇久久| 国产精品美女久久久久久2018| 久久久久久国产a免费观看不卡| 亚洲精品美女久久久久99| 国产激情久久久久影院老熟女免费 | 国产精品无码久久综合| 久久久久久久国产免费看| 久久精品国产亚洲av麻豆小说| 久久久精品国产亚洲成人满18免费网站 | 婷婷综合久久狠狠色99h| 国产精品久久久久久久app| 久久久青草青青亚洲国产免观| 久久香综合精品久久伊人| 国产2021久久精品| 亚洲av成人无码久久精品| 日本加勒比久久精品| 久久精品国产第一区二区三区| 日本久久中文字幕| 日本一区精品久久久久影院| 日韩av无码久久精品免费| 四虎亚洲国产成人久久精品| 欧美久久综合性欧美| 青青草原精品99久久精品66| 久久99热这里只有精品66| 狠狠精品干练久久久无码中文字幕 | 久久99精品久久只有精品| 久久毛片一区二区| 久久久久亚洲AV综合波多野结衣| 色综合久久精品中文字幕首页| 亚洲国产精品无码久久98| 久久综合久久美利坚合众国| 日韩久久久久中文字幕人妻|