分布式
CAP
一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)这3个基本需求,最多只能同时满足其中的2个。
BASE
BASE(Basically Available、Soft state、Eventual consistency)是基于CAP理论逐步演化而来的,核心思想是即便不能达到强一致性(Strong consistency),也可以根据应用特点采用适当的方式来达到最终一致性(Eventual consistency)的效果。
分布式锁
- Mysql分布式锁
- zookeeper
- Redis
分布式事务
保证分布式系统中数据一致性。关键点:
- 需要记录事务在任何节点所做的所有动作
- 事务进行的所有操作要么全部提交,要么全部回滚
分布式事务实现方案
2PC,3PC,TCC,本地消息表、MQ消息事务、最大努力通知、SAGA事务等等
2PC
分布式事务中的XA协议:
三个角色:应用系统、事务管理器、资源管理器(数据库)
概括:参与者将操作成功失败通知协调者,由协调者根据所有参与者的反馈情况决定各参与者是否提交还是回滚
- 准备阶段:事务管理器要求每个涉及到事务的数据库预提交此操作,并反映是否可以提交
- 提交阶段:事务管理器要求每个数据库提交数据、回滚数据
缺点:
- 单点问题:事务管理器宕机会阻塞资源管理器
- 同步阻塞:准备就绪后,资源管理器中的资源一直处于阻塞、直到提交完成、释放资源
- 数据不一致:二阶段仅有部分参与者收到通知
3PC
解决两阶段提交协议的单点故障和同步阻塞
三阶段提交有三个阶段:CanCommit,PreCommit,DoCommit
-
CanCommit:准备阶段。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
-
PreCommit:预提交阶段。协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务,参与者执行完操作之后返回ACK响应,同时开始等待最终指令。
-
DoCommit:提交阶段。协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务:
- 如果所有参与者都返回正确的
ACK响应,则提交事务 - 如果参与者有一个或多个参与者收到错误的
ACK响应或者超时,则中断事务 - 如果参与者无法及时接收到来自协调者的提交或者中断事务请求时,在等待超时之后,会继续进行事务提交
- 如果所有参与者都返回正确的
可以看出,三阶段提交解决的只是两阶段提交中单体故障和同步阻塞的问题,因为加入了超时机制,这里的超时的机制作用于 预提交阶段 和 提交阶段。如果等待 预提交请求 超时,参与者直接回到准备阶段之前。如果等到提交请求超时,那参与者就会提交事务了。
TCC
TCC(Try Confirm Cancel) ,是两阶段提交的一个变种,针对每个操作,都需要有一个其对应的确认和取消操作,当操作成功时调用确认操作,当操作失败时调用取消操作,类似于二阶段提交,只不过这里的提交和回滚是针对业务上的,所以基于TCC实现的分布式事务也可以看做是对业务的一种补偿机制。
本地消息表
本地消息表的核心思想是将分布式事务拆分成本地事务进行处理。
例如,可以在订单库新增一个消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到MQ,库存服务去消费MQ。
执行流程:
- 订单服务,添加一条订单和一条消息,在一个事务里提交
- 订单服务,使用定时任务轮询查询状态为未同步的消息表,发送到MQ,如果发送失败,就重试发送
- 库存服务,接收MQ消息,修改库存表,需要保证幂等操作
- 如果修改成功,调用rpc接口修改订单系统消息表的状态为已完成或者直接删除这条消息
- 如果修改失败,可以不做处理,等待重试
订单服务中的消息有可能由于业务问题会一直重复发送,所以为了避免这种情况可以记录一下发送次数,当达到次数限制之后报警,人工接入处理;库存服务需要保证幂等,避免同一条消息被多次消费造成数据不一致。
本地消息表这种方案实现了最终一致性,需要在业务系统里增加消息表,业务逻辑中多一次插入的DB操作,所以性能会有损耗,而且最终一致性的间隔主要有定时任务的间隔时间决定
MQ事务消息
先投递消息,事务A确定后,消息才会真正发送到下游
Paxos
三个角色,一个节点可以同时充当不同角色:
- 提议者:提出提案,提案=编号+value,编号唯一、递增
- 接受者
- 学习者
两个阶段:
-
准备
- 提议者提出一个提案,向超过半数的接受者发送提案
- 接受者收到提案准备请求,如果是当前最大的编号,那就批准,并且此后都不会批准更小的
-
接受
- 收到半数以上的接受者响应,就会发送一个接受请求给接受者
- 接受者收到接受请求,只要没有对更大的提案作出过响应,就会通过该提案
-
协商结束,共识形成,发送决议给所有学习节点
Raft
三个角色:
- 领导者
- 跟随者
- 候选人
选举过程:
- 使用心跳触发选举
幂等性
- insert前先selet
- 加唯一索引
- 加悲观锁
- 加乐观锁
- 建立防重表
- 状态机:限制状态机的流转
- 分布式锁
- Token:请求接口前先获取一个唯一token,带着token去完成业务操作,服务端根据token是否存在判断是否重复请求
负载均衡
定义:用户请求分发到不同服务器上处理
分类:
- 服务端负载均衡
- 客户端负载均衡:系统内部的不同服务之间,使用线程的负载均衡组件实现。客户端内部维护服务端地址列表,根据负载均衡算法选择具体的服务器处理请求。客户端负载均衡器和服务运行在同一个进程或者java程序里,不存在额外网络开销。
负载均衡算法:
- 随机法
- 轮训法
- 两次随机法:选出两台机器
- 哈希法:将请求的参数信息进行hash,根据hash决定哪台服务器处理
- 一致性哈希法:做成一个环
- 最小连接法:遍历服务器节点列表选择其中连接数最小的一台服务器
- 最少活跃法:活动连接数最少的机器
- 最快响应法:响应时间最短的
七层负载均衡
- DNS解析:DNS服务器中为同一个主机记录配置多个IP地址(对应不同的服务器),用户请求域名的时候,DNS服务器采用轮询算法返回IP地址
- 反向代理:Nginx反向代理服务器,将接收到的客户端请求以一定的规则(负载均衡策略)均匀分配到这个服务器集群中所有的服务器上。
CDN内容分发网络
定义:将静态资源分发到多个不同的地方实现就近访问,进而加快静态资源的访问速度,减轻服务器以及带宽的负担
工作原理
- 静态资源如何被缓存到CDN节点中:1.预热;2.回源
- 如何找到最合适的CDN节点:GSLB全局负载均衡,返回性能最好的CDN节点
- 如何防止静态资源被盗刷:HTTP请求的头信息里的referer字段,获取到当前请求页面的来源页面的网站地址;时间戳防盗链(url通常会有两个参数:签名字符串、过期时间)
高可用
-
设计指南:使用集群;监控流量、限流;超时和重试;熔断(收集资源使用情况和性能指标netfilix的hystrix,ali的sentinel);异步调用;使用缓存
-
冗余设计:高可用集群、异地多活、故障转移
-
服务限流:
-
限流算法
- 固定窗口计数器算法:每分钟的请求数量固定;不够平滑、无法保证限流速率、无法应对激增的流量
- 滑动窗口计数器算法:将固定窗口划分为若干个等分的小窗口,每个小窗口拥有独立的计数器,请求到来的时候会统计小窗口内的请求数量是否达到阈值
- 令牌桶限流:对于每一个请求,都需要从令牌桶中获得一个令牌;如果没有获得令牌,则需要触发限流策略。系统会以恒定速度往固定容量的令牌桶中放入令牌,令牌桶大小固定、被填满后丢弃令牌。
- 漏桶限流:不管上方水流速度多快,漏桶水滴流出的速度保持不变。例如消息队列
-
限流对象:ip,业务id(例如用户id),个性化
-
单机限流:
-
分布式限流
-
基于中心化的限流方案:
- 实现方式:通过一个中心化的限流器来控制所有服务器的请求。例如使用redis,定义限流规则,把每秒钟允许的最大请求数QPS存在Redis中,每个请求、服务器需要先向Redis请求令牌,获取成功则说明请求可以被处理
- 问题:性能瓶颈,使用集群/提高单机性能;单点故障,redis主从、哨兵模式;网络带宽;
-
基于负载均衡的分布式限流方案
- 实现方式:使用负载均衡器把请求均匀地分到每个机器上,在每个机器上维护本机的限流状态,实现本地缓存单机限流的逻辑
-
基于分布式协调服务的限流
- 使用zookeeper或etcd等分布式协调服务来实现限流。每台服务器都会向分布式协调服务申请令牌,只有获取到令牌的请求才能被处理
- 初始化令牌桶:在zookeeper中创建一个节点,节点的数据代表令牌的数量。初始时,将数据设置为令牌桶的容量
- 申请令牌:当一个请求到达时,服务器首先向zookeeper申请一个令牌,通过获取节点的分布式锁、然后将节点数据减1实现
- 释放令牌:获取节点的分布式锁,将节点数据+1
- 补充令牌:设置定时任务,定期向zookeeper中的令牌桶补充令牌。补充频率和数量可以动态调整
-
-
限流设计的特点:
- 多级限流
- 动态阈值调整
- 灵活维度:接口、设备、IP、行为
- 解耦性
- 容错性(熔断、降级),备用的限流服务,放行
- 监控和报警
-
业界方案:
- spring cloud gateway,redisRateLimiter,基于redis+rua
- Redisson中的rrateLimiter,基于lua代码+令牌桶
-
redis实现各种限流
- 固定窗口:INCR命令计数,expire固定时间窗口
- 滑动时间窗口:zset记录每次请求的时间戳作为score,zremrangeByScore移除过期数据,zcard统计请求数量
- 漏斗:redis保存限流器的容积、水量、速度和最近一次请求时间等,计算判断是否允许请求
- 令牌桶:zset记录每个令牌生成时间作为score,通过zremrangeByScore统计过期的令牌数,并据此重新补充令牌
-
参考:
-
-
降级熔断
-
超时重试
- 超时未处理、请求会被取消并抛出特定错误例如504网关超时
- 超时分类:连接超时、读取超时(一般设置为1500-6000;重试:固定间隔时间重试;梯度间隔重试;需要保证重试幂等
-
性能测试
高并发
注意事项
- 系统性能:会增加系统负载
- 数据不一致
- 安全问题
- 缓存穿透
- 队列问题
zookeeper
功能:分布式协调服务。提供了高可用、高性能、稳定的分布式数据一致性解决方案。通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。这些功能的实现主要依赖于 ZooKeeper 提供的 数据存储+事件监听 功能。
特点:
- 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
- 单一系统映像: 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
- 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
- 实时性: 一旦数据发生变更,其他节点会实时感知到。每个客户端的系统视图都是最新的。
- 集群部署:3~5 台(最好奇数台)机器就可以组成一个集群,每台机器都在内存保存了 ZooKeeper 的全部数据,机器之间互相通信同步数据,客户端连接任何一台机器都可以。
- 高可用:如果某台机器宕机,会保证数据不丢失。集群中挂掉不超过一半的机器,都能保证集群可用。比如 3 台机器可以挂 1 台,5 台机器可以挂 2 台。
典型应用场景:
- 命名服务:可以通过 ZooKeeper 的顺序节点生成全局唯一 ID。
- 数据发布/订阅:通过 Watcher 机制 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。
- 分布式锁:通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。分布式锁的实现也需要用到 Watcher 机制 。
CAP:
zookeeper:保证了CP
eureka:保证了AP
分布式id
基本要求:全局唯一、高性能、高可用、方便易用、有序递增、独立部署、有业务含义
常见解决方案:
-
数据库主键自增
-
NoSql,redis incr对id原子顺序递增
-
UUID
- 时间+节点ID(MAC地址
- 标识符(组或用户id)+时间+节点ID
- 随机性或伪随机性
- 确定性UUID通过散列名字空间标识符和名称生成
- 问题:机器时间不对的情况下可能会导致重复ID
-
snowflake雪花算法
- sign,1bit,符号位
- timestamp,41bit,时间戳
- datacenter id + worker id,10bit,机房+机器
- sequence,12bit,序列号,单台机器每毫秒最多产生4096个唯一ID
- 可以在里面加上业务标识,但如果时间回拨,还是会产生重复ID
- 美团leaf进行改进,时钟问题弱依赖zookeeper(使用zookeeper作为注册中心,通过在特定路径下读取和创建子节点来管理worker id)