初识Redis Cluster & 深入Redis Cluster & 缓存设计与优化
实验部分对应 Redis笔记(四)实验部分:redisCluster的原生安装与官方工具安装
第9章 初识Redis Cluster
Redis Cluster是Redis 3提供的分布式解决方案,有效解决了Redis分布式方面的需求,同时它也是学习分布式存储的绝佳案例。本章将针对Redis Cluster的数据分布,搭建集群进行分析说明。
9-1 本章目录
- 呼唤集群
- 数据分布
- 搭建集群
- 集群伸缩
- 客户端路由
- 集群原理
- 开发运维常见问题
9-2 呼唤集群
为什么呼唤
-
并发量
-
数据量
-
网络流量
“解决方法"
配置“强悍”的机器:超大内存、牛xCPU等
正确的解决方法
分布式:简单的认为加机器
9-3 数据分布概论
两种分区方式
两种数据分布对比
9-4 节点取余分区
扩容
若添加一个节点在下面途中会有8成的迁移量
但如果采用翻倍扩容(比如下面 的六个 需要迁移的数据量将大大降低)
优缺点
优点: 客户端分片,使用 哈希+取余较为简单
缺点:节点伸缩时数据节点关系变化,会导致数据迁移,而迁移数量和添加节点数量有关所以建议翻倍扩容
9-5 一致性哈希分区
一个tocken环的长度设为 0~2的32次方,放置四个节点,新添加的key则会顺势针的找到最近的node
扩容
若添加n5 则变动的数据仅为n1和n2之间的 ,需要变动(回写)的数据就会减少
优缺点
优点
客户端分片:哈希+顺时针(优化取余)
节点伸缩:只影响邻近节点,但是还是有数据迁移
缺点
添加n5后,会使除 n2 n5外的节点的数据量相对更大 因此建议使用翻倍伸缩,保证最小迁移数据和负载均衡
9-6 虚拟槽哈希分布
- 预设虚拟槽:每个槽映射一个数据子集,一般比节点数大
- 良好的哈希函数:例如CRC16
- 服务端管理节点、槽、数据:例如Redis Cluster
9-7 基本架构
分布式架构中每个节点都会负责读和写 并且彼此能够相互通信
Redis Cluster架构
- 节点
# 节点集群模式启动该节点
cluster-enabled:yes
-
meet 几点之前要达到互通
-
指派槽
-
复制
9-8 原生安装
#准备节点
节点握手
分配槽
分配主从
9-9 工具安装
第10章 深入Redis Cluster
本章将针对Redis Cluster的集群伸缩,请求路由,故障转移等方面进行分析说明。
10-1 集群伸缩目录
主要分为三个部分 集群的扩容与缩容及其伸缩的原理
10-2 集群伸缩原理
下图代表节点的加入与下线
下图是一个“伸”的过程,即将节点加入6385加入集群,该过程需要meet操作 和槽的分配。
其实集群伸缩就等于是槽和数据在节点之间的移动
10-3 集群的扩展
10-3-1 扩展集群-1.加入节点
准备新节点
新节点:
- 集群模式
- 配置和其他节点统一·启动后是孤儿节点。
redis-server conf/redis-6385.conf
redis-server conf/redis-6386.conf
启动两个孤立节点之后集群结构如下:
加入集群
127.0.0.1:6379>cluster meet 127.0.0.1 6385
127.0.0.1:6379>cluster meet 127.0.0.1 6386
加入之后如下
加入集群有两个作用
- 为它迁移槽和数据实现扩容
- 作为从节点负责故障转移
当然也可以使用官方工具进行加入节点
redis-trib.rb add-node new_host:new port existing_host:existing port --slave --master-id <arg>
redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
使用redis-trib.rb能够避免新节点已经加入了其他集群,造成故障。
迁移槽和数据
主要分为三步
槽迁移计划
分配给新节点一定的槽数:
迁移数据
- 对目标节点发送:
cluster setslot {slot} importing {sourceNodeld}
命令,让目标节点准备导入槽的数据。 - 对源节点发送:
cluster setslot {slot} migrating {targetNodeld}
命令,让源节点准备迁出槽的数据。 - 源节点循环执行
cluster getkeysinslot {slot){count}
命令,每次获取count个属于槽的健。 - 在源节点上执行
migrate {targetlp} {targetPort} key 0 {timeout}
命令把指定key迁移。 - 重复执行步骤3~4直到槽下所有的键数据迁移到目标节点。
- 向集群内所有主节点发送
cluster setslot {slot} node {targetNodeld}
命令,通知槽分配给目标节点。
添加从节点
10-3-2 集群扩容演示-1
10-3-3 集群扩容演示-2
10-4 集群的缩容
10-4-1 集群缩容-说明
10-4-2 集群缩容-操作
10-5客户端路由
10-5-1 moved异常说明和操作
下图是指向自身 之后槽命中的效果
槽不命中:moved异常
下面的实例演示中先 添加 -c 参数以集群方式启动 可以看到 键值为hello是直接命中了节点 键值为php时出现了moved异常 并且帮我们完成跳转成7001客户端。
而不使用 -c 参数时则会直接返回异常
10-5-2 ask重定向
由于槽迁移而客户端中所保存的地址不变会造成访问上的问题 这就有了ask重定向异常
moved和ask的比较
- 两者都是客户单重定向
- moved:槽已经确定迁移
- ask:槽还在迁移中
10-5-3 smart客户端实现原理
smart客户端原理主要就在于追求性能:
- 从集群中选一个可运行节点,使用cluster slots初始化槽和节点映射。
- 将cluster slots的结果映射到本地,为每个节点创建JedisPool。
- 准备执行命令。
其中执行命令是smart客户端的核心过程
JedisCluster中 存储的是 key及其对应的槽的位置 和改槽所在的节点
若目标节点返回出错则会随机请求一个节点 这个过程大概率会发生 moved异常然后就会重置本地缓存 如果五次未成功则返回异常
大致流畅图如下:
10-5-4 JedisCluster执行源码分析
10-6 smart客户端JedisCluster
10-6-1 JedisCluster基本使用
10-6-2 整合spring-1
10-6-3 整合spring-2
10-6-4 多节点操作命令
10-6-5 批量操作优化
批量操作怎么实现?
mget mset必须在一个槽
四种批量优化的方法:
- 串行mget
2. 串行IO
其实就是对上面的改进 通过sunKeys按照所属节点聚合分组 降低传输次数
3. 并行IO
使用多线程进行并行操作
4. hash_tag
使用tag进行包装 效率更高 全部请求到一个节点上
四种方式比较
10-7 故障转移
10-7-1 故障发现
◆通过ping/pong消息实现故障发现:不需要sentinel
◆主观下线和客观下线
主观下线
定义:某个节点认为另一个节点不可用,“偏见"
主管下线流程:
其中 pfail就代表是主管下线
客观下线
定义:当半数以上持有槽的主节点都标记某节点主观下线
客观下线流程:一个主节点接收到其它节点的ping消息 并且该节点会维护每个节点及其对应的pfail信息的一个列表
尝试客观下线的流程
该节点的作用有两个:
- 通知集群内所有节点标记故障节
- 点为客观下线通知故障节点的从节点触发故障
转移流程
10-7-2 故障恢复
故障恢复主要分为四步:
资格检查
- 每个从节点检查与故障主节点的断线时间。
- 超过cluster-node-timeout*cluster-slave-validity-factor取消资格。
- cluster-slave-validity-factor:默认是10
cluster-node-timeout默认是15
即可得知 时间超过150秒则取消资格
备选举时间
上图b-1节点偏移量 更大意味着丢失数据更小 所以要给予偏袒 让其延迟一秒就参与选举以获得更多的票数。
选举投票
获得票数过半即可替换主节点
替换主节点
- 当前从节点取消复制变为主节点。(slaveof no one)
- 执行clusterDelSlot撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽分配给自己。
- 向集群广播自己的pong消息,表明已经替换了故障从节点
10-7-3 故障模拟
10-8 Redis Cluster常见开发运维问题
10-8-1 集群完整性
cluster-require-full-coverage默认为yes(即所有节点和槽可用集群才提提供服务)
- 集群中16384个槽全部可用:保证集群完整性
- 节点故障或者正在故障转移:
(error)CLUSTERDOWN The cluster is down
大多数业务无法容忍,cluster-require-full-coverage建议设置为no
10-8-2 带宽消耗
体现在三个方面:
- 消息发送频率:节点发现与其它节点最后通信时间超过cluster-node-timeout/2时会直接发送ping消息
- 消息数据量:slots槽数组(2KB空间)和整个集群1/10的状态数据(10个节点状态数据约1KB)
- 节点部署的机器规模:集群分布的机器越多且每台机器划分的节点数越均匀,则集群内整体的可用带宽越高。
举例
- 规模:节点200个、20台物理机(每台10个节点)
- cluster-node-timeout=15000,ping/pong带宽为25Mb
- cluster-node-timeout=20000,ping/pong带宽低于15Mb
优化
- 避免“大”集群:避免多业务使用一个集群,大业务可以多集群。
- cluster-node-timeout:带宽和故障转移速度的均衡。
- 尽量均匀分配到多机器上:保证高可用和带宽
10-8-3 Pub/Sub广播
问题:publish在集群每个节点广播:加重带宽
解决:单独“走”一套Redis Sentinel
10-8-4 集群倾斜-目录
10-8-5 数据倾斜
- 节点和槽分配不均
redis-trib.rb info ip:port 查看节点、槽、键值分布
redis-trib.rb rebalance ip:port 进行均衡(谨慎使用)
-
不同槽对应键值数量差异较大
CRC16正常情况下比较均匀。
可能存在hash_tag ,可通过cluster countkeysinslot {slot}
获取槽对应键值个数 -
包含bigkey
bigkey:例如大字符串、几百万的元素的hash、set等
可以在从节点执行:redis-cli --bigkeys
查看bigkey
优化:优化数据结构。 -
内存相关配置不一致
hash-max-ziplist-value、set-max-intset-entries等
优化:定期“检查”配置一致性
10-8-6 请求倾斜
热点key:
- 重要的key或者bigkey
优化:
- 避免bigkey
- 热键不要用hash_tag
- 当一致性不高时,可以用本地缓存+MQ
10-8-7 读写分离
只读连接:集群模式的从节点不接受任何读写请求。
- 重定向到负责槽的主节点
- readonly命令可以读:连接级别命令
读写分离:更加复杂
- 同样的问题:复制延迟、读取过期数据、从节点故障
- 修改客户端:cluster slaves{nodeld}
10-8-9 数据迁移
官方迁移工具:redis-trib.rb import
- 只能从单机迁移到集群
- 不支持在线迁移:source需要停写
- 不支持断点续传
- 单线程迁移:影响速度
在线迁移:
- 唯品会redis-migrate-tool
- 豌豆荚:redis-port
10-8-10 集群vs单机
集群的限制
- key批量操作支持有限:例如mget、mset必须在一个slot
- Key事务和Lua支持有限:操作的key必须在一个节点
- key是数据分区的最小粒度:不支持bigkey分区
- 不支持多个数据库:集群模式下只有一个db0
- 复制只支持一层:不支持树形复制结构
分布式redis不一定好用:
1.Redis Cluster:满足容量和性能的扩展性,很多业务”不需要”。
大多数时客户端性能会”降低"。
命令无法跨节点使用:mget、keys、scan、flush、sinter等。
Lua和事务无法跨节点使用。
客户端维护更复杂:SDK和应用本身消耗(例如更多的连接池)。
2.很多场景Redis Sentinel已经足够好。
第11章 缓存设计与优化
讲解将缓存加入应用架构后带来的一些问题,这些问题常常会成为应用的致命点。
11-1 目录
11-2 缓存的受益和成本
受益
1.加速读写
通过缓存加速读写速度:CPUL1/L2/L3 Cache、Linux page Cache加速硬盘读写、浏览器缓存、Ehcache缓存数据库结果。
2.降低后端负载
后端服务器通过前端缓存降低负载:业务端使用Redis降低后端MySQL负载等
成本
1.数据不一致:缓存层和数据层有时间窗口不一致,和更新策略有关。
2.代码维护成本:多了一层缓存逻辑。
3.运维成本:例如Redis Cluster
使用场景
1.降低后端负载:
对高消耗的SQL:join结果集/分组统计结果缓存。
2.加速请求响应:
利用Redis/Memcache优化IO响应时间
3.大量写合并为批量写:
如计数器先Redis累加再批量写DB
11-3 缓存的更新策略
1.LRU/LFU/FIFO算法剔除:例如maxmemory-policy。
2.超时剔除:例如expire。
3.主动更新:开发控制生命周期
两条建议
1.低一致性:最大内存和淘汰策略
2.高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底。
11-4 缓存粒度问题
缓存粒度控制-三个角度
1.通用性:全量属性更好。
2.占用空间:部分属性更好。
3.代码维护:表面上全量属性更好。
11-5 缓存穿透问题
原因
1.业务代码自身问题
2.恶意攻击、爬虫等等
如何发现
1.业务的相应时间
2.业务本身问题
3.相关指标:总调用数、缓存层命中数、存储层命中数
解决方法1-缓存空对象
两个问题
1.需要更多的键。
2. 存储层数据可能只是短期挂掉,缓存层数据置为空会导致 二者“短期”不一致。
代码中若 存储层中的数据为空 则设置在缓存中设为空,并且设置过期时间
解决方法2-布隆过滤器拦截
该方法更适用于较为固定的数据。
11-6 缓存雪崩优化
11-7 无底洞问题
问题描述:
2010年,Facebook有了3000个Memcache节点。
发现问题:“加”机器性能没能提升,反而下降。
http://highscalability.com/blog/2009/10/26/facebooks-memcached-multiget-hole-more-machines-more-capacit.html
问题关键点:
- 更多的机器!=更高的性能
- 批量接口需求(mget,mset等)
- 数据增长与水平扩展需求
优化IO的几种方法
1.命令本身优化:例如慢查询keys、hgetall bigkey
2.减少网络通信次数
3.降低接入成本:例如客户端长连接/连接池、NIO等
四种批量优化的方法
1.串行mget
2.串行I0
3.并行I0
4.hash_tag
11-8 热点key的重建优化
三个目标和两个解决
1.三个目标:
减少重缓存的次数
数据尽可能一致
减少潜在危险
2.两个解决:
互斥锁(mutex key)
实例代码
永远不过期
1.缓存层面:没有设置过期时间(没有用expire)。
2.功能层面:为每个value添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
代码示例:
两种方案比较: