微服务

该笔记基于b站黑马程序员Java面试题视频制作



一. Spring Cloud

1. Spring Cloud的5大组件有哪些?

  • Eureka:注册中心
  • Ribbon:负载均衡
  • Feign:远程调用
  • Hystrix:服务熔断
  • Zuul/Gateway:网关

随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件

  • 注册中心:Nacos
  • 负载均衡:Ribbon
  • 服务调用:Feign
  • 服务保护:sentinel
  • 服务网关:Gateway


2. Spring Cloud是如何实现服务注册发现

  • 注册中心的核心作用是:服务注册和发现
  • 常见的注册中心:eureka、nacos、zookeeper

Eureka的作用


Nacos的作用


服务注册和发现是什么意思?Spring Cloud 如何实现服务注册发现?

  • 我们当时学习的时候采用的eureka作为注册中心,这个也是spring cloud体系中的一个核心组件
  • 服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等等
  • 服务发现:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用
  • 服务监控:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除

Nacos和Eureka的区别

  • Nacos与eureka的共同点(注册中心)
    1. 都支持服务注册和服务拉取
    2. 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别(注册中心)
    1. Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
    2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    3. Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    4. Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
  • Nacos还支持了配置中心,eureka则只有注册中心,也是选择使用nacos的一个重要原因


3. 负载均衡如何实现的

  • 负载均衡 Ribbon,发起远程调用feign就会使用Ribbon
  • Ribbon负载均衡策略有哪些 ?
  • 如果想自定义负载均衡策略如何实现 ?

Ribbon负载均衡流程


Ribbon负载均衡策略

  • RoundRobinRule:简单轮询服务列表来选择服务器
  • WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
  • RandomRule:随机选择一个可用的服务器
  • BestAvailableRule:忽略那些短路的服务器,并选择并发数较低的服务器
  • RetryRule:重试机制的选择逻辑
  • AvailabilityFilteringRule:可用性敏感策略,先过滤非健康的,再选择连接数较小的实例
  • ZoneAvoidanceRule:以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询

自定义负载均衡的实现

可以自己创建类实现IRule接口 , 然后再通过配置类或者配置文件配置即可 ,通过定义IRule实现可以修改负载均衡规则,有两种方式:


负载均衡如何实现的?

微服务的负载均衡主要使用了一个组件Ribbon,比如,我们在使用feign远程调用的过程中,底层的负载均衡就是使用了ribbon


Ribbon负载均衡策略有哪些?

  • RoundRobinRule:简单轮询服务列表来选择服务器
  • WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
  • RandomRule:随机选择一个可用的服务器
  • ZoneAvoidanceRule:区域敏感策略,以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询(默认)

如果想自定义负载均衡策略如何实现?

提供了两种方式:

  1. 创建类实现IRule接口,可以指定负载均衡策略(全局)
  2. 在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略(局部)


4. 什么是服务雪崩

服务雪崩:一个服务失败,导致整条链路的服务都失败的情形
解决方法: 熔断降级

服务降级

服务降级是服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃

1
2
3
4
5
@FeignClient(value = "leadnews-article", fallback = IArticleClientFallback.class)
public interface IArticleClient {
@PostMapping("/api/v1/article/save")
public ResponseResult saveArticle(@RequestBody ArticleDto dto);
}
1
2
3
4
5
6
7
@Component
public class IArticleClientFallback implements IArticleClient {
@Override
public ResponseResult saveArticle(ArticleDto dto) {
return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "获取数据失败");
}
}

服务熔断

Hystrix 熔断机制,用于监控微服务调用情况, 默认是关闭的,如果需要开启需要在引导类上添加注解:@EnableCircuitBreaker
如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求


什么是服务雪崩,怎么解决这个问题?

  • 服务雪崩:一个服务失败,导致整条链路的服务都失败的情形
  • 服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑
  • 服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求


5. 微服务是怎么监控的

skywalking

  • 服务(service):业务资源应用系统(微服务)
  • 端点(endpoint):应用系统对外暴露的功能接口(接口)
  • 实例(instance):物理机

