Redis 进阶(事务、生存时间、排序)

本篇文章结构为依次介绍Redis的事务、生存时间、排序。

Redis的事务:

Reids 中的事务是一组命令的集合。事务同命令一样是 Redis 的最小执行单位,一个事务中的命令要么都执行,要么都不执行。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd "user:1:following" 2
QUEUED
127.0.0.1:6379> sadd "user:2:followers" 1
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1

以上代码演示了事务的使用方式 :

  1. 首先使用 multi  命令告诉 Redis :“下面我发送的命令属于同一事务,你先不要执行,而是把他们暂存起来。” Redis 回答:“OK”。
  2. 之后发送了两条命令,Redis 返回 QUEUE 表示这两条命令已经进入等待执行的事务队列中了。
  3. 当把所有需要在同一事务中执行的命令都发给 Redis 后,使用 exec 命令告诉 Redis 将等待执行的事务队列中的所有命令(事务中所有返回 QUEUE 的命令)按照发送的顺序依次执行。
  4. exec 命令的返回值是这些命令的返回值组成的列表,返回值的顺序和命令的顺序相同。

 注:如果在发送 exec 命令前客户端断线了,Redis 会清空事务列表,事务中的所有命令都不会执行。另外,Reids的事务还能保证一个事务内的命令一次执行而不被其他命令插入。

错误处理:

如果一个事务中的某条命令执行出错,Redis 会如何处理?

  • 语法错误(命令不存在或者命令参数个数不对)
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set key value
    QUEUED
    127.0.0.1:6379> set key
    (error) ERR wrong number of arguments for 'set' command
    127.0.0.1:6379> errorcommand key
    (error) ERR unknown command 'errorcommand'
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors.

    第一个是正确的命令,成功加入事务队列;剩余两个命令都有语法错误。此种情况,只要有一个命令有语法错误,执行 exec 命令后 Redis 会直接返回错误,语法正确的命令也不会执行。

  • 运行错误(命令执行时出现的错误)
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set key 1
    QUEUED
    127.0.0.1:6379> sadd key 2
    QUEUED
    127.0.0.1:6379> set key 3
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
    3) OK
    127.0.0.1:6379> get key
    "3"

    此种错误在实际执行之前 Redis 是无法发现的,所以事务中这样的命令是会被 Reids 接受并执行的。如果事务里一条命令出现运行错误,事务里其他命令会继续执行。Redis 没有关系数据库事务提供的回滚功能,所以出错后需要自己处理。

拓展:watch 命令介绍

一个事务中只有当所有的命令都依次执行后才能得到每个结果的返回值。有些情况下需要先获得一条命令的返回值,然后再根据这个值执行下一条命令。对于事务,获取键值后,应该保证该键值没有被其他客户端修改。要实现这一场景可以使用 watch 命令:watch 命令可以监控一个或多个键,一旦其中一个键被修改(或删除),之后的一个事务就不会执行。监控一直持续到 exec 命令(事务中的命令是在 exec  之后才执行的,所以在 multi 命令后可以修改 watch 监控的键值)。

127.0.0.1:6379> set key 1
OK
127.0.0.1:6379> watch key
OK
127.0.0.1:6379> set key 2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 3
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get key
"2"

 注:执行 exec 命令后会取消对所有键的监控,如果不想执行事务中的命令可以使用 unwatch 命令来取消监控。


生存时间:Redis 可以设置一个键的生存时间,到时间后 Redis 会自动删除他。

--seconds 参数表示键的生存时间,单位是秒(必须是整数)(返回值为1表示设置成功,返回0表示键不存在或设置失败)
expire key seconds

--如果想知道一个键还有多久的时间会被删除,可以使用ttl命令
--当键不存在时,命令会返回-2。
--如果没有为键设置生存时间,命令也会返回-1(键永久存在,这是建立一个键后的默认情况)
ttl key

--取消键的生存时间设置(将键恢复成永久的)
--生存时间被成功清除返回1;否则返回0(键不存在或者键本来就是永久的)
persist key

--使用 set 或 getset 命令为键赋值会同时清除键的生存时间
set key value
getset key value --返回 key 的旧值

--设置键的生存时间,时间为毫秒
pexpire key millisecond

--返回一个键还有多久会被删除,单位为毫秒
pttl key

--设置一个键的生存时间(time为 Unix 时间(秒),表示键的生存时间的截止时间)
expireat key time

--设置一个键的生存时间(time为 Unix 时间(毫秒),表示键的生存时间的截止时间)
pexpireat key time

注:如果使用 watch 命令监测了一个拥有生存时间的键,该键时间到期自动删除并不会被watch命令认为该键被改变。

实践:

 设置生存时间,查看键还有多长时间被删除

