微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

面试题之 浅谈微服务SpringCloud 核心组件, MQ核心 , Kafka原理

SpringCloud是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件

1、说一说你对于微服务的理解

原来的单体项目, 就是所有模块都写在一起 , 这样开发起来是比较简单的 , 但是这种方式是存在问题的, 他的耦合度太高 , 扩展性很差, 而且如果项目很大 , 其实部署也很麻烦 .
分布式 就是把一个大项目 拆分成若干个小项目 .
微服务也是属于分布式的一种 , 微服务他就是对分布式进一步拆分 , 拆分得更加细致,这些服务都可以独立部署,运行 , 而微服务得好处就是 耦合度低 , 扩展性高 .

2、SpringCloud核心组件

1、注册中心

注册中心的作用就是让 各个服务把自己的信息((IP、Port))注册注册中心来,其他服务需要他们的信息,就直接可以从注册中心里边拿 . 常用注册中心组件:eureka, nacos
CAP原则
指的是 在分布式架构中, 一致性C、可用性A、分区容错性P,三者不可兼得。
一致性:当请求过来时 , 需要获取到最新的数据
可用性:只要一请求,立刻就要响应数据 , 不管数据是否是最新的数据
分区容忍性:系统中任意信息的丢失或失败不会影响系统的继续运作
eureka
采用的是 ap原则 --> 只要读数据,那么就立刻给你响应,但是他并不保证数据一定正确 .
eureka包含了两个组件: eurekaServer服务端 和 eurekaClient客户端 . 各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的 服务列表中 将会存储所有可用服务节点的信息 . 并且会把这些信息立刻写入到 readWrite 缓存中 , 此时会有一个定时器 , 会定时的把readWrite 中的数据 写到 readOnly 缓存中 , 而客户端 就会每隔一段时间(认为30秒)去readOnly中拉取下载这些信息 .
而且在应用启动后,EurekaClient会定时的向EurekaServer发送心跳(认周期为30秒), 如果EurekaServer在多个心跳周期内(认90秒)没有接受到某个节点的心跳,EurekaServer 就会把这个服务节点 从服务列表中踢除 ,
eureka他还有一个 自我保护机制, 就是为了防止由于网络原因, 导致服务端失去了客户端的连接, 从而形成的不可用, 所以在开启 自我保护机制的情况下 , 就算90秒内都没有收到这个心跳 , 现在也不会剔除掉这个服务.

nacos
nacos主要提供的功能:
1、服务的 注册, 发现, 管理 , 健康检查
2、配置中心
3、动态的DNS服务
nacos提供基于DNS协议的服务发现能力 , 支持注册在nacos上的服务以域名的方式暴露端点,让三方的应用方便发现查阅
cp --> 基于raft协议的nacos集群注册中心
nacos的cp原则 是基于 raft协议 来实现的
在nacos集群中 , 会有一个 leader节点 , 其他为 follower节点 , 当客户端需要注册信息时 , 必须要通过leader主节点来进行, 如果是找到的follower节点 , 则从节点会把这个信息转发给leader , leader就会把这个数据写入到自己的内存中 , 然后通过 2pc 方案向每个follower 发出询问, 等待各个follower的回复 , 在一定时间内如果说超过半数的follower都说可以执行, 那leader就 向每个follower发起 正式开写 的命令, 这样尽可能的保证数据的一致性. nacos他是一种观察者设计模式 , 当有服务增加 或者 挂掉后 , 注册中心会立刻感受到 , 他就会通知消费者 , 消费者就会主动的去注册中心把数据重新取下来 .
如果有follower宕机后 , 问题其实不大 . 当leader宕机后, 整个集群就会发起leader选举, 而且在leader没有选举出来之前 , 整个注册中心是无法对外提供服务的 . 选举的方式就是等待一个随机的时间 , 先超时的节点成为 Candidate , 然后这个节点就会发起投票 , 其他节点收到请求并响应 , 如果收到响应的数量超过半数 , 那么这个节点就成为新的leader .

