* 1、SpringCloud有哪些常用组件?分别是什么作用?
答:
Nacos, OpenFeign, Sentinel,Seata,RabbitMQ,Gateway
Nacos: 服务注册中心,提供服务注册和发现功能
OpenFeign: 实现远程调用
Sentinel: 提供服务容错保护
Seata: 实现分布式事务管理
RabbitMQ: 实现异步通知
Gateway:(API网关服务):作用:安全,路由,限流,监控
* 2、服务注册发现的基本流程是怎样的?
答:
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
* 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
* 3、Eureka和Nacos有哪些区别?
答: Nacos与Eureka的区别
1.Nacos支持服务端主动检测提供者状态: 临时实例采用心跳模式,非临时实例采用主动检测模式
2.临时实例心跳不正常会被剔除,非临时实例则不会被剔除
3.Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
4.Nacos集群默认采用AP方式,但也支持CP;Eureka采用AP方式
* 4、Nacos的分级存储模型是什么意思?
答:
任何一个微服务的实例在注册到Nacos时,都会生成以下几个信息,用来确认当前实例的身份,从外到内依次是:
namespace:命名空间
group:分组
service:服务名
cluster:集群
instance:实例,包含ip和端口
这就是nacos中的服务分级模型。
* 5、OpenFeign是如何实现负载均衡的?
答:
Spring在整合OpenFeign的时候,实现org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient类,其中定义了OpenFeign发起远程调用的核心流程。也就是四步:
* 获取请求中的serviceId
* 根据serviceId负载均衡,找出一个可用的服务实例
* 利用服务实例的ip和port信息重构url
* 向真正的url发起请求
而具体的负载均衡则是不是由OpenFeign组件负责。而是分成了负载均衡的接口规范,以及负载均衡的具体实现两部分。
负载均衡的接口规范是定义在Spring-Cloud-Common模块中,包含下面的接口:
* LoadBalancerClient:负载均衡客户端,职责是根据serviceId最终负载均衡,选出一个服务实例
* ReactiveLoadBalancer:负载均衡器,负责具体的负载均衡算法
OpenFeign的负载均衡是基于Spring-Cloud-Common模块中的负载均衡规则接口,并没有写死具体实现。这就意味着以后还可以拓展其它各种负载均衡的实现。
不过目前SpringCloud中只有Spring-Cloud-Loadbalancer这一种实现。
Spring-Cloud-Loadbalancer模块中,实现了Spring-Cloud-Common模块的相关接口,具体如下:
* BlockingLoadBalancerClient:实现了LoadBalancerClient,会根据serviceId选出负载均衡器并调用其算法实现负载均衡。
* RoundRobinLoadBalancer:基于轮询算法实现了ReactiveLoadBalancer
* RandomLoadBalancer:基于随机算法实现了ReactiveLoadBalancer,
* 6、 什么是服务雪崩,常见的解决方案有哪些?
答:
服务雪崩是指在分布式系统中,由于某个服务的响应时间过长或不可用,导致调用该服务的其他服务也受到影响,进而引发连锁反应,最终导致整个系统瘫痪的现象。这种现象通常发生在微服务架构中,因为微服务之间的依赖关系复杂,一个服务的故障可能会迅速传播到整个系统。
解决方案:
熔断机制:
- 熔断器模式(Circuit Breaker Pattern)是一种常见的解决方案。当某个服务的错误率达到一定阈值时,熔断器会自动断开对该服务的调用,避免请求继续发送到故障服务,从而防止故障扩散。熔断器通常有三种状态:关闭(正常)、打开(熔断)和半开(尝试恢复)。
限流机制:
- 限流(Rate Limiting)是指限制某个服务的请求速率,防止服务过载。常见的限流算法包括令牌桶算法、漏桶算法等。
降级机制:
- 降级(Degradation)是指在服务出现故障时,提供一个备用的、简化版的实现,以保证核心功能的可用性。例如,当某个服务不可用时,可以返回一个默认值或缓存数据,而不是直接抛出异常。
超时机制:
- 超时(Timeout)是指设置一个合理的超时时间,当服务调用超过该时间时,直接返回错误,避免长时间等待导致资源耗尽。
重试机制:
- 重试(Retry)是指在服务调用失败时,自动重试一定次数,以提高请求成功的概率。但需要注意,重试机制可能会加重服务负载,因此需要合理设置重试次数和间隔时间。
负载均衡:
- 负载均衡(Load Balancing)是指将请求分发到多个服务实例上,避免单个实例过载。常见的负载均衡算法包括轮询、加权轮询、最少连接等。
* 线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
* 信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求
* 7、Hystix和Sentinel有什么区别和联系?
答:
无论是Hystix还是Sentinel都支持线程隔离,不过其实现方式不同。
Hystix默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的CPU开销,性能一般,但是隔离性更强.支持主动超时和异步调用,适用场景为低扇出
Sentinel则是基于信号量隔离的原理,这种方式不用创建线程池,性能较好,但是隔离性一般.不支持主动超时和异步调用,使用场景为高频调用和高扇出
Sentinel的线程隔离就是基于信号量隔离实现的,而Hystix两种都支持,但默认是基于线程池隔离。
* 8、限流的常见算法有哪些?
答:
固定窗口计算器算法
固定窗口计数器算法概念如下
将时间划分为多个窗口,窗口时间跨度称为Interval,本例中为1000ms
每个窗口分别计数统计,每有一次请求就将计数器加一,限流就是设置计数器阈值,本例为3
如果计数器超过了限流阈值,则超出阈值的请求都被丢弃
滑动窗口算法(默认区间数量是2,区间数越多,计数器越多,压力越大)Sentinel
滑动窗口计数器算法会将一个窗口划分为n个更小的区间,例如
窗口时间跨度Interval为1秒;区间数量n=2,则每个小区间时间跨度为500ms,每个区间都有计数器
限流阈值依然为3,时间窗口(1秒)内请求超过阈值时,超出的请求被限流
窗口会根据当前请求所在时间(currentTime)移动,窗口范围是从(currentTime-Interval)之后的第一个时区开始,到currentTime
限流之后(滑动窗口算法后,默认方案是快速拒绝:抛出异常,失败)
其他方案:
漏桶算法
将每个请求视作"水滴"放入"漏桶"进行存储
"漏桶"以固定速率向外"漏"出请求来执行,如果"漏桶"空了则停止"漏水"
如果"漏桶"满了则多余的"水滴"会被直接丢弃
可以理解成请求在桶内排队等
令牌桶算法
以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,停止生成
请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理
如果令牌桶中没有令牌,则请求等待或丢弃
* 9、什么是CAP理论和BASE思想?
答:
1998年,加州大学的计算机科学家Eric Brewer提出,分布式系统有三个指标:
Consistency(一致性): 用户访问分布式系统中的任意节点,得到的数据必须一致
Availability(可用性): 用户访问分布式系统时,读或写操作总能成功.只能读不能写,或者只能写不能读,或者两者都不执行,就说明系统弱可用或不可用
Partition tolerance(分区容错性): 因为网络故障或其他原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区, 系统要能容忍网络分区现象,出现分区时,整个系统也要持续对外提供服务
分布式系统无法同时满足这三个指标
这个结论就叫做CAP定理
BASE理论是对CAP的一种解决思路,包含三个思想:
Basically Available(基本可用): 分布式系统在出现故障时,允许算是部分可用性,即保证核心可用
Soft State(软状态): 在一定时间内,允许出现中间状态,比如临时的不一致状态
Eventually Consistent(最终一致性): 虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致
* 10、项目中碰到过分布式事务问题吗?怎么解决的?
答:
分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论:
CP模式: 各个子事务执行后互相等待,同时提交,同时回滚,达成强一致.但事务等待过程中,处于弱可用状态
AP模式: 各个事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致.
* 11、AT模式如何解决脏读和脏写问题的?
答:
AT模式也分为两个阶段
第一阶段是记录数据快照,执行并提交事务
第二阶段根据阶段一的结果来判断:
* 如果每一个分支事务都成功,则事务已经结束(因为阶段一已经提交),因此删除阶段一的快照即可
* 如果有任意分支事务失败,则需要根据快照恢复到更新前数据。然后删除快照
这种模式在大多数情况下(99%)并不会有什么问题,不过在极端情况下,特别是多线程并发访问AT模式的分布式事务时,有可能出现脏写问题
解决思路就是引入了全局锁的概念。在释放DB锁之前,先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。
* 12、TCC模式与AT模式对比,有哪些优缺点
答:
TCC模式的每个阶段是做什么的?
Try: 资源检查和预留
Confirm: 业务执行和提交
Cancel: 预留资源的释放
TCC的优点是什么?
一阶段完成直接提交事务,释放数据库资源,性能好
相比AT模型,无需生成快照,无需使用全局锁,性能最强
不依赖数据库,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点是什么?
有代码侵入,需要人为编写try,Confirm和Cancel接口,太麻烦
软状态,事务是最终一致
需要考虑Confirm和Cancel的失败情况,做好幂等处理
* 13、RabbitMQ是如何确保消息的可靠性的?
答:
1.首先第一种情况,就是生产者发送消息时,出现了网络故障,导致与MQ的连接中断。
生产者重试机制:
为了解决这个问题,SpringAMQP提供的消息发送时的重试机制。即:当RabbitTemplate与MQ连接超时后,多次重试。
2.在少数情况下,也会出现消息发送到MQ之后丢失的现象
生产者确认机制:
RabbitMQ提供了生产者消息确认机制,包括Publisher Confirm和Publisher Return两种。在开启确认机制的情况下,当生产者发送消息给MQ后,MQ会根据消息处理的情况返回不同的回执。
3.消息到达MQ以后,如果MQ不能及时保存,也会导致消息丢失,所以MQ的可靠性也非常重要。
数据持久化:
为了提升性能,默认情况下MQ的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须配置数据持久化,包括:- 交换机持久化 - 队列持久化 - 消息持久化
4.为了确认消费者是否成功处理消息,RabbitMQ提供了消费者确认机制(Consumer Acknowledgement)。即:当消费者处理消息结束后,应该向RabbitMQ发送一个回执,告知RabbitMQ自己消息处理状态。回执有三种可选值:
* ack:成功处理消息,RabbitMQ从队列中删除该消息
* nack:消息处理失败,RabbitMQ需要再次投递消息
* reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
5.当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者。如果消费者再次执行依然出错,消息会再次requeue到队列,再次投递,直到消息处理成功为止.
失败重试机制:在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列。
在yml文件上配置retry:enabled: true
* 开启本地重试时,消息处理过程中抛出异常,不会requeue到队列,而是在消费者本地重试
* 重试达到最大次数后,Spring会返回reject,消息会被丢弃
6.本地测试达到最大重试次数后,消息会被丢弃。这在某些对于消息可靠性要求较高的业务场景下,显然不太合适了。
失败处理策略:
因此Spring允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由MessageRecovery接口来定义的,它有3个不同 实现:
* RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
* ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
* RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
* 14、RabbitMQ是如何解决消息堆积问题的?
答:
在默认情况下,RabbitMQ会将接收到的信息保存在内存中以降低消息收发的延迟。但在某些特殊情况下,这会导致消息积压,比如:
* 消费者宕机或出现网络故障
* 消息发送量激增,超过了消费者处理速度
* 消费者处理业务发生阻塞
一旦出现消息堆积问题,RabbitMQ的内存占用就会越来越高,直到触发内存预警上限。此时RabbitMQ会将内存消息刷到磁盘上,这个行为成为PageOut. PageOut会耗费一段时间,并且会阻塞队列进程。因此在这个过程中RabbitMQ不会再处理新的消息,生产者的所有请求都会被阻塞。
为了解决这个问题,从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的模式,也就是惰性队列。惰性队列的特征如下:
* 接收到消息后直接存入磁盘而非内存
* 消费者要消费消息时才会从磁盘中读取并加载到内存(也就是懒加载)
* 支持数百万条的消息存储