Redis 的第 n 次涉及,我该整理整理了
未完待续!
redis你是怎么发音的 ?瑞得儿思
:瑞迪思
曾经被一个人纠正了N多次,还是习惯瑞得儿思
。可能儿化音比较洋气
定义:单线程架构和非阻塞I/O多路复用模型,实现高性能的内存数据库服务
官方解释:Redis是基于内存的存储。本身性能足够高了,没必要多线程。
非阻塞:不必等完全处理完一个任务就可以去处理另一个任务
多路复用:多路-指的是多个socket连接,复用-指的是复用一个线程。
我的疑惑
这里是不是有点疑惑了,单线程又是非阻塞多路复用。redis,到底是怎么一回事呢?
Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的.
Redis 对 IO 多路复用的封装
以 epoll 为例,了解 Redis 内部是如何封装 IO 多路复用的
为了将所有 IO 复用统一,Redis 为所有 IO 复用统一了类型名 aeApiState,对于 epoll 而言,类型成员就是调用 epoll_wait所需要的参数
接下来就是一些对epoll接口的封装了,包括创建 epoll(epoll_create),注册事件(epoll_ctl),删除事件(epoll_ctl),阻塞监听(epoll_wait)等
创建 epoll 就是简单的为 aeApiState 申请内存空间,然后将返回的指针保存在事件驱动循环中\
注册事件和删除事件就是对 epoll_ctl 的封装,根据操作不同选择不同的参数\
阻塞监听是对 epoll_wait 的封装,在返回后将激活的事件保存在事件驱动中
事件驱动循环流程
IO 复用的封装实现完成,那么 Redis 是何时调用 IO 复用函数的呢,这就需要从 server.c/main 函数入手,可以猜测到当 main 函数初始化工作完成后,就需要进行事件驱动循环,而在循环中,会调用 IO 复用函数进行监听\
在初始化完成后,main 函数调用了 aeMain 函数,传入的参数就是服务器的事件驱动
Redis 对于时间事件是采用链表的形式记录的,这导致每次寻找最早超时的那个事件都需要遍历整个链表,容易造成性能瓶颈。而 libevent 是采用最小堆记录时间事件,寻找最早超时事件只需要 O(1) 的复杂度
总结一下:单线程指的是执行任务单线程先把他看做`任务盒子`,非阻塞的多路复用指的是多个用户连接,redis自己封装了一个`用户盒子`把用户连接数据放里面这个过程是非阻塞多路复用,然后把用户盒子到任务盒子去执行这是单线程的(怪怪的)
基本数据类型:
扯了那么多,会用才是王道,毕竟redis不是我能开发的。
1.redis的几种数据类型
string
hash
list
set
zset
string
int:8个字节的长整型
embstr:小于等于39个字节的字符串
raw:大于39个字节的字符串
Redis会根据当前值的类型和长度决定使用哪种内部编码实现
string常用命令描述
序号 | 命令 | 描述 |
---|---|---|
1 | set key value | 设置指定key的值 例:set cfun that is a goog boy |
2 | get key | 设置指定key的值 例:get cfun 输出:that is a goog boy |
3 | getrange key start end | 返回 key 中字符串值的子字符。例: getrange cfun 0 3 输出:that |
4 | getset key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value),key无旧值则返回nil ,key存在但不是字符串是返回错误 |
5 | getbit key offset | 对 key 所储存的字符串值,获取指定偏移量上的位(bit) |
6 | mget key1 key2 | 获取所有(一个或多个)给定 key 的值。 |
7 | setex key seconds value | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
8 | setnx key value | 只有在 key 不存在时设置 key 的值 |
hash
是一个string类型的field和value的映射表,hash特别适合用于存储对象
Redis 中每个 hash 可以存储 2^32^ - 1 键值对(40多亿)。
1.ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现
2.hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)
Hash常用命令描述
序号 | 命令 | 描述 |
---|---|---|
1 | hget key field | 获取存储在哈希表中指定字段的值。 |
2 | hexists key field | 查看哈希表 key 中,指定的字段是否存在。 |
3 | hgetall key | 获取在哈希表中指定 key 的所有字段和值 |
4 | hmset key field1 value1 [field2 value2 ] | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
5 | hset key field value | 将哈希表 key 中的字段 field 的值设为 value 。 |
list
列表类型,存储类似一维数组数据
1. ziplist(压缩列表) 元素个数小于配置(默认512),每个元素大小小于64字节(默认)
2. linkedlist(链表) 当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现
List常用命令描述
序号 | 命令 | 描述 |
---|---|---|
1 | lpush key value1 | 将一个值插入到已存在的列表头部 |
2 | lpop key | 移出并获取列表的第一个元素。 |
3 | lindex key index | 通过索引获取列表中的元素 |
4 | hmset key field1 value1 [field2 value2 ] | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
5 | lset key index value | 通过索引设置列表元素的值 |
6 | lrem key count value | 移除列表元素 |
7 | rpop key | 移除列表的最后一个元素,返回值为移除的元素。 |
8 | rpush key value1 [value2] | 在列表中添加一个或多个值。 |
set
集合类型:Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
1.intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
2.hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
set常用命令描述
序号 | 命令 | 描述 |
---|---|---|
1 | sadd key member1 [member2] | 向集合添加一个或多个成员 |
2 | scard key | 获取集合的成员数。 |
3 | sdiff key1 [key2] | 返回给定所有集合的差集 |
4 | sinter key1 [key2] | 返回给定所有集合的交集 |
5 | sismember key member | 判断 member 元素是否是集合 key 的成员 |
6 | smembers key | 返回集合中的所有成员 |
7 | smove source destination member | 将 member 元素从 source 集合移动到 destination 集合 |
8 | spop key | 移除并返回集合中的一个随机元素 |
9 | srem key member1 [member2] | 移除集合中一个或多个成员 |
zset
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
1.ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现。
2.skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
set常用命令描述
序号 | 命令 | 描述 |
---|---|---|
1 | zadd key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
2 | zcard key | 获取有序集合的成员数。 |
3 | zcount key min max | 计算在有序集合中指定区间分数的成员数 |
4 | zrank key member | 返回有序集合中指定成员的索引 |
5 | zincryby key increment member | 有序集合中对指定成员的分数加上增量 increment |
6 | zinterstore destination numkeys key [key ...] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
7 | zrange key start stop [WITHSCORES] | 通过索引区间返回有序集合成指定区间内的成员 |
8 | zrem key member [member ...] | 移除有序集合中的一个或多个成员 |
应用场景
String:
普通的key/value存储
Hash:
hget,hset,hgetall
存储一个用户对象,以用户id为key.
List:
lpush,rpush,lpop,lrange
存储用户关注列表,粉丝列表
list的实现是一个双向链表,可支持反向查找和遍历
redis内部的很多实现,包括发送缓冲队列也是这个数据结构
Set:
sadd,spop,smembers.sunion
redis set对外提供的功能与list类似是一个列表的功能。特殊之处是set是可以自动排重的(不重复)。
set内部实现是一个value永远为null的hashMap.实际是通过hash的方式来快速排重的
set可提供一个成员是否在一个集合内
sorted set
zadd,zrange,zrem,zcard
Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
具体解释可以参考我的下一篇文章
https://learnku.com/articles/32449
Redis的持久化机制
RDB持久化
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF(append only file)持久化
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
Redis持久化磁盘IO方式优缺点
RDB存在优势:
该方式,整个Redis数据库只包含一个文件,对于文件备份很方便,容易数据恢复
对于Redis服务进程而言,在开始持久化时,需要fork出子进程,之后再由子进程完成这些持久化任务,极大的避免服务进程执行IO操作
相比AOF.如果数据集很大,RDB启动效率会更高
劣势:
如果系统宕机,未来得及写入磁盘数据会丢失
由于RDB是通过fork子进程来协助完成数据持久化,因此数据集较大是,会导致服务器停止服务几百毫秒
本作品采用《CC 协议》,转载必须注明作者和本文链接
rdb可能会丢失最后一次快照后更新的数据
我读ruai dei si