ap --> 基于distro协议
然而nacos在AP模式下的一致性策略就类似于Eureka,采用Server之间互相的数据同步来实现数据在集群中的同步、复制操作。
健康探测
nacos健康探测机制,分成两种,临时实例、持久化实例 , 可以在服务注册加上一个参数设置 ,认为临时实例 .
临时实例 是服务认每隔5s上报一次心跳,nacos如果15s没收到心跳就标记为不健康,超过30s没收到就摘除这个服务实例.
持久化的话,nacos主动探测,20s探测一次,即使探测失败,也就是不健康状态,但是不会摘除这个服务实例
保护阈值 --> 雪崩
认情况下,不健康的实例是不返回的,但是如果健康实例比例太低,会导致健康实例请求压力过大,被打死,引发服务雪崩,所以有保护阈值的概念.
保护阈值,可以设置为0~1之间的比例,如果健康实例比例太低,则把不健康实例一起返回,此时会让很多请求走到不健康实例,会导致请求失败,但是可以避免健康实例的请求流量过大被打死,牺牲了一致性,得到了可用性
配置中心
在nacos中提供了一个配置中心,我们可以把各个服务器的配置文件都交给这个配置中心管理 , 就可以避免我们每次修改配置文件都需要重启服务器 . 当服务启动时, 各个服务先去配置中心拉取配置文件 , 和自己的配置文件配合使用 , 并且服务器对配置中心还有一个监听作用,当我们对配置中心的配置文件进行修改后 , 配置中心会自动将最新的配置文件推送给服务器 , 这样就可以实现配置文件统一管理 , 共享 . 而且我们还可以配置持久化配置文件 , 避免nacos出现挂掉后, 配置文件丢失问题 .
nacos的内核原理

2、Feign

Feign是一个非常轻量级的RPC框架
RPC:远程调用技术,当我们服务是属于进程外,早期的话非常麻烦,是用RestTemplate硬编码url路径来实现服务器之间访问的 .
而feign最大的作用就是伪装 , 他只是把service层对应的请求,伪装成http请求,后续得任何事情,包括建立tcp连接,springmvc的http请求端口暴露,tomcat请求转发 都和feign无关 .

使用Feign会导致数据丢失
场景:浏览器使用JWT技术访问服务器,会携带token,服务器解析token并查表得到对人得用户信息以及权限信息,如果访问得不止一个服务器,还会远程调用其他服务器那么token数据在使用regin远程调用得时候就不能丢失也就是说request对象得一致,不然就无法在下一个服务器进行用户认证和权限鉴定

丢数据原因:
regin远程调用得时候,首先会创建一个RequestTemplate,然后根据这个RequestTemplate再去创建一个request对象,但是RequestTemplate都是新创建出来得再用他去创建request对象得话当然就和最开始的request不一致了,这个时候原本得token数据就丢失了就无法访问下一个服务器了

解决:在使用RequestTemplate创建request对象之前,先遍历拦截器看看有没有拦截RequestTemplate得,如果有得话就在该拦截器内部,把原本得token数据再放入RequestTemplate里面,这个时候再用RequestTemplate去创建request对象就能够保持一致了

3、网关 gateway

网关的作用:
1.统一入口:是微服务唯一的入口点,网关把外部和内部隔离,保障了后台服务的安全性 .
2.鉴权校验:对用户请求做身份验证 , 权限检验 .
3.动态路由:动态的将请求路由到不同的后端集群中 .
4.减少客户端与服务的耦合,服务可以独立发展,通过网关层来做映射 .

4、熔断器 hystrix —> 解决雪崩问题

就是由于某一个服务调用过程中出现了卡顿,导致所有的服务都不能正常使用
● 超时方案
在发起调用时 , 是指一个超时时间 , 如果到了超时时间都没有响应 , 此时就服务降级 , 降级指的是请求古故障时 , 不会阻塞 , 会返回一个友好的提示 . 不会影响其他服务的执行 .
● 线程池方案
把原来的线程池拆分为多个多个小线程池 , 给每个服务都分配一个小线程池 , 用户发请求过来 , 就创建一个线程 , 当创建的线程池满了 或者 请求超时(这里和多线程线程池不一样 , 不存在阻塞队列) ,就启动服务降级 .
● 服务熔断
他就像保险丝一样 , 有多个状态 , 正常是关闭状态 ,所有请求都能正常访问 , 当出现卡顿时 , 断路器就会打开 , 此时所有请求就会被降级 , 走降级逻辑 . hystrix还会对请求情况计数 , 当一段时间内失败的次数比例超过阈值(认为50%,请求次数大于20次) , 此时断路器就会关闭 . 而且断路器的打开状态不是永久的 , 打开后会进入休眠时间(认为5s) , 然后断路器就会进入半开状态 , 此时会释放 1 次 请求通过 , 如果这个请求正常通过 , 就会关闭 , 如果不能通过 , 就再过5s休眠重试 .

Docker

