Maison  >  Article  >  développement back-end  >  Comment réduire l'utilisation de la mémoire de PHP Redis (image et texte)

Comment réduire l'utilisation de la mémoire de PHP Redis (image et texte)

黄舟
黄舟original
2017-03-24 09:34:522575parcourir

Cet article présente principalement des méthodes pour réduire l'utilisation de la mémoire de PHP Redis. A une très bonne valeur de référence. Jetons un coup d'œil avec l'éditeur ci-dessous

1 Avantages de la réduction de l'utilisation de la mémoire Redis

1. Aide à réduire le temps passé à créer et à charger des instantanés

2. Améliorer l'efficacité du chargement et de la réécriture des fichiers AOF

3. Raccourcir le temps nécessaire à la synchronisation depuis le serveur

4. Pas besoin d'ajouter du matériel supplémentaire Cela permet à Redis de stocker plus de données

2. Structure courte

Redis fournit un ensemble d'options de configuration pour les listes, les ensembles, les hachages et les ensembles ordonnés, ces options permettent à Redis de stocker des structures plus courtes dans un manière plus économique.

2.1, ziplistListe compressée (liste, hachage, avec suite et)

Méthodes de stockage habituellement utilisées

Lorsque la longueur de la liste, du hachage ou de l'ensemble commandé est courte ou que le volume est petit, redis utilisera une méthode de stockage compacte appelée ziplist est utilisé pour stocker ces structures.

Ziplist est une représentation non structurée de trois types d'objets différents : liste, hachage et ensemble ordonné. Il stocke les données de manière sérialisée. Ces données sérialisées sont lues à chaque fois. Elles doivent être décodées à chaque fois. récupéré et codé à chaque fois qu’il est écrit.

La différence entre une liste bidirectionnelle et une liste compressée :

Afin de décompresser la liste et d'économiser plus de mémoire que les autres données structures, nous prenons la structure de liste comme exemple. Faites des recherches approfondies.

Liste bidirectionnelle typique

Dans une liste bidirectionnelle typique, chaque valeur est représentée par un nœud. Chaque nœud aura un pointeur vers le nœud précédent et le nœud suivant dans la liste chaînée, ainsi qu'un pointeur vers la valeur

string contenue dans le nœud.

La valeur de chaîne contenue dans chaque nœud sera divisée en trois parties pour le stockage. Inclut la longueur de la chaîne, le nombre d'octets disponibles restant dans la valeur de la chaîne et la chaîne terminée par un caractère nul elle-même.

Exemple :

Si un certain nœud stocke la chaîne 'abc', on estime de manière prudente qu'il nécessite 21 octets de surcharge supplémentaire (trois pointeurs + deux caractères int+null sont : 3*4 +2*4+1=21)

D'après l'exemple, on peut voir que le stockage d'une chaîne de 3 octets nécessite au moins 21 octets de surcharge supplémentaire.

ziplist

Une ziplist est une séquence de nœuds, chacun contenant deux longueurs et une chaîne. La première longueur enregistre la longueur du nœud précédent (utilisée pour parcourir la liste compressée d'arrière en avant) ; la deuxième longueur est la longueur du point actuel dans l'enregistrement ;

Exemple :

stocke la chaîne 'abc'. Les deux longueurs peuvent être stockées avec 1 octet, donc la surcharge supplémentaire est de 2 octets (les deux longueurs sont 1 +1 = 2)

Conclusion :

La compression de la liste réduit la surcharge supplémentaire en évitant de stocker des pointeurs et des métadonnées supplémentaires.

Configuration :

#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
Liste de tests :

1. Créer un fichier test.php

#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条数据
}
?>

La liste de tests contient actuellement 512 éléments de données, ce qui ne dépasse pas la limite dans le

fichier de configuration

2. Aller au test - Insérez une autre donnée dans la liste

À l'heure actuelle, la liste de test contient 513 données, ce qui est supérieur à la limite de 512 dans le fichier de configuration , et l'

index sera abandonné. La méthode de stockage de la liste zip adopte sa méthode de stockage de liste liée d'origine

le hachage et l'ensemble ordonné sont les mêmes.

2.2, intsetintegerset (set)

Prérequis, tous les membres contenus dans l'ensemble peuvent être des analyses comme un entier décimal.

Le stockage des collections dans des

tableaux ordonnés peut non seulement réduire la consommation de mémoire, mais également améliorer la vitesse d'exécution des opérations de collecte.

Configuration :

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

Test :

Créer un fichier test.php

#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()

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn