日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

memcache的深度解析(转)

發布時間:2024/4/17 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 memcache的深度解析(转) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Memcached是danga.com(運營LiveJournal的技術團隊)開發的一套分布式內存對象緩存系統,用于在動態系統中減少數據庫負載,提升性能。關于這個東西,相信很多人都用過,本文意在通過對memcached的實現及代碼分析,獲得對這個出色的開源軟件更深入的了解,并可以根據我們的需要對其進行更進一步的優化。末了將通過對BSM_Memcache擴展的分析,加深對memcached的使用方式理解。

本文的部分內容可能需要比較好的數學基礎作為輔助。

◎Memcached是什么

在闡述這個問題之前,我們首先要清楚它“不是什么”。很多人把它當作和SharedMemory那種形式的存儲載體來使用,雖然memcached使用了同樣的“Key=>Value”方式組織數據,但是它和共享內存、APC等本地緩存有非常大的區別。Memcached是分布式的,也就是說它不是本地的。它基于網絡連接(當然它也可以使用localhost)方式完成服務,本身它是一個獨立于應用的程序或守護進程(Daemon方式)。

Memcached使用libevent庫實現網絡連接服務,理論上可以處理無限多的連接,但是它和Apache不同,它更多的時候是面向穩定的持續連接的,所以它實際的并發能力是有限制的。在保守情況下memcached的最大同時連接數為200,這和Linux線程能力有關系,這個數值是可以調整的。關于libevent可以參考相關文檔。 Memcached內存使用方式也和APC不同。APC是基于共享內存和MMAP的,memcachd有自己的內存分配算法和管理方式,它和共享內存沒有關系,也沒有共享內存的限制,通常情況下,每個memcached進程可以管理2GB的內存空間,如果需要更多的空間,可以增加進程數。

◎Memcached適合什么場合

在很多時候,memcached都被濫用了,這當然少不了對它的抱怨。我經常在論壇上看見有人發貼,類似于“如何提高效率”,回復是“用memcached”,至于怎么用,用在哪里,用來干什么一句沒有。memcached不是萬能的,它也不是適用在所有場合。

Memcached是“分布式”的內存對象緩存系統,那么就是說,那些不需要“分布”的,不需要共享的,或者干脆規模小到只有一臺服務器的應用,memcached不會帶來任何好處,相反還會拖慢系統效率,因為網絡連接同樣需要資源,即使是UNIX本地連接也一樣。 在我之前的測試數據中顯示,memcached本地讀寫速度要比直接PHP內存數組慢幾十倍,而APC、共享內存方式都和直接數組差不多。可見,如果只是本地級緩存,使用memcached是非常不劃算的。

Memcached在很多時候都是作為數據庫前端cache使用的。因為它比數據庫少了很多SQL解析、磁盤操作等開銷,而且它是使用內存來管理數據的,所以它可以提供比直接讀取數據庫更好的性能,在大型系統中,訪問同樣的數據是很頻繁的,memcached可以大大降低數據庫壓力,使系統執行效率提升。另外,memcached也經常作為服務器之間數據共享的存儲媒介,例如在SSO系統中保存系統單點登陸狀態的數據就可以保存在memcached中,被多個應用共享。

需要注意的是,memcached使用內存管理數據,所以它是易失的,當服務器重啟,或者memcached進程中止,數據便會丟失,所以memcached不能用來持久保存數據。很多人的錯誤理解,memcached的性能非常好,好到了內存和硬盤的對比程度,其實memcached使用內存并不會得到成百上千的讀寫速度提高,它的實際瓶頸在于網絡連接,它和使用磁盤的數據庫系統相比,好處在于它本身非常“輕”,因為沒有過多的開銷和直接的讀寫方式,它可以輕松應付非常大的數據交換量,所以經常會出現兩條千兆網絡帶寬都滿負荷了,memcached進程本身并不占用多少CPU資源的情況。

◎Memcached的工作方式

以下的部分中,讀者最好能準備一份memcached的源代碼。

Memcached是傳統的網絡服務程序,如果啟動的時候使用了-d參數,它會以守護進程的方式執行。創建守護進程由daemon.c完成,這個程序只有一個daemon函數,這個函數很簡單(如無特殊說明,代碼以1.2.1為準):

CODE: #include <fcntl.h> #include <stdlib.h> #include <unistd.h>

?

int daemon(nochdir, noclose) ??? int nochdir, noclose; { ??? int fd;

??? switch (fork()) { ??? case -1: ??????? return (-1); ??? case 0: ??????? break;? ??? default: ??????? _exit(0); ??? }

??? if (setsid() == -1) ??????? return (-1);

??? if (!nochdir) ??????? (void)chdir(”/”);

??? if (!noclose && (fd = open(”/dev/null”, O_RDWR, 0)) != -1) { ??????? (void)dup2(fd, STDIN_FILENO); ??????? (void)dup2(fd, STDOUT_FILENO); ??????? (void)dup2(fd, STDERR_FILENO); ??????? if (fd > STDERR_FILENO) ??????????? (void)close(fd); ??? } ??? return (0); }

這個函數 fork 了整個進程之后,父進程就退出,接著重新定位 STDIN 、 STDOUT 、 STDERR 到空設備, daemon 就建立成功了。

Memcached 本身的啟動過程,在 memcached.c 的 main 函數中順序如下:

1 、調用 settings_init() 設定初始化參數 2 、從啟動命令中讀取參數來設置 setting 值 3 、設定 LIMIT 參數 4 、開始網絡 socket 監聽(如果非 socketpath 存在)( 1.2 之后支持 UDP 方式) 5 、檢查用戶身份( Memcached 不允許 root 身份啟動) 6 、如果有 socketpath 存在,開啟 UNIX 本地連接(Sock 管道) 7 、如果以 -d 方式啟動,創建守護進程(如上調用 daemon 函數) 8 、初始化 item 、 event 、狀態信息、 hash 、連接、 slab 9 、如設置中 managed 生效,創建 bucket 數組 10 、檢查是否需要鎖定內存頁 11 、初始化信號、連接、刪除隊列 12 、如果 daemon 方式,處理進程 ID 13 、event 開始,啟動過程結束, main 函數進入循環。

在 daemon 方式中,因為 stderr 已經被定向到黑洞,所以不會反饋執行中的可見錯誤信息。

memcached.c 的主循環函數是 drive_machine ,傳入參數是指向當前的連接的結構指針,根據 state 成員的狀態來決定動作。

Memcached 使用一套自定義的協議完成數據交換,它的 protocol 文檔可以參考: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt

在API中,換行符號統一為\r\n

◎Memcached的內存管理方式

Memcached有一個很有特色的內存管理方式,為了提高效率,它使用預申請和分組的方式管理內存空間,而并不是每次需要寫入數據的時候去malloc,刪除數據的時候free一個指針。Memcached使用slab->chunk的組織方式管理內存。

1.1和1.2的slabs.c中的slab空間劃分算法有一些不同,后面會分別介紹。

Slab可以理解為一個內存塊,一個slab是memcached一次申請內存的最小單位,在memcached中,一個slab的大小默認為1048576字節(1MB),所以memcached都是整MB的使用內存。每一個slab被劃分為若干個chunk,每個chunk里保存一個item,每個item同時包含了item結構體、key和value(注意在memcached中的value是只有字符串的)。slab按照自己的id分別組成鏈表,這些鏈表又按id掛在一個slabclass數組上,整個結構看起來有點像二維數組。slabclass的長度在1.1中是21,在1.2中是200。

slab有一個初始chunk大小,1.1中是1字節,1.2中是80字節,1.2中有一個factor值,默認為1.25

在1.1中,chunk大小表示為初始大小*2^n,n為classid,即:id為0的slab,每chunk大小1字節,id為1的slab,每chunk大小2字節,id為2的slab,每chunk大小4字節……id為20的slab,每chunk大小為1MB,就是說id為20的slab里只有一個chunk:

CODE: void slabs_init(size_t limit) { ??? int i; ??? int size=1;

?

??? mem_limit = limit; ??? for(i=0; i<=POWER_LARGEST; i++, size*=2) { ??????? slabclass[i].size = size; ??????? slabclass[i].perslab = POWER_BLOCK / size; ??????? slabclass[i].slots = 0; ??????? slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0; ??????? slabclass[i].end_page_ptr = 0; ??????? slabclass[i].end_page_free = 0; ??????? slabclass[i].slab_list = 0; ??????? slabclass[i].list_size = 0; ??????? slabclass[i].killing = 0; ??? }

??? /* for the test suite:? faking of how much we’ve already malloc’d */ ??? { ??????? char *t_initial_malloc = getenv(”T_MEMD_INITIAL_MALLOC”); ??????? if (t_initial_malloc) { ??????????? mem_malloced = atol(getenv(”T_MEMD_INITIAL_MALLOC”)); ??????? } ??? }

??? /* pre-allocate slabs by default, unless the environment variable ?????? for testing is set to something non-zero */ ??? { ??????? char *pre_alloc = getenv(”T_MEMD_SLABS_ALLOC”); ??????? if (!pre_alloc || atoi(pre_alloc)) { ??????????? slabs_preallocate(limit / POWER_BLOCK); ??????? } ??? } }

在1.2中,chunk大小表示為初始大小*f^n,f為factor,在memcached.c中定義,n為classid,同時,201個頭不是全部都要初始化的,因為factor可變,初始化只循環到計算出的大小達到slab大小的一半為止,而且它是從id1開始的,即:id為1的slab,每chunk大小80字節,id為2的slab,每chunk大小80*f,id為3的slab,每chunk大小80*f^2,初始化大小有一個修正值CHUNK_ALIGN_BYTES,用來保證n-byte排列 (保證結果是CHUNK_ALIGN_BYTES的整倍數)。這樣,在標準情況下,memcached1.2會初始化到id40,這個slab中每個chunk大小為504692,每個slab中有兩個chunk。最后,slab_init函數會在最后補足一個id41,它是整塊的,也就是這個slab中只有一個1MB大的chunk:

CODE: void slabs_init(size_t limit, double factor) { ??? int i = POWER_SMALLEST – 1; ??? unsigned int size = sizeof(item) + settings.chunk_size;

?

??? /* Factor of 2.0 means use the default memcached behavior */ ??? if (factor == 2.0 && size < 128) ??????? size = 128;

??? mem_limit = limit; ??? memset(slabclass, 0, sizeof(slabclass));

??? while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) { ??????? /* Make sure items are always n-byte aligned */ ??????? if (size % CHUNK_ALIGN_BYTES) ??????????? size += CHUNK_ALIGN_BYTES – (size % CHUNK_ALIGN_BYTES);

??????? slabclass[i].size = size; ??????? slabclass[i].perslab = POWER_BLOCK / slabclass[i].size; ??????? size *= factor; ??????? if (settings.verbose > 1) { ??????????? fprintf(stderr, “slab class %3d: chunk size %6d perslab %5d\n”, ??????????????????? i, slabclass[i].size, slabclass[i].perslab); ??????? }?????? ??? }

??? power_largest = i; ??? slabclass[power_largest].size = POWER_BLOCK; ??? slabclass[power_largest].perslab = 1;

??? /* for the test suite:? faking of how much we’ve already malloc’d */ ??? { ??????? char *t_initial_malloc = getenv(”T_MEMD_INITIAL_MALLOC”); ??????? if (t_initial_malloc) { ??????????? mem_malloced = atol(getenv(”T_MEMD_INITIAL_MALLOC”)); ??????? }??????

??? }

#ifndef DONT_PREALLOC_SLABS ??? { ??????? char *pre_alloc = getenv(”T_MEMD_SLABS_ALLOC”); ??????? if (!pre_alloc || atoi(pre_alloc)) { ??????????? slabs_preallocate(limit / POWER_BLOCK); ??????? } ??? } #endif }

由上可以看出,memcached的內存分配是有冗余的,當一個slab不能被它所擁有的chunk大小整除時,slab尾部剩余的空間就被丟棄了,如id40中,兩個chunk占用了1009384字節,這個slab一共有1MB,那么就有39192字節被浪費了。

Memcached使用這種方式來分配內存,是為了可以快速的通過item長度定位出slab的classid,有一點類似hash,因為item的長度是可以計算的,比如一個item的長度是300字節,在1.2中就可以得到它應該保存在id7的slab中,因為按照上面的計算方法,id6的chunk大小是252字節,id7的chunk大小是316字節,id8的chunk大小是396字節,表示所有252到316字節的item都應該保存在id7中。同理,在1.1中,也可以計算得到它出于256和512之間,應該放在chunk_size為512的id9中(32位系統)。

Memcached初始化的時候,會初始化slab(前面可以看到,在main函數中調用了slabs_init())。它會在slabs_init()中檢查一個常量DONT_PREALLOC_SLABS,如果這個沒有被定義,說明使用預分配內存方式初始化slab,這樣在所有已經定義過的slabclass中,每一個id創建一個slab。這樣就表示,1.2在默認的環境中啟動進程后要分配41MB的slab空間,在這個過程里,memcached的第二個內存冗余發生了,因為有可能一個id根本沒有被使用過,但是它也默認申請了一個slab,每個slab會用掉1MB內存

當一個slab用光后,又有新的item要插入這個id,那么它就會重新申請新的slab,申請新的slab時,對應id的slab鏈表就要增長,這個鏈表是成倍增長的,在函數grow_slab_list函數中,這個鏈的長度從1變成2,從2變成4,從4變成8……:

CODE: static int grow_slab_list (unsigned int id) { ??? slabclass_t *p = &slabclass[id]; ??? if (p->slabs == p->list_size) { ??????? size_t new_size =? p->list_size ? p->list_size * 2 : 16; ??????? void *new_list = realloc(p->slab_list, new_size*sizeof(void*)); ??????? if (new_list == 0) return 0; ??????? p->list_size = new_size; ??????? p->slab_list = new_list; ??? } ??? return 1; }

在定位item時,都是使用slabs_clsid函數,傳入參數為item大小,返回值為classid,由這個過程可以看出,memcached的第三個內存冗余發生在保存item的過程中,item總是小于或等于chunk大小的,當item小于chunk大小時,就又發生了空間浪費。

◎Memcached的NewHash算法

Memcached的item保存基于一個大的hash表,它的實際地址就是slab中的chunk偏移,但是它的定位是依靠對key做hash的結果,在primary_hashtable中找到的。在assoc.c和items.c中定義了所有的hash和item操作。

Memcached使用了一個叫做NewHash的算法,它的效果很好,效率也很高。1.1和1.2的NewHash有一些不同,主要的實現方式還是一樣的,1.2的hash函數是經過整理優化的,適應性更好一些。

NewHash的原型參考:http://burtleburtle.net/bob/hash/evahash.html。數學家總是有點奇怪,呵呵~

為了變換方便,定義了u4和u1兩種數據類型,u4就是無符號的長整形,u1就是無符號char(0-255)。

具體代碼可以參考1.1和1.2源碼包。

注意這里的hashtable長度,1.1和1.2也是有區別的,1.1中定義了HASHPOWER常量為20,hashtable表長為hashsize(HASHPOWER),就是4MB(hashsize是一個宏,表示1右移n位),1.2中是變量16,即hashtable表長65536:

CODE: typedef? unsigned long? int? ub4;?? /* unsigned 4-byte quantities */ typedef? unsigned?????? char ub1;?? /* unsigned 1-byte quantities */

?

#define hashsize(n) ((ub4)1<<(n)) #define hashmask(n) (hashsize(n)-1)

在assoc_init()中,會對primary_hashtable做初始化,對應的hash操作包括:assoc_find()、assoc_expand()、assoc_move_next_bucket()、assoc_insert()、assoc_delete(),對應于item的讀寫操作。其中assoc_find()是根據key和key長尋找對應的item地址的函數(注意在C中,很多時候都是同時直接傳入字符串和字符串長度,而不是在函數內部做strlen),返回的是item結構指針,它的數據地址在slab中的某個chunk上。

items.c是數據項的操作程序,每一個完整的item包括幾個部分,在item_make_header()中定義為:

key:鍵 nkey:鍵長 flags:用戶定義的flag(其實這個flag在memcached中沒有啟用) nbytes:值長(包括換行符號\r\n) suffix:后綴Buffer nsuffix:后綴長

一個完整的item長度是鍵長+值長+后綴長+item結構大小(32字節),item操作就是根據這個長度來計算slab的classid的。

hashtable中的每一個桶上掛著一個雙鏈表,item_init()的時候已經初始化了heads、tails、sizes三個數組為0,這三個數組的大小都為常量LARGEST_ID(默認為255,這個值需要配合factor來修改),在每次item_assoc()的時候,它會首先嘗試從slab中獲取一塊空閑的chunk,如果沒有可用的chunk,會在鏈表中掃描50次,以得到一個被LRU踢掉的item,將它unlink,然后將需要插入的item插入鏈表中。

