閱讀704 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Memcached實施心得

         說到Memcached,大家都不會陌生,前陣子在項目中搭建了一套集群環境,上線運行至今,通過memcache-top監控調優,各指標表現相對平穩,優異,加上之前在負責交易平台時積累的Memcached實施經驗,便整理寫下了此文,如果有不準確的,歡迎拍磚~
  • memcached是怎麼工作的?
  • memcached內存管理的機製是?
  • memcached最大的優勢是什麼?
  • memcached和MySQL的query cache相比,有什麼優缺點?
  • memcached和服務器的local cache(比如PHP的APC、mmap文件等)相比,有什麼優缺點?
  • memcached的cache機製是怎樣的?
  • memcached如何實現冗餘機製?
  • memcached如何處理容錯的? 
  • memcached中item怎麼批量導入導出?
  • 但是我確實需要把memcached中的item都dump出來,確實需要把數據load到memcached中,怎麼辦?
  • memcached是如何做身份驗證的?
  • 如何使用memcached的多線程是什麼?如何使用它們?
  • memcached能接受的key的最大長度是多少?(250bytes)
  • memcached對item的過期時間有什麼限製?(為什麼有30天的限製?)
  • memcached最大能存儲多大的單個item?(1M byte)
  • 為什麼單個item的大小被限製在1M byte之內?
  • 什麼是binary協議?它值得關注嗎?
  • memcached是如何分配內存的?為什麼不用malloc/free!?究竟為什麼使用slab呢?
  • memcached能保證數據存儲的原子性嗎?
  • memcached如何處理大量並發訪問並存在cache過期的情況?
  • memcached client中要關閉Nagle算法嗎?

  memcached是怎麼工作的?

  Memcached的神奇來自兩階段哈希(two-stage hash)。Memcached就像一個巨大的、存儲了很多<key,value>對的哈希表。通過key,可以存儲或查詢任意的數據。

  客戶端可以把數據存儲在多台memcached上。當查詢數據時,客戶端首先參考節點列表計算出key的哈希值(階段一哈希),進而選中一個節點;客戶端將請求發送給選中的節點,然後memcached節點通過一個內部的哈希算法(階段二哈希),查找真正的數據(item)。

  舉個列子,假設有3個客戶端1, 2, 3,3台memcached A, B, C:
  Client 1想把數據"bar"以key "foo"存儲。Client 1首先參考節點列表(A, B, C),計算key "foo"的哈希值,假設memcached B被選中。接著,Client 1直接connect到memcached B,通過key "foo"把數據"bar"存儲進去。  Client 2使用與Client 1相同的客戶端庫(意味著階段一的哈希算法相同),也擁有同樣的memcached列表(A, B, C)。
  於是,經過相同的哈希計算(階段一),Client 2計算出key "foo"在memcached B上,然後它直接請求memcached B,得到數據"bar"。

    從實現的角度看,memcached是一個非阻塞的、基於事件的服務器程序。這種架構可以很好地解決C10K problem ,並具有極佳的可擴展性。可以參考A Story of Caching ,這篇文章簡單解釋了客戶端與memcached是如何交互的。

memcached內存管理的機製是

  Memcached內存管理采取預分配、分組管理的方式(規避內存碎片)。分組管理就是按照chunk的大小分類slab。下麵解釋一下memcached的內存預分配過程。
                                  
  
 
  向memcached添加一個item時候,memcached首先會根據item的大小,來選擇最合適的slab class:例如item的大小為190字節,class 3的chunk大小為176字節顯然不合適,class 4的chunk大小為224字節,大於190字節,因此該item將放在class 4中(顯然這裏34字節的浪費是不可避免的),計算好所要放入的chunk之後,memcached會去檢查該類大小的chunk還有沒有空閑的,如果沒有,將會申請1M(1個slab)的空間並劃分為該種類chunk。例如我們第一次向memcached中放入一個190字節的item時,memcached會產生一個slab class 4(也叫一個page),並會用去一個chunk,剩餘4680個chunk供下次有適合大小item時使用,當我們用完這所有的4681個chunk之後,下次再有一個在160~200字節之間的item添加進來時,memcached會再次產生一個class 4的slab(這樣就存在了2個pages)。
  Slab是一個內存塊,它是memcached一次申請內存的最小單位。在啟動memcached的時候一般會使用參數-m指定其可用內存,但是並不是在啟動的那一刻所有的內存就全部分配出去了,隻有在需要的時候才會去申請,而且每次申請一定是一個slab。Slab的大小固定為1M(1048576 Byte),一個slab由若幹個大小相等的chunk組成。每個chunk中都保存了一個item結構體、一對key和value。如代碼所示:
