Data Structure
Redis中常用数据结构
- String
- List
- Hash
- Set
- Zet
String
实现原理
string有3种编码模式
INT:当字符串是数字,并且小于 long long时。EMBSTR:字符串长度小于等于阈值。RAW:上面的条件都不满足时,才用raw。
INT
如果一个字符串对象保存的是整数值, 并且这个整数值可以用 long 类型来表示, 那么字符串对象会将整数值保存在字符串对象结构的 ptr 属性里面(将 void* 转换成 long ), 并将字符串对象的编码设置为 int 。
EMBSTR
sdshdr的内存是和redisObject一块分配的,是连续的。所以叫做embed string。
因为内存分配需要
RAW
不难发现RAW和EMBSTR都有个sdshdr结构,会在Data Structure Impl文章中介绍。
应用场景
缓存对象
使用 String 来缓存对象有两种方式:
- 直接缓存整个对象的 JSON,命令例子:
SET user:1 '{"name":"xiaolin", "age":18}'。 - 采用将 key 进行分离为 user:ID:属性,采用 MSET 存储,用 MGET 获取各属性值,命令例子:
MSET user:1:name xiaolin user:1:age 18 user:2:name xiaomei user:2:age 20。
常规计数
因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
比如计算文章的阅读量:
# 初始化文章的阅读量
> SET aritcle:readcount:1001 0
OK
#阅读量+1
> INCR aritcle:readcount:1001
(integer) 1
#阅读量+1
> INCR aritcle:readcount:1001
(integer) 2
#阅读量+1
> INCR aritcle:readcount:1001
(integer) 3
# 获取对应文章的阅读量
> GET aritcle:readcount:1001
"3"分布式锁
SET 命令有个 NX 参数可以实现「key不存在才插入」,可以用它配合过期时间来实现分布式锁:
- 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
- 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
更具体的实现,见其他文章。
共享 Session 信息
通常我们在开发后台管理系统时,会使用 Session 来保存用户的会话(登录)状态,这些 Session 信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用。
例如用户一的 Session 信息被存储在服务器一,但第二次访问时用户一被分配到服务器二,这个时候服务器并没有用户一的 Session 信息,就会出现需要重复登录的问题,问题在于分布式系统每次会把请求随机分配到不同的服务器。
分布式系统单独存储 Session 流程图:
因此,我们需要借助 Redis 对这些 Session 信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个 Redis 获取相关的 Session 信息,这样就解决了分布式系统下 Session 存储的问题。
分布式系统使用同一个 Redis 存储 Session 流程图:
List
实现原理
3.2版本之前,List对象有两种编码,方式
- ZipList
- LinkedList:双向链表
当满足如下条件时,用ZipList编码
- 当列表对象保存的所有字符串对象长度都小于64字节;
- 列表元素小于512个;(注意,这是List的限制,不是底层的ZipList的限制)
不满足时,自动转换成LinkedList编码。
Ziplist采用压缩列表实现,内存布局非常紧凑,指针域的空间占比非常少。
ZipList在数据较少时,用于节约内存,插入时也会造成多节点的内存复制。
LinkedList在数据多时,提升效率,但也会导致指针域占比高,占用不少内存。
3.2版本引入了quicklist,是ZipList和Linked的结合体。
LinkedList是一个节点放一个数据,现在QuickList是单个节点寸一个ZipList,即多个数据。
优势是
- 数据较少时,只有一个节点,即是ZipList。
- 数据多时,同时做了折中。
ZipList被ListPack取代
ZipList本身存在一个连锁更行的问题,所以在Redis7.0后,被ListPack给取代了,但本质都是压缩链表,内存是紧凑在一块的。
应用场景
简单消息队列
- 不支持多个消费者消费同一条消息
- 不支持消费组的实现
Set
实现原理
Set有两种编码方式,整数集合INTSET和HASHTABLE。
INTSET
当以下条件满足时,是用INTSET编码,反之使用HASHTABLE。
- 元素都是整数
- 元素数量不超过512个
INTSET实际上是有序的数组,通过二分查找来查询。布局如下
HASHTABLE
实际上就是一个哈希表。
应用场景
点赞
点赞作为一个高频率的操作,如果每次操作都读写数据库会增加数据库的压力,所以采用缓存+定时任务来实现。点赞数据是在redis中缓存半小时,同时定时任务是每隔5分钟执行一次,做持久化存储,这里的缓存时间和任务执行时间可根据项目情况而定。
共同关注
集合取交集。
抽奖活动
存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。
key为抽奖活动名,value为员工名称,把所有员工名称放入抽奖箱 。
- 如果允许重复中奖,可以使用 SRANDMEMBER 命令。
- 如果不允许重复中奖,可以使用 SPOP 命令。
Hash
实现原理
Hset底层有两种编码格式,压缩列表和HASHTABLE。
满足以下两个条件时,使用压缩链表,反之使用HASHTABLE。
- Hset对象保存的所有key和value的长度都小于64字节。
- Hset对象元素小于512个。
其中压缩链表就是把key-value这整体作为entry存入压缩链表。
HASHTABLE的编码方式的话,和Set的区别在于,在Set中value始终为NULL,而HSet是对应的value。
应用场景
缓存对象
一个hash就是一个对象,存储对象的每一个元素。
在介绍 String 类型的应用场景时有所介绍,String + Json也是存储对象的一种方式,那么存储对象时,到底用 String + json 还是用 Hash 呢?
一般对象用 String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储。
购物车
以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素,如下图所示。
涉及的命令如下:
- 添加商品:
HSET cart:{用户id} {商品id} 1 - 添加数量:
HINCRBY cart:{用户id} {商品id} 1 - 商品总数:
HLEN cart:{用户id} - 删除商品:
HDEL cart:{用户id} {商品id} - 获取购物车所有商品:
HGETALL cart:{用户id}
当前仅仅是将商品ID存储到了Redis 中,在回显商品具体信息的时候,还需要拿着商品 id 查询一次数据库,获取完整的商品的信息。
Zset
实现原理
Zset底层有两种编码方式,一种是压缩链表,一种是SkipList+HT。
满足下列条件时,采用压缩链表编码,反之采用SkipList+HT。
- 列表保存的字符串对象都小于64字节
- 列表对象元素个数少于128个。
应用场景
- 电话排序
- 姓名排序