2017-4-28/PHP实现Redis
Posted 呦呦鹿鸣
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2017-4-28/PHP实现Redis相关的知识,希望对你有一定的参考价值。
谈一谈Redis的数据结构,如果换做php,怎么实现?如果再考虑用上LFU或LRU,又该如何实现?
Redis的数据结构有String、List、Set、Sorted Set、Hash等,而PHP支持String、Array、Object等八种基本数据类型。
如果换成PHP,Redis的String可以用PHP的String实现,Redis的List、Set、Sorted Set可以用PHP的索引数组实现,Redis的Hash可以用PHP的关联数组实现。
String
Redis字符串命令用于管理Redis中的字符串值。
PHP里,可以用字符串实现。数据结构类似于
List
Redis的List是string列表,按插入顺序排序。
PHP里,可以用索引数组实现,数据结构类似于array(value1, value2, value3, ...)。
Set
Redis的Set是string类型的无序集合。集合成员是唯一的,即集合中不能出现重复的数据。
PHP里,去重过后的索引数组就相当于Redis中的Set,所以实现Set的过程与实现List相似,但需要向数组添加数据时进行去重操作,或者在添加数据前先判定该值是否存在,如果存在就不添加。
Sorted Set
Redis的Sorted Set也是string类型的无序集合,且不允许重复的成员。但每个元素都会关联一个double类型的分数,Redis通过分数来为集合中的成员进行从小到大的排序。
PHP的数组里,key不能重复,所以用key表示成员,value可以重复,所以用value表示分数,数据结构类似于array(member1=>score1, member2=>score2, member3=>score3, ...)。
Hash
Redis的Hash是一个string类型的field和value的映射表。
PHP里,可以用关联数组实现,数据结构类似于array(key1=>value1, key2=>value2, key3=>value3, ...)。
实现LFU/LRU的思路:
定义三个关联数组,key都一样,一个数组叫kv数组,存值,形如key => value;一个数组叫kc数组,存值被访问的次数,形如key => count,用于实现LFU,当缓存满了,count值最小的数据即最少被访问的数据,最先被淘汰掉;一个数组叫kt数组,形如key => time,存值最后一次被访问的时间,用于实现LRU,当缓存满了,time值最小的数据即最先被访问的数据(不是最新数据),最先被淘汰掉。
部分代码:
/*php实现redis的string,注释部分为Redis命令,下同*/ <?php class stringCache { var $hitNume; var $requestNum; // 缓存类型 var $type; // 缓存总大小 var $totalSize; // 存值$key=>$string var $data = array(); // 存访问次数$key=>$count var $lfu = array(); // 存访问时间$key=>$time var $lru = array(); public function __construct($cacheType, $totalSize) { $this->type = $cacheType; $this->totalSize = $totalSize; } /* SET key value [EX seconds] [PX milliseconds] [NX|XX] * 将字符串值 value 关联到 key 。 * 如果 key 已经持有其他值, SET 就覆写旧值,无视类型。 * 对于某个原本带有生存时间(TTL)的键来说, 当 SET 命令成功在这个键上执行时, 这个键原有的 TTL 将被清除。 * */ public function add($key, $value) { $this->cleanup(); $this->data[$key] = $value; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } return true; } /* GET key * 返回 key 所关联的字符串值。 * 如果 key 不存在那么返回特殊值 nil 。 * 假如 key 储存的值不是字符串类型,返回一个错误,因为 GET 只能用于处理字符串值。 * */ public function get($key) { $value = ""; $this->requestNum += 1; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } if (is_string($this->data[$key])) { $value = $this->data[$key]; } } return $value; } /* APPEND key value * 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。 * 如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样。 * 返回追加 value 之后 key 中字符串的长度。 */ public function append($key, $value) { $this->requestNum += 1; if ($this->data[$key]) { $this->hitNum += 1; $this->data[$key] .= $value; } else { $this->data[$key] = $value; } $len = strlen($this->data[$key]); if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } return $len; } // 清除缓存 public function cleanup() { if (count($this->data) == $this->totalSize) { if ($this->type == "lfu") { asort($this->lfu); $keys = array_keys($this->lfu); $k = $keys[0]; unset($this->data[$k]); unset($this->lfu[$k]); } if ($this->type == "lru") { asort($this->lru); $keys = array_keys($this->lru); $k = $keys[0]; unset($this->data[$k]); unset($this->lru[$k]); } } return true; } } /*php实现redis的list*/ class listCache { var $hitNume; var $requestNum; // 缓存类型 var $type; // 缓存总大小 var $totalSize; // 存值$key=>$array var $data = array(); // 存访问次数$key=>$count var $lfu = array(); // 存访问时间$key=>$time var $lru = array(); public function __construct($cacheType, $totalSize) { $this->type = $cacheType; $this->totalSize = $totalSize; } /* LINDEX key index * 返回列表 key 中,下标为 index 的元素。 * 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。 * 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。 * 如果 key 不是列表类型,返回一个错误。 */ public function get($key, $index) { $this->requestNum += 1; $result = ""; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $result = $this->data[$key][$index]; } return $result; } // 清除缓存 public function cleanup() { if (count($this->data) == $this->totalSize) { if ($this->type == "lfu") { asort($this->lfu); $keys = array_keys($this->lfu); $k = $keys[0]; unset($this->data[$k]); unset($this->lfu[$k]); } if ($this->type == "lru") { asort($this->lru); $keys = array_keys($this->lru); $k = $keys[0]; unset($this->data[$k]); unset($this->lru[$k]); } } return true; } /* LINSERT key BEFORE|AFTER pivot value * 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。 * 当 pivot 不存在于列表 key 时,不执行任何操作。 * 当 key 不存在时, key 被视为空列表,不执行任何操作。 * 如果 key 不是列表类型,返回一个错误。 * @param $pos before或after,在存在的值之前插入还是之后插入 * @param $pivot 列表里存在的值 * @param $value 要插入的值 * @return 如果命令执行成功,返回插入操作完成之后,列表的长度。 如果没有找到 pivot ,返回 -1 。 如果 key 不存在或为空列表,返回 0 。 */ public function insert($key, $pos, $pivot, $value) { $this->cleanup(); $result = 0; if ($this->data[$key]) { if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $index = array_search($pivot, $this->data[$key]); if ($index !== false) { // 找到pivot if ($pos == "before") { array_splice($this->data[$key], $index, 0, array($value)); } else { // after array_splice($this->data[$key], $index + 1, 0, array($value)); } $result = count($this->data[$key]); } else { $result = -1; } } return $result; } /* LPOP key * 移除并返回列表 key 的头元素。 * 相当于php的array_shift()函数 */ public function lpop($key) { $this->requestNum += 1; $result = ""; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $result = array_shift($this->data[$key]); } return $result; } /* LPUSH key value [value ...] * 将一个或多个值 value 插入到列表 key 的表头 * 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头:比如说,对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a , 这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。 * 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。 * 当 key 存在但不是列表类型时,返回一个错误。 * 返回执行 LPUSH 命令后列表的长度。 * @param string $values 一个或多个值,用“,”隔开 */ public function lpush($key, $values) { $this->cleanup(); $values_arr = explode(",", $values); $cnt = count($values_arr); for ($i = 0; $i <= $cnt - 1; $i++) { $value = array_pop($values_arr); $this->data[$key][] = $value; } if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } return count($this->data[$key]); } /* RPOP key * 移除并返回列表 key 的尾元素。 * 相当于php的array_pop()函数 */ public function rpop($key) { $this->requestNum += 1; $result = ""; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $result = array_pop($this->data[$key]); } return $result; } /* RPUSH key value [value ...] * 将一个或多个值 value 插入到列表 key 的表尾(最右边)。 * 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c, 等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。 * 如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。 * 当 key 存在但不是列表类型时,返回一个错误。 * 返回执行 RPUSH 操作后表的长度。 * @param string $values 一个或多个值,用“,”隔开 */ public function rpush($key, $values) { $this->cleanup(); $values_arr = explode(",", $values); foreach ($values_arr as $v) { $this->data[$key][] = $v; } if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } return count($this->data[$key]); } } /*php实现redis的set*/ class setCache { var $hitNume; var $requestNum; // 缓存类型 var $type; // 缓存总大小 var $totalSize; // 存值$key=>$array var $data = array(); // 存访问次数$key=>$count var $lfu = array(); // 存访问时间$key=>$time var $lru = array(); public function __construct($cacheType, $totalSize) { $this->type = $cacheType; $this->totalSize = $totalSize; } /* SMEMBERS key * 返回集合 key 中的所有成员。 * 不存在的 key 被视为空集合。 */ public function getMembers($key) { $this->requestNum += 1; $members = array(); if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $members = $this->data[$key]; } return $members; } /* SADD key member [member ...] * 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。 * 假如 key 不存在,则创建一个只包含 member 元素作成员的集合。 */ public function add($key, $member) { $this->cleanup(); $this->data[$key][] = $member; $result = array_unique($this->data); $this->data = $result; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } return true; } // 清除缓存 public function cleanup() { if (count($this->data) == $this->totalSize) { if ($this->type == "lfu") { asort($this->lfu); $keys = array_keys($this->lfu); $k = $keys[0]; unset($this->data[$k]); unset($this->lfu[$k]); } if ($this->type == "lru") { asort($this->lru); $keys = array_keys($this->lru); $k = $keys[0]; unset($this->data[$k]); unset($this->lru[$k]); } } return true; } /* SCARD key * 返回集合 key 的基数(集合中元素的数量)。 * 当 key 不存在时,返回 0 。 */ public function count($key) { $this->requestNum += 1; $cnt = 0; if ($this->data[$key]) { $this->hitNum += 1; $cnt = count($this->data[$key]); if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } } return $cnt; } /* SISMEMBER key member * 判断 member 元素是否集合 key 的成员。 * 如果 member 元素是集合的成员,返回 1 。 * 如果 member 元素不是集合的成员,或 key 不存在,返回 0 。 */ public function isMember($key, $member) { $this->requestNum += 1; $flag = 0; if($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } if (in_array($member, $this->data[$key])) { $flag = 1; } } return $flag; } /* SPOP key * 移除并返回集合中的一个随机元素。 * 如果只想获取一个随机元素,但不想该元素从集合中被移除的话,可以使用 SRANDMEMBER 命令。 */ public function randomDel($key) { $this->requestNum += 1; $result = ""; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $randomKey = array_rand($this->data[$key]); $result = $this->data[$key][$randomKey]; unset($this->data[$key][$randomKey]); } return $result; } } /*php实现redis的sorted set*/ class sortedsetCache { var $hitNume; var $requestNum; // 缓存类型 var $type; // 缓存总大小 var $totalSize; // 存值$key=>$array var $data = array(); // 存访问次数$key=>$count var $lfu = array(); // 存访问时间$key=>$time var $lru = array(); public function __construct($cacheType, $totalSize) { $this->type = $cacheType; $this->totalSize = $totalSize; } /* ZADD key score member [[score member] [score member] ...] * 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。 * 如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。 * score 值可以是整数值或双精度浮点数。 * 如果 key 不存在,则创建一个空的有序集并执行 ZADD 操作。 * 当 key 存在但不是有序集类型时,返回一个错误。 * @param array $values [member1=>score1, member2=>score2,……]的数组 */ public function add($key, $values) { $this->cleanup(); foreach ($values as $member => $score) { $this->data[$key][$member] = $score; } asort($this->data[$key]); if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } return true; } // 清除缓存 public function cleanup() { if (count($this->data) == $this->totalSize) { if ($this->type == "lfu") { asort($this->lfu); $keys = array_keys($this->lfu); $k = $keys[0]; unset($this->data[$k]); unset($this->lfu[$k]); } if ($this->type == "lru") { asort($this->lru); $keys = array_keys($this->lru); $k = $keys[0]; unset($this->data[$k]); unset($this->lru[$k]); } } return true; } /* ZRANGE key start stop [WITHSCORES] * 返回有序集 key 中,指定区间内的成员。 * 其中成员的位置按 score 值递增(从小到大)来排序。 * 具有相同 score 值的成员按字典序(lexicographical order)来排列。 * 下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。 * 也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。 * 比如说,当 start 的值比有序集的最大下标还要大,或是 start > stop 时, ZRANGE 命令只是简单地返回一个空列表。 * 另一方面,假如 stop 参数的值比有序集的最大下标还要大,那么 Redis 将 stop 当作最大下标来处理。 * 可以通过使用 WITHSCORES 选项,来让成员和它的 score 值一并返回,返回列表以 value1,score1, ..., valueN,scoreN 的格式表示。 */ // TODO:只考虑了下标为正的情况 public function range($key, $start, $stop, $withScore = "false") { $this->requestNum += 1; $res = array(); $end = $this->card($key) - 1; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } if ($start < $stop && $start < $end) { if ($stop > $end) { $stop = $end; $ks = array_keys($this->data[$key]); for ($i = $start; $i <= $stop; $i++) { $k = $ks[$i]; if ($withScore == "true") { $res[] = array($k => $this->data[$key][$k]); } else { $res[] = $k; } } } } } return $res; } /* ZRANK key member * 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。 * 排名以 0 为底,也就是说, score 值最小的成员排名为 0 。 */ public function rank($key, $member) { $this->requestNum += 1; $rank = 0; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $rank = array_search($member, array_keys($this->data[$key])); } return $rank; } } /*php实现redis的hash*/ class hashCache { var $hitNume; var $requestNum; // 缓存类型 var $type; // 缓存总大小 var $totalSize; // 存值$key=>$array var $data = array(); // 存访问次数$key=>$count var $lfu = array(); // 存访问时间$key=>$time var $lru = array(); public function __construct($cacheType, $totalSize) { $this->type = $cacheType; $this->totalSize = $totalSize; } /* * HDEL key field [field ...] * 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 * fields为key中的域,多个域之间用","隔开 * 返回被成功移除的域的数量,不包括被忽略的域。 */ public function delField($key, $fields) { $i = 0; $this->requestNum += 1; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type == "lfu") { $this->lfu[$key] += 1; } if ($this->type == "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } $fields_arr = explode(",", $fields); foreach ($fields_arr as $field) { if (in_array($this->data[$key][$field], $this->data[$key])) { $i++; unset($this->data[$key][$field]); } } } return $i; } /* * HGET key field * 返回哈希表 key 中给定域 field 的值。 * 当给定域不存在或是给定 key 不存在时,返回 nil 。 */ public function getValue($key, $field) { $result = ""; $this->requestNum += 1; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type = "lfu") { $this->lfu[$key] += 1; } if ($this->type = "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } if ($this->data[$key][$field]) { $result = $this->data[$key][$field]; } } return $result; } /* * HGETALL key * 返回哈希表 key 中,所有的域和值。 * 在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。 * 以列表形式返回哈希表的域和域的值。 * 若 key 不存在,返回空列表。 */ public function getAll($key) { $result = array(); $this->requestNum += 1; if ($this->data[$key]) { $this->hitNum += 1; if ($this->type = "lfu") { $this->lfu[$key] += 1; } if ($this->type = "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } foreach ($this->data[$key] as $k => $v) { $result[] = $k . "," . $v; } } return $result; } /* * HKEYS key * 返回哈希表 key 中的所有域。 * 当 key 不存在时,返回一个空表。 */ public function getFields($key) { $this->requestNum += 1; $result = array(); if ($this->data[$key]) { $this->hitNum += 1; if ($this->type = "lfu") { $this->lfu[$key] += 1; } if ($this->type = "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } foreach ($this->data[$key] as $k => $v) { $result[] = $k; } } return $result; } // 清除缓存 public function cleanup() { if (count($this->data) == $this->totalSize) { if ($this->type == "lfu") { asort($this->lfu); $keys = array_keys($this->lfu); $k = $keys[0]; unset($this->data[$k]); unset($this->lfu[$k]); } if ($this->type == "lru") { asort($this->lru); $keys = array_keys($this->lru); $k = $keys[0]; unset($this->data[$k]); unset($this->lru[$k]); } } return true; } /* * HSET key field value * 将哈希表 key 中的域 field 的值设置为 value。 * 如果 key 不存在,一个新哈希表被创建并执行 HSET 命令。 * 如果域 field 已经存在于哈希表中,旧值将被覆盖。 * 返回值: 如果 field 是哈希表中的一个新建域,并且值设置成功,返回 1 。 如果哈希表中域 field 已经存在且旧值已被新值覆盖,返回 0 。 */ public function set($key, $field, $value) { $this->cleanup(); $result = 0; if ($this->data[$key]) { if ($this->type = "lfu") { $this->lfu[$key] += 1; } if ($this->type = "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } if (!$this->data[$field]) { $result = 1; } } else { $result = 1; } $this->data[$key][$field] = $value; return $result; } /* * HVALS key * 返回哈希表 key 中所有域的值。 * 当 key 不存在时,返回一个空表。 */ public function getValues($key) { $this->requestNum += 1; $result = array(); if ($this->data[$key]) { $this->hitNum += 1; if ($this->type = "lfu") { $this->lfu[$key] += 1; } if ($this->type = "lru") { $this->lru[$key] = date("Y-m-d H:i:s"); } foreach ($this->data[$key] as $value) { $result[] = $value; } } return $result; } }
以上是关于2017-4-28/PHP实现Redis的主要内容,如果未能解决你的问题,请参考以下文章