注意item的refcount成員。item被unlink之后只是從鏈表上摘掉,不是立刻就被free的,只是將它放到刪除隊列中(item_unlink_q()函數)。

item對應一些讀寫操作,包括remove、update、replace,當然最重要的就是alloc操作。

item還有一個特性就是它有過期時間,這是memcached的一個很有用的特性,很多應用都是依賴于memcached的item過期,比如session存儲、操作鎖等。item_flush_expired()函數就是掃描表中的item,對過期的item執行unlink操作,當然這只是一個回收動作,實際上在get的時候還要進行時間判斷:

CODE: /* expires items that are more recent than the oldest_live setting. */ void item_flush_expired() { ??? int i;? ??? item *iter, *next; ??? if (! settings.oldest_live) ??????? return; ??? for (i = 0; i < LARGEST_ID; i++) { ??????? /* The LRU is sorted in decreasing time order, and an item’s timestamp ???????? * is never newer than its last access time, so we only need to walk ???????? * back until we hit an item older than the oldest_live time. ???????? * The oldest_live checking will auto-expire the remaining items. ???????? */ ??????? for (iter = heads[i]; iter != NULL; iter = next) { ??????????? if (iter->time >= settings.oldest_live) { ??????????????? next = iter->next; ??????????????? if ((iter->it_flags & ITEM_SLABBED) == 0) { ??????????????????? item_unlink(iter); ??????????????? }?????? ??????????? } else { ??????????????? /* We’ve hit the first old item. Continue to the next queue. */ ??????????????? break;? ??????????? }?????? ??????? }?????? ??? } }

?

CODE: /* wrapper around assoc_find which does the lazy expiration/deletion logic */ item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) { ??? item *it = assoc_find(key, nkey); ??? if (delete_locked) *delete_locked = 0; ??? if (it && (it->it_flags & ITEM_DELETED)) { ??????? /* it’s flagged as delete-locked.? let’s see if that condition ?????????? is past due, and the 5-second delete_timer just hasn’t ?????????? gotten to it yet… */ ??????? if (! item_delete_lock_over(it)) { ??????????? if (delete_locked) *delete_locked = 1; ??????????? it = 0; ??????? }?????? ??? } ??? if (it && settings.oldest_live && settings.oldest_live <= current_time && ??????? it->time <= settings.oldest_live) { ??????? item_unlink(it); ??????? it = 0; ??? } ??? if (it && it->exptime && it->exptime <= current_time) { ??????? item_unlink(it); ??????? it = 0; ??? } ??? return it; }

Memcached的內存管理方式是非常精巧和高效的,它很大程度上減少了直接alloc系統內存的次數,降低函數開銷和內存碎片產生幾率,雖然這種方式會造成一些冗余浪費,但是這種浪費在大型系統應用中是微不足道的。

◎Memcached的理論參數計算方式

影響 memcached 工作的幾個參數有:

常量REALTIME_MAXDELTA 60*60*24*30 最大30天的過期時間

conn_init()中的freetotal(=200) 最大同時連接數

常量KEY_MAX_LENGTH 250 最大鍵長

settings.factor(=1.25) factor將影響chunk的步進大小

settings.maxconns(=1024) 最大軟連接

settings.chunk_size(=48) 一個保守估計的key+value長度,用來生成id1中的chunk長度(1.2)。id1的chunk長度等于這個數值加上item結構體的長度(32),即默認的80字節。

常量POWER_SMALLEST 1 最小classid(1.2)

常量POWER_LARGEST 200 最大classid(1.2)

常量POWER_BLOCK 1048576 默認slab大小

常量CHUNK_ALIGN_BYTES (sizeof(void *)) 保證chunk大小是這個數值的整數倍,防止越界(void *的長度在不同系統上不一樣,在標準32位系統上是4)

常量ITEM_UPDATE_INTERVAL 60 隊列刷新間隔

常量LARGEST_ID 255 最大item鏈表數(這個值不能比最大的classid小)

變量hashpower(在1.1中是常量HASHPOWER) 決定hashtable的大小

根據上面介紹的內容及參數設定,可以計算出的一些結果:

1、在memcached中可以保存的item個數是沒有軟件上限的,之前我的100萬的說法是錯誤的。 2、假設NewHash算法碰撞均勻,查找item的循環次數是item總數除以hashtable大小(由hashpower決定),是線性的。 3、Memcached限制了可以接受的最大item是1MB,大于1MB的數據不予理會。 4、Memcached的空間利用率和數據特性有很大的關系,又與DONT_PREALLOC_SLABS常量有關。 在最差情況下,有198個slab會被浪費(所有item都集中在一個slab中,199個id全部分配滿)。

◎Memcached的定長優化

根據上面幾節的描述,多少對memcached有了一個比較深入的認識。在深入認識的基礎上才好對它進行優化。

Memcached本身是為變長數據設計的,根據數據特性,可以說它是“面向大眾”的設計,但是很多時候,我們的數據并不是這樣的“普遍”,典型的情況中,一種是非均勻分布,即數據長度集中在幾個區域內(如保存用戶 Session);另一種更極端的狀態是等長數據(如定長鍵值,定長數據,多見于訪問、在線統計或執行鎖)。

這里主要研究一下定長數據的優化方案(1.2),集中分布的變長數據僅供參考,實現起來也很容易。

解決定長數據,首先需要解決的是slab的分配問題,第一個需要確認的是我們不需要那么多不同chunk長度的slab,為了最大限度地利用資源,最好chunk和item等長,所以首先要計算item長度。

在之前已經有了計算item長度的算法,需要注意的是,除了字符串長度外,還要加上item結構的長度32字節。

假設我們已經計算出需要保存200字節的等長數據。

接下來是要修改slab的classid和chunk長度的關系。在原始版本中,chunk長度和classid是有對應關系的,現在如果把所有的chunk都定為200個字節,那么這個關系就不存在了,我們需要重新確定這二者的關系。一種方法是,整個存儲結構只使用一個固定的id,即只使用199個槽中的1個,在這種條件下,就一定要定義DONT_PREALLOC_SLABS來避免另外的預分配浪費。另一種方法是建立一個hash關系,來從item確定classid,不能使用長度來做鍵,可以使用key的NewHash結果等不定數據,或者直接根據key來做hash(定長數據的key也一定等長)。這里簡單起見,選擇第一種方法,這種方法的不足之處在于只使用一個id,在數據量非常大的情況下,slab鏈會很長(因為所有數據都擠在一條鏈上了),遍歷起來的代價比較高。

前面介紹了三種空間冗余,設置chunk長度等于item長度,解決了第一種空間浪費問題,不預申請空間解決了第二種空間浪費問題,那么對于第一種問題(slab內剩余)如何解決呢,這就需要修改POWER_BLOCK常量,使得每一個slab大小正好等于chunk長度的整數倍,這樣一個slab就可以正好劃分成n個chunk。這個數值應該比較接近1MB,過大的話同樣會造成冗余,過小的話會造成次數過多的alloc,根據chunk長度為200,選擇1000000作為POWER_BLOCK的值,這樣一個slab就是100萬字節,不是1048576。三個冗余問題都解決了,空間利用率會大大提升。

修改 slabs_clsid 函數,讓它直接返回一個定值(比如 1 ):

CODE: unsigned int slabs_clsid(size_t size) { ??????? return 1; }

修改slabs_init函數,去掉循環創建所有classid屬性的部分,直接添加slabclass[1]:

CODE: slabclass[1].size = 200;??????????????? //每chunk200字節 slabclass[1].perslab = 5000;??????? //1000000/200

◎Memcached客戶端

Memcached是一個服務程序,使用的時候可以根據它的協議,連接到memcached服務器上,發送命令給服務進程,就可以操作上面的數據。為了方便使用,memcached有很多個客戶端程序可以使用,對應于各種語言,有各種語言的客戶端。基于C語言的有libmemcache、APR_Memcache;基于Perl的有Cache::Memcached;另外還有Python、Ruby、Java、C#等語言的支持。PHP的客戶端是最多的,不光有mcache和PECL memcache兩個擴展,還有大把的由PHP編寫的封裝類,下面介紹一下在PHP中使用memcached的方法:

