redis作为Nosql的代表,想必大家已经再熟悉不过了,除了作为缓存来使用,Redis还提供了其他很多有用的功能,例如可作为消息队列、分布式锁、不隆过滤器、限流等功能使用。今天先来说一说redis作为缓存使用,提供了5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈 希) 和 zset (有序集合)。
一、string (字符串)
字符串 string 是 Redis 最简单的数据结构。Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一key值来获取相应的 value 数据。不同类型的数据结构的差异就在于value 的结构不一样。字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。我们将用户信息结构体 使用 JSON 序列化成字符串,然后将序列化后的字符串塞进 Redis 来缓存。同样,取用户 信息会经过一次反序列化的过程。Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 JAVA 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,字符串最大长度为 512M。使用命令如下:
设置键值对:set [key] [value]
获取对应键的值:get [key]
批量操作键值对:mset [key] [value] [key] [value] [key] [value] ...
批量获取对应键的值:mget [key] [key] [key]...
批量操作可以节省网络耗时开销。
可以对 key 设置过期时间,到点自动删除,这个功能常用来控制缓存的失效时间。在保证缓存是热点数据或者保证缓存和数据库一致性的解决方案时,设置失效时间是必不可少的。还有就是整数的自增或者自减功能,如果 value 值是一个整数,可以对它进行自增操作。自增是有范围的,它的范围是signed long 的最大最小值,超过了这个值,Redis 会报错。
设置过期可以用set+expire的组合,但是保证不了原子性,如果想保证原子性,就用setex命令:
关于自增和自减:
incr/decr:自增/自减 1
incrby/decrby [key] [num]:自增/自减 num
二、list (列表)
Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。链表由于有前后节点,特点是插入和删除快,查询慢;数组由于有索引,所以特点是查询快,但是插入和删除慢;这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。
操作命令:
Lpush——先进后出,在列表头部插入元素
Rpush——先进先出,在列表的尾部插入元素
Lrange——出栈,根据索引,获取列表元素
Lpop——左边出栈,获取列表的第一个元素
Rpop——右边出栈,获取列表的最后一个元素
Lindex——根据索引,取出元素
Llen——链表长度,元素个数
Lrem——根据key,删除n个value
Ltrim——根据索引,删除指定元素
Lset——根据index,设置value
Linsert before——根据value,在之前插入值
Linsert after——根据value,在之后插入值
BLPOP/BRPOP key -- 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
BRPOPLPUSH source destination timeout -- 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
LPUSHX -- 将一个值插入到已存在的列表头部
命令lindex 相当于 Java 链表的 get(int index)方法,它需要对链表进行遍历,性能随着参数 index 增大而变差。如果再深入一点,你会发现 Redis 底层存储的还不是一个简单的linkedlist, 而是称之为快速链表 quicklist 的一个结构。首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。快速列表既满足了快速的插入删除性能,又不会出现太大的空间冗余。
三、hash (字典)
Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。所以特点也是和Java中Map结构相似。不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不一样,因为 Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及 hash 的子指令中,循序渐进地将旧 hash 的内容 一点点迁移到新的 hash 结构中。当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
操作命令:
HSET key field value -- 将哈希表 key 中的字段 field 的值设为 value
HSETNX key field value -- 只有在字段 field 不存在时,设置哈希表字段的值。
HVALS key -- 获取哈希表中所有值
HSCAN key cursor [MATCH pattern] [COUNT count] -- 迭代哈希表中的键值对。
HMSET key field1 value1 [field2 value2 ] -- 同时将多个 field-value (域-值)对设置到哈希表 key 中。
HMGET key field1 [field2] -- 获取所有给定字段的值
HLEN key -- 获取哈希表中字段的数量
HKEYS key -- 获取所有哈希表中的字段
HINCRBYFLOAT key field increment -- 为哈希表 key 中的指定字段的浮点数值加上增量 increment
HINCRBY key field increment -- 为哈希表 key 中的指定字段的整数值加上增量 increment
HGETALL key -- 获取在哈希表中指定 key 的所有字段和值
HGET key field -- 获取存储在哈希表中指定字段的值
HEXISTS key field -- 查看哈希表 key 中,指定的字段是否存在
HDEL key field1 [field2] -- 删除一个或多个哈希表字段
四、set (集合)
Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL,Java中的HashSet也是基于一个value为null的HashMap。当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。 set 结构可以用来做有去重需求需要实现的功能,可以保证唯一性。
操作命令:
SADD key member1 [member2] -- 向集合添加一个或多个成员
SCARD key -- 获取集合的成员数
SDIFF key1 [key2] -- 返回给定所有集合的差集
SDIFFSTORE destination key1 [key2] -- 返回给定所有集合的差集并存储在 destination 中
SINTER key1 [key2] -- 返回给定所有集合的交集
SINTERSTORE destination key1 [key2] -- 返回给定所有集合的交集并存储在 destination 中
SISMEMBER key member -- 判断 member 元素是否是集合 key 的成员
SMEMBERS key -- 返回集合中的所有成员
SMOVE source destination member -- 将 member 元素从 source 集合移动到 destination 集合
SPOP key -- 移除并返回集合中的一个随机元素
SRANDMEMBER key [count] -- 返回集合中一个或多个随机数
SREM key member1 [member2] -- 移除集合中一个或多个成员
SUNION key1 [key2] -- 返回所有给定集合的并集
SUNIONSTORE destination key1 [key2] -- 所有给定集合的并集存储在 destination 集合中
SSCAN key cursor [MATCH pattern] [COUNT count] -- 迭代集合中的元素
spop命令是随机移除一个value,这一操作,刚好是baidu从bat中给移除去了,冥冥之中自有天意呀!不过baidu的技术还是最牛逼的!
五、zset (有序列表)
zset 是 Redis 提供的最为特色的数据结构,它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着“跳跃列表”的数据结构。当zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。
操作命令:
ZADD key score1 member1 [score2 member2] -- 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZCARD key -- 获取有序集合的成员数
ZCOUNT key min max -- 计算在有序集合中指定区间分数的成员数
ZINCRBY key increment member -- 有序集合中对指定成员的分数加上增量 increment
ZINTERSTORE destination numkeys key [key ...] -- 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
ZLEXCOUNT key min max -- 在有序集合中计算指定字典区间内成员数量
ZRANGE key start stop [WITHSCORES] -- 通过索引区间返回有序集合指定区间内的成员
ZRANGEBYLEX key min max [LIMIT offset count] -- 通过字典区间返回有序集合的成员
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] -- 通过分数返回有序集合指定区间内的成员
ZRANK key member -- 返回有序集合中指定成员的索引
ZREM key member [member ...] -- 移除有序集合中的一个或多个成员
ZREMRANGEBYLEX key min max -- 移除有序集合中给定的字典区间的所有成员
ZREMRANGEBYRANK key start stop -- 移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYSCORE key min max -- 移除有序集合中给定的分数区间的所有成员
ZREVRANGE key start stop [WITHSCORES] -- 返回有序集中指定区间内的成员,通过索引,分数从高到低
ZREVRANGEBYSCORE key max min [WITHSCORES] -- 返回有序集中指定分数区间内的成员,分数从高到低排序
ZREVRANK key member -- 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
ZSCORE key member -- 返回有序集中,成员的分数值
ZUNIONSTORE destination numkeys key [key ...] -- 计算给定的一个或多个有序集的并集,并存储在新的 key 中
ZSCAN key cursor [MATCH pattern] [COUNT count] -- 迭代有序集合中的元素(包括元素成员和元素分值)
zset 内部的排序功能是通过"跳跃列表"数据结构来实现的,它的结构非常特殊,也比较复杂。因为 zset 要支持随机的插入和删除,所以它不好使用数组来实现。