Redis/MongoDB都是很棒的NoSql数据库,与Mysql之类的关系型数据库有很大的区别,在某些情况下使用它们是更好的选择。
Redis设计与实现
在读这本书之前,我不了解Redis中字典、列表数据结构,不了解Redis如何进行持久化,不了解Redis如何内存回收,不了解Redis事务特点,不知道Redis都有哪些特性;读了这本书,这些就都知道了。_
其实还有一个挺有意思的点是,程序之前底层设计都是想通的,Redis中字典和Java中HashMap很相似,字典扩容机制和ConcurrentHashMap又有异曲同工之妙;Redis内存回收也是通过引用计数来实现的。
Redis数据结构
Redis 字符串 SDS
Java中String每次修改String对象,都会创建一个新的对象,String对象具有不变性,好处是线程安全,缺点是效率较低。
Redis中字符串SDS实现方法类似StringBuffer,每次会分配多一些空间,在操作字符串时不需要新建对象,同时由于Redis单线程特性不需要考虑并发问题。
SDS结构是:free(int),len(int),buf(Byte[])
free代表空闲字节位数;len代表存储字节位数。
Redis 链表
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
}
Redis中列表键、发布和订阅、慢查询、监视器等都是由链表实现的。
Redis 字典
在实现上和Java中HashMap类似,数据结构是数组+链表,当set一个key - value时,先计算key hashcode值然后找到数组对应位置插入;如果该位置已经有值 - 键冲突,那么把数组原有位置的节点赋值给新插入节点的next指针,然后插入新节点到数组。
哈希表的扩展:和Java类似,哈希表有一个负载因子,当 哈希表保存节点数量 / 哈希表大小 > 负载因子 时进行扩容。
渐进式rehash:如果hash表非常大,那么如果一次性的rehash那么服务器计算量很大,一种折中的方法就是渐进式的操作,在每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定操作以外,还会顺带把老哈希表对应的键值移动到新哈希表中,最终在某个时间,全部移动完毕;这种设计思路和Java中ConcurrentHashMap是一致的。
Redis 跳跃表
一种有序数据结构,平均时间复杂度O(longN),实现Redis中有序集合。
至于跳跃表是如何跳跃的,请看下图。
Redis对象
字符串对象、列表对象、哈希对象、集合对象和有序集合对象。
有序集合同时使用跳跃表和字典来实现,使用跳跃表来降低范围型操作的耗时;使用字典表来降低查找设值的耗时。
Redis内存回收
通过引用计数来实现内存回收机制,和Java的垃圾回收机制思想是类似的。
Redis实现
Redis服务器默认会创建16个数据库;客户端默认连接的是0号数据库。
Redis键过期时间实现
当给某个键设置过期时间时,Redis会在一个字典中添加key - value,key是键;value是过期时间。当下次访问这个键时先判断键是否过期。
Redis过期键删除策略
- 定时删除:在设置过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 惰性删除:放任键过期不管,但是每次从键空间获取键时,都检查取得的键是否过期,如果过期,就删除。
- 定期删除:每隔一段时间,程序对数据库进行一次检查,删除里面过期键。
定时删除任务缺点是对CPU不友好,在过期键较多时,会占用CPU相当多一部分时间,优点是对内存友好;惰性删除任务是对CPU友好,内存不友好;定期删除是对两种方式做了个折中。
Redis使用惰性删除和定期删除,在合理使用CPU时间和避免浪费内存空间之间取得平衡。
Redis RDB持久化
save 900 1 900秒内执行最少1次修改
save 300 10 300秒内执行最少10次修改
save 60 10000 60秒内执行最少10000次修改
满足条件后,执行BGSAVE操作,RDB文件是一个经过压缩的二进制文件,由多个部分组成。
AOF持久化
AOF持久化通过保存Redis服务器所执行的写命令来记录数据库状态的。
这样做优点是不会丢失数据,缺点是随着时间流逝,AOF文件会越来越大;所以就有了AOF重写功能,对AOF文件进行缩减。
Redis ServerCron函数
ServerCron函数让我想到了Linux下crontab,这个函数默认每隔100毫秒执行一次,其任务是更新服务器状态信息,处理服务器接收到的SIGTERM信号,管理客户端资源和数据库状态,检查并执行持久化操作等等。
发布与订阅
Redis把对某个主题订阅的客户端以链表形式保存。
struct redisServer {
list *pubsub_patterns; // 保存所有模式订阅关系
}
typedef struct pubsubPattern {
redisClient *client; // 订阅模式的客户端
robj *pattern; // 被订阅的模式
} pubsubPattern;
事务
当一个Redis客户端执行命令MULTI后,当执行SET等命令时,Redis服务器并不会立刻执行而是把命令放入一个队列中,等EXEC时统一执行命令。
事务ACID性质:原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)。
无论Redis服务器运行在哪种持久化模式下,事务执行中途发生停机都不会影响数据库一致性。
Lua脚本
Redis客户端可以使用Lua脚本,直接在服务端原子地执行多个Redis命令。
注意:Redis主从也会把Lua脚本复制到从库中。