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

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

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

    jojo's blog--快樂憂傷都與你同在
    為夢想而來,為自由而生。 性情若水,風起水興,風息水止,故時而激蕩,時又清平……
    posts - 11,  comments - 30,  trackbacks - 0

    1. memcached的基礎


    翻譯一篇技術評論社的文章,是講memcached的連載。fcicq同學說這個東西很有用,希望大家喜歡。

    發表日:2008/7/2
    作者:長野雅廣(Masahiro Nagano)
    原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0001

    我是mixi株式會社開發部系統運營組的長野。 日常負責程序的運營。從今天開始,將分幾次針對最近在Web應用的可擴展性領域 的熱門話題memcached,與我公司開發部研究開發組的前坂一起, 說明其內部結構和使用。

    memcached是什么?

    memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 為首開發的一款軟件。現在已成為 mixihatenaFacebookVox、LiveJournal等眾多服務中 提高Web應用擴展性的重要因素。

    許多Web應用都將數據保存到RDBMS中,應用服務器從中讀取數據并在瀏覽器中顯示。 但隨著數據量的增大、訪問的集中,就會出現RDBMS的負擔加重、數據庫響應惡化、 網站顯示延遲等重大影響。

    這時就該memcached大顯身手了。memcached是高性能的分布式內存緩存服務器。 一般的使用目的是,通過緩存數據庫查詢結果,減少數據庫訪問次數,以提高動態Web應用的速度、 提高可擴展性。


    圖1 一般情況下memcached的用途

    memcached的特征

    memcached作為高速運行的分布式緩存服務器,具有以下的特點。

    • 協議簡單
    • 基于libevent的事件處理
    • 內置內存存儲方式
    • memcached不互相通信的分布式

    協議簡單

    memcached的服務器客戶端通信并不使用復雜的XML等格式, 而使用簡單的基于文本行的協議。因此,通過telnet 也能在memcached上保存數據、取得數據。下面是例子。

    $ telnet localhost 11211

    Trying 127.0.0.1...

    Connected to localhost.localdomain (127.0.0.1).

    Escape character is '^]'.

    set foo 0 0 3 (保存命令)

    bar (數據)

    STORED (結果)

    get foo (取得命令)

    VALUE foo 0 3 (數據)

    bar (數據)

    協議文檔位于memcached的源代碼內,也可以參考以下的URL。

    基于libevent的事件處理

    libevent是個程序庫,它將Linux的epoll、BSD類操作系統的kqueue等事件處理功能 封裝成統一的接口。即使對服務器的連接數增加,也能發揮O(1)的性能。 memcached使用這個libevent庫,因此能在Linux、BSD、Solaris等操作系統上發揮其高性能。 關于事件處理這里就不再詳細介紹,可以參考Dan Kegel的The C10K Problem。

    內置內存存儲方式

    為了提高性能,memcached中保存的數據都存儲在memcached內置的內存存儲空間中。 由于數據僅存在于內存中,因此重啟memcached、重啟操作系統會導致全部數據消失。 另外,內容容量達到指定值之后,就基于LRU(Least Recently Used)算法自動刪除不使用的緩存。 memcached本身是為緩存而設計的服務器,因此并沒有過多考慮數據的永久性問題。 關于內存存儲的詳細信息,本連載的第二講以后前坂會進行介紹,請屆時參考。

    memcached不互相通信的分布式

    memcached盡管是“分布式”緩存服務器,但服務器端并沒有分布式功能。 各個memcached不會互相通信以共享信息。那么,怎樣進行分布式呢? 這完全取決于客戶端的實現。本連載也將介紹memcached的分布式。


    圖2 memcached的分布式

    接下來簡單介紹一下memcached的使用方法。

    安裝memcached

    memcached的安裝比較簡單,這里稍加說明。

    memcached支持許多平臺。

    • Linux
    • FreeBSD
    • Solaris (memcached 1.2.5以上版本)
    • Mac OS X

    另外也能安裝在Windows上。這里使用Fedora Core 8進行說明。

    memcached的安裝

    運行memcached需要本文開頭介紹的libevent庫。Fedora 8中有現成的rpm包, 通過yum命令安裝即可。

    $ sudo yum install libevent libevent-devel

    memcached的源代碼可以從memcached網站上下載。本文執筆時的最新版本為1.2.5。 Fedora 8雖然也包含了memcached的rpm,但版本比較老。因為源代碼安裝并不困難, 這里就不使用rpm了。

    memcached安裝與一般應用程序相同,configure、make、make install就行了。

    $ wget http://www.danga.com/memcached/dist/memcached-1.2.5.tar.gz

    $ tar zxf memcached-1.2.5.tar.gz

    $ cd memcached-1.2.5

    $ ./configure

    $ make

    $ sudo make install

    默認情況下memcached安裝到/usr/local/bin下。

    memcached的啟動

    從終端輸入以下命令,啟動memcached。

    $ /usr/local/bin/memcached -p 11211 -m 64m -vv

    slab class 1: chunk size 88 perslab 11915

    slab class 2: chunk size 112 perslab 9362

    slab class 3: chunk size 144 perslab 7281

    中間省略

    slab class 38: chunk size 391224 perslab 2

    slab class 39: chunk size 489032 perslab 2

    <23 server listening

    <24 send buffer was 110592, now 268435456

    <24 server listening (udp)

    <24 server listening (udp)

    <24 server listening (udp)

    <24 server listening (udp)

    這里顯示了調試信息。這樣就在前臺啟動了memcached,監聽TCP端口11211 最大內存使用量為64M。調試信息的內容大部分是關于存儲的信息, 下次連載時具體說明。

    作為daemon后臺啟動時,只需

    $ /usr/local/bin/memcached -p 11211 -m 64m -d

    這里使用的memcached啟動選項的內容如下。

    選項 說明
    -p 使用的TCP端口。默認為11211
    -m 最大內存大小。默認為64M
    -vv 用very vrebose模式啟動,調試信息和錯誤輸出到控制臺
    -d 作為daemon在后臺啟動

    上面四個是常用的啟動選項,其他還有很多,通過

    $ /usr/local/bin/memcached -h

    命令可以顯示。許多選項可以改變memcached的各種行為, 推薦讀一讀。

    用客戶端連接

    許多語言都實現了連接memcached的客戶端,其中以Perl、PHP為主。 僅僅memcached網站上列出的語言就有

    • Perl
    • PHP
    • Python
    • Ruby
    • C#
    • C/C++
    • Lua

    等等。

    這里介紹通過mixi正在使用的Perl庫鏈接memcached的方法。

    使用Cache::Memcached

    Perl的memcached客戶端有

    • Cache::Memcached
    • Cache::Memcached::Fast
    • Cache::Memcached::libmemcached

    等幾個CPAN模塊。這里介紹的Cache::Memcached是memcached的作者Brad Fitzpatric的作品, 應該算是memcached的客戶端中應用最為廣泛的模塊了。

    使用Cache::Memcached連接memcached

    下面的源代碼為通過Cache::Memcached連接剛才啟動的memcached的例子。

    #!/usr/bin/perl



    use strict;

    use warnings;

    use Cache::Memcached;



    my $key = "foo";

    my $value = "bar";

    my $expires = 3600; # 1 hour

    my $memcached = Cache::Memcached->new({

    servers => ["127.0.0.1:11211"],

    compress_threshold => 10_000

    });



    $memcached->add($key, $value, $expires);

    my $ret = $memcached->get($key);

    print "$ret"n";

    在這里,為Cache::Memcached指定了memcached服務器的IP地址和一個選項,以生成實例。 Cache::Memcached常用的選項如下所示。

    選項 說明
    servers 用數組指定memcached服務器和端口
    compress_threshold 數據壓縮時使用的值
    namespace 指定添加到鍵的前綴

    另外,Cache::Memcached通過Storable模塊可以將Perl的復雜數據序列化之后再保存, 因此散列、數組、對象等都可以直接保存到memcached中。

    保存數據

    向memcached保存數據的方法有

    • add
    • replace
    • set

    它們的使用方法都相同:

    my $add = $memcached->add( '鍵', '值', '期限' );

    my $replace = $memcached->replace( '鍵', '值', '期限' );

    my $set = $memcached->set( '鍵', '值', '期限' );

    向memcached保存數據時可以指定期限(秒)。不指定期限時,memcached按照LRU算法保存數據。 這三個方法的區別如下:

    選項 說明
    add 僅當存儲空間中不存在鍵相同的數據時才保存
    replace 僅當存儲空間中存在鍵相同的數據時才保存
    set 與add和replace不同,無論何時都保存

    獲取數據

    獲取數據可以使用get和get_multi方法。

    my $val = $memcached->get('鍵');

    my $val = $memcached->get_multi('鍵1', '鍵2', '鍵3', '鍵4', '鍵5');

    一次取得多條數據時使用get_multi。get_multi可以非同步地同時取得多個鍵值, 其速度要比循環調用get快數十倍。

    刪除數據

    刪除數據使用delete方法,不過它有個獨特的功能。

    $memcached->delete('鍵', '阻塞時間(秒)');

    刪除第一個參數指定的鍵的數據。第二個參數指定一個時間值,可以禁止使用同樣的鍵保存新數據。 此功能可以用于防止緩存數據的不完整。但是要注意,set函數忽視該阻塞,照常保存數據

    增一和減一操作

    可以將memcached上特定的鍵值作為計數器使用。

    my $ret = $memcached->incr('鍵');

    $memcached->add('鍵', 0) unless defined $ret;

    增一和減一是原子操作,但未設置初始值時,不會自動賦成0。因此, 應當進行錯誤檢查,必要時加入初始化操作。而且,服務器端也不會對 超過2<sup>32</sup>時的行為進行檢查。

    總結

    這次簡單介紹了memcached,以及它的安裝方法、Perl客戶端Cache::Memcached的用法。 只要知道,memcached的使用方法十分簡單就足夠了。

    下次由前坂來說明memcached的內部結構。了解memcached的內部構造, 就能知道如何使用memcached才能使Web應用的速度更上一層樓。 歡迎繼續閱讀下一章。


    2.理解memcached的內存存儲


    下面是《memcached全面剖析》的第二部分。

    發表日:2008/7/9
    作者:前坂徹(Toru Maesaka)
    原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0002

    我是mixi株式會社研究開發組的前坂徹。 上次的文章介紹了memcached是分布式的高速緩存服務器。 本次將介紹memcached的內部構造的實現方式,以及內存的管理方式。 另外,memcached的內部構造導致的弱點也將加以說明。

    Slab Allocation機制:整理內存以便重復使用

    最近的memcached默認情況下采用了名為Slab Allocator的機制分配、管理內存。 在該機制出現以前,內存的分配是通過對所有記錄簡單地進行malloc和free來進行的。 但是,這種方式會導致內存碎片,加重操作系統內存管理器的負擔,最壞的情況下, 會導致操作系統比memcached進程本身還慢。Slab Allocator就是為解決該問題而誕生的。

    下面來看看Slab Allocator的原理。下面是memcached文檔中的slab allocator的目標:

    the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues totally by using fixed-size memory chunks coming from a few predetermined size classes.

    也就是說,Slab Allocator的基本原理是按照預先規定的大小,將分配的內存分割成特定長度的塊, 以完全解決內存碎片問題。

    Slab Allocation的原理相當簡單。 將分配的內存分割成各種尺寸的塊(chunk), 并把尺寸相同的塊分成組(chunk的集合)(圖1)。


    圖1 Slab Allocation的構造圖

    而且,slab allocator還有重復使用已分配的內存的目的。 也就是說,分配到的內存不會釋放,而是重復利用。

    Slab Allocation的主要術語

    Page

    分配給Slab的內存空間,默認是1MB。分配給Slab之后根據slab的大小切分成chunk。

    Chunk

    用于緩存記錄的內存空間。

    Slab Class

    特定大小的chunk的組。

    在Slab中緩存記錄的原理

    下面說明memcached如何針對客戶端發送的數據選擇slab并緩存到chunk中。

    memcached根據收到的數據的大小,選擇最適合數據大小的slab(圖2)。 memcached中保存著slab內空閑chunk的列表,根據該列表選擇chunk, 然后將數據緩存于其中。


    圖2 選擇存儲記錄的組的方法

    實際上,Slab Allocator也是有利也有弊。下面介紹一下它的缺點。

    Slab Allocator的缺點

    Slab Allocator解決了當初的內存碎片問題,但新的機制也給memcached帶來了新的問題。

    這個問題就是,由于分配的是特定長度的內存,因此無法有效利用分配的內存。 例如,將100字節的數據緩存到128字節的chunk中,剩余的28字節就浪費了(圖3)。


    圖3 chunk空間的使用

    對于該問題目前還沒有完美的解決方案,但在文檔中記載了比較有效的解決方案。

    The most efficient way to reduce the waste is to use a list of size classes that closely matches (if that's at all possible) common sizes of objects that the clients of this particular installation of memcached are likely to store.

    就是說,如果預先知道客戶端發送的數據的公用大小,或者僅緩存大小相同的數據的情況下, 只要使用適合數據大小的組的列表,就可以減少浪費。

    但是很遺憾,現在還不能進行任何調優,只能期待以后的版本了。 但是,我們可以調節slab class的大小的差別。 接下來說明growth factor選項。

    使用Growth Factor進行調優

    memcached在啟動時指定 Growth Factor因子(通過-f選項), 就可以在某種程度上控制slab之間的差異。默認值為1.25。 但是,在該選項出現之前,這個因子曾經固定為2,稱為“powers of 2”策略。

    讓我們用以前的設置,以verbose模式啟動memcached試試看:

    $ memcached -f 2 -vv

    下面是啟動后的verbose輸出:

    slab class   1: chunk size    128 perslab  8192

    slab class 2: chunk size 256 perslab 4096

    slab class 3: chunk size 512 perslab 2048

    slab class 4: chunk size 1024 perslab 1024

    slab class 5: chunk size 2048 perslab 512

    slab class 6: chunk size 4096 perslab 256

    slab class 7: chunk size 8192 perslab 128

    slab class 8: chunk size 16384 perslab 64

    slab class 9: chunk size 32768 perslab 32

    slab class 10: chunk size 65536 perslab 16

    slab class 11: chunk size 131072 perslab 8

    slab class 12: chunk size 262144 perslab 4

    slab class 13: chunk size 524288 perslab 2

    可見,從128字節的組開始,組的大小依次增大為原來的2倍。 這樣設置的問題是,slab之間的差別比較大,有些情況下就相當浪費內存。 因此,為盡量減少內存浪費,兩年前追加了growth factor這個選項。

    來看看現在的默認設置(f=1.25)時的輸出(篇幅所限,這里只寫到第10組):

    slab class   1: chunk size     88 perslab 11915

    slab class 2: chunk size 112 perslab 9362

    slab class 3: chunk size 144 perslab 7281

    slab class 4: chunk size 184 perslab 5698

    slab class 5: chunk size 232 perslab 4519

    slab class 6: chunk size 296 perslab 3542

    slab class 7: chunk size 376 perslab 2788

    slab class 8: chunk size 472 perslab 2221

    slab class 9: chunk size 592 perslab 1771

    slab class 10: chunk size 744 perslab 1409

    可見,組間差距比因子為2時小得多,更適合緩存幾百字節的記錄。 從上面的輸出結果來看,可能會覺得有些計算誤差, 這些誤差是為了保持字節數的對齊而故意設置的。

    將memcached引入產品,或是直接使用默認值進行部署時, 最好是重新計算一下數據的預期平均長度,調整growth factor, 以獲得最恰當的設置。內存是珍貴的資源,浪費就太可惜了。

    接下來介紹一下如何使用memcached的stats命令查看slabs的利用率等各種各樣的信息。

    查看memcached的內部狀態

    memcached有個名為stats的命令,使用它可以獲得各種各樣的信息。 執行命令的方法很多,用telnet最為簡單:

    $ telnet 主機名 端口號

    連接到memcached之后,輸入stats再按回車,即可獲得包括資源利用率在內的各種信息。 此外,輸入"stats slabs"或"stats items"還可以獲得關于緩存記錄的信息。 結束程序請輸入quit。

    這些命令的詳細信息可以參考memcached軟件包內的protocol.txt文檔。

    $ telnet localhost 11211

    Trying ::1...

    Connected to localhost.

    Escape character is '^]'.

    stats

    STAT pid 481

    STAT uptime 16574

    STAT time 1213687612

    STAT version 1.2.5

    STAT pointer_size 32

    STAT rusage_user 0.102297

    STAT rusage_system 0.214317

    STAT curr_items 0

    STAT total_items 0

    STAT bytes 0

    STAT curr_connections 6

    STAT total_connections 8

    STAT connection_structures 7

    STAT cmd_get 0

    STAT cmd_set 0

    STAT get_hits 0

    STAT get_misses 0

    STAT evictions 0

    STAT bytes_read 20

    STAT bytes_written 465

    STAT limit_maxbytes 67108864

    STAT threads 4

    END

    quit

    另外,如果安裝了libmemcached這個面向C/C++語言的客戶端庫,就會安裝 memstat 這個命令。 使用方法很簡單,可以用更少的步驟獲得與telnet相同的信息,還能一次性從多臺服務器獲得信息。

    $ memstat --servers=server1,server2,server3,...

    libmemcached可以從下面的地址獲得:

    查看slabs的使用狀況

    使用memcached的創造著Brad寫的名為memcached-tool的Perl腳本,可以方便地獲得slab的使用情況 (它將memcached的返回值整理成容易閱讀的格式)。可以從下面的地址獲得腳本:

    使用方法也極其簡單:

    $ memcached-tool 主機名:端口 選項

    查看slabs使用狀況時無需指定選項,因此用下面的命令即可:

    $ memcached-tool 主機名:端口

    獲得的信息如下所示:

     #  Item_Size   Max_age  1MB_pages Count   Full?

    1 104 B 1394292 s 1215 12249628 yes

    2 136 B 1456795 s 52 400919 yes

    3 176 B 1339587 s 33 196567 yes

    4 224 B 1360926 s 109 510221 yes

    5 280 B 1570071 s 49 183452 yes

    6 352 B 1592051 s 77 229197 yes

    7 440 B 1517732 s 66 157183 yes

    8 552 B 1460821 s 62 117697 yes

    9 696 B 1521917 s 143 215308 yes

    10 872 B 1695035 s 205 246162 yes

    11 1.1 kB 1681650 s 233 221968 yes

    12 1.3 kB 1603363 s 241 183621 yes

    13 1.7 kB 1634218 s 94 57197 yes

    14 2.1 kB 1695038 s 75 36488 yes

    15 2.6 kB 1747075 s 65 25203 yes

    16 3.3 kB 1760661 s 78 24167 yes

    各列的含義為:

    含義
    # slab class編號
    Item_Size Chunk大小
    Max_age LRU內最舊的記錄的生存時間
    1MB_pages 分配給Slab的頁數
    Count Slab內的記錄數
    Full? Slab內是否含有空閑chunk

    從這個腳本獲得的信息對于調優非常方便,強烈推薦使用。

    內存存儲的總結

    本次簡單說明了memcached的緩存機制和調優方法。 希望讀者能理解memcached的內存管理原理及其優缺點。

    下次將繼續說明LRU和Expire等原理,以及memcached的最新發展方向—— 可擴充體系(pluggable architecher))。


    3.memcached的刪除機制和發展方向


    下面是《memcached全面剖析》的第三部分。

    發表日:2008/7/16
    作者:前坂徹(Toru Maesaka)
    原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0003

    memcached是緩存,所以數據不會永久保存在服務器上,這是向系統中引入memcached的前提。 本次介紹memcached的數據刪除機制,以及memcached的最新發展方向——二進制協議(Binary Protocol) 和外部引擎支持。

    memcached在數據刪除方面有效利用資源

    數據不會真正從memcached中消失

    上次介紹過, memcached不會釋放已分配的內存。記錄超時后,客戶端就無法再看見該記錄(invisible,透明), 其存儲空間即可重復使用。

    Lazy Expiration

    memcached內部不會監視記錄是否過期,而是在get時查看記錄的時間戳,檢查記錄是否過期。 這種技術被稱為lazy(惰性)expiration。因此,memcached不會在過期監視上耗費CPU時間。

    LRU:從緩存中有效刪除數據的原理

    memcached會優先使用已超時的記錄的空間,但即使如此,也會發生追加新記錄時空間不足的情況, 此時就要使用名為 Least Recently Used(LRU)機制來分配空間。 顧名思義,這是刪除“最近最少使用”的記錄的機制。 因此,當memcached的內存空間不足時(無法從slab class 獲取到新的空間時),就從最近未被使用的記錄中搜索,并將其空間分配給新的記錄。 從緩存的實用角度來看,該模型十分理想。

    不過,有些情況下LRU機制反倒會造成麻煩。memcached啟動時通過“-M”參數可以禁止LRU,如下所示:

    $ memcached -M -m 1024

    啟動時必須注意的是,小寫的“-m”選項是用來指定最大內存大小的。不指定具體數值則使用默認值64MB。

    指定“-M”參數啟動后,內存用盡時memcached會返回錯誤。 話說回來,memcached畢竟不是存儲器,而是緩存,所以推薦使用LRU。

    memcached的最新發展方向

    memcached的roadmap上有兩個大的目標。一個是二進制協議的策劃和實現,另一個是外部引擎的加載功能。

    關于二進制協議

    使用二進制協議的理由是它不需要文本協議的解析處理,使得原本高速的memcached的性能更上一層樓, 還能減少文本協議的漏洞。目前已大部分實現,開發用的代碼庫中已包含了該功能。 memcached的下載頁面上有代碼庫的鏈接。

    二進制協議的格式

    協議的包為24字節的幀,其后面是鍵和無結構數據(Unstructured Data)。 實際的格式如下(引自協議文檔):

     Byte/     0       |       1       |       2       |       3       |   

    / | | | |

    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|

    +---------------+---------------+---------------+---------------+

    0/ HEADER /

    / /

    / /

    / /

    +---------------+---------------+---------------+---------------+

    24/ COMMAND-SPECIFIC EXTRAS (as needed) /

    +/ (note length in th extras length header field) /

    +---------------+---------------+---------------+---------------+

    m/ Key (as needed) /

    +/ (note length in key length header field) /

    +---------------+---------------+---------------+---------------+

    n/ Value (as needed) /

    +/ (note length is total body length header field, minus /

    +/ sum of the extras and key length body fields) /

    +---------------+---------------+---------------+---------------+

    Total 24 bytes

    如上所示,包格式十分簡單。需要注意的是,占據了16字節的頭部(HEADER)分為 請求頭(Request Header)和響應頭(Response Header)兩種。 頭部中包含了表示包的有效性的Magic字節、命令種類、鍵長度、值長度等信息,格式如下:

    Request Header



    Byte/ 0 | 1 | 2 | 3 |

    / | | | |

    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|

    +---------------+---------------+---------------+---------------+

    0| Magic | Opcode | Key length |

    +---------------+---------------+---------------+---------------+

    4| Extras length | Data type | Reserved |

    +---------------+---------------+---------------+---------------+

    8| Total body length |

    +---------------+---------------+---------------+---------------+

    12| Opaque |

    +---------------+---------------+---------------+---------------+

    16| CAS |

    | |

    +---------------+---------------+---------------+---------------+
    Response Header



    Byte/ 0 | 1 | 2 | 3 |

    / | | | |

    |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|

    +---------------+---------------+---------------+---------------+

    0| Magic | Opcode | Key Length |

    +---------------+---------------+---------------+---------------+

    4| Extras length | Data type | Status |

    +---------------+---------------+---------------+---------------+

    8| Total body length |

    +---------------+---------------+---------------+---------------+

    12| Opaque |

    +---------------+---------------+---------------+---------------+

    16| CAS |

    | |

    +---------------+---------------+---------------+---------------+

    如希望了解各個部分的詳細內容,可以checkout出memcached的二進制協議的代碼樹, 參考其中的docs文件夾中的protocol_binary.txt文檔。

    HEADER中引人注目的地方

    看到HEADER格式后我的感想是,鍵的上限太大了!現在的memcached規格中,鍵長度最大為250字節, 但二進制協議中鍵的大小用2字節表示。因此,理論上最大可使用65536字節(2<sup>16</sup>)長的鍵。 盡管250字節以上的鍵并不會太常用,二進制協議發布之后就可以使用巨大的鍵了。

    二進制協議從下一版本1.3系列開始支持。

    外部引擎支持

    我去年曾經試驗性地將memcached的存儲層改造成了可擴展的(pluggable)。

    MySQL的Brian Aker看到這個改造之后,就將代碼發到了memcached的郵件列表。 memcached的開發者也十分感興趣,就放到了roadmap中。現在由我和 memcached的開發者Trond Norbye協同開發(規格設計、實現和測試)。 和國外協同開發時時差是個大問題,但抱著相同的愿景, 最后終于可以將可擴展架構的原型公布了。 代碼庫可以從memcached的下載頁面 上訪問。

    外部引擎支持的必要性

    世界上有許多memcached的派生軟件,其理由是希望永久保存數據、實現數據冗余等, 即使犧牲一些性能也在所不惜。我在開發memcached之前,在mixi的研發部也曾經 考慮過重新發明memcached。

    外部引擎的加載機制能封裝memcached的網絡功能、事件處理等復雜的處理。 因此,現階段通過強制手段或重新設計等方式使memcached和存儲引擎合作的困難 就會煙消云散,嘗試各種引擎就會變得輕而易舉了。

    簡單API設計的成功的關鍵

    該項目中我們最重視的是API設計。函數過多,會使引擎開發者感到麻煩; 過于復雜,實現引擎的門檻就會過高。因此,最初版本的接口函數只有13個。 具體內容限于篇幅,這里就省略了,僅說明一下引擎應當完成的操作:

    • 引擎信息(版本等)
    • 引擎初始化
    • 引擎關閉
    • 引擎的統計信息
    • 在容量方面,測試給定記錄能否保存
    • 為item(記錄)結構分配內存
    • 釋放item(記錄)的內存
    • 刪除記錄
    • 保存記錄
    • 回收記錄
    • 更新記錄的時間戳
    • 數學運算處理
    • 數據的flush

    對詳細規格有興趣的讀者,可以checkout engine項目的代碼,閱讀器中的engine.h。

    重新審視現在的體系

    memcached支持外部存儲的難點是,網絡和事件處理相關的代碼(核心服務器)與 內存存儲的代碼緊密關聯。這種現象也稱為tightly coupled(緊密耦合)。 必須將內存存儲的代碼從核心服務器中獨立出來,才能靈活地支持外部引擎。 因此,基于我們設計的API,memcached被重構成下面的樣子:


    重構之后,我們與1.2.5版、二進制協議支持版等進行了性能對比,證實了它不會造成性能影響。

    在考慮如何支持外部引擎加載時,讓memcached進行并行控制(concurrency control)的方案是最為容易的, 但是對于引擎而言,并行控制正是性能的真諦,因此我們采用了將多線程支持完全交給引擎的設計方案。

    以后的改進,會使得memcached的應用范圍更為廣泛。

    總結

    本次介紹了memcached的超時原理、內部如何刪除數據等,在此之上又介紹了二進制協議和 外部引擎支持等memcached的最新發展方向。這些功能要到1.3版才會支持,敬請期待!

    這是我在本連載中的最后一篇。感謝大家閱讀我的文章!

    下次由長野來介紹memcached的應用知識和應用程序兼容性等內容。



    4. memcached的分布式算法


    發表日:2008/7/23
    作者:長野雅廣(Masahiro Nagano)
    原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0004

    我是Mixi的長野。 第2次第3次 由前坂介紹了memcached的內部情況。本次不再介紹memcached的內部結構, 開始介紹memcached的分布式。

    memcached的分布式

    正如第1次中介紹的那樣, memcached雖然稱為“分布式”緩存服務器,但服務器端并沒有“分布式”功能。 服務器端僅包括 第2次第3次 前坂介紹的內存存儲功能,其實現非常簡單。 至于memcached的分布式,則是完全由客戶端程序庫實現的。 這種分布式是memcached的最大特點。

    memcached的分布式是什么意思?

    這里多次使用了“分布式”這個詞,但并未做詳細解釋。 現在開始簡單地介紹一下其原理,各個客戶端的實現基本相同。

    下面假設memcached服務器有node1~node3三臺, 應用程序要保存鍵名為“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的數據。


    圖1 分布式簡介:準備

    首先向memcached中添加“tokyo”。將“tokyo”傳給客戶端程序庫后, 客戶端實現的算法就會根據“鍵”來決定保存數據的memcached服務器。 服務器選定后,即命令它保存“tokyo”及其值。


    圖2 分布式簡介:添加時

    同樣,“kanagawa”“chiba”“saitama”“gunma”都是先選擇服務器再保存。

    接下來獲取保存的數據。獲取時也要將要獲取的鍵“tokyo”傳遞給函數庫。 函數庫通過與數據保存時相同的算法,根據“鍵”選擇服務器。 使用的算法相同,就能選中與保存時相同的服務器,然后發送get命令。 只要數據沒有因為某些原因被刪除,就能獲得保存的值。


    圖3 分布式簡介:獲取時

    這樣,將不同的鍵保存到不同的服務器上,就實現了memcached的分布式。 memcached服務器增多后,鍵就會分散,即使一臺memcached服務器發生故障 無法連接,也不會影響其他的緩存,系統依然能繼續運行。

    接下來介紹第1次 中提到的Perl客戶端函數庫Cache::Memcached實現的分布式方法。

    Cache::Memcached的分布式方法

    Perl的memcached客戶端函數庫Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以說是原裝的函數庫了。

    該函數庫實現了分布式功能,是memcached標準的分布式方法。

    根據余數計算分散

    Cache::Memcached的分布式方法簡單來說,就是“根據服務器臺數的余數進行分散”。 求得鍵的整數哈希值,再除以服務器臺數,根據其余數來選擇服務器。

    下面將Cache::Memcached簡化成以下的Perl腳本來進行說明。

    use strict;

    use warnings;

    use String::CRC32;



    my @nodes = ('node1','node2','node3');

    my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma');



    foreach my $key (@keys) {

    my $crc = crc32($key); # CRC値

    my $mod = $crc % ( $#nodes + 1 );

    my $server = $nodes[ $mod ]; # 根據余數選擇服務器

    printf "%s => %s"n", $key, $server;

    }

    Cache::Memcached在求哈希值時使用了CRC。

    首先求得字符串的CRC值,根據該值除以服務器節點數目得到的余數決定服務器。 上面的代碼執行后輸入以下結果:

    tokyo       => node2

    kanagawa => node3

    chiba => node2

    saitama => node1

    gunma => node1

    根據該結果,“tokyo”分散到node2,“kanagawa”分散到node3等。 多說一句,當選擇的服務器無法連接時,Cache::Memcached會將連接次數 添加到鍵之后,再次計算哈希值并嘗試連接。這個動作稱為rehash。 不希望rehash時可以在生成Cache::Memcached對象時指定“rehash => 0”選項。

    根據余數計算分散的缺點

    余數計算的方法簡單,數據的分散性也相當優秀,但也有其缺點。 那就是當添加或移除服務器時,緩存重組的代價相當巨大。 添加服務器后,余數就會產生巨變,這樣就無法獲取與保存時相同的服務器, 從而影響緩存的命中率。用Perl寫段代碼來驗證其代價。

    use strict;

    use warnings;

    use String::CRC32;



    my @nodes = @ARGV;

    my @keys = ('a'..'z');

    my %nodes;



    foreach my $key ( @keys ) {

    my $hash = crc32($key);

    my $mod = $hash % ( $#nodes + 1 );

    my $server = $nodes[ $mod ];

    push @{ $nodes{ $server } }, $key;

    }



    foreach my $node ( sort keys %nodes ) {

    printf "%s: %s"n", $node, join ",", @{ $nodes{$node} };

    }

    這段Perl腳本演示了將“a”到“z”的鍵保存到memcached并訪問的情況。 將其保存為mod.pl并執行。

    首先,當服務器只有三臺時:

    $ mod.pl node1 node2 nod3

    node1: a,c,d,e,h,j,n,u,w,x

    node2: g,i,k,l,p,r,s,y

    node3: b,f,m,o,q,t,v,z

    結果如上,node1保存a、c、d、e……,node2保存g、i、k……, 每臺服務器都保存了8個到10個數據。

    接下來增加一臺memcached服務器。

    $ mod.pl node1 node2 node3 node4

    node1: d,f,m,o,t,v

    node2: b,i,k,p,r,y

    node3: e,g,l,n,u,w

    node4: a,c,h,j,q,s,x,z

    添加了node4。可見,只有d、i、k、p、r、y命中了。像這樣,添加節點后 鍵分散到的服務器會發生巨大變化。26個鍵中只有六個在訪問原來的服務器, 其他的全都移到了其他服務器。命中率降低到23%。在Web應用程序中使用memcached時, 在添加memcached服務器的瞬間緩存效率會大幅度下降,負載會集中到數據庫服務器上, 有可能會發生無法提供正常服務的情況。

    mixi的Web應用程序運用中也有這個問題,導致無法添加memcached服務器。 但由于使用了新的分布式方法,現在可以輕而易舉地添加memcached服務器了。 這種分布式方法稱為 Consistent Hashing。

    Consistent Hashing

    關于Consistent Hashing的思想,mixi株式會社的開發blog等許多地方都介紹過, 這里只簡單地說明一下。

    Consistent Hashing的簡單說明

    Consistent Hashing如下所示:首先求出memcached服務器(節點)的哈希值, 并將其配置到0~232的圓(continuum)上。 然后用同樣的方法求出存儲數據的鍵的哈希值,并映射到圓上。 然后從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。 如果超過232仍然找不到服務器,就會保存到第一臺memcached服務器上。


    圖4 Consistent Hashing:基本原理

    從上圖的狀態中添加一臺memcached服務器。余數分布式算法由于保存鍵的服務器會發生巨大變化 而影響緩存的命中率,但Consistent Hashing中,只有在continuum上增加服務器的地點逆時針方向的 第一臺服務器上的鍵會受到影響。


    圖5 Consistent Hashing:添加服務器

    因此,Consistent Hashing最大限度地抑制了鍵的重新分布。 而且,有的Consistent Hashing的實現方法還采用了虛擬節點的思想。 使用一般的hash函數的話,服務器的映射地點的分布非常不均勻。 因此,使用虛擬節點的思想,為每個物理節點(服務器) 在continuum上分配100~200個點。這樣就能抑制分布不均勻, 最大限度地減小服務器增減時的緩存重新分布。

    通過下文中介紹的使用Consistent Hashing算法的memcached客戶端函數庫進行測試的結果是, 由服務器臺數(n)和增加的服務器臺數(m)計算增加服務器后的命中率計算公式如下:

    (1 - n/(n+m)) * 100

    支持Consistent Hashing的函數庫

    本連載中多次介紹的Cache::Memcached雖然不支持Consistent Hashing, 但已有幾個客戶端函數庫支持了這種新的分布式算法。 第一個支持Consistent Hashing和虛擬節點的memcached客戶端函數庫是 名為libketama的PHP庫,由last.fm開發。

    至于Perl客戶端,連載的第1次 中介紹過的Cache::Memcached::Fast和Cache::Memcached::libmemcached支持 Consistent Hashing。

    兩者的接口都與Cache::Memcached幾乎相同,如果正在使用Cache::Memcached, 那么就可以方便地替換過來。Cache::Memcached::Fast重新實現了libketama, 使用Consistent Hashing創建對象時可以指定ketama_points選項。

    my $memcached = Cache::Memcached::Fast->new({

    servers => ["192.168.0.1:11211","192.168.0.2:11211"],

    ketama_points => 150

    });

    另外,Cache::Memcached::libmemcached 是一個使用了Brain Aker開發的C函數庫libmemcached的Perl模塊。 libmemcached本身支持幾種分布式算法,也支持Consistent Hashing, 其Perl綁定也支持Consistent Hashing。

    總結

    本次介紹了memcached的分布式算法,主要有memcached的分布式是由客戶端函數庫實現, 以及高效率地分散數據的Consistent Hashing算法。下次將介紹mixi在memcached應用方面的一些經驗, 和相關的兼容應用程序。


    5. memcached的應用和兼容程序


    發表日:2008/7/30
    作者:長野雅廣(Masahiro Nagano)
    原文鏈接:http://gihyo.jp/dev/feature/01/memcached/0005

    我是Mixi的長野。memcached的連載終于要結束了。 到上次為止, 我們介紹了與memcached直接相關的話題,本次介紹一些mixi的案例和 實際應用上的話題,并介紹一些與memcached兼容的程序。

    mixi案例研究

    mixi在提供服務的初期階段就使用了memcached。 隨著網站訪問量的急劇增加,單純為數據庫添加slave已無法滿足需要,因此引入了memcached。 此外,我們也從增加可擴展性的方面進行了驗證,證明了memcached的速度和穩定性都能滿足需要。 現在,memcached已成為mixi服務中非常重要的組成部分。


    圖1 現在的系統組件

    服務器配置和數量

    mixi使用了許許多多服務器,如數據庫服務器、應用服務器、圖片服務器、 反向代理服務器等。單單memcached就有將近200臺服務器在運行。 memcached服務器的典型配置如下:

    • CPU:Intel Pentium 4 2.8GHz
    • 內存:4GB
    • 硬盤:146GB SCSI
    • 操作系統:Linux(x86_64)

    這些服務器以前曾用于數據庫服務器等。隨著CPU性能提升、內存價格下降, 我們積極地將數據庫服務器、應用服務器等換成了性能更強大、內存更多的服務器。 這樣,可以抑制mixi整體使用的服務器數量的急劇增加,降低管理成本。 由于memcached服務器幾乎不占用CPU,就將換下來的服務器用作memcached服務器了。

    memcached進程

    每臺memcached服務器僅啟動一個memcached進程。分配給memcached的內存為3GB, 啟動參數如下:

    /usr/bin/memcached -p 11211 -u nobody -m 3000 -c 30720

    由于使用了x86_64的操作系統,因此能分配2GB以上的內存。32位操作系統中, 每個進程最多只能使用2GB內存。也曾經考慮過啟動多個分配2GB以下內存的進程, 但這樣一臺服務器上的TCP連接數就會成倍增加,管理上也變得復雜, 所以mixi就統一使用了64位操作系統。

    另外,雖然服務器的內存為4GB,卻僅分配了3GB,是因為內存分配量超過這個值, 就有可能導致內存交換(swap)。連載的第2次中 前坂講解過了memcached的內存存儲“slab allocator”,當時說過,memcached啟動時 指定的內存分配量是memcached用于保存數據的量,沒有包括“slab allocator”本身占用的內存、 以及為了保存數據而設置的管理空間。因此,memcached進程的實際內存分配量要比 指定的容量要大,這一點應當注意。

    mixi保存在memcached中的數據大部分都比較小。這樣,進程的大小要比 指定的容量大很多。因此,我們反復改變內存分配量進行驗證, 確認了3GB的大小不會引發swap,這就是現在應用的數值。

    memcached使用方法和客戶端

    現在,mixi的服務將200臺左右的memcached服務器作為一個pool使用。 每臺服務器的容量為3GB,那么全體就有了將近600GB的巨大的內存數據庫。 客戶端程序庫使用了本連載中多次提到車的Cache::Memcached::Fast, 與服務器進行交互。當然,緩存的分布式算法使用的是 第4次介紹過的 Consistent Hashing算法。

    應用層上memcached的使用方法由開發應用程序的工程師自行決定并實現。 但是,為了防止車輪再造、防止Cache::Memcached::Fast上的教訓再次發生, 我們提供了Cache::Memcached::Fast的wrap模塊并使用。

    通過Cache::Memcached::Fast維持連接

    Cache::Memcached的情況下,與memcached的連接(文件句柄)保存在Cache::Memcached包內的類變量中。 在mod_perl和FastCGI等環境下,包內的變量不會像CGI那樣隨時重新啟動, 而是在進程中一直保持。其結果就是不會斷開與memcached的連接, 減少了TCP連接建立時的開銷,同時也能防止短時間內反復進行TCP連接、斷開 而導致的TCP端口資源枯竭。

    但是,Cache::Memcached::Fast沒有這個功能,所以需要在模塊之外 將Cache::Memcached::Fast對象保持在類變量中,以保證持久連接。

    package Gihyo::Memcached;



    use strict;

    use warnings;

    use Cache::Memcached::Fast;



    my @server_list = qw/192.168.1.1:11211 192.168.1.1:11211/;

    my $fast; ## 用于保持對象



    sub new {

    my $self = bless {}, shift;

    if ( !$fast ) {

    $fast = Cache::Memcached::Fast->new({ servers => "@server_list });

    }

    $self->{_fast} = $fast;

    return $self;

    }



    sub get {

    my $self = shift;

    $self->{_fast}->get(@_);

    }

    上面的例子中,Cache::Memcached::Fast對象保存到類變量$fast中。

    公共數據的處理和rehash

    諸如mixi的主頁上的新聞這樣的所有用戶共享的緩存數據、設置信息等數據, 會占用許多頁,訪問次數也非常多。在這種條件下,訪問很容易集中到某臺memcached服務器上。 訪問集中本身并不是問題,但是一旦訪問集中的那臺服務器發生故障導致memcached無法連接, 就會產生巨大的問題。

    連載的第4次 中提到,Cache::Memcached擁有rehash功能,即在無法連接保存數據的服務器的情況下, 會再次計算hash值,連接其他的服務器。

    但是,Cache::Memcached::Fast沒有這個功能。不過,它能夠在連接服務器失敗時, 短時間內不再連接該服務器的功能。

    my $fast = Cache::Memcached::Fast->new({

    max_failures => 3,

    failure_timeout => 1

    });

    在failure_timeout秒內發生max_failures以上次連接失敗,就不再連接該memcached服務器。 我們的設置是1秒鐘3次以上。

    此外,mixi還為所有用戶共享的緩存數據的鍵名設置命名規則, 符合命名規則的數據會自動保存到多臺memcached服務器中, 取得時從中僅選取一臺服務器。創建該函數庫后,就可以使memcached服務器故障 不再產生其他影響。

    memcached應用經驗

    到此為止介紹了memcached內部構造和函數庫,接下來介紹一些其他的應用經驗。

    通過daemontools啟動

    通常情況下memcached運行得相當穩定,但mixi現在使用的最新版1.2.5 曾經發生過幾次memcached進程死掉的情況。架構上保證了即使有幾臺memcached故障 也不會影響服務,不過對于memcached進程死掉的服務器,只要重新啟動memcached, 就可以正常運行,所以采用了監視memcached進程并自動啟動的方法。 于是使用了daemontools。

    daemontools是qmail的作者DJB開發的UNIX服務管理工具集, 其中名為supervise的程序可用于服務啟動、停止的服務重啟等。

    這里不介紹daemontools的安裝了。mixi使用了以下的run腳本來啟動memcached。

    #!/bin/sh



    if [ -f /etc/sysconfig/memcached ];then

    . /etc/sysconfig/memcached

    fi



    exec 2>&1

    exec /usr/bin/memcached -p $PORT -u $USER -m $CACHESIZE -c $MAXCONN $OPTIONS

    監視

    mixi使用了名為“nagios”的開源監視軟件來監視memcached。

    在nagios中可以簡單地開發插件,可以詳細地監視memcached的get、add等動作。 不過mixi僅通過stats命令來確認memcached的運行狀態。

    define command {

    command_name check_memcached

    command_line $USER1$/check_tcp -H $HOSTADDRESS$ -p 11211 -t 5 -E -s 'stats"r"nquit"r"n' -e 'uptime' -M crit

    }

    此外,mixi將stats目錄的結果通過rrdtool轉化成圖形,進行性能監視, 并將每天的內存使用量做成報表,通過郵件與開發者共享。

    memcached的性能

    連載中已介紹過,memcached的性能十分優秀。我們來看看mixi的實際案例。 這里介紹的圖表是服務所使用的訪問最為集中的memcached服務器。


    圖2 請求數


    圖3 流量


    圖4 TCP連接數

    從上至下依次為請求數、流量和TCP連接數。請求數最大為15000qps, 流量達到400Mbps,這時的連接數已超過了10000個。 該服務器沒有特別的硬件,就是開頭介紹的普通的memcached服務器。 此時的CPU利用率為:


    圖5 CPU利用率

    可見,仍然有idle的部分。因此,memcached的性能非常高, 可以作為Web應用程序開發者放心地保存臨時數據或緩存數據的地方。

    兼容應用程序

    memcached的實現和協議都十分簡單,因此有很多與memcached兼容的實現。 一些功能強大的擴展可以將memcached的內存數據寫到磁盤上,實現數據的持久性和冗余。 連載第3次 介紹過,以后的memcached的存儲層將變成可擴展的(pluggable),逐漸支持這些功能。

    這里介紹幾個與memcached兼容的應用程序。

    repcached
    為memcached提供復制(replication)功能的patch。
    Flared
    存儲到QDBM。同時實現了異步復制和fail over等功能。
    memcachedb
    存儲到BerkleyDB。還實現了message queue。
    Tokyo Tyrant
    將數據存儲到Tokyo Cabinet。不僅與memcached協議兼容,還能通過HTTP進行訪問。

    Tokyo Tyrant案例

    mixi使用了上述兼容應用程序中的Tokyo Tyrant。Tokyo Tyrant是平林開發的 Tokyo Cabinet DBM的網絡接口。它有自己的協議,但也擁有memcached兼容協議, 也可以通過HTTP進行數據交換。Tokyo Cabinet雖然是一種將數據寫到磁盤的實現,但速度相當快。

    mixi并沒有將Tokyo Tyrant作為緩存服務器,而是將它作為保存鍵值對組合的DBMS來使用。 主要作為存儲用戶上次訪問時間的數據庫來使用。它與幾乎所有的mixi服務都有關, 每次用戶訪問頁面時都要更新數據,因此負荷相當高。MySQL的處理十分笨重, 單獨使用memcached保存數據又有可能會丟失數據,所以引入了Tokyo Tyrant。 但無需重新開發客戶端,只需原封不動地使用Cache::Memcached::Fast即可, 這也是優點之一。關于Tokyo Tyrant的詳細信息,請參考本公司的開發blog。

    總結

    到本次為止,“memcached全面剖析”系列就結束了。我們介紹了memcached的基礎、內部結構、 分散算法和應用等內容。讀完后如果您能對memcached產生興趣,就是我們的榮幸。 關于mixi的系統、應用方面的信息,請參考本公司的開發blog。 感謝您的閱讀。






    posted on 2009-07-03 12:20 Blog of JoJo 閱讀(665) 評論(0)  編輯  收藏 所屬分類: 每日一記Tool 安裝應用

    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    常用鏈接

    留言簿(6)

    隨筆檔案

    文章分類

    文章檔案

    新聞分類

    新聞檔案

    相冊

    收藏夾

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 777成影片免费观看| aa午夜免费剧场| 在线观看成人免费视频不卡| 亚洲成熟xxxxx电影| 久久永久免费人妻精品下载 | 国产成人亚洲综合网站不卡| 希望影院高清免费观看视频| 久久亚洲精品成人无码网站| 黄页网站在线观看免费高清| 亚洲国产综合精品中文第一| 精品国产免费观看| 边摸边吃奶边做爽免费视频99 | 亚洲AV成人无码久久精品老人| 青柠影视在线观看免费高清| 亚洲国产精品国自产电影| 免费h片在线观看网址最新| 亚洲一级毛片视频| 国产成人免费a在线资源| 中国亚洲女人69内射少妇| 久久国产亚洲精品| 国产成人精品免费视频软件| 免费亚洲视频在线观看| 亚洲人成网站18禁止一区 | 精品熟女少妇a∨免费久久| 亚洲欧洲日产国码在线观看| 成**人免费一级毛片| 黄色网址在线免费观看| 亚洲国产精品lv| 日韩一区二区在线免费观看| 成人av片无码免费天天看| 亚洲男人天堂影院| 免费一级毛片正在播放| 三年片在线观看免费观看大全一| 天天爽亚洲中文字幕| 亚洲日韩人妻第一页| 国产成人精品免费午夜app | a级毛片在线免费看| 亚洲一区二区三区亚瑟| 亚洲精品黄色视频在线观看免费资源| 一级毛片在线免费观看| 亚洲AV无码片一区二区三区 |