/** 
    * Structure for storing items within memcached. 
   */  
typedef struct _stritem {  
    struct _stritem *next;  
    struct _stritem *prev;  
    struct _stritem *h_next;    /* hash chain next */  
    rel_time_t      time;       /* least recent access */  
    rel_time_t      exptime;    /* expire time */  
    int             nbytes;     /* size of data */  
    unsigned short  refcount;  
    uint8_t         nsuffix;    /* length of flags-and-length string */  
    uint8_t         it_flags;   /* ITEM_* above */  
    uint8_t         slabs_clsid;/* which slab class we're in */  
    uint8_t         nkey;       /* key length, w/terminating null and padding */  
    /* this odd type prevents type-punning issues when we do 
     * the little shuffle to save space when not using CAS. */  
    union {  
        uint64_t cas;  
        char end;  
    } data[];  
    /* if it_flags & ITEM_CAS we have 8 bytes CAS */  
    /* then null-terminated key */  
    /* then " flags length\r\n" (no terminating null) */  
    /* then data with terminating \r\n (no terminating null; it's binary!) */  
} item;  

  雖然在同一個slab中chunk的大小相等的,但是在不同的slab中chunk的大小並不一定相等,在memcached中按照chunk的大小不同,可以把slab分為很多種類(class)。在啟動memcached的時候可以通過-vv來查看slab的種類:



 從上圖可以看到,memcached把slab分為38類(class1~class38),在class 1中,chunk的大小為104字節,由於一個slab的大小是固定的1048576字節(1M),因此在class1中最多可以有10082個chunk:
       10082×104 + 48 = 1048576
 在class1中,剩餘的48字節因為不夠一個chunk的大小,因此會被浪費掉。
 生產環境呢中我們可以調優的參數的參數有-f(slab分配增量因子,默認圍1.25)、-n(chunk最小分配數,默認48字節),在memcached的實際運行中,我們還需要觀察我們的數據特征,合理的調節f,n的值,使我們的內存得到充分的利用減少浪費。

memcached最大的優勢是什麼?

  Memcached最大的好處就是它帶來了極佳的水平可擴展性,特別是在一個巨大的係統中。由於客戶端自己做了一次哈希,那麼我們很容易增加大量memcached到集群中。memcached之間沒有相互通信,因此不會增加 memcached的負載;沒有多播協議,不會網絡通信量爆炸(implode)。memcached的集群很好用。內存不夠了?增加幾台memcached吧;CPU不夠用了?再增加幾台吧;有多餘的內存?在增加幾台吧,不要浪費了。

  基於memcached的基本原則,可以相當輕鬆地構建出不同類型的緩存架構。

memcached和MySQL的query cache相比,有什麼優缺點?

  把memcached引入應用中,還是需要不少工作量的。MySQL有個使用方便的query cache,可以自動地緩存SQL查詢的結果,被緩存的SQL查詢可以被反複地快速執行。Memcached與之相比,怎麼樣呢?MySQL的query cache是集中式的,連接到該query cache的MySQL服務器都會受益。

  • 當您修改表時,MySQL的query cache會立刻被刷新(flush)。存儲一個memcached item隻需要很少的時間,但是當寫操作很頻繁時,MySQL的query cache會經常讓所有緩存數據都失效。
  • 在多核CPU上,MySQL的query cache會遇到擴展問題(scalability issues)。在多核CPU上,query cache會增加一個全局鎖(global lock), 由於需要刷新更多的緩存數據,速度會變得更慢。
  • 在MySQL的query cache中,我們是不能存儲任意的數據的(隻能是SQL查詢結果)。而利用memcached,我們可以搭建出各種高效的緩存。比如,可以執行多個獨立的查詢,構建出一個用戶對象(user object),然後將用戶對象緩存到memcached中。而query cache是SQL語句級別的,不可能做到這一點。在小的網站中,query cache會有所幫助,但隨著網站規模的增加,query cache的弊將大於利。
  • query cache能夠利用的內存容量受到MySQL服務器空閑內存空間的限製。給數據庫服務器增加更多的內存來緩存數據,固然是很好的。但是,有了memcached,隻要您有空閑的內存,都可以用來增加memcached集群的規模,然後您就可以緩存更多的數據。

