上一篇博客讲了Redis的概要,本篇内容主要学习下Redis的数据结构和一些常用命令,以及这些数据结构在上一篇里的应用场景有哪些,为什么这个数据结构比较适用于该场景。

五种常用数据结构

Redis是高性能键值对数据库,支持的键值数据类型:字符串类型 ,散列类型,列表类型 ,集合类型,有序集合类型 , 这些类型的操作方式和结构需要详细了解下。

Redis 字符串(String)

字符串的操作命令有很多,常用的操作命令有如下几种,涉及到:设置及获取值,获取并修改值,自增值,自减值,追加字符串等操作:

  • set key value:设置指定 key 的值。
  • get key:获取指定 key 的值。
  • getset key value:将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
  • incr key:将 key 中储存的数字值增一。
  • incr key increment:将 key 所储存的值加上给定的增量值(increment)
  • decr key :将 key 中储存的数字值减一。
  • decr key decrement:key 所储存的值减去给定的减量值(decrement) 。
  • append key value:如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
  • del key:删除该key。

以下操作都是O(1)实际操作如下图所示:

注意:这里 append name tml 命令展示的是两段字符串追加后的长度

注意:这里 append num 6 命令可不是数字的增加,而是字符串的拼接。

Redis 哈希(Hash)

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。类似于String Key和String Value的map容器。

  • hset key field value:将哈希表 key 中的字段 field 的值设为 value 。
  • hget key field:获取存储在哈希表中指定字段的值。
  • hmset key field1 value1 field2 value2 ...:同时将多个 field-value (域-值)对设置到哈希表 key 中。时间复杂度为O(n)
  • hmget key field1 field2...:获取所有给定字段的值。时间复杂度为O(n)
  • hgetall key:获取在哈希表中指定 key 的所有字段和值 。时间复杂度为O(n)
  • hdel key field1 field2... :删除一个或多个哈希表字段。返回值为0则表示删除的属性不存在
  • del key:删除该key,也就是删除该哈希。

关于赋值取值和删除的操作如下图所示,当然del key 的时候返回的是不存在,即hash表被删除了:

还有一些自增及判断的命令:

  • hincrby key field increment:为哈希表 key 中的指定字段的整数值加上增量 increment 。
  • hlen key:获取哈希表中字段的数量
  • hvals key:同时将多个 field-value (域-值)对设置到哈希表 key 中。时间复杂度为O(n)
  • hexists key field:查看哈希表 key 中,指定的字段是否存在。时间复杂度为O(n)

可以依据以上命令进行一些更加复杂的操作,如下图所示:

Redis 列表(List)

Redis列表是quicklist(快速列表)的数据结构。 quicklist是由ziplist(压缩列表)和linkedlist (普通链表组成),按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。Redis列表相当于Java里的LinkedList(基于双向循环链表)和ziplist的组合,注意它是链表而不是数组。这意味着list的插入和删除操作非常快时间复杂度为O(1),查询定位很慢,时间复杂度为O(n)。

简单常用的一些相关相关命令:

  • lpush key value1 [value2]:将一个或多个值插入到列表头部,从左侧添加,最后添加的在最左边
  • lrange key start stop:获取列表指定范围内的元素, 假如共有6个元素,[0,-2]表示从列表头到倒数第二个,[0,-1]和[0,5]效果一样。
  • rpush key value1 [value2]:将一个或多个值插入到列表尾部,从右侧添加,最后添加的在最右边
  • lpop key:移出并获取列表的第一个元素,弹出列表头
  • rpop key:移除列表的最后一个元素,返回值为移除的元素。
  • llen key:获取列表长度。
  • lpushx key value:将一个值插入到已存在的列表头部,仅队列存在时有效。当队列不存在时,不进行任何操作。
  • rpushx key value:将一个值插入到已存在的列表尾部,仅队列存在时有效。当队列不存在时,不进行任何操作。

操作实例,对队列tml进行操作。

当然还有些相对复杂的命令:

  • lrem key count value:移除count个为value的元素,如果count大于0,则从左向右数,如果count小于0,从右向左数,如果count等于0,删除全部为value的元素。
  • lset key index value:通过索引设置列表元素的值,列表头的索引是0.
  • linsert key before|after pivot value:在列表的元素前或者后插入元素

实操的效果如下图所示:

当然还有一种命令比较适用某种业务场景:

  • rpoplpush sourcelist destinationlist:移除列表的最后一个元素,并将该元素添加到另一个列表并返回,

命令实操如下图所示:

命令 rpoplpush sourcelist destinationlist经常用于消息队列,一个程序正在执行lpush向列表中插入数据,该程序是生产者,一个程序正在执行rpop向列表中取出数据,该程序是消费者。消息只存在于二者的上下文之间,如果消息取出后消费者程序奔溃,则消息被永久的丢失了。解决方案是消费者程序从主消息队列中取出消息并且还通过rpoplpush存放到备份队列中,消费者程序正常执行后删除该备份队列里的消息,如果备份队列里的消息过期,说明消费者程序没使用该消息(使用意味着删除备份队列里的消息),则从备份队列放到主队列,方便其他消费者使用。这样消息就永远不会丢失了,是安全队列的实现原理。