docker是一个容器化技术,这个docker使用在linux上的技术,这个技术主要是为了解决
1、环境问题(可以通过docker技术从而实现让大家的环境一致)
开发环境,线上环境,测试环境
2、他怎么做到环境一致呢?
其实上他是通过镜像 去生产容器,我们要保证环境一样,其实对于部署者来说,只需要去拿到同一份镜像
2.1 下载一个镜像
2.2 只需要通过镜像去构建一个容器
3、当你去构建环境的事情,你可以使用compose技术一键生成对应的环境。
我们去学习docker,主要就是学习一下他的思想,知道他是干嘛的
第二个学习一下他的命令,怎么去下载一个镜像,怎么通过镜像去构建一个容器啊

RabbitMQ

MQ 的优点:
削峰
把消息压入RabbitMQ中缓冲系统压力。
比如现在系统只能接受2000请求,但是一下子有10000个请求过来,那么多的请求就会压在RabbitMQ中,这样缓解高并发。
异步
以前是先去发短信,再去发邮件, 这样是同步的。引入RabbitMQ之后,我们就可以进行在发短信的同时再去发邮箱。
解耦
当多个系统耦合在一起的时候,系统的消息会发送给连在一起的系统,但是这个消息有些系统可能是不需要的。所以引入了之后,很方便将这个系统进行解耦,每个系统需要的就在消息队列解耦。
MQ 的缺点
增加了系统的复杂度
rabbitmq的消息丢失
MQ消息传递是分为 3 个阶段的 :
● 生产者把消息发送到 交换机 , 交换机再发送给 Queue , 然后 Queue再把消息发给 消费者
而其中每一步都可能导致消息丢失 :

  1. 向MQ发送消息时丢失:由于在发送过程中出现网络抖动 或 程序出现异常
    ○ 生产者发送的消息未送达交换机 exchange
    ○ 消息到达交换机后未到达队列 queue
  2. 消息到达MQ 后 , MQ宕机,queue将消息丢失
  3. 消费者接收到消息后 , 未消费就宕机
    针对这些问题,RabbitMQ分别给出了解决方案:
    ● 生产者确认机制
    ○ RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
    返回结果有两种方式:
    ■ publisher-confirm,发送者确认
    ● 消息成功投递到交换机,返回ack
    ● 消息未投递到交换机,返回nack
    ■ publisher-return,发送者回执
    ● 消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。
    在我们发送失败后 , 可以选择重发
    ● mq持久化
    ○ mq消息他是存在的内存的,所以我们需要去设置持久化
    a、交换机持久化 : mq重启后就丢失 , 可以指定交换机为持久化
    b、queue的持久化 : 认是非持久化的 , mq重启后就丢失 , 可以在创建队列时 , 设置为持久化
    c、消息持久化 : 我们在发送消息时 , 可以设置消息的属性 , 将其设置为持久化
    ● 消费者确认机制
    ○ RabbitMQ是阅后即焚机制,就是当RabbitMQ确认消息 被消费者消费后会立刻删除 . 如果在消费者获取消息后 还没处理, 此时消费者宕机了 , 这样消息就丢失了 . 所以消费者返回ACK的时机就非常重要 , 我们可以把SpringAMQP的配置设置为 手动ack(就是在业务方法执行完后 , 手动调用api发送ack) , 或者设置为 自动ack(由spring监测代码是否出现异常,没有异常则返回ack;抛出异常则返回nack , MQ就会不停的重试给消费者发送消息) , 但是这个重试机制 会导致mq带来很大的压力 .
    ● 失败重试机制
    所以我们可以通知配置 开启消费者失败重试 , 设置重复失败等待时间 , 最大重试次数等参数来优化 .

消息重复消费
就是当消息被消费者成功,消费后, 在把ack返回给MQ的过程中 , 比如出现了网络抖动 , 导致ack丢失 , 这时MQ就不会收到ack , 他就会重复给消费者发发消息 , 导致消息重复消费 .
解决 :

  1. 可以使用MysqL的主键唯一来做, 消费者消费成功后 , 把这个消息id记录到MysqL , 下次消息过来时 , 先判断一下消息id存在不 , 存在的话 , 就说明已经消费过了 .
  2. 可以使用redis的setNx() 方法 , 利用他的互斥性 , 不存在key, 添加就返回 1 , 否则就返回 0

