分布式
CAP理论
什么是CAP?
CAP 理论是对分布式系统的特性做了一个高度的抽象,变成了三大指标:
一致性(Consistency)
一致性强调的是数据正确,每次读取节点中的数据都是最新写入的数据。
可用性(Availability)
可用性,每个节点使用本地数据来响应客户端的请求。另外当节点不可用时,可以使用快速失败策略,至少不能让服务长时间不能响应可用性强调的是服务可用,不保证数据正确。
分区容错性(Partition Tolerance)
分区容错性的含义就是节点间出现任意数量的消息丢失或高延迟的时候,系统仍然在继续工作。分布式系统告诉客户端,我的内部不论出现什么样的数据同步问题,我会一直运行。强调的是集群堆分区故障的容错能力。
对于分布式系统,CAP 三个指标只能选择其中两个。
CA:保证一致性和可用性。当分布式系统正常运行时(大部分时候所处的状态),这个时候不需要 P,那么 C 和 A 能够同时保证。只有在发生分区故障时,才需要 P,这个时候就只能在 C 和 A 之间做出选择。典型应用:单机版部署的 MySQL。
CP:保证数据的一致性和分区容错性,比如配置信息,必须保证每个节点存的都是最新的,正确的数据。比如 Raft 的强一致性系统,会导致无法执行读操作和写操作。典型应用:Etcd、Consul、Hbase。
AP:保证分布式系统的可用性和分区容错性。用户访问系统,都能得到相应数据,不会出现响应错误,但是可能会读到旧的数据。典型应用:Cassandra 和 DynamoDB。
BASE理论
什么是BASE理论?
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。BASE 理论是对 CAP 中 AP 的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足 BASE 理论的事务,我们称之为柔性事务。
基本可用 : 分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如电商网址交易付款出现问题来,商品依然可以正常浏览。
软状态: 由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单中的“支付中”、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
最终一致性: 最终一致是指的经过一段时间后,所有节点数据都将会达到一致。如订单的“支付中”状态,最终会变为“支付成功”或者“支付失败”,使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。
负载均衡
常用的负载均衡算法有哪几个?
轮询:请求依次按顺序分发到不同的可用服务器执行,循环分发请求。
最小连接:分发请求到连接数最少的服务器。场景:处理请求用时较长的场景。
散列:根据用户请求的IP地址的散列(hash)来选择要转发的服务器。场景:需要处理状态而要求用户能连接到相同服务器。
分布式事务
事务是一个程序执行单元,里面的所有操作要么全部执行成功,要么全部执行失败。
分布式的理论基础是CAP,由于P(分区容错)是必选项,所以只能在AP或者CP中选择
分布式理论的CP -> 刚性事务,遵循ACID,对数据要求强一致性
分布式理论的AP+BASE -> 柔性事务,遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致
分布式事务的实现主要有以下6种方案,XA方案,TCC方案,SAGA方案,本地消息表,可靠消息最终一致性方案,最大努力通知方案。
介绍下XA两阶段提交方案
第一阶段询问,第二阶段提交执行。
有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚
事务。这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。那么基于Spring + JTA 就可以搞定
介绍下TCC三阶段提交方案
TCC的全称是,try,confirm,cancel
金融核心等业务可能会选择 TCC 方案,以追求强一致性和更高的并发量
Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留(冻结银行B资金,类似于上锁)
Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作(在自己库插数据,调用银行B扣款接口,调用银行C转账接口)
Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作(回滚,将银行B的扣款加回去)
这种方案几乎很少人使用,这个事务回滚实际上是严重依赖于自己写代码来回滚和补偿,会造成补偿代码巨大。
跟钱打交道的,支付、交易相关的场景,会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,而且最好是各个业务执行的时间都比较短。一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心,业务代码是很难维护的。
介绍下SAGA方案
目前业界比较公认的是采用 Saga 作为长事务的解决方案
业务流程中每个参与者都提交本地事务,若某一个参与者失败,则补偿前面已经成功的参与者
正常事务流程是T1--->T2--->T3,当执行到T3时发生了错误,那开始从T3开始反向执行补偿服务C3--->C2--->C1,把T1,T2,T3已经修改的数据补偿掉
很多金融核心以上的业务(渠道层、产品层、系统集成层),这些系统的特点是最终一致即可。
流程多、流程长、还可能要调用其它公司的服务,这种情况如果选择 TCC 方案开发的话,一来成本高,二来无法要求其它公司的服务也遵循 TCC 模式。同时流程长,事务边界太长,加锁时间长,也会影响并发性能。
所以 Saga 模式的适用场景是,业务流程长、业务流程多;参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口。
优势是
一阶段提交本地事务,无锁,高性能
参与者可异步执行,高吞吐
补偿服务易于实现,比如一个更新操作的反向操作是比较容易理解的
缺点是
不保证事务的隔离性
介绍下本地消息表方案
1:A 系统在自己本地一个事务里操作同时,插入一条数据到消息表
2:接着 A 系统将这个消息发送到 MQ 中去
3:B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息
4:B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态
5:如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理
6:这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止
这个方案最大的问题就在于严重依赖于数据库的消息表来管理事务,如果是高并发场景咋办呢?咋扩展呢?所以一般很少用
介绍下可靠消息最终一致性方案
直接基于 MQ 来实现事务。比如阿里的RocketMQ 就支持消息事务
1:A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了
2:如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息
3:如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务
4:mq 会自动定时轮询所有 prepared 消息回调你的接口,询问这个消息是不是本地事务处理失败了,所有没发送确认的消息是继续重试还是回滚?一般来说这里就可以查下数据库看之前本地事务是否执行,如果回滚了那么这里也回滚。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了
5:要是系统 B 的事务失败了咋办?重试,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿
目前国内互联网公司大都是这么玩儿的,要不你就用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。
介绍下最大努力通知方案
1:系统 A 本地事务执行完之后,发送个消息到 MQ
2:有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口
3:要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃
限流
限流其实就是:当高并发或者瞬时高并发时,为了保证系统的稳定性、可用性,系统以牺牲部分请求为代价或者延迟处理请求为代价,保证系统整体服务可用。
令牌桶(Token Bucket)、漏桶(leaky bucket)和计数器算法是最常用的三种限流的算法
分布式限流和接入层限流来进行全局限流。
- redis+lua实现中的lua脚本
- 使用Nginx+Lua实现的Lua脚本
- 使用 OpenResty 开源的限流方案
- 限流框架,比如Sentinel实现降级限流熔断
服务熔断降级
什么是雪崩?熔断?降级?
第一次滚雪球:库存服务不可用(如响应超时等),库存服务收到的很多请求都未处理完,库存服务将无法处理更多请求。
第二次滚雪球:因商品服务的请求都在等库存服务返回结果,导致商品服务调用库存服务的很多请求未处理完,商品服务将无法处理其他请求,导致商品服务不可用
第三次滚雪球:因商品服务不可用,订单服务调用商品服务的的其他请求无法处理,导致订单服务不可用。
第四次滚雪球:因订单服务不可用,客户端将不能下单,更多客户将重试下单,将导致更多下单请求不可用。
熔断:设置服务的超时,当被调用的服务某段时间内失败率达到某个阈值,则对该服务开启短路保护,后来的请求不调用这个服务,直接返回默认的数据。
降级:
对非核心业务降级运行:某些服务不处理,或者简单处理(抛异常、返回Null、返回Mock数据)
幂等
分布式系统中,幂等性如果设计?
在高并发场景的架构里,比如支付场景,幂等性是必须得保证的。
建唯一索引:唯一索引或唯一组合索引来防止新增数据存在脏数据 (当表存在唯一索引,并发时新增异常时,再查询一次就可以了,数据应该已经存在了,返回结果即可)。
token机制:由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交。前端在数据提交前要向后端服务的申请token,token放到 Redis 或 JVM 内存,token有效时间。提交后后台校验token,同时删除token,生成新的token返回。redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用。
悲观锁
select id ,name from table_# where id='##' for update;
悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用(另外还要考虑id是否为主键,如果id不是主键或者不是 InnoDB 存储引擎,那么就会出现锁全表)
乐观锁,给数据库表增加一个version字段,可以通过这个字段来判断是否已经被修改了
update table_xxx set name=#name#,version=version+1 where version=#version#
分布式锁,比如 Redis 、 Zookeeper 的分布式锁。单号为key,然后给Key设置有效期(防止支付失败后,锁一直不释放),来一个请求使用订单号生成一把锁,业务代码执行完成后再释放锁。
三高架构
什么是三高架构?
高并发性能能够保证系统在面对大量并发请求时能够高效处理;
高可用性能够保证系统在面对故障和异常时能够持续提供服务;
高性能能够保证系统在有限资源下能够以较快的速度完成任务。
这三者相辅相成,构成了一个健壮、可靠和高效的软件系统。
高并发性
1. 多线程与并发库:java.util.concurrent包,synchronized关键字,volatile关键字。
2. 线程池:Executors,ThreadPoolExecutor。
3. 异步处理:CompletableFuture,RxJava。
4. 消息队列:RabbitMQ,Kafka,ActiveMQ。
5. 负载均衡:Nginx,Apache Load Balancer,Zuul。
高可用性
1. 集群:通过多实例部署实现负载均衡和故障转移。
2. 服务发现与注册:Eureka,Consul,Zookeeper。
3. 断路器模式:Hystrix,Resilience4j。
4. 配置中心:Spring Cloud Config。
5. 微服务架构:Spring Boot,Spring Cloud。
6. 容器化与编排:Docker,Kubernetes。
高性能
1. JVM调优:垃圾回收算法(G1,CMS,ZGC),JVM参数调优。
2. 缓存:Redis,Memcached,Caffeine。
3. 数据库优化:
SQL调优:索引,查询优化。
数据库连接池:HikariCP,DBCP,Tomcat JDBC。
分库分表:ShardingSphere,MyCAT。
4. 静态资源处理:使用CDN,静态资源服务器(如Nginx)。
5. 代码优化:算法优化,数据结构选择。
6. I/O优化:使用NIO,AIO。
7. 代码生成和编译:GraalVM,JIT编译。
其他相关技术
1. API网关:Spring Cloud Gateway,Zuul。
2. 限流:Guava的RateLimiter,Sentinel。
3. 监控和跟踪:Prometheus,Grafana,Zipkin,SkyWalking。
4. 日志管理:ELK Stack(Elasticsearch,Logstash,Kibana)。
5. 测试:单元测试(JUnit),性能测试(JMeter,Gatling)。
6. 安全:OAuth 2.0,JWT,Spring Security。