本文是参考《redis中文手册》,将示例代码用php来实现,注意php-redis与redis_cli的区别(主要是返回值类型和参数用法)。
目录(使用CTRL+F快速查找命令):
Key | String | Hash | List | Set |
---|---|---|---|---|
Sorted Set | Pub/Sub | Transaction | Connection | Server |
---|---|---|---|---|
phpredis是redis的php的一个扩展,效率是相当高有链表排序功能,对创建内存级的模块业务关系
很有用;以下是redis官方提供的命令使用技巧:
下载地址如下:
https://github.com/owlient/phpredis(支持redis 2.0.4)
Redis::__construct构造函数 $redis = new Redis();
connect, open 链接redis服务 参数 host: string,服务地址 port: int,端口号 timeout: float,链接时长 (可选, 默认为 0 ,不限链接时间) 注: 在redis.conf中也有时间,默认为300
pconnect, popen 不会主动关闭的链接 参考上面
setOption 设置redis模式
getOption 查看redis设置的模式
ping 查看连接状态
KEY相关操作
移除给定的一个或多个 key 。
如果 key 不存在,则忽略该命令。
时间复杂度:O(N), N 为要移除的 key 的数量。 移除单个字符串类型的 key ,时间复杂度为O(1)。 移除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M), M 为以上数据结构内的元素数量。返回值:被移除 key 的数量。//DEL # 情况1: 删除单个key $redis->set('myname','ikodota'); echo $redis->get('myname').'<br>'; # 返回:ikodota $redis->del('myname');# 返回 TRUE(1) var_dump($redis->get('myname')); # 返回 bool(false) # 情况2: 删除一个不存在的key if(!$redis->exists('fake_key')) # 不存在 var_dump($redis->del('fake_key')); # 返回 int(0) # 情况3: 同时删除多个key $array_mset=array('first_key'=>'first_val', 'second_key'=>'second_val', 'third_key'=>'third_val'); $redis->mset($array_mset); #用MSET一次储存多个值 $array_mget=array('first_key','second_key','third_key'); var_dump($redis->mget($array_mget)); #一次返回多个值 //array(3) { [0]=> string(9) "first_val" [1]=> string(10) "second_val" [2]=> string(9) "third_val" } $redis->del($array_mget); #同时删除多个key var_dump($redis->mget($array_mget)); #返回 array(3) { [0]=> bool(false) [1]=> bool(false) [2]=> bool(false) }
KEYSKEYS pattern 查找符合给定模式的 key 。KEYS * 命中数据库中所有 key 。 KEYS h?llo 命中 hello , hallo and hxllo 等。 KEYS h*llo 命中 hllo 和 heeeeello 等。 KEYS h[ae]llo 命中 hello 和 hallo ,但不命中 hillo 。
特殊符号用 "\" 隔开
时间复杂度:O(N), N 为数据库中 key 的数量。返回值:符合给定模式的 key 列表。警告 :KEYS的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 key ,你最好还是用集合(Set)。
//KEYS #$redis->FLUSHALL(); $array_mset_keys=array('one'=>'1', 'two'=>'2', 'three '=>'3', 'four'=>'4'); $redis->mset($array_mset_keys); #用MSET一次储存多个值 var_dump($redis->keys('*o*')); //array(3) { [0]=> string(4) "four" [1]=> string(3) "two" [2]=> string(3) "one" } var_dump($redis->keys('t??')); //array(1) { [0]=> string(3) "two" } var_dump($redis->keys('t[w]*')); //array(1) { [0]=> string(3) "two" } print_r($redis->keys('*')); //Array ( [0] => four [1] => three [2] => two [3] => one )
从当前数据库中随机返回(不删除)一个 key 。
时间复杂度:O(1)返回值:当数据库不为空时,返回一个 key 。 当数据库为空时,返回 nil。//RANDOMKEY $redis->FLUSHALL();#情况1:数据库不为空 $array_mset_randomkey=array('fruit'=>'apple', 'drink'=>'beer', 'food'=>'cookis'); $redis->mset($array_mset_randomkey); echo $redis->randomkey(); print_r($redis->keys('*')); # 查看数据库内所有key,证明RANDOMKEY并不删除key //Array ( [0] => food [1] => drink [2] => fruit ) # 情况2:数据库为空 $redis->flushdb(); # 删除当前数据库所有key var_dump($redis-> randomkey()); //bool(false)
TTL TTL key
返回给定 key 的剩余生存时间(time to live)(以秒为单位)。
时间复杂度:O(1)返回值:key 的剩余生存时间(以秒为单位)。 当 key 不存在或没有设置生存时间时,返回 -1 。//TTL # 情况1:带TTL的key $redis->flushdb(); //$redis->set('name','ikodota'); # 设置一个key $redis->expire('name',30); # 设置生存时间为30秒 //return (integer) 1 echo $redis->get('name'); //return ikodota echo $redis->ttl('name'); //(integer) 25 //echo $redis->ttl('name'); # 30秒过去,name过期 //(integer) -1 var_dump($redis->get('name')); # 过期的key将被删除 //return bool(false); # 情况2:不带TTL的key $redis->set('site','wikipedia.org');//OK var_dump($redis->ttl('site'));//int(-1) # 情况3:不存在的key $redis->EXISTS('not_exists_key');//int(0) var_dump($redis->TTL('not_exists_key'));//int(-1)
EXISTSEXISTS key
检查给定 key 是否存在。
时间复杂度:O(1)返回值:若 key 存在,返回 1 ,否则返回 0 。//EXISTS echo '<br>EXISTS<br>'; $redis->set('db',"redis"); //bool(true) var_dump($redis->exists('db')); # key存在 //bool(true) $redis->del('db'); # 删除key //int(1) var_dump($redis->exists('db')) # key不存在 //bool(false)
将当前数据库(默认为 0 )的 key 移动到给定的数据库 db 当中。
如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。
因此,也可以利用这一特性,将MOVE当作锁(locking)原语。
时间复杂度:O(1)返回值:移动成功返回 1 ,失败则返回 0 。//MOVE echo '<br><br>MOVE<br>'; # 情况1: key存在于当前数据库 $redis->SELECT(0); # redis默认使用数据库0,为了清晰起见,这里再显式指定一次。//OK $redis->SET('song',"secret base - Zone"); //OK var_dump ($redis->MOVE('song',1)); # 将song移动到数据库1 //bool(true) # 情况2:当key不存在的时候 $redis->SELECT(1); var_dump ($redis->EXISTS('fake_key'));//bool(false); var_dump($redis->MOVE('fake_key', 0)); # 试图从数据库1移动一个不存在的key到数据库0,失败) //bool(false) $redis->SELECT(0); # 使用数据库0 var_dump($redis->EXISTS('fake_key')); # 证实fake_key不存在 //bool(false) # 情况3:当源数据库和目标数据库有相同的key时 $redis->SELECT(0); # 使用数据库0 $redis->SET('favorite_fruit',"banana"); $redis->SELECT(1); # 使用数据库1 $redis->SET('favorite_fruit',"apple"); $redis->SELECT(0); # 使用数据库0,并试图将favorite_fruit移动到数据库1 var_dump($redis->MOVE('favorite_fruit',1)); # 因为两个数据库有相同的key,MOVE失败 //return bool(false) echo $redis->GET('favorite_fruit'); # 数据库0的favorite_fruit没变 //return banana $redis->SELECT(1); echo $redis->GET('favorite_fruit'); # 数据库1的favorite_fruit也是 //return apple
RENAME key newkey
将 key 改名为 newkey 。
当 key 和 newkey 相同或者 key 不存在时,返回一个错误。
当 newkey 已经存在时,RENAME命令将覆盖旧值。
时间复杂度:O(1)返回值:改名成功时提示 OK ,失败时候返回一个错误。//RENAME echo '<br><br>RENAME<br>'; # 情况1:key存在且newkey不存在 $redis->SET('message',"hello world"); var_dump($redis->RENAME('message','greeting')); //bool(true) var_dump($redis->EXISTS('message')); # message不复存在 //bool(false) var_dump($redis->EXISTS('greeting')); # greeting取而代之 //bool(true) # 情况2:当key不存在时,返回错误 ,php返回false; var_dump($redis->RENAME('fake_key','never_exists')); //bool(false) # 情况3:newkey已存在时,RENAME会覆盖旧newkey $redis->SET('pc',"lenovo"); $redis->SET('personal_computer',"dell"); var_dump($redis->RENAME('pc','personal_computer')); //bool(true) var_dump($redis->GET('pc')); //(nil) bool(false) var_dump($redis->GET('personal_computer')); # dell“没有”了 //string(6) "lenovo"
RENAMENX RENAMENX key newkey
当且仅当 newkey 不存在时,将 key 改为 newkey 。
出错的情况和RENAME一样( key 不存在时报错)。
时间复杂度:O(1)返回值:修改成功时,返回 1 。 如果 newkey 已经存在,返回 0 。//RENAMENX echo '<br><br>RENAMENX<br>'; # 情况1:newkey不存在,成功 $redis->SET('player',"MPlyaer"); $redis->EXISTS('best_player'); //int(0) var_dump($redis->RENAMENX('player','best_player')); // bool(true) # 情况2:newkey存在时,失败 $redis->SET('animal',"bear"); $redis->SET('favorite_animal', "butterfly"); var_dump($redis->RENAMENX('animal', 'favorite_animal'));// bool(false) var_dump($redis->get('animal')); //string(4) "bear" var_dump($redis->get('favorite_animal')); //string(9) "butterfly"TYPETYPE key
返回 key 所储存的值的类型。
时间复杂度:O(1)返回值:none (key不存在) int(0) string (字符串) int(1) list (列表) int(3) set (集合) int(2) zset (有序集) int(4) hash (哈希表) int(5)//TYPE $redis->flushALL(); echo '<br><br>TYPE<br>'; var_dump($redis->TYPE('fake_key')); //none /int(0) $redis->SET('weather',"sunny"); # 构建一个字符串 var_dump($redis->TYPE('weather'));//string / int(1) $redis->SADD('pat',"dog"); # 构建一个集合 var_dump($redis->TYPE('pat')); //set /int(2) $redis->LPUSH('book_list',"programming in scala"); # 构建一个列表 var_dump($redis->TYPE('book_list'));//list / int(3) $redis->ZADD('pats',1,'cat'); # 构建一个zset (sorted set) // int(1) $redis->ZADD('pats',2,'dog'); $redis->ZADD('pats',3,'pig'); var_dump($redis->zRange('pats',0,-1)); // array(3) { [0]=> string(3) "cat" [1]=> string(3) "dog" [2]=> string(3) "pig" } var_dump($redis->TYPE('pats')); //zset / int(4) $redis->HSET('website','google','www.g.cn'); # 一个新域 var_dump($redis->HGET('website','google')); //string(8) "www.g.cn" var_dump($redis->TYPE('website')); //hash /int(5)
EXPIRE key seconds
为给定 key 设置生存时间。
当 key 过期时,它会被自动删除。
在Redis中,带有生存时间的 key 被称作“易失的”(volatile)。
在低于2.1.3版本的Redis中,已存在的生存时间不可覆盖。 从2.1.3版本开始, key 的生存时间可以被更新,也可以被PERSIST命令移除。(详情参见 http://redis.io/topics/expire)。
时间复杂度:O(1)返回值:设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于2.1.3中你尝试更新 key 的生存时间),返回 0 。
//EXPIRE $redis->select(7); //$redis->flushdb(); echo '<br><br>EXPIRE<br>'; $redis->SET('cache_page',"www.cnblogs.com/ikodota"); $redis->EXPIRE('cache_page', 30); # 设置30秒后过期 sleep(6); echo $redis->TTL('cache_page').'<br>'; # 查看给定key的剩余生存时间 //(integer) 24 $redis->EXPIRE('cache_page', 3000); # 更新生存时间,3000秒 sleep(4); echo $redis->TTL('cache_page').'<br>'; //(integer) 2996
EXPIREAT EXPIREAT key timestamp
EXPIREAT的作用和EXPIRE一样,都用于为 key 设置生存时间。
不同在于EXPIREAT命令接受的时间参数是UNIX时间戳(unix timestamp)。
时间复杂度:O(1)返回值:如果生存时间设置成功,返回 1 。 当 key 不存在或没办法设置生存时间,返回 0 。//EXPIREAT echo '<br><br>EXPIREAT<br>'; $redis->SET('cache','www.google.com'); echo $redis->EXPIREAT('cache','1355292000'); # 这个key将在2012.12.12过期 echo ($redis->TTL('cache')); //return 124345085
OBJECT OBJECT subcommand [arguments [arguments]]
OBJECT命令允许从内部察看给定 key 的Redis对象。
它通常用在除错(debugging)或者了解为了节省空间而对 key 使用特殊编码的情况。 当将Redis用作缓存程序时,你也可以通过OBJECT命令中的信息,决定 key 的驱逐策略(eviction policies)。OBJECT命令有多个子命令:
- OBJECT REFCOUNT <key> 返回给定 key 引用所储存的值的次数。此命令主要用于除错。
- OBJECT ENCODING <key> 返回给定 key 锁储存的值所使用的内部表示(representation)。
- OBJECT IDLETIME <key> 返回给定 key 自储存以来的空转时间(idle, 没有被读取也没有被写入),以秒为单位。
- 字符串可以被编码为 raw (一般字符串)或 int (用字符串表示64位数字是为了节约空间)。
- 列表可以被编码为 ziplist 或 linkedlist 。 ziplist 是为节约大小较小的列表空间而作的特殊表示。
- 集合可以被编码为 intset 或者 hashtable 。 intset 是只储存数字的小集合的特殊表示。
- 哈希表可以编码为 zipmap 或者 hashtable 。 zipmap 是小哈希表的特殊表示。
- 有序集合可以被编码为 ziplist 或者 skiplist 格式。 ziplist 用于表示小的有序集合,而 skiplist 则用于表示任何大小的有序集合。
//OBJECT $redis->select(8); echo '<br><br>OBJECT<br>'; $redis->SET('game',"WOW"); # 设置一个字符串 $redis->OBJECT('REFCOUNT','game'); # 只有一个引用 //sleep(5); echo $redis->OBJECT('IDLETIME','game'); # 等待一阵。。。然后查看空转时间 //(integer) 10 //echo $redis->GET('game'); # 提取game, 让它处于活跃(active)状态 //return WOW //echo $redis->OBJECT('IDLETIME','game'); # 不再处于空转 //(integer) 0 var_dump($redis->OBJECT('ENCODING','game')); # 字符串的编码方式 //string(3) "raw" $redis->SET('phone',15820123123); # 大的数字也被编码为字符串 var_dump($redis->OBJECT('ENCODING','phone')); //string(3) "raw" $redis->SET('age',20); # 短数字被编码为int var_dump($redis->OBJECT('ENCODING','age')); //string(3) "int"
PERSIST PERSIST key
移除给定 key 的生存时间。
时间复杂度:O(1)返回值:当生存时间移除成功时,返回 1 . 如果 key 不存在或 key 没有设置生存时间,返回 0 。
//PERSIST echo '<br><br>PERSIST<br>'; $redis->SET('time_to_say_goodbye',"886..."); $redis->EXPIRE('time_to_say_goodbye', 300); sleep(3); echo $redis->TTL('time_to_say_goodbye'); # (int) 297 echo '<br>'; $redis->PERSIST('time_to_say_goodbye'); # 移除生存时间 echo $redis->TTL('time_to_say_goodbye'); # 移除成功 //int(-1)
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
排序,分页等 参数
array( ‘by’ => ‘some_pattern_*’, ‘limit’ => array(0, 1), ‘get’ => ‘some_other_pattern_*’ or an array of patterns, ‘sort’ => ‘asc’ or ‘desc’, ‘alpha’ => TRUE, ‘store’ => ‘external-key’ )
返回或保存给定列表、集合、有序集合 key 中经过排序的元素。
排序默认以数字作为对象,值被解释为双精度浮点数,然后进行比较。
一般SORT用法
最简单的SORT使用方法是 SORT key 。
假设 today_cost 是一个保存数字的列表,SORT命令默认会返回该列表值的递增(从小到大)排序结果。
# 将数据一一加入到列表中 $redis->LPUSH('today_cost', 30); $redis->LPUSH('today_cost', 1.5); $redis->LPUSH('today_cost', 10); $redis->LPUSH('today_cost', 8); # 排序 var_dump($redis->SORT('today_cost')); //array(4) { [0]=> string(3) "1.5" [1]=> string(1) "8" [2]=> string(2) "10" [3]=> string(2) "30" }
当数据集中保存的是字符串值时,你可以用ALPHA 修饰符(modifier)进行排序。
# 将数据一一加入到列表中 $redis->LPUSH('website', "www.reddit.com"); $redis->LPUSH('website', "www.slashdot.com"); $redis->LPUSH('website', "www.infoq.com"); # 默认排序 var_dump($redis->SORT('website'));//array(3) { [0]=> string(13) "www.infoq.com" [1]=> string(16) "www.slashdot.com" [2]=> string(14) "www.reddit.com" } # 按字符排序 ALPHA=true var_dump($redis->SORT('website', array('ALPHA'=>TRUE))); //array(3) { [0]=> string(13) "www.infoq.com" [1]=> string(14) "www.reddit.com" [2]=> string(16) "www.slashdot.com" }
如果你正确设置了 !LC_COLLATE 环境变量的话,Redis能识别 UTF-8 编码。
排序之后返回的元素数量可以通过 LIMIT 修饰符进行限制。 LIMIT 修饰符接受两个参数: offset 和 count 。 offset 指定要跳过的元素数量, count 指定跳过 offset 个指定的元素之后,要返回多少个对象。以下例子返回排序结果的前5个对象( offset 为 0 表示没有元素被跳过)。
# 将数据一一加入到列表中 $redis->LPUSH('rank', 30); //(integer) 1 $redis->LPUSH('rank', 56); //(integer) 2 $redis->LPUSH('rank', 42); //(integer) 3 $redis->LPUSH('rank', 22); //(integer) 4 $redis->LPUSH('rank', 0); //(integer) 5 $redis->LPUSH('rank', 11); //(integer) 6 $redis->LPUSH('rank', 32); //(integer) 7 $redis->LPUSH('rank', 67); //(integer) 8 $redis->LPUSH('rank', 50); //(integer) 9 $redis->LPUSH('rank', 44); //(integer) 10 $redis->LPUSH('rank', 55); //(integer) 11 # 排序 $redis_sort_option=array('LIMIT'=>array(0,5)); var_dump($redis->SORT('rank',$redis_sort_option)); # 返回排名前五的元素 // array(5) { [0]=> string(1) "0" [1]=> string(2) "11" [2]=> string(2) "22" [3]=> string(2) "30" [4]=> string(2) "32" }
修饰符可以组合使用。以下例子返回降序(从大到小)的前5个对象。
$redis_sort_option=array( 'LIMIT'=>array(0,5), 'SORT'=>'DESC' ); var_dump($redis->SORT('rank',$redis_sort_option)); //array(5) { [0]=> string(2) "67" [1]=> string(2) "56" [2]=> string(2) "55" [3]=> string(2) "50" [4]=> string(2) "44" }
使用外部key进行排序
有时候你会希望使用外部的key作为权重来比较元素,代替默认的对比方法。
假设现在有用户(user)数据如下:
id name level ------------------------------- 1 admin 9999 2 huangz 10 59230 jack 3 222 hacker 9999
id数据保存在key名为user_id的列表中。 name数据保存在key名为user_name_{id}的列表中 level数据保存在user_level_{id}的key中。
# 先将要使用的数据加入到数据库中 # admin $redis->LPUSH('user_id', 1);//(integer) 1 $redis->SET('user_name_1', 'admin'); $redis->SET('user_level_1',9999); # huangz $redis->LPUSH('user_id', 2);//(integer) 2 $redis->SET('user_name_2', 'huangz'); $redis->SET('user_level_2', 10); # jack $redis->LPUSH('user_id', 59230);//(integer) 3 $redis->SET('user_name_59230','jack'); $redis->SET('user_level_59230', 3); # hacker $redis->LPUSH('user_id', 222); //(integer) 4 $redis->SET('user_name_222', 'hacker'); $redis->SET('user_level_222', 9999);
如果希望按 level 从大到小排序 user_id ,可以使用以下命令:
$redis_sort_option=array('BY'=>'user_level_*', 'SORT'=>'DESC' ); var_dump($redis->SORT('user_id',$redis_sort_option)); //array(4) { [0]=> string(3) "222" [1]=> string(1) "1" [2]=> string(1) "2" [3]=> string(5) "59230" } #--------------------------- #1) "222" # hacker #2) "1" # admin #3) "2" # huangz #4) "59230" # jack
但是有时候只是返回相应的 id 没有什么用,你可能更希望排序后返回 id 对应的用户名,这样更友好一点,使用 GET 选项可以做到这一点:
$redis_sort_option=array('BY'=>'user_level_*', 'SORT'=>'DESC', 'GET'=>'user_name_*' ); var_dump($redis->SORT('user_id', $redis_sort_option)); //array(4) { [0]=> string(6) "hacker" [1]=> string(5) "admin" [2]=> string(6) "huangz" [3]=> string(4) "jack" }
#1) "hacker" #2) "admin" #3) "huangz" #4) "jack"
可以多次地、有序地使用 GET 操作来获取更多外部 key 。
比如你不但希望获取用户名,还希望连用户的密码也一并列出,可以使用以下命令:
# 先添加一些测试数据 $redis->SET('user_password_222', "hey,im in"); $redis->SET('user_password_1', "a_long_long_password"); $redis->SET('user_password_2', "nobodyknows"); $redis->SET('user_password_59230', "jack201022"); # 获取name和password $redis_sort_option=array('BY'=>'user_level_*', 'SORT'=>'DESC', 'GET'=>array('user_name_*','user_password_*') ); var_dump($redis->SORT('user_id',$redis_sort_option));//array(8) { [0]=> string(6) "hacker" [1]=> string(9) "hey,im in" [2]=> string(5) "admin" [3]=> string(20) "a_long_long_password" [4]=> string(6) "huangz" [5]=> string(11) "nobodyknows" [6]=> string(4) "jack" [7]=> string(10) "jack201022" } #------------------------------------ #1) "hacker" # 用户名 #2) "hey,im in" # 密码 #3) "jack" #4) "jack201022" #5) "huangz" #6) "nobodyknows" #7) "admin" #8) "a_long_long_password"
# 注意GET操作是有序的,GET user_name_* GET user_password_* 和 GET user_password_* GET user_name_*返回的结果位置不同
# 获取name和password 注意GET操作是有序的 $redis_sort_option=array('BY'=>'user_level_*', 'SORT'=>'DESC', 'GET'=>array('user_password_*','user_name_*') ); var_dump($redis->SORT('user_id',$redis_sort_option));// array(8) { [0]=> string(9) "hey,im in" [1]=> string(6) "hacker" [2]=> string(20) "a_long_long_password" [3]=> string(5) "admin" [4]=> string(11) "nobodyknows" [5]=> string(6) "huangz" [6]=> string(10) "jack201022" [7]=> string(4) "jack" }
GET 还有一个特殊的规则—— "GET #" ,用于获取被排序对象(我们这里的例子是 user_id )的当前元素。
比如你希望 user_id 按 level 排序,还要列出 id 、 name 和 password ,可以使用以下命令:
$redis_sort_option=array('BY'=>'user_level_*', 'SORT'=>'DESC', 'GET'=>array('#','user_password_*','user_name_*') ); var_dump($redis->SORT('user_id',$redis_sort_option));//array(12) { [0]=> string(3) "222" [1]=> string(9) "hey,im in" [2]=> string(6) "hacker" [3]=> string(1) "1" [4]=> string(20) "a_long_long_password" [5]=> string(5) "admin" [6]=> string(1) "2" [7]=> string(11) "nobodyknows" [8]=> string(6) "huangz" [9]=> string(5) "59230" [10]=> string(10) "jack201022" [11]=> string(4) "jack" } #-------------------------------------------------------------- #1) "222" # id #2) "hacker" # name #3) "hey,im in" # password #4) "1" #5) "admin" #6) "a_long_long_password" #7) "2" #8) "huangz" #9) "nobodyknows" #10) "59230" #11) "jack" #12) "jack201022"
只获取对象而不排序
BY 修饰符可以将一个不存在的 key 当作权重,让SORT跳过排序操作。
该方法用于你希望获取外部对象而又不希望引起排序开销时使用。
# 确保fake_key不存在 $redis->EXISTS('fake_key');//(integer) 0 # 以fake_key作BY参数,不排序,只GET name 和 GET password $redis_sort_option=array('BY'=>'fake_key', 'SORT'=>'DESC', 'GET'=>array('#','user_name_*','user_password_*') ); var_dump($redis->SORT('user_id',$redis_sort_option));//array(12) { [0]=> string(3) "222" [1]=> string(6) "hacker" [2]=> string(9) "hey,im in" [3]=> string(5) "59230" [4]=> string(4) "jack" [5]=> string(10) "jack201022" [6]=> string(1) "2" [7]=> string(6) "huangz" [8]=> string(11) "nobodyknows" [9]=> string(1) "1" [10]=> string(5) "admin" [11]=> string(20) "a_long_long_password" } #---------------------------------------------- #1) "222" # id #2) "hacker" # user_name #3) "hey,im in" # password #4) "59230" #5) "jack" #6) "jack201022" #7) "2" #8) "huangz" #9) "nobodyknows" #10) "1" #11) "admin" #12) "a_long_long_password"
保存排序结果
默认情况下,SORT操作只是简单地返回排序结果,如果你希望保存排序结果,可以给 STORE 选项指定一个 key 作为参数,排序结果将以列表的形式被保存到这个 key 上。(若指定 key 已存在,则覆盖。)
$redis->EXISTS('user_info_sorted_by_level'); # 确保指定key不存在 //(integer) 0 $redis_sort_option=array('BY'=>'user_level_*', 'GET'=>array('#','user_name_*','user_password_*'), 'STORE'=>'user_info_sorted_by_level' ); var_dump($redis->SORT('user_id',$redis_sort_option)); //int(12) var_dump($redis->LRANGE('user_info_sorted_by_level', 0 ,11)); # 查看排序结果 //array(12) { [0]=> string(5) "59230" [1]=> string(4) "jack" [2]=> string(10) "jack201022" [3]=> string(1) "2" [4]=> string(6) "huangz" [5]=> string(11) "nobodyknows" [6]=> string(3) "222" [7]=> string(6) "hacker" [8]=> string(9) "hey,im in" [9]=> string(1) "1" [10]=> string(5) "admin" [11]=> string(20) "a_long_long_password" } #----------------------------------------------------------------- #1) "59230" #2) "jack" #3) "jack201022" #4) "2" #5) "huangz" #6) "nobodyknows" #7) "222" #8) "hacker" #9) "hey,im in" #10) "1" #11) "admin" #12) "a_long_long_password"
一个有趣的用法是将SORT结果保存,用EXPIRE为结果集设置生存时间,这样结果集就成了SORT操作的一个缓存。
这样就不必频繁地调用SORT操作了,只有当结果集过期时,才需要再调用一次SORT操作。
有时候为了正确实现这一用法,你可能需要加锁以避免多个客户端同时进行缓存重建(也就是多个客户端,同一时间进行SORT操作,并保存为结果集),具体参见SETNX命令。
在GET和BY中使用哈希表
可以使用哈希表特有的语法,在SORT命令中进行GET和BY操作。
# 假设现在我们的用户表新增了一个serial项来为作为每个用户的序列号 # 序列号以哈希表的形式保存在serial哈希域内。 $redis_hash_testdata_array=array(1=>'23131283', 2=>'23810573', 222=>'502342349', 59230=>'2435829758' ); $redis->HMSET('serial',$redis_hash_testdata_array); # 我们希望以比较serial中的大小来作为排序user_id的方式 $redis_sort_option=array('BY'=>'*->serial'); var_dump($redis->SORT('user_id', $redis_sort_option)); //array(4) { [0]=> string(3) "222" [1]=> string(5) "59230" [2]=> string(1) "2" [3]=> string(1) "1" } #---------------------------------------- #1) "222" #2) "59230" #3) "2" #4) "1"
符号 "->" 用于分割哈希表的关键字(key name)和索引域(hash field),格式为 "key->field" 。
除此之外,哈希表的 BY 和 GET 操作和上面介绍的其他数据结构(列表、集合、有序集合)没有什么不同。
时间复杂度:O(N+M*log(M)), N 为要排序的列表或集合内的元素数量, M 为要返回的元素数量。 如果只是使用SORT命令的 GET 选项获取数据而没有进行排序,时间复杂度O(N)。返回值:没有使用 STORE 参数,返回列表形式的排序结果。 使用 STORE 参数,返回排序结果的元素数量。字符串(String) SET key value
将字符串值 value 关联到 key 。
如果 key 已经持有其他值,SET就覆写旧值,无视类型。
时间复杂度:O(1)返回值:总是返回 OK(TRUE) ,因为SET不可能失败。
# 情况1:对字符串类型的key进行SET $redis->SET('apple', 'www.apple.com');#OK //bool(true) $redis->GET('apple');//"www.apple.com" # 情况2:对非字符串类型的key进行SET $redis->LPUSH('greet_list', "hello"); # 建立一个列表 #(integer) 1 //int(1) $redis->TYPE('greet_list');#list //int(3) $redis->SET('greet_list', "yooooooooooooooooo"); # 覆盖列表类型 #OK //bool(true) $redis->TYPE('greet_list');#string //int(1)
SETNXSETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则SETNX不做任何动作。
SETNX是”SET if Not eXists”(如果不存在,则SET)的简写。
时间复杂度:O(1)返回值:设置成功,返回 1 。 设置失败,返回 0 。//SETNX echo '<br><br>SETNX<br>'; $redis->EXISTS('job'); # job不存在 //bool(false); $redis->SETNX('job', "programmer"); # job设置成功 //bool(true) $redis->SETNX('job', "code-farmer"); # job设置失败 //bool(false) echo $redis->GET('job'); # 没有被覆盖 //"programmer"
设计模式(Design pattern): 将SETNX用于加锁(locking)
SETNX可以用作加锁原语(locking primitive)。比如说,要对关键字(key) foo 加锁,客户端可以尝试以下方式:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果SETNX返回 1 ,说明客户端已经获得了锁, key 设置的unix时间则指定了锁失效的时间。之后客户端可以通过 DEL lock.foo 来释放锁。
如果SETNX返回 0 ,说明 key 已经被其他客户端上锁了。如果锁是非阻塞(non blocking lock)的,我们可以选择返回调用,或者进入一个重试循环,直到成功获得锁或重试超时(timeout)。
处理死锁(deadlock)
上面的锁算法有一个问题:如果因为客户端失败、崩溃或其他原因导致没有办法释放锁的话,怎么办?
这种状况可以通过检测发现——因为上锁的 key 保存的是unix时间戳,假如 key 值的时间戳小于当前的时间戳,表示锁已经不再有效。
但是,当有多个客户端同时检测一个锁是否过期并尝试释放它的时候,我们不能简单粗暴地删除死锁的 key ,再用SETNX上锁,因为这时竞争条件(race condition)已经形成了:
- C1和C2读取 lock.foo 并检查时间戳,SETNX都返回 0 ,因为它已经被C3锁上了,但C3在上锁之后就崩溃(crashed)了。
- C1向 lock.foo 发送DEL命令。
- C1向 lock.foo 发送SETNX并成功。
- C2向 lock.foo 发送DEL命令。
- C2向 lock.foo 发送SETNX并成功。
- 出错:因为竞争条件的关系,C1和C2两个都获得了锁。
幸好,以下算法可以避免以上问题。来看看我们聪明的C4客户端怎么办:
- C4向 lock.foo 发送SETNX命令。
- 因为崩溃掉的C3还锁着 lock.foo ,所以Redis向C4返回 0 。
- C4向 lock.foo 发送GET命令,查看 lock.foo 的锁是否过期。如果不,则休眠(sleep)一段时间,并在之后重试。
- 另一方面,如果 lock.foo 内的unix时间戳比当前时间戳老,C4执行以下命令:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
- 因为GETSET的作用,C4可以检查看GETSET的返回值,确定 lock.foo 之前储存的旧值仍是那个过期时间戳,如果是的话,那么C4获得锁。
- 如果其他客户端,比如C5,比C4更快地执行了GETSET操作并获得锁,那么C4的GETSET操作返回的就是一个未过期的时间戳(C5设置的时间戳)。C4只好从第一步开始重试。
警告
为了让这个加锁算法更健壮,获得锁的客户端应该常常检查过期时间以免锁因诸如DEL等命令的执行而被意外解开,因为客户端失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着DEL命令被尝试执行(但这时锁却在另外的客户端手上)。
SETEX SETEX key seconds value
将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。
如果 key 已经存在,SETEX命令将覆写旧值。
这个命令类似于以下两个命令:
$redis->SET('key', 'value'); $redis->EXPIRE('key','seconds'); # 设置生存时间
不同之处是,SETEX是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成,该命令在Redis用作缓存时,非常实用。
时间复杂度:O(1)返回值:设置成功时返回 OK 。 当 seconds 参数不合法时,返回一个错误。# 情况1:key不存在 $redis->SETEX('cache_user_id', 60,10086);//bool(true) echo $redis->GET('cache_user_id'); # 值 //"10086" sleep(4); echo $redis->TTL('cache_user_id'); # 剩余生存时间 //int(56) # 情况2:key已经存在,key被覆写 $redis->SET('cd', "timeless"); //bool(true); $redis->SETEX('cd', 3000,"goodbye my love"); //bool(true); echo $redis->GET('cd');//"goodbye my love"
SETRANGE key offset value
用 value 参数覆写(Overwrite)给定 key 所储存的字符串值,从偏移量 offset 开始。
不存在的 key 当作空白字符串处理。
SETRANGE命令会确保字符串足够长以便将 value 设置在指定的偏移量上,如果给定 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,但你设置的 offset 是 10 ),那么原字符和偏移量之间的空白将用零比特(zerobytes, "\x00" )来填充。
注意你能使用的最大偏移量是2^29-1(536870911),因为Redis的字符串被限制在512兆(megabytes)内。如果你需要使用比这更大的空间,你得使用多个 key 。
时间复杂度:对小(small)的字符串,平摊复杂度O(1)。(关于什么字符串是”小”的,请参考APPEND命令) 否则为O(M),M为value参数的长度。返回值:被SETRANGE修改之后,字符串的长度。警告
当生成一个很长的字符串时,Redis需要分配内存空间,该操作有时候可能会造成服务器阻塞(block)。在2010年的Macbook Pro上,设置偏移量为536870911(512MB内存分配),耗费约300毫秒, 设置偏移量为134217728(128MB内存分配),耗费约80毫秒,设置偏移量33554432(32MB内存分配),耗费约30毫秒,设置偏移量为8388608(8MB内存分配),耗费约8毫秒。 注意若首次内存分配成功之后,再对