RDB和AOF机制
RDB:Redis DataBase
在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
优点:
1、整个Redis数据库将只包含一个文件dump.rdb,方便持久化。
2、容灾性好,方便备份。
3、性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 10 操作,保证了 redis的高性能
4、相对于数据集大时,比AOF的启动效率更高。
缺点:
- 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这
种方式更适合数据要求不严谨的时候) - 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务
器停止服务几百毫秒,甚至是1秒钟。
AOF: Append Only File
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录
优点:
1、数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成
的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同
步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。
2、通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 redis-check-aof工具
解决数据一致性问题。
3、AOF 机制的 rewrite 模式。定期对AOF文件进行重写,以达到压缩的目的
缺点:
1、AOF文件比RDB文件大,且恢复速度慢。
2、数据集大的时候,比 rdb 启动效率低。
3、运行效率没有RDB高。
AOF文件比RDB更新频率高,优先使用AOF还原数据。
Redis的过期键的删除策略
Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
Redis线程模型、单线程快的原因
Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器 fille event handler。这个文件事件处理器,它是单线程的,所以Redis 才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了 Redis 内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。多个Socket可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,会将Socket放入一个队列中排队,每次从队列中取出一个Socket 给事件分派器,事件分派器把 Socket给对应的事件处理器。
然后一个Socket的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket 给事件分派器。文件事件分派器会根据每个Socket当前产生的事件,来选择对应的事件处理器来处理。
单线程快的原因:
1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题
缓存雪崩、缓存穿透、缓存穿击
redis集群方案
主从
哨兵模式:
sentinel,哨兵是 redis 集群中非常重要的一个组件,主要有以下功能:
- 集群监控;负责监控 redis master和 slave 进程是否正常工作。
- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会自动转移到slave node 上。
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
- 故障转移时,判断一个master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
- 哨兵通常需要 3个实例,来保证自己的健壮性。
- 哨兵+redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
- 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
redis 主从复制的核心原理
通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
全量夏制:
(1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的
(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
(3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行
bgrewriteaof,也会带来额外的消耗
部分复制:
1.复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
- 复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
- 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。从节点Redis断开重连的时候,就是根据运行ID来
判断同步的进度:
- 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
- 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。
CAP理论,BASE理论
Consistency (一致性):
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。
对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。
从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
Availability (可用性):
即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
Partition Tolerance (分区容错性):
即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。
CP和AP:分区容错是必须保证的,当发生网络分区的时候,如果要继续服务,那么强一致性和可用性只能2选1
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)
BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结, 是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
基本可用:
响应时间上的损失:正常情况下,处理用户请求需要0.5s返回结果,但是由于系统出现故障,处理用户请求的时间变为3s。
系统功能上的损失;正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
软状态:数据同步允许一定的延迟
最终一致性:系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要求实时
负载均衡算法、类型
1、轮询法
将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。
2、随机法
通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,
其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
3、源地址哈希法
源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
4、加权轮询法
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。5、加权随机法
与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重,不同的是,它是按照权重随机请求后端服务器,而非顺序。
6、最小连接数法
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前
积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。
分布式架构下,Session 共享有什么方案
1、采用无状态服务,抛弃session
2、存入cookie(有安全风险)、
3、服务器之间进行Session 同步,这样可以保证每个服务器上都有全部的 Session 信息,不过当服务器数量比较多的时候,同步是会有延迟甚至同步失败;
4、IP 绑定策略
使用Nginx(或其他复杂均衡软硬件)中的IP绑定策略,同一个IP只能在指定的同一个机器访问,但是这样做失去了负载均衡的意义,当挂掉一台服务器的时候,会影响一批用户的使用,风险很大;
5、使用 Redis 存储
把 Session 放到Redis 中存储,虽然架构上变得复杂,并且需要多访问一次 Redis,但是这种方案带来的好处也是很大的:
- 实现了 Session 共享;
- 可以水平扩展(增加 Redis 服务器);
- 服务器重后 Session不丢失(不过也要注意Session在Redis 中的刷新/失效机制);
- 不仅可以跨服务器Session 共享,甚至可以跨平台(例如网页端和APP端)
简述你对RPC、RMI的理解
RPC: 在本地调用远程的函数,远程过程调用,可以跨语言实现 httpClient
RMI:远程方法调用,java中用于实现RPC的一种机制,RPC的java版本,是J2EE的网络调用机制,跨JVM调用对象的方法,面向对象的思维方式
分布式锁解决方案
需要这个锁独立于每一个服务之外,而不是在服务里面。
数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
Zookeeper分布式锁:
zk通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后实然挂掉(Session连接断开),那么这个临时节点就会自动删除掉,其他客户端自动获取锁。临时顺序节点解决惊群效应
Redis分布式锁:setNX,单线程处理网络请求,不需要考虑并发安全性
所有服务节点设置相同的key,返回为0. 则锁获取失败
setnx
问题:
1、早期版本没有超时参数,需要单独设置,存在死锁问题(中途宕机)
2、后期版本提供加锁与设置时间原子操作,但是存在任务超时,锁自动释放,导致并发问题,加锁与释放锁不是同一线程问题
删除锁:判断线程唯一标志,再删除
可重入性及锁续期没有实现,通过redisson解决(类似AQS的实现,看门狗监听机制)
redlock:意思的机制都只操作单节点、即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况(redis同步设置可能数据丢失)。redlock从多个节点申请锁,当一半以上节点获取成功,锁才算获取成功,redission有相应的实现
分布式事务解决方案
XA规范:分布式事务规范,定义了分布式事务模型
四个角色:事务管理器(协调者TM)、资源管理器(参与者RM),应用程序AP,通信资源管理器CRM
全局事务:一个横跨多个数据库的事务,要么全部提交、要么全部回滚
JTA事务时java对XA规范的实现,对应JDBC的单库事务
两阶段协议:
第一阶段(prepare):每个参与者执行本地事务但不提交,进入 ready 状态,并通知协调者已经准备就绪。
第二阶段(commit) 当协调者确认每个参与者都 ready 后,通知参与者进行 commit 操作;如果有参与者 fail则发送rollback 命令,各参与者做回滚。
问题:
- 单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住)
- 数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
- 响应时间较长:参与者和协调者资源都被锁住,提交或者回滚之后才能释放
- 不确定性:当协事务管理器发送commit之后,并且此时只有一个参与者收到了commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
三阶段协议:主要是针对两阶段的优化,解决2PC单点故障的问题,但是性能问题和不一致问题仍然没有根本解决
阶段一:发送CanCommit消息,确认数据库环境正常
阶段二:发送PreCommit消息,完成Sqh语句的操作,但未提交事务
阶段三:发送DoCommit消息,通知所有库提交事务/回滚事务
引入了超时机制解决参与者阻塞的问题,超时后本地提交,2pc只有协调者有超时机制
- 第一阶段:CanCommit阶段,协调者询问事务参与者,是否有能力完成此次事务。
。如果都返回yes,则进入第二阶段
。有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求 - 第二阶段:PreCommit阶段,此时协调者会向所有的参与者发送PreCommit请求,参与者收到后开始执行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
- 第三阶段:DoCommit阶段,在阶段二中如果所有的参与者节点都返回了Ack,那么协调者就会从“预提交状态 转变为“提交状态”。然后向所有的参与者节点发送”doCommit”请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“ACK”消息,协调者收到所有参与者的Ack消息后完成事务。相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
TCC(补偿事务):Try、Confirm、Cancel
针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作
Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作既回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。
TCC模型对业务的侵入性较强,改造的难度较大,每个操作都需要有 try、confirm、cance1三个接口实现
confirm 和 cancel 接口还必须实现幂等性。
消息队列的事务消息:
- 发送prepare消息到消息中间件
- 发送成功后,执行本地事务
。如果事务执行成功,则commit,消息中间件将消息下发至消费端(commit前,消息不会被消费)
。如果事务执行失败,则回滚,消息中间件将这条prepare消息删除 - 消费端接收到消息进行消费,如果消费失败,则不断重试
如何实现接口的幂等性
唯一id。每次操作,都根据操作和内容生成唯一的id,在执行之前先判断id是否存在,如果不存在则执行后续操作,并且保存到数据库或者redis等。
服务端提供发送token的接口,业务调用接口前先获取token,然后调用业务接口请求时,把token携带过去,务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务完成后,最后需要把redis中的token删除
建去重表。将业务中有唯一标识的字段保存到去重表,如果表中存在,则表示已经处理过了
版本控制。增加版本号,当版本号符合时,才能更新数据
状态控制。例如订单有状态已支付 未支付 支付中 支付失败,当处于未支付的时候才允许修改为支付中等
简述ZAB协议
ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议,实现分布式数据一致性
所有客户端的请求都是写入到 Leader 进程中,然后,由 Leader 同步到其他节点,称为 Follower。在集群数据同步的过程中,如果出现 Follower 节点崩溃或者 Leader 进程崩溃时,都会通过 Zab 协议来保证数据一致性
ZAB 协议包括两种基本的模式:崩溃恢复和消息广播。
消息广播:
集群中所有的事务请求都由 Leader 节点来处理,其他服务器为 Follower,Leader将客户端的事务请求转换为事务Proposal,并且将Proposal分发给集群中其他所有的Follower。
完成广播之后,Leader等待Follwer反馈,当有过半数的 Follower 反馈信息后,Leader将再次向集群内Follower 广播 Commit 信息,Commit 信息就是确认将之前的 Proposal 提交。
Leader 节点的写入是一个两步操作,第一步是广播事务操作,第二步是广播提交操作,其中过半数指的是反馈的节点数 >=N/2+1,N 是全部的 Follower 节点数量。
崩溃恢复:
初始化集群,刚刚启动的时候
Leader 崩溃,因为故障宕机
Leader 失去了半数的机器支持,与集群中超过一半的节点断连
此时开启新一轮Leader 选举,选举产生的 Leader 会与过半的 Follower 进行同步,使数据一致,当与过半的机器同步完成后,就退出恢复模式,然后进入消息广播模式
整个 ZooKeeper 集群的一致性保证就是在上面两个状态之前切换,当 Leader 服务正常时,就是正常的消息广播模式;当Leader不可用时,则进入崩溃恢复模式,崩溃恢复阶段会进行数据同步,完成以后,重新进入消息广播阶段。
Zxid 是 Zab 协议的一个事务编号,Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增计数器,针对客户端每一个事务请求,计数器加1;而高32位则代表Leader 周期年代的编号。
Leader 周期(epoch),可以理解为当前集群所处的年代或者周期,每当有一个新的 Leader 选举出现时,就会从这个Leader 服务器上取出具本地日志中最大事务的 Zxid,并从中读取 epoch 值,然后加 1,以此作为新的周期ID。高32位代表了每代Leader的唯一性,低32位则代表了每代Leader中事务的唯一性。
zab节点的三种状态:
following:服从leader的命令
leading:负责协调事务
election/looking:选举状态
zk的数据模型和节点类型
数据模型:树形结构
zk维护的数据主要有:客户端的会话(session)状态及数据节点(dataNode)信息。
zk在内存中构造了个DataTree的数据结构,维护若path到dataNode的映射以及dataNode间的树状层级关系。为了提高读取性能,集群中每个服务节点都是将数据全量存储在内存中。所以,zk最适于读多写少且轻量级数据的应用场景。
数据仅存储在内存是很不安全的,zk采用事务日志文件及快照文件的方案来落盘数据,保障数据在不丢失的情况下能快速恢复。
树中的每个节点被称为一 Znode
Znode 兼具文件和目录两种特点。可以做路径标识,也可以存储数据,并可以具有子 Znode。具有增、删、改、查等操作。
Znode 具有原子性操作,读操作将获取与节点相关的所有数据,写操作也将 替换掉节点的所有数据。另外,每个节点都拥有自己的 ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作
Znode存储数据大小有限制。每个 Znode的数据大小至多1M,常规使用中应该远小于此值。
Znode 通过路径引用,如同 Unix 中的文件路径。路径必须是绝对的,因此他们必须由斜杠字符来开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。在 ZooKeeper 中,路径由Unicode 字符串组成,并且有一些限制。字符串”/zookeeper”用以保存管理信息,比如关键配额信息。
持久节点:一旦创建、该数据节点会一直存储在zk服务器上、即使创建该节点的客户端与服务端的会话关闭了、该节点也不会被删除
临时节点:当创建该节点的客户端会话因超时或发生异常而关闭时、该节点也相应的在zk上被删除。
有序节点:不是一种单独种类的节点、而是在持久节点和临时节点的基础上、增加了一个节点有序的性质。
简述zk的命名服务、配置管理、集群管理
命名服务:
通过指定的名字来获取资源或者服务地址。Zookeeper可以创建一个全局唯一的路径,这个路径就可以作为一个名字。被命名的实体可以是集群中的机器,服务的地址,或者是远程的对象等。一些分布式服务框架(RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据特定的名字来获取资源的实体、服务地址和提供者信息等
配置管理:
实际项目开发中,经常使用.properties或者xml需要配置很多信息,如数据库连接信息、fps地址端口等等。程序分布式部署时,如果把程序的这些配置信息保存在zk的znode节点下,当你要修改配置,即znode会发生变化时,可以通过改变zk中某个目录节点的内容,利用watcher通知给各个客户端,从而更改配置。
集群管理:
集群管理包括集群监控和集群控制,就是监控集群机器状态,剔除机器和加入机器。zookeeper可以方便集群机器的管理,它可以实时监控znode节点的变化,一旦发现有机器挂了,该机器就会与zk断开连接,对应的临时目录节点会被删除,其他所有机器都收到通知。新机器加入也是类似。
讲下Zookeeper watch机制
客户端,可以通过在znode上设置watch,实现实时监听znode的变化
Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端
- 父节点的创建,修改,删除都会触发Watcher事件。
- 子节点的创建,删除会触发Watcher事件。
一次性:一旦被触发就会移除,再次使用需要重新注册,因为每次变动都需要通知所有客户端,一次性可以减轻压力,3.6.0默认持久递归,可以触发多次
轻量:只通知发生了事件,不会告知事件内容,减轻服务器和带宽压力
Watcher 机制包括三个角色:客户端线程、客户端的 WatchManager 以及 ZooKeeper 服务器
客户端向ZooKeeper服务器注册一个Watcher 监听,
把这个监听信息存储到客户端的WatchManager中
当ZooKeeper中的节点发生变化时,会通知客户端,客户端会调用相应Watcher对象中的回调方法。watch回调是串行同步的
zk和eureka的区别
zk: CP设计(强一致性),目标是一个分布式的协调系统,用于进行资源的统一管理。
当节点crash后,需要进行leader的选举,在这个期间内,zk服务是不可用的。
eureka;AP设计(高可用),目标是一个服务注册发现系统,专门用于微服务的服务发现注册。
Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时如果发现连接失败,会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)
同时当eureka的服务端发现85%以上的服务都没有心跳的话,它就会认为自己的网络出了问题,就不会从服务列表中删除这些失去心跳的服务,同时eureka的客户端也会缓存服务信息。eureka对于服务注册发现来说是非常好的选择。
Spring Cloud和Dubbo的区别
底层协议: springcloud基于http协议,dubbo基于Tcp协议,决定了dubbo的性能相对会比较好
注册中心:Spring Cloud 使用的 eureka , dubbo推荐使用zookeeper
模型定义:dubbo 将一个接口定义为一个服务,SpringCloud 则是将一个应用定义为一个服务
SpringCloud是一个生态,而Dubbo是SpringCloud生态中关于服务调用一种解决方案(服务治理)
什么是Hystrix?简述实现机制
分布式容错框架
- 阻止故障的连锁反应,实现熔断
- 快速失败,实现优雅降级
- 提供实时的监控和告警
资源隔离:线程隔离,信号量隔离
- 线程隔离:Hystrix会给每一个Command分配一个单独的线程池,这样在进行单个服务调用的时候,就可以在独立的线程池里面进行,而不会对其他线程池造成影响
- 信号量隔离:客户端斋向依赖服务发起请求时,首先要获取一个信号量才能真正发起调用,由于信号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进入fallback流程。信号量隔离主要是通过控制并发请求量,防止请求线程大面积阻塞,从而达到限流和防止雪崩的目的。
熔断和降级:调用服务失败后快速失败
熔断是为了防止异常不扩散,保证系统的稳定性
降级:编写好调用失败的补救逻辑,然后对服务直接停止运行,这样这些接口就无法正常调用,但又不至于直接报错,只是服务水平下降
- 通过HystrixCommand或者HystrixObservableCommand 将所有的外部系统(或者称为依赖)包装起来,整个包装对象是单独运行在一个线程之中(这是典型的命令模式)
- 超时请求应该超过你定义的阈值
- 为每个依赖关系维护一个小的线程池(或信号量);如果它变满了,那么依赖关系的请求将立即被拒绝,而不是排队等待。
- 统计成功,失败(由客户端抛出的异常),超时和线程拒绝。
- 打开断路器可以在一段时间内停止对特定服务的所有请求,如果服务的错误百分比通过阈值,手动或自动的关闭断路器。
- 当请求被拒绝、连接超时或者断路器打开,直接执行fallback逻辑。
- 近乎实时监控指标和配置变化。
springcloud核心组件及其作用
Eureka:服务注册与发现
注册:每个服务都向Eureka登记自己提供服务的元数据,包括服务的ip地址、端口号、版本号、通信协议等。eureka将各个服务维护在了一个服务清单中 (双层Map,第一层key是服务名,第二层key是实例名, value是服务地址加端口)。同时对服务维持心跳,剔除不可用的服务,eureka集群各节点相互注册每个实例中都有一样的服务清单。
发现:eureka注册的服务之间调用不需要指定服务地址,而是通过服务名向注册中心咨询,并获取所有服务实例清单(缓存到本地),然后实现服务的请求访问。
Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台(被调用方的服务地址有多个),Ribbon也是通过发起http请求,来进行的调用,只不过是通过调用服务名的地址来实现的。虽然说Ribbon不用去具体请求服务实例的ip地址或域名了,但是每调用一个接口都还要手动去发起Http请求
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求,简化服务间的调用,在Ribbon的基础上进行了进一步的封装。单独抽出了一个组件,就是Spring Cloud Feign。在引入Spring Cloud Feign后,我们只需要创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。
调用远程就像调用本地服务一样
Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务计
商离,通过统计接口超时次数返回默认值,实现服务熔断和降级
Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务,通过与Eureka进行整合,将自身注册为Eureka下的应用,从Eureka下获取所有服务的实例,来进行服务的路由。Zuul还提供了一套过滤器机制,开发者可以自己指定哪些规则的请求需要执行校验逻辑,只有通过校验逻辑的请求才会被路由到具体服务实例上,否则返回错误提示。
Dubbo的整体架构设计及分层
五个角色:
注册中心registry:服务注册与发现
服务提供者provider:暴露服务
服务消费者consumer:调用远程服务
监控中心monitor:统计服务的调用次数和调用时间
容器container:服务允许容器
调用流程:
1:container容器负责启动、加载、运行provider
2:provider在启动时,向regisitry中心注册自己提供的服务
3:consumer在启动时,向regisitry中心订阅自己所需的服务
4:regisitry返回服务提供者列表给consumer,如果有变更,registry将基于长连接推送变更数据给consumer
5:consumer调用provider服务,基于负载均衡算法进行调用
6:consumer调用provider的统计,基于短链接定时每分钟一次统计到monitor
分层:
接口服务层(Service):面向开发者,业务代码、接口、实现等
配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心
服务代理层(Proxy):对生产者和消费者、dubbo都会产生一个代理类封装调用细节,业务层对远程调用无感
服务注册层(Registry):封装服务地址的注册和发现,以服务URL为中心
路由层(Cluster): 封装多个提供者的路由和负载均衡, 并桥接注册中心
监控层(Monitor): RPC 调用次数和调用时间监控
远程调用层(Protocal):封装 RPC 调用
信息交换层(Exchange): 封装请求响应模式,同步转异步
网络传输层(Transport):抽象 mina 和 netty 为统一接口,统一网络传输接口
数据序列化层(Serialize): 数据传输的序列化和反序列化
简述RabbitMQ的架构设计
Broker:rabbitmq的服务节点
Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中。生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑定多个队列,由多个消费者来订阅这些队列的方式。
Exchange: 交换器。生产者将消息发送到Exchange ,由交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其它处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪里。
Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ就可以指定如何正确的路由到队列了。
交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多关系表)。在投递消息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相对应的队列。
信道:信道是建立在Copnection 之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,客户端紧接若可以创建一个AMQP信道(Channel),每个信道都会被指派一个唯一的D。RabbitMQ 处理的每条AMQP 指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允许所有的连接通过多条光线束进行传输和接收。
RabbitMQ如何确保消息发送?消息接收?
发送方确认机制:
信道需要设置为 confirm 模式,则所有在信道上发布的消息都会分配一个唯一 ID。
一旦消息被投递到queue(可持久化的消息需要写入磁盘),信道会发送一个确认给生产者(包含消息唯一 ID)。
如果 RabbitMQ 发生内部错误从而导致消息丢失,会发送一条 nack(未确认)消息给生产者。
所有被发送的消息都将被confirm(即 ack)或者被nack 次。但是没有对消息被 confirm 的快侵做任何保证,并且同一条消息不会既被 confirm又被nack
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者,生产者的回调方法会被触发。
ConfirmCallback接口:只确认是否正确到达 Exchange 中,成功到达则回调
Returncallback接口:消息失败返回时回调
接收方确认机制:
消费者在声明队列时,可以指定noAck参数,当noAck=false时。RabbitMQ公等待消费者是式发园ack信号后才从内存(或者磁盘,持久化消息)中移去消息。否则,消息被消费后会被立即删除。
消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
RabbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经新开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很长。保证数据的最终一致性;
如果消费者返回ack之前断开了链接,RabbitMQ 会重新分发给下一个订阅的消费者,(可能存在消息重复消费的隐患,需要去重)
RabbitMQ事务消息
通过对信道的设置实现
channel.txSelect():通知服务器开启事务模式;服务端会返回Tx.Select-Ok
channel.basicPublish;发送消息,可以是多条,可以是消费消息提交ack
channel.txCommit0提交事务;
channel.txRollback()回滚事务;
消费者使用事务:
autoAck=false,手动提交ack,以事务提交或回滚为准;
autoAck=true,
不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消
息移除了
如果具中任意一个环节出现问题,就会抛出loException异常,用户可以拦截异常进行事务回滚,或决定要不要重复消息。
事务消息会降低rabbitmq的性能
RabbitMQ死信队列、延时队列
消息被消费方否定确认,使用 channe1.basicNack或channe1.basicReject,并且此时requeue 属性被设置为false。
消息在队列的存活时间超过设置的TTL时间。
消息队列的消息数量已经超过最大队列长度。
那么该消息将成为”死信”。”死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃
为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后为每个业务队列分配一个单独的路由key,死信队列只不过是绑定在死信交换机上的队列,死信交换机也不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任何类型【Direct、Fanout、Topic】
TTL:一条消息或者该队列中的所有消息的最大存活时间
如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为“死信”。如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。
只需要消费者一直消费死信队列里的消息
简述kafka架构设计
Consumer Group:消费者组,消费者组内每个消费者负责消费不同分区的数据,提高消费能力。逻辑上的一个订阅者。
Topic:可以理解为一个队列,Topic 将消息分类,生产者和消费者面向的是同一个 Topic。
Partition:为了实现扩展性,提高并发能力,一个Topic以多个Partition的方式分布到多个Broker上,每个Partition 是一个有序的队列。一个 Topic 的每个Partition都有若干个副本(Replica)
,一个Leader和若干个Follower。生产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。Follower负责实时从 Leader中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,某个 Follower 还会成为新的 Leader。
offset:消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。
Zookeeper: Kafka集群能够正常工作,需要依赖于Zookeeper,Zookeeper帮助Kafka存储和管理集群信息。
Kafka在什么情况下会出现消息丢失及解决方案?
- 消息发送
1、ack=0,不重试
producer发送消息完,不管结果了,如果发送失败也就丢失了。2、ack=1, 1eader crash
producer发送消息完,只等待1ead写入成功就返回了,1eader crash了,这时follower没来及同步,消息丢失。3、unclean.1eader.election.enable 配置true
允许选举ISR以外的副本作为1eader,会导致数据丢失,默认为false。producer发送异步消息完,只等待1ead写入成功就返回了,1eader crash了,这时ISR中没有follower,1eader从0SR中选举,因为OSR中本来落后于Leader造成消息丢失。解决方案;
1、配置:ack=a11 / -1,tries > 1,unclean.1eader.election.enable : false
producer发送消息完,等待follower同步完再返回,如果异常则重试。副本的数量可能影响吞吐量。不允许选举ISR以外的副本作为1eader。
2、配置:min.insync.replicas > 1
副本指定必须确认写操作成功的最小副本数量。如果不能满足这个最小值,则生产者将引发一个异常(要么是NotEnoughReplicas,要久是NotEnoughReplicasAfterAppend)。min.insync.replicas和ack更大的持久性保证。确保如果大多数副本没有收到写操作,则生产者将引发异常。
3、失败的offset单独记录
producer发送消息,会自动重试,遇到不可恢复异常会抛出,这时可以捕获异常记录到数据库或缓存,进行单独处理。
- 消费
先commit再处理消息。如果在处理消息的时候异常了,但是offset 已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了。
- broker的刷盘
减小刷盘间隔
Kafka是pull?push? 优劣势分析
pull模式:
- 根据consumer的消费能力进行数据拉取,可以控制速率
- 可以批量拉取、也可以单条拉取
- 可以设置不同的提交方式,实现不同的传输语义
缺点:如果kafka没有数据,会导致consumer空循环,消耗资源
解决:通过参数设置,consumer拉取数据为空或者没有达到一定数量时进行阻塞
push模式:不会导致consumer循环等待
缺点:速率固定、忽略了consumer的消费能力,可能导致拒绝服务或者网络拥塞等情况
Kafka中zk的作用
/brokers/ids:临时节点,保存所有broker节点信息,存储broker的物理地址、版本信息、启动时间等,节点名称为brokerID,broker定时发送心跳到zk,如果断开则该brokerID会被删除
/brokers/topics:临时节点,节点保存broker节点下所有的topic信息,每一个topic节点下包含一个固定的partitions节点,partitions的子节点就是topic的分区,每个分区下保存一个state节点、保存着当前leader分区和ISR的brokerID, state节点由leader创建,若leader宕机该节点会被删除,直到有新的leader选举产生、重新生成state节点
/consumers/Igroup_id]/owners/[topic]/[broker_id-partition_id]:维护消费者和分区的注册关系
/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]: 分区消息的消费进度Offset
client通过topic找到topic树下的state节点、获取leader的brokerID,到broker树中找到broker的物理地址,但是client不会直连zk,而是通过配置的broker获取到zk中的信息
Kafka的性能好在什么地方
kafka不基于内存,而是硬盘存储,因此消息堆积能力更强
顺序写:利用磁盘的顺序访问速度可以接近内存,kafka的消息都是append操作,partition是有序的,节省了磁盘的寻道时间,同时通过批量操作、节省写入次数,partition物理上分为多个segment存储,方便删除
传统:
- 读取磁盘文件数据到内核缓冲区
- 将内核缓冲区的数据copy到用户缓冲区
- 将用户缓冲区的数据copy到socket的发送缓冲区
- 将socket发送缓冲区中的数据发送到网卡、进行传输
零拷贝:
- 直接将内核缓冲区的数据发送到网卡传输
- 使用的是操作系统的指令支持
kafka不太依赖jvm,主要理由操作系统的pageCache,如果生产消费速率相当,则直接用pageCache交换数据,不需要经过磁盘IO
简述kafka的rebalance机制
consumer group中的消费者与topic下的partion重新匹配的过程
何时会产生rebalance:
- consumer group中的成员个数发生变化
- consumer消费超时
- group订阅的topic个数发生变化
- group订阅的topic的分区数发生变化
coordinator:通常是partition的leader节点所在的broker,负责监控group中consumer的存活,consumer维持到coordinator的心跳,判断consumer的消费超时
- coordinator通过心跳返回通知consumer进行rebalance
- consumer请求coordinator加入组,coordinator选举产生leader consumer
- leader consumer从coordinator获取所有的consumer,发送syncGroup(分配信息)给到coordinator
- coordinator通过心跳机制将syncGroup下发给consumer
- 完成rebalance
leader consumer监控topic的变化,通知coordinator触发rebalance
如果C1消费消息超时,触发rebalance,重新分配后、该消息会被其他消费者消费,此时C1消费完成提交offset、导致错误
解决:coordinator每次rebalance,会标记一个Generation给到consumer,每次rebalance该Generation会+1,consumer提交offset时,coordinator会比对Generation,不一致则拒绝提交
- 本文作者: GHOSTLaycoo
- 本文链接: http://example.com/2022/02/12/Java高级/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!