Heim  >  Artikel  >  Backend-Entwicklung  >  So reduzieren Sie die Speichernutzung von PHP Redis (Bild und Text)

So reduzieren Sie die Speichernutzung von PHP Redis (Bild und Text)

黄舟
黄舟Original
2017-03-24 09:34:522604Durchsuche

In diesem Artikel werden hauptsächlich Methoden zur Reduzierung der Speichernutzung von PHP Redis vorgestellt. Hat einen sehr guten Referenzwert. Werfen wir einen Blick mit dem Editor unten

1. Vorteile der Reduzierung der Redis-Speichernutzung

1. Hilft, den Zeitaufwand für das Erstellen und Laden von Snapshots zu reduzieren

2. Verbessern Sie die Effizienz beim Laden und Neuschreiben von AOF-Dateien

3. Verkürzen Sie die Zeit, die für die Synchronisierung vom Server erforderlich ist

4. Keine Notwendigkeit, zusätzliche Hardware hinzuzufügen Dadurch kann Redis mehr Daten speichern

2. Kurze Struktur

Redis bietet eine Reihe von Konfigurationsoptionen für Listen, Mengen, Hashes und geordnete Mengen. Diese Optionen ermöglichen es Redis, kürzere Strukturen in einem zu speichern wirtschaftlicher Weg.

2.1, zipListeKomprimierte Liste (Liste, Hash, mit Fortsetzung und)

Normalerweise verwendete Speichermethoden

Wenn die Länge der Liste, des Hashs oder des geordneten Satzes kurz oder das Volumen klein ist, verwendet Redis eine kompakte Speichermethode namens Ziplist dient der Speicherung dieser Strukturen.

Ziplist ist eine unstrukturierte Darstellung von drei verschiedenen Objekttypen: Liste, Hash und geordneter Satz. Sie speichert Daten in serialisierter Form. Diese serialisierten Daten müssen jedes Mal dekodiert werden jedes Mal abgerufen und codiert, wenn es geschrieben wird.

Der Unterschied zwischen einer Zwei-Wege-Liste und einer komprimierten Liste:

Um die Liste zu dekomprimieren und mehr Speicher als andere Daten zu sparen Strukturen, wir nehmen die Listenstruktur als Beispiel Führen Sie eine eingehende Recherche durch.

Typische Zwei-Wege-Liste

In einer typischen Zwei-Wege-Liste wird jeder Wert durch einen Knoten dargestellt. Jeder Knoten verfügt über einen Zeiger auf den vorherigen Knoten und den nächsten Knoten in der verknüpften Liste sowie einen Zeiger auf den im Knoten enthaltenen String-Wert.

Der in jedem Knoten enthaltene Zeichenfolgenwert wird zur Speicherung in drei Teile geteilt. Enthält die Zeichenfolgenlänge, die Anzahl der im Zeichenfolgenwert verbleibenden verfügbaren Bytes und die nullterminierte Zeichenfolge selbst.

Beispiel:

Wenn ein bestimmter Knoten die Zeichenfolge „abc“ speichert, wird konservativ geschätzt, dass 21 Byte zusätzlicher Overhead erforderlich sind (drei Zeiger + zwei Int+Null-Zeichen ist: 3*4). +2*4+1=21)

Aus dem Beispiel ist ersichtlich, dass das Speichern einer 3-Byte-Zeichenfolge mindestens 21 Byte zusätzlichen Overhead erfordert.

Ziplist

Eine Ziplist ist eine Folge von Knoten, die jeweils zwei Längen und eine Zeichenfolge enthalten. Die erste Länge zeichnet die Länge des vorherigen Knotens auf (wird zum Durchlaufen der komprimierten Liste von hinten nach vorne verwendet); die zweite Länge ist die Länge des aktuellen Punkts in der gespeicherten Zeichenfolge;

Beispiel:

speichert die Zeichenfolge „abc“ Beide Längen können in 1 Byte gespeichert werden, sodass der zusätzliche Overhead 2 Bytes beträgt (die beiden Längen sind 1 +1=2)

Fazit:

Das Komprimieren der Liste reduziert den zusätzlichen Overhead, indem das Speichern zusätzlicher Zeiger und Metadaten vermieden wird.

Konfiguration:

