降低PHP Redis內(nèi)存占用
1、降低redis內(nèi)存占用的優(yōu)點(diǎn)
1、有助于減少創(chuàng)建快照和加載快照所用的時(shí)間
2、提升載入AOF文件和重寫AOF文件時(shí)的效率
3、縮短從服務(wù)器進(jìn)行同步所需的時(shí)間
4、無需添加額外的硬件就可以讓redis存貯更多的數(shù)據(jù)
2、短結(jié)構(gòu)
Redis為列表、集合、散列、有序集合提供了一組配置選項(xiàng),這些選項(xiàng)可以讓redis以更節(jié)約的方式存儲(chǔ)較短的結(jié)構(gòu)。
2.1、ziplist壓縮列表(列表、散列、有續(xù)集和)
通常情況下使用的存儲(chǔ)方式

當(dāng)列表、散列、有序集合的長度較短或者體積較小的時(shí)候,redis將會(huì)采用一種名為ziplist的緊湊存儲(chǔ)方式來存儲(chǔ)這些結(jié)構(gòu)。
ziplist是列表、散列、有序集合這三種不同類型的對象的一種非結(jié)構(gòu)化表示,它會(huì)以序列化的方式存儲(chǔ)數(shù)據(jù),這些序列化的數(shù)據(jù)每次被讀取的時(shí)候都需要進(jìn)行解碼,每次寫入的時(shí)候也要進(jìn)行編碼。
雙向列表與壓縮列表的區(qū)別:
為了了解壓縮列表比其他數(shù)據(jù)結(jié)構(gòu)更加節(jié)約內(nèi)存,我們以列表結(jié)構(gòu)為例進(jìn)行深入研究。
典型的雙向列表
在典型雙向列表里面,每個(gè)值都都會(huì)有一個(gè)節(jié)點(diǎn)表示。每個(gè)節(jié)點(diǎn)都會(huì)帶有指向鏈表前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)的指針,以及一個(gè)指向節(jié)點(diǎn)包含的字符串值的指針。
每個(gè)節(jié)點(diǎn)包含的字符串值都會(huì)分為三部分進(jìn)行存儲(chǔ)。包括字符串長度、字符串值中剩余可用字節(jié)數(shù)量、以空字符結(jié)尾的字符串本身。
例子:
假若一個(gè)某個(gè)節(jié)點(diǎn)存儲(chǔ)了’abc’字符串,在32位的平臺(tái)下保守估計(jì)需要21個(gè)字節(jié)的額外開銷(三個(gè)指針+兩個(gè)int+空字符即:3*4+2*4+1=21)
由例子可知存儲(chǔ)一個(gè)3字節(jié)字符串就需要付出至少21個(gè)字節(jié)的額外開銷。
ziplist
壓縮列表是由節(jié)點(diǎn)組成的序列,每個(gè)節(jié)點(diǎn)包含兩個(gè)長度和一個(gè)字符串。第一個(gè)長度記錄前一個(gè)節(jié)點(diǎn)的長度(用于對壓縮列表從后向前遍歷);第二個(gè)長度是記錄本當(dāng)前點(diǎn)的長度;被存儲(chǔ)的字符串。
例子:
存儲(chǔ)字符串’abc’,兩個(gè)長度都可以用1字節(jié)來存儲(chǔ),因此所帶來的額外開銷為2字節(jié)(兩個(gè)長度即1+1=2)
結(jié)論:
壓縮列表是通過避免存儲(chǔ)額外的指針和元數(shù)據(jù),從而達(dá)到降低額外的開銷。
配置:
#list list-max-ziplist-entries 512 #表示允許包含的最大元素?cái)?shù)量 list-max-ziplist-value 64 #表示壓縮節(jié)點(diǎn)允許存儲(chǔ)的最大體積 #hash #當(dāng)超過任一限制后,將不會(huì)使用ziplist方式進(jìn)行存儲(chǔ) 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條數(shù)據(jù)
}
?>
此時(shí)的test-list中含有512條數(shù)據(jù),沒有超除配置文件中的限制
2、往test-list中再推入一條數(shù)據(jù)

