降低PHP Redis內(nèi)存占用
1、降低redis內(nèi)存占用的優(yōu)點(diǎn)
1、有助于減少創(chuàng)建快照和加載快照所用的時(shí)間
2、提升載入AOF文件和重寫(xiě)AOF文件時(shí)的效率
3、縮短從服務(wù)器進(jìn)行同步所需的時(shí)間
4、無(wú)需添加額外的硬件就可以讓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)列表、散列、有序集合的長(zhǎng)度較短或者體積較小的時(shí)候,redis將會(huì)采用一種名為ziplist的緊湊存儲(chǔ)方式來(lái)存儲(chǔ)這些結(jié)構(gòu)。
ziplist是列表、散列、有序集合這三種不同類(lèi)型的對(duì)象的一種非結(jié)構(gòu)化表示,它會(huì)以序列化的方式存儲(chǔ)數(shù)據(jù),這些序列化的數(shù)據(jù)每次被讀取的時(shí)候都需要進(jìn)行解碼,每次寫(xiě)入的時(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ǔ)。包括字符串長(zhǎng)度、字符串值中剩余可用字節(jié)數(shù)量、以空字符結(jié)尾的字符串本身。
例子:
假若一個(gè)某個(gè)節(jié)點(diǎn)存儲(chǔ)了’abc’字符串,在32位的平臺(tái)下保守估計(jì)需要21個(gè)字節(jié)的額外開(kāi)銷(xiāo)(三個(gè)指針+兩個(gè)int+空字符即:3*4+2*4+1=21)
由例子可知存儲(chǔ)一個(gè)3字節(jié)字符串就需要付出至少21個(gè)字節(jié)的額外開(kāi)銷(xiāo)。
ziplist
壓縮列表是由節(jié)點(diǎn)組成的序列,每個(gè)節(jié)點(diǎn)包含兩個(gè)長(zhǎng)度和一個(gè)字符串。第一個(gè)長(zhǎng)度記錄前一個(gè)節(jié)點(diǎn)的長(zhǎng)度(用于對(duì)壓縮列表從后向前遍歷);第二個(gè)長(zhǎng)度是記錄本當(dāng)前點(diǎn)的長(zhǎng)度;被存儲(chǔ)的字符串。
例子:
存儲(chǔ)字符串’abc’,兩個(gè)長(zhǎng)度都可以用1字節(jié)來(lái)存儲(chǔ),因此所帶來(lái)的額外開(kāi)銷(xiāo)為2字節(jié)(兩個(gè)長(zhǎng)度即1+1=2)
結(jié)論:
壓縮列表是通過(guò)避免存儲(chǔ)額外的指針和元數(shù)據(jù),從而達(dá)到降低額外的開(kāi)銷(xiāo)。
配置:
#list list-max-ziplist-entries 512 #表示允許包含的最大元素?cái)?shù)量 list-max-ziplist-value 64 #表示壓縮節(jié)點(diǎn)允許存儲(chǔ)的最大體積 #hash #當(dāng)超過(guò)任一限制后,將不會(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
測(cè)試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ù),沒(méi)有超除配置文件中的限制
2、往test-list中再推入一條數(shù)據(jù)
此時(shí)test-list含有513條數(shù)據(jù),大于配置文件中限制的512條,索引將放棄ziplist存儲(chǔ)方式,采用其原來(lái)的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ǔ)
測(cè)試:
建立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、性能問(wèn)題
不管列表、散列、有序集合、集合,當(dāng)超出限制的條件后,就會(huì)轉(zhuǎn)換為更為典型的底層結(jié)構(gòu)類(lèi)型。因?yàn)殡S著緊湊結(jié)構(gòu)的體積不斷變大,操作這些結(jié)構(gòu)的速度將會(huì)變得越來(lái)越慢。
測(cè)試:
#將采用list進(jìn)行代表性測(cè)試
測(cè)試思路:
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、對(duì)比時(shí)間,得出結(jié)論
默認(rèn)配置下測(cè)試:
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'; ?>
更改配置文件下測(cè)試
1、先修改配置文件
list-max-ziplist-entries 100000 #將這個(gè)值修改大一點(diǎn),可以更好的凸顯對(duì)性能的影響
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í)行測(cè)試50000條數(shù)據(jù)就相差8s,若在高并發(fā)下,長(zhǎng)壓縮列表和大整數(shù)集合將起不到任何的優(yōu)化,反而使得性能降低。
3、片結(jié)構(gòu)
分片的本質(zhì)就是基于簡(jiǎn)單的規(guī)則將數(shù)據(jù)劃分為更小的部分,然后根據(jù)數(shù)據(jù)所屬的部分來(lái)決定將數(shù)據(jù)發(fā)送到哪個(gè)位置上。很多數(shù)據(jù)庫(kù)使用這種技術(shù)來(lái)擴(kuò)展存儲(chǔ)空間,并提高自己所能處理的負(fù)載量。
結(jié)合前面講到的,我們不難發(fā)現(xiàn)分片結(jié)構(gòu)對(duì)于redis的重要意義。因此我們需要在配置文件中關(guān)于ziplist以及intset的相關(guān)配置做出適當(dāng)?shù)恼{(diào)整。
3.1、分片式散列
#ShardHash.class.php
<?php class ShardHash { private $redis=''; #存儲(chǔ)redis對(duì)象 /** * @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)通過(guò)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ǔ)每一天的唯一用戶(hù)訪問(wèn)量。那么就可以使用將用戶(hù)的唯一標(biāo)識(shí)符轉(zhuǎn)化成十進(jìn)制數(shù)字,再存入分片式set中。
#ShardSet.class.php
<?php class ShardSet { private $redis=''; #存儲(chǔ)redis對(duì)象 /** * @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ì)算唯一用戶(hù)日訪問(wèn)量 * * @param $member int | 用戶(hù)唯一標(biāo)識(shí)符 * * @return string | ok表示count加1 false表示用戶(hù)今天已經(jīng)訪問(wèn)過(guò)不加1 */ public function count($member) { $shardKey=$this->shardKey('count',$member,$total=10); #$totla調(diào)小一點(diǎn)用于測(cè)試 $exists=$this->redis->sismember($shardKey,$member); if(!$exists) #判斷member今天是否訪問(wèn)過(guò) { $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è)置過(guò)期時(shí)間 $ttl2=$this->redis->ttl($shardKey); if($ttl2===-1) { $this->redis->expireat("$shardKey",strtotime(date('Y-m-d 23:59:59'))); #設(shè)置過(guò)期時(shí)間 #echo $shardKey; #測(cè)試使用 } #echo $shardKey; #測(cè)試使用 return 'ok'; } return 'false'; } } $str=substr(md5(uniqid()), 0, 8); #取出前八位 #將$str作為客戶(hù)的唯一標(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用戶(hù)存儲(chǔ)信息。
使用定長(zhǎng)字符串,為每一個(gè)ID分配n個(gè)字節(jié)進(jìn)行存儲(chǔ)相應(yīng)的信息。
接下來(lái)我們將采用存儲(chǔ)用戶(hù)國(guó)家、省份的例子進(jìn)行講解:
假若某個(gè)用戶(hù)需要存儲(chǔ)中國(guó)、廣東省這兩個(gè)信息,采用utf8字符集,那么至少需要消耗5*3=15個(gè)字節(jié)。如果網(wǎng)站的用戶(hù)量大的話(huà),這樣的做法將會(huì)占用很多資源。接下來(lái)我們采用的方法每個(gè)用戶(hù)僅僅只需要占用兩個(gè)字節(jié)就可以完成存儲(chǔ)信息。
具體思路步驟:
1、首先我們?yōu)閲?guó)家、以及各國(guó)家的省份信息建立相應(yīng)的’信息表格’
2、將’信息表格’建好后,也意味著每個(gè)國(guó)家,省份都有相應(yīng)的索引號(hào)
3、看到這里大家應(yīng)該都想到了吧,對(duì)就是使用兩個(gè)索引作為用戶(hù)存儲(chǔ)的信息,不過(guò)需要注意的是我們還需要對(duì)這兩個(gè)索引進(jìn)行相應(yīng)的處理
4、將索引當(dāng)做ASCII碼,將其轉(zhuǎn)換為對(duì)應(yīng)ASCII(0~255)所指定的字符
5、使用前面所講的分片技術(shù),定長(zhǎng)分片string結(jié)構(gòu),將用戶(hù)的存儲(chǔ)位置找出來(lái)(redis中一個(gè)string不能超過(guò)512M)
6、實(shí)現(xiàn)信息的寫(xiě)入以及取出(getrange、setrange)
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
Thinkphp3.2.3整合phpqrcode生成帶logo的二維碼
這篇文章主要為大家詳細(xì)介紹了Thinkphp3.2.3整合phpqrcode生成帶logo的二維碼的實(shí)現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-07-07php版微信支付api.mch.weixin.qq.com域名解析慢原因與解決方法
這篇文章主要介紹了php版微信支付api.mch.weixin.qq.com域名解析慢原因與解決方法,詳細(xì)分析了微信支付api.mch.weixin.qq.com域名解析慢原因與使用curl_easy_setopt指定ipv4解決ipv6解析問(wèn)題的相關(guān)技巧,需要的朋友可以參考下2016-10-10PHP實(shí)現(xiàn)時(shí)間比較和時(shí)間差計(jì)算的方法示例
這篇文章主要介紹了PHP實(shí)現(xiàn)時(shí)間比較和時(shí)間差計(jì)算的方法,涉及php日期與時(shí)間的轉(zhuǎn)換、運(yùn)算等相關(guān)操作技巧,需要的朋友可以參考下2017-07-07php創(chuàng)建無(wú)限級(jí)樹(shù)型菜單
這篇文章主要介紹了php創(chuàng)建無(wú)限級(jí)樹(shù)型菜單 ,主要使用的是遞歸函數(shù),感興趣的小伙伴們可以參考一下2015-11-11