緩衝的使用,緩衝使用_PHP教程

來源:互聯網
上載者:User

緩衝的使用,緩衝使用


  緩衝,提高訪問速度的利器。工作中主要用到的是memcache和redis,它們是B/S軟體,類似練習時裝在機子上的Apache,它也會監聽連接埠,可以在用戶端(如在cmd上通過telnet操作Memcache)直接敲各自對應的命令來存取值,自學時可以通過這樣的方式熟悉下原生命令,看看效果。它們常駐記憶體,得到資料後寫入記憶體(安裝軟體後會佔用一片記憶體地區),設定資料的到期時間,用到時直接從記憶體中讀出來,因為是記憶體所以訪問速度上有數量級的提高。lz曾今寫了個非常簡單的後台,完全不用緩衝那種,那速度我自己看了都忍不住要咆哮:這麼慢tm也能叫網頁?!可那時我年幼無知,不會用緩衝,當然現在也不見得好到哪兒去,但願那時前端妹子沒在心裡暗自唾罵>3<......

  memcache的使用較簡單,至少從命令上來看是這樣。在memcache中,儲存的方式是key->value,鍵-值對應的方式儲存,類似聯結數組的元素形式,可儲存的類型可以是數字、字串、數組、對象等,項目中一般會將非字串(數字可看成是數字字串)類型的先進行json編碼或者序列化再存入緩衝,取出來時則解碼或者還原序列化。Memcache是php專門處理memcache緩衝的類,封裝的主要方法是get、set、delete、flush、connect、close,即一個緩衝必備的擷取、設定、刪除、清空、串連、關閉等。(中括弧為可傳參數)

add key, var, [flag], [expire] 添加一個鍵為key、值為var到服務端,可指定到期時間expire,0則永不到期,最長30天
get key, [flag] 從服務端擷取某一或某些元素
delete key, [timeout] 在指定timeout時間內刪除key對應元素,預設timeout為0立即刪除
flush 清空,即刪除所有元素
connect host, [port], [timeout] 串連到memcache伺服器host主機的port連接埠,它會在指令碼執行完自動關閉,也可主動關閉
close 主動關閉memcache服務端串連,但不會關閉pconnect產生的串連
set key, var, [flag], [expire] 儲存鍵為key、值為var的元素到服務端,key存在則覆蓋其值,不存在新add一個
replace key, var, [flag], [expire] 尋找鍵為key的元素,以值var替換,查不到會報錯
increment key, [value] 在鍵為key的元素上增加值value,value預設為1,可能改變原值
decrement key, [value] 在鍵為key的元素上減少值value,value預設為1,可能改變原值
addServer host, [port], [persistent], [weight], [timeout], ...... 添加一個memcache服務到串連池(儲存多個memcache串連的地方)中,即建立一個到host的串連,port為連接埠
pconnect host, [port], [timeout] 建立到位於host主機的memcache伺服器的持久串連,port為連接埠,指令碼執行完或調用close也無法關閉這種串連

  其中有幾點需要留意下:

  1. set和replace的用法區別:set幾乎任何情況下,當然不是串連都失敗、記憶體不夠用等硬性原因,都是成功的,replace只是在已經存在的鍵的基礎上進行修改,鍵若不存在不成功;

  2. connect方法建立的串連,要麼指令碼運行結束自己斷開,要麼調用close()方法被斷開掉;

  3. increment或者的decrement方法得到的數永遠是大於或等於0,如果確實計算後得到的值是小於0的則進行轉化,比如int類型,32位上佔4位元組,則返回的值是(232-1),計算後大於或等於0的直接返回;而元素之類型為非數字(或數字字串)的,先轉為數字再計算;

  4. 當我們new一個Memcache對象並進行串連時,可以有兩種方式,一是connect方法:$mem =new Memcache; $mem->connect('127.0.0.1', 11211); 或者使用addserver方法:$mem->addServer('127.0.0.1', 11211); 後者以向已有的串連池中添加一個Memcache伺服器串連的方式建立,效果一樣。

  Redis,一款強大的緩衝工具,得益於在國內門戶網站中的廣泛使用,功能上在不斷完善,現在有穩定的版本(貌似微軟github上最新64位的release 2.8版本,在我工作的機子上運行不起來...),方法比memcache多得多,作為開發使用到的主要是資料的存取,而其他的事物、通訊協定、叢集,在一般開發個後台,不是對速度追求到極致的情況下,還是基本用不到。redis中有按操作分為以下幾種類型:

  1. String:也是key-value對,主要也是對它的鍵對應的元素進行get、set、求長度、自增等操作,redis將添加的類型全存為字串,即便純數字;

  

  2. Hash:雜湊表(散列表),就是資料結構中的字典,採用特定的散列演算法,比如手機通訊錄有幾百人,以姓的第一個字母作為關鍵碼進行排序,這樣我們可以快速找到連絡人,這是通過特定的映射方式,將一個字母與姓名聯絡起來。在redis中,建立hash表時,首先指定表名,你存在哪張表裡邊,然後指定一個域(field,實際就是一個變數),和它對應的值,單用cmd操作就像下面這樣(在hash表hash1中儲存一個域username對應值Jack,redis會自動建立username到Jack的映射),一張hash表裡邊可以儲存多個域和對應的變數;

  

  3. List:列表,類似於相當於資料結構的鏈表,通過它還可實現棧和隊列,前面的string(這名字取得很怪異)儲存一一對應的單個索引值對,它則存多個值(沒有鍵),重點是作為一個列表結構,它可以分別從左右(表頭和表尾)進行插入和彈出變數值,求長度等一系列操作,這也是為什麼稱為列表,下面lpush、rpush命令就是從表左邊和右邊插入變數hello和world;

  

  4. Set:集合,顧名思義也可以儲存多個變數值(沒有鍵),只是符合數學上定義的集合的特性,比如集合中元素不重複,因此set中各個資料對的鍵名也不一樣,它也可以進行並、交、差等運算(下面命令是向集合mySet添加一個值var);

  

5. SortedSet:有序集合,可看做Set的特殊方式,在每一個SortedSet類型集合中,添加一個值(沒有鍵)時,都要給它們指定一個稱為score的變數值,有序有序,得有個欄位來判定它的順序是不,就是這個玩意兒,下面命令建立一個有序集合stset,添加一個值google.com,它的score是10。其實猜也猜得到,既然叫有序集合,既然給定了score,基本確定可以用這個來進行某些排序操作,事實也是這樣;

  

  6. Key:單獨對鍵進行操作,當然對其對應的元素值也有影響,如查看鍵對應元素的資料類型、鍵對應元素的存活時間、重設存活時間、返回所有鍵、正則形式查看相關鍵名、刪除鍵(元素也跟著沒了)等等。它不是資料類型,實際上,Redis把Hash表表名、List列表表名、集合Set名都稱為鍵,整個集合或者表是它的值。下面是type命令查看它儲存值得類型,它儲存的是List列表。

  

  上面這些命令,找個參考文檔走一遍,很快就能玩熟,個人在這兒也只是泛泛而談。那麼在實際項目中,通常是怎樣使用緩衝呢?不往大的談,比如微博的緩衝設計,這可能涉及到架構層面的東西了,單說作為一個有一定訪問量的後台,又想使用緩衝來提高速度的情況下。

  一個重要的原則(個人目前碰得多的情況)是,以傳入的參數來拼鍵名,以這個鍵名來存取值。比如現在Model模型類裡面有個方法:getUserInfoByUid($params),傳入包含uid欄位的數組查詢這個使用者的資訊,在這兒使用緩衝,先取快取資料,取不到則去查資料庫,然後重新加入緩衝,最後返回結果資料,這也是使用緩衝的一個通用流程。

  以Memcache為例,先看代碼,定義一個Cache類:

    /**     * Memcache緩衝類     */    define('CACHE_HOST', '127.0.0.1');    define('CACHE_PORT', 11211);        class Cache{        private static $instance = null;                  private $_cache = null;  // 快取作業對象                const prefix = 'APP|';  // 緩衝索引值的首碼,為該項目名稱                 private function __construct(){            if($this->_cache === null){                try{                    $this->_cache = new Memcache;                    if(!$this->_cache->connect(CACHE_HOST, CACHE_PORT)){                        exit('connect failed');                    }                                    }                catch(Exception $e){                    echo 'ERROR: '.$e->getMessage();                }                            }        }        /**          * 返回單例         */        public static function getInstance(){            if(self::$instance === null){                self::$instance = new self();            }            return self::$instance;        }        /**         * 產生鍵名         */        private function genKey($key){            return Cache::prefix.$key;        }                public function __destruct(){            if($this->_cache !== null){                return $this->_cache->close();            }        }        /**         * 添加元素,設定到期時間         */        public function add($key, $val, $expire = 3600){            return $this->_cache->add($this->genKey($key), $val, 0, $expire);        }        /**         * 重設元素         */        public function set($key, $val, $expire = 3600){            echo 'cache key: '.$this->genKey($key).'
'; // test return $this->_cache->set($this->genKey($key), $val, 0, $expire); } /** * 擷取元素 */ public function get($key){ return $this->_cache->get($this->genKey($key)); } /** * 自增 */ public function increment($key, $val = 1){ return $this->_cache->increment($this->genKey($key), $val); } /** * 自減 */ public function decrement($key, $val = 1){ return $this->_cache->decrement($this->genKey($key), $val); } /** * 刪除元素 */ public function delete($key, $timeout = 0){ return $this->_cache->delete($this->genKey($key), $timeout); } /** * 刪除所有元素 */ public function flush(){ return $this->_cache->flush(); } }

  然後定義一個操作資料庫操作類:

    /**     * 資料庫操作     */    define('DB_NAME', 'test');    define('DB_HOST', '127.0.0.1');    define('DB_PORT', 3306);    define('DB_USER', 'root');    define('DB_PASS', '1234');        class Dal{        private static $instance = null;                public $pdo = null;                        private function __construct(){            try{                $dsn = 'mysql:dbname='.DB_NAME.';host='.DB_HOST.':'.DB_PORT;                $this->pdo = new PDO($dsn, DB_USER, DB_PASS);            }            catch(PDOException $e){                echo 'Error: '.$e->getMessage();                return false;            }        }        //擷取執行個體        public static function getInstance(){            if(self::$instance === null){                self::$instance = new self();            }            return self::$instance;        }        // 擷取使用者資訊        public function getUserInfoByUid($uid){            $sql = sprintf('select * from tab1 where uid = %s limit 1', $uid);            $stmt = $this->pdo->query($sql);            return $stmt->fetch(PDO::FETCH_ASSOC);        }    }

  而一般來說,通常是在Model模型類中才對資料進行讀寫,因此再定義一個UserModel類,盡量從簡,不再去寫可能會有的Model公用公用基類。

    include 'Dal.php';    include 'Cache.php';        class UserModel{        private static $instance = null;                public $cache = null;                public $db = null;                // 緩衝鍵名部分,通過函數名稱及參數拼接        const cache_get_userinfo_uid = 'GET|USER|INFO|BY|UID|%s';                private function __construct(){            // 擷取對應類執行個體            $this->cache = Cache::getInstance();            $this->db = Dal::getInstance();        }                public static function getInstance(){            if(self::$instance === null){                self::$instance = new self();            }            return self::$instance;        }                /**         根據uid擷取使用者資訊         */        public function getUserInfoByUid($params, $timeout = 3600){            // 缺少必要參數uid,返回            if(empty($params['uid'])){                return null;            }                // 拼接緩衝鍵名            $key = sprintf(self::cache_get_userinfo_uid, $params['uid']);                                // 擷取快取資料            $data = json_decode($this->cache->get($key), true);                        echo 'cache=>
';            var_dump($data);                        // 緩衝為空白            if(!$data){                $data = $this->db->getUserInfoByUid($params['uid']);                if(empty($data)){                    return null;                }                // 在資料庫中擷取到資料後,重新寫入緩衝                $this->cache->set($key, json_encode($data), $timeout);            }            else{                            }            return $data;        }    }    // 測試    $data = UserModel::getInstance()->getUserInfoByUid(array('uid'=>17653), 5);    echo 'UserModel=>
';    var_dump($data);

  這裡,我們要通過使用者的uid擷取一個使用者的資訊,這個查詢條件是uid,傳過來的必要參數是uid,本來我們就是要緩衝每個使用者的資料,以期在重複查詢時速度更快,因此在定義緩衝的鍵時,就可以考慮使用uid。但是單單使用這個uid不行,因為假如其他的表中也有根據uid查詢的,就會出現鍵名重複,造成了資料混亂,好我們使用的是getUserInfoByUid這個方法,可以把方法名也拼進去,因此在UserModel類中,定義了一個常量cache_get_userinfo_uid,它最後的%s就是為了拼接參數uid。但是,這樣可能還是不行,現在公司啟動了另外一個項目,也要用到這個表,某人寫的方法跟這個方法名完全一樣,畢竟函數名字又沒有專利,而且營運一般單獨拿伺服器出來作為緩衝使用,幾個項目公用一台伺服器,資料寫在一個緩衝池裡也正常,此時可以考慮再加個首碼---工程名稱(或說項目),因此,在Cache類中,又定義了一個常量prefix,給這個項目叫APP,這樣基本就可以保證不會衝突了。當然如果這是一個組的子項目,這個組還有其他項目,這個組叫Star的話,可以再將項目組名稱加在前面。

  看看效果,第一次(左邊)運行讀取cache是空的,但是可以看到列印的緩衝鍵名,

  

  第二次(右邊)cache就有資料了,因為我把列印緩衝鍵名的地方放在Cache類的set方法中,第二次直接讀到了快取資料,沒有去連資料庫,自然也沒有重設緩衝,所以沒列印cache key。

  扯了半天就是要保證鍵名不衝突,也夾雜了一般在一個項目中怎樣使用緩衝,有幾個供參考的方式:

  1. 當前參數,可以有幾個拼接幾個,這裡只舉了個uid,假如還有name, age等等,可以都放在後面,具體怎麼弄看個人喜好或者代碼規範;

  2. 當前方法名稱,盡量接近當前方法名稱,拼在緩衝key中間;

  3. 當前項目名稱作為首碼(可選),更安全;

  4. 所在項目組名稱,再次作為首碼拼進來(一般用不到)。

  然後就是,在Model模型類中,先取緩衝,取不到則讀資料庫,讀到後不要忘了寫入緩衝,設定好到期時間等細節,就這麼一個流程。

  當然你還可以做得更加細緻,比如讀取到快取資料後,再檢測下它的到期時間,發現還有5秒就要到期了,於是我們重新給它設定為3000秒,這樣下次訪問時又能取到了,不至於下次又讀庫,頻繁地串連資料庫是很耗時的一件事。

  不過我還是覺得Redis好用,Redis中的Hash表最好的啦>-_-<,但恰好機子上還沒配。

  唔......不早了,洗洗睡~=_=

http://www.bkjia.com/PHPjc/950709.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/950709.htmlTechArticle緩衝的使用,緩衝使用 緩衝,提高訪問速度的利器。工作中主要用到的是memcache和redis,它們是B/S軟體,類似練習時裝在機子上的Apache,它...

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.