127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> expire foo 20
(integer) 1
127.0.0.1:6379> ttl foo
(integer) 15
127.0.0.1:6379> ttl foo
(integer) 8
127.0.0.1:6379> ttl foo
(integer) -2
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> ttl foo
(integer) -1

取消键的生存时间

127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> persist foo
(integer) 0
127.0.0.1:6379> expire foo 20
(integer) 1
127.0.0.1:6379> persist foo
(integer) 1

使用 set 或者 getset 重新给键赋值,会清除键的生存时间

127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> expire foo 20
(integer) 1
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> ttl foo
(integer) -1

  查看键还有多久被删除,返回值单位(毫秒)

127.0.0.1:6379> expire foo 20
(integer) 1
127.0.0.1:6379> pttl foo
(integer) 15828
127.0.0.1:6379>

拓展:

因为 Redis 的生存时间设置,可以利用 Redis 实现缓存功能。但是当服务器内存有限时,如果大量地使用缓存键且生存时间设置的过长就会导致 Redis 占满内存;另一方面如果为了防止 Redis 占用内存过大而将缓存键的生存时间设置的太短,就可能导致命中率过低并且大量内存白白闲置。 

实际开发中会发现很难为缓存键设置合理的生存时间,为此可以限制 Redis 能够使用的最大内存,并让 Redis 按照一定的规则淘汰不需要的缓存键。

具体设置方法为:修改配置文件的 maxmemory 参数,限制 Redis 最大可用内存大小(单位字节),当超过这个限制,Redis 会依据 maxmemory-policy 参数指定的策略删除不需要的键,直到 Redis 占用的内存小于指定内存。

maxmemory-policy 支持的规则:

  • volatile-lru ——使用LRU(最近最少使用)算法删除一个键(只对设置了生存时间的键)
  • allkeys-lru——使用LRU算法删除一个键
  • volatile-random——随机删除一个键(只对设置了生存时间的键)
  • allkeys-random——随机删除一个键
  • volatile-ttl——删除生存时间最近的一个键
  • noeviction——不删除键,只返回错误

 排序SORT命令

Sort 命令可以对列表类型、集合类型和有序集合类型键进行排序。

--集合排序
sort set

--列表排序
sort list

--有序集合排序(对有序集合排序时会忽略元素的分数,只针对元素自身的值进行排序)
sort zset

--除了可以排列数字外,sort命令还可以通过alpha参数实现按照字典顺序排列非数字元素
sort set|list|zset alpha

--sort 命令默认是从小到大排序,sort 命令的 desc 参数可以实现元素按照从大到小的顺序排列
sort set|list|zset desc

--sort命令支持 limit 参数来返回指定范围的结果。
--用法:limit offset count(表示跳过前 offset 个元素并获取之后的count个元素)
sort set|list|zset limit offset count

实践:

集合排序

127.0.0.1:6379> sadd myset 2 10 8 22 5 7
(integer) 6
127.0.0.1:6379> sort myset
1) "2"
2) "5"
3) "7"
4) "8"
5) "10"
6) "22"

列表排序

127.0.0.1:6379> lpush mylist 4 2 6 1 3 7
(integer) 6
127.0.0.1:6379> sort mylist
1) "1"
2) "2"
3) "3"
4) "4"
5) "6"
6) "7"

有序集合排序

127.0.0.1:6379> zadd myzset 50 2 40 3 20 1 60 5
(integer) 4
127.0.0.1:6379> sort myzset
1) "1"
2) "2"
3) "3"
4) "5"

按照字典元素顺序排序非数字元素

127.0.0.1:6379> lpush mylistalpha a c e d B C A
(integer) 7
127.0.0.1:6379> sort mylistalpha
(error) ERR One or more scores can't be converted into double
127.0.0.1:6379> sort mylistalpha alpha
1) "A"
2) "B"
3) "C"
4) "a"
5) "c"
6) "d"
7) "e"

 注:如果没有加 alpha 参数,sort 命令会尝试将所有元素转换成双精度浮点数来比较,如果无法转换则会提示错误。

 实现元素从大到小进行排序

127.0.0.1:6379> smembers myset
1) "2"
2) "5"
3) "7"
4) "8"
5) "10"
6) "22"
127.0.0.1:6379> sort myset desc
1) "22"
2) "10"
3) "8"
4) "7"
5) "5"
6) "2"
127.0.0.1:6379> sort myset desc limit 1 2
1) "10"
2) "8"

Sort 参数介绍:

  • BY 参数

很多情况下列表(或集合、有序集合)中存储的元素值代表的是对象的 ID ,单纯对这些 ID 自身排序有时意义不大。更多时候我们希望根据 ID 对应的对象的某个属性进行排序,此时需要使用 SORT 命令的 BY 参数。

BY 参数的语法为: “BY 参考键” ,其中参考键可以是字符串类型或者是散列类型的某个字段(表示为键名->字段名)。如果提供了 BY 参数,SORT 命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“ * ” 并获取其值,然后依据该值对元素排序。

