文章目录
Redis常见数据类型与应用场景
Type1:String
内部实现
-
也是key-value的形式。value 也可以是整数 不一定非得是String 最多容纳512mb
-
底层数据结构是int 和 SDS(简单动态字符串) SDS和C的不一样,区别在于
- 不仅能保存文本数据 还能保存二进制数据 根据长度len来判断是否结束而不是空值 存放在buf[]中
- 本身就保存len 所以获取长度为O(1)(SDS包含free剩余空间 len内容长度 buf存放内容)
- 安全、拼接不会溢出 因为空间不够会自动扩容,不是死的
-
字符串对象内部编码int raw embstr
- int 整数值保存在ptr属性里
- embstr 长度小于32字节时 一次分配连续内存保存redisObject 和SDS(只读的,但能提升性能 修改时变成raw)
- raw 长度大于32bit时用 两次内存分配分别存放redisObject 和SDS
应用场景
- 缓存对象
- user:1:name---->xiaolin user:1:age---->18 user:2:name---->xiaomei user:2:age---->20
- SET user:1 ‘{“name”:“xiaolin”, “age”:18}’ (json)
- 常规计数:redis处理命令是单线程、原子性的 计数不会混乱 适合:计算访问次数、点赞、转发、库存数量
- 分布式锁:利用”不存在才插入的特性“来实现分布式锁 key存在则加锁失败 并且可以设置过期时间 来释放锁(解锁:先判断是否是加了锁的 再解锁 这个需要保证原子性)
- 共享session信息 :session常用来表示登录状态 当服务器分布式时,别的服务器不知道登录信息,就需要重新登录 ,这里可以用redis缓存来解决
*
Type2:List
内部实现
- list按照插入顺序排序 包括头插和尾插 先进先出
- 数据结构是quicklist 快速链表 快速链表是压缩链表和双向链表的集合体,将链表按段切分,每一段使用压缩链表ziplist来存储 ,多个段之间用双向指针串接起来。压缩链表是多个结点凑成一段,次序为 内存大小、 到尾部距离、结点数、各个结点、尾端
应用场景
- 消息队列:处理消息要满足三个需求
- 01、消息保序: 先发的消息先收到 因为list本身就是先入先出 刚好符合 LPUSH+RPOP(或者反过来) 生产者将消息插入到队列的头部 消费者依次读取消息
- 注:有个潜在的性能风险点 :生产者发消息时不会告诉消费者,消费者想及时处理消息就要不停的RPOP,有消息则得到消息,没有则得到空 。消费者的CPU一直在读消息 ,带来性能损失。
- 解决办法:redis提供了BRPOP命令,即阻塞式读取,客户端没有消息时,自动阻塞,有消息才开始读取数据。节省性能开销。
- 02、处理重复的消息:消费者要记录消息id,对于重复的消息不会再处理。但是List并不会自动为消息生成id号。我们要自行为消息生成一个全局唯一id。因此push的消息是加了id的
- 03、保证消息的可靠性:消费者读取消息后list不会保留这个消息了,如果消费者处理消息时宕机,那么就读不到这个消息了。redis的list提供了BRPOPLPUSH命令,消费者读完消息后,这个消息放到另一个list中作为备份。
- 缺点:不支持多个消费者读取同一个消息。这个功能需要将消费者组成消费组,但是list不支持这个功能。需要靠后面的Stream
Type3:Hash
内部实现
- 键值对集合 (key-value)其中value={(field1,value1),(field2,value2)…(field1,value2)}
- 底层结构为压缩列表和哈希表 redis7.0之后用listpack实现
应用场景
- 缓存对象:key field value 和对象的 对象id 属性 值这种天然相似,所以可以用来存储对象
- 本来string + json也可以存储对象 一般对象用它存储,那些属性值频繁改变的用hash存储
- 购物车:用户id(key) 商品id(field) 商品数量(value)
Type4:Set
内部实现
- set是无序且唯一的键值集合。存储顺序与插入顺序无关;放进去多个一样的值只会保存其中一个。
- 有集合的特性,除了支持集合内的增删改查,还支持多个set集合之间的交集、并集、差集。
- k1----> it、music、sport、…
- set 和 list 的区别
- list按照插入的先后顺序排序
- list可以存储重复元素
- 内部实现:元素为int且<512个 用整数集合存 ,其余情况用哈希表存
应用场景
- 应用场景取决于其特性:无序、唯一、支持并交差操作
- 注:潜在风险是 求差集、交集、并集的计算复杂度很高,数据量较大的时候进行这些计算,会产生Redis实例阻塞。
- 场景1:点赞 保证一个用户只能点一个赞(唯一性)eg: article1---->uid:1; article1---->uid:2;article1---->uid:3 重复点赞不会增加赞数
- 场景2:共同关注(交集) 用交集来计算可以关注的共同好友、公众号
- 场景3:抽奖 去重功能保证一个人不会中奖两次 存 lucky—>a,b,c,d,e,… 抽奖lucky 2得到 c,e
Type5:Zset
内部实现
- Zset是有序集合类型,相比set多了个score用于排序 key---->score+内容
- 元素数小于128且单个小于64用 用压缩列表实现
- 其余用跳表实现
- zset不支持差集运算 其他和set运算差不多
应用场景
- 排行榜:用score排序
- 电话、姓名排序 这里要求score一样 按照内容排序(A-Z)
Type6:BitMap
内部实现
- 这是位图,是一串连续的二进制数组01 可以通过偏移量(offset)来定位元素 、
- 非常节省空间,特别适合于非常大数据量且用二进制值统计的场景
- 底层数据结构是String 但每个位置存放01可以看成bit数组
应用场景
- 签到统计 用01标记是否签到 101101010111111.。。一年也才365bit
- 判断用户登录状态
- 连续签到用户总数 周一 110 周二 111 周三 111 。。。周日101 这些一起与运算 得到 100说明就一个人连续七天打卡了、
Type6:HyperLogLog
介绍
- 是一种用于{统计基数}的数据集合类型 基数统计就是统计一共集合中不重复元素个数
- 它是基于概率完成的,不是非常准确的 误差率是0.81%
- 简单来说功能就是提供不精确的去重计数
- 优点:在输入元素数量级特别大的时候,计算所需内存空间总是固定的,而且很小 12kb空间就能计算2^64个元素的基数
应用场景
*统计百万级网页UV计数(独立访客数量) 比如统计直播间访问人数 100万 但可能真实结果是101万 不精确但是内存小
Type7:GEO
内部实现
- 用sorted set集合类型
- 存储地理空间位置的经纬度
- 使用GeoHash编码的方法实现了经纬度到SortedSet元素权重分数的转化 按照权重可以查询地理位置的附近范围地址
应用场景
- 滴滴叫车 根据用户自己的经纬度信息查找周围五公里内的车辆信息
Type8:Stream
介绍
- 这时专门为消息队列设计的数据类型,弥补list的不足
- List的不足:不能持久化 无法可靠的保存消息 不能重复消费消息 需要自行实现全局唯一id
应用场景
消息队列
- 生产者通过XADD插入一条消息 而且可以同时生成唯一id (由插入数据时以毫秒为单位计算的时间+该毫秒内消息的次序)
- 还可以实现阻塞读 设定阻塞时间 没有消息到来 阻塞一段时间再返回
- XGROUP创建消费组 一堆消息来了 消费组1 2 3 都能读取这些消息 组内分工每个处理一部分消息 从而分担消息 实现负载的均衡(消息队列中的消息一旦被组内某个读取,就不能被组内其余读取)
- 会使用内部队列留存消费者读取过的消息 直到用XACK通知stream消息已经处理完成(消费者重启后也可以去看看已读取为确认的消息)
- 总结:消息保序、阻塞读取、重复消息处理、消息可靠性、支持消费组读取数据。
Redis Stream会出现消息丢失吗
三大块:消息生产者、队列中间件、消费者 保证闭环就是保证这三个消息不丢失
Redis Stream会消息可堆积吗
Stream提供了可以指定队列最大长度的功能 长度超过上限后,删除旧消息 所以还是会有丢失风险的
场景
- 业务场景简单,对数据丢失不敏感,消息积压概率较低的情况下,完全可以用redis
- 如果不能接受上述,还是用专业的消息中间件吧