微服务

微服务

  • 单体架构

    • 将所有功能几中在一个项目中开发 打包成一个包部署

    • 架构简单 部署成本低

    • 团队协作成本高

    • 系统发布效率低 大型项目编译需要几十分钟 耗时长

    • 系统可用性差

    • 适合开发功能简单 规模较小的项目

  • 微服务

    • 把单体架构中的功能模块拆分成多个独立的项目
    • 粒度小
    • 团队自治
    • 服务自治 每个服务对应 一个web服务器 一个数据库

SpringCloud

集成了各种微服务功能组件 并基于SpringBoot实现了组件的自动装配 从而提供了良好的开箱即用体验

拆分目标 高内聚 每个微服务的职责尽量单一 包含业务相互关联度高 完整度高 低耦合 每个微服务功能相对独立 尽量减少对其他微服务的依赖

工程结构:

  • 独立project
  • maven聚合 一个project下好几个module

远程调用

image-20240727113255997

@RequiredArgsContructor 声明 final 的bean 能够自动注入 取消使用@Autowired注解使用

服务治理

服务提供者 暴露服务接口 供其他服务调用

服务消费者 调用其他服务提供的接口

注册中心 记录并监控微服务各实例状态 推送服务变更信息(服务提供者的心跳机制)

提供者会在启动时注册自己信息到注册中心 消费者可以从注册中心订阅和拉取服务信息

提供者有多个实例时 消费者可以通过负载均衡算法 从多个实例中选择一个

Nacos

国内企业占比最多的注册中心组件 目前已加入到SpringCloudAlibaba

  1. 配置Nacos数据表
  2. doker部署
  3. 引入依赖
  4. 配置yml nacos地址
  5. 服务消费者: 调用 DiscoveryClient API image-20240727123451360

拉取实例列表 挑选一个实例 负载均衡 获取实例的ip

OpenFegin

声明式的http客户端 基于SpringMvC常见的注解 帮我们优雅的实现http请求的发送

image-20240727124448074

  1. 引入依赖 openfegin和loadbalance
  2. 开启注解
  3. 编写feignClient

image-20240727124914944

连接池

openfeign对http请求做了伪装 底层发起http请求依赖于其他框架 默认为HttpURLConnection 不支持连接池

HTTP连接池允许客户端重用TCP连接来发送多个HTTP请求,而不是为每个请求都创建一个新的TCP连接。这不仅可以减少TCP连接的建立和销毁的开销,还可以减少由于TCP握手和SSL/TLS握手导致的延

openfegin整合okhttp的步骤

image-20240727153309479
  • 引入依赖
  • 配置文件开启连接池功能

最佳实践

微服务课程P52 !!!!

方法一 image-20240727155951160

方法二image-20240727160037147

日志输出

声明类型为 Logger.Level的Bean

public class DefaultFeignConfig{
@Bean
public Logger.Level feignLogLevel(){
return Logger.level.FUll; //四个级别
}
}

在@FeignClient或者@EnableFeignClient注解上声明 bean的位置

(configuration = DefaultFeignConfig.class)

网关

网络的关口 负责请求的路由 转发 身份校验

SpringCloudGateway

基于webFlux响应式编程

无需调优即可获得优异性能

网关路由

  1. 创建新的模块
  2. 引入网关依赖
  3. 编写启动类
  4. 配置路由规则
image-20240727173046433

image-20240727181510339

路由过滤器

image-20240727182410802

image-20240727183709992

自定义过滤器

  • GatewayFilter 路由过滤器 作用于任意指定的路由 默认不生效 要配置到路由后生效
  • GlobalFilter 全局过滤的

用户信息的传递

网关传递到微服务

首先在网关的登录校验过滤器中 把获取的用户写入请求头

拦截器里 获取到用户信息 然后保存到threadLocal里

微服务之间传递用户信息

openfeign提供了一个拦截器的接口 RequestInterceptor

image-20240729154450791

提供header API 允许对请求头进行操作

配置管理

微服务重复配置过多 维护成本高 业务配置经常变动 每次修改都要重启服务 网关路由配置写死 如果变更要重启网关

nacos 配置管理服务

配置共享

  1. 添加共享配置到nacos 包括jdbc mp 日志 swagger openfeign
  2. 基于nacosconfig拉取共享配置代替微服务的本地配置image-20240729182954077
image-20240729183013126
  1. 添加依赖 自动配置bootstrap配置文件

  2. 新建yaml文件 image-20240729183140971

配置热更新

当修改配置文件中的配置时 微服务无序重启即可使配置生效

