微服务面试
微服务原理
分布式事务
CAP和BASE
CAP定理 就是一致性(Consistency)可用性(Availability) 分区容错性(Partition tolerance)不能同时满足 最多满足俩 就是CAP定理
- 一致性 让用户访问分布式系统的任一节点 数据必须一致
- 可用性 用户访问分布式系统时 读或写操作都能成功 叫可用性 如果有其中一个不能访问或拒绝请求 叫弱可用或不可用
- 分区 因为网络故障或其他原因导致分布式系统中节点与其他节点断联 形成独立分区
- 容错 系统要能容忍网络分区现象 出现分区也要对外提供服务
P是一定要满足的 但是满足了P只会有CP AP 如果要力保完美 就有了BASE定理
BASE定理
BASE是解决CAP的思路
- Basica Available(基本可用) 分布式系统出现故障时 允许损失部分可用 保证核心可用即可
- Soft State(软状态) 在一定时间内 允许出现中间状态 比如临时的不一致状态
- Eventually Consistent(最终一致性) 虽然无法保证强一致性 但是在软状态结束后 最终达到数据一致即可
而分布式事务就可以借鉴CAP定理和BASE定理
- CP模式 各个子事务执行后互相等待 同时提交 同时回滚 达成强一致 但是等待过程中 处于弱可用状态 XC模式
- AP模式 各个子事务分别执行和提交 允许出现结果不一致 后续补救恢复数据即可 实现最终一致 AT模式
AT模式的脏写问题
就是当事务在进行修改的时候 事务一获取DB锁 保存快照 执行并释放锁 在二阶段提交还是回滚之前 事务二进来获取了锁 又执行了一遍业务 保存快照释放锁 然后这时候事务一进到二阶段 获取锁并恢复了快照 这时候导致本来该是90的数据变成了100 导致出现丢失更新的问题
为了解决这个问题 AT模式引入了全局锁 全局锁有Seata管理 让TC记录当前正在操作某行的数据 并上锁 只锁这一行的数据

流程就发生改变了 事务一先获取DB锁执行完业务之后提交之前获取全局锁 再提交 释放DB锁 然后事务二拿到DB锁 是可以执行业务的 但是获取不到全局锁 无法提交事务 DB锁也没释放因此数据未发生改变 会一直重试全局锁 然后事务一会一直等待DB锁 事务二一致获取全局锁 但是全局锁重试时间短 事务二很快释放 然后事务一拿到锁 正常回滚 释放全局锁
这个过程中 就保证了不会丢失更新的问题 跟XC的模式的区别就是全局锁获取的只是当前行的锁 并不会影响表中其他数据 而XC是整个DB锁一直在等待 无法操作
但是同样有问题是全局锁是由Seata管理的 如果有个事务没Seata管理 还操作了当前行 是拦不住的
AT为了解决该问题 对快照备份了两份 一份是修改之前数据的快照 一份是修改之后的数据快照 到最后回滚的时候 会拿第二份快照和数据库数据进行对比 如果发现不一样 就会发出警告 记录日志 人工介入
TCC模式
TCC和AT类似 一阶段都是独立事务 执行完直接提交 二阶段TCC是用人工编码方式来恢复数据 不需要快照逻辑 因此性能更好 需要实现三个方法
- Try 资源的检测和预留
- Confirm 完成资源操作业务 要求Try成功 Confirm一定要成功
- Cancel 预留资源释放 理解为Try的反向操作
TCC工作模型图如下

TCC优点
- 一阶段完成直接提交事务 释放数据库资源 性能好
- 相比AT模型 无序生成快照 不用使用全局锁 性能好
- 不依赖数据库事务 依赖补偿操作 可以用于非事务型数据库 比如Redis
TCC缺点
- 有代码侵入 认为编写Try Confirm Cancel接口 麻烦
- 软状态 事务是最终一致
- 需要考虑Confirm和Cancel的失败情况 因为失败了Seata会重试 所以需要做好幂等处理
最大努力通知
最大努力通知是一种最终一致性的分布式事务解决方案 就是通过消息通知的方式来通知事务参与者完成业务执行 如果执行失败会多次通知 不用任何分布式事务组件介入

注册中心
环境隔离
企业开发中 同一个项目可能有多个环境 开发 测试 发布环境 因此需要对不同环境做隔离

可以通过配置文件来指定命名空间

分级模型
就是通过Map来定义的层级关系 第一个Map的Key是namespace 值又是一个Map 这个Map下的key是一个group::serviceName拼起来 值是service实例 然后实例里面还有一个map 来存放集群信息 key是集群 值就是Set集合 来保存不同实例的IP

