哨兵Sentinel(一主多从):
主从热备的作用:1. 实现集群高可用性;2.读多写少的任务可以增大并发访问能力;
哨兵方式,是不改变原有系统的基础上,额外增加一组监控机器;是的手动故障恢复变成自动故障恢复。
故障迁移的步骤:(1)主节点出现故障,从节点们和主节点失去连接,主从复制失败;(2)每个哨兵节点通过定期监控发现主节点出现了故障;(3) 多个哨兵节点对主节点故障达成一致,选举出某哨兵作领导者负责故障迁移;(4) 领导者执行故障迁移
(2)定期监控,通过3套操作(复用现成的功能): info命令(得到主从拓扑结构),发布订阅(哨兵之间交换信息),ping(集群心跳检测)
(3)多个哨兵发现主节点故障,才认为真有故障(客观下线),一般设为哨兵数半数以上;Leader选举,谁先得出客观下线结论,就选谁,由Leader一个人去做故障转移;多个哨兵,减少误判的可能,防止单个哨兵挂掉;所以哨兵们尽量部署在不同的物理机,容灾;
(4)新主节点选择原则:健康,手动设过高优先级,复制偏移量最大的,id最小的;
哨兵的作用:(1)监控:定期检测所有节点是否可达; (2)通知:将故障转移的结果通知给应用程序; (3)主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系;(4)配置提供者:应用程序客户端连接的时候先连哨兵,得到主节点和从节点地址和状态,再去连主节点或从节点(JedisSentinelPool是先联系哨兵得到主节点,再和主节点建立连接)
高可用读写分离:客户端知道谁是主节点,谁们是从节点,写操作只往主节点上写,读操作只在从节点里挑一个读(维护一份从节点健康表,谁挂了就不读谁了);客户端可以通过读哨兵来获知谁是主从节点及他们的状态;
集群(多主多从):
分布式路线:1. 客户端分区(故障处理等操作要客户端自己实现)2.代理(增加了中间环节损耗性能) 3.原生集群
数据分布方案:0.顺序分区(例如ps-lite, 容易数据倾斜); 1.哈希取余(节点增减会造成数据大量迁移);2.一致性哈希(节点增减只会造成部分环的数据迁移;虚拟节点是对其的完善) ;3.虚拟槽分区(每个节点负责一部分槽; Redis是16K个槽,相互间通过流言协议Sync, 用数组每个槽属于哪个节点)
水平伸缩:增减机器,可通过命令或者脚本实现;
分布式系统中Meta-data的维护方式:1.集中式(放到一个地方,比如zookeeper); 2.P2P方式(例如流言协议,不停地互通信息;通常是随机选择,考虑谁最近疏远了就优先通信)
客户端请求方式:1.重定向:客户端随机选一个节点发命令,如果收到重定向回复(告诉哪个节点包含这个key),就向那个节点重发该命令; 2.Smart客户端:客户端维护slot归属数组, 为每个节点维护一个连接池, 收到重定向回复则更新slot归属数组;
hash_tag机制:为了让用户指定一批key放到同一个节点上,在key里{}里面的字符串用于哈希计算slot;只要{}里面的字符串相同,则必然分配到同一个slot上;
槽确定在那台机器上,发送MOVED重定向;槽在迁移中,发送ASK重定向;
故障转移(故障主节点的一个从节点升为主节点并接管槽位):1.故障发现:主观下线(可能存在误判), 客观下线(半数以上主节点同意该节点主观下线后); 2.故障恢复:故障主节点的所有从节点检查与主节点的断线时间,超长的不具备升主资格;3. 复制偏移量最大的具有最高选举资格,低的要延迟更多时间才有资格选举(很可能就轮不到它了);发起选举(配置纪元?); 4. 投票选举(其他主节点们参与投票, 故障节点的从节点不参与投票, 因为数目可能很少),谁先联系上投票者就投给谁; 票数不够则作废,下一轮投票(延迟的那些节点有机会了); 5. 票数够了则该从节点取消变为主节点,接管主节点的这些槽, 广播消息公告天下;
集群完整性: 任意一个主节点故障迁移过程中,是否允许集群继续提供服务,有选项控制;建议打开,因为一部分槽查不到大部分槽还是能用的;
发布/订阅的广播: 集群模式下,发布的消息,会被广播到集群里的所有节点(为了让连接这个节点的客户端得到消息),消息一多会有较大带宽消耗;
数据倾斜: 1.节点和槽分配严重不分配(可通过命令来重新rebalance);2.不同槽对应键数量差异过大(通过命令查询槽里的键数量以及具体键值,发现hash_tag情况); 3.集合对象包含大量元素(重新拆分); 4.ziplist/intset等配置不一致导致各节点内存使用量差异大;
请求倾斜:1. 热点大对象做拆分; 2.谨慎使用热键作为hash_tag,防止热键分到同一个槽; 3.用本地缓存减少热键调用;
默认从节点只热备,不处理任何客户端的读写请求;可以通过命令使其可以处理读请求;
缓存:
缓存优点:1.加速读写;2.降低后端(一般是DB)负载;
适用场景: 1. 开销大的复杂计算; 2.加速请求响应;
缓存更新策略:1. LRU等策略(可使用Redis自带的maxmemory+LRU); 2. 超时删除(Redis的TTL);3.主动更新; 1的一致性最高维护成本最低,2中等,3相反;一致性就是DB更新后缓存也更新的及时性
缓存的粒度:粒度越细,越可能省内存,但维护成本较高;
缓存穿透:当key在DB中也不存的时候,会先查缓存然后查DB,大量这种请求会给DB过大压力,造成系统卡死或崩溃;解决方法:1.缓存空对象(浪费内存;一致性保障需要另用过期或者主动更新来支持);2.客户端自己实现bloomfilter(适合命中率较低的情况,否则误判率就会上升)
缓存"无底洞"现象:数据量大并发请求量大后,水平扩容增加缓存机器数目,延迟不降反升;原因:key被分散到多台机器上,使得原先请求一台机器的延迟变成了max(多台机器);解决方案:1. 把要分到每台机器上的key放到一起,调1次pipeline或mget实现仅1次网络传输; 2.把1多线程并行执行(多台机器同时请求); 3. 把有可能一次请求的key们放到同一个节点上(Redis集群的hash_tag功能)
大量读key经典优化:1. 笨办法,n次get(n次网络传输,n次命令执行); 2.1次pipeline(1次网络传输,n次命令执行); 3.1次mget(1次网络传输,1次命令执行); 4.1次pipeline包含m次get((1次网络传输,m次命令执行)
雪崩:缓存一挂,DB立马也会撑不住了,雪崩效应;解决方案:1.缓存架构成高可用性,减少崩溃可能性;2.服务降级,一旦崩了,就临时返回一些凑合的数据糊弄一下用户,也比系统崩溃要好;3.提前演习,先人为的崩一次试试能否扛得住(tang雷测试)
"缓存+过期时间"方式的危险:某个key是热点key并发量非常大&&该key的重建缓存耗时较长,则过期后的短时间内会有大量请求miss这个key,然后他们都去DB里读取数据并试图加载至缓存,造成DB压力骤然提升,容易崩溃;解决方式:1.使用互斥锁,miss之后去抢占锁,加锁的才允许访问DB重建缓存,抢不到锁的都去重试读缓存;2.永不过期:不给Redis的key们设置TTL,应用程序自己起一个线程去定期更新所有的key(需要自己实现,且定期周期太长会增大数据不一致的可能)
Redis和Linux内存机制:
overcommit_memory机制: cat /proc/sys/vm/overcommit_memory 如果是0,表示新fork子进程的时候如果内存(swap+物理内存)不够,就创建失败;如果是1,则允许创建子进程(子进程虽然和父进程占用内存一样多,但是copy-on-write,都是和父进程复用的,新增内存很少),跑着跑着不够用了再说;Redis在RDB时创建子进程,如果不允许overcommit_memory,则会备份失败.
最好要设置Redis的maxmemory,使其不要影响整个机器其他进程的运行(给别人留点儿地儿);
任何一个进程使用内存超了使Linux没可用内存了,Linux会触发OOM Killer机制;挑选/proc/PID/oom_score_adj里数字高的去优先kill -9掉。别的进程用超了内存,或者Redis自己用超了内存,都会导致Redis或者其他进程有被杀掉的可能!
Linux的swappiness: cat /proc/sys/vm/swappiness 显示Linux是否同意Swap。0表示启用OOM killer不启用Swap; 1表示优先启用Swap实在不行再OOM killer;
好例子:生产环境一次Redis导致OOM Killer的问题
安全性:
1. 设密码; 2.伪装危险命令(rename-command); 3.使用防火墙限制外网IP和端口;4.定期备份大法;5.不使用默认端口6379;6.使用非root用户启动