前言
本文小结Redis中List,Set,ZSet和Hash四种数据类型的,基本特点,使用场景和实现方式。
一、List
1. 基本特点
a. 作为数组,基于下标索引操作, 但支持正向索引和反向索引;
b. 作为链表, 支持高效插入;
c. 约定FIFO, 作为队列使用; 结合阻塞操作,形成一个单播阻塞队列;
d. 约定FILO, 作为栈使用;
2. 使用场景
a. 静态Top N问题: 其中N中的每个1是一个List,比如保留一个人最近三天的消息,每1天的消息是一个List,然后删除最早的List;
b. 消息队列: 给每个用户创建一个消息队列作为收件箱, 后续从该队列中消费消息。
3. 底层实现
a. 基于双向链表实现, 支持双向遍历, 快速插入和删除;
b. 增加长度属性, llen时间复杂度为O(1);
c. 关于阻塞的具体规则:
- 如果client 尝试阻塞多个key, a, b, c, d, 此时b中存在元素, 则立刻返回b中的元素;
- 如果多个client尝试阻塞同一个key, 当该key中存在数据时, 会将数据喂给阻塞最久的那个client。一旦client得到数据就会失去优先级。当该client再次阻塞时, 则进入该key相关的阻塞队列中;
- 如果client已经在多个key上阻塞, 当其中的第一个key存在数据时, 则直接提供给该client;
- Redis执行完每条command之后, 会返回有新数据进入并且存在至少一个block client的List,其中的key按照数据到达时间排序。针对每个key, Redis会根据排队的client, FIFO去服务, 直到key中没有数据,然后继续处理下一个key;
二、Set
1. 基本特点
- 不维护插入和删除顺序, 随机读取, 但支持去重;
- 集合的基本操作, 并交差补;
2. 使用场景
- 去重,保持唯一性场景;
- 随机,比如抽奖的细粒度支持,比如奖品和人员数量的匹配度,以及是否允许重复;
3. 实现方式
HashTable
a. 基于hash function对应一个带bucket的数组。读取时的随机性, 基于随机数+数组索引完成; 写入时先通过hash函数定位到bucket,然后通过拉链法解决冲突;
b. 当链表变得很长时,会影响 Redis 的查找性能,为了减小 链表的长度,Redis 采用了 rehash 操作,也就是把扩大当前哈希表的长度。Redis 在 rehash 是不是一次性rehash ,而是采用了渐进式方式,这样可以解决长时间阻塞,在渐进式 rehash 的同时,Redis 在空闲时间也会进行 1Ms 的定时 rehash。
三、Ordered-Set
1. 基本特点
- 具备Set的基本特点;
- 插入时需要提供score,并基于score排序;
- 支持并交差补, 但需要基于score提供聚合方式, 如最大, 最小, 权重, 默认方式为求和;
- 更新score时联动更新排序结果;
2. 使用场景
动态topN问题, 比如排行榜,举办比赛时随着赛程的推进每个team的得分在不断更新,影响当时的即时排名。当然这里的动态通常指的是动态score。
3. 实现方式
Dictionary+SkipList
其中Dictionary记录key,SkipList则维护了1个按照score排序的索引,索引指向的目标与Dictionary的目标相同。
四、Hash
1. 基本特点
a. 可以提供类似于对象一样的多字段存储;
b. 对Key-Value的操作,同样可使用于Hash的key;
2. 使用场景
a. 多字段组织管理,部分字段读取;如果是全字段读取, 直接序列化为string再完全读到client即可;
b. 部分字段更新: 比如一篇博客的点赞,评论,转发数量;
3. 实现方式
基于HashTable,与set的不同之处在于set中每个Item只有key,而hash中的item包括key和value。
总结
这里对每种数据类型使用的数据结构做一个小结,其中每种数据结构在数据量比较小时还有些特别的优化,文中暂未涉及(图片中斜体部分)。