Eureka和nacos
Nacos和Eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
区别
- Nacos的心跳检测是5秒一次 Eureka是30s一次 nacos支持服务端主动检测提供者状态 不过这种情况只有永久实例才会采用
- 临时实例心跳不正常会被剔除 永久实例不会被剔除
- Nacos支持服务列表变更的消息推送模式 服务列表更新及时 而Eureka只能每隔30s去拉取服务
- Nacos集群默认采用AP模式 也支持CP Eureka采用AP方式
远程调用
负载均衡原理
从SpringCloud2020版本开始 就弃用了Ribbon 改用Spring自己开源的SpringCloudLoadBalancer
OpenFeign再整合时 负载均衡流程如下
- 获取serviceId 服务名称
- 根据serviceId拉取服务列表
- 用负载均衡算法选择一个服务
- 重构请求URL路径 发起远程调用
服务保护
线程隔离
线程隔离有两种方式实现
- 线程池隔离(Hystix默认采用)
每一个单独的服务都有一个线程池来规划服务的调用 更加灵活也更方便控制具体线程的执行 但是如果依赖服务过多 管控资源就会很麻烦 甚至不可避免的浪费资源 并且线程过多会带来额外的CPU开销 性能一般 隔离性好 - 信号量隔离(Sentinel默认采用)
采用信号量隔离 不用创建线程池 直接在服务调用时根据计数器实现 性能较好但是隔离性一般
滑动窗口算法
固定窗口计数器算法
- 把时间划分成多个窗口 每个窗口时间跨度称为Interval 例如1000ms
- 每个窗口分别计数统计 有一次请求就加1 限流就是设置计数器阈值 比如3
- 如果超出了阈值 则超出的部分会被丢弃
但是这样分会有问题 因为把时间固定成了窗口 但是时间是连续的 如果有三个请求在后半秒到 又有三个在下一秒的前半秒到 这样的话一秒内是6个请求 但是在固定窗口看来并不是 因此可优化为滑动窗口
滑动窗口计数器算法
滑动窗口计数器算法会将一个窗口划分为n个更小的区间
- 窗口跨度Interval为1s 区间数量=2 则每个小区间跨度是500ms 每个区间都有计数器
- 限流阈值依然是3 时间窗口一秒内超出 就会抛弃该请求
- 窗口会根据当前请求到达的时间(currentTime)移动 范围就是从当前时间减去时间跨度(1秒)(currentTime - Interval)之后的第一个时区开始 到currentTime所在时区结束
比如900ms来了个请求 然后减去1s是-100ms 下一个时区是0-500ms 因此就是第一第二个时区区间统计 为3个 没有超出 但是仍然有问题 比如700ms-1700ms 有四个请求 解决思路就是提高区间数量 数量越大 精度越高 但是CPU性能越差
漏桶算法
漏桶算法
- 把每个请求视作“水滴”放入“漏桶”进行存储
- 漏桶以固定速率向外漏出请求来执行 如果漏桶内空了就停止漏水
- 如果漏桶满了就把多余水滴丢弃
- 可以理解为请求在桶内排队等待
就类似于漏斗 整流qps 而Sentinel内部就基于漏桶算法实现了排队等待的效果 桶的容量决定限流的QPS阈值以及允许等待的最大超时时间
比如限流QPS=5 超时时间是2000ms QPS=5 每秒五个请求 那么一个请求是200ms 那么第一个请求等待0ms 第二个请求等待200ms 第三个400ms以此类推 就可以根据最大等待时长以及QPS算出桶的容量
Sentinel实现漏桶算法是用队列实现

令牌桶算法
令牌桶算法
- 以固定速率生成令牌 存入令牌桶中 如果令牌桶满了之后 停止生成
- 请求进入后 必须先尝试从桶中获取令牌 获取到令牌之后才可以被处理
- 如果令牌桶中没有令牌 则请求等待或抛弃
实现起来就是用计数器 计数器固定速率增长 然后请求来了计数器减1 因此令牌桶算法实现起来简单 成本低 但是问题是如果第一秒没有请求 然后一秒末来了十个请求 全消耗完令牌 第二秒又来十个 这样就超出了规定的令牌数量 因此令牌桶不适合于QPS忽高忽低的情况 令牌桶用于热点参数限流 只对某一个参数进行限流 参数比较多 就用令牌桶 实现起来成本低
Sentinel限流和Gateway限流有什么差别
限流算法有三种实现 令牌桶 滑动窗口 漏桶算法 Gateway是基于Redis实现的令牌桶算法 用计数器加一减一来实现流控
而Sentinel内部比较复杂
- 默认采用滑动窗口算法 另外Sentinel熔断的技术也是基于滑动窗口算法
- 限流后可以快速失败或排队等待 排队等待是基于漏桶算法
- 还有热点参数限流是基于令牌桶算法

浙公网安备 33010602011771号