1.SpringCloud是什么?跟SpringBoot有什么区别?
Spring Cloud 是一个基于Spring Boot 实现的云应用开发工具集,它为开发者提供了在分布式系统操作的开发工具,使用Spring Cloud可以快速的构建起一套微服务系统,且还支持与云平台的集成。
如果你正在构建一个简单的Web应用或后端服务,可能会直接使用 Spring Boot。而当你需要构建一个复杂的、分布式的微服务架构时,则可能需要用到 Spring Cloud 来帮助管理各个服务之间的交互。Spring Boot 是构建单个服务的基础,而 Spring Cloud 则是构建整个微服务生态系统的关键
2.你知道SpringCloud中有哪些组件?
-
Eureka:服务发现与注册中心,允许服务实例进行注册与发现。服务可以通过 Eureka 相互查找和通信。
-
Config Server:集中化配置管理。可以将所有环境的配置文件存储在一个地方,方便管理和更新。
-
Zuul / Gateway:API 网关,作为系统的入口点,负责请求路由、过滤、安全等任务。Spring Cloud Gateway 是 Zuul 的新一代产品,具有更好的性能和灵活性。
-
Hystrix:断路器模式的实现,用于防止服务雪崩效应,当某个服务失败时,不会影响到其他服务的调用。
-
Ribbon:客户端负载均衡器,可以在没有外部负载均衡设备的情况下实现服务间的负载均衡。
-
Feign:声明式 Web 服务客户端,简化了 HTTP API 的调用过程,结合 Ribbon 可以轻松实现负载均衡。
-
Sleuth:服务跟踪,用于收集微服务之间请求的追踪信息,便于分析和诊断问题。
-
Zipkin:分布式追踪系统,可以收集服务间的时间数据,帮助理解服务的延迟情况。
-
Consul:除了服务发现与配置管理外,Consul 还支持健康检查、KV 存储、多数据中心等特性,是一个完整的微服务平台。
-
Nacos:阿里巴巴开源的服务发现与配置管理平台,支持动态配置服务、服务发现及管理。
-
Turbine:聚合多个 Hystrix 指标的监控数据,通常与 Hystrix Dashboard 结合使用,用于监控多个服务实例的聚合视图。
-
Stream:提供了一种简单的方式,用于连接和管理消息中间件(如 RabbitMQ, Kafka),实现消息驱动的微服务架构。
-
Bus:事件和消息总线,用于在微服务架构中传播状态变化(例如配置信息的变化)。
-
Security:提供安全认证和授权服务,保护微服务免受未授权访问。
一般服务注册发现和统一配置管理都使用Nacos
3.注册中心的原理?
流程如下:
-
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
-
调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
-
调用者自己对实例列表负载均衡,挑选一个实例
-
调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
-
服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
-
当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
-
当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
-
当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
4.你们微服务中登陆和校验的流程是什么样的?
我们将登录功能封装在用户服务中,在登陆时,首先要对前端传过来的数据进行核实,查询数据库比对,比对通过生成令牌并封装进对象返回给前端,前端收到后将令牌保存,每次发起请求时都会携带令牌。
在网关服务中配置了优先级高于转发路由的过滤器,在里面校验前端传过来的JWT令牌是否有效,无效返回异常结果,因为其余的服务都要用到用户信息,所以我们将用户信息保存到请求头中,在转发给微服务,微服务中配置了拦截器,拦截到请求后将获取请求头中的用户信息存入线程局部变量以便服务中使用,而微服务之间的互相调用使用feign的方式,设置了发请求之前将用户id存入请求头中,方便其他微服务获取
5.你们微服务中配置文件是怎么管理的?
微服务共享的配置可以统一交给Nacos保存和管理,在Nacos控制台修改配置后,Nacos会将配置变更推送给相关的微服务,并且无需重启即可生效,实现配置热更新。
网关的路由同样是配置,因此同样可以基于这个功能实现动态路由功能,无需重启网关即可修改路由配置。
6.服务雪崩什么原因导致的?如何解决?
产生的原因:
微服务相互调用,服务提供者出现故障或阻塞。 服务调用者没有做好异常处理,导致自身故障。 调用链中的所有服务级联失败,导致整个集群故障
解决的思路:
尽量避免服务出现故障或阻塞。 保证代码的健壮性; 保证网络畅通; 能应对较高的并发请求; 服务调用者做好远程调用异常的后备方案,避免故障扩散
解决的方案:
请求限流:限制流量在服务可以处理的范围,避免因突发流量而故障
线程隔离:控制业务可用的线程数量,将故障隔离在一定范围
服务熔断:将异常比例过高的接口断开,拒绝所有请求,直接走fallback,定义fallback逻辑,让业务失败时不再抛出异常,而是返回默认数据或友好提示
7.Seata中XA模式的原理?
Seata的事务管理中有三个重要的角色:
-
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
-
TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
-
RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
RM`一阶段的工作:
-
TM注册分支事务到
TC
-
RM执行分支业务sql但不提交
-
报告执行状态到
TC
TC
二阶段的工作:
-
TC
检测各分支事务执行状态-
如果都成功,通知所有RM提交事务
-
如果有失败,通知所有RM回滚事务
-
RM
二阶段的工作:
-
接收
TC
指令,提交或回滚事务
8.Seata中AT模式的原理?与XA模式的区别?
一阶段:
-
TM
发起并注册全局事务到TC
-
TM
调用分支事务 -
分支事务准备执行业务SQL
-
RM
拦截业务SQL,根据where条件查询原始数据,形成快照。 -
RM
执行业务SQL,提交本地事务,释放数据库锁。 -
RM
报告本地事务状态给TC
二阶段:
-
TM
通知TC
事务结束 -
TC
检查分支事务状态-
如果都成功,则立即删除快照
-
如果有分支事务失败,需要回滚。读取快照数据({"id": 1, "money": 100}),将快照恢复到数据库。此时数据库再次恢复为100
-
与XA模式的区别:
XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
XA模式强一致;AT模式最终一致
9.RabbitMQ有哪些消息模型,有什么特点?
看我MQ基础那篇文章
10.RabbitMQ如何确保消息可靠性
从三个方面:消息生产者、消息消费者、消息代理 MQ本身
生产者:
生产者重连机制
生产者发送消息时,出现了网络故障,导致与MQ的连接中断。为了解决这个问题,RabbitMQ提供的消息发送时的重连机制。即:当RabbitTemplate
与MQ连接超时后,多次重试。
生产者确认机制
publisher-confirm(发送者确认)
消息未投递到交换机,返回NACK。(可能是由于网络波动未能连接到RabbitMQ,可利用生产者重连机制解决)
publisher-return(发送者回执)
消息投递到交换机了,但是没有路由到队列。返回ACK和路由失败原因。(这种问题一般是因为路由键设置错误,可以人为规避)
消费者:
消费者确认机制:
为了确认消费者是否正确处理了消息,RabbitMQ提供了消费者确认机制。当消费者处理 消息后,会返回回执信息给RabbitMQ。回执有三种值:
ack:消息处理成功,RabbitMQ从队列中删除消息。
nack:消息处理失败,RabbitMQ需要再次投递消息。
reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除消息。
SpringAMQP帮我们实现了消息确认。有三种模式:
none
:不处理。即消息投递给消费者后立刻ack,消息会立刻从MQ删除。非常不安全, 不建议使用
manual
:手动模式。需要自己在业务代码中调用api,发送ack
或reject
,存在业务入 侵,但更灵活
auto
:自动模式。SpringAMQP利用AOP对我们的消息处理逻辑做了环绕增强,当业务 正常执行时则自动返回ack
. 当业务出现异常时,根据异常判断返回不同结果:
如果是业务异常,会自动返回nack
;
如果是消息处理或校验异常,自动返回reject
;
失败重试机制、失败处理策略:
当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者。如果消费者再次执行依然出错,消息会再次requeue到队列,再次投递,直到消息处理成功为止。
极端情况就是消费者一直无法执行成功,那么消息requeue就会无限循环,导致mq的消息处理飙升,带来不必要的压力
所以我们会设置重试次数,来让消息不会无限requeue,但是消息会被丢弃掉,某些对于消息可靠性要求较高的业务场景下不适用,所以我们还加入了失败处理策略
Spring中允许自定义重试次数耗尽后的消息处理策略,三种:
默认的处理策略是重试耗尽后,返回reject,消息直接丢弃,
第二种是重试耗尽后,返回nack
,消息重新入队,
第三种是将错误信息转发至错误交换机,然后错误交换机路由到错误队列,交由人工处理,一般使用第三种
MQ本身:
-
交换机持久化(确保RabbitMQ重启后交换机仍然存在)
-
队列持久化(确保RabbitMQ重启后队列仍然存在)
-
消息持久化(确保RabbitMQ重启后队列中的消息仍然存在)
消息持久化:
MQ本身默认基于内存存储,如果服务宕机,则消息都会丢失,而消息过多,则会间歇性的pageout刷盘,PageOut
会耗费一段时间,并且会阻塞队列进程
SpringAMQP则是内存与磁盘同时存储,避免了pageout的情况,取消息也比较快
MQ新版本推出了LazyQueue惰性队列,将消息直接存入磁盘,取消息时再将数据读到内存中
11.MQ如何确保幂等性
1.唯一消息ID:
每一条消息都生成一个唯一的id,与消息一起投递给消费者。
消费者接收到消息后处理自己的业务,业务处理成功后将消息ID保存到数据库
如果下次又收到相同消息,去数据库查询判断是否存在,存在则为重复消息放弃处理。
2.业务判断
业务判断就是基于业务本身的逻辑或状态来判断是否是重复的请求或消息,不同的业务场景判断的思路也不一样。
例如我们当前案例中,处理消息的业务逻辑是把订单状态从未支付修改为已支付。因此我们就可以在执行业务时判断订单状态是否是未支付,如果不是则证明订单已经被处理过,无需重复处理。
12.如何实现延迟消息
死信交换机+过期时间 DLX+TTL
消息发送给一个绑定了死信交换机的队列,并设置了过期时间,当该消息过期时,自动将消息发送给死信交换机,路由到死信队列并被消费者消费,从而实现了延迟消息
延迟消息插件DelayExchange
MQ社区提供了一个延迟消息插件来实现相同的效果,该插件将普通交换机改造成支持延迟消息的交换机,暂存一段时间后在路由给队列
例如超时订单这个问题
当用户支付完后,同步扣减余额,异步给MQ发送消息执行修改订单状态、扣减库存等,如果正常收发消息,功能都可以执行,中间出现了一些问题,MQ崩了,或者服务器故障等等的问题导致消息不可靠,则修改订单状态那些功能都没有执行,所以订单服务延迟30分钟给自身发一条消息,检测支付的状态,成功就更新订单状态,失败则设置订单超时并恢复对应商品的库存
13.什么是死信
当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter):
-
消费者使用
basic.reject
或basic.nack
声明消费失败,并且消息的requeue
参数设置为false -
消息是一个过期消息,超时无人消费
-
要投递的队列消息满了,无法投递
如果一个队列中的消息已经成为死信,并且这个队列通过dead-letter-exchange属性指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机就称为死信交换机(Dead Letter Exchange)。而此时加入有队列与死信交换机绑定,则最终死信就会被投递到这个队列中。
死信交换机有什么作用呢?
-
收集那些因处理失败而被拒绝的消息
-
收集那些因队列满了而被拒绝的消息
-
收集因TTL(有效期)到期的消息
14.用过ES吗?怎么用的?倒排索引的原理是什么?
Elasticsearch是由elastic公司开发的一套基于Lusene的搜索引擎技术,Lucene是一个Java语言的搜索引擎类库,它是elastic技术栈中的一部分。完整的技术栈包括:
-
Elasticsearch:用于数据存储、计算和搜索
-
Logstash/Beats:用于数据收集
-
Kibana:用于数据可视化
整套技术栈被称为ELK,经常用来做日志收集、系统监控和状态分析等等!
某宝,某东都用的ES做搜索引擎
倒排索引的原理:
倒排索引的概念是基于MySQL这样的正向索引而言的。
正向索引是基于文档id创建索引。根据id查询快,但是查询词条时必须先找到文档,而后判断是否包含词条
概念:对文档内容分词,对词条创建索引,并记录词条所在文档的id。查询时先根据词条查询到文档id,而后根据文档id查询文档
每一条数据就是一个文档
对文档中的内容分词,得到的词语就是词条
15.Redis持久化方案有哪些?有什么区别?
RDB和AOF
1. RDB(Redis Database Backup)
原理:
-
RDB 是一种快照持久化方式,它会在指定的时间间隔内将内存中的数据集快照写入磁盘。
-
当 Redis 接收到保存命令(如
SAVE
或BGSAVE
)时,会生成一个 RDB 文件。
优点:
-
性能高:RDB 持久化是在后台异步进行的,对 Redis 性能影响较小。
-
文件紧凑:RDB 文件体积小,适合备份和恢复。
-
恢复速度快:加载 RDB 文件恢复数据的速度比 AOF 快。
缺点:
-
数据丢失风险:如果 Redis 服务意外宕机,可能会丢失最后一次快照以来的数据。
-
不适合频繁更新:对于数据更新频繁的场景,RDB 可能会导致频繁的磁盘 I/O 操作。
2. AOF(Append Only File)
原理:
-
AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时重新执行这些命令以恢复数据。
-
AOF 文件是一个只追加的日志文件,因此在写入时不会进行任何修改,避免了数据损坏的风险。
优点:
-
数据安全性高:AOF 持久化可以提供更好的数据完整性和恢复能力,尤其是在
appendfsync always
模式下。 -
支持更多命令:AOF 可以记录更多的命令,包括
DEL
、EXPIRE
等,因此恢复的数据更完整。
缺点:
-
性能较低:频繁的磁盘 I/O 操作会影响 Redis 的性能,尤其是
appendfsync always
模式。 -
文件体积大:AOF 文件通常比 RDB 文件大,因为记录了所有的写操作。
16.Redis主从同步的原理
主从同步分为了两个阶段,一个是全量同步,一个是增量同步 。
全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是 这样的:
第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id 和offset偏移量。
第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节 点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息 保持一致。
第三:在同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执 行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这 样就保持了一致 。
当然,如果在rdb生成执行期间,依然有请求到了主节点,而主节点会以命 令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送 给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时 候,都是依赖于这个日志文件,这个就是全量同步
增量同步指的是,当从节点服务重启之后,数据不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是 第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后 的数据,发送给从节点进行数据同步
17.Redis repl_baklog原理 主从优化
这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。 repl_baklog中会记录Redis处理过的命令及offset,包括master当前的offset,和slave已经拷贝到的offset: slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。 repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于repl_baklog做增量同步,只能再次全量同步。
主从优化
-
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
-
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
-
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
-
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
18.Redis哨兵三个作用
-
状态监控:
Sentinel
会不断检查您的master
和slave
是否按预期工作 -
故障恢复(failover)/故障转移:如果
master
故障,Sentinel
会将一个slave
提升为master
。当故障实例恢复后会成为slave
-
状态通知:
Sentinel
充当Redis
客户端的服务发现来源,当集群发生failover
时,会将最新集群信息推送给Redis
的客户端
19.Redis哨兵如何判断Master是否下线
Sentinel
基于心跳机制监测服务状态,每隔1秒向集群的每个节点发送ping命令,并通过实例的响应结果来做出判断:
-
主观下线(sdown):如果某sentinel节点发现某Redis节点未在规定时间响应,则认为该节点主观下线。
-
客观下线(odown):若超过指定数量(通过
quorum
设置)的sentinel都认为该节点主观下线,则该节点客观下线。quorum值最好超过Sentinel节点数量的一半,Sentinel节点数量至少3台。
20.如何挑选slave
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
-
首先会判断slave节点与master节点断开时间长短,如果超过
down-after-milliseconds * 10
则会排除该slave节点 -
然后判断slave节点的
slave-priority
值,越小优先级越高,如果是0则永不参与选举(默认都是1)。 -
如果
slave-prority
一样,则判断slave节点的offset
值,越大说明数据越新,优先级越高 -
最后是判断slave节点的
run_id
大小,越小优先级越高(通过info server可以查看run_id
)。
21.如何挑选哨兵leader
首先,Sentinel集群要选出一个执行failover
的Sentinel节点,可以成为leader
。要成为leader
要满足两个条件:
-
最先获得超过半数的投票
-
获得的投票数不小于
quorum
值
而sentinel投票的原则有两条:
-
优先投票给目前得票最多的
-
如果目前没有任何节点的票,就投给自己
比如有3个sentinel节点,s1
、s2
、s3
:
s1认为一个master主观下线并且客观下线数量+1,
在之后s2也认为主观下线并且客观下线数量+1,同时客观下线数量大于quorum值,开始发起投票
此时没有节点投票,所以s2投票给自己
s3也认为下线,s1和s3都加入投票发现s2有1票,都给s2投票,选举出leader
不难看出,谁先投票,谁就会称为leader,第一个确认master客观下线的人会立刻发起投票,一定会成为 leader。
22、Redis分片集群结构特点和插槽原理
Redis分片集群结构特点:
集群中有多个master,每个master保存不同数据
且每个master都可以有多个slave节点,
master之间通过ping监测彼此健康状态,
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
插槽原理:
每一个master节点都会分配一定数量的hash slots(插槽),通过CRC16 算法对key做hash运算,得到的结果与16384取余,就计算出了这个key的slot值。然后到slot所在的Redis节点执行读写操作。
计算key的hash值分两种情况:
当key中包含{}时,根据{}之间的字符串计算hash slot itcastnum 整个key计算
当key中不包含{}时,则根据整个key字符串计算hash slot {itcast}num 只计算itcast
23、Redis种Zset数据结构,跳表(SkipList)原理
Zset也是SortedSet,是有序集合,基于HashTable 哈希表 + SkipList 跳表实现,底层的存储的每个数据都包含element和score两个值。score是得分,element则是字符串值。SortedSet会根据每个element的score值排序,形成有序集合。底层基于
根据element查询score值:
SortedSet底层是基于HashTable来实现的。
按照score值升序或降序查询element:
ortedSet是基于跳表实现的。
因为SortedSet底层需要用到两种数据结构,对内存占用比较高。因此Redis底层会对SortedSet中的元素大小做判断。如果元素大小小于128且每个元素都小于64字节,SortedSet底层会采用ZipList,也就是压缩列表来代替HashTable和SkipList,不过,ZipList存在连锁更新问题,因此而在Redis7.0版本以后,ZipList又被替换为Listpack(紧凑列表)。
跳表(SkipList)是一个链表结构,元素按照升序排列存储,节点可能包含多个指针,指针跨度不同。
但内部包含跨度不同的多级指针,可以让我们跳跃查找链表中间的元素,效率非常高。
24、Redis过期删除策略
-
惰性删除:有命令需要操作一个key的时候,检查该key的存活时间,如果已经过期才执行删除。
-
周期删除:定时任务,周期性的抽样部分过期的key,然后执行删除。
-
SLOW模式:Redis会设置一个定时任务
serverCron()
,按照server.hz
的频率来执行过期key清理,默认执行频率为每秒10次,但每次执行时长不能超过25ms -
FAST模式:Redis的每个事件循环前执行过期key清理(事件循环就是NIO事件处理的循环)。频率不固定,跟随Redis内部IO事件循环执行。两次任务之间间隔不低于2ms,执行时长不超过1ms
-
Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。
25、Redis内存淘汰策略
八种淘汰策略
noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。
volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰
allkeys-random:对全体key ,随机进行淘汰。也就是直接从db->dict中随机挑选
volatile-random:对设置了TTL的key ,随机进行淘汰。也就是从db->expires中随机挑选。
allkeys-lru: 对全体key,基于LRU算法进行淘汰
volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰
allkeys-lfu: 对全体key,基于LFU算法进行淘汰
volatile-lfu: 对设置了TTL的key,基于LFU算法进行淘汰
LRU(Least Recently Used),最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
LFU(Least Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高
LFU的访问次数之所以叫做逻辑访问次数,是因为并不是每次key被访问都计数,而是通过运算:
1、生成[0~1)之间的随机数R
2、计算 1/(旧次数 * lfu_log_factor + 1),记录为P,
3、lfu_log_factor默认为10如果 R < P ,则计数器 + 1,且最大不超过255
4、访问次数会随时间衰减,距离上一次访问时间每隔 lfu_decay_time 分钟(默认1) ,计数器 -1
26如何保证缓存一致性
保证数据库和缓存一致性,采用先更新数据库再删缓存策略
先删除缓存再更新数据库策略:
线程1删除缓存后在操作数据库时,线程2来查询数据,数据库还没有改变,导致读到旧数据存入缓存,产生不一致,而操作数据库耗时较长,期间其他线程访问概率很高,所以不推荐该策略
先更新数据库再删除缓存策略:
更新数据库后再删除缓存,在别的线程来访问时,突然缓存失效了,查询数据库查到旧数据,在此时线程2更新完数据库执行一次删除缓存后,线程1将旧数据存入缓存
为了提高一致性,可以采用延迟双删策略
先删除缓存-->更新数据库-->延迟N秒后再次删除缓存,延迟时间要大于预期更新数据库的时间,它是最终一致性,不是强一致性,如果要强一致,则需加锁,
延时双删适用于对数据一致性要求较高的场景。它需要进行两次缓存删除操作,可能会增加一定的资源开销;
先更新数据库后删除缓存适用于对一致性要求较低,对性能要求较高的场景。它能够减少一次缓存删除的开销,但是在数据库更新期间,读取请求可能会读取到已经失效的缓存数据,从而导致数据不一致。
27、缓存穿透原因及解决方案?布隆过滤原理和特点
原因:
有人恶意攻击,多个线程大量访问数据库中不存在的数据,缓存不生效,导致大量请求到达数据库,冲击数据库的服务,
解决方案:
1.缓存空值:当访问数据库中不存在的数据时,在缓存中存入有TTL的null值,以减少数据库服务的压力
优点:成本低,好维护
缺点:额外的内存消耗
2.布隆过滤器:在请求访问redis之前,先经过布隆过滤器判断元素是否在数据库中,不在就拦截,可能存在再放行,
优点:内存占用少,查询效率高
缺点:实现复杂,存在误判,删除困难
原理:布隆过滤是一个数据统计算法,用于检索一个元素是否存在一个集合中;在一个很长的二进制数,每存一个元素,拿到哈希值通过k个算法函数将二进制数指定位置的数字变成1,判断一个元素是否存在时,只需要判断进行相同的算法得到的位置是否是1,是1则可能存在,不是1则一定不存在
28、缓存雪崩原因及解决方案?
原因:
缓存预热将大批量的数据存入缓存并设置了相同的过期时间,在到期后大量key同时失效或Redis服务宕机,导致大量请求访问数据库,
解决方案
1.给不同的Key的TTL添加随机值,这样KEY的过期时间不同,不会大量KEY同时过期
2.利用Redis集群提高服务的可用性,避免缓存服务宕机
3.给缓存业务添加降级限流策略
4.给业务添加多级缓存,前端浏览器缓存--->nginx级别缓存--->jvm本地缓存--->redis缓存--->数据库
29、缓存击穿原因及解决方案?
原因:
缓存击穿也叫热点Key问题,高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,导致服务响应慢,甚至是服务崩溃
解决方案:
互斥锁:将缓存重建业务和写入缓存两个逻辑加互斥锁保证事物的原子性,阻塞其他线程以保证减少数据库的压力
逻辑过期:缓存中加业务字段,并不真正删除,当读取到逻辑过期字段过期时,获取互斥锁并返回过期的数据,开启新线程来完成缓存重建的流程并重置逻辑过期字段,完成后释放锁。当其他线程来访问时,发现过期,获取互斥锁,获取失败后返回过期数据
30、CAP定理和BASE理论
CAP定理:
Consistency(一致性): 用户访问分布式系统中的任意节点,得到的数据必须一致。
Availability(可用性): 用户访问分布式系统时,读或写操作总能成功。
Partition tolerance (分区容错性): 就是当分布式系统节点之间出现网络故障导致节点之间无法通信,出现网络分区,整个系统也要持续对外提供服务。
-
允许用户任意读写,保证可用性。但由于node03无法完成同步,就会出现数据不一致的情况。满足AP
-
不允许用户写,可以读,直到网络恢复,分区消失。这样就确保了一致性,但牺牲了可用性。满足CP
网络分区的情况一定会存在,所以必须满足P分区容错性,在分布式系统中,A和C之间只能满足一个。
BASE理论:
-
Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
-
Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
-
Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
BASE理论就是一种取舍的方案,不再追求完美,而是最终达成目标。因此解决分布式事务的思想也是这样,有两个方向
-
AP思想:各个子事务分别执行和提交,无需锁定数据。允许出现结果不一致,然后采用弥补措施恢复,实现最终一致即可。例如
AT
模式就是如此 -
CP思想:各个子事务执行后不要提交,而是等待彼此结果,然后同时提交或回滚。在这个过程中锁定资源,不允许其它人访问,数据处于不可用状态,但能保证一致性。例如
XA
模式
31、Seata种AT模式脏写如何解决
脏写就是事务1在一阶段完成后,二阶段事务提交或回滚之前,事务2在这个阶段执行完数据库的更新操作并保存,而事务1此时二阶段回滚,导致事务2的无效更新
解决思路:加全局锁
在提交事务,释放DB锁之前先获取全局锁,避免同一时刻有另外一个事务来操作当前数据
以上仅对都被seata管理的事务有效,如果有一个未被seata管理的事务操作数据库,则还会出现脏写问题,这种情况下,seata管理的事务会根据前后快照比对当前数据库的数据,判断是否中间被修改,被修改则循环打印日志,触发警报,转人工处理
32、Nacos注册中心分级存储模型和环境隔离(了解)
Nacos注册中心分级存储模型:
-
namespace:命名空间
-
group:分组
-
service:服务名
-
cluster:集群
-
instance:实例,包含ip和端口
环境隔离:Nacos基于namespace命名空间 和group分组 将项目中各个环境隔离开
33、Nacos和Eureka的区别
相同点: 都支持服务注册和服务拉取,都支持心跳机制的服务健康检测方式
不同点: Nacos对于临时实例,采用心跳机制,对于非临时实例,采用主动监测,临时实例心跳异常会被剔除,非临时实例则不会 ,Eureka只支持心跳机制
Nacos支持服务拉取,也会主动推送服务变更,服务列表更新更及时,Eureka只能服务拉取
集群模式Nacos默认采用AP模式,也可以支持CP,Eureka采用AP模式
34、SpringCloud LoadBalance负载均衡的原理?(了解)
35、Nacos负载均衡策略
会先获取到当前服务的实例对应的集群,然后获取到要远程调用的服务集群的集合,并过滤出与当前服务同一个集群下的实例集合
同集群优先,然后加权随机
36、线程池隔离和信号量隔离区别?
信号量隔离:
不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求,性能好,隔离性一般
线程池隔离:
给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果,性能一般,隔离性较好,每一个被隔离的业务都要单独创建一个线程池
37、限流常见的算法有哪些?哪些地方使用到这些算法
限流算法常见的有三种实现:固定窗口计数算法、滑动窗口计数算法、令牌桶算法、漏桶算法。
固定窗口计数算法是将时间划分为多个固定窗口,其中窗口跨度固定,每有一次请求在一个窗口过,则计数器+1,超出阈值则丢弃该请求,存在一定的问题,例在上个窗口的500ms后和下个窗口的500ms前出现了超出阈值的请求则无法限制
滑动窗口计数算法在固定窗口计数算法的基础上,划分了更微小的窗口,设置了请求的时区,结合前后的时间窗的数据做综合统计,它的窗口不固定,会把请求的时间减去时间跨度得到的时刻后的第一个时区当做开始,自己所在的时区当做结束来判断是否超出阈值
漏桶算法,请求到达后不是直接处理,而是先放入一个队列。而后以固定的速率从队列中取出并处理请求。之所以叫漏桶算法,就是把请求看做水,队列看做是一个漏了的桶。
令牌桶算法,以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,暂停生成令牌,请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理,如果令牌桶中没有令牌,则请求等待或丢弃
Gateway则采用了基于Redis实现的令牌桶算法,
而Sentinel内部却比较复杂:
默认限流模式是基于滑动时间窗口算法,另外Sentinel中断路器的计数也是基于滑动时间窗口算法
限流后可以快速失败和排队等待,其中排队等待基于漏桶算法
而热点参数限流则是基于令牌桶算法