memcached和服務器的local cache(比如PHP的APC、mmap文件等)相比,有什麼優缺點?

  首先,local cache有許多與上麵(query cache)相同的問題。local cache能夠利用的內存容量受到(單台)服務器空閑內存空間的限製。不過,local cache有一點比memcached和query cache都要好,那就是它不但可以存儲任意的數據,而且沒有網絡存取的延遲。

  • local cache的數據查詢更快。考慮把highly common的數據放在local cache中吧。如果每個頁麵都需要加載一些數量較少的數據,考慮把它們放在local cached吧。
  • local cache缺少集體失效(group invalidation)的特性。在memcached集群中,刪除或更新一個key會讓所有的觀察者覺察到。但是在local cache中, 我們隻能通知所有的服務器刷新cache(很慢,不具擴展性),或者僅僅依賴緩存超時失效機製。
  • local cache麵臨著嚴重的內存限製,這一點上麵已經提到。

memcached的cache機製是怎樣的?

  Memcached主要的cache機製是LRU(最近最少用)算法+超時失效。當您存數據到memcached中,可以指定該數據在緩存中可以呆多久Which is forever, or some time in the future。如果memcached的內存不夠用了,過期的slabs會優先被替換,接著就輪到最老的未被使用的slabs。

memcached如何實現冗餘機製? 
  不實現!我們對這個問題感到很驚訝。Memcached應該是應用的緩存層。它的設計本身就不帶有任何冗餘機製。如果一個memcached節點失去了所有數據,您應該可以從數據源(比如數據庫)再次獲取到數據。您應該特別注意,您的應用應該可以容忍節點的失效。不要寫一些糟糕的查詢代碼,寄希望於memcached來保證一切!如果您擔心節點失效會大大加重數據庫的負擔,那麼您可以采取一些辦法。比如您可以增加更多的節點(來減少丟失一個節點的影響),熱備節點(在其他節點down了的時候接管IP),等等。目前,業界有一些現成的解決方案,如: pecl-memcache,repcached,memagent等等。

memcached如何處理容錯的?

  不處理!:) 在memcached節點失效的情況下,集群沒有必要做任何容錯處理。如果發生了節點失效,應對的措施完全取決於用戶。節點失效時,下麵列出幾種方案供您選擇:

  • 忽略它! 在失效節點被恢複或替換之前,還有很多其他節點可以應對節點失效帶來的影響。
  • 把失效的節點從節點列表中移除。做這個操作千萬要小心!在默認情況下(餘數式哈希算法),客戶端添加或移除節點,會導致所有的緩存數據不可用!因為哈希參照的節點列表變化了,大部分key會因為哈希值的改變而被映射到(與原來)不同的節點上。
  • 啟動熱備節點,接管失效節點所占用的IP。這樣可以防止哈希紊亂(hashing chaos)。
  • 如果希望添加和移除節點,而不影響原先的哈希結果,可以使用一致性哈希算法(consistent hashing)。您可以百度一下一致性哈希算法。支持一致性哈希的客戶端已經很成熟,而且被廣泛使用。去嚐試一下吧!
  • 兩次哈希(reshing)。當客戶端存取數據時,如果發現一個節點down了,就再做一次哈希(哈希算法與前一次不同),重新選擇另一個節點(需要注意的時,客戶端並沒有把down的節點從節點列表中移除,下次還是有可能先哈希到它)。如果某個節點時好時壞,兩次哈希的方法就有風險了,好的節點和壞的節點上都可能存在髒數據(stale data)。

memcached中item怎麼批量導入導出?

  您不應該這樣做!Memcached是一個非阻塞的服務器。任何可能導致memcached暫停或瞬時拒絕服務的操作都應該值得深思熟慮。向memcached中批量導入數據往往不是您真正想要的!想象看,如果緩存數據在導出導入之間發生了變化,您就需要處理髒數據了;如果緩存數據在導出導入之間過期了,您又怎麼處理這些數據呢?

  因此,批量導出導入數據並不像您想象中的那麼有用。不過在一個場景倒是很有用。如果您有大量的從不變化的數據,並且希望緩存很快熱(warm)起來,批量導入緩存數據是很有幫助的。雖然這個場景並不典型,但卻經常發生,因此我們會考慮在將來實現批量導出導入的功能。

  Steven Grimm,一如既往地,,在郵件列表中給出了另一個很好的例子:https://lists.danga.com/pipermail/memcached/2007-July/004802.html 。