如 token时长 验证次数等等 需要修改

  1. nacos中要有一个与微服务名有关的配置文件
  2. 微服务中要以特定的方式读取需要热更新的配置属性
  3. image-20240729184553673
  4. 推荐使用第一种 添加@Bean注解

动态路由

微服务保护和分布式事务

雪崩问题

微服务调用链路中的某个服务故障 引起整个链路中的所有微服务都不用

  • 请求限流 限制微服务的请求的并发量

即qbs

  • 线程隔离 限定每个业务能使用的线程数量而将故障业务隔离 避免一个业务将整个服务阻塞

调节并发线程数 (5个并发线程 如果单线程qps为2 那么5线程的qps为10)

  • 服务熔断 断路器统计请求的异常比例或慢调用比例 如果超出与之则会熔断该业务 拦截该接口的请求 所有请求快速失败 全都走fallback 逻辑

将feignclient作为sentinel的簇点资源 配置文件开启 feignclient.sentinel.enable:true

feignclient的fallback配置有两种方式

  1. fallbackclass 无法对远程调用的异常做处理
  2. fallbackfactory 可以对远程调用的异常做处理
    • 自定义fallbackfactory image-20240730160001049

image-20240730160108903

  • 失败处理 定义fallback逻辑 让业务失败时不再抛出异常 而是返回默认数据或友好提示

Sentinel

阿里巴巴开源的一款微服务流量控制组件

分布式事务

Seata

阿里巴巴和蚂蚁金服 共同设计的分布式事务的解决方案

  • TC(transaction coordinator)事务协调者 维护全局和分支事务的状态 协调全局事务提交或回滚
  • TM (transaction manage) 事务管理器 定义全局事务的范围 开启全局事务 提交或回滚全局事务
  • RM(resource manager)资源管理器 管理分支事务 与TC交谈以注册分支事务和报告分支事务的状态

image-20240730163932495

RabbitMQ

高性能异步通讯组件

同步调用

  • 时效性强 等待到结果才返回
  • 扩展性差 性能下降 级联失败

异步调用

  • 解除耦合 扩展性强 无需等待 性能好
  • 故障隔离 下游服务故障不影响上游业务
  • 缓存消息 流量削峰填补
  • 不会得到结果 时效性差 业务安全依赖broker的可靠性

MQ技术选型

  • RabbitMQ
  • ActiveMQ
  • RocketMQ
  • Kafka

image-20240726101452651

虚拟主机 数据隔离

操作

基于AMQP Spring AMQP 操作rabbitMQ

  • 引入 Spring boot starter amqp依赖
  • 配置yml 文件 添加 rabbitmq配置 host port virtual-host username password
  • 注入 RabbitTemplate 调用api 发消息
  • 添加注释 @RabbitListener 传入监听队列名称 方法参数存储接收的消息(spring自动进行消息与参数对象的转换)

Work Queues

任务模型 让多个消费者绑定到一个队列 共同消费队列中的消息

消费者消息推送限制

默认情况下 RabbitMQ会将消息依次轮询投递给绑定在队列上的消费者 但是这并没有考虑消费者是否已经处理完消息 可能会出现消费堆积 所以修改yml配置文件 设置preFetch为1 确保统一时刻最多投递给消费者1条消息

work模型的使用

  • 多个消费者绑定到一个队列 可以加快消息处理速度
  • 同一条消息只会被一个消费者处理
  • 通过设置prefetch来控制消费者预取的消息数量 处理完一条在处理下一条 实现能者多劳

交换机

接收publisher发送的消息 将消息按照规则路由到与之绑定的队列

  • Fanout 广播 将消息发送给所有队列

  • Direct 定向 将收到消息按照规则路由到指定的Queue

    • 每个Queue斗鱼Exchange设置一个BindingKey
    • 发布者发送消息时 指定消息的RoutingKey
    • Exchange 将消息路由到BindingKey与消息RoutingKey一致的队列
  • Topic 话题

    • 基于RoutingKey做消息路由 但是routingKey通常是多个单词的组合并且是以 . 分隔
    • Queue与Exchange指定BindingKey可以使用通配符 #代指0个或多个单词 *代指一个单词

声明队列交换机

SpringAMQP提供了几个类 同于声明 队列 交换机 及其绑定关系

  • Queue 声明队列 QueueBuilder

  • Exchange 声明交换机 ExchangeBuilder

  • Binding 声明交换机和队列的绑定关系 使用工厂类BindingBuilder构建

案例

改造余额支付功能 不再同步调用支付服务的openfegin接口 而是采用异步MQ通知交易服务更新订单状态

一些边缘业务适合使用mq处理 更新订单状态 短信通知用户 增加用户积分

RabbitMQ高级

消息可靠性问题 ·

