分布式与高并发
配套知识点:Spring Cloud 微服务
怎么用这份笔记
- 分级:
★★★ 必须吃透/★★☆ 了解即可(记结论就行)/★☆☆ 可以跳过(一句话看一眼)- 【简历亮点,高频追问】:简历里写过的技术,面试官会顺着往下挖,这段必须能展开讲
- 每个考点按「定义 → 原理 → 要点 / 易错点」组织,只讲知识点
1. CAP 与 BASE ★★★
CAP 指一致性(Consistency)、可用性(Availability)、分区容错(Partition tolerance)。分布式系统里网络分区不可避免,P 必须保,所以实际只能在 C 和 A 之间取舍,三者无法同时满足。
- CP:优先一致性和分区容错,典型是 ZooKeeper、etcd。
- AP:优先可用性和分区容错,典型是 Eureka。
BASE 是对 CAP 的妥协,包含三个要素:
- 基本可用:允许损失部分可用性,例如响应变慢、降级;
- 软状态:允许中间状态存在,不要求各副本时刻相同;
- 最终一致:不追求实时强一致,接受「过一会儿才一致」。
落地时按链路取舍:扣费、订单状态这类核心链路要强一致;跨系统上报、统计报表这类非核心链路用消息 + 重试做到最终一致即可,可用性更好。
2. 分布式事务方案 ★★★
跨服务、跨库的写操作无法靠本地事务保证,必须用分布式事务方案。
- 2PC(两阶段提交):协调者先询问所有参与者能否提交,再统一提交或回滚。强一致,但同步阻塞、协调者单点、性能差。
- TCC(Try-Confirm-Cancel):Try 预留资源,Confirm 确认提交,Cancel 取消并释放预留资源。不锁整个资源、性能较好,但侵入业务,三个方法都要按业务实现,且 Confirm / Cancel 必须幂等。
- SAGA:把一个长事务拆成多个本地事务,失败时按反向顺序执行补偿,达到最终一致。
- 本地消息表:业务数据和消息记录放在同一个本地事务里落库,再由定时任务把未发送/失败的消息重投,消费端幂等收敛。
- 事务消息:RocketMQ 的半消息机制——先发半消息,执行本地事务,再确认或回滚,保证「本地事务成功」与「消息可投递」同时成立;确认失败由 broker 回查生产者。
- 最大努力通知:反复重试通知对方,配合定时对账兜底。
- 中间件是 Seata(AT / TCC / SAGA 等模式)。
选型上,业务量大、链路长时优先「本地事务 + 消息 + 对账补偿」的最终一致路线:2PC 阻塞且单点,TCC 改造成本高,在高并发场景划不来;对账本身已有,用消息重试加定时对账兜底最稳。
3. 分布式 ID 生成 ★★★
分布式 ID 的要求:全局唯一、趋势递增、高可用、最好带时间信息。常见方案:
- UUID:结合网卡、时间、随机数生成,本地生成、简单、无高可用风险;但长度长、存储冗余、无序,做数据库主键会频繁页分裂,查询效率低。
- 数据库自增 ID:绝对有序,可用两台库设置不同步长实现高可用;但要独立部署实例、成本高,有单点和性能瓶颈。
- 号段模式:一次从数据库批量取一段 ID 缓存在本地,减少访问数据库的次数;属于本地生成策略,存在单点故障,服务重启会造成 ID 不连续。
- Redis 自增:
incr/incrby原子且天然有序,性能优于数据库,对分页排序友好;代价是引入新组件、需要编码配置,单点有瓶颈时可多节点设置不同起始值和步长。 - 雪花算法(主流):1 位符号位 + 41 位时间戳 + 10 位机器 id + 12 位序列号,共 64 位 Long。本地生成、趋势递增、高性能,一般不会碰撞。
雪花算法唯一的硬伤是依赖机器时钟:发生时钟回拨会生成重复 ID,处理方式是检测到回拨后等待时钟追上,或更换机器 id。订单号、流水号用雪花或号段都合适,趋势递增对 B+ 树索引也友好。
补充(原文详解)
分布式ID的特性
唯一性:确保生成的ID是全网唯一的。
有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
高可用性:确保任何时候都能正确的生成ID。
带时间:ID里面包含时间,一眼扫过去就知道哪天的交易。
分布式ID生成方案
- UUID
算法的核心思想是结合机器的网卡、当地时间、 一个随记数来生成UUID。
. 优点:本地生成,生成简单,性能好,没有高可用风险
缺点:长度过长,存储冗余,且无序不可读,查询效率低
- 数据库自增ID
使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。
优点:数据库生成的ID绝对有序,高可用实现方式简单
缺点:需要独立部署数据库实例,成本高,有性能瓶颈
- 批量生成ID
一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。
优点:避免了每次生成ID都要访问数据库并带来压力,提高性能
缺点:属于本地生成策略,存在单点故障,服务重启造成ID不连续
- Redis生成ID
Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。
优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;需要编码和配置的工作量比较大。
考虑到单节点的性能瓶颈,可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台 Redis。可以初始化每台 Redis 的值分别是1, 2, 3, 4, 5,然后步长都是 5。
- Twitter的snowflake算法(重点)
Twitter 利用 zookeeper 实现了一个全局ID生成的服务 Snowflake
如上图的所示,Twitter 的 Snowflake 算法由下面几部分组成:
1位符号位:
由于 long 类型在 java 中带符号的,最高位为符号位,正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。
41位时间戳(毫秒级):
需要注意的是此处的 41 位时间戳并非存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 起始时间戳),这里的起始时间戳一般是ID生成器开始使用的时间戳,由程序来指定,所以41 位毫秒时间戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年。
10位数据机器位:
包括5位数据标识位和5位机器标识位,这10位决定了分布式系统中最多可以部署 1 << 10 = 1024
s个节点。超过这个数量,生成的ID就有可能会冲突。
12位毫秒内的序列:
这 12 位计数支持每个节点每毫秒(同一台机器,同一时刻)最多生成 1 << 12 = 4096个ID
加起来刚好64位,为一个Long型。
优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞
缺点:需要独立的开发和部署,依赖于机器的时钟
- 百度UidGenerator
UidGenerator是百度开源的分布式ID生成器,基于于snowflake算法的实现,看起来感觉还行。不过,国内开源的项目维护性真是担忧。
- 美团Leaf
Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、 Zookeeper等中间件。
4. 限流算法:令牌桶、漏桶、滑动窗口 ★★★
四种常见算法:
- 固定窗口(计数器):按周期累加访问次数,达到限流值就触发限流,下一周期清零重新计数。实现最简单,Redis 的
incr原子自增即可;但存在临界突发问题——在窗口交界处两侧各打满限额,短时间内的实际流量可达限额的两倍。 - 滑动窗口:把大周期拆成 N 个小周期,分别记录访问次数,并随时间滑动删除过期的小周期。格子划分越细,滚动越平滑、统计越精确,能解决固定窗口的临界问题。
- 漏桶:请求先放入漏桶,容量到上限(限流值)就丢弃;漏桶以固定速率释放请求。流量严格平滑,但扛不住突发,多余请求直接被限掉。
- 令牌桶:以固定速率(时间周期 / 限流值)往桶里放令牌,直到桶满;请求拿到令牌才通过,否则触发限流。平均速率受限,但桶里攒下的令牌允许一定突发,所以最常用。
常用工具:Sentinel、Guava RateLimiter、Redis + Lua(分布式限流)、网关层限流。除了限 QPS,也可以用信号量限制对下游依赖的并发数,避免把对方打挂。
5. 熔断、降级、隔离 ★★★
- 熔断:被动应对。依赖服务的错误率或慢调用比例超过阈值时,直接快速失败、走兜底,避免线程被拖住引发级联雪崩。状态机三态:关闭 → 打开 → 半开;打开后放少量请求到半开状态探测,恢复正常再闭合。
- 降级:主动关功能。压力大或依赖不可用时关掉非核心功能,把资源让给核心链路,返回兜底数据(缓存旧值、默认值、空结果)。
- 隔离:用线程池 / 信号量把不同依赖的调用隔开,一个依赖占满资源也不会拖垮整体;线程池隔离还能给每个依赖配独立超时和队列。
三者通常一起用:调外部接口时配超时 + 熔断 + 降级,对方超时或报错就返回兜底数据,不影响主流程;不同外部调用分配独立线程池,互不干扰。
6. 接口幂等性 ★★★ 【简历亮点,高频追问】
幂等指同一个请求执行一次和执行多次,产生的副作用相同、最终结果一致。查询天然幂等;删除第一次成功后后续返回 0,也算幂等。
需要幂等的原因:网络超时重试、用户重复点击、网关/反向代理重发、消息重复投递、Dubbo 等 RPC 框架的重试机制。
常见方案:
- 唯一索引 / 去重表:数据库层兜底,重复插入触发唯一键冲突,捕获异常后再查询已有记录返回即可。
- Token 机制:提交前先申请 token 存到 Redis 或 JVM 内存并设有效期,提交时校验并删除 token,删除成功才算通过,然后下发新 token。必须用删除操作的返回值判断,
select + delete两步存在并发问题。 - 状态机:单据状态只能按规定流转,例如只有「待支付」能改成「已支付」,重复回调因状态不满足而被拒绝。
- 乐观锁:表增加
version字段,执行update ... set version = version + 1 where version = 原值,版本不符则更新失败,据此判断是否已被处理。 - 悲观锁:
select ... for update锁住对应记录,需配合事务使用;锁定时间长,读多写少时慎用,且要保证命中主键或索引,否则可能锁全表。 - 分布式锁:以订单号为 key 加锁并设置有效期(防止支付失败后锁不释放),业务执行完释放。
- 先查后改:先查关键数据判断是否执行过,再处理;只适合并发不高的后台任务,核心高并发链路不要用。
- 全局唯一 ID:按业务内容生成全局 ID,执行前判断该 ID 是否存在,存在即视为已执行。
工程上通常多层兜底:唯一约束防脏数据、状态机管流转、分布式锁串行化并发。支付回调、上报回调这类场景重复投递是常态,应在设计阶段就按幂等实现。
7. 一致性哈希 ★★★
普通取模哈希(hash(key) % N)的问题是:节点数 N 一变,几乎所有 key 的归属都要重新计算,缓存大面积失效。
一致性哈希把哈希值空间组织成一个环,节点和 key 都映射到环上;key 沿环顺时针找到的第一个节点就是它的归属。节点增删时,只有该节点相邻区间内的 key 需要迁移,其余不受影响。
节点少时容易分布不均、出现数据倾斜:引入虚拟节点,每个真实节点在环上映射成多个虚拟节点,把区间打散,负载更均匀。
实际使用中一致性哈希多由客户端或中间件封装,核心价值是节点变动时只迁移少量数据,减少缓存穿透到数据库的冲击。
8. 秒杀系统设计 ★★★(场景题)
核心思路是「把流量挡在数据库之前」,逐层削减:
- 前端 / CDN:页面静态化走 CDN,前端做按钮置灰、限流、答题等拦截,尽量把请求挡在源头。
- 接入层限流:Nginx 或网关按 IP / 用户 / 接口限流,挡掉大部分无效流量。
- Redis 预扣库存:用 Lua 把「查库存 + 扣减」做成原子操作,防超卖;库存不足直接返回失败,不落数据库。
- 消息队列削峰:预扣成功的请求投递到 MQ,异步生成订单,数据库按自己的消费能力处理。
- 数据库兜底:真正扣减用乐观锁
where stock > 0或唯一约束,保证最终不超卖。 - 风控:一人一单,用唯一索引或 Redis 用户维度记录,防止重复抢。
完整链路:点击 → CDN → 网关限流 → Redis 预扣 → MQ → 生成订单 → 落库 → 异步扣真实库存。限时优惠券这类营销活动可以复用同一套:Redis 原子预扣名额、MQ 异步落单、唯一约束防重复领。
9. 分布式锁的实现与问题 ★★★ 【简历亮点,高频追问】
- Redis 实现:加锁用
SET key value NX EX,每把锁带唯一 value(如 UUID + 线程 id)。- 释放时必须用 Lua 先校验 value 是否为自己持有、再删除,否则可能误删别人的锁(业务超时、锁已过期后被他人获取)。
- 业务执行时间可能超过锁过期时间,需要看门狗自动续期(Redisson 默认锁 30 秒、每 10 秒续一次)。
- 加锁要设等待超时,抢不到就快速失败,避免线程一直阻塞。
- 隐患是主从切换:主节点加锁成功后尚未同步到从节点就宕机,从节点升主后锁丢失,可用 Redlock 或业务层幂等兜底。
- ZooKeeper 实现:在锁节点下创建临时顺序节点,序号最小者获得锁,其余监听前一个节点;会话断开临时节点自动删除,天然避免死锁。强一致,但性能低于 Redis。
业务上主要用于把「同一资源的并发操作」串行化,例如同一订单的状态变更、同一设备的指令下发;锁的粒度按资源 id 划分,不要把不相关的操作锁在一起。
10. 服务治理:注册发现、配置中心、网关 ★★★
- 注册发现:服务启动后把实例信息注册到注册中心,并通过心跳续约保活;调用方拉取实例列表并本地缓存,配合负载均衡发起调用;实例下线或心跳超时会被摘除。常见组件 Eureka、Nacos、Consul。
- 配置中心:集中管理各环境配置,支持动态刷新(推送或长轮询),改配置不用重启服务,也便于灰度与回滚。
- 网关:统一入口,负责路由转发、鉴权、限流、灰度、协议转换和日志埋点,把横切逻辑从业务服务里收上来。
需要区分两类网关:这里是 HTTP/RPC 层的服务治理网关;另一类是TCP 协议网关(如基于 Netty),负责接入设备、维护长连接和协议解析,两者职责完全不同。
11. 分布式链路追踪 ★★☆
目标是把一次请求跨多个服务的调用串成一条链路,用于定位瓶颈和故障。
- traceId:一次请求全链路唯一的标识,从入口生成后一路透传,日志、RPC、消息里都带上,出问题按 traceId 就能捞出全链路日志。
- span:表示链路中的一次调用(服务内方法或一次远程调用),记录 spanId、parentSpanId、开始时间、耗时和标签,从而还原调用树。
- 透传要点:跨线程要显式传递(ThreadLocal 不会自动继承到线程池),跨服务走请求头,跨 MQ 要写进消息属性。
- 常见方案:SkyWalking(无侵入探针)、Zipkin、Jaeger,都遵循 OpenTracing / OpenTelemetry 规范。
12. 缓存双写一致性与读写分离 ★★☆
缓存与数据库的一致性常用 Cache Aside 模式:读时先查缓存,未命中查库并回填;写时先更新数据库,再删除缓存。
- 不采用「更新缓存」:并发写时更新顺序可能错乱,比删缓存更容易出现不一致。
- 删缓存可能失败,或与并发读交错被回填旧值,可加延迟双删(更新库后删一次,延迟几百毫秒再删一次),或订阅 binlog(如 Canal)异步删缓存,把删除做成可靠的重试链路。
- 极端情况下仍会短暂读到旧值,强一致要求高的场景可以加分布式锁或直接读主库。
读写分离是主库写、从库读,用于分摊读压力;代价是主从延迟(毫秒到秒级),写完立刻要读的场景(如下单后马上查订单)应强制走主库或先读缓存。
13. 幂等 + 分布式锁 + 限流在订单场景的综合运用 ★★☆
下单链路把多个手段叠起来,逐层收口:
- 限流:入口按用户 / 接口限 QPS,防刷和防突发。
- 幂等:用 Token 或唯一请求号防重复提交。
- 分布式锁:按订单号或商品 id 加锁,防止并发扣减。
- 库存扣减:用乐观锁(库存大于 0 才允许扣减)或 Redis 原子扣减,保证不超卖。
- 生成订单:落库,订单状态用状态机约束流转。
- 消息通知:发 MQ 通知下游,失败重试,消费端保证幂等。
多层叠加后,高并发下既不会被流量打穿,也不会重复下单或超卖。
14. 亿级黑名单快速定位 URL(布隆过滤器)★★★
布隆过滤器 = 一个比特数组 + k 个哈希函数。
- 写入:对元素做 k 次哈希,把对应的 k 个 bit 位置 1。
- 查询:任一 bit 为 0,则元素一定不存在;全为 1,则可能存在。
- 因此它有误判、但不漏判,不会把存在的判成不存在。误判率由比特数组长度和哈希个数决定,通常可控在 1% 以内。
特点是极省空间(亿级数据几百 MB)、查询 O(k) 极快;缺点是不支持删除(删除会牵连其他元素),需要删除时用计数布隆或定期重建。
用法是分层拦截:布隆过滤器做第一层,判断「一定不存在」的直接拒绝;命中的再回数据库精确确认,避免误判导致误杀。
15. 设计短网址系统 ★★★(场景题)
核心是「长 URL ↔ 短码」的映射与跳转。
- 短码生成有两条路:
- 哈希 + 截断:对长 URL 做 MD5 等哈希后取前几位,需要处理冲突(加盐重算或调整长度)。
- 发号器:全局自增 id 转 Base62(0-9、a-z、A-Z,6 位可表示约 568 亿个),无冲突、长度短,推荐。
- 存储:MySQL 存映射(短码做唯一索引),Redis 缓存热点映射;也可用 K-V 存最近/最热门的对应关系,省空间又提速。
- 跳转:用 302 临时重定向而不是 301——301 会被浏览器缓存,后续请求不再经过服务器,既统计不到点击,也无法更换目标。
- 扩展:点击统计异步写 MQ;同一长 URL 可复用已有短码;过期短码和黑名单可结合布隆过滤器拦截。
16. 限时订单设计(超时未支付取消)★★★ 【项目强相关】
订单创建后限时(如 30 分钟)未支付则自动取消并释放资源。三种实现:
- 延迟消息(最推荐):下单时发一条延迟消息,到点消费检查订单状态,仍未支付就取消。准时、没有扫库压力;RocketMQ 支持延迟级别,也可用 RabbitMQ 死信队列 + TTL 实现。
- 定时任务扫描:定时扫出超时未支付订单批量取消。实现简单,但有扫描延迟和扫库压力,数据量大时要分批处理。
- Redis 过期监听:key 过期触发回调。精度和可靠性都差(过期事件可能丢失),生产上慎用。
关键点:
- 取消动作必须幂等,重复消息不能重复释放资源;
- 用状态机约束「只有待支付能改成已取消」,防止已支付订单被误取消;
- 取消后要回补库存 / 释放资源,回补同样要幂等;
- 工程上用「延迟消息 + 定时任务兜底」双保险:延迟消息保证准时,定时任务捞出漏掉的消息,避免消息丢失导致订单永久占用资源。
17. 设计高并发支付网关 ★★★ 【项目强相关】
关注七个方面:
- 安全:请求签名验签(私钥签名、公钥验签),防重放(时间戳 + nonce + 唯一请求号),敏感信息加密传输。
- 高可用:多节点无状态部署,配合限流、熔断、依赖隔离,避免单点。
- 一致性:支付结果与订单状态一致,靠状态机 + 幂等 + 对账保证。
- 幂等:同一订单不能重复扣款,用唯一约束、状态机、分布式锁多层兜底。
- 异步通知:回调走 MQ 异步处理,失败按退避策略重试,回调接口必须幂等。
- 对账:T+1 与渠道对账,处理掉单(渠道成功、本地无记录)和差错账(金额或状态不一致),差错走自动补单或人工处理。
- 状态机:支付状态严格流转(待支付 → 支付中 → 成功/失败 → 已退款),非法流转直接拒绝。
回调丢失是必然而不是偶然,兜底要三层:渠道回调通知、定时主动查询(回调没来就轮询渠道查单)、T+1 对账。只靠回调一定会漏单,对账是最后一道收敛手段。
18. 设计打车服务 ★★☆(场景题)
主线是「位置存储 + 附近检索 + 派单」。
- 位置存储:司机端定时上报经纬度,用 GeoHash 把二维坐标编码成字符串,相近位置的编码前缀相同,便于按前缀检索;也可用空间索引(R 树)。
- 附近检索:用 Redis GEO(底层是 GeoHash + 有序集合),
GEOADD写入、GEOSEARCH按半径查附近司机并排序。 - 派单:按距离、评分、空闲状态筛选后,用分布式锁或状态机保证一个订单只派给一个司机,防止重复派单;司机接单后状态置为「服务中」。
- 削峰:位置上报频率高,用 WebSocket 长连接 + MQ 异步处理,只保留最新位置、旧位置覆盖,避免写放大。
同类的「找附近场站」也是同一思路:经纬度 + 距离检索,只是数据量和实时性要求不同。
19. 设计视频流系统 ★★☆
- 存储:视频存对象存储(S3 / OSS),支持大文件与多副本,元数据存数据库。
- 分发:用 CDN 就近分发,减轻源站压力;热门内容预热到边缘节点。
- 上传:分片上传 + 断点续传,大文件切片并行上传,失败只重传分片;上传后做多码率转码(不同分辨率/码率)和内容审核。
- 播放:自适应码率(HLS / DASH)按网络状况切换清晰度,支持断点续传和拖动,首帧优化靠预加载与边下边播。
- 元数据:读写分离 + 缓存扛读。
一句话结论:视频系统的核心是「对象存储 + CDN + 多码率转码」,CDN 就近分发减轻源站压力,多码率适配不同网络。
20. 实时统计 DAU/WAU/MAU(Bitmap)★★★
用 Redis Bitmap:一个用户占一个 bit 位,用户 id 作为偏移量。
- 记录当天活跃:
setbit key user_id 1; - 统计当天活跃数:
bitcount key; - 周活 / 月活:把多天的 bitmap 用
bitop OR求并集,再bitcount,天然去重。
优点是极省内存(1 亿用户约 12MB)、天然去重、位运算快。前提是用户 id 能映射成连续整数;如果 id 稀疏(大量空洞)或量级远超实际用户数,Bitmap 反而浪费空间,应改用 HyperLogLog。
21. 统计网站每个网页的 UV(HyperLogLog)★★★
HyperLogLog 是概率数据结构,用固定极小的内存(每个 key 约 12KB)估算集合的基数(独立元素个数),标准误差约 0.81%。
- 每个网页每天一个 key:
pfadd key visitor_id记录访客,pfcount key得到 UV,多个 key 可用pfmerge合并。 - 原理:基于哈希后前导零的最大长度估计基数,并用分桶求调和平均降低方差,所以内存固定、与元素数量无关。
- 相比 Set 精确去重,它是用「近似值」换「固定的小内存」;不支持删除单个元素(可覆盖重建)。
选型:id 连续且范围可控用 Bitmap;稀疏、超大基数去重用 HyperLogLog;必须精确再用 Set(内存随元素线性增长)。
22. 亿数量高效排序(外部排序)★★☆
内存装不下时用外部归并排序,分治 + 归并:
- 分块:按内存容量把大文件切成若干块,每块读入内存排好序后写回临时有序文件。
- 多路归并:用小顶堆维护每一路当前最小元素,每次取堆顶输出并补充该路的下一个元素,把多个有序块合并成一个有序大文件。
- 优化:增大块大小以减少归并路数、多路并行归并、顺序读写减少随机 IO。
瓶颈在磁盘 IO 而不是比较次数,所以优化方向是减少 IO 次数、提升顺序读。这种量级的统计排序通常直接用 ClickHouse、InfluxDB 这类引擎,不必自己实现外部排序。
23. 为什么要系统拆分、如何拆分 ★★★
单体随业务变大的问题:编译部署慢、代码耦合、团队协作冲突、故障影响面大(一处 OOM 全站挂)、无法按模块独立扩容。
拆分收益:独立部署、独立扩展、故障隔离、团队职责和技术栈清晰。
怎么拆:
- 按业务域拆(识别核心领域与边界,参考 DDD 限界上下文),而不是按技术分层拆;
- 每个服务独立数据库,避免共享库造成隐性耦合;
- 先拆边缘、耦合低的服务,核心链路最后动;
- 配套注册中心、配置中心、网关、链路追踪、监控告警,否则拆完更难排查;
- 拆分代价是跨服务一致性、分布式事务、联调和运维成本上升,所以要避免过度拆;小规模先单体,规模到了再拆,粒度与团队规模匹配。
24. 为什么做分库分表 ★★☆
单表到千万行级别后:查询变慢、索引维护成本高、DDL 锁表时间长;单库则受连接数、磁盘容量、IO 上限约束。分库分表是横向扩展手段,让每个库/表的数据量和压力变小。
- 垂直拆分:按业务或字段拆,把不常用的大字段拆出去,或按业务域分库。
- 水平拆分:同一张表按分片键(如用户 id、订单号)散到多个库/表,结构一致。
代价是分布式事务、跨库 join 和分页、分布式主键、路由与扩容复杂度、跨片聚合统计。
所以它是最后手段,演进顺序是:索引优化 → 缓存 → 读写分离 → 分库分表。数据量未到瓶颈前,用索引优化、冷热归档、读写分离通常就能撑住。
附:原始题库(旧版逐题,与上文重叠,可折叠跳过)
展开查看原始题库
25. 分布式幂等性如何设计?
在高并发场景的架构里,幂等性是必须得保证的。比如说支付功能,用户发起支付,如果后台没有做幂等校验,刚好用户手抖多点了几下,于是后台就可能多次受到同一个订单请求,不做幂等很容易就让用户重复支付了,这样用户是肯定不能忍的。
解决方案
1,查询和删除不在幂等讨论范围,查询肯定没有幂等的说,删除:第一次删除成功后,后面来删除直接返回0,也是返回成功。
2,建唯一索引:唯一索引或唯一组合索引来防止新增数据存在脏数据 (当表存在唯一索引,并发时新增异常时,再查询一次就可以了,数据应该已经存在了,返回结果即可)。
3,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;
InnoDB 存储引擎,那么就会出现锁全表)。悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用(另外还要考虑id是否为主键,如果id不是主键或者不是
5,乐观锁,给数据库表增加一个version字段,可以通过这个字段来判断是否已经被修改了
update table_xxx set name=#name#,version=version+1 where version=#version#
6,分布式锁,比如Redis、Zookeeper 的分布式锁。单号为key,然后给Key设置有效期(防止支付失败后,锁一直不释放),来一个请求使用订单号生成一把锁,业务代码执行完成后再释放锁。
7,保底方案,先查询是否存在此单,不存在进行支付,存在就直接返回支付结果。
26. 简单一次完整的 HTTP 请求所经历的步骤?
IP 地址,递归搜索)。1、DNS 解析(通过访问的域名找出其
2、 HTTP 请求,当输入一个请求时,建立一个 Socket 连接发起 TCP的 3 次握手。
如果是 HTTPS 请求,会略微有不同。等到 HTTPS 小节,我们在来讲。
3.1、客户端向服务器发送请求命令(一般是 GET 或 POST 请求)。
这个是补充内容,面试一般不用回答。
客户端的网络层不用关心应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,我不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。
客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定 IP 地址的 MAC 地址,然后发送 ARP 请求查找目的地址,如果得到回应后就可以使用 ARP 的请求应答交换的 IP 数据包现在就可以传输了,然后发送 IP 数据包到达服务器的地址。
3.2、客户端发送请求头信息和数据。
4.1、服务器发送应答头信息。
4.2、服务器向客户端发送数据。
5、服务器关闭 TCP 连接(4次挥手)。
这里是否关闭 TCP 连接,也根据 HTTP Keep-Alive 机制有关。
同时,客户端也可以主动发起关闭 TCP 连接。
6、客户端根据返回的CSS、HTML、JS 进行渲染。

27. 说说你对分布式事务的了解
分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西, 特别是在微服务架构中,几乎可以说是无法避免。
首先要搞清楚:ACID、CAP、 BASE理论。
ACID
指数据库事务正确执行的四个基本要素:
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation )
持久性(Durability)
CAP
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性
(Availability)、分区容忍性(Partition tolerance)。 CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。
可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
分区容忍性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
BASE理论
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
28. 你知道哪些分布式事务解决方案?
我目前知道的有五种:
两阶段提交(2PC)
三阶段提交(3PC)
补偿事务(TCC=Try-Confirm-Cancel)
本地消息队列表(MQ)
Sagas事务模型(最终一致性)
说完上面五种,面试官一般都会继续问下面这几个问题(可能就问一两个,也可能全部问)。
29. 什么是二阶段提交?
两阶段提交2PC是分布式事务中最强大的事务类型之一,两段提交就是分两个阶段提交:
第一阶段询问各个事务数据源是否准备好。
. 第二阶段才真正将数据提交给事务数据源。
为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被称为参与者 (Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。
处理流程如下:

阶段一
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 消息后,即完成事务。
问题
性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。
可靠性问题:如果协调者存在单点故障问题,或出现故障,提供者将一直处于锁定状态。
数据一致性问题:在阶段 2 中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一 致。
优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)。
缺点:实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
补充(原文详解)
三阶段提交是在二阶段提交上的改进版本,3PC最关键要解决的就是协调者和参与者同时挂掉的问题,所以3PC把2PC的准备阶段再次一分为二,这样三阶段提交。
处理流程如下:
阶段一
a) 协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。
b) 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。
阶段二
协调者根据参与者响应情况,有以下两种可能。
情况1:所有参与者均反馈 yes,协调者预执行事务
a) 协调者向所有参与者发出 preCommit 请求,进入准备阶段。
b) 参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。
c) 各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。
情况2:只要有一个参与者反馈 no,或者等待超时后协调者尚无法收到所有提供者的反馈,即中断事务
a) 协调者向所有参与者发出 abort 请求。
b) 无论收到协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事务。
阶段三
该阶段进行真正的事务提交,也可以分为以下两种情况。
情况 1:所有参与者均反馈 ack 响应,执行真正的事务提交
a) 如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。
b) 参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。
c) 各参与者向协调者反馈 ack 完成的消息。
d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。
情况2:只要有一个参与者反馈 no,或者等待超时后协调组尚无法收到所有提供者的反馈,即回滚事务。
a) 如果协调者处于工作状态,向所有参与者发出 rollback 请求。
b) 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
c) 各参与者向协调组反馈 ack 完成的消息。
d) 协调组收到所有参与者反馈的 ack 消息后,即完成事务回滚。
优点:相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。 避免了协调者单点问题。阶段 3 中协调者出现问题时,参与者会继续提交事务。
缺点:数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 do commite 指令时, 此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
30. 什么是补偿事务?
TCC ( Try Confirm Cancel)是服务化的二阶段编程模型,采用的补偿机制:

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
它分为三个步骤:
Try 阶段主要是对业务系统做检测及资源预留。
Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
举个例子,假入你要向老田转账,思路大概是: 我们有一个本地方法,里面依次调用步骤: 1、
首先在 Try 阶段,要先调用远程接口把你和老田的钱给冻结起来。 2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。 3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点:
性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源。
数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。
缺点:TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
31. 消息队列是怎么实现的?
本地消息表(异步确保)
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:

