1、分布式中 接口的幂等性的设计
在高并发场景的架构里,幂等性是必须得保证的。比如说提交作业。
查询和删除不在 幂等讨论范围。
1、建唯一索引id
每次操作,都根据操作和内容生成唯一的id,在执行之前先判断id是否存在,如果不存在,则
执行后续操作,并且保存到数据库或者redis等。
2、token机制
由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交。前端在数据提交
前要向后端服务申请token,token放到 Redis 或 JVM 内存。
如果在token有效时间内,提交后台校验token,同时删除token,生成新的token返回。
判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务完成后,最
后需要把redis中的token删除。
3、建去重表
将业务中有唯一标识的字段保存到去重表,如果表中存在,则表示已经处理过了。
4、版本控制
增加版本号,当版本号符合时,才能更新数据
5、状态控制
例如订单有状态已支付 未支付 支付中 支付失败,当处于未支付的时候才允许修改为支付中
2、说说你对分布式事务的理解?
可以从 场景、理论、解决方案三个角度来说。
2.1、场景
多个服务或者多个库,要保持在一个事务中。
我们执行一次任务,可能要操作多个服务或者多个库。
2.2、理论ACID、CAP、BASE。
ACID 指数据库事务正确执行的四个基本要素:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
CAP
一致性(Consistency)、
可用性(Availability)、
分区容忍性(Partition tolerance)。
BASE理论
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
2.3、解决方案
seata,消息队列+本地事件表,事务消息,最大努力通知方案,tcc
3、什么是两阶段提交(2PC)?
两阶段提交2PC 是分布式事务中最强大的事务类型之一。
3.1、 两个阶段提交流程:
第一阶段 事务协调者询问各个 事务数据源(资源管理器) 是否准备好,投票阶段。
第二阶段 事务协调者根据资源管理器反馈的情况执行commit 或者 fallback操作。
处理流程如下:
阶段一
a) 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复。
b) 各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中**(但不提交事务)**。
c) 如参与者执行成功,给协调者反馈 yes,否则反馈 no。
阶段二
如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(rollback)消息;
否则,发送提交(commit)消息。两种情况处理如下:
情况1:当所有参与者均反馈 yes,提交事务
a) 协调者向所有参与者发出正式提交事务的请求(即 commit 请求)。
b) 参与者执行 commit 请求,并释放整个事务期间占用的资源。
c) 各参与者向协调者反馈 ack(应答)完成的消息。
d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。
情况2:当有一个参与者反馈 no,回滚事务
a) 协调者向所有参与者发出回滚请求(即 rollback 请求)。
b) 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
c) 各参与者向协调者反馈 ack 完成的消息。
d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务。
3.2、 两阶段提交(2PC)问题
1、性能问题(阻塞问题):所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,比如数
据库的连接一直占用着,容易导致性能瓶颈。
优化: 资源管理器执行完之后,将数据库的连接断开,并记录到日志中。
2、可靠性问题:如果协调者 存在单点故障问题 ,资源管理器将一直处于锁定状态。
优化:我们可以通过集群 的方式 提高 协调者的高可用。
3、数据一致性问题:在阶段 二 中,如果出现资源管理器在执行commit之前 挂了的情况,有可能
导致数据不一致。
优化:通过脚本对数据进行监控
**优点:尽量保证了数据的强一致,**适合对数据强一致要求很高的关键领域。(其实也不能100%保
证强一致)。
**缺点:实现复杂,牺牲了可用性,对性能影响较大,**不适合高并发高性能场景。
3.3、两阶段提交(2PC)的应用
seata,lcn,tcc。
4、什么是补偿性事务(TCC)?
TCC (Try Confifirm Cancel)采用的补偿机制。
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和
补偿(撤销)操作。
它分为三个步骤:
1。Try 阶段主要是 对业务系统做检测及资源预留。
2。Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默
认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
3。Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
4.1、使用场景
业务场景:比如转账
技术场景:比如保证MySQL和Redis的数据一致性。
4.2、补偿性事务(TCC)的优缺点
优点:
性能提升:资源管理器 实现控制资源锁 的粒度变小,不会锁定整个资源。
数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据
的一致性。
可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方 发起并控制整个业务活动,业务活
动管理器也变成多点,引入集群。
缺点:
TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
5、消息队列和事件表是如何实现分布式事务的
6、 分布式ID生成有几种方案?
6.1、分布式ID的特性
唯一性:确保生成的ID是全网唯一的。
有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
高可用性:确保任何时候都能正确的生成ID。
带时间:ID里面包含时间,不容易重复。
6.2、UUID
算法的核心思想是 结合机器的网卡、当地时间、一个随机数 来生成UUID。
优点:本地生成,**生成简单,性能好,**没有高可用风险
缺点:长度过长 ,存储冗余,且无序不可读,查询效率低
6.3、数据库自增ID
使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略 来实现高可用。
优点:数据库生成的ID绝对有序,高可用实现方式简单
缺点:需要独立部署数据库实例,成本高,有性能瓶颈
6.4、批量生成ID
一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在
内存中记录当前值及最大值。
优点:避免了每次生成ID都要访问数据库并带来压力,提高性能
缺点:属于本地生成策略,存在 单点故障,服务重启(内存中记录的最大值丢失)造成ID不连续
6.5、Redis生成ID
Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所
以能保证生成的 ID 肯定是唯一有序的。
优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序。
缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;
6.6、Twitter的snowflflake算法(重点)
优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞
缺点:需要独立的开发和部署,依赖于机器的时钟
1位符号位
正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。
41位时间戳(毫秒级)
存储时间戳的差值(当前时间戳 - 起始时间戳),这里的起始时间戳一般是ID生成器开始使用
的时间戳。
10位数据机器位
这10位决定了分布式系统中最多可以部署 1 << 10 = 1024个节点。超过这个数量,生成的ID
就有可能会冲突。
12位毫秒内的序列
6.7、百度UidGenerator
UidGenerator是百度开源的分布式ID生成器,基于于snowflflake算法的实现。
6.8、美团Leaf
Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,
但也需要依赖关系数据库、Zookeeper等中间件。
7、常见的负载均衡算法有哪些?
轮询负载均衡算法
挨个发,适合于所有服务器硬件都相同的场景。
代码实现:用i 保存服务器的编号。第一次来 取0,第二次来 取1 ,第三次来 取 0。
加权轮询算法
按照权重不同来分发,基本上是基于配置。
代码实现:
比如:两个服务权重分别是6和4,我们的方法,在1-10之间取 随机数,比如取到 1-6,就走6的权重,取到7-10,就走4权重的服务。
随机轮询算法
代码实现:这个就随意了。
最少链接
记录每个服务器正在处理的 连接数 (请求数),将新的请求 分发到最少连接的服务器上,这
是最符合负载均衡的算法。
代码实现:放到redis里,每次调用一次,服务次数+1。
原地址散列
**根据请求来源的ip地址 进行hash计算,**只要原地址不变,每次请求映射来的后面提供服务的
节点也不变。这有利于进行session信息的维护。
8、数据库如何处理大数据量
对数据库进行:分区、分库分表,主从架构(读写分离)。
分区:
原来的一个数据库在磁盘是一个文件,把这个文件分成4个文件,放到不同的位置实现隔离
数据访问。
分库分表:
水平分库/表,各个库和表的结构一模一样,数据量不一样。
垂直分库/表,各个库和表的结构不一样,数据量一样。
主从架构(读写分离):
读写分离:主机负责写,从机负责读。
9、什么是可靠消息最终一致性方案?
它是保证事务最终一致性的一种方案,允许数据在业务中出现短暂的不一致状态。
可靠消息 最终一致性方案是指:消息发送者 执行完本地事务后,同时发出一条消息,消息的消费
者 一定能够接收消息 并可以成功处理自己的事务。
这里面强调两点:
可靠消息:消息发送者 一定得把消息传递到 消费者。
最终一致性:最终消息发送者 的业务处理 和 消息的消费者 的业务处理得完成,达成最终一致。
9.1、可靠消息最终一致性 问题分析
事务发起方和消息中间件之间,事务消费方和消息中间件之间,都有网络通信,由于网络通
信的不确定性,这块会导致数据的问题。下面针对导致的问题来分别进行解决。
(1) 事务发起方 本地事务 和 消息发送之间的原子性问题。
即本地事务 执行成功和消息的发送成功,要么都成功,要么都失败。
java
begin transaction;
操作数据库;
发送消息;
commit transaction;
如果操作数据库出错,回滚,不影响数据;如果发送消息出错,也回滚,不影响数据。
这么一看似乎可以保证原子性,但是会有一种情况,发送消息响应超时,导致数据库回滚,
但是消息已经发送成功了。这时原子性还是无法保证的,这个时候就需要人工补偿了。
(2) 事务消费方 本地事务 和 消息消费 的原子性问题。
如果由于程序故障,导致事务消费方 重启,那么需要消息中间件要有消息重发机制;
由于网络延时的存在,当事务消费方消费消息成功,没有向消息中间件响应时,而消息中间
件由于重发机制,会再次投递消息,就导致了消息重复消费的问题。此时在消费方要有幂等性解决
方案。
10、RocketMQ在分布式事务中如何应用的?
它在4.3之后的版本支持了事务消息,为解决分布式事务提供了便利 。
RocketMQ的事务消息,主要为了解决 事务生产方 执行业务 和 消息发送的原子性问题。
具体流程如下:
(1)发送half message。在执行本地业务之前,先向消息队列发送一条事务消息,此时叫做half
message, 此时消息被标记为(Prepared预备状态),此时的消息是无法被消费者消费的,需要
生产者对消息进行二次确认后,消费者才能去消费它。
(2)消息队列回应half message发送成功。
(3)当事务发起方收到消息队列的成功响应之后,开始执行本地业务。
(4)如果本地事务执行成功,则向消息队列发送half message的确认,这样事务消费方就可以消
费消息了。
(5)**如果本地事务执行失败,则向消息队列发送half message的回滚,删除half message。**事
务消费方就无法消费消息。
(6)回查机制。由于网络闪断,生产者应用重启等原因,导致生产者无法对消息队列中的half
message进行二次确认时,消息队列中的half message就不知道应该怎么办了。此时消息队列会定
时扫描长期处于half message的消息,并发起一个回查机制,来确认此时的half message应该是
**提****交还是回滚。**此时,消息队列主动询问生产者该消息的最终状态(提交还是回滚),即为消息的
回查机制。
11、请说说微服务注册中心的存储结构
11.1、为什么需要注册中心?
服务太多,使用注册中心方便管理。注册中心相当于通讯录、花名册。
11.2、注册中心的存储结构
注册中心示例:
打开Eureka的源码,它的存储结构是用的ConcurrentHashMap,它的key是服务名,值是个Map。而值的Map中键是服务实例ID,值是租约(租约里面包括服务信息)。
key: value
<服务名:<服务实例1:ip+port,服务配置等等>,<服务实例1:ip+port,服务配置等等>>
12、设计一个注册中心需要写哪些接口?
注册中心需要提供一个接口,让服务调用它来进行服务的登记注册,这就是注册中心的第一
个功能, 接受服务注册 。
服务注册完成后,注册中心得知道这个服务是否还是有效服务?所以需要服务定期地告诉注
册中心,自己的工作状态(是否可用)。此时需要注册中心提供第二个功能, 接受服务心跳 。
当服务下线时,要通知注册中心自己要下线,注册中心需要提供对应的接口,来让服务调
用。此时需要注册中心的第三个功能, 接受服务下线 。
如果服务挂了,没有及时通知注册中心,此时注册中心也发现服务最近没有发送心跳。注册
中心要主动剔除挂了的服务。此时需要注册中心第四个功能, 服务剔除 。
注册表中存储的信息,是要供其他服务查询的,就像通讯录一样,是要供主人查阅的,所以
注册中心还需要第五个功能, 查询注册表中的服务信息 。
一般微服务中,每个服务都要避免单点故障,注册中心也要做集群 ,所以还要涉及到注册中
心间,注册信息的同步 。这就是注册中心的第六个功能, 注册中心集群间注册表的同步。
其实本质就是一个web服务,提供上面分析的5个接口,供服务调用。这就是平时所说的 注册
中心服务端 。那对应的调用注册中心的服务(业务服务),一般称之为 注册中心客户端 。
13、谈谈你对RESTful规范的理解?
RESTful其实是一种风格并不是一种协议,其实他就是普通的http请求。其中REST表示Resource
Representational State Transfer,直接翻译即"资源表现层状态转移"。
Resource代表互联网资源。每种"资源"对应一个URI。
Representational是"表现层"意思。**"资源"是一种消息实体,它可以有多种外在的表现形式,**比如
map3、avi等。它的具体表现形式,应该由HTTP请求的头信息Accept和Content-Type字段指定,
这两个字段是对"表现层"的描述。
State Transfer是指"状态转移"。客户端想要操作服务端资源,通过使用HTTP协议中的常用的四
**个****动词,让服务器端资源发生"状态转移"。**它们分别是获取资源的GET、新建或更新资源POST、
更新资源的PUT和删除资源的DELETE。
RESTful接口URL命名原则
命名原则1:HTTP方法后跟的URL 必须是名词且 统一成名词复数形式。
命名原则2:URL中不采用大小写混合的驼峰命名,尽量采用全小写单词,如果需要连接多个单
词,则采用"-"连接。
14、分布式系统中为什么引入熔断?
大家看一下下面的服务调用场景。C服务和D服务调用B服务,B服务调用A服务。在下面情况1中,服务正常调用。服务在运行过程中,A服务发生故障(网络延时,服务异常,负载过大无法及时响应),系统变成了情况2。由于B服务调用A服务,A服务出故障,导致B服务调用A的代码处也出故障,此时B服务也出故障了,系统变成了情况3。**以此类推,系统最终发展成A、B、C、D所有的服务都出错了,整个系统崩塌了。这就是雪崩,**如图所示。
雪崩效应
微服务系统之间通过互相调用来实现业务功能,但每个系统都无法百分之百保证自身运行不
出问题。在服务调用中,很可能面临依赖服务失效的问题(网络延时,服务异常,负载过大无法及
时响应),导致服务雪崩,这对于一个系统来说是灾难性的。 因此需要一个组件,能提供强大的容
错能力,当服务发生异常时,能提供保护和控制,把影响控制在较小范围内,不要造成所有服务的
雪崩。
什么时候恢复系统?
熔断开关状态:开(走降级的方法),关闭(正常的调用),半开()
15、描述一下熔断和降级的区别?
(1) 相似性:
①目的一致:都是从可用性和可靠性着想,防止系统的整体响应缓慢甚至崩溃而采用的技术手段。
②最终表现类似 :对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用。
③粒度一致:都是服务级别的。
④自治性要求很高:熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段。
(2) 区别:
①触发原因不一样:服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负
荷考虑。
②管理目标的层次不一样:熔断是一个框架级的处理,每个服务都需要,而降级一般有业务层级之
分,例如,降级一般在服务调用的上层处理。
16、如何提升系统的并发能力?
分流:自动扩缩容、负载均衡,消息队列,数据库拆分(主从架构、分库分表)
导流:分布式缓存,cdn
并行/发:异步线程
17、你是依据什么来进行服务划分的?
进行微服务设计时,服务的数量相对于单体应用来说,会比较多,上个公司项目分了10个服务。
考虑的重点就是如何准确识别 系统业务的边界。只有每个服务的边界确定了,才能在以后的开发
中做到更好的协作。
18、微服务设计一般都遵循了什么原则?
(1)单一职责原则。让每个服务能独立,有界限的工作,每个服务只关注自己的业务。做到高内
聚,服务和服务之间做到低耦合。
(2)服务自治原则。每个服务要能做到独立开发、独立测试、独立构建、独立部署,独立运行,
与其他服务进行解耦。
(3)轻量级通信原则。让每个服务之间的调用是轻量级,并且能够跨平台、跨语言。例如采用
RESTful风格,利用消息队列进行通信等。
(4)粒度进化原则。服务的粒度随着业务和用户的发展而发展。
软件是为业务服务的,好的系统不是设计出来的,而是进化出来的。
19、什么是最大努力通知方案?
如果接入过支付宝或者微信的支付接口,会遇到这样一种流程。例如,APP调用支付宝或微信的
SDK进行了支付,钱已经从用户的支付宝或微信账户,转到了公司的支付宝或微信账户上,但是支
付系统,并不知道钱是否已经支付成功,需要支付宝或者微信回调公司的支付系统,才能进行后续
的业务, 这其实就是一个最大努力通知的解决方案。在方案中主要保证两点:
(1)**有一定的消息重复通知机制。**因为接收通知方(图中的我方支付系统)可能没有接收到通
知,此时要有一定的机制对消息进行重复通知。
(2)**消息校对机制。**如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,
此时可由接收方主动向通知方查询消息信息来满足需求。
落地:
**最大努力通知,**其实针对内部系统和外部系统,有不同的做法。
(1)公司内部系统。**针对公司内部系统来做的话,可以通过系统直接订阅消息队列来完成。**因为
都是自己的系统,直接订阅就可以。
(2)公司外部系统。针对公司外部系统来做的话,直接让消费方订阅消息队列就有点不合适了,毕竟不能让两家公司同时对一个消息队列进行操作,所以此时,**可以在内部写一个程序来订阅消息队列,通过RPC的方式,调用消费方,使其被动的的接受通知消息。**在接支付宝和微信时,一般都是采用这种方式。
20、spring cloud和Dubbo你是如何做选择的?
采用微服务会带来 更清晰的业务划分 和 更好的可扩展性,在很多企业中十分流行。支持微服务的技术栈也是多种多样。当前主流的是Spring Cloud 和 Dubbo。
Spring Cloud的功能比Dubbo更全面,更完善,它可以与Spring其他项目无缝结合,完美对
接,整个软件生态环境比较好。
Spring Cloud就像品牌机,整合在Spring的大家庭中,并做了大量的兼容性测试,保证了机器
各部件的稳定。
Dubbo就像组装机,每个组件的选择自由度很高,但是如果你不是高手,如果你选择的某个
组件出问题,就会导致整个机器的宕机,造成整体服务的可不用。
21、Ribbon的原理是什么?
重点答以下三点。
(1)通过拦截器 对 被注解@LoadBalanced 修饰的RestTemplate进行 拦截。
(2)将RestTemplate中调用的服务名 ,解析成具体的IP地址,由于一个服务名会对应多个地址,
那么在选择具体服务地址的时候,需要做负载均衡。
(3)确定目标服务的 IP 和 port 后,通过Httpclient进行http的调用。
22、能说一下你对 认证 和 授权 的认识吗?
(1)Authentication(认证) 是验证您的身份的凭据(例如用户名/用户ID和密码/手机验证码),
通过这个凭据,系统得以知道你就是你,也就是说系统存在 你这个用户。所以,Authentication 被
称为身份/用户验证。
(2)Authorization**(授权) 发生在 Authentication(认证) 之后。授权,它主要掌管访问系统**
**的****权限。**例如有些特定资源,只能是具有特定权限的人才能访问例如admin,有些对系统资源操作
例如删除、添加、更新只能特定人才具有。