此時(shí)test-list含有513條數(shù)據(jù),大于配置文件中限制的512條,索引將放棄ziplist存儲(chǔ)方式,采用其原來的linkedlist存儲(chǔ)方式
散列與有序集合同理。
2.2、intset整數(shù)集合(集合)
前提條件,集合中包含的所有member都可以被解析為十進(jìn)制整數(shù)。
以有序數(shù)組的方式存儲(chǔ)集合不僅可以降低內(nèi)存消耗,還可以提升集合操作的執(zhí)行速度。
配置:
set-max-intset-entries 512 #限制集合中member個(gè)數(shù),超出則不采取intset存儲(chǔ)
測試:
建立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個(gè)member
}
?>
2.3、性能問題
不管列表、散列、有序集合、集合,當(dāng)超出限制的條件后,就會(huì)轉(zhuǎn)換為更為典型的底層結(jié)構(gòu)類型。因?yàn)殡S著緊湊結(jié)構(gòu)的體積不斷變大,操作這些結(jié)構(gòu)的速度將會(huì)變得越來越慢。
測試:
#將采用list進(jìn)行代表性測試
測試思路:
1、在默認(rèn)配置下往test-list推入50000條數(shù)據(jù),查看所需時(shí)間;接著在使用rpoplpush將test-list數(shù)據(jù)全部推入到新列表list-new中,查看所需時(shí)間
2、修改配置,list-max-ziplist-entries 100000,再執(zhí)行上面的同樣操作
3、對比時(shí)間,得出結(jié)論
默認(rèn)配置下測試:
1、數(shù)據(jù),查看時(shí)間
#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 "插入耗時(shí)為:".($end-$start).'s';
?>

結(jié)果耗時(shí)4秒
2、執(zhí)行相應(yīng)命令,查看耗時(shí)
#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 '執(zhí)行次數(shù)為:'.$num."<br/>";
$end=time();
echo "耗時(shí)為:".($end-$start).'s';
?>
更改配置文件下測試
1、先修改配置文件
list-max-ziplist-entries 100000 #將這個(gè)值修改大一點(diǎn),可以更好的凸顯對性能的影響
list-max-ziplist-value 64 #此值可不做修改
2、數(shù)據(jù)
執(zhí)行test1.php
結(jié)果為:耗時(shí)12s