微服务是怎么监控的?

我在学习的时候采用的skywalking进行监控的

  1. skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的分析和优化。
  2. 我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的bug情况,第一时间修复


二. 业务相关

1. 限流的实现

为什么要限流?
并发量大(突发流量)、防止用户恶意刷接口

限流的实现方式:

  • Tomcat:可以设置最大连接数
  • Nginx,漏桶算法
  • 网关,令牌桶算法
  • 自定义拦截器

Nginx限流

控制速率(突发流量)

1
2
3
4
5
6
7
8
9
10
11
http {
limit_req_zone $binary_remote_addr zone=servicelRateLimit:10m rate=10r/s
server {
listen 80;
server_name localhost;
location / {
limit_req_zone = servicelRateLimit burst=20 nodelay;
proxy_pass http://targetserver;
}
}
}
  • 语法: limit_req_zone key zone rate
  • key:定义限流对象,binary_remote_addr就是一种key,基于客户端ip限流
  • Zone:定义共享存储区来存储访问信息,10m可以存储16wip地址访问信息
  • Rate:最大访问速率,rate=10r/s 表示每秒最多请求10个请求
  • burst=20:相当于桶的大小
  • Nodelay:快速处理

控制并发连接数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
http {
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
listen 80;
server_name localhost;
location / {
...
limit_conn perip 20;
limit_conn perserver 100;
proxy_pass http://targetserver;
}
}
}
  • limit_conn perip 20:对应的key是 $binary_remote_addr,表示限制单个IP同时最多能持有20个连接。
  • limit_conn perserver 100:对应的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数

网关限流

