上一篇博客讲了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
:有序集合中对指定成员的分数加上增量 incrementzcount 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月之后再进行业务场景的深入使用式理解。