您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關如何降低PHP中Redis內存占用的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
1、降低redis內存占用的優點
1、有助于減少創建快照和加載快照所用的時間
2、提升載入AOF文件和重寫AOF文件時的效率
3、縮短從服務器進行同步所需的時間
4、無需添加額外的硬件就可以讓redis存貯更多的數據
2、短結構
Redis為列表、集合、散列、有序集合提供了一組配置選項,這些選項可以讓redis以更節約的方式存儲較短的結構。
2.1、ziplist壓縮列表(列表、散列、有續集和)
通常情況下使用的存儲方式
當列表、散列、有序集合的長度較短或者體積較小的時候,redis將會采用一種名為ziplist的緊湊存儲方式來存儲這些結構。
ziplist是列表、散列、有序集合這三種不同類型的對象的一種非結構化表示,它會以序列化的方式存儲數據,這些序列化的數據每次被讀取的時候都需要進行解碼,每次寫入的時候也要進行編碼。
雙向列表與壓縮列表的區別:
為了了解壓縮列表比其他數據結構更加節約內存,我們以列表結構為例進行深入研究。
典型的雙向列表
在典型雙向列表里面,每個值都都會有一個節點表示。每個節點都會帶有指向鏈表前一個節點和后一個節點的指針,以及一個指向節點包含的字符串值的指針。
每個節點包含的字符串值都會分為三部分進行存儲。包括字符串長度、字符串值中剩余可用字節數量、以空字符結尾的字符串本身。
例子:
假若一個某個節點存儲了'abc'字符串,在32位的平臺下保守估計需要21個字節的額外開銷(三個指針+兩個int+空字符即:3*4+2*4+1=21)
由例子可知存儲一個3字節字符串就需要付出至少21個字節的額外開銷。
ziplist
壓縮列表是由節點組成的序列,每個節點包含兩個長度和一個字符串。第一個長度記錄前一個節點的長度(用于對壓縮列表從后向前遍歷);第二個長度是記錄本當前點的長度;被存儲的字符串。
例子:
存儲字符串'abc',兩個長度都可以用1字節來存儲,因此所帶來的額外開銷為2字節(兩個長度即1+1=2)
結論:
壓縮列表是通過避免存儲額外的指針和元數據,從而達到降低額外的開銷。
配置:
#list list-max-ziplist-entries 512 #表示允許包含的最大元素數量 list-max-ziplist-value 64 #表示壓縮節點允許存儲的最大體積 #hash #當超過任一限制后,將不會使用ziplist方式進行存儲 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 #zset zset-max-ziplist-entries 128 zset-max-ziplist-value 64
測試list:
1、建立test.php文件
#test.php <?php $redis=new Redis(); $redis->connect('192.168.95.11','6379'); for ($i=0; $i<512 ; $i++) { $redis->lpush('test-list',$i.'-test-list'); #往test-list推入512條數據 } ?>
此時的test-list中含有512條數據,沒有超除配置文件中的限制
2、往test-list中再推入一條數據
此時test-list含有513條數據,大于配置文件中限制的512條,索引將放棄ziplist存儲方式,采用其原來的linkedlist存儲方式
散列與有序集合同理。
2.2、intset整數集合(集合)
前提條件,集合中包含的所有member都可以被解析為十進制整數。
以有序數組的方式存儲集合不僅可以降低內存消耗,還可以提升集合操作的執行速度。
配置:
set-max-intset-entries 512 #限制集合中member個數,超出則不采取intset存儲
測試:
建立test.php文件
#test.php <?php $redis=new Redis(); $redis->connect('192.168.95.11','6379'); for ($i=0; $i<512 ; $i++) { $redis->sadd('test-set',$i); #給集合test-set插入512個member } ?>
2.3、性能問題
不管列表、散列、有序集合、集合,當超出限制的條件后,就會轉換為更為典型的底層結構類型。因為隨著緊湊結構的體積不斷變大,操作這些結構的速度將會變得越來越慢。
測試:
#將采用list進行代表性測試
測試思路:
1、在默認配置下往test-list推入50000條數據,查看所需時間;接著在使用rpoplpush將test-list數據全部推入到新列表list-new中,查看所需時間
2、修改配置,list-max-ziplist-entries 100000,再執行上面的同樣操作
3、對比時間,得出結論
默認配置下測試:
1、插入數據,查看時間
#test1.php <?php header("content-type: text/html;charset=utf8;"); $redis=new Redis(); $redis->connect('192.168.95.11','6379'); $start=time(); for ($i=0; $i<50000 ; $i++) { $redis->lpush('test-list',$i.'-aaaassssssddddddkkk'); } $end=time(); echo "插入耗時為:".($end-$start).'s'; ?>
結果耗時4秒
2、執行相應命令,查看耗時
#test2.php <?php header("content-type: text/html;charset=utf8;"); $redis=new Redis(); $redis->connect('192.168.95.11','6379'); $start=time(); $num=0; while($redis->rpoplpush('test-list','test-new')) { $num+=1; } echo '執行次數為:'.$num."<br/>"; $end=time(); echo "耗時為:".($end-$start).'s'; ?>
更改配置文件下測試
1、先修改配置文件
list-max-ziplist-entries 100000 #將這個值修改大一點,可以更好的凸顯對性能的影響
list-max-ziplist-value 64 #此值可不做修改
2、插入數據
執行test1.php
結果為:耗時12s
3、執行相應命令,查看耗時
執行test2.php
結果為:執行次數:50000,耗時12s
結論:
在本機中執行測試50000條數據就相差8s,若在高并發下,長壓縮列表和大整數集合將起不到任何的優化,反而使得性能降低。
3、片結構
分片的本質就是基于簡單的規則將數據劃分為更小的部分,然后根據數據所屬的部分來決定將數據發送到哪個位置上。很多數據庫使用這種技術來擴展存儲空間,并提高自己所能處理的負載量。
結合前面講到的,我們不難發現分片結構對于redis的重要意義。因此我們需要在配置文件中關于ziplist以及intset的相關配置做出適當的調整。
3.1、分片式散列
#ShardHash.class.php
<?php class ShardHash { private $redis=''; #存儲redis對象 /** * @desc 構造函數 * * @param $host string | redis主機 * @param $port int | 端口 */ public function __construct($host,$port=6379) { $this->redis=new Redis(); $this->redis->connect($host,$port); } /** * @desc 計算某key的分片ID * * @param $base string | 基礎散列 * @param $key string | 要存儲到分片散列里的鍵名 * @param $total int | 預計非數字分片總數 * * @return string | 返回分片鍵key */ public function shardKey ($base,$key,$total) { if(is_numeric($key)) { $shard_id=decbin(substr(bindec($key),0,5)); #取$key二進制高五位的十進制值 } else { $shard_id=crc32($key)%$shards; #求余取模 } return $base.'_'.$shard_id; } /** * @desc 分片式散列hset操作 * * @param $base string | 基礎散列 * @param $key string | 要存儲到分片散列里的鍵名 * @param $total int | 預計元素總數 * @param $value string/int | 值 * * @return bool | 是否hset成功 */ public function shardHset($base,$key,$total,$value) { $shardKey=$this->shardKey($base,$key,$total); return $this->redis->hset($shardKey,$key,$value); } /** * @desc 分片式散列hget操作 * * @param $base string | 基礎散列 * @param $key string | 要存儲到分片散列里的鍵名 * @param $total int | 預計元素總數 * * @return string/false | 成功返回value */ public function shardHget($base,$key,$total) { $shardKey=$this->shardKey($base,$key,$total); return $this->redis->hget($shardKey,$key); } } $obj=new ShardHash('192.168.95.11'); echo $obj->shardHget('hash-','key',500); ?>
散列分片主要是根據基礎鍵以及散列包含的鍵計算出分片鍵ID,然后再與基礎鍵拼接成一個完整的分片鍵。在執行hset與hget以及大部分hash命令時,都需要先將key(field)通過shardKey方法處理,得到分片鍵才能夠進行下一步操作。
3.2、分片式集合
如何構造分片式集合才能夠讓它更節省內存,性能更加強大呢?主要的思路就是,將集合里面的存儲的數據盡量在不改變其原有功能的情況下轉換成可以被解析為十進制的數據。根據前面所講到的,當集合中的所有成員都能夠被解析為十進制數據時,將會采用intset存儲方式,這不僅能夠節省內存,而且還可以提高響應的性能。
例子:
假若要某個大型網站需要存儲每一天的唯一用戶訪問量。那么就可以使用將用戶的唯一標識符轉化成十進制數字,再存入分片式set中。
#ShardSet.class.php
<?php class ShardSet { private $redis=''; #存儲redis對象 /** * @desc 構造函數 * * @param $host string | redis主機 * @param $port int | 端口 */ public function __construct($host,$port=6379) { $this->redis=new Redis(); $this->redis->connect($host,$port); } /** * @desc 根據基礎鍵以及散列包含的鍵計算出分片鍵 * * @param $base string | 基礎散列 * @param $key string | 要存儲到分片散列里的鍵名 * @param $total int | 預計分片總數 * * @return string | 返回分片鍵key */ public function shardKey ($base,$member,$total=512) { $shard_id=crc32($member)%$shards; #求余取模 return $base.'_'.$shard_id; } /** * @desc 計算唯一用戶日訪問量 * * @param $member int | 用戶唯一標識符 * * @return string | ok表示count加1 false表示用戶今天已經訪問過不加1 */ public function count($member) { $shardKey=$this->shardKey('count',$member,$total=10); #$totla調小一點用于測試 $exists=$this->redis->sismember($shardKey,$member); if(!$exists) #判斷member今天是否訪問過 { $this->redis->sadd($shardKey,$member); $this->redis->incr('count'); $ttl1=$this->redis->ttl('count'); if($ttl1===-1) $this->redis->expireat('count',strtotime(date('Y-m-d 23:59:59'))); #設置過期時間 $ttl2=$this->redis->ttl($shardKey); if($ttl2===-1) { $this->redis->expireat("$shardKey",strtotime(date('Y-m-d 23:59:59'))); #設置過期時間 #echo $shardKey; #測試使用 } #echo $shardKey; #測試使用 return 'ok'; } return 'false'; } } $str=substr(md5(uniqid()), 0, 8); #取出前八位 #將$str作為客戶的唯一標識符 $str=hexdec($str); #將16進制轉換為十進制 $obj=new ShardSet('192.168.95.11'); $obj->count($str); ?>
4、將信息打包轉換成存儲字節
結合前面所講的分片技術,采用string分片結構為大量連續的ID用戶存儲信息。
使用定長字符串,為每一個ID分配n個字節進行存儲相應的信息。
接下來我們將采用存儲用戶國家、省份的例子進行講解:
假若某個用戶需要存儲中國、廣東省這兩個信息,采用utf8字符集,那么至少需要消耗5*3=15個字節。如果網站的用戶量大的話,這樣的做法將會占用很多資源。接下來我們采用的方法每個用戶僅僅只需要占用兩個字節就可以完成存儲信息。
具體思路步驟:
1、首先我們為國家、以及各國家的省份信息建立相應的'信息表格'
2、將'信息表格'建好后,也意味著每個國家,省份都有相應的索引號
3、看到這里大家應該都想到了吧,對就是使用兩個索引作為用戶存儲的信息,不過需要注意的是我們還需要對這兩個索引進行相應的處理
4、將索引當做ASCII碼,將其轉換為對應ASCII(0~255)所指定的字符
5、使用前面所講的分片技術,定長分片string結構,將用戶的存儲位置找出來(redis中一個string不能超過512M)
6、實現信息的寫入以及取出(getrange、setrange)
實現代碼:
#PackBytes.class.php
<?php #打包存儲字節 #存儲用戶國家、省份信息 class PackBytes { private $redis=''; #存儲redis對象 /** * @desc 構造函數 * * @param $host string | redis主機 * @param $port int | 端口 */ public function __construct($host,$port=6379) { $this->redis=new Redis(); $this->redis->connect($host,$port); } /** * @desc 處理并緩存國家省份數據 * @param $countries string | 第一類數據,國家字符串 * @param $provinces 二維array | 第二類數據,各國省份數組 * @param $cache 1/0 | 是否使用緩存,默認0不使用 * * @return array | 返回總數據 */ public function dealData($countries,$provinces,$cache=0) { if($cache) { $result=$this->redis->get('cache_data'); if($result) return unserialize($result); } $arr=explode(' ',$countries); $areaArr[]=$arr; $areaArr[]=$provinces; $cache_data=serialize($areaArr); $this->redis->set('cache_data',$cache_data); return $areaArr; } /** * @desc 將具體信息按表索引轉換成編碼信息 * * @param $countries,$provinces,$cache| 參考dealData方法 * @param $country string | 具體信息--國家 * @param $province string | 具體信息--省份 * * @return string | 返回轉換的編碼信息 */ public function getCode($countries,$provinces,$country,$province,$cache=0) { $dataArr=$this->dealData($countries,$provinces,$cache=0); $result=array_search($country, $dataArr[0]); #查找數組中是否含有data1 if($result===false) #判斷是否存在 return chr(0).chr(0); #不存在則返回初始值 $code=chr($result); $result=array_search($province, $dataArr[1][$country]); #查找數組中是否含有data2 if($result===false) return $code.chr(0); return $code.chr($result); #返回對應ASCII(0~255)所指定的字符 } /** * @desc 計算用戶存儲編碼數據的相關位置信息 * * @param $userID int | 用戶的ID * * @return array | 返回一個數組 包含數據存儲時的分片ID、以及屬于用戶的存儲位置(偏移量) */ public function savePosition($userID) { $shardSize=pow(2, 3); #每個分片的大小 $position=$userID*2; #user的排位 $arr['shardID']=floor($position/$shardSize); #分片ID $arr['offset']=$position%$shardSize; #偏移量 return $arr; } /** * @desc | 整合方法,將編碼信息存入redis中string相應的位置 * * @param $userID int | 用戶ID * @param $countries string | 第一類數據,國家字符串 * @param $provinces 二維array | 第二類數據,各國省份數組 * @param $country string | 具體信息--國家 * @param $province string | 具體信息--省份 * @param $cache 1/0 | 是否使用緩存,默認0不使用 * * @return 成功返回寫入位置/失敗false */ public function saveCode($userID,$countries,$provinces,$country,$province,$cache=0) { $code=$this->getCode($countries,$provinces,$country,$province,$cache=0); $arr=$this->savePosition($userID); #存儲相關位置信息 return $this->redis->setrange('save_code_'.$arr['shardID'],$arr['offset'],$code); } /** * @desc 獲取用戶的具體國家與省份信息 * * @param $userID int | 用戶ID * * @return array | 返回包含國家和省份信息的數組 */ public function getMessage($userID) { $position=$this->savePosition($userID); $code=$this->redis->getrange('save_code_'.$position['shardID'],$position['offset'],$position['offset']+1); $arr=str_split($code); $areaArr=$this->dealData('', '',$cache=1); #使用緩存數據 $message['country']=$areaArr[0][ord($arr[0])]; $message['province']=$areaArr[1][$message['country']][ord($arr[1])]; return $message; } } header("content-type: text/html;charset=utf8;"); $countries="無 中國 日本 越南 朝鮮 俄羅斯 巴基斯坦 美國"; $provinces=array( '無'=>array('無'), '中國'=>array('無','廣東','湖南','湖北','廣西','云南','湖南','河北'), '日本'=>array('無','龜孫子區','王八區','倭國鬼區','鬼子區','蘿卜頭區'), ); $obj=new PackBytes('192.168.95.11'); /* #數據處理,并將其緩存到redis中 $b=$obj->dealData($countries,$provinces); echo "<pre>"; print_r($b); echo "</pre>";die; */ /* #存儲用戶國家省份信息 $country='中國'; $province='廣東'; $result=$obj->saveCode(0,$countries,$provinces,$country,$province); echo "<pre>"; print_r($result); echo "</pre>"; */ /* #取出用戶國家省份信息 $a=$obj->getMessage(15); echo "<pre>"; print_r($a); echo "</pre>";die; */ ?>
測試:
1、dealData處理后的信息,即為'信息表表格'
2、saveCode()
userID | 國家 | 省份 |
0 | 中國 | 廣東 |
13 | 日本 | 龜孫子區 |
15 | 日本 | 王八區 |
3、getMessage()
感謝各位的閱讀!關于“如何降低PHP中Redis內存占用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。