基本思路就是:
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像 TCC那样可能出现确认或者回滚不了的情况。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中有现成的解决方案。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
MQ 事务消息
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:
第一阶段Prepared消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

遗憾的是,RocketMQ并没有 .NET 客户端。有关 RocketMQ的更多消息,大家可以查看这篇博客
优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 实现难度大,主流MQ不支持,没有.NET客户端,RocketMQ事务消息部分代码也未开源。
32. 那你说说Sagas事务模型
Saga模式是一种分布式异步事务,一种最终一致性事务,是一种柔性事务,有两种不同的方式来实现saga事务,最流行的两种方式是:
一、 事件/编排Choreography:没有中央协调器(没有单点风险)时,每个服务产生并聆听其他服务的事件,并决定是否应采取行动。
该实现第一个服务执行一个事务,然后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件,当最后一个服务执行本地事务并且不发布任何事件时,意味着分布式事务结束,或者它发布的事件没有被任何Saga参与者听到都意味着事务结束。

处理流程说明:
订单服务保存新订单,将状态设置为pengding挂起状态,并发布名为ORDER_CREATED_EVENT的事件。
支付服务监听ORDER_CREATED_EVENT,并公布事件BILLED_ORDER_EVENT。
库存服务监听BILLED_ORDER_EVENT,更新库存,并发布ORDER_PREPARED_EVENT。
货运服务监听ORDER_PREPARED_EVENT,然后交付产品。最后,它发布
ORDER_DELIVERED_EVENT。
最后,订单服务侦听ORDER_DELIVERED_EVENT并设置订单的状态为concluded完成。
假设库存服务在事务过程中失败了。进行回滚:
库存服务产生PRODUCT_OUT_OF_STOCK_EVENT
订购服务和支付服务会监听到上面库存服务的这一事件:
①支付服务会退款给客户。
②订单服务将订单状态设置为失败。
优点:事件/编排是实现Saga模式的自然方式; 它很简单,容易理解,不需要太多的努力来构建,所有参与者都是松散耦合的,因为他们彼此之间没有直接的耦合。如果您的事务涉及2至4个步骤,则可能是非常合适的。
二、 命令/协调orchestrator:中央协调器负责集中处理事件的决策和业务逻辑排序。
saga协调器orchestrator以命令/回复的方式与每项服务进行通信,告诉他们应该执行哪些操作。

