- 异常快 - Redis 非常快每秒可执行大约 110000 次的设置(SET)操作每秒大约可执行 81000 次的读取/获取(GET)操作。
- 支持丰富的数据类型 - Redis 支持开发人员常用的大多数数据类型例如列表集合排序集和散列等等。这使得 Redis 很容易被用来解决各种问题因为我们知道哪些问题可以更好使用地哪些数据类型来处理解决。
- 操作具有原子性 - 所有 Redis 操作都是原子操作这确保如果两个客户端并发访问Redis 服务器能接收更新的值。
- 多实用工具 - Redis 是一个多实用工具可用于多种用例如缓存消息队列(Redis 本地支持发布/订阅)应用程序中的任何短期数据例如web应用程序中的会话网页命中计数等。
Redis 有 5 种基础数据结构它们分别是string(字符串)、list(列表)、hash(字典)、set(集合) 和 zset(有序集合)。这 5 种是 Redis 相关知识中最基础、最重要的部分下面我们结合源码以及一些实践来给大家分别讲解一下。
1字符串 string
Redis 中的字符串是一种 动态字符串这意味着使用者可以修改它的底层实现有点类似于 Java 中的 ArrayList有一个字符数组
- 注Redis 规定了字符串的长度不得超过 512 MB。
对字符串的基本操作
常见命令
字符串常用操作SET key value //存入字符串键值对MSET key value [key value ...] //批量存储字符串键值对SETNX key value //存入一个不存在的字符串键值对GET key //获取一个字符串键值MGET key [key ...] //批量获取字符串键值DEL key [key ...] //删除一个键EXPIRE key seconds //设置一个键的过期时间(秒)原子加减INCR key //将key中储存的数字值加1DECR key //将key中储存的数字值减1INCRBY key increment //将key所储存的值加上incrementDECRBY key decrement //将key所储存的值减去decrement
设置和获取键值对
> SET key valueOK> GET key"value"
值可以是任何种类的字符串包括二进制数据例如你可以在一个键下保存一张 .jpeg 图片只需要注意不要超过 512 MB 的最大限度就好了。
当 key 存在时SET 命令会覆盖掉你上一次设置的值
> SET key newValueOK> GET key"newValue"
另外你还可以使用 EXISTS 和 DEL 关键字来查询是否存在和删除键值对
> EXISTS key(integer) 1> DEL key(integer) 1> GET key(nil)
批量设置键值对
> SET key1 value1OK> SET key2 value2OK> MGET key1 key2 key3 # 返回一个列表1) "value1"2) "value2"3) (nil)> MSET key1 value1 key2 value2> MGET key1 key21) "value1"2) "value2"
过期和 SET 命令扩展
可以对 key 设置过期时间到时间会被自动删除这个功能常用来控制缓存的失效时间。(过期可以是任意数据结构)
> SET key value1> GET key"value1"> EXPIRE name 5 # 5s 后过期... # 等待 5s> GET key(nil)
等价于 SET EXPIRE 的 SETNX 命令
> SETNX key value1... # 等待 5s 后获取> GET key(nil)> SETNX key value1 # 如果 key 不存在则 SET 成功(integer) 1> SETNX key value1 # 如果 key 存在则 SET 失败(integer) 0> GET key"value" # 没有改变
计数
如果 value 是一个整数还可以对它使用 INCR 命令进行 原子性 的自增操作这意味着及时多个客户端对同一个 key 进行操作也决不会导致竞争的情况
> SET counter 100> INCR count(interger) 101> INCRBY counter 50(integer) 151
返回原值的 GETSET 命令
对字符串还有一个 GETSET 比较让人觉得有意思它的功能跟它名字一样为 key 设置一个值并返回原值
> SET key value> GETSET key value1"value"
这可以对于某一些需要隔一段时间就统计的 key 很方便的设置和查看例如系统每当由用户进入的时候你就是用 INCR 命令操作一个 key当需要统计时候你就把这个 key 使用 GETSET 命令重新赋值为 0这样就达到了统计的目的。
应用场景
1、单值缓存 SET key value GET key 2、分布式锁 SETNX product:10001 true //返回1代表获取锁成功 SETNX product:10001 true //返回0代表获取锁失败 。。。执行业务操作 DEL product:10001 //执行完业务释放锁 SET product:10001 true ex 10 nx //防止程序意外终止导致死锁 3、计数器 INCR article:readcount:{文章id} GET article:readcount:{文章id} 4、Web集群session共享 spring session redis实现session共享 5、分布式系统全局序列号 INCRBY orderId 1000 //redis批量生成序列号提升性能 6、对象缓存 1) SET user:1 value(json格式数据) 2) MSET user:1:name zhz user:1:balance 1888 MGET user:1:name user:1:balance
2列表 list
Redis 的列表相当于 Java 语言中的 LinkedList注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快时间复杂度为 O(1)但是索引定位很慢时间复杂度为 O(n)。
链表的基本操作
常用命令
1、List常用操作 LPUSH key value [value …] //将一个或多个值value插入到key列表的表头(最左边) RPUSH key value [value …] //将一个或多个值value插入到key列表的表尾(最右边) LPOP key //移除并返回key列表的头元素 RPOP key //移除并返回key列表的尾元素 LRANGE key start stop //返回列表key中指定区间内的元素区间以偏移量start和stop指定
BLPOP key [key ...] timeout//从key列表表头弹出一个元素若列表中没有元素阻塞等待timeout秒,如果timeout0,一直阻塞等待BRPOP key [key ...] timeout //从key列表表尾弹出一个元素若列表中没有元素阻塞等待timeout秒,如果timeout0,一直阻塞等待
- LPUSH 和 RPUSH 分别可以向 list 的左边头部和右边尾部添加一个新元素
- LRANGE 命令可以从 list 中取出一定范围的元素
- LINDEX 命令可以从 list 中取出指定下表的元素相当于 Java 链表操作中的 get(int index) 操作
示范
> rpush mylist A(integer) 1> rpush mylist B(integer) 2> lpush mylist first(integer) 3> lrange mylist 0 -1 # -1 表示倒数第一个元素, 这里表示从第一个元素到最后一个元素即所有1) "first"2) "A"3) "B"
list 实现队列
队列是先进先出的数据结构常用于消息排队和异步逻辑处理它会确保元素的访问顺序
> RPUSH books python java golang(integer) 3> LPOP books"python"> LPOP books"java"> LPOP books"golang"> LPOP books(nil)
list 实现栈
栈是先进后出的数据结构跟队列正好相反
> RPUSH books python java golang> RPOP books"golang"> RPOP books"java"> RPOP books"python"> RPOP books(nil)
应用场景
1、常用数据结构 Stack(栈) LPUSH LPOP Queue(队列 LPUSH RPOP Blocking MQ(阻塞队列 LPUSH BRPOP 2、微博和微信公号消息流 3、微博消息和微信公号消息 /** * 事例 **/ A关注了MacTalk备胎说车等大V 1MacTalk发微博消息ID为10018 LPUSH msg:{A-ID} 10018 2备胎说车发微博消息ID为10086 LPUSH msg:{A-ID} 10086 3查看最新微博消息 LRANGE msg:{A-ID} 0 4
3字典 hash
常用命令
Hash常用操作 HSET key field value //存储一个哈希表key的键值 HSETNX key field value //存储一个不存在的哈希表key的键值 HMSET key field value [field value …] //在一个哈希表key中存储多个键值对 HGET key field //获取哈希表key对应的field键值 HMGET key field [field …] //批量获取哈希表key中多个field键值 HDEL key field [field …] //删除哈希表key中的field键值 HLEN key //返回哈希表key中field的数量 HGETALL key //返回哈希表key中所有的键值 HINCRBY key field increment //为哈希表key中field键的值加上增量increment
Redis 中的字典相当于 Java 中的 HashMap内部实现也差不多类似都是通过 “数组 链表” 的链地址法来解决部分 哈希冲突同时这样的结构也吸收了两种不同数据结构的优点。 具体实现为字典结构的内部包含两个 hashtable**通常情况下只有一个 hashtable 是有值的但是在字典扩容缩容时需要分配新的 hashtable然后进行 渐进式搬迁 (下面说原因)。
渐进式 rehash
大字典的扩容是比较耗时间的需要重新申请新的数组然后将旧字典所有链表中的元素重新挂接到新的数组下面这是一个 O(n) 级别的操作作为单线程的 Redis 很难承受这样耗时的过程所以 Redis 使用 渐进式 rehash 小步搬迁
渐进式 rehash 会在 rehash 的同时保留新旧两个 hash 结构如上图所示查询时会同时查询两个 hash 结构然后在后续的定时任务以及 hash 操作指令中循序渐进的把旧字典的内容迁移到新字典中。当搬迁完成了就会使用新的 hash 结构取而代之。
扩缩容的条件
正常情况下当 hash 表中 元素的个数等于第一维数组的长度时就会开始扩容扩容的新数组是 原数组大小的 2 倍。不过如果 Redis 正在做 bgsave(持久化命令)为了减少内存也得过多分离Redis 尽量不去扩容但是如果 hash 表非常满了达到了第一维数组长度的 5 倍了这个时候就会 强制扩容。
当 hash 表因为元素逐渐被删除变得越来越稀疏时Redis 会对 hash 表进行缩容来减少 hash 表的第一维数组空间占用。所用的条件是 元素个数低于数组长度的 10%缩容不会考虑 Redis 是否在做 bgsave。
字典的基本操作
hash 也有缺点hash 结构的存储消耗要高于单个字符串所以到底该使用 hash 还是字符串需要根据实际情况再三权衡
> HSET books java "think in java" # 命令行的字符串如果包含空格则需要使用引号包裹(integer) 1> HSET books python "python cookbook"(integer) 1> HGETALL books # key 和 value 间隔出现1) "java"2) "think in java"3) "python"4) "python cookbook"> HGET books java"think in java"> HSET books java "head first java" (integer) 0 # 因为是更新操作所以返回 0> HMSET books java "effetive java" python "learning python" # 批量操作OK
应用场景
对象缓存 HMSET user {userId}:name zhz {userId}:balance 1888 HMSET user 1:name zhz1:balance 1888 HMGET user 1:name 1:balance 电商购物车 1以用户id为key 2商品id为field 3商品数量为value 购物车操作 添加商品->hset cart:1001 10088 1 增加数量->hincrby cart:1001 10088 1 商品总数->hlen cart:1001 删除商品->hdel cart:1001 10088 获取购物车所有商品->hgetall cart:1001
优缺点
优点 1同类数据归类整合储存方便数据管理 2相比string操作消耗内存与cpu更小 3相比string储存更节省空间 缺点 过期功能不能使用在field上只能用在key上 Redis集群架构下不适合大规模使用
4集合 set
Redis 的集合相当于 Java 语言中的 HashSet它内部的键值对是无序、唯一的。它的内部实现相当于一个特殊的字典字典中所有的 value 都是一个值 NULL。
常用命令
1、Set常用操作 SADD key member [member …] //往集合key中存入元素元素存在则忽略若key不存在则新建 SREM key member [member …] //从集合key中删除元素 SMEMBERS key //获取集合key中所有元素 SCARD key //获取集合key的元素个数 SISMEMBER key member //判断member元素是否存在于集合key中 SRANDMEMBER key [count] //从集合key中选出count个元素元素不从key中删除 SPOP key [count] //从集合key中选出count个元素元素从key中删除
2、Set运算操作 SINTER key [key …] //交集运算 SINTERSTORE destination key [key …] //将交集结果存入新集合destination中 SUNION key [key …] //并集运算 SUNIONSTORE destination key [key …] //将并集结果存入新集合destination中 SDIFF key [key …] //差集运算 SDIFFSTORE destination key [key …] //将差集结果存入新集合destination中
集合 set 的基本使用
由于该结构比较简单我们直接来看看是如何使用的
> SADD books java(integer) 1> SADD books java # 重复(integer) 0> SADD books python golang(integer) 2> SMEMBERS books # 注意顺序set 是无序的 1) "java"2) "python"3) "golang"> SISMEMBER books java # 查询某个 value 是否存在相当于 contains(integer) 1> SCARD books # 获取长度(integer) 3> SPOP books # 弹出一个"java"
5有序列表 zset
常用命令
ZSet常用操作 ZADD key score member [[score member]…] //往有序集合key中加入带分值元素 ZREM key member [member …] //从有序集合key中删除元素 ZSCORE key member //返回有序集合key中元素member的分值 ZINCRBY key increment member //为有序集合key中元素member的分值加上increment ZCARD key //返回有序集合key中元素个数 ZRANGE key start stop [WITHSCORES] //正序获取有序集合key从start下标到stop下标的元素 ZREVRANGE key start stop [WITHSCORES] //倒序获取有序集合key从start下标到stop下标的元素 Zset集合操作 ZUNIONSTORE destkey numkeys key [key …] //并集计算 ZINTERSTORE destkey numkeys key [key …] //交集计算
这可能使 Redis 最具特色的一个数据结构了它类似于 Java 中 SortedSet 和 HashMap 的结合体一方面它是一个 set保证了内部 value 的唯一性另一方面它可以为每个 value 赋予一个 score 值用来代表排序的权重。
它的内部实现用的是一种叫做 「跳跃表」 的数据结构。
跳跃表最下面一层所有的元素都会串起来然后每隔几个元素就会挑选出一个代表再把这几个代表使用另外一级指针串起来。然后再在这些代表里面挑出二级代表再串起来。最终形成了一个金字塔的结构。
想一下你目前所在的地理位置亚洲 > 中国 > 某省 > 某市 > …就是这样一个结构
有序列表 zset 基础操作
> ZADD books 9.0 "think in java"> ZADD books 8.9 "java concurrency"> ZADD books 8.6 "java cookbook"> ZRANGE books 0 -1 # 按 score 排序列出参数区间为排名范围1) "java cookbook"2) "java concurrency"3) "think in java"> ZREVRANGE books 0 -1 # 按 score 逆序列出参数区间为排名范围1) "think in java"2) "java concurrency"3) "java cookbook"> ZCARD books # 相当于 count()(integer) 3> ZSCORE books "java concurrency" # 获取指定 value 的 score"8.9000000000000004" # 内部 score 使用 double 类型进行存储所以存在小数点精度问题> ZRANK books "java concurrency" # 排名(integer) 1> ZRANGEBYSCORE books 0 8.91 # 根据分值区间遍历 zset1) "java cookbook"2) "java concurrency"> ZRANGEBYSCORE books -inf 8.91 withscores # 根据分值区间 (-∞, 8.91] 遍历 zset同时返回分值。inf 代表 infinite无穷大的意思。1) "java cookbook"2) "8.5999999999999996"3) "java concurrency"4) "8.9000000000000004"> ZREM books "java concurrency" # 删除 value(integer) 1> ZRANGE books 0 -11) "java cookbook"2) "think in java"
应用场景
1、微信抽奖小程序 1点击参与抽奖加入集合 SADD key {userlD} 2查看参与抽奖所有用户 SMEMBERS key 3抽取count名中奖者 SRANDMEMBER key [count] / SPOP key [count] 2、微信微博点赞收藏标签 1) 点赞 SADD like:{消息ID} {用户ID} 2) 取消点赞 SREM like:{消息ID} {用户ID} 3) 检查用户是否点过赞 SISMEMBER like:{消息ID} {用户ID} 4) 获取点赞的用户列表 SMEMBERS like:{消息ID} 5) 获取点赞用户数 SCARD like:{消息ID} 3、集合操作 SINTER set1 set2 set3 -> { c } SUNION set1 set2 set3 -> { a,b,c,d,e } SDIFF set1 set2 set3 -> { a } 4、集合操作实现微博微信关注模型 1) A关注的人: ASet-> {B, C} 2) D老师关注的人: DSet–> {A, E, B, C} 3) B老师关注的人: BSet-> {A, D, E, C, F) 4) 我A和D老师共同关注: SINTER ASet DSet–> {B, C} 5) 我A关注的人也关注他(D老师): SISMEMBER BSet D SISMEMBER CSet D 6) 我可能认识的人: SDIFF DSet ASet->(A, E} 5、集合操作实现电商商品筛选 SADD brand:huawei P40 SADD brand:xiaomi mi-10 SADD brand:iPhone iphone12 SADD os:android P40 mi-10 SADD cpu:brand:intel P40 mi-10 SADD ram:8G P40 mi-10 iphone12 SINTER os:android cpu:brand:intel ram:8G -> {P40mi-10}
Zset集合操作实现排行榜 1点击新闻 ZINCRBY hotNews:20190819 1 守护香港 2展示当日排行前十 ZREVRANGE hotNews:20190819 0 9 WITHSCORES 3七日搜索榜单计算 ZUNIONSTORE hotNews:20190813-20190819 7 hotNews:20190813 hotNews:20190814… hotNews:20190819 4展示七日排行前十 ZREVRANGE hotNews:20190813-20190819 0 9 WITHSCORES
6其他常见命令
1、keys 全量遍历键 用来列出所有满足特定正则字符串规则的key当redis数据量比较大时性能比较差要避免使用
2、SCAN cursor [MATCH pattern] [COUNT count] scan 参数提供了三个参数第一个是 cursor 整数值(hash桶的索引值)第二个是 key 的正则模式 第三个是一次遍历的key的数量(参考值底层遍历的数量不一定)并不是符合条件的结果数量。第 一次遍历时cursor 值为 0然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历 到返回的 cursor 值为 0 时结束。 注意但是scan并非完美无瑕 如果在scan的过程中如果有键的变化增加、 删除、 修改 那 么遍历效果可能会碰到如下问题 新增的键可能没有遍历到 遍历出了重复的键等情况 也就是说 scan并不能保证完整的遍历出来所有的键开发市需要考虑 3、Info 查看redis服务运行信息分为 9 大块每个块都有非常多的参数这 9 个块分别是: 4、Server 服务器运行的环境参数 5、Clients 客户端相关信息 6、Memory 服务器运行内存统计数据 7、Persistence 持久化信息 8、Stats 通用统计数据 9、Replication 主从复制相关信息 10、CPU CPU 使用情况 11、Cluster 集群信息 12、KeySpace 键值对统计数量信息