消息乱序
就是在MQ中其实是有多个queue的 , 当生产者发送消息到MQ的时候 , 这些消息会均匀的分布在多个queue中 , 如果此时我有几个消息 ,他是按照顺序排列的 , 现在他分布在多个queue中 , 而每一个queue只能被一个消费者消费 , 就会导致这几个消息会被不同的消费者消费 , ,消费顺序就得不到保证 , 这样就会导致多个消息被乱序消费了 .
解决 :
只要将有序的几个消息放到一个queue中 , 可以采用hash思想 (用消息 id % 队列数 ), 来保证多个消息落到一个queue中, 这样就可以保证被一个消费者消费 , 消息也就能 有序消费了 .
延迟队列
他作用于像订单支付 这样的功能 , 超时未支付就会自动取消订单这种需求 ,
我们可以使用 设置 ttl超时时间 + 死信队列 来实现 延迟队列
我们准备支付 —> 他会发出一条ttl = 30 分钟的时间 , 如果在30分钟内 , 用户成功消费就成功嘛 , 当30 分钟后还没有被消费者消费 , 这个消息会进行死信交换机–>死信队列 —> 我们拿到消息之后,再判断当前这个用户是否已经支付过了,如果没有支付,把这个订单 关闭了。

什么情况下放到死信交换机:
1、ttl时间到期(延迟消息)
2、queue满了
消息堆积
当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列满了 , 后面发送过来的消息就会成为死信,可能会被丢弃,这就是消息堆积问题。
有两个思路解决:
1、出现问题之后,去快速解决
a、临时去增加一些消费者 , 加快消费者的处理速度
b、我们可以在消费者这边加一个线程池,由线程池快速的去处理这些任务
但是会存在一些问题,假设说你还没有来得及处理,大批量的数据可能已经丢掉了,我们可以把抛弃的 消息放到死信队列中 , 后续人为的去做一些补偿 .
2、提前做好预防
增大我们的队列 , 采用备忘录设计模式 , 可以使用惰性队列 , 接收到消息后直接存入磁盘中 , 消费者消费消息 时, 从磁盘中读取并加载到内存
mq的高可用 —> 集群
普通集群方式
把数据分散到各个服务器中去 , 各个节点都持有其他的节点的元数据,
我们去拿数据时,可能访问的这个节点没有这个数据,我们就可以通过元数据去找到具体有数据的那个服务器然后把数据拿下来
但是致命的问题:持有数据的那个节点宕机了的话,整个数据就丢掉了
镜像集群方式(主从)
他是主从模式 , 创建这个节点的称之为这个节点的主节点,他把这个数据同步给其他节点(互相备份),其他节点实际上就是他的镜像节点
如果说要进行写操作,这个请求会转发给这个节点的主节点,主节点操作完之后,他又会同步给从节点
这种备份方式,假设某个节点宕机之后,其他节点上依然可能存在 全部的数据
当我们的主节点宕机之后,镜像节点就会变成主节点.
镜像集群的主从同步并不是强一致的,还是可能有数据丢失的风险(就是在主节点同步数据到镜像节点时宕机了)。因此在RabbitMQ的3.8版本以后:仲裁队列来代替镜像集群,底层采用Raft协议 加强主从的数据一致性。

Kafka --> 消息中间件