发送者可靠性

  • 发送者重连
    • 由于网络波动 可能会出现发送者发送MQ失败的情况 通过配置可以开启连接失败后的重连机制
    • 但是SpringAMQP重连机制是阻塞式的重试 多次重连等待过程中 当前线程是被阻塞的 影响业务性能
    • 对于有业务性能需求 建议禁用重试机制 当然也可以考虑使用异步线程来执行发送消息的代码
  • 发送者确认机制
    • SpringAMQP提供了PublisherConfim和PublisherReturn两种确认机制 开启后 当发送者发送消息给MQ后 MQ会返回结果给发送者
    • 消息投递到MQ 但是路由失败 返回 ACK
    • 临时消息投递到MQ 并且入队成功 返回ACK
    • 持久消息投递到MQ 并且入队完成持久化 返回ACK
    • 其他情况返回NACK 告知投递失败
    • 开启机制三种模式
      • none 关闭
      • simple 同步阻塞等待MQ回执
      • correlated MQ异步回调方式返回回执消息

MQ可靠性

默认情况MQ会把消息存储在内存降低消息收发的延迟

一旦MQ宕机 内存中的消息会丢失 并且内存空间有限 当消费者故障或者处理过慢 会导致消息挤压

开启数据持久化 会存入磁盘 但是会导致性能下降 无法应对高并发

LazyQueue 惰性队列

接收消息后直接存入磁盘不再存入内存 消费者要消费时才会从磁盘中读取并加载到内存(可以提前缓存部分消息到内存 最多2048条)

在3.12版本后 所有队列都是LazyQUeue模式无法更改

消费者可靠性

消费者确认机制

当消费者处理消息结束后 向MQ发送一个回执 告知MQ自己消息处理的状态

  • ack 成功处理消息 MQ从队列中删除消息
  • nack 消息处理失败 MQ再次投递消息
  • reject 消息处理失败并拒绝该消息 MQ从队列中删除该消息

springAMQP实现了消息确认机制 配置文件开启

  • none 不处理 消息投递之后立刻ack 消息从mq中删除
  • manual 手动模式 需要自己在业务代码中调用api 发送ack或者reject 存在业务入侵 但灵活
  • auto 自动模式 SpringAMQP利用AOP对消息处理逻辑做了环绕增强 当业务正常时自动返回ack
    • 业务异常 自定返回nack
    • 消息处理或校验异常 返回reject

消费者失败重试机制

配置文件开启重试机制 如果重试次数达到上限 则需要MessagesRecoverer接口来处理 包含三种不同的实现:

  • RejectAndDontRequeueRecoverer 重试次数耗尽后 直接reject 丢弃消息 默认该方法
  • ImmediateRequeueMessageRecoverer 重试次数耗尽后 返回nack 消息重新入队
  • RepublishMessageRecoverer 重试后 将失败的消息投递到指定的交换机

业务幂等性

f(f(x))=f(x)在程序开发中 指同一个业务 执行一次或多次对业务的状态的影响是一致的

唯一消息id
给每个消息都设置一个唯一id liyongid区分是否是重复消息
  1. 每一条消息都生成一个唯一的id 与消息一起投递给消费者
  2. 消费者接收后处理自己的业务 业务处理成功后将消息id存入数据库
  3. 下次收到同样的消息 数据库查询是否存在

image-20240807143141817

业务判断
结合业务逻辑 基于业务本身做判断

面试题:如何确保支付服务和交易服务之间的订单状态一致性

  • 首先支付服务会在用户支付成功后利用MQ通知交易服务 完成订单状态同步
  • 其次 为了保证MQ消息可靠性 我们采用生产者确认机制 消费者确认 消费者失败重试等策略 确保消息投递和处理的可靠性 同时也开启了MQ的持久化 避免因为服务宕机导致消息丢失
  • 左后我们还在交易服务更新订单状态时做了业务幂等判断 避免因为重复消息导致订单状态异常

延迟消息

在网络异常等特殊情况

发送者发送消息指定一个时间 消费者不会立刻收到消息 而是在指定时间后才收到消息

image-20240807144837853

死信交换机

死信:

  • 消费者使用reject或者nack声明消费失败 并且消费的requere参数设置为false
  • 消息是一个过期消息(达到了队列或消息本身设置的过期时间)超时无人消费
  • 要投递的队列消息堆积满了 最早的消息可能成为死信

如果队列通过dead-letter-exchange 属性制定了一个交换机 那么该队列中的死信就会投递到这个交换机中 这个交换机成为死信交换机

image-20240807195123756

延迟消息插件

将普通交换机改为支持延迟消息功能的交换机 当消息投递到交换机可以暂存一定时间 到期后再投递到队列

取消超时订单

Elasticsearch