Redis设计与实现(一) 分布式部分 复制 和 发布/订阅模式
Redis设计与实现
第一部分 数据结构与对象
Redis对象
-
首先key value,key是固定的字符串对象,value可以是那5种中的一种,而那5种根据场景的不同,每种都有至少两种编码方式,也就是数据结构
-
数据结构有linkedlist 双端链表
-
ziplist压缩列表
- 这个用的太多了 以至于我有深刻的印象
-
skiplist
- 跳跃表 类似于平衡树的作用 但是实现方式太友好了
-
raw
- 都是sds simply dynamic string
-
embstr
- 都是sds simply dynamic string 区别是这个更短喽 压缩过的 是连续的内存 所以速度比raw的快
-
hashtable
- 两个表嘛ht[0]和ht[1]
- 嗯 冲突就是在同一个哈希值组成链表 然后有一个负载因子 默认好像是1 进行rehash 然后rehasn的话会在另外一个大于当前数量number的最小的2的n次方那么大的扩容 扩容期间 服务器空闲就转移 当然 新插入的都是在这个新表里 hashtable数组ht[1] 然后全部拷到ht[1]后 就会把ht[0]换成ht[1] 然后h[1]又变成空
-
linkedlist
- 就是一个简单的双链表
-
intset
- int_8 int_16 int_32 int_64
- 会保存编码方式
- 一旦有东西需要往上提 比如从int_32到int_64了 这个过程是不可逆的 即使把64的都删了 也不会降编码回32了
第八章 对象
-
字符串
-
int
- redis默认有0~9999的int字符串
- 顺便说一下 整数都是先转字符串 然后用的时候再转回数字
-
raw
-
embstr
-
-
列表
- linkedlist
- ziplist
-
哈希对象
- ziplist
- dict 或者叫hashtable
-
集合
- intset
- hashtable
-
有序集合
-
ziplist
- score key
-
数据结构叫zset
-
skiplist
- 子主题 1
-
dict
- 子主题 1
-
同时用这两个的原因是因为 既要单点查询的速度 也要范围查询的速度吧
-
-
第二部分 单机数据库的实现
第9章 数据库
-
类型检查 命令多态
-
先看key是不是符合那个命令的
- 再看看编码方式是什么
-
-
对象回收
- 计数呗
-
对象共享
-
不共享包含字符串的对象
-
对整数检查O(1)
-
对字符串O(N)
-
对象包含了多个值对象
- O(N^2)
-
-
空转时长
- 有个lru时间 记录最后一次被程序访问的时间
第10章 RDB持久化
第11章 AOF持久化
第12章 事件
第13章 客户端
第14章 服务器
第三部分 多机数据库的实现
第15章 复制
-
Redis的复制分为两步, 同步(sync)和传播(command propagate)
- 在我理解无非一个是先获取一个服务器的快照
- 另外一个是追赶式恢复
-
1.同步
-
客户端向服务器发送SYNC命令来完成
- 1.从服务器向主服务器发送SYNC命令
- 2.收到SYNC的master执行 BGSAVE命令, 在后台生成一个RDB文件, 并用一个缓冲区来记录现在开始的所有命令
- 3.主服务器把RDB文件传给从服务器,从服务区更新至 主服务器执行BGSAVE的命令状态
- 4.主服务器把缓冲区的所有命令发送给从服务器, 从服务器进行追赶式恢复
-
SYNC命令非常号资源,
- 生成RDB文件需要CPU 内存, 磁盘I/O
- 发送RDB文件需要网络带宽
- 从节点载入的时候也无法处理请求
-
-
2.命令传播
- 无非就是主服务器执行命令 , 然后发送给 从服务器 来保持一致性
-
旧版复制功能的缺陷
-
每次都是把完整的RDB文件进行传输
- 如果断线时间不是很长也要 全部复制一遍
- 所有要引入部分复制(PSYNC)
-
-
新版复制功能
-
部分重同步的实现
-
1.主服务器的复制偏移量(replication offset)
-
主服务器和从服务器都维护一个偏移量
- 每次主服务器向从服务器传播N个字节的数据,就把字节的偏移量加上N
- 从服务器收到N个字节的数据, 也把自己的偏移量加上N
- 如果二者偏移量不一样, 那就是数据不一致
-
-
2.主服务器的复制挤压缓冲区(replication backlog)
-
是一个固定长度的先进先出队列
-
如果从服务器连接上主服务器的时候, 从服务器会把自己的偏移量发给服务器
-
如果已经不在了
- 执行完整的重同步
-
如果这个偏移量还在复制积压缓冲区之内
- 执行部分同步
-
-
默认1MB
- 一般设成= 2(冗余)* 平均重连时间*每秒写入命令的字节数
-
-
-
3.服务器的运行ID
-
主服务器会把自己的ID发送给客户端
- 如果从服务器保存的运行ID和当前的主服务器发过来的一样, 说明之前连的就是这台, 尝试部分重同步
- 如果不一样, 执行完整重同步
-
-
-
-
复制功能的实现
-
步骤1 设置主服务器的ip和端口
- 加上俩机子服务器
- 异步的, 返回ok, 然后后台执行复制
-
步骤2 建立socket连接
-
如果成功连接
-
从服务器会被主服务器看成特殊的客户端
-
对于普通的客户端来说
- 从服务器还是个服务器
-
-
-
步骤3 发送ping命令
- 如果不成功会回到第2步的
-
步骤4 身份验证
- 主要是看主服务器和从服务器有没有设置密码, 常识
- 失败就重试
- 成功就准备复制
-
步骤5 发送端口信息
- 从服务器向主服务器发送从服务器的监听端口号
-
步骤6 同步
- 互为客户端
-
步骤7 命令传播
-
进入命令传播状态
- 一直接受主服务器的写命令
-
-
-
心跳检测
-
命令传播阶段, 每1s ,从服务器会向主服务器发送replication ack [offset]
-
作用
-
1.检测主从服务器的连接状态
-
2.辅助实现min-slaves选项
- 比如min-slave-to-write 3
- min-salves-max-lag 10
- 如果从服务器少于3 , 拒绝写
- 如果三个服务器延迟都大于等于10, 拒绝写的请求
-
3.检测命令丢失
- 如果主服务器发现落后于自己, 就会从复制挤压缓冲区中重新发给从服务器
-
-
第16章 Sentinel哨兵
第17章 集群
第四部分 独立功能的实现
第18章 发布与订阅
-
发布与订阅的核心命令是两组
-
Publish
-
Subsctibe
- Subscribe
- UnSubscribe
- PSubscribe
- PUnSubscribe
-
-
1.频道的订阅与退订
-
订阅频道
-
服务器有个字典叫pubsub_channels
- 键是 某个被订阅的频道
- 值是 一个链表, 这里面存了该频道的订阅者
-
-
退订频道
-
根据频道的名字在字典中找
- 把客户端从链表中删除
- 如果恰好是最后一个, 把这个频道也删除
-
-
-
2.模式的订阅与退订
-
服务器有个list叫pubsub_patterns
- <client, pattern>作为list链表的元素
- list<client,pattern>
-
订阅模式
- 新建一个node, 把这个node添加到list后面
-
退订模式
- 遍历这个list, 找到node然后删掉
-
-
3.发送消息
-
发送消息publish channelA "message"有两步,
- 1.首先把消息发给channelA的所有订阅者
- 2.然后把消息发给所有模式匹配channelA的
-
1.很简单啦, 找到key对应的链表, 然后全部发一遍
-
2.先查找所有list里的pattern, 如果匹配, 就把这个pattern对应的client都发一遍
-
-
4.查看订阅消息
-
pubsub channels
-
pubsub numsub [channel]
- 返回channel的订阅者数量
-
pubsub numpat
- 返回被订阅模式的数量
- number of pattern
-
第19章 事务
第23章 慢查询日志
第24章 监视器
- 客户端可以通过MONITOR命令 ,将客户端转换成监视器 ,接受并打印每个命令请求的相关信息
- 把客户端的REDIS_MONITOR标识打开, 就可以从普通客户端变成监视器了
- 服务器把所有监视器都存在一个链表里
- 每次都会遍历链表, 挨个发送
实践
Redis的入门应用
-
Redis是key-value型数据库
-
Redis 里的单行命令都是原子的 是为了同时有多个用户对同一个数据修改
-
string
-
set key value
-
子主题 1
-
SET server:name "fido"
-
GET server:name => "fido"
-
EXISTS server:name => 1
-
EXISTS server:blabla => 0
-
SET connections 10
-
INCR connections => 11
-
INCR connections => 12
-
DEL connections
-
INCR connections => 1
-
It is also possible to increment the number contained inside a key by a specific amount:
-
INCRBY connections 100 => 101
-
And there are similar commands in order to decrement the value of the key.
-
DECR connections => 100
-
DECRBY connections 10 => 90
-
-
-
get key
-
exists key
-
DEL key
-
EXPIRE key 100
-
100秒后删除
-
TTL key
- 显示还有几秒删除
- 只要set key 一次 之前的计时失效 TTL key 返回-1
- 返回-2说明计时过了 这东西被删了
-
PERSIST key
-
让这个计时结束
- 永久保存
-
-
-
INCR key
- INCRBY key 100
-
DECR key
- DECRBY key 100
-
不能简单的get key \ value=value+1 \set key value
-
-
list
-
对头尾操作较快 且 有序号的
-
lpush friend "yanhao"
-
rpush friend "yanhao"
-
lpush friend 1 2 3 4
-
执行顺序是
- lpush friend 1
- lpush friend 2
- lpush friend 3
- lpush friend 4
-
所以结果是 4 3 2 1
-
-
lrange friend 0 -1
-
切片
-
example
-
LRANGE friends 0 -1 => 1) "Sam", 2) "Alice", 3) "Bob"
-
LRANGE friends 0 1 => 1) "Sam", 2) "Alice"
-
LRANGE friends 1 2 => 1) "Alice", 2) "Bob"
-
lrange friend 0 10
-
- "Sam", 2) "Alice", 3) "Bob"
-
-
lrange friend -3 -1
-
- "Sam", 2) "Alice", 3) "Bob"
-
-
-
-
lset friend 0 "yanhao"
- 把friend这个list里面的第0个元素改成"yanhao"
-
lpop friend
- rpop friend
- 子主题 2
-
llen
-
-
set
-
sadd setname "yanhao"
-
return 0
- 没加成功,因为本来就有这个key了
-
return 1
- 加成功了
-
-
sismember setname "yanhao"
-
smembers friend
-
sincr
-
sdecr
-
srem friend "yanhao"
- return 0
- return 1
-
srandmeber friend 2
- 随机返回数据成员
-
spop
- 随机删
-
sunion set1 set2
-
-
sorted set
-
在set的同时加上一个用来排序的东西
-
ZADD fruit 1 apple
-
ZADD fruit 2 banano
-
Zrange fruit 0 1
- 切片
-
-
hashes
-
hset user:37 name "yanhao"
-
hgetall user:37
-
hmset user:37 name "yanhao" age 17
- 同时设置多个
-
hget user:37 name
-
hincrby user:37 name 2
- name会加2
-
hdel
-
XMind: ZEN - Trial Version