但是我確實需要把memcached中的item批量導出導入,怎麼辦??

  好吧好吧。如果您需要批量導出導入,最可能的原因一般是重新生成緩存數據需要消耗很長的時間,或者數據庫壞了讓您飽受痛苦。

  如果一個memcached節點down了讓您很痛苦,那麼您還會陷入其他很多麻煩。您的係統太脆弱了。您需要做一些優化工作。比如處理"驚群"問題(比如 memcached節點都失效了,反複的查詢讓您的數據庫不堪重負...這個問題在FAQ的其他提到過),或者優化不好的查詢。記住,Memcached 並不是您逃避優化查詢的借口。

  如果您的麻煩僅僅是重新生成緩存數據需要消耗很長時間(15秒到超過5分鍾),您可以考慮重新使用數據庫。這裏給出一些提示:

  • 使用MogileFS(或者CouchDB等類似的軟件)在存儲item。把item計算出來並dump到磁盤上。MogileFS可以很方便地覆寫item,並提供快速地訪問。您甚至可以把MogileFS中的item緩存在memcached中,這樣可以加快讀取速度。 MogileFS+Memcached的組合可以加快緩存不命中時的響應速度,提高網站的可用性。
  • 重新使用MySQL。MySQL的InnoDB主鍵查詢的速度非常快。如果大部分緩存數據都可以放到VARCHAR字段中,那麼主鍵查詢的性能將更好。從memcached中按key查詢幾乎等價於MySQL的主鍵查詢:將key 哈希到64-bit的整數,然後將數據存儲到MySQL中。您可以把原始(不做哈希)的key存儲都普通的字段中,然後建立二級索引來加快查詢...key被動地失效,批量刪除失效的key,等等。

  上麵的方法都可以引入memcached,在重啟memcached的時候仍然提供很好的性能。由於您不需要當心"hot"的item被memcached LRU算法突然淘汰,用戶再也不用花幾分鍾來等待重新生成緩存數據(當緩存數據突然從內存中消失時),因此上麵的方法可以全麵提高性能。

  關於這些方法的細節,詳見博客:https://dormando.livejournal.com/495593.html 。

memcached是如何做身份驗證的? 
  沒有身份認證機製!memcached是運行在應用下層的軟件(身份驗證應該是應用上層的職責)。memcached的客戶端和服務器端之所以是輕量級的,部分原因就是完全沒有實現身份驗證機製。這樣,memcached可以很快地創建新連接,服務器端也無需任何配置。

  如果您希望限製訪問,您可以使用防火牆,或者讓memcached監聽unix domain socket。

memcached的多線程是什麼?如何使用它們? 

  線程就是定律(threads rule)!在Steven Grimm和Facebook的努力下,memcached 1.2及更高版本擁有了多線程模式。多線程模式允許memcached能夠充分利用多個CPU,並在CPU之間共享所有的緩存數據。memcached使用一種簡單的鎖機製來保證數據更新操作的互斥。相比在同一個物理機器上運行多個memcached實例,這種方式能夠更有效地處理multi gets。

  如果您的係統負載並不重,也許您不需要啟用多線程工作模式。如果您在運行一個擁有大規模硬件的、龐大的網站,您將會看到多線程的好處。

  更多信息請參見:https://code.sixapart.com/svn/memcached/trunk/server/doc/threads.txt

  簡單地總結一下:命令解析(memcached在這裏花了大部分時間)可以運行在多線程模式下。memcached內部對數據的操作是基於很多全局鎖的(因此這部分工作不是多線程的)。未來對多線程模式的改進,將移除大量的全局鎖,提高memcached在負載極高的場景下的性能。