订单服务保存pending状态,并要求订单Saga协调器(简称OSO)开始启动订单事务。
OSO向收款服务发送执行收款命令,收款服务回复Payment Executed消息。
OSO向库存服务发送准备订单命令,库存服务将回复OrderPrepared消息。
OSO向货运服务发送订单发货命令,货运服务将回复Order Delivered消息。
OSO订单Saga协调器必须事先知道执行“创建订单”事务所需的流程(通过读取BPM业务流程XML配置获得)。如果有任何失败,它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回滚。当你有一个中央协调器协调一切时,回滚要容易得多,因为协调器默认是执行正向流程,回滚时只要执行反向流程即可。
优点:
避免服务之间的循环依赖关系,因为saga协调器会调用saga参与者,但参与者不会调用协调器。
集中分布式事务的编排。
只需要执行命令/回复(其实回复消息也是一种事件消息),降低参与者的复杂性。
在添加新步骤时,事务复杂性保持线性,回滚更容易管理。
如果在第一笔交易还没有执行完,想改变有第二笔事务的目标对象,则可以轻松地将其暂停在协调器上,直到第一笔交易结束。
33. 幂等解决方法有哪些?
什么是幂等?
常见描述:对于相同的请求应该返回相同的结果,所以查询类接口是天然的幂等性接口。
真正的回答方式:幂等指的是相同请求(identical request)执行一次或者多次所带来的副作用(side-effects)是一样的。
什么常见会出现幂等?
前端调后端接口发起支付超时,然后再次发起重试。可能会导致多次支付。
Dubbo中也有重试机制。
页面上多次点击。
我们想要的是:接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。
解决方案
在插入数据的时候,插入去重表,利用数据库的唯一索引特性,保证唯一的逻辑。
悲观锁,select for update,整个执行过程中锁定该订单对应的记录。注意:这种在DB读大于写的情况下尽量少用。
先查询后修改数据,并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,就可以了。注意:核心高并发流程不要用这种方法。
状态机幂等,在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机,就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的
话,保证了有限状态机的幂等。
token机制,防止页面重复提交:
集群环境:采用token加redis( redis单线程的,处理需要排队)或者
单JVM环境:采用token加redis或token加jvm内存
数据提交前要向服务的申请token,token放到redis或jvm内存,设置token有效时间,提交后后台校验token,同时删除token,生成新的token返回。token特点:要申请,一次有效性,可以限流。
全局唯一ID,如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、 redis等。如果存在则表示该方法已经执行。
34. 常见负载均衡算法有哪些?
35. 你知道哪些限流算法?
限流算法有四种常见算法:
计数器算法(固定窗口)
滑动窗口
漏桶算法
令牌桶算法
36. 说说什么是计数器(固定窗口)算法
计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。
此算法在单机还是分布式环境下实现都非常简单,使用redis的incr原子自增性和线程安全即可轻松实现。
这个算法通常用于QPS限流和统计总访问量,对于秒级以上的时间周期来说,会存在一个非常严重的问题,那就是临界问题,如下图:

