Redis进阶学习
数据丢失问题 持久化
并发问题 主从集群保证可用
故障恢复 哨兵机制
存储能力扩展 分片集群
RDB被称为Redis数据快照
当1 停机以后 或者 2 满足我们在配置文件中配置的RDB触发规则
会进行一次RDB持久化
把数据保存到本地磁盘
尽量使用save命令而非bgsave
bgsave会异步开启一个子进程 避免Redis主进程的正常工作受影响
redis.conf 有关RDB的配置如下
AOF简介
Redis的每一个写命令都会记录在AOF文件中 下次重启Redis执行AOF文件中的命令加载数据
redis.conf中关于AOF的配置
AOF默认关闭 需要配置appendonly yes打开
AOF的默认命令记录频率 每秒钟记录一次 每秒刷盘 这时最多丢失一秒的数据量 性能适中
AOF的bgrewriteaof 命令 重写命令
让AOF文件执行重写功能 对 其中存储的命令做 优化 以最少的命令达成相同效果
并且redis.conf中有一些默认的AOF文件重写机制
为了应对更高的并发 我们把单机的redis 升级为主从集群
Redis主从集群 一台master 应对写操作 其余的slave应对读操作
master把数据同步到slave来保证数据的一致性
假如有 A B两个节点
在B上执行命令 slaveof A节点的IP A节点的端口
相当于让B成为A的子节点
之后B会成为只读节点 对B的写操作失效 同时对A的写操作 会同步到B节点中
1 slave往master发送replid和 offset
2 master节点判断replid是否一致 一致说明不是第一次来 恢复continue
3 主节点去repl_backlog获取offset之后数据发给slave节点
4 slave节点执行命令
主从集群的优化
1 如果网络带宽足够 我们可以开启无磁盘复制 减少磁盘io
2 redis单节点内存占用不要太大 避免复制RDB时过多的磁盘IO
避免全量同步
适当提升repl_backlog的大小 尽量在slave宕机后快速恢复 避免全量同步
限制一个Master节点的slace数量 采取主-从-从的链式结构
哨兵的三大作用
1 监控
sentinel会不断检查slave和master是否正常工作
2 故障恢复
当master宕机时 会选取一个slave作为Master 原来的master恢复后作为slave
3 通知
sentinel作为redis客户端的服务发现来源 当集群发生故障转移时
会将最新信息通知给客户端
三大作用之监控
sentinel监控基于心跳机制 每隔一秒向集群中的每个实例发送Ping命令
1 主观下线
某sentinel发现某实例未在规定时间内响应‘’
2 客观下线
一定数量的sentinel发现某实例主观下线
master选举规则
三大作用之故障转移
1 给指定要成为master的节点发送slaveof no one 使其成为master
2 给其他所有节点发送新的Master节点的地址 通过salveof 命令使他们成为新master的从节点
3 把故障原master节点标记为slave 故障恢复后成为新master节点的salve
sentinel.conf 配置文件
port 27001
sentinel announce-ip 192.168.150.101
sentinel monitor mymaster 192.168.150.101 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s1"
解读:
port 27001
:是当前sentinel实例的端口sentinel monitor mymaster 192.168.150.101 7001 2
:指定主节点信息
mymaster
:主节点名称,自定义,任意写192.168.150.101 7001
:主节点的ip和端口2
:选举master时的quorum值启动哨兵
redis-sentinel s1/sentinel.conf
解读
s1/sentinel.conf 你哨兵配置文件存放的绝对路径
Redis主从解决了高并发读 可用性的问题
但仍存在 高并发写 海量数据存储的问题 在这时我们应该采用分片集群
分片集群中有多个master 每个存储不同的数据 这解决了海量数据存储 和 高并发写的问题
并且master节点之间通过Ping互相检测状态 分片集群没有哨兵机制
每个Master也可以配置从节点 保证其高可用
==1 编写配置文件 ==
这里给出一个节点的配置文件 其他节点的配置文件参照这个
redis.conf
port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.168.150.101
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log
2 一键启动所有节点
# 进入/tmp目录
cd /tmp
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
3 创建集群 让节点之间产生关系
redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003
命令说明:
redis-cli --cluster
或者./redis-trib.rb
:代表集群操作命令create
:代表是创建集群--replicas 1
或者--cluster-replicas 1
:指定集群中每个master的副本个数为1,此时节点总数 ÷ (replicas + 1)
得到的就是master的数量。因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master通过命令可以查看集群状态:
redis-cli -p 7001 cluster nodes
== 1 什么是散列插槽==
Redis会默认对分片集群的每一个master节点分配插槽
数据不是和master节点绑定 而是和插槽绑定
在添加数据时 redis会根据Key的有效值计算插槽值 然后存到对应的slot上
有效值是指如果有大括号 那么有效值就是大括号内的 没有大括号 整个Key都是有效值
2 散列插槽解决的问题
由于数据存储在slot上 当一个master节点宕机时 其对应的slot可以转移给别的节点
防止了数据的丢失问题 保证可用性
1 自动故障转移
当集群中的一个master宕机
首先他失去与其他实例的链接
确认下线以后会自动进行故障转移 提升一个slave作为master
2 数据迁移(手动故障转移 cluster failover)
可以手动切换slave和master节点
缺省流程
1 slave节点通知master节点拒绝新的任何客户端请求
2 Master返回offset 等待slave数据同步一致
4 开始故障转移
5 slave 最后标记自己为master 广播通知其他master节点 和原来的master节点
配置跟连接主从集群差不多 可以参考上文
1 引入Pom
2
主要是配置文件和配置主从集群不同
3 配置读写分离
1 TTL的记录方式
TTL记录在datebase结构体的一个dict字典中 以Key TTL形式存储 另一个dict字典 存储的是 Key - Value值
2 过期key删除策略
1 惰性清理
每次查找对应的Key时 判断key是否过期 过期则删除
其存在问题 如果极端情况下 Key过期后一直没有被查找 则会堆积大量的Key
2 定期清理
定期抽样一定数量的Key 判断是否 过期 过期则删除
关于定期清理
Redis会设置一个定时任务serverCron() 按照server.hz的频率执行过期Key清理 模式slow
Redis每个事件循环前会调用beforesleep() 执行过期Key清理 模式为fast
1 Redis在什么时候进行内存淘汰
Redis会在每一次处理客户端命令时 processcommand()函数中判断是否进行内存淘汰
2 Redis内存淘汰策略
要注意 Redis默认的内存淘汰策略是noevication 不进行内存淘汰 内存满时不会写入新数据
1 allkeys 淘汰策略 所有key淘汰
random 在所有Key中随机淘汰
lru 最少最近使用 淘汰 用当前时间减去最后一次访问时间 值越大越优先被淘汰
lfu 最少频率使用 淘汰 优先淘汰使用频率低的
2 volatile 淘汰策略 设置TTL的key中进行淘汰
random
ttl 优先淘汰ttl值最小的Key
lru
lfu
3 lru lfu算法
redis的数据都会被封装成redisobject结构
其中存有类型 编码方式 指向真实数据的数据指针等
其中还有一个字段 根据内存淘汰策略的不同 存储不同的值
加入使用lru 最少最近使用策略
那么这个值24位存储的是最近最后一次使用的秒数
假如使用lfu 最少频率使用策略
16高位记录的是最近最后一次访问该Key的时间 低8位记录的是访问的频率
其中这个频率是基于Lfu算法的逻辑访问频率
并且会这个访问频率在算法计算中会随着时间逐渐降低 默认每和上一次访问时间间隔一分钟 计数器就减去1
4 淘汰策略流程分析
1 先会判断内存是否充足
2 判断内存策略是哪种 noevicaiton 则不进行内存淘汰 allkeys volitail
3 具体判断是 lru lfu ttl random的哪种策略
4 如果是 random随机挑选一个key删除
如果是lru lfu noevication 则会创建一个删除池
随机抽样一些数据加入这个删除池 进行删除
5 如果删除后判断内存足够 则取消内存淘汰
如果删除后内存仍然不够用 则再次进行淘汰策略循环
1 什么是bigkey
bigKey通常以Key的大小和其成员数量来进行综合判断
通常 如果一个key大于10kb
或者其成员数量超过1000 我们就认为它是一个bigkey
bigkey的危害
网络阻塞 假如读一个bigkey 那么少量的QPS就可能导致带宽被占满 影响Redis 乃至物理机
数据倾斜 bigkey所在的实例内存占用远超其他实例 难以达到内存的均衡
Redis阻塞 当对一个hash zset类型的bigkey进行操作 因为Redis是单线程 很可能会造成阻塞
cpu压力 当对一个Bigkey进行序列化和反序列化 会对cpu造成巨大压力
发现bigkey
1 通过redis-cli 的 --bigkeys 会返回每一种类型的最大key 但是只会返回最大的一个0 并且不一定返回满足bigkey要求的key
所以不推荐使用
2 自己编写scan进行扫描 根据长度进行判断
3 第三方工具分析RDB文件 如Redis rdb tools
4 云服务器自带的分析功能
删除Bigkey
由于Bigkey很大 占用内存过多
对其进行删除操作也可能造成Redis阻塞
在Redis4.0之后提供了Unlink命令来进行异步删除
Redis3.0和以下版本最好遍历bigkey 逐个元素进行删除 来避免阻塞
1 什么是跳表
跳表首先是一个双向链表
他和普通链表的区别是升序排列
一个链表节点可能包含多个节点指针 跨度不同 这就解决了传统链表的查询遍历慢问题
2 跳表的数据结构
跳表的本身
包含头尾指针
节点的数量 最大的层级(默认为1)
跳表的节点
ele 节点存储的值
score 节点分数 可以排序用
backward 前一个节点的指针
多级索引数组 里面有索引跨度和下一个节点的指针
ZSET也就是sortedset 没一个元素需要指定一个socre和member值
可根据score值排序 可以根据member查出对应的score member值唯一
基于sorted ser这几个特性
zset底层采用了dict 实现 键值对存储 键值唯一
skiplist 实现排序
skiplist每个节点的ele存储member score就存储score
dict的每个dictentry存储Key value 就是member score值 实现通过member查询score
但是当元素不多时dict和skiplist的优势不明显
并且由于dict和skiplist都存储了一份member dict数据 所以比较占用内存空间
因此当满足一下两个条件
1 元素数量小于一定值 默认为128
2 每个元素都小于一定字节数 默认为64字节
zset底层会采用ziplist存储
由于ziplist没有键值对这一概念 是连续的内存空间
因此会把score和member连续存储
score和member存在紧挨在一起的entry
队首的score小
如果ziplist的元素数量或者大小超标 则zset底层会转为dict和skiplist存储
hash底层默认采用ziplist进行编码
zplist中两个相邻的entry分别存储field和value
当数据量较大
满足一定条件
1 ziplist中元素个数超过512 默认
2 ziplist单个元素大小超过64字节
zplist会转为dict结构进行存储
当执行heset命令时
先会判断hash的key是否存在 如果不存在则创建一个 默认为ziplist
接着判断是否需要把ziplist转为dict存储 (主要检查元素大小)
接着最后进行set操作 在进行set操作的同时 检查ziplist的长度是否超出
超出的话也向dict转化
Dict由三部分组成
dict(字典) dicthashtable(哈希表) dictentry(哈希节点)
向dict中存储数据的步骤
先通过Key计算出hash值
然后用hash&sizemask 得到存储到数组中的哪一个位置
这个数组中存储的是dictentry 哈希节点的指针 指向hash节点
(size是哈希表的大小 sizemask是哈希表的大小减1 hash&sizemask 相当于 hash % size )
dict结构中包含两个dicthashtable 一个平常存储数据 一个平常为null
供rehash使用
dict的扩容条件
dict在每次新增键值对时会检查负载因子(used/size) 如果满足条件
1 负载因子>=1 且后台没有bgsave bgrewriteaof等后台进程
==2 负载因子>=5 ==
则会执行hash扩容
== dict的收缩条件 ==
dict每次删除元素时 会对负载因子做检查 当负载因子<0.1时
会触发hash收缩
hash扩容和收缩的流程
1 根据当前需要扩容还是收缩 重新计算size值大小
如果是扩容size是第一个大于等于userd+1的第一个2的n次方
如果收缩size是 第一个大于used的第一个2的n次方
2 根据新的size值申请内存空间 创建哈希表 并将这个表放到dict结构的另一个哈希表中
3 把rehashid 变为 0 代表开始rehash
4 每次执行增删改查时都看一看是否rehashid > -1 如果是 则一个一个元素的转移到新的哈希表中
每次转移的这个元素是hash[0]表的rehashid这个元素 直到所有的数据都转移到新的哈希表中
5 把新的hash表1赋值给旧的hash表0 同时把hash表1 赋值Null
Redis的核心命令处理部分是单线程
但从整体看Redis已经是多线程了
在Redis4.0中已经引入多线程处理耗时较长的任务 如bigkey等
在RS 6.0 中 网络模型是多线程 提高对cpu的利用率
那为什么Redis的命令处理仍然是单线程
1 因为Redis纯内存操作 速度极快 限制它的往往是网络瓶颈 多线程提升不大
2 引入多线程不可避免的产生线程安全问题
3 过多的多线程会造成频繁的上下文切换 造成开销
由于redis 是一个cs架构软件
为保证客户端和服务端的正常通信
因此定义了一个RESP协议
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。