Redis数据结构,一个字牛。

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
简介: Redis数据结构,一个字牛。

redis的数据结构

redis前导知识

redis是什么?


The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker.


进入官网是这么显示展示的,从这能够得到的信息就是:1:redis是开源的。2:redis是用来做数据存储的数据库。3:数百万的开发者用redis来做数据库,缓存,流式处理引擎和消息代理的。


redis的优势


  • 高性能 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。


写文章的目的


目前的工作做中redis扮演者越来越重要的角色,但是我看挺多人都只会使用redis的字符串,当然选择也应该根据实际的业务场景来选择合适的数据结构。由此我想对此做一个总结,同时自己也做一个复习。好了废话说了这么多下面就进入我们的主题吧!


数据结构


String


单值缓存


set key value //存储
get key  //取值


对象缓存


set user:1 vale(json格式数据)  //
get user:1 
mset user:1:name qijian user:2:balance 188
mget user:1:name user:1:balance


缓存对象: key(对象+id+属性) value(值)


如果经常需要修改对象的部分属性使用 第二中方式方便一点。如果使用第一种需要获取出来使用 json转换为对象


分布式锁


**setnx当且仅当key不存在的时候才会设置成功。如果当前的key已经被其他线程设置过,这条命令不会做任何操作。**在多线程的情况下,只有第一个线程执行该条命令的线程才能执行成功,那么就可以认为该条线程获得了分布式锁。


setnx product:10001 true  //返回1代表获取锁成功
setnx product:10001 true  //返回0代表获取锁失败
...执行业务操作
del product:10001         //执行完业务释放锁
set product:10001 true ex 10 nx   //防止程序意外终止导致死锁


计数器


INCR article:readcount:{文章id}   //只要文章被打开就执行这条命令,没执行一次就加一
GET article:readcount:{文章id}      // 获取最终值



这个使用也特别多:比如用来计算浏览量,访问量等等。


hash结构


Hash常用操作
HSET  key  field  value       //存储一个哈希表key的键值 
注意:如果 key 指定的哈希集不存在,会创建一个新的哈希集并与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


注意:每个 hash 可以存储 2^32 - 1 键值对(40多亿)。


对象缓存


对象缓存
HMSET  user  {userId}:name  zhuge  {userId}:balance  1888
HMSET  user  1:name  QiJian  1:balance  1888
HMGET  user  1:name  1:balance


**注意:如果存在上千万条甚至上亿条记录,就不太还是使用这种存储方式。在redis中最怕的就是大key(big key),因为大key的话会阻塞redis。当几百万条的数据时,就只有一个user ,当获取所有的值的时候。这一条命令需要执行很长的时间。big key 会阻塞redis,解决办法:**分段存储


应用场景:


购物车

c7518151123e5f5de7fce0c385015a33.png


如上面的购物车中,我们可以做如下存储:


1)以用户id为key 2)商品id为field 3)商品数量为value. 这样就可以简单的做一个购物车商品的操作。代码,如下:


购物车操作
添加商品 hset user:1001 10088 1
增加数量 hincrby user:1001 10088 1
商品总数 hlen user:1001
删除商品 hdel user:1001 10088
获取购物车所有商品 hgetall user:1001


但是需要特别注意:过期功能不能使用在field上,只能用在key上。也就是说使用hash结构时,不能对field使用过期功能。


List结构


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秒,如果timeout=0,一直阻塞等待
BRPOP  key  [key ...]  timeout  //从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待          timeout秒,如果timeout=0,一直阻塞等待


注意:列表最多可存储 2^32 - 1 元素 (4294967295, 每个列表可存储40多亿)。


使用List结构还可以用来做栈,队列和阻塞队列


Stack(栈) = LPUSH + LPOP

Queue(队列)= LPUSH + RPOP

Blocking MQ(阻塞队列)= LPUSH + BRPOP


应用场景:微博和微信公众号消息流


显示消息时,存在时间上的先后顺序,先发消息时展示在前面,后发消息展示在后面


1)A发微博,消息ID为10018
LPUSH  msg:{user-ID}  10018
2)B微博,消息ID为10086
LPUSH  msg:{user-ID} 10086
3)查看最新微博消息
LRANGE  msg:{user-ID}  0  4 


Set


Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(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中删除
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中


运算


SINTER set1 set2 set3 -> { c }     //交集(所有共有)
SUNION set1 set2 set3 -> { a,b,c,d,e }   // 合集(所有元素)
SDIFF set1 set2 set3 -> { a }   // 差集(只看第一个集合set1)


注意:SDIFF set1 set2 set3 第一个集合减去后面两个集合的并集。(set1 - set2 n set3)

应用场景:

微信抽奖小程序
1)点击参与抽奖加入集合
SADD key {userlD}
2)查看参与抽奖所有用户
SMEMBERS key
3)抽取count名中奖者
SRANDMEMBER key [count] 或 SPOP key [count]


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 …] //交集计算


应用场景:


根据商品销售对商品进行排序显示:定义商品排行榜(sorted set集合),key为goods:sellsort,分数为商品的销售数量。


1)商品编号1001的销量是9,商品编号对的1002的销售是15


zadd goods:sellsort 9 1001 15 1002


2)客户下单2件1001商品