mcache擴展是基于libmemcache再封裝的。libmemcache一直沒有發布stable版本,目前版本是1.4.0-rc2,可以在這里找到。libmemcache有一個很不好的特性,就是會向stderr寫很多錯誤信息,一般的,作為lib使用的時候,stderr一般都會被定向到其它地方,比如Apache的錯誤日志,而且libmemcache會自殺,可能會導致異常,不過它的性能還是很好的。

mcache擴展最后更新到1.2.0-beta10,作者大概是離職了,不光停止更新,連網站也打不開了(~_~),只能到其它地方去獲取這個不負責的擴展了。解壓后安裝方法如常:phpize & configure & make & make install,一定要先安裝libmemcache。使用這個擴展很簡單:

CODE: <?php $mc = memcache();??? // 創建一個memcache連接對象,注意這里不是用new! $mc->add_server(‘localhost’, 11211);??? // 添加一個服務進程 $mc->add_server(‘localhost’, 11212);??? // 添加第二個服務進程 $mc->set(‘key1′, ‘Hello’);??? // 寫入key1 => Hello $mc->set(‘key2′, ‘World’, 10);??? // 寫入key2 => World,10秒過期 $mc->set(‘arr1′, array(‘Hello’, ‘World’));??? // 寫入一個數組 $key1 = $mc->get(‘key1′);??? // 獲取’key1′的值,賦給$key1 $key2 = $mc->get(‘key2′);??? // 獲取’key2′的值,賦給$key2,如果超過10秒,就取不到了 $arr1 = $mc->get(‘arr1′);??? // 獲取’arr1′數組 $mc->delete(‘arr1′);??? // 刪除’arr1′ $mc->flush_all();??? // 刪掉所有數據 $stats = $mc->stats();??? // 獲取服務器信息 var_dump($stats);??? // 服務器信息是一個數組 ?>

這個擴展的好處是可以很方便地實現分布式存儲和負載均衡,因為它可以添加多個服務地址,數據在保存的時候是會根據hash結果定位到某臺服務器上的,這也是libmemcache的特性。libmemcache支持集中hash方式,包括CRC32、ELF和Perl hash。

PECL memcache是PECL發布的擴展,目前最新版本是2.1.0,可以在pecl網站得到。memcache擴展的使用方法可以在新一些的PHP手冊中找到,它和mcache很像,真的很像:

CODE: <?php

?

$memcache

= new Memcache; $memcache->connect(‘localhost’, 11211) or die (“Could not connect”);

$version = $memcache->getVersion(); echo “Server’s version: ”.$version.“n”;

$tmp_object = new stdClass; $tmp_object->str_attr = ‘test’; $tmp_object->int_attr = 123;

$memcache->set(‘key’, $tmp_object, false, 10) or die (“Failed to save data at the server”); echo “Store data in the cache (data will expire in 10 seconds)n”;

$get_result = $memcache->get(‘key’); echo “Data from the cache:n”;

var_dump($get_result);

?>

這個擴展是使用php的stream直接連接memcached服務器并通過socket發送命令的。它不像libmemcache那樣完善,也不支持add_server這種分布操作,但是因為它不依賴其它的外界程序,兼容性要好一些,也比較穩定。至于效率,差別不是很大。

另外,有很多的PHP class可以使用,比如MemcacheClient.inc.php,phpclasses.org上可以找到很多,一般都是對perl client API的再封裝,使用方式很像。

◎BSM_Memcache

從C client來說,APR_Memcache是一個很成熟很穩定的client程序,支持線程鎖和原子級操作,保證運行的穩定性。不過它是基于APR的(APR將在最后一節介紹),沒有libmemcache的應用范圍廣,目前也沒有很多基于它開發的程序,現有的多是一些Apache Module,因為它不能脫離APR環境運行。但是APR倒是可以脫離Apache單獨安裝的,在APR網站上可以下載APR和APR-util,不需要有Apache,可以直接安裝,而且它是跨平臺的。

BSM_Memcache是我在BS.Magic項目中開發的一個基于APR_Memcache的PHP擴展,說起來有點拗口,至少它把APR扯進了PHP擴展中。這個程序很簡單,也沒做太多的功能,只是一種形式的嘗試,它支持服務器分組。

和mcache擴展支持多服務器分布存儲不同,BSM_Memcache支持多組服務器,每一組內的服務器還是按照hash方式來分布保存數據,但是兩個組中保存的數據是一樣的,也就是實現了熱備,它不會因為一臺服務器發生單點故障導致數據無法獲取,除非所有的服務器組都損壞(例如機房停電)。當然實現這個功能的代價就是性能上的犧牲,在每次添加刪除數據的時候都要掃描所有的組,在get數據的時候會隨機選擇一組服務器開始輪詢,一直到找到數據為止,正常情況下一次就可以獲取得到。

BSM_Memcache只支持這幾個函數:

CODE: zend_function_entry bsm_memcache_functions[] = { ??? PHP_FE(mc_get,????????? NULL) ??? PHP_FE(mc_set,????????? NULL) ??? PHP_FE(mc_del,????????? NULL) ??? PHP_FE(mc_add_group,??? NULL) ??? PHP_FE(mc_add_server,?? NULL) ??? PHP_FE(mc_shutdown,???? NULL) ??? {NULL, NULL, NULL} };

mc_add_group函數返回一個整形(其實應該是一個object,我偷懶了~_~)作為組ID,mc_add_server的時候要提供兩個參數,一個是組ID,一個是服務器地址(ADDRORT)。

CODE: /** * Add a server group */ PHP_FUNCTION(mc_add_group) { ??? apr_int32_t group_id; ??? apr_status_t rv;

?

??? if (0 != ZEND_NUM_ARGS()) ??? { ??????? WRONG_PARAM_COUNT; ??????? RETURN_NULL(); ??? }

??? group_id = free_group_id(); ??? if (-1 == group_id) ??? { ??????? RETURN_FALSE; ??? }

??? apr_memcache_t *mc; ??? rv = apr_memcache_create(p, MAX_G_SERVER, 0, &mc);

??? add_group(group_id, mc);

??? RETURN_DOUBLE(group_id); }

?

CODE: /** * Add a server into group */ PHP_FUNCTION(mc_add_server) { ??? apr_status_t rv; ??? apr_int32_t group_id; ??? double g; ??? char *srv_str; ??? int srv_str_l;

?

??? if (2 != ZEND_NUM_ARGS()) ??? { ??????? WRONG_PARAM_COUNT; ??? }

??? if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “ds”, &g, &srv_str, &srv_str_l) == FAILURE) ??? { ??????? RETURN_FALSE; ??? }

??? group_id = (apr_int32_t) g;

??? if (-1 == is_validate_group(group_id)) ??? { ??????? RETURN_FALSE; ??? }

??? char *host, *scope; ??? apr_port_t port;

??? rv = apr_parse_addr_port(&host, &scope, &port, srv_str, p); ??? if (APR_SUCCESS == rv) ??? { ??????? // Create this server object ??????? apr_memcache_server_t *st; ??????? rv = apr_memcache_server_create(p, host, port, 0, 64, 1024, 600, &st); ??????? if (APR_SUCCESS == rv) ??????? { ??????????? if (NULL == mc_groups[group_id]) ??????????? { ??????????????? RETURN_FALSE; ??????????? }

??????????? // Add server ??????????? rv = apr_memcache_add_server(mc_groups[group_id], st);

??????????? if (APR_SUCCESS == rv) ??????????? { ??????????????? RETURN_TRUE; ??????????? } ??????? } ??? }

??? RETURN_FALSE; }

在set和del數據的時候,要循環所有的組:

CODE: /** * Store item into all groups */ PHP_FUNCTION(mc_set) { ??? char *key, *value; ??? int key_l, value_l; ??? double ttl = 0; ??? double set_ct = 0;

?

??? if (2 != ZEND_NUM_ARGS()) ??? { ??????? WRONG_PARAM_COUNT; ??? }

??? if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “ss|d”, &key, &key_l, &value, &value_l, ttl) == FAILURE) ??? { ??????? RETURN_FALSE; ??? }

??? // Write data into every object ??? apr_int32_t i = 0; ??? if (ttl < 0) ??? { ??????? ttl = 0; ??? }

??? apr_status_t rv;

