redis是基于内存的数据库,对数据的读写操作都是在内存中完成的,操作速度快。常用于缓存,消息队列,分布式锁等场景
数据结构:常见数据类型:String,Hash,list,set,Zset
新支持:BitMap,HyperLogLog,GEO,Stream
1.String内部实现:
采用int 加SDS(简单动态字符串)来实现。
如果为整数 则在ptr中存储值,并将编码设置改为int
如果为字符串小于等于32字节(2.*版本)会将字符串对象使用一个简单动态字符串(SDS)来保存,将编码方式设置为embstr. 大于32字节则设置为raw。
应用场景
缓存,计数(点赞,转发,库存数量等等。),分布式锁,共享session
2.List类型内部实现:
3.2之前采用压缩头部与双向链表的方式,之后采用quicklist实现。
引用场景:
消息队列:(list和Stream都可以实现)
需求:1.消息的保序2.处理重复消息3.保证消息的可靠性
-
如何保证消息的保序需求:
list本身就是先进先出的顺序对数据进行存储,因此能够满足消息的保序性。
使用LPUSH/RPOP || RPUSH/LPOP来实现。
缺陷:消费者不知道什么时候有消息传入,那么就需要一直使用RPOP,成功则拿到消息,失败就返回空值接着 循环,导致cpu一直消耗在RPOP上,带来不必要的性能损耗
解决方案:redis官方提供了了BRPOP,阻塞式读取,客户端没有读取到队列数据时,自动阻塞,直到有新的数据写入队列,再重新读取
2. 如何处理重复的命令
要求:1.每个消息有个全局的ID 2.消费者记录已经处理过的ID,拿到消息之后将消息ID进行比对,没有处理就 处理,处理了就不再做处理。
但是List不会对消息主动生产ID,需要我们自己添加。
3.保证消息的可靠性
当消费者读取到消息后,list会删除消息。如果消费者在处理消息的过程中电脑宕机或故障,导致没有处理完消息,在电脑恢复之后消息就无法再次从list中读取
List类型提供了BRPOPLPUSH命令,将消费者程序从一个List中读取消息,同时放入另一个List中留存。那么电脑回复之后还可以再此list中读取。
缺陷:
无法多个消费者消费同一条消息。如果要消费同一个消息那么必须将多个消费者组成消费组,使得多个消费者消费同一个消息,但是List类型并不提供消费组的实现。而Stream能够满足三大需求的同时,还支持消费组的消息读取。
Hash
Hash是一个键值对(key-value)集合,其中value的格式为value=[{field1,value1},...{fieldN,valueN}]。
Hash底层实现数据结合为压缩列表或哈希表实现,在7.0之后采用listpack数据结构来实现
# 存储一个哈希表key的键值 HSET key field value # 获取哈希表key对应的field键值 HGET key field # 在一个哈希表key中存储多个键值对 HMSET key field value [field value...] # 批量获取哈希表key中多个field键值 HMGET key field [field ...] # 删除哈希表key中的field键值 HDEL key field [field ...] # 返回哈希表key中field的数量 HLEN key # 返回哈希表key中所有的键值 HGETALL key # 为哈希表key中field键的值加上增量n HINCRBY key field n
应用场景:
缓存对象
购物车,以用户id为key,商品id为field,商品数量为value。当前仅仅是将商品ID存储到了Redis中,在回显商品具体信息的时候,还需要去数据库中用商品id去查询到完整的商品信息。
Set类型
set类型是一个无序且唯一的键值集合,它的存储顺序不会按照插入的先后顺序来存储。
一个集合支持存储2^32-1个元素。且支持多个集合取交集,并集,差集。
内部实现:
采用哈希表或整数集合实现
-
集合中元素都是整数且小于512个会使用整数集合作为Set类型的底层数据结构
-
如果不满足就使用哈希表来作为底层数据结构。
应用场景:
特点:无序,不可重复,支持并交集等操作
Set适合用来数据去重,且保证唯一性,还可以用来统计多个集合的交集等。
但是!!!Set的差集、并集、交集的计算复杂度较高,在数据量较大的情况下会导致Redis实例阻塞。
在主从集群中,为了避免主库因为Set做聚合运算时导致主库阻塞,可以选择一个从库来完成聚合统计,或者把数据返回为客户端,由客户端来完成。
点赞:文章id为key,value为用户id
共同关注:用来计算共同好友等。
抽奖活动:key为抽奖名,value为参与人员。
Zset
Zset相比于Set类型多了一个排序属性score,每个元素相当于有两个值组成,一个为有序集合的元素值,一个是排序值。保留了set类型中不能包含重复成员的特性。
内部实现:
由压缩列表或调表来实现
-
如果有序集合个数小于128个,且每个元素小于64字节,redis会使用压缩列表的形式。
-
不满足则使用调表
在7.0中,压缩列表已经废弃,交由listpack数据结构实现。
应用场景:
Zset 类型(Sorted Set,有序集合) 可以根据元素的权重来排序,可以根据元素的权重来排序,可以自己来决定每个元素的权重值。
当需要展示最新列表,排行榜等场景时,数据更新频繁或者需要分页显示,可以优先考虑使用Sorted Set
排行榜:
电话,姓名排序
Stream
专门为消息队列设计的数据类型。
在Stream没出来之前,消息队列的实现方式都有着缺陷
-
发布定略模式,不能持久化也就无法可靠的保存消息,并且对于离线重连的客户端不能读取消息的缺陷
-
List实现消息队列不能重复消费,一个消费消息完就会被删除,而且生产者需要自行实现全局唯一ID
基于以上问题,Stream完美的实现消息队列,支持消息的持久化,支持函自动生成全局唯一ID,支持ack确定到的模式、支持消费组模式等。
命令
-
XADD:插入消息,保证有序,可以自动生成全局唯一 ID;
-
XLEN :查询消息长度;
-
XREAD:用于读取消息,可以按 ID 读取数据;
-
XDEL : 根据消息 ID 删除消息;
-
DEL :删除整个 Stream;
-
XRANGE :读取区间消息
-
XREADGROUP:按消费组形式读取消息;
XPENDING 和 XACK:
-
XPENDING 命令可以用来查询每个消费组内所有消费者「已读取、但尚未确认」的消息;
-
XACK 命令用于向消息队列确认消息处理已完成;
应用场景:
消息队列:
-
消息保序:XADD/XREAD
-
阻塞读取:XREAD block
-
重复消息处理:Stream 在使用 XADD 命令,会自动生成全局唯一 ID;
-
消息可靠性:内部使用PENDING List自动保存信息,使用XPENDING命令查看消费组已经读取但是未被确认的消息,消费者使用XACK确认消息
-
支持消费组形式消费数据
Redis作为消息队列来使用面临两个问题:
-
redis本身可能会丢数据(AOF持久化中为异步写盘,Redis宕机时会存在数据丢失的可能。主从复制也是异步,主从切换时也可能存在丢失数据的可能)
-
面对消息挤压,内存资源会紧张
-
Redis 的数据都存储在内存中,这就意味着一旦发生消息积压,则会导致 Redis 的内存持续增长,如果超过机器内存上限,就会面临被 OOM 的风险。Redis提供了可以指定队列最大长度的功能,避免这种情况发生,当达到上限后,旧消息会被删除,只保留固定长度 新消息,那么就有可能丢失消息。
-
而kafka、RabbitMQ会写在磁盘上。
-