代码改变世界

三、 redis进阶篇

2019-02-27 11:26  QQ~sunshine  阅读(289)  评论(0编辑  收藏  举报

1. redis事务

  使用方法:方法为先发送multi命令告诉redis,下面所有的命令属于同一个事务,先不要执行,而是把他们暂时存起来,redis返回OK,然后后面执行需要放在同一个事务里的命令,可以看到每个命令都会返回QUEUED表示这几条命令已经进入等待执行的事务队列中了,当需要在同一个事务中执行的命令发送完毕后,使用exec命令告诉redis将等待执行的事务队列中的所有命令按照发送顺序依次执行,exec命令的返回值就是这些命令的返回值组成的列表,返回值顺序和命令执行顺序一样。

  性质:redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送exec命令之前客户端断线了,则redis会清空事务队列,事务中所有命令都不会执行。而一旦客户端发送了exec命令,所有的命令都会被执行,即使客户端断线也没有关系,因为redis中已经记录了所有要执行的命令。

  使用场景:在需要都执行或者都不执行的情况下,或者希望执行哪几条命令不被插入也可以使用事务。

  错误处理:分以下两种情况

  (1)语法错误:语法错误指命令不存在或者命令参数不对,这种情况当执行exec命令后redis就会直接返回错误,连语法正确的命令也不会执行。(其实在输入错误命令时就已经返回错误了,只不过不会退出队列,所以出现错误时直接返回重新来就行了,因为后面就算都对执行exec后也是没法执行的)

  (2)运行错误:运行错误是指在命令执行的时候出现的错误,比如用散列类型的命令操作集合类型的键,这种错误在实际执行之前redis是无法发现的,所以在事务里这样的命令是会被redis接受并执行的,如果事务里的一条命令出现了运行错误,事务里的其他命令依然会被执行(包括出错命令之后的命令)。

  缺点:就是没有关系型数据库事务提供的回滚功能,所以如果出现事务执行错误必须自己解决后续问题。

  优点:由于redis不支持回滚功能,所以redis在事务上可以保持简洁和快捷。

  watch命令:可以监控一个或多个键,一旦其中一个键被修改或删除,之后的事务就不会执行,监控一直持续到exec命令(事务中的命令是在exec之后才执行的,所以在multi命令后可以修改watch监控的键值。这里的修改只是说向事务发送这个命令不会报错,但其实并没有修改,因为实际修改操作是在exec命令之后,但这个时候事务里的命令其实并没有执行,exec命令也会返回空结果)。由于watch命令的作用只是当被监控的键值被修改后阻止后一个事务的执行,而不能保证其他客户端不修改这一键值。执行exec命令后会取消对所有键的监控,如果不想执行事务中的命令也可以使用unwatch命令来取消监控

2. 生存时间

expire  key  seconds   给键设置生存时间,到期自动删除,seconds秒数,返回1表示设置成功,返回0表示设置失败或键不存在

ttl  key    查看键还有多少时间会被删除,返回值是剩余的秒数,如果返回-1说明键永久存在,返回-2说明键不存在

persist  key   取消键的生存时间,即将键恢复成永久的,如果生存时间被成功清除则返回1,否则返回0(因为键不存在或者本身就是永久的)

  使用set或getset命令为键赋值也会同时清除键的生存时间,使用expire命令会重新设置键的生存时间,其他只对键值进行操作的命令(如incr、lpush、hset、zrem)均不会影响键的生存时间。

  expire命令的seconds参数必须是整数,如果想要更精确的控制键的生存时间应该使用pexpire命令,两者唯一区别就是这个seconds参数是毫秒,同样可以使用pttl命令以毫秒为单位返回键的剩余时间。

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

  还有两个相关命令expireat和pexpireat,这两个命令与原命令的差别在于这两个seconds参数是unix时间,表示键的生存时间的截止时间。

  过期时间这一属性可以把redis用作缓存,比如登录功能,登录成功放入缓存中,设置一个过期时间,如有则认为已经登录,如果过期删除则需要重新登录。

  实际开发中如果缓存键生存时间太长会导致redis占满内存,如果太短可能导致缓存命中率太低并且大量内存被白白的闲置,为此可以限制redis能够使用的最大内存,并让redis按照一定的规则淘汰不需要的缓存键,这种方式在只将redis用作缓存系统时非常实用。

  具体设置方法为:修改配置文件的maxmemory参数,限制redis最大可用内存大小(单位是字节),当超出了这个限制时redis会根据maxmemory-policy参数指定的策略来删除不需要的键,直到redis占用的内存小于指定内存。maxmemory-policy支持的规则如下:(LRU算法即"最长时间未被使用的",LFU算法即"一段时间使用最少的")

  volatile-lru      使用LRU算法删除一个键(只对设置了生存时间的键)

  volatile-lfu      使用LFU算法删除一个键(只对设置了生存时间的键)

  volatile-random   随机删除一个键(只对设置了生存时间的键)

  allkeys-lru      使用LRU算法删除一个键(所有)

  allkeys-lfu      使用LFU算法删除一个键(所有)

  allkeys-random   随机删除一个键(所有)

  volatile-ttl       从设置了过期时间的键中选择过期时间最早的键删除

  noeviction     不删除键,只返回错误

  使用maxmemory-poliy参数删除键时redis并不会准确的从整个数据库中查找,而是每次随机取3个键并删除这三个键中符合条件的,3这个数字可以通过redis的配置文件中的maxmemory-samples参数设置。

3. 排序

基本排序:

sort  key  [alpha]  [desc]  [limit start end]  该命令可以给集合和有序集合进行排序,当给有序集合排序时会忽略元素的分数,只针对元素本身的值进行排序。如果要按照ASCLL码排序非数字元素,需要加上"alpha",倒序需要加上desc,分页需要加上limit,方法和mysql数据库一样。

by参数:

  有时候排序不一定完全按照集合中的元素值本身排序,而是会根据别的元素作为相关键进行排序,这时候就用到了by参数。

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

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

  在不需要排序但是需要借助sort命令获得与元素相关联的数据时,常量键名是很有用的。

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

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

  参考键虽然支持散列类型,但是"*"只能在"->"符号前面(即键名部分)才有用,在后面(即字段名部分)会被当成字段名本身而不会作为占位符被元素的值替换,即常量键名,这样的结果就是依旧会按照元素本身的值进行排序,因为参考键如果是常量redis不会进行排序,但是检测是否是常量的方法是判断参考键中是否含有"*",上面显然含有,所以不会被当成常量不排序,而排序的话每个元素的参考值是相通的,所以redis会按照元素本身的大小排序。

get参数:

  get参数可以指定排序后返回的键值。get参数不影响排序,它的作用是使sort命令的返回结果不再是元素自身的值,而是get参数中指定的键值,get参数的规则和by参数的一样,get参数也支持字符串类型和散列类型的键,并使用"*"作为占位符。一个sort命令中可以有多个get参数,在后面直接加就可以了。如果还需要返回原来的元素值,可以使用"get #"。

store参数:

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

性能优化:

  sort命令是redis中最强大最复杂的命令,使用不好容易成为性能瓶颈,redis在排序前会建立一个长度等同于要排序的集合中的元素个数的容器来存储待排序的元素,虽然是一个临时的过程,但如果同时进行较多的大数据量排序操作则会严重影响性能。所以开发中使用sort命令时需要注意以下几点:

  (1)尽可能减少待排序键中元素的数量;

  (2)尽量使用limit参数只获取需要的数据;

  (3)如果要排序的数据数量较大,尽可能使用store参数将结果缓存。

4. 消息通知

使用redis实现任务队列:

  (1)简单队列概念

  使用loush和rpop命令可以实现队列的概念,但是这样即使队列中没有任务也会一直调用rpop命令查看是否有新任务,最好可以实现有新任务加入队列时通知调用者就好了。这里再介绍一个命令brpop。

  brpop命令接收两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过了此时间仍然没有获得新元素的话就会返回nil。如果超时时间设置为0的话,表示不限制等待时间,即如果没有新元素加入队列就会永远阻塞下去。

  除了brpop名另外,还有blpop命令,即从左取元素。

  (2)队列优先级问题

  brpop命令可以同时接收多个键,其完整的命令格式为"blpop key1 [key2...] timeout",意思是同时检测多个键,如果所有键都没有元素则阻塞,如果其中有一个键有元素则会从该键中弹出元素。如果多个键都有元素,则会按照从左到右的顺序取第一个键中的一个元素。这样如果想要设置优先级,比如先取哪个队列中的元素,只要把它放在blpop前面的参数即可,这样只要第一个键里有元素,不管第二个键里有多少个元素都会先从第一个键中取出。

  (3)发布/订阅模式

  发布订阅模式中包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或多个频道,而发布者可以向指定的频道发送消息,所以订阅此频道的订阅者都会收到此消息。

  发布者发布消息的命令是publish,用法是publish channel message,该命令返回值表示收到这条消息的订阅者数量。发出的消息不会被持久化,也就是说客户端订阅某频道之后只能收到后续发布的该频道的消息,之前发送的就收不到了。

  subscribe channel [channel...] 订阅频道,可以多个

  执行过订阅命令后客户端会进入订阅状态,处于此状态下客户端不能使用除subscribe/unsubscribe/psubscribe/punsubscribe这四个属于发布订阅模式的命令之外的命令,否则会报错。

  进入订阅状态后客户端可能收到三种类型回复,每种类型回复都包含三个值,第一个值是消息的类型,根据消息类型不同,第二、三个值的含义也不同。消息类型可能的值有:

  Subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个值是当前客户端订阅的频道数量。

  message。这个类型的回复是我们最关心的,它表示接收到的消息,第二个值表示产生消息的频道名称,第三个值是消息的内容。

  unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非发布订阅模式的命令了。

  unsubscribe  [channel  [channel ...]]    取消订阅指定频道,如果不指定则会取消订阅所有频道。

  (4)按照规则订阅

  可以使用psubscribe命令订阅指定的规则,规则支持glob风格通配符格式。

  当使用psubscribe命令订阅时,收到的message类型回复会有所改变,一共四个值,第一个是pmessage而不是message,第二个是订阅时使用的通配符,第三个值表示实际收到消息的频道命令,第四个值则表示消息内容。

  使用psubscribe命令可以重复订阅一个频道,如某客户端执行了psubscribe channel.? channel.?*,这时向channel.2发布消息后该客户端会收到两条消息,而同时publish命令返回的值也是2而不是1。同样的,如果另一个客户端执行了subscribe channel.10,和psubscribe channel.?*的话,向channel.10发送命令该客户端也会收到两条消息(但是是两种类型,message和pmessage),同时publish命令会返回2。

  punsubscribe命令可以退订指定的规则,用法是punsubscribe [pattern [pattern ...]],如果没有参数会退订所有的规则。

  使用punsubscribe命令只能退订通过psubscribe命令订阅的规则,不会影响直接通过subscribe命令订阅的频道;同样unsubscribe命令也不会影响通过psubscribe命令订阅的规则。另外容易出错的一点是使用punsubscribe命令退订某个规则时不会将其中的通配符展开,而是进行严格的字符串匹配,所以punsubscribe *无法退订channel.*规则,而是必须使用punsubscribe channel.*才能退订。