memcached能接受的key的最大長度是多少? 

  key的最大長度是250個字符。需要注意的是,250是memcached服務器端內部的限製,如果您使用的客戶端支持"key的前綴"或類似特性,那麼key(前綴+原始key)的最大長度是可以超過250個字符的。我們推薦使用使用較短的key,因為可以節省內存和帶寬。工程上,我們經常這麼幹:

  /**
     * @param cacheDomain
     * @param valueId
     * @param version
     * @see https://en.wikipedia.org/wiki/SHA-2
     * @return
     */
    public static String getKey(CacheDomain cacheDomain, String valueId, int version) {
        String key = cacheDomain.getValue() + SPLIT + valueId + SPLIT + version;
        final byte[] bytes = key.getBytes();
        if (bytes.length > 250) {
            key = DigestUtils.sha256Hex(key);
        }
        return key;
    }

memcached對item的過期時間有什麼限製? 

  過期時間最大可以達到30天。memcached把傳入的過期時間(時間段)解釋成時間點後,一旦到了這個時間點,memcached就把item置為失效狀態。這是一個簡單但obscure的機製。

memcached最大能存儲多大的單個item? 
  1MB。如果你的數據大於1MB,可以考慮在客戶端壓縮或拆分到多個key中。

為什麼單個item的大小被限製在1M byte之內? 

  啊...這是一個大家經常問的問題!

  簡單的回答:因為內存分配器的算法就是這樣的。

  詳細的回答:Memcached的內存存儲引擎(引擎將來可插拔...),使用slabs來管理內存。內存被分成大小不等的slabs chunks(先分成大小相等的slabs,然後每個slab被分成大小相等chunks,不同slab的chunk大小是不相等的)。chunk的大小依次從一個最小數開始,按某個因子增長,直到達到最大的可能值。

  如果最小值為400B,最大值是1MB,因子是1.20,各個slab的chunk的大小依次是:slab1 - 400B slab2 - 480B slab3 - 576B ...

  slab中chunk越大,它和前麵的slab之間的間隙就越大。因此,最大值越大,內存利用率越低。Memcached必須為每個slab預先分配內存,因此如果設置了較小的因子和較大的最大值,會需要更多的內存。

  還有其他原因使得您不要這樣向memcached中存取很大的數據...不要嚐試把巨大的網頁放到mencached中。把這樣大的數據結構load和unpack到內存中需要花費很長的時間,從而導致您的網站性能反而不好。

  如果您確實需要存儲大於1MB的數據,你可以修改slabs.c:POWER_BLOCK的值,然後重新編譯memcached;或者使用低效的malloc/free。其他的建議包括數據庫、MogileFS等。

什麼是二進製協議,我該關注嗎?

  關於二進製最好的信息當然是二進製協議規範:https://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol 。

  二進製協議嚐試為端提供一個更有效的、可靠的協議,減少客戶端/服務器端因處理協議而產生的CPU時間。
根據Facebook的測試,解析ASCII協議是memcached中消耗CPU時間最多的環節。所以,我們為什麼不改進ASCII協議呢?

  在這個郵件列表的thread中可以找到一些舊的信息:https://lists.danga.com/pipermail/memcached/2007-July/004636.html 。

memcached的內存分配器是如何工作的?為什麼不適用malloc/free!?為何要使用slabs? 

  實際上,這是一個編譯時選項。默認會使用內部的slab分配器。您確實確實應該使用內建的slab分配器。最早的時候,memcached隻使用malloc/free來管理內存。然而,這種方式不能與OS的內存管理以前很好地工作。反複地malloc/free造成了內存碎片,OS最終花費大量的時間去查找連續的內存塊來滿足malloc的請求,而不是運行memcached進程。如果您不同意,當然可以使用malloc!隻是不要在郵件列表中抱怨啊:)

  slab分配器就是為了解決這個問題而生的。內存被分配並劃分成chunks,一直被重複使用。因為內存被劃分成大小不等的slabs,如果item的大小與被選擇存放它的slab不是很合適的話,就會浪費一些內存。Steven Grimm正在這方麵已經做出了有效的改進。

  郵件列表中有一些關於slab的改進(power of n 還是 power of 2)和權衡方案:https://lists.danga.com/pipermail/memcached/2006-May/002163.htmlhttps://lists.danga.com/pipermail/memcached/2007-March/003753.html 。

  如果您想使用malloc/free,看看它們工作地怎麼樣,您可以在構建過程中定義USE_SYSTEM_MALLOC。這個特性沒有經過很好的測試,所以太不可能得到開發者的支持。

  更多信息:https://code.sixapart.com/svn/memcached/trunk/server/doc/memory_management.txt 。