yml配置文件中,微服务路由设置添加局部过滤器RequestRateLimiter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: gateway-consumer
uri: lb://GATEWAY-CONSUMER
predicates:
- Path=/order/**
filters:
- name: RequestRateLimiter
args:
# 使用SpEL从容器中获取对象
key-resolver: '#{@pathKeyResolver}'
# 命令桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 命令桶的上限
redis-rate-limiter.burstCapacity: 3

  • key-resolver :定义限流对象( ip 、路径、参数),需代码实现,使用spel表达式获取
  • replenishRate :令牌桶每秒填充平均速率。
  • urstCapacity :令牌桶总容量。


2. CAP和BASE

CAP定理

1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标:

  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance (分区容错性)

Eric Brewer 说,分布式系统无法同时满足这三个指标
这个结论就叫做 CAP 定理。


Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致


Availability (可用性):用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝


Partition(分区):因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。
Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务


  • 分布式系统节点之间肯定是需要网络连接的,分区(P)是必然存在的
  • 如果保证访问的高可用性(A),可以持续对外提供服务,但不能保证数据的强一致性–> AP
  • 如果保证访问的数据强一致性(C),就要放弃高可用性 –> CP

BASE理论

BASE理论是对CAP的一种解决思路,包含三个思想:

  • BasicallyAvailable (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用
  • Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态
  • Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致

解释一下CAP和BASE

  • CAP 定理(一致性、可用性、分区容错性)
    1. 分布式系统节点通过网络连接,一定会出现分区问题(P)
    2. 当分区出现时,系统的一致性(C)和可用性(A)就无法同时满足
  • BASE理论
    1. 基本可用
    2. 软状态
    3. 最终一致
  • 解决分布式事务的思想和模型
    1. 最终一致思想:各分支事务分别执行并提交,如果有不一致的情况,再想办法恢复数据(AP)
    2. 强一致思想:各分支事务执行完业务不要提交,等待彼此结果。而后统一提交或回滚(CP)


3. 分布式事务解决方案

你采用哪种分布式事务解决方法?

  • Seata框架(XA、AT、TCC)
  • MQ

Seata框架

Seata事务管理中有三个重要的角色:

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata的XA模式

RM一阶段的工作:

  1. 注册分支事务到TC
  2. 执行分支业务SQL但不提交
  3. 报告执行状态到TC

TC二阶段的工作:

  • TC检测各分支事务执行状态
    • 如果都成功,通知所有RM提交事务
    • 如果有失败,通知所有RM回滚事务

RM二阶段的工作:

  • 接受TC指令,提交或回滚事务

AT模式原理

AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

TCC模式原理

  1. Try:资源的检测和预留;
  2. Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
  3. Cancel:预留资源释放,可以理解为try的反向操作。

MQ分布式事务

MQ模式实现分布式事务,在A服务写数据的时候,需要在同一个事务内发送消息到另外一个事务,异步,性能最好



4. 分布式服务的接口幂等性设计

幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致

需要幂等场景

  • 用户重复点击(网络波动)
  • MQ消息重复
  • 应用使用失败或超时重试机制

接口幂等

基于RESTful API的角度对部分常见类型请求的幂等性特点进行分析

请求方式 说明
GET 查询操作,天然幂等
POST 新增操作,请求一次与请求多次造成的结果不同,不是幂等的
PUT 更新操作,如果是以绝对值更新,则是幂等的。如果是通过增量的方式更新,则不是幂等的
DELETE 删除操作,根据唯一值删除,是幂等的

token+redis

创建商品、提交订单、转账、支付等操作


分布式锁

  • 快速失败(抢不到锁的线程)
  • 控制锁的力度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void saveOrder(Item item) throws InterruptedException {
//获取锁(重入锁),执行锁的名称
RLock lock = redissonClient.getLock("heimalock");
//尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
try {
//判断是否获取成功
if (!isLock) {
log.info("下单操作获取锁失败,order:{}",item);
throw new RuntimeException("新增或修改失败");
}
//下单操作
} finally {
//释放锁
lock.unlock();
}
}

分布式服务的接口幂等性如何设计?

  • 幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致
  • 如果是新增数据,可以使用数据库的唯一索引
  • 如果是新增或修改数据
    • 分布式锁,性能较低
    • 使用token+redis来实现,性能较好
      • 第一次请求,生成一个唯一token存入redis,返回给前端
      • 第二次请求,业务处理,携带之前的token,到redis进行验证,如果存在,可以执行业务,删除token;如果不存在,则直接返回,不处理业务


5. 分布式任务调度

XXL-JOB解决的问题

  • 解决集群任务的重复执行问题
  • cron表达式定义灵活
  • 定时任务失败了,重试和统计
  • 任务量大,分片执行

XXL-JOB路由策略有哪些?

  1. FIRST(第一个):固定选择第一个机器;
  2. LAST(最后一个):固定选择最后一个机器;
  3. ROUND(轮询)
  4. RANDOM(随机):随机选择在线的机器;
  5. CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
  6. LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
  7. LEAST_RECENTLY_USED(最近最久未使用):最久未使用的机器优先被选举;
  8. FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
  9. BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
  10. SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;

XXL-JOB任务执行失败怎么解决?

故障转移+失败重试,查看日志分析—-> 邮件告警


如果有大数据量的任务同时都需要执行,怎么解决?

执行器集群部署时,任务路由策略选择分片广播情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务

分片参数

  • index:当前分片序号(从0开始),执行器集群列表中当前执行器的序号;
  • total:总分片数,执行器集群的总机器数量;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@XxlJob("shadingSample")
public void shardingJobHandler() throws Exception {
// 分片参数
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();
XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
// 业务逻辑
List<Integer> list = getList();
for (Integer integer : list) {
if(integer % shardTotal == shardIndex){
System.out.println("第"+shardIndex+"分片执行,执行数据为:"+integer);
}
}
}

xxl-job路由策略有哪些?
xxl-job提供了很多的路由策略,我们平时用的较多就是:轮询、故障转移、分片广播…

xxl-job任务执行失败怎么解决?

  • 路由策略选择故障转移,使用健康的实例来执行任务
  • 设置重试次数
  • 查看日志+邮件告警来通知相关负责人解决

如果有大数据量的任务同时都需要执行,怎么解决?

  • 让多个实例一块去执行(部署集群),路由策略分片广播
  • 在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行

微服务
https://lzhengjy.github.io/2023/10/13/微服务面试题/
作者
Zheng
发布于
2023年10月13日
许可协议