#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

Testliste:

1. Test.php-Datei erstellen

#test.php
<?php
$redis=new Redis();
$redis->connect(&#39;192.168.95.11&#39;,&#39;6379&#39;);
for ($i=0; $i<512 ; $i++) 
{ 
  $redis->lpush(&#39;test-list&#39;,$i.&#39;-test-list&#39;); #往test-list推入512条数据
}
?>

Die Testliste enthält derzeit 512 Daten, was den Grenzwert in der Konfigurationsdatei

2 nicht überschreitet. Gehe zum Test – Schiebe ein weiteres Datenelement in die Liste

Zu diesem Zeitpunkt enthält die Testliste 513 Daten, was größer ist als der Grenzwert von 512 in der Konfigurationsdatei . Der Index wird aufgegeben. Die Ziplist-Speichermethode übernimmt die ursprüngliche Linkedlist-Speichermethode

Hashing und geordneter Satz sind gleich.

2.2, intsetintegerset (set)

Voraussetzung, alle im Set enthaltenen Mitglieder können als analysiert werden eine dezimale ganze Zahl.

Das Speichern von Sammlungen in geordneten Arrays kann nicht nur den Speicherverbrauch reduzieren, sondern auch die Ausführungsgeschwindigkeit von Sammlungsvorgängen verbessern.

Konfiguration:

set-max-intset-entries  512   #限制集合中member个数,超出则不采取intset存储

Test:

test.php-Datei erstellen

#test.php
<?php
$redis=new Redis();
$redis->connect(&#39;192.168.95.11&#39;,&#39;6379&#39;);
for ($i=0; $i<512 ; $i++) 
{ 
  $redis->sadd(&#39;test-set&#39;,$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(&#39;192.168.95.11&#39;,&#39;6379&#39;);
$start=time();
for ($i=0; $i<50000 ; $i++) 
{ 
  $redis->lpush(&#39;test-list&#39;,$i.&#39;-aaaassssssddddddkkk&#39;);
}
$end=time();
echo "插入耗时为:".($end-$start).&#39;s&#39;;
?>

结果耗时4秒

2、执行相应命令,查看耗时

#test2.php
<?php
header("content-type: text/html;charset=utf8;");
$redis=new Redis();
$redis->connect(&#39;192.168.95.11&#39;,&#39;6379&#39;);
$start=time();
$num=0;
while($redis->rpoplpush(&#39;test-list&#39;,&#39;test-new&#39;))
{
  $num+=1;
}
echo &#39;执行次数为:&#39;.$num."<br/>";
$end=time();
echo "耗时为:".($end-$start).&#39;s&#39;;
?>

更改配置文件下测试  

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=&#39;&#39;; #存储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.&#39;_&#39;.$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(&#39;192.168.95.11&#39;);
echo $obj->shardHget(&#39;hash-&#39;,&#39;key&#39;,500);
?>

散列分片主要是根据基础键以及散列包含的键计算出分片键ID,然后再与基础键拼接成一个完整的分片键。在执行hset与hget以及大部分hash命令时,都需要先将key(field)通过shardKey方法处理,得到分片键才能够进行下一步操作。

3.2、分片式集合

如何构造分片式集合才能够让它更节省内存,性能更加强大呢?主要的思路就是,将集合里面的存储的数据尽量在不改变其原有功能的情况下转换成可以被解析为十进制的数据。根据前面所讲到的,当集合中的所有成员都能够被解析为十进制数据时,将会采用intset存储方式,这不仅能够节省内存,而且还可以提高响应的性能。

例子:

假若要某个大型网站需要存储每一天的唯一用户访问量。那么就可以使用将用户的唯一标识符转化成十进制数字,再存入分片式set中。

#ShardSet.class.php

<?php
class ShardSet
{
  private $redis=&#39;&#39;; #存储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.&#39;_&#39;.$shard_id;
  }
  /**
  * @desc 计算唯一用户日访问量
  * 
  * @param $member int | 用户唯一标识符
  *
  * @return string | ok表示count加1 false表示用户今天已经访问过不加1
  */
  public function count($member)
  {
    $shardKey=$this->shardKey(&#39;count&#39;,$member,$total=10); #$totla调小一点用于测试
    $exists=$this->redis->sismember($shardKey,$member); 
    if(!$exists)  #判断member今天是否访问过
    {
      $this->redis->sadd($shardKey,$member);
      $this->redis->incr(&#39;count&#39;);
      $ttl1=$this->redis->ttl(&#39;count&#39;);
      if($ttl1===-1)
        $this->redis->expireat(&#39;count&#39;,strtotime(date(&#39;Y-m-d 23:59:59&#39;))); #设置过期时间
      $ttl2=$this->redis->ttl($shardKey);
      if($ttl2===-1)
      {
        $this->redis->expireat("$shardKey",strtotime(date(&#39;Y-m-d 23:59:59&#39;))); #设置过期时间
        #echo $shardKey; #测试使用
      }
      #echo $shardKey;  #测试使用
      return &#39;ok&#39;;
    }
    return &#39;false&#39;;
  }
}
$str=substr(md5(uniqid()), 0, 8);  #取出前八位
#将$str作为客户的唯一标识符
$str=hexdec($str);   #将16进制转换为十进制
$obj=new ShardSet(&#39;192.168.95.11&#39;);
$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=&#39;&#39;; #存储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(&#39;cache_data&#39;);
      if($result)
        return unserialize($result);
    }
    $arr=explode(&#39; &#39;,$countries);
    $areaArr[]=$arr;
    $areaArr[]=$provinces;
    $cache_data=serialize($areaArr);
    $this->redis->set(&#39;cache_data&#39;,$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[&#39;shardID&#39;]=floor($position/$shardSize);  #分片ID
    $arr[&#39;offset&#39;]=$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(&#39;save_code_&#39;.$arr[&#39;shardID&#39;],$arr[&#39;offset&#39;],$code);
  }
  /**
  * @desc 获取用户的具体国家与省份信息
  *
  * @param $userID int | 用户ID
  *
  * @return array | 返回包含国家和省份信息的数组
  */
  public function getMessage($userID)
  {
    $position=$this->savePosition($userID);
    $code=$this->redis->getrange(&#39;save_code_&#39;.$position[&#39;shardID&#39;],$position[&#39;offset&#39;],$position[&#39;offset&#39;]+1);
    $arr=str_split($code);
    $areaArr=$this->dealData(&#39;&#39;, &#39;&#39;,$cache=1); #使用缓存数据
    $message[&#39;country&#39;]=$areaArr[0][ord($arr[0])];
    $message[&#39;province&#39;]=$areaArr[1][$message[&#39;country&#39;]][ord($arr[1])];
    return $message;
  }
}
header("content-type: text/html;charset=utf8;");
$countries="无 中国 日本 越南 朝鲜 俄罗斯 巴基斯坦 美国";
$provinces=array(
    &#39;无&#39;=>array(&#39;无&#39;),
    &#39;中国&#39;=>array(&#39;无&#39;,&#39;广东&#39;,&#39;湖南&#39;,&#39;湖北&#39;,&#39;广西&#39;,&#39;云南&#39;,&#39;湖南&#39;,&#39;河北&#39;),
    &#39;日本&#39;=>array(&#39;无&#39;,&#39;龟孙子区&#39;,&#39;王八区&#39;,&#39;倭国鬼区&#39;,&#39;鬼子区&#39;,&#39;萝卜头区&#39;),
  );
$obj=new PackBytes(&#39;192.168.95.11&#39;);
/*
#数据处理,并将其缓存到redis中
$b=$obj->dealData($countries,$provinces);
echo "<pre class="brush:php;toolbar:false">";
print_r($b);
echo "
";die; */ /* #存储用户国家省份信息 $country='中国'; $province='广东'; $result=$obj->saveCode(0,$countries,$provinces,$country,$province); echo "
";
print_r($result);
echo "
"; */ /* #取出用户国家省份信息 $a=$obj->getMessage(15); echo "
";
print_r($a);
echo "
";die; */ ?>

测试:

1、dealData处理后的信息,即为'信息表表格'

2、saveCode()

userID 国家 省份
0 中国 广东
13 日本 龟孙子区
15 日本 王八区

3、getMessage()

Das obige ist der detaillierte Inhalt vonSo reduzieren Sie die Speichernutzung von PHP Redis (Bild und Text). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn