对Redis的第一印象
三高一丰富
- 高性能:性能特性
- 丰富的数据类型:应用接口
- 内存数据库:持久化模式(高可用)
- 缓存:应用场景
- Redis集群:部署架构,主从,切片
- 端口号:6379
Redis的系统架构
要学习一门技术,就要系统学习他的架构,而不是细枝末节,Redis的系统架构图如下所示:
Redis技术栈
从设计角度上看,设计一个缓存数据库需要实现什么功能,我们拆解一下Redis到底是什么样的,整体都有哪些功能?
需求1:数据存取
- 提供不同数据类型的数据结构满足应用多样化需求
- 充分优化数据结构,在时间和空间上取得平衡
- 有限内存空间,内存分配,释放和数据淘汰
- 数据存取需求实现要包含的两大部分:基本数据结构和内存管理,如下图所示:
需求2:应用的访问
- 通过网络访问Redis实例,快速响应网络请求
- 不同语言的客户端,访问协议,集群功能的支持
- 数据库安全机制
- 应用的访问需求实现的功能,如下图所示:
需求3:实例的可用性
- 通过主从节点集群创建数据副本,通过副本保证数据可靠性
- 复制机制,数据一致性保证
- 故障恢复与哨兵机制
- 实例的可用性需求实现的功能,如下图所示:
需求4:缓存应用
- 缓存的基本原理
- 缓存数据替换和Redis数据淘汰策略相结合
- 缓存和后端数据库一致性保证
- 缓存需求需要实现的功能,如下图所示:
需求5:性能优化
- 避免任何阻塞Redis操作的潜在瓶颈
- 理解Redis线程模型,掌握和内存分配、磁盘读写相关的关键机制
- 性能优化需求实现,如下图所示:自己可以对比下,检验自己是否掌握了这些,掌握了这些之后就是实战了。
数据存取
下面开始就是数据存取需求的具体细节了,这个功能是如此重要,所以是一定要掌握的。
基本数据模型
这个章节开始展开讲第一个技术栈,数据存取。包含基本数据类型,内存管理
1.键值基本类型
整体来说redis就是靠一张全局的HashTable来存储数据的,下面来了解下我们的基本类型:
- key-value
- key:字符串,Redis数据库的键总是字符串
- value:类型多样化,满足应用多样化需求;Redis数据库的值可以字符串,集合,列表等多种类型的对象。
2.全局哈希表(初始值大小是4)
- 保存所有的key-value对,注意跟基本数据类型的hash类型的区别
- 使用两个哈希表,实现渐进式Rehash
- 哈希表表项:dictEntry,如下图:表项包含三个字段!
- dictEntry中的key,value都是指向一个redisObject,是c语言的一个结构体,占16字节。然后这个key,value,next都是指针,占8byte
typedef struct redisObject {
unsigned type:4; // 4个bit位,数据类型 integer string list set zset
unsigned encoding:4; // 指定使用ziplist,quicklist等等底层数据结构
unsigned lru:LRU_BITS; /* 占24位,LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time).
* redis用24个位来保存LRU和LFU的信息,当使用LRU时保存上次
* 读写的时间戳(秒),使用LFU时保存上次时间戳(16位 min级) 保存近似统计数8位 */
int refcount; // 占4个字节,引用计数
void *ptr; // 占8个字节,指针指向具体存储的值,类型用type区分
} robj;
- 键值对的hash值对哈希表的size取模
- hash值取模后对于相同的键值使用链表连接
- Rehash操作
- 哈希冲突较多时,哈希表表项的链长增加,哈希表操作变慢
- 两张哈希表ht0,ht1,实现渐进式rehash,比当前bucket数量值大的最小的2的n次方
- 为什么说是渐进式rehash,其实都是为了减少阻塞,渐进式rehash怎么实现的
- Rehash的触发条件:负载因子,约束条件
- Rehash操作时机:伴随正常读写操作执行,或者周期性执行(由databasecron定时器控制)
3.String类型
- 二进制安全的字节数组
- 适用场景:数值,文本数据,图片
- 底层是SDS(Simple Dynamic String)
4.List类型
- 双向链表,支持双向的POP和PUSH,元素可以重复
- 使用场景:排行榜,关注列表
5.Hash类型
- Hash字典,一个key对应多个value
- 使用场景:结构化数据
6.Set类型
- 无序集合,元素去重,支持集合操作
- 使用场景:共同关注,共同爱好,二度好友
7.Sorted Set类型
- 有序集合,元素去重,支持集合操作
- 使用场景:有序排行榜,排序
8.位图(bitmap)类型
- 二进制安全的字节数组,每个bit位表示位图的一位
- 使用场景:用户签到,用户状态统计
9.HyperLogLog类型
- 基数统计,统计一个集合中不重复的元素的个数,实现近似统计
- 使用场景:用户日活,月活统计
底层数据结构
SDS
- 是String类型的底层数据结构,是二进制安全的,即没有像C语言那样,字符串里面不能以\0结尾,这样一来,就会以第一个空字符做为结尾了。
- 3.x.x版本,具体实现源码,如下代码所示,最新版本有所改变:
typedef char *sds;
struct sdshdr {
// buf 已占用长度
int len;
// buf 剩余可用长度
int free;
// 实际保存字符串数据的地方
char buf[];
};
- 对比C字符串,sds有以下的特性:
- 可以高效地执行长度计算:(strlen),传统C语言的字符串实现是一个char* 数组,要想获取字符串长度的话,需要遍历数组,时间复杂度是O(N),而使用SDS的话,因为存有字符串的长度,直接获取就行,时间复杂度O(1)
- 可以高效地执行追加操作:(append)
- SDS能保存的数据更加多种多样
- 如下图所示,两者对比之下,SDS无疑是更加好的选择
| C字符串 | SDS |
|---|---|
| 获取字符串长度的复杂度为 O(N) | 获取字符串长度的复杂度为 O(N) |
| 操作字符串函数不安全,可能造成缓冲区溢出 | 安全的操作字符串API,避免缓冲区溢出 |
| 修改字符串长度 N 次必然需要执行 N 次内存重分配 | 修改字符串长度 N 次必然需要执行 N 次内存重分配 |
| 只能保存文本数据 | 可以保存文本以及图片、音频、视频、压缩文件这样的二进制数据。 |