1. 分布式架构:先摸清“团建”的底层逻辑
分布式系统这个词,在技术圈被聊得最多,也最容易被聊糊。一搜“分布式”,出来的全是分布式锁、分布式事务、分布式缓存、分布式架构、分布式爬虫、分布式定时任务、分布式UUID……知识点碎得跟一地零件似的。在Java+分布式系统开发这个方向上,这些几乎就是必修课。我自己的体会是,与其死记这些名词,不如换个角度:把一群服务器当成一支团队。分布式系统说白了就是组织一场“分布式团建”——每个节点都是独立个体,有自己算力、自己内存、自己的磁盘,网络还可能随时抖动,而你要做的,就是让这群“性格各异”的节点像同一个团队一样协作,完成一件单机干不了的事。这篇文章我就按这个思路,把分布式里最常见的几个技术点挨个拆开讲,连命令、代码、踩坑记录一起给你摆出来。
1.1 单机撑不住的时候,就是需要“团队”的时候
先用大白话说清楚为什么要分布式。单机时代,一台服务器吃喝拉撒全包:接收请求、处理业务、读写数据库、存文件。它能扛的并发量其实就是靠线程池堆,扛不住就加内存、加CPU,也就是“垂直扩容”。垂直扩容有个硬天花板,机器配置越往上越贵,而且到了一定规模后,加了配置也不一定线性提升性能。我做过一个纯单机的接口,八核十六G的机器,压测到2000 QPS就开始出现明显抖动,线程池排队、GC频繁,再加内存也只是延迟问题爆发的时间。
更重要的是可用性。再好的服务器也有宕机的一天,重启、断电、硬件老化、机房故障,随便哪个都够单点喝一壶。一旦这台机器挂了,整个业务全断,用户直接骂街。分布式要解决的核心问题不外乎三个:一是性能扩展,让多个节点分担请求压力;二是可用性,一个节点挂了其他节点接着顶;三是数据容量,单机磁盘不够用时,把数据分散到多台机器上。这也是为什么现在大家张口就是“分布式架构”“分布式算力”——本质上是想用一群便宜、普通甚至偶尔不稳定的机器,拼出一台“可靠的大机器”。
1.2 节点、注册中心与CAP:团队协作的基本规则
分布式架构里,每个独立运行的进程就是一个节点。节点和节点之间要通信,就得通过网络,比如HTTP、RPC、消息队列。但光能通信不够,上游调用下游,怎么知道下游在哪、有几个?这就轮到注册中心登场了。常见的注册中心有Nacos、Eureka、Consul、ZooKeeper,服务启动时往注册中心登记自己的IP和端口,调用方再去注册中心拉取服务列表,动态选一个来调,这就是“服务发现”。配置中心则用来统一管理所有节点的配置,修改一个配置项,不需要一台台机器去改文件,推送到所有节点就行。这就像是团建前的分工表和统一口径,没有这两样,几十个节点各怀心思,协作根本无从谈起。这套协作框架不仅在应用层成立,网络设备领域的分布式交换机系统架构,比如VxLAN、SDN控制器,本质也是把控制面和数据面分离到不同节点,采用同样的协调思想。
讲分布式,绕不开CAP理论。C是一致性,A是可用性,P是分区容忍性。分布式系统里网络分区一定会发生,所以P必须选,剩下C和A只能二选一。理解CAP的要点在于:所谓“取舍”,不是说系统完全不要一致性或者完全不要可用性,而是说在极端场景下选择优先保全哪一边。比如注册中心Nacos默认是AP模式,允许短暂的数据不一致,保证服务调用不中断;而像ZooKeeper则更偏CP,节点挂了之后宁可暂时不可用,也要保证数据一致。实际做架构选型时,第一件事就是问自己:业务在分区发生时,更怕数据错,还是更怕服务停?
1.3 分布式的本质:在不可靠环境里达成共识
如果让我用一句话概括分布式系统的本质,就是“在不可靠的环境里达成共识”。网络会断、机器会宕、消息会延迟,但业务还是得跑,数据还是得对。分布式锁是让多个节点对“谁进临界区”达成共识,分布式事务是让多个服务对“业务到底成没成”达成共识,分布式ID是让所有节点对“编号不重复”达成共识。只要带着这个视角去学,你会发现分布式技术不是一堆孤立的方案,而是一套围绕“共识”展开的组合拳。后面几章,我按这个逻辑一个个拆。
2. 分布式锁:团队里只有“一杆话筒”
团建里最怕抢话,发言没秩序,一个项目就乱套。分布式里的“抢话”,就是多个节点同时操作同一个共享资源。典型场景是库存扣减、秒杀下单、账号提现、分布式定时任务防重。分布式锁的使用场景,基本都逃不开这几个。
2.1 为什么单机锁到了分布式环境就失灵
单机环境下我们用synchronized、ReentrantLock就能把并发线程挡在门外,但分布式环境下,多个请求可能被负载均衡分发到不同的节点上,每个节点有自己的JVM锁,谁也锁不住别人。我举个真实例子。做一个秒杀接口,商品库存只有10件,前端一放开,10000个请求进来。第一个节点先查库存,发现还有10件,刚要执行UPDATE库存减1,同一瞬间另一个节点也查到了10件,两个节点都认为自己扣减成功,最后库存只减了1,超卖就发生了。解决思路是让多个节点去访问一个公共的“锁服务”,谁拿到锁谁才有资格操作共享资源。这个公共锁服务,就是分布式锁。
Redis分布式锁为什么能做这事?因为Redis是单线程执行命令,命令之间有天然的原子性。只要多个节点都去同一个Redis实例上执行SETNX,同时只能有一个节点设置成功,这就形成了“一杆话筒”的效果。
2.2 Redis分布式锁的经典写法和那点“坑”
目前最常见的分布式锁实现是Redis,核心是SET命令加NX和PX参数:
SET lock:order:1001 1 NX PX 30000这条命令的含义是:只有当lock:order:1001这个key不存在时才设置成功,同时设置30秒过期时间。设置成功就说明拿到了锁,业务处理完再释放锁。释放锁不能直接DEL,因为你可能拿到锁之后业务执行超过了30秒,锁已经自动过期被别人拿走了,再DEL就误删别人的锁了。所以要带上身份校验,用Lua脚本保证判断和删除的原子性:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end这里ARGV[1]是加锁时写入的唯一标识,一般是UUID加线程ID,用来标识“这把锁是我拿的”。工程上我更推荐直接用Redisson,它把加锁、解锁、续期都封装好了。Redisson会为锁启动一个“看门狗”机制,默认锁30秒,如果业务还没执行完,看门狗会自动把锁续期,避免业务没跑完锁先过期的问题。
RLock lock = redissonClient.getLock("stock:sku:1001"); if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { try { // 扣库存业务逻辑 } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }tryLock第一个参数是等待锁的时间,第二个是锁的有效期,需要结合业务执行时间压测来定,别死记参数。坑主要在两方面:一是Redis主从切换导致锁丢失,如果主节点还没把锁数据同步到从节点就挂了,新主节点上这把锁就没了,另一个节点就能拿到同一把锁,产生并发问题。Redis官方提出过RedLock算法,让客户端向多个独立的Redis实例同时申请锁,超过半数成功才算加锁成功,但RedLock本身争议很大,工程上应用不多。实践中的常规做法是充分评估业务对“锁失效窗口”的容忍度,必要时改用ZooKeeper、etcd这类带强一致语义的锁服务。
2.3 ZooKeeper、etcd锁:更稳但是更重
ZooKeeper实现分布式锁用的是临时顺序节点。多个节点同时在一个锁路径下创建临时顺序节点,序号最小的节点拥有锁,其他节点监听自己前一个节点的删除事件,前一个节点释放锁就轮到下一个。由于ZooKeeper节点是强一致模型,同时只有一个节点能拿到锁,所以不会有Redis主从切换导致的锁丢失问题。缺点也很明显:性能不如Redis,加锁解锁要多轮网络交互,而且需要维持Session,客户端一旦和ZK断开会话,临时节点会被删除,锁也会释放。
etcd用Revision机制实现锁,思路类似,但是etcd的线性一致性读在分布式协调场景下更可靠,云原生项目里用得越来越多。三者的选择可以简单粗暴地这样记:追求性能、业务允许短时间锁异常,选Redis;业务强一致敏感、锁生命周期短、并发量不太大,选ZooKeeper或etcd。如果问我的话,除了强一致场景,我基本都是Redis,毕竟部署运维成本最低,出问题也好排查。
2.4 分布式锁面试题:绕不开的几个问题
聊到分布式锁,面试官最爱问的几个问题大概是:Redis分布式锁过期了怎么办,比如业务超时导致锁被自动释放,其他节点进入临界区。解法就是上面说的看门狗自动续期,或者结合业务做幂等兜底。再问锁的粒度怎么设计,答案是锁的key尽量细化,锁用户维度就别锁全表,锁商品维度就别锁订单,锁粒度越细,并发度越高。还会问可重入怎么做,Redisson里默认就是可重入的,它内部用hash结构记录持有锁的线程和计数,同一线程重复tryLock会累加计数,释放一次减一次,减到0才真正释放。这些题没有标准答案,核心是理解分布式锁的本质:它是在不可靠的网络上,让多个节点对“只有一个赢家”达成共识。
3. 分布式事务与一致性:团建要求“全员行动一致”
3.1 订单和库存的“跨库难题”
为什么会有分布式事务?因为微服务化之后,数据被拆开了。一个下单流程涉及订单服务、库存服务、积分服务,每个服务的数据都放在自己的数据库里。本地事务只能保证自己的库,跨库、跨服务的事务,本地事务就管不到了。
我用一个大家都懂的“订单与库存分布式事务”场景来说明。用户下单支付成功后,假设业务规则是:订单表落一条记录,库存表减一个SKU数量,用户积分表加积分。如果订单落库成功、库存扣减失败,怎么办?用户看不到订单,但可能已经扣了钱;如果库存扣了但订单失败了,库存白白少一件。传统单机数据库一条SQL能解决的事,在分布式环境下要设计一套跨服务的一致性方案。
分布式事务的思路不是“数字上完全同时成功”,而是“最终都成功,或者都已经明确失败并补偿”。这就要分清两种模型:强一致模型和最终一致模型。
3.2 强一致方案:2PC、TCC、SAGA
强一致的代表方案是两阶段提交(2PC)。事务协调者先问所有参与者“准备好了吗”,也就是第一阶段prepare,所有参与者都返回OK后,协调者再发commit指令,让所有人同时提交。2PC的弊端也众所周知:协调者是单点,prepare之后如果协调者挂了,所有参与者都在等着,资源被长时间锁定,性能很差,所以在互联网高并发场景下基本不直接用。
TCC则把事务拆成Try、Confirm、Cancel三个动作。Try阶段完成资源预留和业务检查,比如冻结库存;Confirm阶段真正执行业务,扣减预冻的库存;Cancel阶段回滚释放资源。TCC优点是业务控制力强,缺点是要为每个操作写三套逻辑,开发量翻倍,适合银行转账这类对一致性要求极高的场景。SAGA则把一个长事务拆成一系列子事务,每个子事务都有对应的补偿操作,按顺序执行,某一步失败就按逆序执行补偿。SAGA更贴近业务流程,但补偿逻辑设计复杂,而且中间状态对外可见,需要考虑业务是否接受。
考虑到多数互联网业务其实没那么强一致,我更推荐下面的最终一致方案。
3.3 最终一致性:本地消息表与消息队列
最终一致的核心思想是“先保住本地事务,然后通过异步不断重试,确保最终都落地”。最经典的是本地消息表方案,流程大概是这样的:
- 订单服务在自己的本地事务里同时做两件事:写订单数据,写一条消息记录到本地消息表,状态为“待发送”。
- 本地事务提交后,订单服务启动一个定时任务,扫描消息表中状态为“待发送”的记录,把消息发送到MQ。
- 库存服务消费MQ里的消息,执行库存扣减,处理成功后回调订单服务,把消息状态更新为“已发送/已处理”。
- 如果库存服务处理失败或者MQ消息丢失,本地消息表里的消息一直没被确认,定时任务就会重发。重发时消费方要处理幂等,也就是重复消息不能重复扣库存。
这套方案的关键点有两个:第一,业务操作和写消息必须在同一个本地事务里,这是“最终一致”的起点;第二,消息表和定时任务要设计好,防止消息积压和重复发送。后来出现的RocketMQ事务消息,其实就是把本地消息表搬到了MQ内部,思路完全一致。
如果不想自己开发,可以使用Seata框架。Seata的AT模式对业务侵入很小,它通过全局事务管理器记录数据快照,业务SQL正常写,Seata自动完成回滚。AT模式适合“单库不大、SQL相对简单”的业务,性能比TCC好,但快照管理和全局锁会有额外开销,需要压测确认。简单说:TCC适合高一致性高控制力,MQ最终一致适合高并发高吞吐,Seata适合想快速落地、业务逻辑不太复杂的团队。
3.4 选型的真实经验:别为了分布式事务而事务
我见过不少团队,业务量一天就几万单,非要把简单的下单流程搞成TCC三套逻辑,结果开发周期翻倍,线上问题还更多。我的建议是:先用本地事务加消息表把最终一致做起来,订单状态单独用一张状态机表维护,补偿逻辑写在定时任务里。等到订单量确实大到消息表成了瓶颈,或者业务确实需要实时强一致,再引入分布式事务中间件。分布式事务是最后的武器,不是常规的默认姿势。
4. 分布式缓存与ID:公告栏和工牌号
4.1 缓存三大问题:穿透、击穿、雪崩
分布式系统里缓存几乎是标配,它的作用是在数据库前面加一层“公共公告栏”,读请求先看公告栏,有就直接返回,没有再查库再回填。最常见的实现是Redis,和数据库构成Cache Aside模式:读的时候先读缓存,读不到读数据库,然后回写缓存;写的时候先是更新数据库,再删除缓存。
但这套模式有几个知名大坑。第一个是缓存穿透,请求查询一个数据库中根本不存在的数据,缓存永远没有,请求每次都会打到数据库。大量恶意请求用不存在的ID刷你接口,数据库很快被打垮。解决手段是布隆过滤器,把所有合法ID预先放进过滤器,查询前先判断ID是否存在,不存在直接拒绝。第二个是缓存击穿,某个热点key的缓存刚好过期,一瞬间大量请求同时回源查库,数据库被瞬时打爆。解决方案是加互斥锁,只允许一个请求去重建缓存,其他请求等待;更简单粗暴的方法是逻辑过期,也就是不给key设置物理过期时间,而是在value里存一个过期时间戳,后台异步刷新。第三个是缓存雪崩,大量key在同一时间过期,或者Redis实例宕机,所有请求一起打到数据库,数据库直接崩掉。解决思路是过期时间加随机抖动,不要把过期时间设成同一个值;同时做多级缓存,本地缓存一份,Redis一份,数据库最后兜底。真遇到Redis宕机,还要有降级预案,比如直接走数据库限流。
4.2 分布式ID:为什么不用UUID
团建要给人发工牌,每个人都要有一个不重复的编号。在分布式系统里,这个编号就是分布式ID。最常见的做法是数据库自增ID,但数据库一旦分库分表,每个表自己自增,就会重复。有人会用UUID,全局唯一没问题,但UUID无序、太长,32位字符串存进数据库当主键,索引体积变大,写入时还因为随机性导致页分裂,性能比自己增ID差很多。
所以业界普遍用雪花算法(Snowflake)。一个64位的long型ID,组合为:1位符号位(不用) + 41位时间戳(毫秒级,可以支撑约69年) + 10位机器ID(支持最多1024台机器) + 12位序列号(同一毫秒内支持4096个不重复ID)。这个结构的好处是趋势递增,查询索引友好,而且不需要中心化节点,每个机器自己生成就行。
public class SnowflakeIdWorker { private final long twepoch = 1288834974657L; private final long workerIdBits = 5L; private final long datacenterIdBits = 5L; private final long sequenceBits = 12L; private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = System.currentTimeMillis(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨,拒绝生成ID"); } if (timestamp == lastTimestamp) { sequence = (sequence + 1) & 4095; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0; } lastTimestamp = timestamp; return ((timestamp - twepoch) << 22) | (datacenterId << 17) | (workerId << 12) | sequence; } }这段代码只是个骨架,真正落地时要注意几个问题。时钟回拨是雪花算法的经典隐患,服务器NTP时间同步往前跳,同一个毫秒内生成的ID可能会重复。解决思路有几种:回拨时间小于阈值时等待,大于阈值时拒绝服务,或者用ZooKeeper等外部组件记录上一次生成ID的时间戳,用内存默认值兜底。机器ID的分配也要管理,最好有个配置平台下发,避免两台机器用同一个机器ID导致ID冲突。
4.3 分布式IO与跨节点通信的底层形态
分布式节点之间要交换数据,绕不开网络IO。常见的有两种编程模型:传统BIO每一个连接要一个线程,连接多了线程数爆炸;而NIO通过事件驱动,一个线程可以管理成千上万个连接。像Netty框架就是基于NIO的典型实现,很多RPC框架的底层通信都建立在它之上。
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); } }); bootstrap.bind(8080).sync();这段代码就是一个最简单的Netty服务端骨架,两个线程组分别负责接受连接和处理IO事件。理解了NIO,你就会明白为什么分布式系统里一个节点撑几万连接不稀奇。有些特殊领域也会用到“分布式驱动”的思想,比如Trucksim这类车辆动力学仿真软件,做多机联合仿真时把整车模型拆成多个子模块,每个子模块跑在一台机器上,通过网络同步状态数据,本质上也是分布式协同。你把这套底层模型搞清楚,再去看Hadoop伪分布式、分布式爬虫、分布式算力,都会觉得顺理成章。
5. 分布式定时任务与爬虫:值班表和分头行动
5.1 分布式定时任务:防止“全员重复值班”
定时任务在老系统里就是一台服务器的cron表,到点执行。分布式环境下问题来了:多个节点上如果都部署了定时任务,同一个任务会被重复执行,比如每天凌晨给用户发短信,结果三个节点各发一遍,用户得被短信轰炸。要解决这个问题,要么用分布式锁把任务锁起来,要么用专门的分布式任务调度框架。
最简单的方式是ShedLock,Spring生态里常用的轻量方案。它的原理其实就是在数据库或Redis里记录一把锁,任务执行前先尝试加锁,加锁成功才执行。配置一个锁的有效期,确保任务执行期间锁不会被释放,还要注意任务崩溃后锁要能自动过期,别把任务卡死。Spring Cloud架构下任务简单数量少的场景,ShedLock或者Quartz集群已经够用。
真正复杂的定时任务场景,比如需要分片处理几十万条数据、需要失败重试、需要动态调整任务,建议用XXL-JOB或ElasticJob。XXL-JOB是中心化架构:调度中心负责任务管理、触发、日志,执行器部署在业务节点上负责干活。一个任务可以在多个执行器上做分片,比如10台机器轮流处理用户列表,每台处理十分之一,效率一下就上去了。ElasticJob则更偏分布式架构,没有集中调度中心,用的ZooKeeper做注册和协调,适合高可用要求更高的场景。我的建议很简单:任务复杂需要管理界面和分片,直接上XXL-JOB,运维和排障体验好得多。
5.2 分布式爬虫:任务分派与结果汇总
分布式爬虫和分布式定时任务其实是一对兄弟,都是“把一个大任务拆成很多小任务,分给多个节点干”。设计思路通常是:Master节点维护一个待抓取URL队列,一般用Redis的List或Set存储;多个Worker节点启动时从Master同步队列任务,抓到页面后解析出新的URL,回填到队列;抓取失败的URL要进入重试队列;最后结果统一写入数据库或消息队列。
这里有个很容易被忽视的细节:URL去重。一个网站内页互相链接,同一个URL可能被多个Worker抓到,不去重的话爬虫效率极低,还可能把目标站搞垮。去重用Redis的Set集合,数据量小的时候问题不大;数据量超过千万级别,就要用布隆过滤器,牺牲少量误判率换取极低的内存占用。为了防止请求频率过高被目标站点封IP,Worker节点还要维护代理池,定期换IP。你认真想想,这套架构和后台系统的任务调度没有任何本质区别,理解了“任务分派 + 结果回收 + 去重幂等”这个套路,分布式爬虫基本就是手到擒来。
6. 常见问题与排查技巧实录
6.1 三个经典故障:脑裂、超时重试、幂等
分布式系统里我遇到最多的三类故障,第一类就是脑裂。所谓脑裂,就是网络分区后,一部分节点联系不上主节点,它们自己选出一个新的主节点,导致系统里同时出现两个“主”。数据库主从切换、消息队列的leader选举、ZooKeeper集群都可能遇到。处理脑裂的核心是“多数派原则”,也就是只有获得超过一半节点投票的候选者才能当主,也就是Quorum机制。设计系统时,对每个选举和写操作都要问一句:如果分区发生,系统会不会出现两个大脑?
第二类是超时重试引发的数据重复。分布式调用不可避免会超时,超时后通常会重试。但如果第一次请求其实已经成功,只是响应超时,重试就会让服务方执行两次。比如支付回调,很可能因为网络延迟导致支付网关回调重复发送。解决方法是消费方做幂等:用订单号和事件类型生成一个唯一约束,处理前先去Redis查一下这个事件是否处理过,或者数据库加唯一索引。幂等是分布式开发里最重要的基础功。
第三类是级联故障。上游服务没做降级和限流,依赖的下游服务出现慢查询,导致上游线程全部阻塞,最后整个链路雪崩。排查时先看慢调用拓扑,找到拖后腿的节点,再针对它做熔断降级。
6.2 排查手段:链路追踪、日志与监控
分布式问题最麻烦的地方是“不知道请求到底走了哪些节点”。几十个微服务,一个请求来回跳好几跳,出了问题看每个服务自己的日志,根本连不起来。所以一定要在最开始就把链路追踪做好,最简单的办法是每个请求入口生成一个traceId,塞进日志里,各服务打印日志时都带上它,排查时按traceId一把梭。更专业的方案是用SkyWalking或Zipkin,自动采集调用链数据,在界面上直接看到每个请求调用了哪些服务、每跳耗时是多少、哪一段变慢了。
日志输出也要注意几条经验:一是全链路必须保持同一套时间标准,服务器都同步好时钟,不然排查时时间对不上;二是日志要带业务标识,单号、用户ID、订单ID都要出现在日志里;三是日志里别打印敏感信息,比如密码、手机号明文,等出安全事故就麻烦了。监控则要覆盖基础指标:CPU、内存、磁盘、GC、线程数、数据库连接池占用、Redis命中率、MQ积压量。这些指标不一定要全,但关键节点一定要有,不然故障一发生,你连“现在到底谁出了问题”都不知道。
6.3 高频问题速查表
| 问题表现 | 可能原因 | 排查方向 | 常用解法 |
|---|---|---|---|
| 分布式锁偶尔失效 | Redis主从切换、锁未续期 | 查Redis集群状态、客户端日志 | 用Redisson看门狗、必要时换etcd |
| 定时任务重复执行 | 多节点未做互斥 | 查看任务调度日志 | ShedLock、XXL-JOB分片幂等 |
| 消息重复消费 | 消费端未做幂等 | 查看MQ消费日志、数据库是否重复 | 唯一索引、Redis幂等标记 |
| 接口突然变慢 | 缓存穿透、击穿、雪崩 | 看缓存命中率、数据库慢SQL | 布隆过滤器、互斥锁重建、过期时间抖动 |
| 支付回调重复处理 | 网络重试、回调重复推送 | 按订单号查操作流水 | 幂等表加唯一索引 |
| 服务间调用超时 | 线程池被打满、慢SQL、网络抖动 | 链路追踪看瓶颈节点、线程dump | 熔断降级、连接池调优 |
| 集群脑裂 | 网络分区、选举机制不合理 | 检查节点心跳、投票记录 | 多数派机制、踢出孤岛节点 |
| ID冲突 | 雪花算法时钟回拨、机器ID重复 | 查看生成ID的机器标识和时间戳 | 时钟回拨处理、机器ID统一管理 |
这张表是我实际工作中反复遇到的场景汇总,不全面,但足够覆盖多数团队前期的分布式踩坑需求。
6.4 从伪分布式开始学习一套组合拳
如果你是一名刚开始接触分布式的新手,别一上来就折腾K8s、微服务全家桶。我建议从Hadoop伪分布式搭建入门。所谓伪分布式,就是在一台机器上把HDFS的NameNode、DataNode以及YARN的ResourceManager、NodeManager这些进程分别起出来,每个进程扮演一个“虚拟节点”。虽然都在同一台机器上,但它们的通信方式、数据读写流程、主备切换机制和真实集群完全相同。你可以在上面练习HDFS文件读写、提交MapReduce任务、观察数据块分布,这套东西跑通一遍,对“分布式到底在协调什么”的体感会比只看文档强得多。
搭建过程不复杂:准备一台Linux机器,装好JDK,下载Hadoop安装包,配置SSH免密登录,修改core-site.xml、hdfs-site.xml、yarn-site.xml三个文件,然后执行start-dfs.sh、start-yarn.sh,访问对应端口就能看到集群状态。伪分布式的意义不在于能抗多大并发,而在于让你低成本地理解NameNode如何管理元数据、DataNode如何汇报心跳、任务如何被调度。等你想通了这些,再回来看微服务那套注册中心、负载均衡、配置中心,会发现都是同一个思想在具体场景里的不同变形。
分布式开发这几年一直是面试重点,也是工程难点,但说白了就是一群人(节点)在不可靠的环境里齐步走。你按“团建”的思路去理解,先理清角色分工(架构设计),再管好发言秩序(分布式锁),然后统一行动标准(分布式事务),最后设计好公告栏和工牌(缓存和ID),多数场景都能找到清晰的答案。我在实际项目中最大的体会是:不要一上来就追求所有技术都上全套,先把一个简单的分布式锁用明白,把一条消息链路的幂等做扎实,比堆砌一堆高大上的组件有用得多。架构是生长的,不是装修出来的。真遇到拿不准的方案,回到那句老话——如果这是一支团队,你会怎么安排?答案往往就在那里。