zincrby good:sellsort 2 1002
• 1


3)获取商品销售前十


zrange goods:sellsort 0 10 withscores


以上可以说是redis的核心数据结构,相对比较常见就不再继续深入了。但是在数据量大的时候依然不能满足我们的需求。下面还有三种新类型。


bitmap


面试:如何记录对集合中的数据进行统计。

例如;在移动应用中,需要统计每天的新增用户和第二天留存的用户数;在签到打卡中需要统计一个月内连续打卡的用户数;

在网页上统计独立访客(UV)量;京东领取金豆的签到日历;


京东领取金豆签到日历.png


这时有人就会说了,前面提到的数据结构已经能够做到上面的需求了啊,例如上面的签到日历,使用string结构,签了是1,没有签是2。别急我们接着往下看。


没错很显然,使用上面的sting结构就可做到。但是用没有想过一个问题如果数据量和大的时候呢?在现代信息化时代用户访问的级别为亿级不过分吧?比如头条,抖音,淘宝,京东等,上亿级别不为过吧。


biimap的基本思想就是使用一个bit位来标记某个元素对应的value,而key就是该元素,这样就可以大大节省存储空间。本质上位图就是一个普通的字符串,也就是bit数组,如下:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bV3fuEEF-1664703181420)(https://qijian-1301807797.cos.ap-guangzhou.myqcloud.com/markdown/%E4%BD%8D%E5%9B%BE.png)]


说到此时我想大家应该想到了,如果使用位图来存储京东的打卡日历,签到就1,否则0.最后就会形成用0和1组成一串数字。注意这里每一天的记录只占一位,也就是一年就占365位,所占字节等于365/8,约等于46个字节。这时如果想要统计用户的签到的天数,那么只需要统计这一年里1出现的次数。会发现相比较于使用string(每一个1就需要一个字节),使用位图大大节省了空间同时也提高了效率。


一时过瘾说了bitmap的这么多。接下来我们看看常见的命令:


setbit key offset value  //用来设置或清楚某一位上的值。初始状态下所有位都是0 offset代表偏移量从0开始
getbit key offset //获取某一位上的值,不存在返回0;注意这里用string的get key是获取不到正确的数的,使用get会以16进制来显示。
bitcount key start end //统计指定位区间上,值为1的个数。可以使用负数,代表倒数第几位
strlen key //统计占多上字节数


注意:bitmap 的数组是由二进制位组成,每个二进制都对应一个偏移量。支持的最大位数是2^ 32位,这时也只需要大概512M左右。


不知不觉已经5:30 ,还有就是hayerloglog , geo。国庆假期先出去玩会 回来写,哈哈哈。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
存储 NoSQL Redis
Redis系列学习文章分享---第十六篇(Redis原理1篇--Redis数据结构-动态字符串,insert,Dict,ZipList,QuickList,SkipList,RedisObject)
Redis系列学习文章分享---第十六篇(Redis原理1篇--Redis数据结构-动态字符串,insert,Dict,ZipList,QuickList,SkipList,RedisObject)
61 1
|
2月前
|
存储 消息中间件 缓存
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
62 0
|
2月前
|
存储 NoSQL 安全
Redis系列学习文章分享---第十五篇(Redis最佳实践--设计优雅的key+合适的数据结构+持久化如何配置+慢查询问题解决)
Redis系列学习文章分享---第十五篇(Redis最佳实践--设计优雅的key+合适的数据结构+持久化如何配置+慢查询问题解决)
49 1
|
1月前
|
消息中间件 存储 NoSQL
Redis数据结构—跳跃表 skiplist 实现源码分析
Redis 是一个内存中的数据结构服务器,使用跳跃表(skiplist)来实现有序集合。跳跃表是一种概率型数据结构,支持平均 O(logN) 查找复杂度,它通过多层链表加速查找,同时保持有序性。节点高度随机生成,最大为 32 层,以平衡查找速度和空间效率。跳跃表在 Redis 中用于插入、删除和按范围查询元素,其内部节点包含对象、分值、后退指针和多个前向指针。Redis 源码中的 `t_zset.c` 文件包含了跳跃表的具体实现细节。
|
1月前
|
存储 NoSQL Redis
Redis数据结构—跳跃表 skiplist
Redis数据结构—跳跃表 skiplist
|
1月前
|
NoSQL Redis
Redis05数据结构介绍,数据结构介绍,官方网站中看到
Redis05数据结构介绍,数据结构介绍,官方网站中看到
|
2月前
|
存储 缓存 NoSQL
Redis为什么速度快:数据结构、存储及IO网络原理总结
Redis为什么速度快:数据结构、存储及IO网络原理总结
|
存储 NoSQL 算法
「Redis」数据结构与对象
Redis数据结构与对象介绍
|
NoSQL 算法 Java
Redis进阶 - 数据结构:对象机制详解,一文深入底层分析
我们在前文已经阐述了Redis 5种基础数据类型详解,分别是字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合(zset),以及5.0版本中Redis Stream结构详解;那么这些基础类型的底层是如何实现的呢?Redis的每种对象其实都由对象结构(redisObject) 与 对应编码的数据结构组合而成, 本文主要介绍对象结构(redisObject) 部分。
Redis进阶 - 数据结构:对象机制详解,一文深入底层分析