??? for (i = 0; i < MAX_GROUP; i++) ??? { ??????? if (0 == is_validate_group(i)) ??????? { ??????????? // Write it! ??????????? rv = apr_memcache_add(mc_groups[i], key, value, value_l, (apr_uint32_t) ttl, 0); ??????????? if (APR_SUCCESS == rv) ??????????? { ??????????????? set_ct++; ??????????? } ??????? } ??? }

??? RETURN_DOUBLE(set_ct); }

在mc_get中,首先要隨機選擇一個組,然后從這個組開始輪詢:

CODE: /** * Fetch a item from a random group */ PHP_FUNCTION(mc_get) {?????????????? ??? char *key, *value = NULL; ??? int key_l; ??? apr_size_t value_l;

?

??? if (1 != ZEND_NUM_ARGS()) ??? { ??????? WRONG_PARAM_COUNT; ??? }

??? if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “s”, &key, &key_l) == FAILURE) ??? { ??????? RETURN_MULL(); ??? } ??? ??? // I will try … ??? // Random read ??? apr_int32_t curr_group_id = random_group(); ??? apr_int32_t i = 0; ??? apr_int32_t try = 0; ??? apr_uint32_t flag; ??? apr_memcache_t *oper; ??? apr_status_t rv;

??? for (i = 0; i < MAX_GROUP; i++) ??? { ??????? try = i + curr_group_id; ??????? try = try % MAX_GROUP; ??????? if (0 == is_validate_group(try)) ??????? { ??????????? // Get a value ??????????? oper = mc_groups[try]; ??????????? rv = apr_memcache_getp(mc_groups[try], p, (const char *) key, &value, &value_l, 0); ??????????? if (APR_SUCCESS == rv) ??????????? { ??????????????? RETURN_STRING(value, 1); ??????????? } ??????? } ??? }

??? RETURN_FALSE; }

?

CODE: /** * Random group id * For mc_get() */ apr_int32_t random_group() { ??? struct timeval tv; ??? struct timezone tz; ??? int usec;

?

??? gettimeofday(&tv, &tz);

??? usec = tv.tv_usec;

??? int curr = usec % count_group();

??? return (apr_int32_t) curr; }

BSM_Memcache的使用方式和其它的client類似:

CODE: <?php $g1 = mc_add_group();??? // 添加第一個組 $g2 = mc_add_group();??? // 添加第二個組 mc_add_server($g1, ‘localhost:11211′);??? // 在第一個組中添加第一臺服務器 mc_add_server($g1, ‘localhost:11212′);??? // 在第一個組中添加第二臺服務器 mc_add_server($g2, ‘10.0.0.16:11211′);??? // 在第二個組中添加第一臺服務器 mc_add_server($g2, ‘10.0.0.17:11211′);??? // 在第二個組中添加第二臺服務器

?

?

mc_set(‘key’, ‘Hello’);??? // 寫入數據 $key = mc_get(‘key’);??? // 讀出數據 mc_del(‘key’);??? // 刪除數據 mc_shutdown();??? // 關閉所有組 ?>

?

APR_Memcache的相關資料可以在這里找到,BSM_Memcache可以在本站下載。

◎APR環境介紹

APR的全稱:Apache Portable Runtime。它是Apache軟件基金會創建并維持的一套跨平臺的C語言庫。它從Apache httpd1.x中抽取出來并獨立于httpd之外,Apache httpd2.x就是建立在APR上。APR提供了很多方便的API接口可供使用,包括如內存池、字符串操作、網絡、數組、hash表等實用的功能。開發Apache2 Module要接觸很多APR函數,當然APR可以獨立安裝獨立使用,可以用來寫自己的應用程序,不一定是Apache httpd的相關開發。

◎后記

這是我在農歷丙戌年(我的本命年)的最后一篇文章,由于Memcached的內涵很多,倉促整理一定有很多遺漏和錯誤。感謝新浪網提供的研究機會,感謝部門同事的幫助。

NP博士 02-13-2007

原文發表于:http://www.54np.com/ 轉載請注明

轉載于:https://www.cnblogs.com/kaizokuo/archive/2012/02/02/2335378.html

總結

以上是生活随笔為你收集整理的memcache的深度解析(转)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

天天操操操操操操 | 国产91在线观 | 四虎免费在线观看 | 夜夜骑天天操 | 日韩免费二区 | 久久免费视频7 | 91亚洲国产成人久久精品网站 | 91人人澡人人爽人人精品 | 国产高h视频 | 亚洲免费av片 | 高清在线一区二区 | 99精品热视频 | 96久久欧美麻豆网站 | 美女网站免费福利视频 | 欧美日韩xx | 国产成人黄色av | 中午字幕在线观看 | 欧美性大胆 | 欧美成a人片在线观看久 | 91精品少妇偷拍99 | 99久久婷婷国产精品综合 | 国产免费区 | 日日夜夜艹 | 免费福利影院 | 亚洲一区二区视频在线播放 | 成人a在线观看高清电影 | 欧美人牲| 免费成人黄色av | 青青河边草免费视频 | 超碰在线观看99 | 少妇自拍av | 久久少妇av| 五月婷婷在线综合 | a视频免费在线观看 | 亚洲高清精品在线 | 欧美va天堂va视频va在线 | 欧美aaaxxxx做受视频 | 91香蕉视频黄色 | 中文字幕在线一区二区三区 | av不卡中文字幕 | 国产中文字幕在线视频 | 国内视频在线观看 | 午夜久久久久 | 国产精品青草综合久久久久99 | 四虎国产免费 | 免费看的黄色片 | 国产午夜精品免费一区二区三区视频 | 99热官网| 97超级碰碰 | 国产丝袜网站 | 国产永久免费观看 | 色婷婷久久久综合中文字幕 | 免费视频久久 | 日韩高清片 | 久草久热| 亚洲一区视频免费观看 | 色中色亚洲 | 不卡视频一区二区三区 | 久久综合婷婷综合 | 久久久久久久久久久久久国产精品 | 国产高清视频在线观看 | 91精品一区国产高清在线gif | 欧美日韩免费观看一区二区三区 | 中文字幕在线看片 | 香蕉影视在线观看 | 国产精品激情偷乱一区二区∴ | av免费试看 | 国产免费a| 99在线热播 | 国产69熟| 欧美日韩一区二区三区视频 | 香蕉视频18 | 伊人在线视频 | 久久精品中文字幕少妇 | 天天操天天能 | 精品视频免费 | 黄污网站在线 | 超碰人人国产 | 91刺激视频| 成人一区二区三区在线 | 91av视频导航 | 久久久精品亚洲 | 久久99精品国产91久久来源 | 黄污在线看 | 在线观看韩日电影免费 | 日韩av男人的天堂 | 日韩精品在线免费播放 | 成人av在线影视 | 国产九色91 | 日本久久不卡视频 | 国产精品免费观看久久 | 国产一级视频在线免费观看 | 91亚洲精品乱码久久久久久蜜桃 | 久久亚洲视频 | 久久九九精品久久 | 182午夜在线观看 | 亚洲日本黄色 | 欧美日韩高清免费 | av中文字幕网 | 国产精品国产三级国产 | av在线免费观看黄 | 丁香九月婷婷综合 | 中文av影院 | 欧美日本高清视频 | 色资源中文字幕 | 精品视频久久 | 521色香蕉网站在线观看 | 欧美a级片网站 | 天天操天天操天天操天天 | 色婷婷综合五月 | 国产综合91 | 玖玖色在线观看 | 亚洲精品免费在线播放 | 国产自制av| www激情com| 久久久久久久久久网 | 午夜电影中文字幕 | 国产va饥渴难耐女保洁员在线观看 | 青草视频在线看 | 日本性高潮视频 | 久久永久免费视频 | 国产精品免费看久久久8精臀av | 免费看污污视频的网站 | 日韩精品一区二区久久 | 国产在线黄| 国产精品成人自产拍在线观看 | 少妇bbw搡bbbb搡bbb | 久久精品免费电影 | 日韩av五月天 | 国产美女视频免费 | 国产高清在线 | 国产啊v在线观看 | 免费毛片一区二区三区久久久 | 久久天堂精品视频 | 天天干天天操天天入 | 在线看片日韩 | 欧美黄色成人 | 久久久www免费电影网 | 国产精品久久久久久久久大全 | www免费看| 99久久精品国产网站 | 成人九九视频 | 日韩欧美一二三 | 国产欧美精品xxxx另类 | 综合五月 | 看av免费网站 | 99精品在线免费在线观看 | 久久人网 | 国产黄色片免费看 | 色综合久久中文字幕综合网 | 天天干天天操天天爱 | 午夜精品视频在线 | 日韩美女黄色片 | 日本久久免费电影 | 91精品国产91p65 | 91麻豆免费看 | 亚洲人久久久 | 国产精品观看在线亚洲人成网 | 亚洲激情视频在线 | 久久精品国产精品亚洲精品 | 精品a在线 | 亚洲精品视频在线观看免费视频 | 日韩在线电影观看 | 亚洲91网站 | av解说在线 | 亚洲 欧洲av| 日批网站免费观看 | 天天射天天艹 | 九九久久国产精品 | 婷婷在线精品视频 | 黄色精品国产 | 狠狠干成人综合网 | 免费视频xnxx com| 2022久久国产露脸精品国产 | 日韩精品一区二区三区免费观看视频 | 欧美日韩在线视频免费 | 狠狠躁日日躁夜夜躁av | 国产亚洲视频在线免费观看 | 成人91av| 国产成人av综合色 | 国产美女搞久久 | 亚洲精品黄色在线观看 | 国产精品成人一区二区 | 丝袜少妇在线 | 精品资源在线 | 五月天丁香 | 91精品国产麻豆国产自产影视 | 亚洲国产成人精品电影在线观看 | 性色xxxxhd| www日韩在线观看 | 亚洲美女在线一区 | 中文字幕不卡在线88 | 久久综合九色综合久99 | 国产不卡在线观看 | 波多野结衣视频一区二区三区 | 亚洲成熟女人毛片在线 | 中文字幕在线播放av | 国产精品一区在线观看 | 五月综合色婷婷 | 99久久99久久精品 | 91完整版在线观看 | 在线观看国产 | 久久精品79国产精品 | 国产精品久久久久久99 | 色伊人网| 久久夜色精品国产欧美一区麻豆 | 亚洲va天堂va欧美ⅴa在线 | 国产精品99久久久久久久久 | 水蜜桃亚洲一二三四在线 | 日韩高清网站 | 成人性生交大片免费观看网站 | 国产成本人视频在线观看 | 97视频资源 | 国产成人61精品免费看片 | 成年人三级网站 | 精品免费视频 | 久久免费视频这里只有精品 | 999热线在线观看 | 午夜精品久久久久久 | 欧美人操人 | 91看片淫黄大片91 | 国产综合精品一区二区三区 | 免费韩国av| av在线网站观看 | 草久电影| 亚洲日韩精品欧美一区二区 | 国产精品久久久精品 | 日韩av在线免费播放 | 亚洲高清视频在线观看免费 | 亚洲不卡在线 | 欧美日韩中文字幕在线视频 | 国产一区二区高清不卡 | 97视频在线免费播放 | 国产又粗又猛又爽又黄的视频先 | 日韩av影视 | 免费国产在线视频 | 亚洲伊人天堂 | 日本激情中文字幕 | 麻豆影视网 | 丁香午夜| 91久久人澡人人添人人爽欧美 | 国产日韩欧美视频在线观看 | 欧美日本在线视频 | 亚洲jizzjizz日本少妇 | 亚洲视频电影在线 | 国产精品视频在线看 | 精品一区二区三区在线播放 | 久久99精品一区二区三区三区 | 91片黄在线观看动漫 | 在线免费视频a | 狠狠网亚洲精品 | 午夜视频一区二区三区 | 在线观看小视频 | 精品一区二区在线免费观看 | 亚洲2019精品 | 国产91在线 | 美洲 | 久久久天天操 | 日韩欧美国产精品 | 在线观看国产永久免费视频 | 99re8这里有精品热视频免费 | 欧美一级裸体视频 | 8x成人免费视频 | 久久国产高清视频 | 日p视频 | 日本中文字幕视频 | 国产精品毛片久久久久久久久久99999999 | 日韩精品一区二区三区高清免费 | 免费又黄又爽视频 | 麻豆一二 | 久久手机免费观看 | av再线观看 | 亚洲春色奇米影视 | 久久久久在线 | 51久久成人国产精品麻豆 | 久久国产高清 | av黄色成人 | 91精品视频免费看 | 成人aaa毛片 | 天天天色综合 | 久久久一本精品99久久精品66 | 亚洲国产日韩在线 | 91精品啪 | 欧美性超爽 | 欧美日韩在线观看不卡 | 99热最新地址 | 欧美成a人片在线观看久 | 福利视频导航网址 | 中文字幕精品视频 | 欧美一级日韩三级 | 丝袜美腿一区 | 久99久视频 | 91禁在线观看 | 久久精品国产成人 | 最新日韩在线观看视频 | 精品久久久久久久久中文字幕 | 99色精品视频 | 黄色免费网站大全 | 天天av在线播放 | 欧美国产日韩激情 | 波多野结衣视频在线 | 婷婷成人在线 | 国产美女视频网站 | 天天操夜夜逼 | 成人影片在线播放 | 久久久精品一区二区三区 | 91高清不卡 | 97色综合| 深爱婷婷激情 | 久要激情网 | 五月天丁香 | 国产美女网站视频 | 亚洲精品系列 | 亚洲做受高潮欧美裸体 | 丁香网五月天 | 777视频在线观看 | 激情丁香久久 | 日本中文字幕电影在线免费观看 | 奇米影视8888在线观看大全免费 | 国产一级片在线播放 | 玖玖999| 99r在线 | 精品毛片一区二区免费看 | 伊人久久国产 | 久久久国产一区 | www.日本色| 久久在线免费视频 | 一区二区三区日韩精品 | 国产精品成人av久久 | 97国产超碰 | 美女网站色 | 97在线视频观看 | 91麻豆精品国产91久久久久久久久 | 久草免费看| 91在线超碰 | 亚洲黄色高清 | www.少妇 | 成 人 黄 色 片 在线播放 | 精品久久亚洲 | 亚洲成人黄 | 免费看一级片 | 91毛片在线观看 | 亚洲精品免费在线视频 | 午夜av色| 亚洲区视频在线观看 | 在线观看日本韩国电影 | 成人av.com| 久久免费视频这里只有精品 | 综合五月婷婷 | 热久久这里只有精品 | 黄色毛片大全 | 成人羞羞视频在线观看免费 | 91精品视频免费在线观看 | 国产成人久久77777精品 | 四虎在线永久免费观看 | 国产精品久久久久高潮 | 成人app在线免费观看 | 国产午夜亚洲精品 | 99精品国产在热久久 | 国产一区二区三区高清播放 | 五月丁色 | 操操操干干干 | 91片在线观看 | 亚洲成人频道 | 久久国产热 | 亚洲激情综合网 | 色多多污污 | 91重口视频 | 成人毛片100免费观看 | 欧美一区二区精美视频 | 在线观看国产日韩 | 国产黄网在线 | 激情在线网址 | 在线黄色免费 | 日本在线观看中文字幕 | 国产美女精品人人做人人爽 | 在线色亚洲 | 波多野结衣资源 | 国产字幕在线播放 | 久久综合影院 | 97色综合| 国产福利小视频在线 | 狠狠色丁香婷婷综合 | 91久久精品一区 | 国产精品自在欧美一区 | 国产精品免费视频一区二区 | 日本性xxx| 精品国产一区二区三区久久久蜜臀 | 国产主播99| 国产不卡av在线 | 久久99久久99精品 | 国产日产av | 99视屏 | 综合久久久久久久久 | 国内精品小视频 | 国产精品国产三级国产不产一地 | 99久久婷婷国产精品综合 | 日韩sese | 91精品国 | 午夜在线免费视频 | 蜜桃久久久| 久久精品1区 | 91精品国产一区 | 久草视频在 | 91看片一区二区三区 | 久久麻豆视频 | 美女天天操 | 狠狠操狠狠 | 黄色大全免费网站 | 国产精彩视频一区 | 五月天亚洲精品 | 久久久久9999亚洲精品 | 91福利视频久久久久 | 美女黄网久久 | 午夜性色| 国产日韩三级 | 国内精品久久久久影院一蜜桃 | 国产成人黄色av | 中日韩三级视频 | 黄色a大片| 国产福利91精品 | 超碰在线公开 | 日韩av高清在线观看 | 最新av网址在线观看 | av一级片在线观看 | 欧美久久久久久久久久 | 午夜美女福利直播 | 中文字幕第一页av | 欧美激情精品 | 九九九九热精品免费视频点播观看 | 国产91区 | 在线视频 亚洲 | 丁香花在线观看视频在线 | 日韩久久久久久久 | 久久久久久精 | av九九九 | www.69xx| 久久av在线播放 | 一区二区三区日韩在线 | 97超碰人人澡人人爱 | 91麻豆精品| 免费午夜av | 久久久99精品免费观看app | 五月综合网 | 天天色综合三 | 成人毛片在线视频 | 成人久久久精品国产乱码一区二区 | 日韩a在线观看 | 国产在线观看你懂的 | 久久国产精品99久久久久 | 丰满少妇在线观看 | 欧美日韩高清在线观看 | 亚洲人在线7777777精品 | 久久天堂网站 | 日韩有码欧美 | 免费a现在观看 | 欧美男同网站 | av在线播放中文字幕 | 日韩美女免费线视频 | 精品在线视频一区二区三区 | www激情com | 国产精品热视频 | 国产精品久久久久久吹潮天美传媒 | 亚洲精品午夜久久久久久久久久久 | 日韩精品一区二区不卡 | 在线观看中文字幕视频 | 亚洲高清资源 | 日韩影视大全 | 欧美精品三级在线观看 | 久久久免费观看 | 在线观看黄色免费视频 | 国产午夜一级毛片 | 五月天狠狠操 | 久久午夜视频 | 中文字幕免费一区二区 | 黄色成人影视 | 99热这里只有精品国产首页 | av免费线看 | 一级免费片 | 六月丁香综合网 | 成人av一区二区在线观看 | 99久久精品国产免费看不卡 | 亚洲综合成人专区片 | 999久久久免费精品国产 | 99免在线观看免费视频高清 | 精品国产a | 免费观看91视频大全 | 超碰人人做 | 国产免费美女 | 日日夜夜免费精品 | 看v片 | 久久夜色精品国产欧美乱极品 | 青青啪 | 国产免费不卡 | 五月天色婷婷丁香 | 高清在线观看av | 亚洲自拍偷拍色图 | 久久成人免费电影 | 国产成人免费观看久久久 | 久青草国产在线 | 四虎在线观看视频 | 视频在线91 | 99热亚洲精品 | 国产高清不卡一区二区三区 | 久久久久久久久久久成人 | 高清av中文字幕 | 日韩大片在线看 | 九九九九热精品免费视频点播观看 | 毛片永久免费 | 亚洲国产精品久久久久久 | 日韩在线 一区二区 | 国产人成看黄久久久久久久久 | 久久中文字幕导航 | 欧美另类调教 | 国产精品免费视频网站 | 午夜婷婷在线观看 | 久久久久久毛片精品免费不卡 | 麻豆精品在线 | 手机看国产毛片 | 日日干天夜夜 | 国产精品麻豆91 | 在线观看中文字幕dvd播放 | 久久久在线 | 久久久久二区 | 亚洲最新毛片 | 毛片永久新网址首页 | 国产成人333kkk | 探花视频在线观看 | 97在线视频免费 | 国产午夜亚洲精品 | 97看片吧| 啪一啪在线 | 免费的国产精品 | 日韩精品在线视频免费观看 | 欧美日比视频 | 色播六月天 | 欧美夫妻生活视频 | 公与妇乱理三级xxx 在线观看视频在线观看 | 国产精品99久久久久的智能播放 | av在线播放快速免费阴 | 亚洲国产中文在线观看 | 91综合视频在线观看 | 91精品区 | 欧美综合干 | 国产视频精品视频 | 免费在线一区二区三区 | 免费a级毛片在线看 | 中文字幕在线高清 | 午夜久操 | 亚洲国产精品久久久 | 99久久精品国产观看 | 香蕉视频在线网站 | 97伊人网| 91天堂影院 | 九九99| 久久久91精品国产一区二区精品 | 天天爱天天 | 高清视频一区二区三区 | 天天操天天色天天 | av免费观看网站 | 最近中文字幕大全 | 日本精品视频免费观看 | 久久深爱网 | 亚洲视频精选 | 黄色大全视频 | 日韩精品91偷拍在线观看 | 激情综合五月网 | 日韩| 天天爽天天摸 | 国产免费资源 | 成年人免费电影在线观看 | 国产成人99av超碰超爽 | 国产日产欧美在线观看 | 国产一级久久久 | 久久免费的精品国产v∧ | 午夜 免费 | 日本中文字幕在线一区 | 日韩欧美一区二区在线观看 | 91毛片在线| 欧美精品日韩 | 97超碰总站 | 亚州欧美精品 | 99久久999久久久精玫瑰 | 久久国产精品区 | 亚洲在线激情 | 夜夜爽www | 91麻豆免费看 | 日本中文字幕在线观看 | 91视频-88av | 夜夜摸夜夜爽 | 免费99精品国产自在在线 | 波多野结衣视频在线 | 中文字幕在线国产 | 亚洲国产美女久久久久 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | 国产精品久久久区三区天天噜 | 国产亚洲欧美在线视频 | 四川妇女搡bbbb搡bbbb搡 | 日韩二区三区 | 国产一级片免费视频 | www亚洲国产| 17videosex性欧美 | 国产精品久久 | 久热国产视频 | 在线日本v二区不卡 | 日本精品在线看 | 日本成人中文字幕在线观看 | 欧美性免费 | 视频在线99re | 欧美一区二区三区在线播放 | 97在线观看免费高清完整版在线观看 | 天天射天天拍 | 国产精品乱码在线 | jizzjizzjizz亚洲 | 中文字幕大全 | 国产亚洲精品久久19p | 天天爱天天射 | 国产精品video爽爽爽爽 | 97超碰人人 | 中文字幕在线观看视频一区二区三区 | 91精品视屏| 亚洲成人网av | 91粉色视频 | 97在线视频免费看 | 精品国产久| 久草在线视频在线 | 免费激情在线电影 | 日韩精品一区二区三区免费观看 | 在线视频观看亚洲 | 欧美一级特黄aaaaaa大片在线观看 | 一本一道久久a久久综合蜜桃 | 日韩激情免费视频 | 视频在线91 | 99久久毛片| 在线观看网站你懂的 | 五月婷婷在线观看 | 97福利| 久久久96| 日日日网| 国产在线1区 | 视频二区 | 久久久久中文字幕 | 精品国产99 | 免费看黄网站在线 | 岛国片在线 | 国产精品免费视频网站 | 伊人天堂网 | 国产最新在线观看 | 久久精品美女视频网站 | 99这里都是精品 | 日韩电影在线观看中文字幕 | 婷婷六月中文字幕 | 国产小视频在线免费观看视频 | 欧美午夜激情网 | 99国产精品久久久久久久久久 | 日韩精品一区不卡 | 美女视频黄是免费的 | 91看片在线播放 | 亚洲欧美怡红院 | 成人av一级片 | 国产精品24小时在线观看 | 久久成人一区二区 | 欧美日韩精品免费观看 | 97超碰在线久草超碰在线观看 | 国产视频精选 | 99精品视频免费看 | 在线亚州 | 99精品视频在线播放观看 | 国产精品区二区三区日本 | 免费v片| 国产在线91在线电影 | 综合网中文字幕 | 在线观看不卡视频 | www黄在线 | av免费观看高清 | 欧美久久久久 | 99久久久久国产精品免费 | 国产中文字幕在线 | 成年人免费电影在线观看 | 久久午夜网 | 日本狠狠干| 色婷婷久久一区二区 | 亚洲永久精品在线 | 美国三级黄色大片 | 亚洲精品一区二区三区新线路 | 精品国内 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 免费日韩 精品中文字幕视频在线 | 欧美中文字幕久久 | 精品国产一区二区三区四区在线观看 | 2022国产精品视频 | 国产美女搞久久 | 亚洲无吗av | 在线观看国产成人av片 | 色噜噜狠狠狠狠色综合 | 久久国内精品视频 | 国产小视频你懂的在线 | 国产免费久久 | 精品一区二区电影 | 啪啪凸凸 | 欧美日韩国产在线 | 精品国产乱码一区二区三区在线 | 久久都是精品 | 亚洲精品免费在线 | 成人动漫精品一区二区 | 久草视频免费看 | 中文字幕免费观看 | 天天操操 | 久久国产手机看片 | 美女视频又黄又免费 | 黄色av电影网 | 亚洲色综合 | 久操久 | 349k.cc看片app| 国产精品专区h在线观看 | 中文字幕视频播放 | 91专区在线观看 | 久久视频在线观看免费 | 超碰在线观看97 | 国产精品 中文字幕 亚洲 欧美 | 在线观看中文字幕dvd播放 | 9999亚洲| 亚洲欧美怡红院 | 极品国产91在线网站 | 亚洲色图 校园春色 | 日韩免费观看高清 | 特片网久久 | 欧美日韩视频精品 | 国产原创在线 | av看片在线| 亚洲aⅴ免费在线观看 | 久久久久区 | 亚洲五月婷婷 | 色天天天 | 国产无吗一区二区三区在线欢 | 99超碰在线播放 | 视频国产一区二区三区 | 日日夜夜免费精品视频 | 久久免费看视频 | 日韩精品在线免费观看 | 日本三级在线观看中文字 | 黄色中文字幕在线 | 精品一区二区精品 | 日韩一级黄色av | av在线免费观看网站 | 久久久久久国产精品免费 | 婷婷综合av | 黄色h在线观看 | 手机成人在线 | 日日婷婷夜日日天干 | 最新av免费在线观看 | 一级免费看 | 中国精品少妇 | 深爱激情站 | 三级动图 | 亚洲国产中文字幕在线 | 日韩成人免费电影 | www.久久精品视频 | se婷婷| 日日爽天天爽 | 久久永久视频 | 中文字幕永久在线 | 亚洲一区av | 91在线观看视频网站 | 久久免费视频一区 | 99精品视频观看 | 在线观看视频 | 午夜精品99久久免费 | 五月婷婷一级片 | 国产精品日韩精品 | 国产一区二区三区免费视频 | 国产一区黄色 | 精品影院| 亚洲日日日 | 97视频资源| 亚洲欧美一区二区三区孕妇写真 | 午夜影院一区 | 色婷婷亚洲综合 | 久久久久久久久久久网 | 黄色一级大片在线观看 | 99久久精品国产亚洲 | 日本中文字幕电影在线免费观看 | 日韩国产欧美在线视频 | 久久精品直播 | 五月激情亚洲 | 911久久| 国产精品99久久久久人中文网介绍 | 婷婷六月激情 | 在线观看国产一区 | 国产精品网在线观看 | 欧美一性一交一乱 | 亚洲黄色成人网 | 国产亚洲一区二区在线观看 | 一区二区三区免费在线观看视频 | 久久久九色精品国产一区二区三区 | 91av视频在线观看免费 | 国内精品在线一区 | 丁香花中文在线免费观看 | 中文字幕亚洲国产 | 日本久久视频 | 久久大片 | 久久久人人人 | 亚洲婷婷综合色高清在线 | 午夜99| 99精品视频在线看 | av成人免费观看 | 91久色蝌蚪 | 久久99热久久99精品 | 狠狠色噜噜狠狠狠 | 国产精品毛片一区二区在线看 | 国产精品毛片一区二区在线看 | www.亚洲黄色 | 九九九热精品免费视频观看网站 | 国产精品99久久久久久人免费 | 91在线视频免费91 | www.狠狠色.com | 免费观看国产视频 | 日韩欧美视频免费观看 | 中文字幕在线观看视频免费 | 中文字幕在线观看三区 | 免费观看一区二区三区视频 | 日韩在线小视频 | 亚洲成aⅴ人片久久青草影院 | 午夜久久久久 | 亚洲专区免费观看 | 日韩理论片在线 | 91香蕉久久 | 狠狠操.com| 亚洲激情av | 四虎国产精品成人免费影视 | 久草热久草视频 | 操操操日日| 超碰资源在线 | 国产小视频免费观看 | 色综合a | 99亚洲国产| 亚洲第五色综合网 | 狠狠狠狠狠狠干 | www视频在线免费观看 | 日韩欧美精品免费 | 久久精品久久99精品久久 | 国精产品999国精产品视频 | 天天操综合网站 | 国产涩涩网站 | 啪啪精品| 国产成人亚洲在线电影 | 日韩精品免费在线播放 | 亚洲人成精品久久久久 | 久久精品视频日本 | 综合激情 | 精品黄色在线观看 | 久久99久久99免费视频 | 三上悠亚一区二区在线观看 | 精品免费国产一区二区三区四区 | 国产午夜精品福利视频 | 亚洲欧美国产精品va在线观看 | 日韩成人精品一区二区三区 | 亚洲黄色在线观看 | 亚洲黄色成人av | av 一区 二区 久久 | 91综合视频在线观看 | 一区 二区电影免费在线观看 | 中文字幕日本特黄aa毛片 | 国产精品久久久久久久久婷婷 | 黄色1级毛片 | 国产精品国产三级在线专区 | 成人三级视频 | 中文字幕乱码电影 | 色婷婷亚洲 | 极品久久久久久久 | 欧美久久久久久久久久久久久 | 久久草在线免费 | 999抗病毒口服液 | 欧美日比视频 | 啪啪资源 | 黄网站app在线观看免费视频 | 2024av| 欧美日韩在线免费视频 | 欧美a视频在线观看 | 天天操综合 | 91精品国产99久久久久久久 | 久久精品一区二区三区视频 | 欧美淫视频 | 成人av直播 | 91喷水 | 日韩电影中文,亚洲精品乱码 | 波多野结衣久久精品 | 91麻豆精品国产91久久久使用方法 | 亚洲国产美女精品久久久久∴ | 一区二区欧美在线观看 | 国产一二三在线视频 | 亚洲永久国产精品 | 99九九热只有国产精品 | 精品96久久久久久中文字幕无 | 91精品视频免费在线观看 | 97色资源 | 欧美在线视频第一页 | 91精品在线免费 | 国产精品不卡视频 | 韩国一区视频 | 成人cosplay福利网站 | 精品96久久久久久中文字幕无 | 久久这里精品视频 | 免费毛片一区二区三区久久久 | 久久精品美女视频网站 | 国产一区二区三区午夜 | 九九久久久久久久久激情 | 福利视频导航网址 | 欧美激情视频在线观看免费 | 91在线视频免费播放 | 国产美女精品人人做人人爽 | 欧美性高跟鞋xxxxhd | 亚洲一区精品二人人爽久久 | 婷婷夜夜 | 亚洲mv大片欧洲mv大片免费 | 婷婷色五| 国外av在线| 美女网站在线播放 | 手机成人av | 中文字幕精品一区二区三区电影 | 人人狠狠综合久久亚洲婷 | av理论电影| 一区二区三区中文字幕在线观看 | 国产九色91 | 97小视频 | 91欧美精品| 91av在线国产 | 欧美巨乳网 | 在线免费观看视频一区 | 色综合色综合久久综合频道88 | 麻豆精品传媒视频 | 成人亚洲网| 欧美日韩在线观看一区二区三区 | 美女在线免费观看视频 | 日韩精品欧美精品 | 欧美日韩在线观看一区 | 久久艹免费 | 久久黄色a级片 | 人人爽人人射 | 成人羞羞视频在线观看免费 | 久久麻豆精品 | 91精品综合在线观看 | 98超碰在线 | 97在线视频免费看 | av成人免费 | 国产高清在线精品 | 国精产品满18岁在线 | 久久国产电影 | 韩日视频在线 | 在线免费观看国产视频 | 色99久久 | 日本精品视频一区 | 亚洲欧美日韩国产一区二区 | 成人免费精品 | 国色天香在线 | 天堂va欧美va亚洲va老司机 | 999精品视频 | 色综合久久综合中文综合网 | 欧美日韩国产高清视频 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 久久人人爽人人 | 久久久精品亚洲 | 91在线91拍拍在线91 | 免费在线观看av网址 | 色橹橹欧美在线观看视频高清 | 视频一区二区在线观看 | 日韩激情中文字幕 | 免费午夜视频在线观看 | 精品一区二区影视 | 91视频com| 国产精品久久久久久久久久久久午夜 | 丝袜美女视频网站 | 欧美一级片在线 | 国产精品av免费观看 | 成人蜜桃视频 | 怡红院av久久久久久久 | av电影免费在线看 | 天天综合区 | 五月开心婷婷 | 日韩av电影中文字幕 | 在线不卡的av | 国产综合精品一区二区三区 | 天天曰| 欧美肥妇free | 色九九在线 | 天天干亚洲| 欧美日韩在线电影 | 亚洲精品午夜国产va久久成人 | 国精产品999国精产 久久久久 | 99tvdz@gmail.com| 日韩av电影中文字幕 | 久久久久久久久久久影院 | 久久久国产精品亚洲一区 | 国产精品成人久久久 | 国产精品久久人 | 日韩伦理片hd| 91久久国产自产拍夜夜嗨 | 天无日天天操天天干 | 久草网站在线观看 |