Redis 集合(Set)

Redis 的 Set 是 String 类型的无序集合集合成员是唯一的,这就意味着集合中不能出现重复的数据。集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

  • sadd key member1 [member2]:向集合添加一个或多个成员
  • srem key member1 [member2]:移除集合中一个或多个成员
  • smembers key:返回集合中的所有成员
  • sismember key member:判断 member 元素是否是集合 key 的成员
  • scard key:获取集合的成员数
  • srandmember key [count]:返回集合中一个或多个随机数
  • spop key:移除并返回集合中的一个随机元素

命令实操图如下所示:

set集合之间的操作通过如下命令实现:

  • sdiff key1 [key2]:返回给定所有集合的差集,两个集合的第一个不同数字。
  • sdiffstore destination key1 [key2]:返回给定所有集合的差集并存储在 destination 中
  • sinter key1 [key2]:返回给定所有集合的交集
  • sinterstore destination key1 [key2]:返回给定所有集合的交集并存储在 destination 中
  • sunion key1 [key2]:返回所有给定集合的并集
  • sunionstore destination key1 [key2]:返回所有给定集合的并集存储在 destination 集合中
  • smove source destination member:将 member 元素从 source 集合移动到 destination 集合

实操效果如下图所示:

常用的使用场景是set里存储唯一的访问ip。还有一种是:所有购买某一个电子设备的id存储在一个set中,购买另外一个电子设备的id存储在set中,使用交集操作获取同时购买两个电子设备的id。

Redis 有序集合(sorted set)

redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。添加和删除都需要修改skiplist,所以复杂度为O(log(n))。 但是如果仅仅是查找元素的话可以直接使用hash,其复杂度为O(1)。 其他的range操作复杂度一般为O(log(n))当然如果是小于64的时候,因为是采用了ziplist的设计,其时间复杂度为O(n)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。set集合之间的操作通过如下命令实现:

  • zadd key score1 member1 [score2 member2]:向有序集合添加一个或多个成员,或者更新已存在成员的分数
  • zscore key member:返回有序集中,成员的分数值
  • zcard key:获取有序集合的成员数
  • zrem key member [member ...]:移除有序集合中的一个或多个成员
  • zrange key start stop [withscores]:通过索引区间返回有序集合成指定区间内的成员,如果需要同时返回scores ,带上后边那段,默认分数从低到高
  • zrevrange key start stop [withscores]:返回索引区间集中指定区间内的成员,分数从高到底
  • zrangebyscore key min max [withscores] [limit]:通过分数区间返回有序集合指定区间内的成员,默认分数从低到高
  • zremrangebyrank key start stop:移除有序集合中给定的排名区间的所有成员
  • zremrangebyscore key min max:移除有序集合中给定的分数区间的所有成员
  • zincrby key increment member:有序集合中对指定成员的分数加上增量 increment
  • zcount key min max:计算在有序集合中指定区间分数的成员数

实操如下图所示:

主要使用场景是排行榜!,玩家分数变化可以更新玩家分数,然后再次获取积分信息。

数据结构常用场景

通过N天的学习,终于了解了这些数据结构,并且实操手打了一遍命令,通过命令的编写实际的体验了一波redis,感觉这些数据结构的设计简直就是针对现在web2.0的使用场景量身定做啊,再次梳理下场景:

  • String,redis对于KV的操作效率很高,可以直接用作计数器。例如,统计在线人数等等,另外string类型是二进制存储安全的,所以也可以使用它来存储图片,甚至是视频等。
  • hash,存放键值对,一般可以用来存某个对象的基本属性信息,例如,用户信息,商品信息等,另外,由于hash的大小在小于配置的大小的时候使用的是ziplist结构,比较节约内存,所以针对大量的数据存储可以考虑使用hash来分段存储来达到压缩数据量,节约内存的目的,例如,对于大批量的商品对应的图片地址名称。比如:商品编码固定是10位,可以选取前7位做为hash的key,后三位作为field,图片地址作为value。这样每个hash表都不超过999个,只要把redis.conf中的hash-max-ziplist-entries改为1024,即可。
  • list,列表类型,可以用于实现消息队列,也可以使用它提供的range命令,做分页查询功能。
  • set,集合,整数的有序列表可以直接使用set。可以用作某些去重功能,例如用户名不能重复等,另外,还可以对集合进行交集,并集操作,来查找某些元素的共同点
  • zset,有序集合,可以使用范围查找,排行榜功能或者topN功能

总而言之,string当做计数器,hash存储对象,list实现消息队列(安全队列),set用来去重和联表查询,zset用来做排行榜。

总结

这篇博客的工作量较大,详细的学习了下redis的数据结构并且思考了下为什么要使用这样的结构,以及不同的数据结构能应对哪些使用场景。当然目前而言对Redis数据结构的了解不算太深,例如跳跃列表。按照刷墙式学习,之后涂抹第二层的时候可以更加深入一些。继续加油,在6月底前对Redis有个大概的了解,6月之后再进行业务场景的深入使用式理解。