假设1min内服务器的负载能力为100,因此一个周期的访问量限制在100,然而在第一个周期的最后5秒和下一个周期的开始5秒时间段内,分别涌入100的访问量,虽然没有超过每个周期的限制
量,但是整体上10秒内已达到200的访问量,已远远超过服务器的负载能力,由此可见,计数器算法方式限流对于周期比较长的限流,存在很大的弊端。
37. 说说什么是滑动窗口算法
滑动窗口算法是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。
如下图,假设时间周期为1min,将1min再分为2个小周期,统计每个小周期的访问数量,则可以看到,第一个时间周期内,访问数量为75,第二个时间周期内,访问数量为100,超过100的访问则被限流掉了

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
此算法可以很好的解决固定窗口算法的临界问题。
38. 说说什么是漏桶算法
漏桶算法是访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。

补充(原文详解)
令牌桶算法是程序以r( r=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,请求到
达时向令牌桶请求令牌,如获取到令牌则通过请求,否则触发限流策略

39. 数据库如何处理海量数据?
对数据库进行:分库分表,主从架构,读写分离。
水平分库/分表,垂直分库/分表。
水平分库/表,各个库和表的结构一模一样。
垂直分库/表,各个库和表的结构不一样。
读写分离:主机负责写,从机负责读。
40. 如何将长链接转换成短链接,并发送短信?
短 URL 从生成到使用分为以下几步:
有一个服务,将要发送给你的长 URL 对应到一个短 URL 上.例如 www.baidu.com -> www.t.cn/ 1。
把短 url 拼接到短信等的内容上发送。
用户点击短 URL ,浏览器用 301 / 302 进行重定向,访问到对应的长 URL。
展示对应的内容。
41. 长链接和短链接如何互相转换?
思路是建立一个发号器。每次有一个新的长 URL 进来,我们就增加一。并且将新的数值返回 .第一个来的 url 返回"www.x.cn/0",第二个返回"www.x.cn/1"。
42. 长链接和短链接的对应关系如何存储?
如果数据量小且 QPS 低,直接使用数据库的自增主键就可以实现。 还可以将最近/最热门的对应关系存储在 K-V 数据库中,这样子可以节省空间的同时,加快响应速度。
43. 如何提高系统的并发能力?
使用分布式系统。
部署多台服务器,并做负载均衡。
使用缓存(Redis)集群。
数据库分库分表 + 读写分离。
引入消息中间件集群。
44. 什么是SpringCloud
Spring cloud 流应用程序启动器是基于 Spring Boot 的 Spring 集成应用程序,提供与外部系统的集成。Spring cloud Task,一个生命周期短暂的微服务框架,用于快速构建执行有限数据处理的应用程序。
补充(原文详解)
使用 Spring Boot 开发分布式微服务时,我们面临以下问题
( 1)与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。
( 2)服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。
( 3)冗余-分布式系统中的冗余问题。
( 4)负载平衡 --负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路, 中央处理单元,或磁盘驱动器的分布。
( 5)性能-问题由于各种运营开销导致的性能问题。
( 6)部署复杂性-Devops 技能的要求。
45. 什么是微服务
微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分为一组小的服务, 每个服务运行在其独立的自己的进程中,服务之间相互协调、互相配合,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API),每个服务都围绕着具体的业务进行构建,并且能够被独立的构建在生产环境、类生产环境等。另外,应避免统一的、集中
式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。
46. 什么是服务熔断?什么是服务降级?
熔断机制是应对雪崩效应的一种微服务链路保护机制。当某个微服务不可用或者响应时间太长时, 会进行服务降级,进而熔断该节点微服务的调用,快速返回“错误”的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内调用20次,如果失败,就会启动熔断机制。
服务降级,一般是从整体负荷考虑。就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。这样做,虽然水平下降,但好歹可用, 比直接挂掉强。
Hystrix相关注解 @EnableHystrix:开启熔断 @HystrixCommand(fallbackMethod=”XXX”):声明一个失败回滚处理函数XXX,当被注解的方法执行超时(默认是1000毫秒),就会执行fallback函数,返回错误提示。
47. SpringBoot和SpringCloud的区别?
SpringBoot专注于快速方便的开发单个个体微服务。
SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,
为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot,属于
依赖的关系.
SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
48. 负载平衡的意义什么?
在计算中,负载平衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源的过载。使用多个组件进行负载平衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务器进程。
49. 什么是Hystrix?它如何实现容错?
Hystrix是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的故障时,停止级联故障并在复杂的分布式系统中实现弹性。
通常对于使用微服务架构开发的系统,涉及到许多微服务。这些微服务彼此协作。
思考以下微服务

