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,解决办法:**分段存储
应用场景:
购物车
如上面的购物车中,我们可以做如下存储:
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)量;京东领取金豆的签到日历;
这时有人就会说了,前面提到的数据结构已经能够做到上面的需求了啊,例如上面的签到日历,使用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。国庆假期先出去玩会 回来写,哈哈哈。