写在前面
🍁个人主页:HNUJSY
✨本期专栏:《史上最全经典面试题总结》欢迎订阅学习~
📌Xmind文件获取:GitHub 持续更新中,别忘了 star 喔~
目录
「Java学习+面试指南」思维导图,计算机自学指南,包括Java基础、JVM、数据库、mysql、redis、计算机网络、算法、数据结构、操作系统等,后台技术栈/架构师之路/全栈开发社区,阿里,腾讯,百度,美团,头条等春招/秋招/校招/面试
思维导图(png格式可下载放大)
分布式系统
系统拆分
为什么
- 功能耦合
- 开发效率极其低下
如何
- 先拆分一轮,后面如果系统更复杂了,可以继续分拆
dubbo
工作流程
- 第一步:provider 向注册中心去注册
- 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
- 第三步:consumer 调用 provider
- 第四步:consumer 和 provider 都异步通知监控中心
- 生产工作、服务注册、注册中心、服务发现、消费者、代理通信、负载均衡
通信协议
- 序列化
- 把数据结构或者是一些对象,转换为二进制串的过程
- 反序列化是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
- 序列化协议
- hession、Java 二进制序列化、json、SOAP 文本序列化多种序列化协议
- hessian 是其默认的序列化协议
- hession、Java 二进制序列化、json、SOAP 文本序列化多种序列化协议
- dubbo 协议
- 单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议
- 传输数据量小(每次请求在 100kb 以内),但是并发量很高,以及服务消费者机器数远大于服务提供者机器数的情况
- rmi
- 阻塞式短连接和 JDK 标准序列化
- 多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用
- hessian
- 底层采用 Http 通讯,采用 Servlet 暴露服务
Dubbo 缺省内嵌 Jetty 作为服务器实现 - hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用
- 底层采用 Http 通讯,采用 Servlet 暴露服务
- http
- 基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现。走表单序列化
- PB
- 结构化数据串行化方法
- 类似 JSON, XML
- 结构化数据串行化方法
- PB 的效率是最高的
- 使用 proto 编译器,自动进行序列化和反序列化,速度非常快
- 数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。
负载均衡策略
- RandomLoadBalance默认
- 随机调用实现负载均衡,可以对 provider 不同实例设置不同的权重,会按照权重来负载均衡
- 假设有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为 10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。
- RoundRobinLoadBalance
- 均匀地将流量打到各个机器上去
- LeastActiveLoadBalance
- 最小活跃数负载均衡
- 活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求,那么此时请求会优先分配给该服务提供者
- 每个服务提供者会对应着一个活跃数 active。初始情况下,所有服务提供者的 active 均为 0。每当收到一个请求,对应的服务提供者的 active 会加 1,处理完请求后,active 会减 1。所以,如果服务提供者性能较好,处理请求的效率就越高,那么 active 也会下降的越快。因此可以给这样的服务提供者优先分配请求。
- 在实现上还引入了权重值。所以准确的来说,LeastActiveLoadBalance 是基于加权最小活跃数算法实现的。
- 最小活跃数负载均衡
- ConsistentHashLoadBalance
- 一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。
- 不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性 Hash 策略
集群容错策略
- Failover Cluster 模式
- 失败自动切换,自动重试其他机器,默认就是这个,常见于读操作。(失败重试其它机器)
- Failfast Cluster 模式
- 一次调用失败就立即失败,常见于非幂等性的写操作,比如新增一条记录(调用失败就立即失败)
- Failsafe Cluster
- 出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。
- Failback Cluster 模式
- 失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种。
- Forking Cluster 模式
- 并行调用多个 provider,只要一个成功就立即返回。常用于实时性要求比较高的读操作,但是会浪费更多的服务资源,可通过 forks=“2” 来设置最大并行数。
- Broadcast Cluster 模式
- 逐个调用所有的 provider。任何一个 provider 出错则报错(从 2.1.0 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。
动态代理策略
- 默认使用 javassist 动态字节码生成,创建代理类
- 可以通过 spi 扩展机制配置自己的动态代理策略
分布式事务
两阶段提交方案/XA 方案
- 事务管理器,负责协调多个数据库(资源管理器)的事务
- 事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
- Spring + JTA
- 严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景
- 这个方案,我们很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的
- 每个服务只能操作自己对应的一个数据库
TCC 方案
- 三阶段
- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
- Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作。
- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
- 几乎很少人使用,因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常之恶心
- 般来说跟钱相关的,跟钱打交道的,支付、交易相关的场景,我们会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题
Saga 方案
- 金融核心等业务可能会选择 TCC 方案,以追求强一致性和更高的并发量,而对于更多的金融核心以上的业务系统 往往会选择补偿事务,补偿事务处理在 30 多年前就提出了 Saga 理论
并不需要这么强的一致性,只需要保证最终一致性即可 - 下图左侧是正常的事务流程,当执行到 T3 时发生了错误,则开始执行右边的事务补偿流程,反向执行 T3、T2、T1 的补偿服务 C3、C2、C1,将 T3、T2、T1 已经修改的数据补偿掉。
- 优势
- 一阶段提交本地事务,无锁,高性能;
- 参与者可异步执行,高吞吐;
- 补偿服务易于实现,因为一个更新操作的反向操作是比较容易理解的。
- 缺点
- 不保证事务的隔离性。
可靠消息最终一致性方案
- A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
- 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
- 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
- mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
- 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
- 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你就用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。
如何处理分布式事务
- 某某特别严格的场景,用的是 TCC 来保证强一致性
- 然后其他的一些场景基于阿里的 RocketMQ 来实现分布式事务
- 严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案。