实践:

参考键为散列类型,按照 user 年龄升序排序

127.0.0.1:6379> hmset user:1 id 1 name zhangsan age 23 class 3
OK
127.0.0.1:6379> hmset user:2 id 2 name lisi age 20 class 3
OK
127.0.0.1:6379> hmset user:3 id 3 name wangwu age 19 class 2
OK
127.0.0.1:6379> sadd user 1 2 3
(integer) 3
127.0.0.1:6379> sort user by user:*->age
1) "3"
2) "2"
3) "1"

 参考键为字符串类型

127.0.0.1:6379> lpush sortbylist 2 1 3
(integer) 3
127.0.0.1:6379> set itemscore:1 50
OK
127.0.0.1:6379> set itemscore:2 100
OK
127.0.0.1:6379> set itemscore:3 -10
OK
127.0.0.1:6379> sort sortbylist by itemscore:* desc
1) "2"
2) "1"
3) "3"

拓展:当参考键中不包含 * 号时(即常量键名,与元素值无关),SORT 命令将不会执行排序操作,因为 Redis 认为这种情况是没有意义的(因为所有要比较大的值都一样) 。

127.0.0.1:6379> sort sortbylist by anytext
1) "3"
2) "1"
3) "2"

如果 几个元素的参考键值相同,则 SORT 命令会再比较元素本身的值来决定元素的顺序。

127.0.0.1:6379> lpush sortbylist 4
(integer) 4
127.0.0.1:6379> set itemscore:4 50
OK
127.0.0.1:6379> sort sortbylist by itemscore:* desc
1) "2"
2) "4"
3) "1"
4) "3"

当某个元素的参考键不存在时,会默认参考键的值为 0 。

127.0.0.1:6379> lpush sortbylist 5
(integer) 5
127.0.0.1:6379> sort sortbylist by itemscore:* desc
1) "2"
2) "4"
3) "1"
4) "5"
5) "3"

参考键虽然支持散列类型,但是 “ * ” 只能说是在 “ -> ” 符号前面(即键名部分)才有用,在 “ -> ” 后会被当做字段名本身而不会作为占位符被元素的值替换,即常量键名。

127.0.0.1:6379> sort sortbylist by somekey->somefield:*
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

注:上面的参考键名 “ * ” 出现在字段名中,属于参考键为常量名,sort 命令部队执行排序操作,但是结果显示却是排序了的,但是只是对元素本身进行了排序。这是因为 Redis 判断参考键名是不是常量键名的方式是判断参考键中是否包含 “ * ”,而 somekey->somefileld:*中包含了 “ * ”,所以不是常量键名。所以在排序的时候对每个元素都会读取 somekey 中的 somefield:* 字段,无论能否获得其值,每个元素的参考键值都是相同的,所以 Redis 会按照元素本身的大小排列。 

  • GET 参数

作用:使得 SORT 命令的返回结果不再是元素自身的值,而是 GET 参数中指定的键值。GET 参数支持字符串类型和散列表类型的键,使用 “ * ” 作为占位符。同时 SORT 命令中可以有多个 GET 命令;返回 ID 可以使用 get # 。

127.0.0.1:6379> sort user by user:*->age get user:*->name
1) "wangwu"
2) "lisi"
3) "zhangsan"
127.0.0.1:6379> sort user by user:*->age get user:*->name get user:*->age
1) "wangwu"
2) "19"
3) "lisi"
4) "20"
5) "zhangsan"
6) "23"
127.0.0.1:6379> sort user by user:*->age get user:*->name get user:*->age get #
1) "wangwu"
2) "19"
3) "3"
4) "lisi"
5) "20"
6) "2"
7) "zhangsan"
8) "23"
9) "1"
  • STORE 参数

默认情况下 SORT 会直接返回排序结果,如果希望保存排序结果,可以使用 STORE 参数,保存后的键的类型为列表类型,如果键已经存在则会覆盖它。加上 STORE 参数后 SORT 命令的返回值为结果的个数。

127.0.0.1:6379> sort user by user:*->age get user:*->name get user:*->age get # store sort.result
(integer) 9
127.0.0.1:6379> lrange sort.result 0 -1
1) "wangwu"
2) "19"
3) "3"
4) "lisi"
5) "20"
6) "2"
7) "zhangsan"
8) "23"
9) "1"

拓展:SORT 性能优化 

SORT 命令的时间复杂度为O(n+mlogm) ,n 表示要排序的列表中的元素个数,m 表示要返回的元素个数。

开发中使用 SORT 命令时需要注意一下几点:

  1. 尽可能减少待排序键中元素的数量(n尽可能小)。
  2. 使用 limit 参数只获取需要的数据 (m尽可能小)。
  3. 如果要排序的数据数量较大,尽可能使用 STORE 参数将结果缓存。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值