<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Kevin.Zhong

    彪悍的人生不需要解釋,彪悍的代碼不需要測(cè)試。

      BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      17 隨筆 :: 12 文章 :: 14 評(píng)論 :: 0 Trackbacks
    1.關(guān)于本文檔
           本文檔所有的分析都是在1.2版本之上,偶爾會(huì)提到比較1.1版本.其他版本沒(méi)有閱讀.
            一個(gè)星期時(shí)間的工作,不可能對(duì)memcache有很深刻的分析.文檔本身的目的在于為以后的研究準(zhǔn)備一個(gè)總結(jié)資料.剛接觸memcache時(shí),對(duì)其設(shè)計(jì)分 布式的思路感到十分欣喜,因?yàn)樵谥虚g層以極小的代價(jià)實(shí)現(xiàn)簡(jiǎn)單分布式無(wú)疑成為一些要求不是很高的分布式應(yīng)用的一個(gè)很好的設(shè)計(jì)思路,這個(gè)特性決定 memcache本身在分布式應(yīng)用中,單個(gè)結(jié)點(diǎn)之間是Server相互獨(dú)立,不會(huì)存在同級(jí)之間的通信.一個(gè)結(jié)點(diǎn)拒絕訪問(wèn),如果沒(méi)有相應(yīng)的冗余策略,將導(dǎo)致 該結(jié)點(diǎn)的數(shù)據(jù)丟失.同時(shí),memcache的Server結(jié)點(diǎn)對(duì)數(shù)據(jù)的存儲(chǔ)操作都是在內(nèi)存中完成,而memcache對(duì)內(nèi)存分配和回收采用了曾在 SunOS實(shí)現(xiàn)的分頁(yè)機(jī)制,預(yù)分配一個(gè)大內(nèi)存(默認(rèn)是 <= 200M),然后分頁(yè)切塊,對(duì)每個(gè)數(shù)據(jù)對(duì)象的存儲(chǔ)便在所切的塊中進(jìn)行操作.這個(gè)特點(diǎn)決定memcache沒(méi)有設(shè)計(jì)到任何磁盤(pán)IO操作,那么所有的關(guān)于 memcache的性能瓶頸都在網(wǎng)絡(luò)通信部分,而memcache正是將這一部分拋給了一個(gè)中間層完成.可以說(shuō)真正的memcache是一個(gè)單進(jìn)程,單線 程,監(jiān)聽(tīng)某個(gè)網(wǎng)絡(luò)端口的daemon(或非daemon),是一個(gè)輕量級(jí)的應(yīng)用服務(wù)進(jìn)程.這些特性決定了memcache的應(yīng)用范圍,性能瓶頸和優(yōu)化策 略.
           本文檔的目的也就詣在探討查看memcache源碼后的一些觀點(diǎn).
           文檔分為六個(gè)部分:
           1.  文檔介紹.主要介紹文檔組織和文檔目的.
           2.  memcache的代碼分析.分析memcache的源代碼,指出核心的數(shù)據(jù)結(jié)構(gòu)和算法.
           3.  memcache的使用優(yōu)化.分析memcache的特點(diǎn),結(jié)合實(shí)際應(yīng)用給出一些優(yōu)化memcache的策略.
           4.  memcache的測(cè)試分析.初略測(cè)試了memcache,給出優(yōu)化方案的例證.
           5.  memcache的中間層客戶端編寫(xiě).分析memcache的通信協(xié)議,模擬編寫(xiě)了一個(gè)簡(jiǎn)單的memcache中間層客戶端.
           6.  libevent簡(jiǎn)介.memcache采用的是libevent進(jìn)行網(wǎng)絡(luò)IO處理,libevent作為一種新的網(wǎng)絡(luò)IO方式以高效的方法(epoll/kqueue)組織IO.
           其中第六章可以不看.對(duì)于系統(tǒng)管理員,需要查看第一,三,四部分;進(jìn)行二次開(kāi)發(fā)的程序員可以查看第一,二,四,五,六部分.

    2.memcache代碼分析
           1. memcache main流程
    點(diǎn)擊在新窗口中瀏覽此圖片
    圖2.1 memcache main流程
           libevent的事件處理機(jī)制在main進(jìn)程里體現(xiàn)在處理網(wǎng)絡(luò)IO上.在TCP memcache的服務(wù)流程里,也是利用event處理事件的.
           2. memcache服務(wù)流程(TCP)
    點(diǎn)擊在新窗口中瀏覽此圖片
    圖2.2 memcache服務(wù)流程圖(TCP)
           3. memcache狀態(tài)轉(zhuǎn)換和通信協(xié)議處理
    點(diǎn)擊在新窗口中瀏覽此圖片  
    圖2.3 memcache狀態(tài)轉(zhuǎn)換和通信協(xié)議處理
           需要說(shuō)明的是,這里需要排除所有出錯(cuò)處理.很顯然,不管是哪種操作下,一旦出錯(cuò),信息需要通過(guò)conn_write狀態(tài)往client寫(xiě)入出錯(cuò)信息的,那么在string_out時(shí),必定轉(zhuǎn)入conn_write狀態(tài).
           而且,很多細(xì)節(jié)也沒(méi)有在流程圖中給出,如統(tǒng)計(jì)信息的處理,超時(shí)后get操作時(shí)刪除等等.對(duì)于在memcache協(xié)議中定義的其他操作,如 stats,version,quit,flush_all,own,disown等等由于使用很少,在流程中沒(méi)有詳細(xì)給出,可以查看源代碼.
           4.  memcache核心數(shù)據(jù)結(jié)構(gòu)
                   1. item結(jié)構(gòu)
           item是存儲(chǔ)在memcache的key-value對(duì)的抽象.由于組織item存放是按照LRU算法組織的.那么在其中有幾個(gè)成員在修改源代碼時(shí)必 須注意,time是最近訪問(wèn)時(shí)間.exptime是item消亡時(shí)間.item是一個(gè)雙向列表.同時(shí)還掛在一個(gè)Hash table上.
                   2. conn結(jié)構(gòu)
           conn結(jié)構(gòu)是聯(lián)系上下文的關(guān)鍵對(duì)象.對(duì)于每個(gè)連接的到來(lái),都有一個(gè)conn結(jié)構(gòu)與其對(duì)應(yīng),并且對(duì)應(yīng)到某個(gè)連接狀態(tài),進(jìn)入狀態(tài)轉(zhuǎn)換而完成操作.
           conn在程序開(kāi)始也進(jìn)行了一次預(yù)分配,分配200個(gè)連接空間.當(dāng)200個(gè)使用完之后便是按需分配,到達(dá)一個(gè)分配一個(gè).
           conn和item,iovec(內(nèi)核級(jí)別緩沖結(jié)構(gòu))關(guān)聯(lián).
                   3. slabclass_t結(jié)構(gòu)
           slabclass_t保存了分級(jí)大小的空間槽,以分別適用于不同大小的item存放.取決于兩個(gè)命令行參數(shù),-f和-n.在應(yīng)用 slabclass_t時(shí),定義的是一個(gè)數(shù)組,該數(shù)組長(zhǎng)度取決于增長(zhǎng)的指數(shù)級(jí)別和初始值大小(32+chunk_size),每個(gè)空間槽是不允許大于1M 的,也就是1048576.
                   4. settings結(jié)構(gòu)
            系統(tǒng)獲取的命令行參數(shù)保存的地方.
                   5. stats結(jié)構(gòu):
           統(tǒng)計(jì)信息保存地方,可以考慮對(duì)其進(jìn)行操作以適應(yīng)不同的統(tǒng)計(jì)信息處理,如獲取某個(gè)時(shí)間段的get命中率等等操作.
           5.  memcache核心算法
           事件觸發(fā)處理連接,LRU算法掛載item,加鎖并事后處理刪除操作,預(yù)分配空間和按需請(qǐng)求不夠空間策略獲取存儲(chǔ)空間,freelist方式管理空閑空間.名為new_hash的hash算法計(jì)算key的hash值(http://burtleburtle.net/bo...

    3.memcache使用優(yōu)化
           在優(yōu)化memcache的工作之前,需要了解memcache體系的工作流程.一個(gè)分布式的memcache的運(yùn)作是需要三個(gè)部分的,多臺(tái)提供 memcache服務(wù)的servers(簡(jiǎn)稱S),一個(gè)進(jìn)行分布式映射的中間層lib(其實(shí)這個(gè)也可以當(dāng)作客戶端的一部分,簡(jiǎn)稱L),和進(jìn)行 memcache請(qǐng)求的客戶端(簡(jiǎn)稱C).
           在memcache工作時(shí),有以下四個(gè)步驟:
           1.  C通過(guò)帶有特性化的Key值的命令串,向L請(qǐng)求memcache服務(wù),L將命令串進(jìn)行分解,并通過(guò)對(duì)key的某種Hash算法決定S的地址
           2.  L將分解的(Comm Key-Value)或者(Comm Key)向相關(guān)的S請(qǐng)求memcache服務(wù).
           3.  相關(guān)的S根據(jù)memcache協(xié)議向L返回服務(wù)結(jié)果.
           4.  L將結(jié)果進(jìn)行聚集包裝后返回給C一個(gè)人性化的響應(yīng).
           這個(gè)流程可以用圖3.1進(jìn)行描述.
    點(diǎn)擊在新窗口中瀏覽此圖片
    圖3.1 memcache工作步驟
           在每個(gè)S端,請(qǐng)求處理的Key-Value對(duì)當(dāng)作一個(gè)對(duì)象(不過(guò)這個(gè)對(duì)象的結(jié)構(gòu)是單一的),再進(jìn)行另一步hash之后,存儲(chǔ)在內(nèi)存里.存儲(chǔ)算法在第二部分有詳細(xì)的描述.這種通過(guò)雙層hash來(lái)分開(kāi)處理分布式和緩存兩個(gè)功能的方法值得學(xué)習(xí).
           從上面的分析可以看出,分布式的memcache服務(wù)是需要很大的網(wǎng)絡(luò)開(kāi)銷的.對(duì)于一般的應(yīng)用而言,是否都要進(jìn)行memcache的優(yōu)化,甚至是否需要 用到memcache,都需要進(jìn)行權(quán)衡分析.如果只是本地小規(guī)模的應(yīng)用,在數(shù)據(jù)庫(kù)可承受的范圍內(nèi),是寧愿采用數(shù)據(jù)庫(kù)+文件緩存的方式.1.1版本的 memcache走TCP模式在本地應(yīng)用的處理速度甚至比不上Mysql數(shù)據(jù)的Unix域套接口的處理速度的一半,當(dāng)然會(huì)更加比不上在程序內(nèi)部直接操作內(nèi) 存了.雖然1.2版本的memcache已經(jīng)提供了-s參數(shù)指定Unix域套口和-u指定udp模式.而且如果不需要用到分布式的話,不推薦使用 memcache,除非你的內(nèi)存足夠大到浪費(fèi)的程度.
           因此,在進(jìn)行優(yōu)化之前,需要看看是否真的需要優(yōu)化memcache,是否真正需要用到memcache.
           優(yōu)化可以從以下幾個(gè)方面進(jìn)行:
           1.  命中率.
           對(duì)于緩存服務(wù)而言,命中率是至關(guān)重要的.命中率的提升可以通過(guò)多種方案實(shí)現(xiàn).其一,提高服務(wù)獲取的內(nèi)存總量.這無(wú)疑是增加命中的最直接的辦法,將緩存數(shù) 據(jù)完全放入數(shù)據(jù)池中.只要連接不失效,就一定命中.其二,提高空間利用率,這實(shí)際上也是另一種方式的增加內(nèi)存總量.具體實(shí)現(xiàn)在下一個(gè)方面給出.其三,對(duì)于 一些很特別的memcache應(yīng)用,可以采用多個(gè)memcache服務(wù)進(jìn)行偵聽(tīng),分開(kāi)處理,針對(duì)服務(wù)提供的頻繁度劃分服務(wù)內(nèi)存,相當(dāng)于在應(yīng)用一級(jí)別上再來(lái) 一次LRU.其四,對(duì)于整體命中率,可以采取有效的冗余策略,減少分布式服務(wù)時(shí)某個(gè)server發(fā)生服務(wù)抖動(dòng)的情況.如,14臺(tái)機(jī)器實(shí)現(xiàn)分布式 memcache,劃分兩組服務(wù),其中一組13臺(tái)做一個(gè)分布式的memcache,一組1臺(tái)做整個(gè)的memcache備份.對(duì)于update操作,需要進(jìn) 行兩邊,get操作只需要一遍,一旦訪問(wèn)失效,則訪問(wèn)備份服務(wù)器.這樣,對(duì)于備份服務(wù)器需要內(nèi)存比較大,而且只適應(yīng)于讀操作大于寫(xiě)操作的應(yīng)用中.這可以認(rèn) 為是RAID3,當(dāng)然,也可以采用RAID1完全鏡像.
           2.  空間利用率.
           對(duì)于使用memcache做定長(zhǎng)數(shù)據(jù)緩存服務(wù)而言,是可以在空間利用率上進(jìn)行優(yōu)化.甚至最簡(jiǎn)單的辦法可以不用更改memcache的源碼遍可以完成由 -f和-n參數(shù)的配合可以做到定長(zhǎng)優(yōu)化,不過(guò)極可能需要浪費(fèi)掉預(yù)分配的199M內(nèi)存空間.當(dāng)然前提是memcache的版本是1.2,同時(shí)如果使用的是 1.2.0和1.2.1的話,需要更改掉一個(gè)BUG,那就是getopt時(shí)將opt串中最后一個(gè)”s”改成”n”,希望memcache能在以后的版本發(fā) 現(xiàn)這個(gè)BUG.例如,如果key是一個(gè)定長(zhǎng)id(如一個(gè)8位的流水號(hào)00000001),value是一個(gè)定長(zhǎng)的串(如16位的任意字符串),對(duì)應(yīng)于一個(gè) chunk_size可以這么計(jì)算:chunk_size = sizeof(item) + nkey + *nsuffix + nbytes = 32 + 9 + (flag的數(shù)位長(zhǎng)度 )2+ (16)的數(shù)位長(zhǎng)度) 2+(兩換行的長(zhǎng)度)4 + 17 = 40 + 10 + 16 = 66,那么可以通過(guò) -f 1.000001 -n  `expr 66 - 32`,即 -f 1.000001 -n 34 來(lái)啟動(dòng)memcache.這種情況下,會(huì)浪費(fèi)掉memcache預(yù)先分配的200M空間中的199M.從第2個(gè)預(yù)分配等級(jí)到第200個(gè)預(yù)分配等級(jí)將不會(huì)用 到.然而,存在解決辦法,那就是在編譯memcache是加入編譯參數(shù)-DDONT_PREALLOC_SLABS,或者在源代碼中加入#define DONT_PREALLOC_SLABS即可,只是會(huì)去除memcache的預(yù)分配內(nèi)存機(jī)制.
           如果需要memcache的預(yù)分配內(nèi)存機(jī)制,那么需要對(duì)其源代碼進(jìn)行修改.修改如下:  
    引用
       1. 在slabs.c中,將函數(shù)slabs_clsid改成:
         unsigned int slabs_clsid(size_t size)
         {   unsigned int res = POWER_SMALLEST;
           if(size==0)
                     return 0;
           res = (size)%power_largest;
           return res;
         }
       2. 在item.c中,將函數(shù) item_make_header改為:
         int item_make_header(char *key, uint8_t nkey, int flags, int nbytes,
                        char *suffix, int *nsuffix)
         {
               *nsuffix = sprintf(suffix, " %u %u"r"n", flags, nbytes - 2);
               return sizeof(item)+ nkey + *nsuffix + nbytes + hash(key,nkey,0);
         }
       3. 在item.c中,將函數(shù) item_free改為:
         void item_free(item *it)
         {  unsigned int ntotal = it->slabs_clsid;
               assert((it->it_flags & ITEM_LINKED) == 0);
               assert(it != heads[it->slabs_clsid]);
               assert(it != tails[it->slabs_clsid]);
               assert(it->refcount == 0);

               it->slabs_clsid = 0;
               it->it_flags |= ITEM_SLABBED;
               slabs_free(it, ntotal);
         }

           做一個(gè)輪流存儲(chǔ)的機(jī)制使用預(yù)分配的內(nèi)存,這樣的好處是其他地方不需要做任何修改就可以了,當(dāng)然你可以在源代碼中加入上面的代碼,并將它們放在一個(gè)自定義的宏后面.
           3.  加速比.
           加速比,也即事件的處理效率.是否可以修改libevent的事件處理效率,需要研究.如果內(nèi)存空間很大,可以將freeconn的數(shù)值調(diào)大,增加預(yù)分配的conn內(nèi)存大小.
           是否可以將memcache做成多線程處理,但在處理多線程數(shù)據(jù)同步是個(gè)問(wèn)題.
            如果有時(shí)間,愿意來(lái)試試這個(gè)策略.
           4.  安全性能.
           memcache還存在一個(gè)比較顯著的問(wèn)題,那就是其安全性能.只要了解memcache監(jiān)聽(tīng)的端口,對(duì)于能夠使用分布式memcache進(jìn)行數(shù)據(jù)通信 的網(wǎng)絡(luò)環(huán)境的機(jī)器,都可以通過(guò)memcache協(xié)議于memcache服務(wù)器進(jìn)行通信,獲取或種植數(shù)據(jù).不能保證種植進(jìn)內(nèi)存里的數(shù)據(jù)不會(huì)被別有心意的人再 利用.也不能保證服務(wù)器的內(nèi)存不被漫天遍地的垃圾數(shù)據(jù)所堆積,造成命中極低.
           memcache的設(shè)計(jì)理念在一個(gè)輕字,如果對(duì)每次Client的通訊需要校驗(yàn)身份,那么恐怕memcache也就達(dá)不到其想要的效果了.存在解決辦法 緩解這個(gè)問(wèn)題,一般而言,需要使用memcache服務(wù)的機(jī)器,可以在Server維持一張紅色列表.這張表上的機(jī)器便可以獲取服務(wù).很顯 然,memcache并非任意Client都能訪問(wèn),只有信任的機(jī)器訪問(wèn),那么為什么不將這些信任的機(jī)器放在一個(gè)/etc/mem_passwd下呢.
           還有,memcached走udp時(shí),很大幾率接受到upd時(shí),都會(huì)使服務(wù)死掉,特別是set,add,replace時(shí),這個(gè)問(wèn)題需要去考究一下.不過(guò)沒(méi)有時(shí)間了.

    4.memcache測(cè)試分析
           服務(wù)器端memcache在命令行運(yùn)行的參數(shù):
    引用
    # memcached –d –m 512 –l *.*.*.* -u ** -f 1.00001 –n 16 –c 10000 -vv

           1.  讀寫(xiě)memcache指令測(cè)試
           在利用了memcache官方推薦的c客戶端libmemcache和自己編寫(xiě)的一個(gè)簡(jiǎn)單客戶端測(cè)試之后,在set/get/add/del指令的運(yùn)行速度如表4.1和圖4.1所示:
    點(diǎn)擊在新窗口中瀏覽此圖片
    表4.1 memcache的指令運(yùn)行速度
    圖4.1 memcache的指令運(yùn)行速度
           2.  并發(fā)連接
           由于在memcache服務(wù)器端,一個(gè)結(jié)點(diǎn)運(yùn)行的是單進(jìn)程單線程的daemon(或非daemon)服務(wù),同時(shí)對(duì)于采用了libevent處理網(wǎng)絡(luò)IO 而言,其并發(fā)連接的數(shù)目是和libevent采用的機(jī)制相關(guān)連的.很顯然,accept函數(shù)在接收到connection后將Client的socket 放進(jìn)event庫(kù)中,等待處理.而libevent庫(kù)在LINUX中使用的是epoll,監(jiān)聽(tīng)EPOLLIT水平觸發(fā).因此從理論上講,memcache 的并發(fā)連接可以達(dá)到infinite,前提是event池和內(nèi)存空間足夠大.而沒(méi)有和linux的線程處理有關(guān)系.事實(shí)上,在后面的測(cè)試中便可發(fā)現(xiàn),在單 結(jié)點(diǎn)連接壓力測(cè)試時(shí),瞬時(shí)并發(fā)連接可以達(dá)到5000多個(gè).只是等待觸發(fā)時(shí)間上的長(zhǎng)短和有效無(wú)效的區(qū)別.
           在表4.2中可以清晰的看到并發(fā)連接的一些數(shù)據(jù).
           3.  服務(wù)端系統(tǒng)負(fù)載
           通過(guò)自己編寫(xiě)的服務(wù)器端,對(duì)單結(jié)點(diǎn)的memcache進(jìn)行了連接壓力測(cè)試.其中測(cè)試用例的編寫(xiě)是這樣的:啟用七個(gè)客戶端,每個(gè)客戶端串行運(yùn)行1000個(gè) 進(jìn)程,每個(gè)進(jìn)程開(kāi)3000線程,每個(gè)線程執(zhí)行10次memcache的讀操作或者寫(xiě)操作(操作相同).客戶端并發(fā)連接.
           1.  客戶端(7)的環(huán)境:Intel(R) Xeon(R) CPU 5120 @ 1.86GHz,4G memory.
           2.  服務(wù)器端(1)的環(huán)境:Intel(R) Xeon(R) CPU 5120 @ 1.86GHz,4G memory.
           3.  網(wǎng)絡(luò)環(huán)境:100M網(wǎng)卡,Cisco交換機(jī).
           4.  數(shù)據(jù)記錄:見(jiàn)表4.2和圖4.2.
    點(diǎn)擊在新窗口中瀏覽此圖片
    表4.2 memcache連接和系統(tǒng)負(fù)載
    圖4.2 memcache連接和系統(tǒng)負(fù)載
           很顯然,memcache的運(yùn)行在系統(tǒng)cpu的消耗上占十分少的比重,即便是很恐怖的并發(fā)連接也不會(huì)給系統(tǒng)帶來(lái)多大的負(fù)載,因?yàn)槠浯疟P(pán)IO free(所有操作都在內(nèi)存中)和相應(yīng)的內(nèi)存分配機(jī)制決定其占用cpu的極少,而相反,在網(wǎng)絡(luò)IO上卻花費(fèi)很大的時(shí)間.
           4.  空間分配,命中率
           由于本地測(cè)試式的get數(shù)據(jù)非常固定,因此命中率基本為100%.在10.68.1.31上運(yùn)行了一個(gè)有前端應(yīng)用的memcachce服務(wù)器,運(yùn)行時(shí)間已經(jīng)有364個(gè)多小時(shí)了.
           因此通過(guò)10.68.1.31上的數(shù)據(jù)說(shuō)明(版本為1.1.13).通過(guò)memcache的統(tǒng)計(jì)協(xié)議可以清楚的看到其命中率高達(dá)95.9%,如表4.3所示:
    點(diǎn)擊在新窗口中瀏覽此圖片
    表4.3 memcache空間分配和命中

    5.memcache客戶端編寫(xiě)
           1.  memcache協(xié)議
           在memcache協(xié)議中規(guī)定了Client和Server的通信規(guī)則.
           在源碼分析中,主要分析了update/get/del/incr/decr幾類的處理過(guò)程.其具體的規(guī)則可以在官方文檔中有說(shuō)明(),這里做簡(jiǎn)單的解釋.
    引用
    1.  Update(set/add/replace):
    Client請(qǐng)求規(guī)則:
    "r"n
    "r"n
    Server響應(yīng)規(guī)則:
    STORED"r"n 或者 NOT_STORED"r"n

    其中,是set,add,replace三種中的一種;
    是client請(qǐng)求存儲(chǔ)的鍵值;
    是任意16bit長(zhǎng)的unsigned int值,在get操作時(shí),也將伴隨data一起返回,可以用來(lái)存儲(chǔ)某些認(rèn)證信息或者描述信息;
    是key-value對(duì)象的消亡時(shí)間,如果為0,則代表永不消亡;
    是數(shù)據(jù)的長(zhǎng)度,千萬(wàn)小心,這個(gè)很重要,在memcache源代碼里,直接讀取這個(gè)數(shù)值來(lái)當(dāng)作數(shù)據(jù)的長(zhǎng)度,而不是用strlen計(jì)算的.這個(gè)顯而易見(jiàn),因?yàn)閿?shù)據(jù)中有可能存在/r/n符號(hào),也就是協(xié)議中規(guī)定的分隔符.如果出現(xiàn),則嚴(yán)格按長(zhǎng)度取數(shù)據(jù);
    也就是value值,可以包含"r"n值.

    STORED代表update操作成功,NOT_STORED代表update操作失敗.

    2.  Get(get/bget)
    Client請(qǐng)求規(guī)則:
    *"r"n
    Server響應(yīng)規(guī)則:
    VALUE "r"n
    "r"n
    END"r"n

    Get/bget操作可以一次操作多個(gè)key值,server的響應(yīng)格式中的關(guān)鍵字可以參看上面的解釋,END代表數(shù)據(jù)顯示結(jié)束.如果沒(méi)有數(shù)據(jù),則只有一個(gè)END"r"n.

    3.  Delete(delete)
    Client請(qǐng)求規(guī)則:
    delete

           2.  針對(duì)協(xié)議的一個(gè)簡(jiǎn)單實(shí)現(xiàn)
           在這個(gè)例子中簡(jiǎn)單實(shí)現(xiàn)了一個(gè)能進(jìn)行update/get/delete操作測(cè)試用例,只是簡(jiǎn)單socket的應(yīng)用而已.如果可以,模仿這個(gè)寫(xiě)一個(gè)簡(jiǎn)單的客戶端應(yīng)該難度不大.
    引用
    /****************************************"
    *            mem_benchmark_conn2.c
    *
    *  Mon Mar 7 10:52:30 2007
    *  Copyright  2007  Spark Zheng
    *  Mail
    *  v0.1 Mar 5 2007 file:mem_benchmark_conn.c
    "***************************************
    */

    #include 
    < stdio.h>
    #include 
    < stdlib.h>
    #include 
    < string.h>
    #include 
    < ctype.h>

    #include 
    < unistd.h>
    #include 
    < pthread.h>
    #include 
    < time.h>
    #include 
    < sys/types.h>
    #include 
    < sys/time.h>
    #include 
    < sys/resource.h>
    //#include < sys/socket.h>
    //#include < netdb.h>
    //#include < arpa/inet.h>

    #ifndef MEM_SERVER
    #define MEM_SERVER "10.210.71.25"
    #endif

    #ifndef MEM_PORT
    #define MEM_PORT 11211
    #endif

    void p_usage(void);
    void *conn_mem(void);
    int NonbSocket(const char *server, int port);
    int mem_set(int sock,const char *key,const char *value);
    int mem_add(int sock,const char *key,const char *value);
    int mem_get(int sock,const char *key,char *value);
    int mem_del(int sock,const char *key);

    int main(int argc,char **argv)
    {
           
    int conn=0;
           
    int i=0;
           pthread_t ptid[
    10000];
           
    struct rlimit rlim;
           
    struct timeval tv1,tv2;

           
    if(argc < 2)
           {
                   p_usage();
                   exit(
    255);
           }

           conn 
    = atoi(argv[1]);

           
    if(getrlimit(RLIMIT_NOFILE,&rlim) != 0)
           {
                   fprintf(stderr,
    "getrlimit error in line %d"n",__LINE__);
                   exit(254);
           }

           
    if((conn > rlim.rlim_cur) && (2*conn > 1024))
           {
                   rlim.rlim_cur 
    = 2*conn;
           }
           
    if(rlim.rlim_cur > rlim.rlim_max)
           {
                   rlim.rlim_max 
    = rlim.rlim_cur;
           }

           
    if(setrlimit(RLIMIT_NOFILE,&rlim) != 0)
           {
                   fprintf(stderr,
    "setrlimit error in line %d"n",__LINE__);
                   exit(254);
           }

           gettimeofday(
    &tv1,NULL);

           
    while(i++ < conn)
           {
                   
    if(pthread_create(&ptid[i],NULL,(void *)conn_mem,NULL) != 0)
                   {
                           perror(
    "pthread_create error"n");
                           exit(253);
                   }
           }

           i
    =0;

           
    while(i++ < conn)
           {
                   
    if(pthread_join(ptid[i],NULL) != 0)
                   {
                           perror(
    "pthread_join error"n");
                           exit(253);
                   }
           }

           gettimeofday(
    &tv2,NULL);

           printf(
    "time is %f,conn is %f persecond"n",((tv2.tv_sec-tv1.tv_sec)+(tv2.tv_usec-tv1.tv_usec)/1000000.0),conn/((tv2.tv_sec-tv1.tv_sec)+(tv2.tv_usec-tv1.tv_usec)/1000000.0));

           
    return 0;
    }

    void p_usage(void)
    {
           printf(
    "Usage:./mem_benchmark_conn < conn_num >"n");
           printf("Notice: the conn_num must <= 10000"n");
           return;
    }

    void *conn_mem(void)
    {
           
    int sock;
           
    char *key = "test_a";
           
    char *value = "this is a";

           
    if((sock=NonbSocket(MEM_SERVER,MEM_PORT)) < 0)
           {
                   fprintf(stderr,
    "socket error in line %d"n",__LINE__);
                   return NULL;
           }

    int i=0;
    while(i++ < 10)
    {
    ///*
           mem_set(sock,key,value);
    //*/
    /*

           char *key2="test_b";
           char *value2="this is b";
           mem_add(sock,key2,value2);
    */
    /*
           char buf[101];
           mem_get(sock,key,buf);
           printf("get value for %s is %s"n",key,buf);
    */
    /*
           char *key3="test_c";
           mem_del(sock,key);
    */
    }
           close(sock);

           
    return NULL;
    }

    int mem_set(int sock,const char *key,const char *value)
    {
           
    char set[101];
           
    char recv_buf[101];

           sprintf(
    set,"set %s 0 0 %d"r"n%s"r"n",key,strlen(value),value);

           
    if(write(sock,set,strlen(set)) < 0)
           {
                   fprintf(stderr,
    "write error in line %d"n",__LINE__);
                   return -1;
           }

           
    if(read(sock,recv_buf,100< 0)
           {
                   fprintf(stderr,
    "read error in line %d"n",__LINE__);
                   return -2;
           }

           printf(
    "in set %s"n",recv_buf);

           
    return 0;
    }

    int mem_add(int sock,const char *key,const char *value)
    {
           
    char add[101];
           
    char recv_buf[101];

           sprintf(add,
    "add %s 0 0 %d"r"n%s"r"n",key,strlen(value),value);

           
    if(write(sock,add,strlen(add)) < 0)
           {
                   fprintf(stderr,
    "write error in line %d"n",__LINE__);
                   return -1;
           }

           
    if(read(sock,recv_buf,100< 0)
           {
                   fprintf(stderr,
    "read error in line %d"n",__LINE__);
                   return -2;
           }

           printf(
    "in add %s"n",recv_buf);

           
    return 0;
    }

    int mem_get(int sock,const char *key,char *value)
    {
           
    char get[101];
           
    char recv_buf[101];

           sprintf(
    get,"get %s"r"n",key);

           
    if(write(sock,get,strlen(get)) < 0)
           {
                   fprintf(stderr,
    "write error in line %d"n",__LINE__);
                   return -1;
           }

           
    if(read(sock,recv_buf,100< 0)
           {
                   fprintf(stderr,
    "read error in line %d"n",__LINE__);
                   return -2;
           }

           strncpy(value,recv_buf,strlen(recv_buf));

           printf(
    "in get %s"n",recv_buf);

           
    return 0;
    }

    int mem_del(int sock,const char *key)
    {
           
    char del[101];
           
    char recv_buf[101];

           sprintf(del,
    "delete %s 0"r"n",key);

           
    if(write(sock,del,strlen(del)) < 0)
           {
                   fprintf(stderr,
    "write error in line %d"n",__LINE__);
                   return -1;
           }

           
    if(read(sock,recv_buf,100< 0)
           {
                   fprintf(stderr,
    "read error in line %d"n",__LINE__);
                   return -2;
           }

           printf(
    "in del %s"n",recv_buf);

           
    return 0;
    }

           3.  分布式的實(shí)現(xiàn)
           分布式的實(shí)現(xiàn)可以這么完成,構(gòu)建一個(gè)struct用于存放server信息.對(duì)于每個(gè)請(qǐng)求的key值,用很簡(jiǎn)單的hash算法(如 libmemcache用的是crc32)映射到server數(shù)組中的某個(gè)數(shù)組,然后對(duì)其進(jìn)行通信.獲取處理結(jié)果之后,將結(jié)果美化返回client.

    6.libevent簡(jiǎn)介
           1.  libevent
           libevent是一個(gè)事件觸發(fā)的網(wǎng)絡(luò)庫(kù),適用于windows,linux,bsd等多種平臺(tái),內(nèi)部使用iopc/epoll/kqueue等系統(tǒng)調(diào)用管理事件機(jī)制,而且根據(jù)libevent官方網(wǎng)站上公布的數(shù)據(jù)統(tǒng)計(jì),似乎也有著非凡的性能.
           從代碼中看,libevent支持用戶使用三種類型的事件,分別是網(wǎng)絡(luò)IO,定時(shí)器,信號(hào)三種,在定時(shí)器的實(shí)現(xiàn)上使用了紅黑樹(shù)(RB tree)的數(shù)據(jù)結(jié)構(gòu),以達(dá)到高效查找,排序,刪除定時(shí)器的目的,網(wǎng)絡(luò)IO上,libevent的epoll居然用的EPOLLLT水平觸發(fā)的方式,不容 易出錯(cuò),但是在效率上可能比EPOLLET要低一些.跟網(wǎng)絡(luò)無(wú)關(guān)的,libevent也有一些緩沖區(qū)管理的函數(shù),libevent沒(méi)有提供緩存的函數(shù).而 且libevent的接口形式非常值得參考.
           2.  epoll
    在linux中,libevent用的是epoll.如果有興趣的話,可以查看man epoll頁(yè)面.或者看前面blog上引用的libevent的資源.
           
           文章學(xué)習(xí)了黑夜路人相關(guān)文章,奶瓶博士的memcache深度分析文章和一些memcache作者的文章,在此向他們致敬.
    posted on 2008-10-15 12:28 Kevin.Zhong 閱讀(334) 評(píng)論(0)  編輯  收藏 所屬分類: memcache
    主站蜘蛛池模板: 热99re久久精品精品免费| 最近中文字幕mv免费高清电影| 久久成人a毛片免费观看网站| 91精品成人免费国产片| 成人毛片免费视频| 亚洲午夜精品一级在线播放放| 久久亚洲精品中文字幕三区| 亚洲国产精品日韩在线| 午夜亚洲WWW湿好爽| 国产精品玖玖美女张开腿让男人桶爽免费看 | 亚洲日韩国产一区二区三区| 好看的亚洲黄色经典| 亚洲乱人伦精品图片| 美女无遮挡免费视频网站 | 国产精品亚洲一区二区在线观看 | 亚洲AV无码一区二区乱子仑| 中文字幕在线视频免费观看| 69免费视频大片| 国产成人免费a在线资源| 久久亚洲高清观看| 色老板亚洲视频免在线观| 一级女性全黄生活片免费看| 99re6免费视频| 亚洲AV无码无限在线观看不卡| 精品亚洲福利一区二区| 一个人免费视频观看在线www| 免费人成视频在线| 亚洲无线观看国产精品| 亚洲国产成人99精品激情在线| 男人j进女人p免费视频| 在线看片v免费观看视频777| 亚洲国产成人精品久久久国产成人一区二区三区综 | 91亚洲国产在人线播放午夜| 免费国产va在线观看| 18禁无遮挡无码国产免费网站| 亚洲国产成人爱av在线播放| 亚洲天堂福利视频| 成人无码精品1区2区3区免费看| 国产免费不卡v片在线观看| 伊人久久大香线蕉亚洲五月天| 亚洲成_人网站图片|