假设如果上图中的微服务9失败了,那么使用传统方法我们将传播一个异常。但这仍然会导致整个系统崩溃。
随着微服务数量的增加,这个问题变得更加复杂。微服务的数量可以高达1000.这是hystrix出现的地方我们将使用Hystrix在这种情况下的Fallback方法功能。我们有两个服务employee-consumer 使用由employee-consumer公开的服务。
简化图如下所示
现在假设由于某种原因,employee-producer公开的服务会抛出异常。我们在这种情况下使用
Hystrix定义了一个回退方法。这种后备方法应该具有与公开服务相同的返回类型。如果暴露服务中出现异常,则回退方法将返回一些值。
50. 什么是Hystrix断路器?我们需要它吗?
由于某些原因,employee-consumer公开服务会引发异常。在这种情况下使用Hystrix我们定义了一个回退方法。如果在公开服务中发生异常,则回退方法返回一些默认值。

如果firstPage method() 中的异常继续发生,则Hystrix电路将中断,并且员工使用者将一起跳过
firtsPage方法,并直接调用回退方法。 断路器的目的是给第一页方法或第一页方法可能调用的其他
方法留出时间,并导致异常恢复。可能发生的情况是,在负载较小的情况下,导致异常的问题有更好的恢复机会。
51. eureka自我保护机制是什么?
当Eureka Server 节点在短时间内丢失了过多实例的连接时(比如网络故障或频繁启动关闭客户
端)节点会进入自我保护模式,保护注册信息,不再删除注册数据,故障恢复时,自动退出自我保护模式。
52. 什么是Ribbon?
ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。feign默认集成了ribbon。
53. 什么是feigin?它的优点是什么?
1.feign采用的是基于接口的注解 2.feign整合了ribbon,具有负载均衡的能力 3.整合了Hystrix,具有熔断的能力
使用: 1.添加pom依赖。 2.启动类添加@EnableFeignClients 3.定义一个接口
@FeignClient(name=“xxx”)指定调用哪个服务
54. Ribbon和Feign的区别?
1.Ribbon都是调用其他服务的,但方式不同。 2.启动类注解不同,Ribbon是@RibbonClient feign
的是@EnableFeignClients 3.服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign 则是在定义抽象方法的接口中使用@FeignClient声明。 4.调用方式不同,Ribbon需要自己构建http 请求,模拟http请求
附:一句话速记(可以跳过 ★☆☆)
- 2PC 的 XA:数据库层协议,同步阻塞重,现在基本只做了解。
- Forking 容错:并行调多个实例取最快返回,浪费资源,只在极低延迟要求时用。
Cao Note