memcached是原子的嗎? 
  當然!好吧,讓我們來明確一下:
  所有的被發送到memcached的單個命令是完全原子的。如果您針對同一份數據同時發送了一個set命令和一個get命令,它們不會影響對方。它們將被串行化、先後執行。即使在多線程模式,所有的命令都是原子的,除非程序有bug:)
  命令序列不是原子的。如果您通過get命令獲取了一個item,修改了它,然後想把它set回memcached,我們不保證這個item沒有被其他進程(process,未必是操作係統中的進程)操作過。在並發的情況下,您也可能覆寫了一個被其他進程set的item。

  memcached 1.2.5以及更高版本,提供了gets和cas命令,它們可以解決上麵的問題。如果您使用gets命令查詢某個key的item,memcached會給您返回該item當前值的唯一標識。如果您覆寫了這個item並想把它寫回到memcached中,您可以通過cas命令把那個唯一標識一起發送給memcached。如果該item存放在memcached中的唯一標識與您提供的一致,您的寫操作將會成功。如果另一個進程在這期間也修改了這個item,那麼該item存放在memcached中的唯一標識將會改變,您的寫操作就會失敗。

  通常,基於memcached中item的值來修改item,是一件棘手的事情。除非您很清楚自己在做什麼,否則請不要做這樣的事情。
memcached如何處理大量並發訪問並存在cache過期的情況 
       在大並發的場合,當cache失效時,大量並發同時取不到cache,會同一瞬間去訪問db並回設cache,可能會給係統帶來潛在的超負荷風險。這時我們可以采取類似於網絡上麵的擁塞控製算法思路(CSMA/CD),簡單一點,就是在load db之前先add一個mutex key, mutex key add成功之後再去做加載db, 如果add失敗則sleep之後重試讀取原cache數據。為了防止死鎖,mutex key也需要設置過期時間。偽代碼如下:
if (memcache.get(key) == null) {
    // 1 min timeout to avoid mutex holder crash
    if (memcache.add(key_mutex, 1 * 60 * 1000) == true) {
        value = db.get(key);
        memcache.set(key, value);
        memcache.delete(key_mutex);
    } else {
        sleep(50);
        retry();
    }
}
       有sleep的地方就應該得到大家的注意,一旦這點sleep時間堵塞住了所有客戶端,那就得另想良策了,別急,是時候Gearman登場了,當需要更新Cache的時候,我們不再直接查詢數據庫,而是把任務拋給Gearman來處理,當並發量比較大的時候,Gearman內部的優化可以保證相同的請求隻查詢一次後端數據庫。
memcached client中要關閉Nagle算法嗎?
      等等,什麼是Nagle算法?
      TCP/IP協議中,無論發送多少數據,總是要在數據前麵加上協議頭,同時,對方接收到數據,也需要發送ACK表示確認。為了盡可能地利用網絡帶寬,TCP總是希望盡可能地發送足夠大的數據。(一個連接會設置MSS參數,因此,TCP/IP希望每次都能夠以MSS尺寸的數據塊來發送數據)。Nagle算法就是為了盡可能發送大塊數據,避免網絡中充斥著許多小數據塊。
      Nagle算法的基本定義是任意時刻,最多隻能有一個未被確認的小段 。 所謂“小段”,指的是小於MSS尺寸的數據塊,所謂“未被確認”,是指一個數據塊發送出去後,沒有收到對方發送的ACK確認該數據已收到。
        Nagle算法的規則(可參考tcp_output.c文件裏tcp_nagle_check函數注釋): 
      (1)如果包長度達到MSS,則允許發送;
      (2)如果該包含有FIN,則允許發送;
      (3)設置了TCP_NODELAY選項,則允許發送;
      (4)未設置TCP_CORK選項時,若所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送; 
      (5)上述條件都未滿足,但發生了超時(一般為200ms),則立即發送。
       那麼,了解了原理,就可以根據你緩存的數據大小進行合理判斷了(MSS的定義見:https://en.wikipedia.org/wiki/Maximum_segment_size),不過目前主流的client如Xmemcached,spymemcached默認都禁掉了該算法。

最後更新:2017-04-03 16:48:42

  上一篇:go 微軟將Bing變開放平台 同穀歌爭奪開發者
  下一篇:go Oracle樹結構查詢——connect by語法詳解