微服务

微服务
Acting微服务
单体架构
将所有功能几中在一个项目中开发 打包成一个包部署
架构简单 部署成本低
团队协作成本高
系统发布效率低 大型项目编译需要几十分钟 耗时长
系统可用性差
适合开发功能简单 规模较小的项目
微服务
- 把单体架构中的功能模块拆分成多个独立的项目
- 粒度小
- 团队自治
- 服务自治 每个服务对应 一个web服务器 一个数据库
SpringCloud
集成了各种微服务功能组件 并基于SpringBoot实现了组件的自动装配 从而提供了良好的开箱即用体验
拆分目标 高内聚 每个微服务的职责尽量单一 包含业务相互关联度高 完整度高 低耦合 每个微服务功能相对独立 尽量减少对其他微服务的依赖
工程结构:
- 独立project
- maven聚合 一个project下好几个module
远程调用
@RequiredArgsContructor 声明 final 的bean 能够自动注入 取消使用@Autowired注解使用
服务治理
服务提供者 暴露服务接口 供其他服务调用
服务消费者 调用其他服务提供的接口
注册中心 记录并监控微服务各实例状态 推送服务变更信息(服务提供者的心跳机制)
提供者会在启动时注册自己信息到注册中心 消费者可以从注册中心订阅和拉取服务信息
提供者有多个实例时 消费者可以通过负载均衡算法 从多个实例中选择一个
Nacos
国内企业占比最多的注册中心组件 目前已加入到SpringCloudAlibaba
- 配置Nacos数据表
- doker部署
- 引入依赖
- 配置yml nacos地址
- 服务消费者: 调用 DiscoveryClient API
拉取实例列表 挑选一个实例 负载均衡 获取实例的ip
OpenFegin
声明式的http客户端 基于SpringMvC常见的注解 帮我们优雅的实现http请求的发送
- 引入依赖 openfegin和loadbalance
- 开启注解
- 编写feignClient
连接池
openfeign对http请求做了伪装 底层发起http请求依赖于其他框架 默认为HttpURLConnection 不支持连接池
HTTP连接池允许客户端重用TCP连接来发送多个HTTP请求,而不是为每个请求都创建一个新的TCP连接。这不仅可以减少TCP连接的建立和销毁的开销,还可以减少由于TCP握手和SSL/TLS握手导致的延
openfegin整合okhttp的步骤
- 引入依赖
- 配置文件开启连接池功能
最佳实践
微服务课程P52 !!!!
方法一
方法二
日志输出
声明类型为 Logger.Level的Bean
public class DefaultFeignConfig{ |
在@FeignClient或者@EnableFeignClient注解上声明 bean的位置
(configuration = DefaultFeignConfig.class) |
网关
网络的关口 负责请求的路由 转发 身份校验
SpringCloudGateway
基于webFlux响应式编程
无需调优即可获得优异性能
网关路由
- 创建新的模块
- 引入网关依赖
- 编写启动类
- 配置路由规则
路由过滤器
自定义过滤器
- GatewayFilter 路由过滤器 作用于任意指定的路由 默认不生效 要配置到路由后生效
- GlobalFilter 全局过滤的
用户信息的传递
网关传递到微服务
首先在网关的登录校验过滤器中 把获取的用户写入请求头
拦截器里 获取到用户信息 然后保存到threadLocal里
微服务之间传递用户信息
openfeign提供了一个拦截器的接口 RequestInterceptor
提供header API 允许对请求头进行操作
配置管理
微服务重复配置过多 维护成本高 业务配置经常变动 每次修改都要重启服务 网关路由配置写死 如果变更要重启网关
nacos 配置管理服务
配置共享
- 添加共享配置到nacos 包括jdbc mp 日志 swagger openfeign
- 基于nacosconfig拉取共享配置代替微服务的本地配置
添加依赖 自动配置bootstrap配置文件
新建yaml文件
配置热更新
当修改配置文件中的配置时 微服务无序重启即可使配置生效
如 token时长 验证次数等等 需要修改
- nacos中要有一个与微服务名有关的配置文件
- 微服务中要以特定的方式读取需要热更新的配置属性
- 推荐使用第一种 添加@Bean注解
动态路由
微服务保护和分布式事务
雪崩问题
微服务调用链路中的某个服务故障 引起整个链路中的所有微服务都不用
- 请求限流 限制微服务的请求的并发量
即qbs
- 线程隔离 限定每个业务能使用的线程数量而将故障业务隔离 避免一个业务将整个服务阻塞
调节并发线程数 (5个并发线程 如果单线程qps为2 那么5线程的qps为10)
- 服务熔断 断路器统计请求的异常比例或慢调用比例 如果超出与之则会熔断该业务 拦截该接口的请求 所有请求快速失败 全都走fallback 逻辑
将feignclient作为sentinel的簇点资源 配置文件开启 feignclient.sentinel.enable:true
feignclient的fallback配置有两种方式
- fallbackclass 无法对远程调用的异常做处理
- fallbackfactory 可以对远程调用的异常做处理
- 自定义fallbackfactory
- 自定义fallbackfactory
- 失败处理 定义fallback逻辑 让业务失败时不再抛出异常 而是返回默认数据或友好提示
Sentinel
阿里巴巴开源的一款微服务流量控制组件
分布式事务
Seata
阿里巴巴和蚂蚁金服 共同设计的分布式事务的解决方案
- TC(transaction coordinator)事务协调者 维护全局和分支事务的状态 协调全局事务提交或回滚
- TM (transaction manage) 事务管理器 定义全局事务的范围 开启全局事务 提交或回滚全局事务
- RM(resource manager)资源管理器 管理分支事务 与TC交谈以注册分支事务和报告分支事务的状态
RabbitMQ
高性能异步通讯组件
同步调用
- 时效性强 等待到结果才返回
- 扩展性差 性能下降 级联失败
异步调用
- 解除耦合 扩展性强 无需等待 性能好
- 故障隔离 下游服务故障不影响上游业务
- 缓存消息 流量削峰填补
- 不会得到结果 时效性差 业务安全依赖broker的可靠性
MQ技术选型
- RabbitMQ
- ActiveMQ
- RocketMQ
- Kafka
虚拟主机 数据隔离
操作
基于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个或多个单词*代指一个单词
- 基于RoutingKey做消息路由 但是routingKey通常是多个单词的组合并且是以
声明队列交换机
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区分是否是重复消息
- 每一条消息都生成一个唯一的id 与消息一起投递给消费者
- 消费者接收后处理自己的业务 业务处理成功后将消息id存入数据库
- 下次收到同样的消息 数据库查询是否存在
业务判断
结合业务逻辑 基于业务本身做判断
面试题:如何确保支付服务和交易服务之间的订单状态一致性
- 首先支付服务会在用户支付成功后利用MQ通知交易服务 完成订单状态同步
- 其次 为了保证MQ消息可靠性 我们采用生产者确认机制 消费者确认 消费者失败重试等策略 确保消息投递和处理的可靠性 同时也开启了MQ的持久化 避免因为服务宕机导致消息丢失
- 左后我们还在交易服务更新订单状态时做了业务幂等判断 避免因为重复消息导致订单状态异常
延迟消息
在网络异常等特殊情况
发送者发送消息指定一个时间 消费者不会立刻收到消息 而是在指定时间后才收到消息
死信交换机
死信:
- 消费者使用reject或者nack声明消费失败 并且消费的requere参数设置为false
- 消息是一个过期消息(达到了队列或消息本身设置的过期时间)超时无人消费
- 要投递的队列消息堆积满了 最早的消息可能成为死信
如果队列通过dead-letter-exchange 属性制定了一个交换机 那么该队列中的死信就会投递到这个交换机中 这个交换机成为死信交换机
延迟消息插件
将普通交换机改为支持延迟消息功能的交换机 当消息投递到交换机可以暂存一定时间 到期后再投递到队列


























