分布式
2024-02-29 12:28:02 14 举报
AI智能生成
分布式是一种计算模式,它通过将计算任务分配到多个独立的计算机上进行处理和存储数据,以提高性能、可靠性和可扩展性。这种模式的核心思想是利用多台计算机的闲置资源,实现负载均衡和资源共享,从而降低单个计算机的压力,提高整个系统的效率。分布式系统通常采用模块化的设计,各个模块之间通过网络进行通信和协作,以实现复杂的功能。常见的分布式技术包括分布式文件系统、分布式数据库、分布式缓存等。
作者其他创作
大纲/内容
一致性(C):数据在多个副本中是一致的(强一致性),多个节点读取的都是最新数据注意:要跟数据库事务的一致性区分开
可用性(A):系统提供的服务必须一直处于可用状态,任何请求都能返回响应数据,不出现超时和错误,但不能保证获取的是最新数据
分区容错性(P):部署于不同网络分区下的服务,不可避免会出现由于网络问题而导致的节点通信失败,此时仍可对外提供服务,除非整个网络环境全部故障
未出现网络分区或网络故障时,C和A是能同时满足的
P必须保证,因为分布式系统一般是跨服务器部署的,服务之间依赖于网络通信,而网络故障是无法保证的,所以必须保证在发生网络分区故障时,系统不崩溃
保证CP,当发生网络故障时,服务之间无法通信,数据无法同步,要保证数据一致性,只能放弃操作返回错误或超时,而此时违背了可用性(场景:在涉及资金等严格要求一致性的场景)
保证AP:在网络分区故障时,为了保证可用性,势必不能追求数据的一致性。不过,可以通过其他方案来实现数据的最终一致性(场景:一般场景都选择保证AP+最终一致性)
为什么不能同时满足CAP
CAP理论不能同时满足,一般是满足CP或AP
基本可用:出现不可预知故障时,允许损失部分的可用性。例如:响应时间可以变长,部分非核心业务允许短暂的无法使用
软状态:允许系统中的数据存在中间状态,且不会影响系统的整体可用性。例如:订单的“支付中”状态,待数据最终一致后改为“已支付”状态
最终一致:系统中所有的数据副本,在经过一段时间同步后,最终达到一致
BASE理论由eBay工程师提出的对可用性和一致性的权衡
理论
基于数据库的分布式协议,像Oracle、mysql也实现了XA接口
事务管理器(TM/协调者):作为全局事务的协调管理者,与每个资源管理器通信,完成分布式事务的管理
资源管理器(RM/参与者):资源管理器管理每个参与者的事务资源,其应该具有提交和回滚的能力,如:数据库
定义了两个角色:
XA协议
TM向所有RM发送prepare请求
RM执行成功后,暂不提交事务,向TM发送Done(成功)消息或者Fail(失败)消息
准备阶段
如果所有RM返回成功,TM下发Commit消息,RM提交事务,释放锁住的资源,并向TM发送Ack确认
否则,TM下发Rollback请求,RM利用Undo Logo来执行事务回滚,回滚完成后释放资源,并向TM发送Ack确认,中断事务
提交阶段
同步阻塞:RM都为事务阻塞型,当某个RM出现通信超时,其余RM会被动阻塞占用资源不能释放,而且预先锁定资源对高并发也不友好
单点故障:严重依赖TM,一旦TM发生故障,RM将无法释放资源。虽然TM故障后,会重新选举一个TM,可无法解决因前一个TM宕机导致RM处于阻塞的问题
数据不一致:提交阶段中TM向RM发送commit命令后,一旦发生网络抖动,导致一部分RM收到请求并执行,可其他未收到请求的RM无法执行事务提交。进而导致整个分布式系统出现数据不一致
缺点
2PC
TM向RM发送CanCommit命令,询问是否可以执行事务提交操作
CanCommit
发送预提交请求:TM向所有RM发送PreCommit命令,询问是否可以进行事务的预提交操作
事务预提交:RM收到请求,执行事务操作,并写入Undo Logo和Redo Logo日志 RM收到请求执行操作,然后返回Yes,进入下一个阶段
返回执行结果(成功):如果RM成功执行了事务操作,返回Ack响应,进入下一阶段
发送事务中断请求:如果RM返回了No响应,或者因网络波动造成超时,TM未收到RM的响应,TM会向所有RM发abort请求
事务中断:不管RM是接收abort请求还是因网络超时收不到TM的命令,都会执行事务的中断
PreCommit
TM向RM发送DoCommit命令正式提交事务,RM提交事务后返回Ack响应
TM收到所有RM的响应,完成事务,结束
否则,TM向所有RM发送abort请求,RM收到请求后,根据Undo Logo执行事务回滚,并释放锁定的资源,然后返回Ack响应
TM收到所有响应后,中断事务
DoCommit
3PC在TM和RM中引入了超时机制,在第三阶段中,当RM因各种原因未收到提交事务或回滚事务请求后,会直接提交事务,不会一直阻塞等待(因为能进入第二阶段,代表第一步所有RM都返回了Yes,也代表所有RM是可以正常执行事务的,所以当RM未收到doCommit或者abort请求时,就直接提交事务,避免长时间阻塞)
Try阶段:调用Try接口,尝试执行业务,完成所有业务检查,预留业务资源。例如:下单时通过try操作去扣除库存资源
两者互斥只能进一个,且都需满足幂等性,允许失败重试
Comfirm:对业务做确认提交
Cancel:业务执行错误时,回滚执行业务取消,释放预留资源
失败如何处理:TCC会添加事务日志,如果Confirm或Cancel阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,需要人工介入进行恢复和处理
Confirm阶段或Cancel阶段:
如果服务发生宕机或网络异常,导致未执行try方法,当服务恢复后一般会进入cancel阶段,如果cancel不能处理此情况就会出现空回滚
1.主业务发起事务时,生成全局事务唯一id,再创建一张分支事务表,用于记录分支事务
2.执行try时,将全局事务id和分支事务id存入分支事务表中,表示某个服务执行了try方法
3.当执行cancel时,通过通过表来判断分支事务是否执行,有则回滚,否则不做操作
解决方案:分支事务表(seata的AT模式就使用了分支事务表)
空回滚
如果服务宕机或网络问题导致方法调用超时,为了保证事务正常执行往往需要重试,因此需要保证confirm或cancel操作的幂等性
幂等性
如果执行Try接口时网络波动超时,使得触发cancel接口回滚,在回滚之后,try方法又被执行了,这就导致cancel接口比try接口先执行,从而造成资源一直被锁无法释放
解决方案:与空回滚类似,在try执行时判断是否执行了confirm或cancel
悬挂
TCC又称补偿事务,与2PC思想和处理流程都很相似2PC应用于DB层面,TCC可以理解为应用层面的2PC,需要我们编写业务逻辑实现
Saga是由一系列的本地事务构成每一个本地事务在更新完数据库后,会发布一条消息或一个事件来触发Saga中的下一个本地事务的执行如果一个本地事务因某些原因失败,Saga会执行在这个失败的事务之前成功提交的所有事务的补偿操作
一个事务中,每个服务执行业务后,都发布一个事件。例如:订单服务创建订单后,发布一个订单创建的事件
其他服务监听到某个事件后,就执行业务逻辑,然后再发布一个事件。例如:库存服务监听到订单创建的事件,就执行库存扣减操作,并发布一个事件,让支付服务监听并执行扣款操作
如果某个流程出现错误,就执行预留的失败补偿接口
优点:简单易理解,且完全解耦,适用于步骤较少的事务
缺点:如果涉及调用方过多,就容易失控,甚至最后搞混各个事件和各个服务的逻辑关系,而且容易产生环形监听(也就是两个服务互相监听对方的事件)
基于事件的方法
定义了一个新的服务:协调中心,该服务通过命令/回复的方式来和Saga中其他服务进行交互
避免了服务间的环形依赖
将分布式事务的管理交由协调中心管理,协调中心对整个逻辑非常清楚
减少了服务间的复杂度,各服务不再需要监听不同的消息,只需要响应命令并回复消息
测试和回滚更容易
优点
缺点:需要维护协调中心,而这个协调中心并不属于任何业务方
基于命令的方法
SAGA
核心思路:将分布式事务拆分称本地事务进行处理
1.事务发起方通过本地事务保证业务和消息表同时写入成功
2.事务发起方启动定时任务轮询消息表,通过MQ的发送确认模式,保证消息投递到MQ中(RocketMQ,kafaka等)
3.事务被动方消费消息执行业务,并返回
4.事务主动方更新消息状态为成功
需要做对应的失败重试机制,以及保证幂等性
本地消息表
基于MQ的分布式事务方案其实是对本地消息表的封装,将本地消息表基于MQ内部,其他方面的协议与本地消息表一致
1.发送方向MQ服务端发送half消息,MQ服务端将消息状态标记为Prepared(预备状态),此时订阅方是无法消费该消息的
2.MQ服务端将消息持久化成功后,向发送方发送ack消息表示该消息已成功发送到MQ
3.发送方执行本地事务,并根据结果向MQ服务端提交二次确认(commit或rollback)
4.MQ服务端收到commit状态则将消息标记为可消费,订阅方将正常消费该消息 收到rollback状态则删除该消息,订阅方就不会消费该消息
事务回查:指的是,发送方在执行本地事务超时或断网,MQ服务端未收到二次确认,此时MQ会查询发送方事务执行状态,根据结果来判断是commit或rollback
优点:相较于本地消息表吞吐量更大,且可以降低业务系统与消息系统间的耦合(不需要在业务中手动编写本地消息表相关的逻辑代码)
缺点:一次消息发送需要两次网络请求(half消息 + commit/rollback消息),发送方需要实现消息状态回查接口
使用:需要继承RocketMQLocalTransactionListener并实现executeLocalTransaction(执行本地事务,也就是业务逻辑和事务日志表)和checkLocalTransaction(用于消息回查)两个方式1.调用接口,生产者发送MQ半事务消息(此时消息不会被消费者察觉)2.MQ服务端收到消息并响应生产者,执行executeLocalTransaction方法(本地业务逻辑+事务日志),生产者返回提交或回滚(回滚的话,MQ服务器删除消息就结束)3.生产者返回COMMIT或ROLLBACK,或者网络问题宕机超时等无法发送出消息4.MQ服务端未收到响应,则执行checkLocalTransaction方法(事务回查),并返回COMMIT或ROLLBACK4.COMMIT则标记消息可投递,消费者消费消息(消费者消费消息失败的话,会重试,实现不行则人工介入,所以消费者需要保证幂等性)4.ROLLBACK则流程结束,消费不会被消费者消费,且在一定时间后删除
消息事务(RocketMQ4.3)
发起方通知方通过一定的机制最大努力将业务处理结果通知到接收方
有一定的消息重复通知机制
消息校对机制
适用于业务通知类型的场景,例如:微信支付的结果,通过最大努力通知方式通知给商户,既有回调通知,也有交易查询接口
最大努力通知方案
最终一致性(柔性事务)
不过3PC还是没能从根本上解决数据一致性的问题
多一次网络开销
与2CP比较
3PC
强一致性(刚性事务)
解决方案
事务协调者/TC:维护全局和分支事务的状态,驱动全局事务提交和回滚
事务管理器/TM:定义全局事务的范围,开始全局事务、提交或回滚全局事务
资源管理器/RM:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
角色
Seata客户端拦截业务SQL语句,解析语义
向Seata服务端(也就是TC)申请全局事务id(XID)
查询数据,并生成数据的前镜像(before image)
更新数据,并生成数据的后镜像(after image)
保存到undo_log表中,并提交本地事务
阶段一
全局事务提交,删除undo_log
全局事务回滚,先根据后镜像来校验脏写,出现脏写需要人工补偿,通过前镜像和undo_log反向生成SQL语句,恢复数据,删除前后镜像
阶段二
AT:最终一致优点:无侵入,开发便利,seata默认模式缺点:锁竞争(全局事务提交前,会锁资源),事务悬挂
申请全局事务id
调用各服务的try操作,预留或锁定相关资源,并记录本地事务日志
阶段一(try)
全局事务提交,释放资源,并删除本地事务日志
全局事务回滚,释放资源,并删除本地事务日志
阶段二(commit或cancel)
TCC:最终一致优点:高灵活性,无锁竞争缺点:业务入侵(手动实现try,confirm,cancel逻辑)
根据状态机的定义,依次调用各个子事务的正向操作,执行业务逻辑,并记录本地事务日志
返回子事务的正向操作的状态(成功或失败)
全局事务提交,删除本地事务日志,结束分支事务
全局事务回滚,根据本地事务日志,从后往前依次调用各个子事务的反向操作,补偿业务逻辑,并删除本地事务日志
将一个长时间运行的事务拆分为多个子事务,每个子事务可以独立的提交或回滚,从而避免长时间占用资源
通过状态机编排子事务的执行顺序和逻辑,每个子事务都有正向操作和反向操作,正向操作用于执行业务逻辑,反向操作用于补偿业务逻辑
核心思想
SAGA:长事务模式优点:异步协调,容错性缺点:一致性问题,业务入侵
TM向所有RM发送start命令,要求他们开始分支事务
RM执行各自业务,并锁定相关资源
TM向RM发送end命令,要求他们结束当前的分支事务
RM结束分支事务,并向TM汇报状态:就绪状态(prepared)或失败状态(abort)
RM全部就绪,TM下发commit命令,RM提交分支事务,并释放资源
出现问题,TM下发rollback命令,RM回滚分支事务,并释放资源
XA:强一致性缺点:性能较差,资源占用
模式
Seata
分布式事务
通过在数据库表新建记录的方式来表示资源是否被占用,例如:集群下通过往表中新增任务id记录来判断哪台机器抢到了锁
1.数据表(锁表,很少用)
增加版本号version字段,每次更新时+1,通过where version=#{version}判断version是否被其他线程修改
2.乐观锁(基于版本号)
select...for update
3.悲观锁(基于排它锁)
缺陷:对数据库依赖,开销问题,行锁变表锁问题,无法解决数据库单点和可重入的问题
基于数据库
1.单节点:set NX PX + Lua。使用setnx命令+过期时间+重试机制来加锁,使用lua脚本解锁
获取时间戳
使用key和value依次尝试从N个主节点(master)获取锁(注意:设置锁获取时间<<锁过期时间,避免redis挂掉的情况下,客户端等待过长时间)
获取所有锁的时间,要小于TTL时间,并且要有一半以上(n/2 + 1)节点加锁成功,才算真正获取锁成功
如果获取锁失败,则依次去各个节点解锁
2.多节点:RedLock
所有指令都通过lua脚本执行,保证了原子性
设置了watch dog看门狗,保证不发生死锁
支持redlock的实现方式
3.redisson
设置锁失效时间,finlly里释放锁
1.锁未被释放
setnx加锁时,设置唯一的value,释放锁时,通过value检查是否为当前线程持有锁,然后释放
2.B线程的锁被A线程释放
如果获取锁时间>数据库事务超时时间,就会异常,可以将数据库事务改为手动提交、回滚
3.数据库事务超时
使用redisson,它有个看门狗机制,会启动一个线程定时监听锁,如果还持有锁则续期
4.锁失效时间到了,业务还未执行完
问题详述:在集群环境下,客户端会根据路由规则选择一台主节点setnx加锁,加锁成功后会把key异步复制到从节点,若此时主节点宕机,为保证集群可用性,会将一台从节点切换为主节点,此时B在新的主节点上加锁成功,而A也认为自己是成功加锁了,此时就会导致在同一时间点,多个客户端都对一个key加了锁
解决方案:应该还没有完美的解决方案,不过可以使用RedLock,它会依次去所有主节点尝试加锁,当在一半以上(n/2 + 1)主节点都加锁成功后,才会被判定为加锁成功,否则就会依次释放自己加的锁redisson也提供了redLock的实现方式
5.主从复制的问题
常见问题
基于redis
线程加锁时,都会去key节点下创建临时顺序节点,该节点会有一个序号
临时顺序节点
序号最小可以判断为当前线程抢占锁成功,其他可以排队等待
序号最小者获得锁
每个节点都会监听前一个节点的通知,当前一个节点删除时,就可以判定为当前节点获得锁,依次下去
节点监听机制
假设网络异常,占有锁的线程与zookeeper断开链接,zookeeper会将这个临时节点删除,下一个节点将占有锁,避免资源被长久锁定
监听机制能保证上一个节点释放锁后,下一个节点占有锁,不会发生羊群效应(也就是全部去抢锁)
基于zookeeper公平锁
分布式锁
百万级,功能较少,理论上不丢消息
kafaka
邮局(NameServer):消息队列的协调者,Broker向其注册,生产者和消费者获取Broker
邮递员(Broker):核心,负责消息的接收,存储,发放等功能
生产者(Producer):从NameServer获取Broker,并与Broker建立链接,向Broker发送消息
消费者(Consumer):从NameServer获取Broker,建立链接,从Broker获取消息
主题/地区(Topic):用于区分不同类型的消息,需要先创建Topic,然后针对Topic来发送和接收消息
Message Queue:一个Topic可以设置多个队列,这样就可以并行的发送和消费消息
Message:消息
生产者组:将发送同一类的消息的生产者分成一个组
消费者组:将消费同一类消息的消费者组成一个组
其他概念
可靠同步发送:消息发送后,会在接收者回应后再发下一个消息
可靠异步发送:消息发送后,不等回应继续发送下一个消息,通过回调接口来接收响应,并进行处理(异步发送一般用于链路耗时较长,对响应时间比较敏感的业务)
单向发送:只发送消息,不需要响应(适用于对可靠性要求不高的场景,例如:日志)
普通消息
顺序消息
可用于做分布式事务
事务消息
生产者发送一个延时消息,broker将延时消息根据延时等级放入内置topic和延时队列中,开启定时任务,将到期的消息拿出来放到原来的topic中,消费者就可正常消费消息了,但是延时不一定精准,所以还提供了基于时间轮算法的定时消息,延迟到秒级
定时/延时消息
消息类型
广播消费:每个消费者实例都会收到消息集群消费:一条消息只会被一个消费者消费
十万级,理论上不丢消息
rocketmq
万级,消息丢失率低
activemq
万级,延时低(微秒级),消息丢失率低
rabbitmq
rocketmq:发送阶段(同步发送 + 失败重试 + 多主服务) 消息存储阶段(同步刷盘)消费阶段(消费失败可以重试)
如何保证消息不丢失?
消费端根据字段做幂等,并且可以使用分布式锁的解决方案来保证不会同时消费两条消息
如何保证消息不重复消费?
rocketmq使用的是先进先出的队列(FIFO),一个topic下有多个队列,一般会轮询将消息放入各队列保证更高效率,我们可以将消息放到一个队列来保证消息的顺序消费
如何保证顺序消费?
rocketmq:如果topic配置了多个消息队列的话(最大4个),可以增加消费者服务,来并行消费。 如果未配置多个消息队列,那么新建一个消费者,专门用于消费消息,并转发到新的topic上,在新topic上设置多个消息队列,再通过上一个方案来加速消费
消息积压怎么办?
相关问题
消息中间件
指的是大量数据在同一时间过期,导致大量请求访问数据库
可以为缓存设置随机过期时间,或者后台定时刷新缓存
缓存雪崩
指的是查询一个不存在的数据,导致请求直接到数据库
可以为不存在的数据缓存空值,或者使用布隆过滤器
缓存穿透
热点数据过期时,大量请求直接访问数据库
热点数据不设置过期时间
缓存击穿
失效场景
动态字符串;动态分配内存防止缓冲区溢出;保存字符长度,防止\\0异常结束;优化内存大小(内置五种类型:sds5等,取消编译字节对齐)
SDS(String)
O(1)访问头尾及前后节点;内存不连续,无法很好利用CPU缓存
双向链表(List)旧版本
存储在连续的内存空间(类似于数组);O(1)访问头尾entry;每个entry会用1或5字节存储上一个entry的长度,所以极端情况下entry长度的更改会引发连锁更新
ziplist/压缩列表(List、Hash、Zset)已废弃
双向链表+压缩列表;用于减小压缩列表太大的连锁更新问题;新增元素时先判断链表节点对应的压缩列表是否能存下,能则直接存,否则新增节点再放入该节点下的压缩列表中
quicklist(List)
用于替代压缩列表;entry只记录自身长度,不记录上一个元素长度
listpack
数组+链表(与hashmap类似);两个hash表用于数据扩容(2倍扩容)时互相迁移数据;渐进式rehash,每次写入时将本应该放入位置的链表数据迁移至扩容表
rehash条件:负载因子(节点数量/表大小),如果大于等于1且没有在执行RDB快照和AOF文件写入时,或者大于5会立即执行
hash表(hash,Zset)
连续内存空间;
整数集合(Set)
O(logn)访问复杂度,极端情况O(n);根据分值排序;通过随机数决定节点层级,通过节点维护多个指向其他节点的指针,来达到跳跃访问的目的
新增节点时,会随机0-1的数,如果小于0.25就层级+1,直到大于0.25,则确定该节点拥有的层级,通过层级以及指针将一条链表变成多条链表,1层是最底层链表,能访问所有数据,2层则只能访问到层级为2的数据,往上依次减少。访问时,从顶层向下查找
相比二叉树和红黑树:实现简单,能排序,能范围查找,且变更时不需要对子节点进行各种操作
跳表(Zset)
数据结构
客户端发送写操作命令时,先执行命令,再将命令追加写入日志文件。重启redis时,先读取AOF文件的命令,然后执行它,来恢复缓存数据
简述:
在redis.conf中配置:appendonly yes(表示开启AOF持久化),appendfilename \"test.aof\"(AOF持久化文件的名称)
使用:
1.日志丢失(执行写命令后,还未将命令写入硬盘就宕机) 2.阻塞(执行写命令和记录AOF都是主线程,所以下一个命令需等待该命令的AOF成功后才能执行)(redis提供了三种写回硬盘的策略,来让用户选择权衡风险)
风险:
1.执行写命令 -> 2.命令追加到server.aof_buf缓存区 -> 3.I/O 系统调用write() -> 4.拷贝数据到内核缓存区 -> 5.内核发起写操作(fsync()函数) -> 6.写入硬盘
流程:
Always:执行命令后,同步将命令写入硬盘(最大程度保证数据不丢失,但可能影响性能)
Everysec:执行命令后,先将命令写入内核缓冲区,然后开启线程每隔一秒将缓冲区的内容写回硬盘(折中方案,可能丢失一秒内的数据,也避免了Always的性能)
No:将命令写入内核缓冲区后,由操作系统决定何时将缓冲区内容写入硬盘(避免了Always的性能开销,但丢失数据风险更大,毕竟操作系统写入硬盘的时机是不可预知的)
写回策略:
当AOF日志超过阈值大小时,redis重启时加载日志缓存会越来越耗时,这时会启动新线程将AOF日志重写到新文件。
重写时只会将key的最新命令写入新文件,以此来压缩文件大小(例如:对key为name的值进行了多次修改,那么前几次的修改命令其实对现有数据是无用过时的,只需要记录最后一次对name的修改命令即可)
重写机制
持久化机制(AOF)
记录某个瞬间的全部数据(存储的是全量数据,而不是命令,所以恢复数据比AOF快,但是持久化过程会比较慢,一般会配置较长时间来持久化一次,所以丢失数据风险相对较大)
有两种持久化方式:1.主线程(save命令,在主线程写文件,阻塞时间长) 2.子进程(bgsave,创建子进程来写文件,避免阻塞主线程)
持久化时,如果碰到写命令,就会将内存数据复制一份副本,在新副本修改,而RDB存储的是旧数据,修改的数据得下一次持久化
redis4.0后,支持AOF+RDB组合来进行持久化(也就是先将数据写入日志,然后再将命令写入日志,这样既减少了数据丢失的风险,也能快速加载数据)
持久化机制(RDB快照)
创建定时事件删除key,key可以最快被删除,对内存友好,但是设置了过期时间的key过多时,会消耗大量CPU去执行定时事件
定时删除
不主动删除过期key,等下次访问key时再来判断是否过期删除,对CPU友好,但是可能会导致大量的过期key占用在内存
惰性删除
定期去随机检查一批key,删除其中的过期key,算一种折中方案,但是需控制时间和频率
定期删除
redis采用惰性删除+定期删除策略
过期删除策略(过期时间key)
传统LRU:额外使用链表管理数据,按照访问排序,每次访问节点都将其移至头节点,需要淘汰时,直接删除尾部节点Redis:记录访问时间,需要淘汰时,随机抽取并删除时间最久的数据。节省了内存,节省了链表移动耗时缓存污染:对于大量只缓存一次的数据,可能无法即时删除
LRU算法
记录访问频率和访问次数
LFU算法
内存淘汰策略(超过内存)
清理
主服务器提供读写功能,从服务器只提供读功能,往主服务器写数据时,通过网络同步给从服务器(一个主服务器可以有多个从服务器)
优点:1.读写分离提高性能和可靠性 2.从服务器可互相连接和同步,降低主服务器的同步压力 3.主/从服务器同步是非阻塞的,同步时依然可以处理请求
缺点:1.不具备容错和恢复功能,主/从机宕机会导致部分请求失败 2.主机宕机时,如果数据未同步到从机,切换新主机会导致数据不一致 3 多个从机断线重启的话,都会对主机发起全量同步,导致主机IO剧增,严重可导致主机宕机
同步命令:sync命令(2.8之前,全量同步),psync(2.8后,支持全量/部分同步),避免短时间断线也全量同步导致的开销
同步原理:从服务器发送同步命令,并带上偏移量,主服务器响应后,开始写入RDB快照,并将数据同步到从服务器,同步过程中的写命令会存入环形缓冲区,然后再同步到从服务器执行来保证数据一致,主从断开重连后,也是从缓冲区增量同步。(环形缓冲区满了会覆盖,所以要根据情况预估分配大一些的空间)
主从复制/读写分离
开启一个独立的进程(哨兵),用于监控(监控主从节点状态PING/PONG)、选主(主机下线,主从切换)、通知(发布/订阅通知新主节点)
\"君主式哨兵\",只需单个哨兵对服务器做出下线判断,哨兵发送PING,若服务器未在指定时间(master-down-after-milliseconds配置项)内回复,则会被哨兵标记为主观下线
主观下线
\"民主式哨兵\",多个哨兵来评判主机是否下线,哨兵会向其他哨兵发送主机的下线报告,当传播范围足够时,就会将主机从主观下线切换为客观下线,当然如果没有足够的哨兵同意主机下线,那么主机的客观下线状态会被移除,如果主机对哨兵的PING做出回复,那么主机的主观下线也会被移除(从服务器不需要多个哨兵协商,可以由单个哨兵直接对其下线)
客观下线
哨兵如何判断主机是否下线?
先过滤掉下线和网络不好的从节点,然后最多经过三轮筛选出一台从机:1.优先级(从机启动时配置的)2.复制进度(从机同步主机的偏移量)3.从机id最小。集群哨兵的话会选举leader来进行主从服务器的切换(选举就是投票,每台哨兵投票,发现主机故障的哨兵会优先投给自己,票数大于一半的哨兵为leader)
主从切换原理
哨兵通过发布/订阅,能够从主机那里获取从机及其他哨兵的信息,并互相建立连接,在主从切换时,也可以通过发布/订阅,告诉从机主服务切换了
发布/订阅
优点:基于主从复制,拥有主从复制的所有优点,并且可以自动切换主服务器(没有哨兵需要手动来切换主服务器)
缺点:所有数据都存储在一个主节点上,一个主节点对于写功能的性能有限
哨兵机制(Sentinel模式)
会将16384个哈希槽分配给集群中的主机,每个key会通过CRC1校验来决定放置在哪个槽(也就是存到哪个主机),相当于分库分表,将数据均匀分布到不同的主机上
如果半数以上(n/2 + 1,所以主机一般是单数,便于投票)的主节点与另一个主节点通信超时,那么就认为该主节点宕机
如果想移除某个主机,可以先将分配给该主机的哈希槽分配给其他主机,然后就可以移除该主机了(从机可以直接移除)
优点:拥有哨兵机制的所有优点,并且提供了数据分片功能(数据分布在多个主机),由于拥有多个主机,所以大大提高了写功能
内置集群(Cluster模式)
集群方案
redis
一般使用延时双删,先删除缓存,再更新数据库,然后sleep一段时间后再删除缓存。以此来解决缓存的最终一致性。不过信灵项目不同,仅用户和部分等模块查询过多,所以项目启动时就可将数据加入缓存,少部分更新时直接更新缓存即可
如何解决缓存一致性
分布式缓存
分布式
0 条评论
回复 删除
下一页