3、執(zhí)行相應(yīng)命令,查看耗時(shí)
執(zhí)行test2.php
結(jié)果為:執(zhí)行次數(shù):50000,耗時(shí)12s
結(jié)論:
在本機(jī)中執(zhí)行測試50000條數(shù)據(jù)就相差8s,若在高并發(fā)下,長壓縮列表和大整數(shù)集合將起不到任何的優(yōu)化,反而使得性能降低。
3、片結(jié)構(gòu)
分片的本質(zhì)就是基于簡單的規(guī)則將數(shù)據(jù)劃分為更小的部分,然后根據(jù)數(shù)據(jù)所屬的部分來決定將數(shù)據(jù)發(fā)送到哪個(gè)位置上。很多數(shù)據(jù)庫使用這種技術(shù)來擴(kuò)展存儲(chǔ)空間,并提高自己所能處理的負(fù)載量。
結(jié)合前面講到的,我們不難發(fā)現(xiàn)分片結(jié)構(gòu)對于redis的重要意義。因此我們需要在配置文件中關(guān)于ziplist以及intset的相關(guān)配置做出適當(dāng)?shù)恼{(diào)整。
3.1、分片式散列
#ShardHash.class.php
<?php
class ShardHash
{
private $redis=''; #存儲(chǔ)redis對象
/**
* @desc 構(gòu)造函數(shù)
*
* @param $host string | redis主機(jī)
* @param $port int | 端口
*/
public function __construct($host,$port=6379)
{
$this->redis=new Redis();
$this->redis->connect($host,$port);
}
/**
* @desc 計(jì)算某key的分片ID
*
* @param $base string | 基礎(chǔ)散列
* @param $key string | 要存儲(chǔ)到分片散列里的鍵名
* @param $total int | 預(yù)計(jì)非數(shù)字分片總數(shù)
*
* @return string | 返回分片鍵key
*/
public function shardKey ($base,$key,$total)
{
if(is_numeric($key))
{
$shard_id=decbin(substr(bindec($key),0,5)); #取$key二進(jìn)制高五位的十進(jìn)制值
}
else
{
$shard_id=crc32($key)%$shards; #求余取模
}
return $base.'_'.$shard_id;
}
/**
* @desc 分片式散列hset操作
*
* @param $base string | 基礎(chǔ)散列
* @param $key string | 要存儲(chǔ)到分片散列里的鍵名
* @param $total int | 預(yù)計(jì)元素總數(shù)
* @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 | 基礎(chǔ)散列
* @param $key string | 要存儲(chǔ)到分片散列里的鍵名
* @param $total int | 預(yù)計(jì)元素總數(shù)
*
* @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);
?>
散列分片主要是根據(jù)基礎(chǔ)鍵以及散列包含的鍵計(jì)算出分片鍵ID,然后再與基礎(chǔ)鍵拼接成一個(gè)完整的分片鍵。在執(zhí)行hset與hget以及大部分hash命令時(shí),都需要先將key(field)通過shardKey方法處理,得到分片鍵才能夠進(jìn)行下一步操作。
3.2、分片式集合
如何構(gòu)造分片式集合才能夠讓它更節(jié)省內(nèi)存,性能更加強(qiáng)大呢?主要的思路就是,將集合里面的存儲(chǔ)的數(shù)據(jù)盡量在不改變其原有功能的情況下轉(zhuǎn)換成可以被解析為十進(jìn)制的數(shù)據(jù)。根據(jù)前面所講到的,當(dāng)集合中的所有成員都能夠被解析為十進(jìn)制數(shù)據(jù)時(shí),將會(huì)采用intset存儲(chǔ)方式,這不僅能夠節(jié)省內(nèi)存,而且還可以提高響應(yīng)的性能。
例子:
假若要某個(gè)大型網(wǎng)站需要存儲(chǔ)每一天的唯一用戶訪問量。那么就可以使用將用戶的唯一標(biāo)識(shí)符轉(zhuǎn)化成十進(jìn)制數(shù)字,再存入分片式set中。
#ShardSet.class.php
<?php
class ShardSet
{
private $redis=''; #存儲(chǔ)redis對象
/**
* @desc 構(gòu)造函數(shù)
*
* @param $host string | redis主機(jī)
* @param $port int | 端口
*/
public function __construct($host,$port=6379)
{
$this->redis=new Redis();
$this->redis->connect($host,$port);
}
/**
* @desc 根據(jù)基礎(chǔ)鍵以及散列包含的鍵計(jì)算出分片鍵
*
* @param $base string | 基礎(chǔ)散列
* @param $key string | 要存儲(chǔ)到分片散列里的鍵名
* @param $total int | 預(yù)計(jì)分片總數(shù)
*
* @return string | 返回分片鍵key
*/
public function shardKey ($base,$member,$total=512)
{
$shard_id=crc32($member)%$shards; #求余取模
return $base.'_'.$shard_id;
}
/**
* @desc 計(jì)算唯一用戶日訪問量
*
* @param $member int | 用戶唯一標(biāo)識(shí)符
*
* @return string | ok表示count加1 false表示用戶今天已經(jīng)訪問過不加1
*/
public function count($member)
{
$shardKey=$this->shardKey('count',$member,$total=10); #$totla調(diào)小一點(diǎn)用于測試
$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'))); #設(shè)置過期時(shí)間
$ttl2=$this->redis->ttl($shardKey);
if($ttl2===-1)
{
$this->redis->expireat("$shardKey",strtotime(date('Y-m-d 23:59:59'))); #設(shè)置過期時(shí)間
#echo $shardKey; #測試使用
}
#echo $shardKey; #測試使用
return 'ok';
}
return 'false';
}
}
$str=substr(md5(uniqid()), 0, 8); #取出前八位
#將$str作為客戶的唯一標(biāo)識(shí)符
$str=hexdec($str); #將16進(jìn)制轉(zhuǎn)換為十進(jìn)制
$obj=new ShardSet('192.168.95.11');
$obj->count($str);
?>
4、將信息打包轉(zhuǎn)換成存儲(chǔ)字節(jié)
結(jié)合前面所講的分片技術(shù),采用string分片結(jié)構(gòu)為大量連續(xù)的ID用戶存儲(chǔ)信息。
使用定長字符串,為每一個(gè)ID分配n個(gè)字節(jié)進(jìn)行存儲(chǔ)相應(yīng)的信息。
接下來我們將采用存儲(chǔ)用戶國家、省份的例子進(jìn)行講解:
假若某個(gè)用戶需要存儲(chǔ)中國、廣東省這兩個(gè)信息,采用utf8字符集,那么至少需要消耗5*3=15個(gè)字節(jié)。如果網(wǎng)站的用戶量大的話,這樣的做法將會(huì)占用很多資源。接下來我們采用的方法每個(gè)用戶僅僅只需要占用兩個(gè)字節(jié)就可以完成存儲(chǔ)信息。
具體思路步驟:
1、首先我們?yōu)閲摇⒁约案鲊业氖》菪畔⒔⑾鄳?yīng)的’信息表格’
2、將’信息表格’建好后,也意味著每個(gè)國家,省份都有相應(yīng)的索引號(hào)
3、看到這里大家應(yīng)該都想到了吧,對就是使用兩個(gè)索引作為用戶存儲(chǔ)的信息,不過需要注意的是我們還需要對這兩個(gè)索引進(jìn)行相應(yīng)的處理
4、將索引當(dāng)做ASCII碼,將其轉(zhuǎn)換為對應(yīng)ASCII(0~255)所指定的字符
5、使用前面所講的分片技術(shù),定長分片string結(jié)構(gòu),將用戶的存儲(chǔ)位置找出來(redis中一個(gè)string不能超過512M)
6、實(shí)現(xiàn)信息的寫入以及取出(getrange、setrange)
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
PHP入門教程之?dāng)?shù)組用法匯總(創(chuàng)建,刪除,遍歷,排序等)
這篇文章主要介紹了PHP入門教程之?dāng)?shù)組用法,結(jié)合大量實(shí)例總結(jié)分析了php關(guān)于數(shù)組的創(chuàng)建、打印、遍歷、獲取、排序、插入、刪除等常見操作技巧,需要的朋友可以參考下2016-09-09
PHP遞歸遍歷多維數(shù)組實(shí)現(xiàn)無限分類的方法
這篇文章主要介紹了PHP遞歸遍歷多維數(shù)組實(shí)現(xiàn)無限分類的方法,涉及PHP遞歸操作遍歷數(shù)組的相關(guān)技巧,在聯(lián)動(dòng)菜單及父子欄目設(shè)計(jì)等方面非常具有實(shí)用價(jià)值,需要的朋友可以參考下2016-05-05
PHP 事務(wù)處理數(shù)據(jù)實(shí)現(xiàn)代碼
PHP 事務(wù)處理數(shù)據(jù)實(shí)現(xiàn)代碼,需要的朋友可以參考下。2010-05-05
php 函數(shù)中靜態(tài)變量使用的問題實(shí)例分析
這篇文章主要介紹了php 函數(shù)中靜態(tài)變量使用的問題,結(jié)合實(shí)例形式分析了php 函數(shù)中靜態(tài)變量使用過程中遇到的問題,以及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2020-03-03
php實(shí)現(xiàn)上傳圖片保存到數(shù)據(jù)庫的方法
這篇文章主要介紹了php實(shí)現(xiàn)上傳圖片保存到數(shù)據(jù)庫的方法,可通過將圖片保存在數(shù)據(jù)庫實(shí)現(xiàn)多臺(tái)服務(wù)器共享文件的功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-02-02
PHP字符串word末字符實(shí)現(xiàn)大小寫互換的方法
這篇文章主要介紹了PHP字符串word末字符實(shí)現(xiàn)大小寫互換的方法,是涉及PHP字符串轉(zhuǎn)換非常實(shí)用的技巧,需要的朋友可以參考下2014-11-11
PHP+HTML+JavaScript+Css實(shí)現(xiàn)簡單爬蟲開發(fā)
這篇文章主要為大家詳細(xì)介紹了PHP+HTML+JavaScript+Css實(shí)現(xiàn)簡單爬蟲開發(fā),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03

