帝国手机网站模板,电商网销,经营网站 备案,深圳市住房和建设局红色警示查询Redis常用数据结构与应用场景
redis中存储数据是以key-value键值对的方式去存储的#xff0c;其中key为string字符类型#xff0c;value的数据类型可以是string(字符串)、list(列表)、hash(字典)、set(集合) 、 zset(有序集合)。 这5种数据类型在开发中可以应对大部分场景的…Redis常用数据结构与应用场景
redis中存储数据是以key-value键值对的方式去存储的其中key为string字符类型value的数据类型可以是string(字符串)、list(列表)、hash(字典)、set(集合) 、 zset(有序集合)。 这5种数据类型在开发中可以应对大部分场景的存储
拓展key的底层存储方式SDS
这里有一个问题当我们使用一条redis命令set key value的时候redis进行了什么操作 其实当我们使用命令操作redis的时候也是会经过redis客户端到redis服务端的过程这些命令相当于一个请求
redis客户端通过socket传输这些命令redis服务端通过io读取到这些请求命令把所有的命令解析成一个字符串并执行对应命令操作然后再经过socket写回操作结果
同时redis是使用c语言写的但它底层在存放redis的key时并没有用c语言原生的字符串数据结构而是定义了一个属于redis的数据结构SDS(Simple Dynamic String)
struct sdshdr{//记录buf数组中已使用字节的数量//等于 SDS 保存字符串的长度int len;//记录 buf 数组中未使用字节的数量int free;//字节数组用于保存字符串char buf[];
}为什么要用这样的一个数据结构去存储字符串呢
二进制安全的数据结构。 比如是操作命令是get aaa\0 获取 aaa\0的值。如果是c语言的字符数组就会把\0吞掉变为get aaa而使用SDS就会完整的操作aaa\0SDS把所有接受到的数据都转成字符串即使是一些特殊字符SDS提供了内存预分配机制避免频繁的内存分配。 如果是c语言在修改一个key时会分配一个新的字符数组然后进行内存赋值而SDS则采用预先分配机制直接把字符串容量扩大两倍key的长度变化时直接在已分配的内存中修改即可如果不够继续扩大2倍 思考SDS采用的也是一种空间换时间的思路无论是扩展之后分配多余空间从而降低下次扩展时需要再次内存分配的概率还是缩容之后并不立即回收空间而是留给下次扩容这两种操作都会导致空闲空间增大内存占用提升而Redis又了很多数据压缩策略来控制内存。杜绝缓冲区溢出。 在C语言原生的字符串中当需要修改字符串且修改后的长度大于修改前的长度时在修改之前需要先对原数组申请空间扩容否则可能导致数组溢出内容写入到相邻的下一个数组中从而改变下一个字符串的值。 在SDS中SDS屏蔽了用户对数组空间的分配SDS在增长之前会根据free属性自动检测是否足够修改之后的字符串所需空间如果足够则直接修改并更新修改之后的len和free属性如果当前剩余空间不够SDS会根据空间分配策略自动进行扩容无需用户关心。 思考类似于Java中的String类高级容器等会提供自动扩容缩容的功能具体的细节对使用者透明能减少开发者的编码负担。
string应用场景
常用操作
命令说明SET key value存入字符串键值对MSET key value [key value …]批量存储字符串键值对SETNX key value存入一个不存在的字符串键值对GET key获取一个字符串键值MGET key [key …]批量获取字符串键值EXPIRE key seconds设置一个键的过期时间(秒)DEL key [key …]删除一个键INCR key将key中储存的数字值加1DECR key将key中储存的数字值减1INCRBY key increment将key所储存的值加上incrementDECRBY key decrement将key所储存的值减去decrement
设置和获取键值对 SET key value
OKGET key
value值得注意的是 当key存在时set命令会覆盖掉你上一次设置的值 SET key newValue
OKGET key
newValue使用 EXISTS 和 DEL 关键字来查询是否存在和删除键值对 EXISTS key
(integer) 1DEL key
(integer) 1GET key
(nil)批量设置键值对 SET key1 value1
OKSET key2 value2
OKMGET key1 key2 key3 # 返回一个列表
1) value1
2) value2
3) (nil)MSET key1 value1 key2 value2MGET key1 key2
1) value1
2) value2过期和 SET 命令扩展 可以对 key 设置过期时间到时间会被自动删除这个功能常用来控制缓存的失效时间。(过期可以是任意数据结构) SET key value1GET key
value1EXPIRE name 5 # 5s 后过期
... # 等待 5sGET key
(nil)返回原值的 GETSET 命令 对字符串还有一个 GETSET 比较让人觉得有意思它的功能跟它名字一样为 key 设置一个值并返回原值 SET key valueGETSET key value1
value这可以对于某一些需要隔一段时间就统计的 key 很方便的设置和查看例如系统每当由用户进入的时候你就是用 INCR 命令操作一个 key当需要统计时候你就把这个 key 使用 GETSET 命令重新赋值为 0这样就达到了统计的目的。
使用场景
单值缓存
SET key value
GET key使用这两条命令可以做用户id存储、商品库存存储等等
对象存储 以缓存user对象为例有以下两种方式 1SET user:1 value(json格式数据)把对象转json存入redis也是当下常用的方式获取数据需要做数据转换 2MSET user:1:name zhuge user:1:balance 1888 MGET user:1:name user:1:balance 使用Mset命令把对象拆开存储每一个key只保存对象的一个字段信息适用于经常修改user的某个字段的场景分布式锁
SETNX product:10001 true //操作product:10001
##执行业务操作...
DEL product:10001 //删除product:10001其中SETNX key value 命令要求如果key已存在则其他的setnx命令无法对当前key进行操作。
在使用分布式锁时通常还会通过 SET product:10001 true ex 10 nx 命令设置key的超时时间防止死锁
计数器
NCR 文章id可以使用INCR命令实现数量自增可以用于文章阅读量、热度人数统计等用户每点进去一次执行一次INCR命令
分布式系统全局序列号 在分布式系统下如果需要分库分表 mysql的数据库自增id已经无法满足分库分表下的id自增这时就需要一个独立于数据库之外的中间件来实现id的分配。
redis的INCR命令可以实现id、序列号的生成但如果用户量非常大每生成一个id、序列号都去redis会给redis添加不小的压力我们可以一次性从redis中自增1000次把序列号放入本地内存中这1000个id用完了再去redis再取1000个可有效降低redis的压力
hash应用场景
Redis 中的字典hash相当于 Java 中的 HashMap内部实现也差不多类似都是通过 “数组 链表” 的链地址法来解决部分 哈希冲突同时这样的结构也吸收了两种不同数据结构的优点。
常用操作
命令说明HSET key field value存储一个哈希表key的键值HGET key field获取哈希表key对应的field键值HMSET key field value [field value …]在一个哈希表key中存储多个键值对HMGET key field [field …]批量获取哈希表key中多个field键值HSETNX key field value存储一个不存在的哈希表key的键值HDEL key field [field …]删除哈希表key中的field键值HLEN key返回哈希表key中field的数量HGETALL key返回哈希表key中所有的键值HINCRBY key field increment为哈希表key中field键的值加上增量increment
字典相关操作 HSET books java think in java # 命令行的字符串如果包含空格则需要使用引号包裹
(integer) 1HSET books python python cookbook
(integer) 1HGETALL books # key 和 value 间隔出现
1) java
2) think in java
3) python
4) python cookbookHGET books java
think in javaHSET books java head first java
(integer) 0 # 因为是更新操作所以返回 0HMSET books java effetive java python learning python # 批量操作
OK使用场景
电商购物车 1以用户id为key cart:1001 2以商品id为field10088 3商品数量为value 1 因此购物车操作可以如下 1添加商品hset cart:1001 10088 1 2增加数量 hincrby cart:1001 10088 1 3商品总数 hlen cart:1001 4删除商品 hdel cart:1001 10088 5获取购物车所有商品 hgetall cart:1001
思考-优点 1同类数据归类整合储存方便数据管理 2相比string操作消耗内存与cpu更小 因为string类型通过set key - val 的方式存储数据通过对key进行hash运算决定当前key是存储在数组哪个位置。如果把hash类型的数据变成string类型来存储则需要更多的key同时在存放时也需要更多的hash(key)运算消耗更多的cpu资源 3相比string储存更节省空间 如果把hash类型的数据变成string类型来存储将需要存储更多key如果数据量很多的情况下redis底层那么存储数据的数组将很快会被占满占满就会进行扩容加大内存消耗。由此可见string结构与hash结构只存储一个key相比需要更多的内存空间 思考-缺点 1过期功能不能使用在field上只能用在key上 redis的过期时间只能用在key上而hash的key是一个大的概念里面的map型结构才是重要数据但过期时间只能用在外边的大key上hash结构相比于string不能实现精准过期 2hash结构在Redis集群架构下不适合大规模使用 因为如果一个hash的key中的属性很多的话只能存在一个redis节点上那么这个节点压力会比其他节点压力大很多造成redis集群下压力分配不均衡
list应用场景
Redis 的列表相当于 Java 语言中的 LinkedList注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快时间复杂度为 O(1)但是索引定位很慢时间复杂度为 O(n)。
常用操作
命令说明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,一直阻塞等待
list相关
LPUSH和RPUSH分别可以向list的左边头部和右边尾部添加一个新元素LRANGE命令可以从list中取出一定范围的元素LINDEX命令可以从list中取出指定下标的元素相当于java量表操作中的getint index操作 rpush mylist A
(integer) 1rpush mylist B
(integer) 2lpush mylist first
(integer) 3lrange mylist 0 -1 # -1 表示倒数第一个元素, 这里表示从第一个元素到最后一个元素即所有
1) first
2) A
3) Blist 实现队列 队列是先进先出的数据结构常用于消息排队和异步逻辑处理它会确保元素的访问顺序 RPUSH books python java golang
(integer) 3LPOP books
pythonLPOP books
javaLPOP books
golangLPOP books
(nil)list 实现栈 栈是先进后出的数据结构跟队列正好相反 RPUSH books python java golangRPOP books
golangRPOP books
javaRPOP books
pythonRPOP books
(nil)list的使用场景
模拟分布式系统数据结构 ①Stack(栈) LPUSH左边放 LPOP左边取 ②Queue(队列 LPUSH左边放 RPOP右边取 ③Blocking MQ(阻塞队列 LPUSH左边放 BRPOP右边阻塞取没有数据就阻塞 思考那么redis实现的数据结构和jdk中提供的数据结构有什么区别呢 答jdk提供的数据结构仅在本服务中有用如果在分布式环境下则需要借助redis等中间件模拟数据结构来统一管理数据。微博、朋友圈、公众号等关注的文章列表展示 假如 小明 关注了 中国青年报、三太子敖丙 等大V的订阅号当这些大V发布订阅号时通过推或拉的方式把消息LPUSH放入redis中属于小明的list中。其中key为msg:{小明_ID}。当小明要获取大V们发的消息时使用LRANGE 命令从队列中获取指定个数的订阅号信息
# ①MacTalk发微博消息ID为10010
LPUSH msg:{小明_ID} 10010# ②备胎说车发微博消息ID为10086
LPUSH msg:{小明_ID} 10086# ③查看最新微博消息(前4条)
LRANGE msg:{小明_ID} 0 4思考大V发了消息的是怎么存储在粉丝的redis中呢一般有两种处理方案 1推送博主发了消息通过线程先推送到在线粉丝的队列中其他不在线的粉丝等后面系统在空闲的时候再慢慢推送过去 2拉取如果粉丝太多推的方案还是要很长时间去处理还有一种方案就是拉每一个粉丝上线后就去关注的博主那里拉取他发送的最新的消息在使用LRANGE取出即可。
set应用场景
Redis 的集合相当于 Java 语言中的 HashSet它内部的键值对是无序、唯一的。它的内部实现相当于一个特殊的字典字典中所有的 value 都是一个值 NULL。
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 …]SDIFFSTORE destination key [key …]
集合set的基本使用 SADD books java
(integer) 1SADD books java # 重复
(integer) 0SADD books python golang
(integer) 2SMEMBERS books # 注意顺序set 是无序的
1) java
2) python
3) golangSISMEMBER books java # 查询某个 value 是否存在相当于 contains
(integer) 1SCARD books # 获取长度
(integer) 3SPOP books # 弹出一个
javaset的使用场景
抽奖活动 1点击参与抽奖加入集合 SADD key {userlD} 2查看参与抽奖所有用户 SMEMBERS key 3随机抽取count名中奖者 SRANDMEMBER key [count] ------元素不从集合中删除 SPOP key [count] ------ 元素从集合中删除朋友圈点赞 当某人在朋友圈发布消息可用set来点赞展示 1点赞 SADD like:{消息ID} {用户ID} 2取消点赞 SREM like:{消息ID} {用户ID} 3检查用户是否点过赞 SISMEMBER like:{消息ID} {用户ID} 4获取点赞的用户列表 SMEMBERS like:{消息ID} 5获取点赞用户数 SCARD like:{消息ID}利用set的交、并、差集实现微博、微信关注模型 关注模型如下图
首先了解一下set的集合操作假如有三个集合 set1a、b、c set2b、c、d set3c、d、e
三个集合的 交集为SINTER set1 set2 set3 { c } 并集为SUNION set1 set2 set3 { a,b,c,d,e } 差集为SDIFF set1 set2 set3 { a } 差集计算方式set1 - set2并set3 {a、b、c} - {b、c、d、e} {a} 只保留a中单独存在的元素
共同关注A的人可以用交集来实现 我可能认识的人可以使用差集来实现把我的好友求差集例如a的好友{b,c},b的好友{a,c,d}那么a可能认识的人可以是b-a{a,b,c,d}-{a,b,c} {d}。当然这只是一个简单又不大现实的想法相当于把对方的好友全部推荐给你了真实情况的还需要考虑到关系网络好友的权重数等
zset应用场景
这可能使 Redis 最具特色的一个数据结构了它类似于 Java 中 SortedSet 和 HashMap 的结合体一方面它是一个 set保证了内部 value 的唯一性另一方面它可以为每个 value 赋予一个 score 值用来代表排序的权重。 zset相比于set多一个score 分值正是根据这个分值进行排序所以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的分值加上incrementZCARD key返回有序集合key中元素个数ZRANGE key start stop [WITHSCORES]正序获取有序集合key从start下标到stop下标的元素ZREVRANGE key start stop [WITHSCORES]倒序获取有序集合key从start下标到stop下标的元素
zset的基本使用 ZADD books 9.0 think in javaZADD books 8.9 java concurrencyZADD 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 # 根据分值区间遍历 zset
1) 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) 1ZRANGE books 0 -1
1) java cookbook
2) think in javazset应用场景
实现热搜排行榜 ①点击 “国庆放假” 新闻时为其分值1 ZINCRBY hotNews:20190819 1 国庆放假 ②展示当日排行前十 ZREVRANGE hotNews:20190819 0 9 WITHSCORES ③七日搜索榜单计算 取7天的key求并集放入新的keyhotNews:20190813-20190819中就得出这7天中的访问量排行榜 ZUNIONSTORE hotNews:20190813-20190819 7 hotNews:20190813 hotNews:20190814… hotNews:20190819 ④展示七日排行前十 根据上边的并集从新的keyhotNews:20190813-20190819中取出前10名 ZREVRANGE hotNews:20190813-20190819 0 9 WITHSCORES