特点: kafka吞吐量极高,延迟低 , 一般配合大数据类进行实时数据计算,日志采集等功能
吞吐量 : 在单位时间内 , 可以处理的数据量
延迟性 : 一条数据发送到kafka, 在数据落盘后, 就是延迟性
1、Kafka如何做到高吞吐 低延迟
1.写技术
当生产者把数据推送到kafka后 , kafka 会把数据写入 page cache (缓存)中 , 然后kafka就不管后面的事情了 ,他会继续取处理生产者推送过来的信息 , cache会自动的把数据 顺序写 到磁盘 , 这个顺序写是操作系统层面的 非常快, 而且kafka采用了批处理技术 , 这种方式让kafka的写性能极高 , 吞吐量就会极高 ,
2.读技术
读取数据时 , 正常情况下是 非0拷贝, 先从缓存cache中读数据 , 没有的话就从磁盘中读取 , 然后把读取的数据放到缓存cache中 , 然后会进行 上下文切换 到系统那边, 把读取的数据拷贝到应用缓存中 , 再进行上下文切换到缓存 , 把应用缓存的数据拷贝到socket中, 发送到网卡 .
这样进行多次的上下文切换 是非常消耗时间的 , 而且过程很繁杂 . kafka采用 0拷贝 技术 , 先从缓存中读数据 , 没有的话就从磁盘读取 , 然后把数据放到缓存中 , 此时直接把数据拷贝给 网卡 , 就可以了 .
2、kafka分布式
kafka分布式存储 就是把 用户行为 分类 , 存储到不同的 topic中 , 每个topic中都有多个 分区partition . 把数据放在不同的分区上 , 多个topic就可以组成一个完整的数据.
2.1 kafka宕机时 , 还具备高可用性
kafka允许同一个 partition存在多个消息副本 , kafka会从多个副本中选举出一个leader , 这个leader负责对外提供这个partition的 数据读写, 生产者把消息发送过来后, leader就把数据同步到其他follower上 , 而且一般同一partition的副本 不会存在同一服务器上 , 因为一旦这个服务器宕机 , 那么这个partition就不可用了 .
如果此时有一个服务器宕机了 , 此时zookeeper就会通过心跳检测 , 就会检测到这个leader partition挂了 , 此时就会从follower中重新选举一个leader出来 , 这个leader就可以继续对外提供服务 , 就可以实现kafka的高可用架构.
ISR 机制
在kafka中还有一个 ISR 机制 , 就是当leader宕机,但是leader的数据还没同步到follower上去,此时即使选举了follower作为新的leader,当时的数据已经丢失了 . ISR 就是 跟leader 保持数据同步的follower数量 , 只有在 ISR列表中的follower 才能被选举为新的leader . 要想保证kafka中的数据不丢失 , 就必须保证ISR列表中至少有一个follower , 而且在一条数据写入leader后 , 必须要把该数据同步到这个ISR 中所有的 follower , 这样就能保证数据绝对不会丢失 .
2.2、如何让Kafka集群处理请求的时候实现负载均衡的效果
kafka集群会自动实现负载均衡算法 , 尽可能均匀的把请求发到集群的每一台机器上 ,认采用的是轮询算法 , 就是轮着来给分区 . 如果指定了key:对 Key值进行 Hash计算 , 来写入对应分区 .
2.3、基于ZooKeeper实现Kafka无状态可伸缩的架构设计思路
有状态存储 : 存储的内容和自身的信息有关
无状态存储 : 存储的内容和自身的信息无关
zookeeper的通知机制 :
基于他的 观察者设计模式+ 树节点来做到的
2.4、消息重复消费
broker中 , 它会根据消费者消费消息后 , 提交过来的offest 来判断消息是否消费成功 , 根据不同的返回结果 , 会有不同的策略 .
这时 如果设置的自动提交offest , 当消费者拿到消息消费成功后 , 在还没有自动提交offest 之前 , 消费者宕机了 ,这时他的offest就没有提交给broker , broker就会认为这个消息没有被消费成功 , 当消费者重启后 , 他就会把消息重新发给消费者 , 就会产生消息重复消费
解决方法 : 设置为手动提交offest , 在执行成功后, 手动提交offest .
2.5、消息乱序
如果生产者有两个消息是必须要有序执行的 , 此时根据kafka的负载均衡 , 可能就会把这两个消息 分配给两个不同的partition , 此时两个消息的执行顺序就是乱序的 , 就会出现问题 .
解决:
把需要有序执行的消息 , 放到一个partition中 ,可以设置 key , 这样两个消息就会进入一个partition , 而且topic的一个分区只会分配给一个消费组下的一个consumer来处理 , 这样就可以保证消息顺序执行 .
2.6、消息丢失

  1. leader 还没有向follower同步数据 , 此时leader 挂掉 , 重新选举的leader的数据就会不完整
    a. 利用 ISR 机制 , 强制leader的数据同步到 ISR列表中的的follower , ISR列表中的follower才能参加leader选举
  2. 生产者发送消息到kafka的过程中 , 必须要kafka确认收到消息
    a. 设置配置 ack = all : ISR列表中的follower都把这个数据同步了 , 才算发送成功
  3. 消费者拿到消息后 , 如果设置的是 自动提交offest , 那么可能消费者在一段时间内还没有消费成功 , 但是此时自动提交了offest , kafka就会认为这个消息已经消费成功 , 此时消费者又宕机了, 其实这个消息并没有消费成功 , 这个消息也就丢失了.解决方法就是 设置为手动提交offest , 在消费者成功执行完后, 手动提交offest .
    2.7、消息确认收到机制
    利用ack机制
  4. producer发送消息至kafka端 ack确认
  5. consumer从kafka中消费消息 ack确认
    acks=0 表示发送消息后就确认,不管是否成功消息 , 速度快 , 有丢失的风险
    acks=1(认值) 只要集群首领节点收到消息,生产者就会收到一个来自服务器的成功响应
    acks=all 只有当所有参与赋值的节点全部同步消息时,才表示成功 , 性能
    2.8、消息堆积
    生产者生产的消息太多 , 导致消费者消费不过来 , 或者消费者突然宕机了 , 就会导致消息堆积
    解决:
  6. 增加更多的partition分区 , 增加消费组
  7. 消费者内部加入线程池 , 加快消息的处理速度
  8. 使Kafka分区之间的数据均匀分布

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