加载中...
加载中...
Deep dives into modern software development, distributed systems, and frontend architecture.

从 2015 年某社交平台 "某某明星离婚" 热点事件导致缓存击穿、数据库连接池耗尽的故障出发,引入缓存三大问题的本质差异——穿透是 "查询不存在的数据",击穿是 "热点 Key 过期",雪崩是 "批量 Key 失效或节点宕机";深入布隆过滤器的概率设计——k 个哈希函数、m 位位数组、n 个元素,误判率 p 的理论下界,以及 "无法删除" 和 "误判率不可调零" 的暗面;剖析 SET NX EX 的原子语义——为什么必须用 Lua 脚本或 Redis 2.6+ 的原子命令保证 "判断 + 设值 + 过期" 三动作不可分割,揭示 "先 SET 再 EXPIRE" 方案在进程崩溃时的死锁风险;推导 Redlock 的多数派算法——在 N 个独立 Redis 节点上依次获取锁,总耗时小于 TTL 且成功节点 >= N/2+1 时才认为加锁成功,分析时钟漂移对安全性的破坏(Martin Kleppmann 的质疑);最后揭示 Redisson 看门狗的设计——加锁成功后启动后台线程,每隔 lockWatchdogTimeout/3 时间检查并续期,防止业务执行时间超过锁 TTL 导致的误释放,以及续期失败时的优雅降级策略。

从一个真实的生产故障出发——某电商大促期间 Redis 实例崩溃,恢复时发现 RDB 快照缺失了最近 5 分钟数据,而 AOF 文件体积膨胀到 50GB 导致重启耗时 30 分钟,由此引入 Redis 持久化的设计权衡;深入 bgsave 的 fork 机制——Linux fork 创建子进程时共享父进程的页表,仅在写操作发生时触发 COW 页复制,揭示 "bgsave 期间内存可能翻倍" 的暗面(父进程持续修改导致大量页复制);剖析 AOF 重写的精妙设计——子进程遍历内存写新 AOF 文件的同时,父进程将新命令同时写入旧 AOF 和重写缓冲区,子进程结束后父进程将缓冲区的增量追加到新文件,保证重写期间不丢失数据;介绍 Redis 4.0 的混合持久化——文件前半段是 RDB 格式的全量数据(加载快),后半段是 AOF 格式的增量数据(丢失少),兼顾恢复速度和数据安全;推导主从同步的 PSYNC 协议——从节点上报 runid 和复制偏移量,主节点判断是否在 repl_backlog 环形缓冲区内,决定走全量同步(生成 RDB + 发送)还是部分同步(直接发送积压命令);最后揭示哨兵的分布式仲裁困境——Sentinel 集群通过 Raft 算法选举 Leader 执行故障转移,但网络分区下可能出现 "双主" 脑裂,引出 "min-slaves-to-write" 等防御配置。

从 Redis 作者 antirez 面临的真实约束出发——用 C 语言实现一个高性能内存数据库,必须解决 C 字符串的 O(n) 长度计算和缓冲区溢出问题,由此推导出 SDS 的空间预分配(<1MB 翻倍,>=1MB 加 1MB)与惰性释放策略;深入 ziplist 的紧凑内存布局,揭示 "prevlen 用变长编码记录前节点长度" 这一设计如何在节点变大时触发级联更新(cascading update)——一次插入可能引发整个压缩列表的连锁重写,从而理解 Redis 7.0 用 listpack 替代 ziplist 的演进动机(listpack 用 total length 替代 prevlen,彻底消除级联更新);剖析 quicklist 的分段哲学——在 "内存紧凑" 和 "插入效率" 之间取平衡,每个节点是一个 ziplist/listpack,超过阈值后分裂;推导 skiplist 的概率层数设计——p=0.25 时平均层数为 1/(1-p)=1.33,查询复杂度 O(log n) 的概率保证,以及为什么 Redis 选择 skiplist 而非红黑树实现 zset(范围查询友好、实现简单);最后揭示渐进式 rehash 的双字典机制——dict 维护 ht[0] 和 ht[1],在定时任务和增删查操作中逐步迁移桶位,既避免了 HashMap 一次性 rehash 的卡顿,又保证了服务可用性。

从 FutureTask 的 get() 阻塞与无法链式组合的局限性出发,引入 CompletableFuture 的 Completion 链表设计——每个异步操作创建一个 Completion 节点挂到依赖的 CompletableFuture 上,形成回调链而非阻塞等待;推导 thenApply/thenAccept/thenRun 的串行语义与 thenCombine/applyToEither 的并行组合语义,揭示 exceptionally/handle 的异常处理差异;深入 ThreadLocal 的底层结构——每个 Thread 对象持有 ThreadLocalMap(ThreadLocal.ThreadLocalMap),ThreadLocal 作为弱引用 key,value 是强引用,GC 后 key 变为 null 但 value 仍被 Thread 引用导致内存泄漏;解释 InheritableThreadLocal 通过 inheritableThreadLocals 字段实现父子线程值传递,但在线程池复用线程时失效的问题;最后介绍 TransmittableThreadLocal 的装饰器方案——在提交任务时捕获当前线程的上下文,在任务执行时还原到 Worker 线程,实现线程池场景下的上下文透传。

从 ThreadPoolExecutor 的七参数模型出发,用状态机推导任务提交的完整路径——核心线程未满时创建新 Worker 线程;核心线程满后入 workQueue;队列满后创建非核心线程直到 maximumPoolSize;全部满后触发拒绝策略;深入四种拒绝策略的适用场景,AbortPolicy 直接抛异常、CallerRunsPolicy 让调用者线程执行实现反压;剖析线程回收的 keepAliveTime 逻辑与 allowCoreThreadTimeOut 的陷阱;对比 ForkJoinPool 的工作窃取算法——每个 Worker 线程维护自己的双端任务队列,空闲时从其他线程队列尾部窃取任务,减少全局竞争;最后揭示 Executors 工厂方法的危险性——FixedThreadPool 的无界 LinkedBlockingQueue 和 CachedThreadPool 的无限 SynchronousQueue 都会导致 OOM,生产环境必须用 ThreadPoolExecutor 手动构造。

从 AQS 的 state 字段和 FIFO 等待队列出发,剖析独占获取与共享获取的模板方法模式——acquire/release 定义算法骨架,子类实现 tryAcquire/tryRelease 的语义细节;推导 ReentrantLock 的公平与非公平策略差异:非公平锁允许插队(tryLock 直接 CAS),公平锁通过 hasQueuedPredecessors 检查队列前驱;深入 synchronized 的锁升级路径——无锁状态下 Mark Word 存储哈希码,偏向锁记录线程 ID,轻量级锁用 CAS 自旋,重量级锁委托操作系统 Mutex;对比 ReentrantReadWriteLock 的读读共享与 StampedLock 的乐观读模式,揭示乐观读"不加锁读数据,验证版本号后使用"的性能优势与使用限制。

从 JMM 的主内存与工作内存抽象出发,推导 happens-before 规则的物理含义——不是时序上的先后,而是编译器和 CPU 重排序的边界约束;深入 volatile 的读写语义,揭示读 volatile 插入的 LoadLoad + LoadStore 屏障、写 volatile 插入的 StoreStore + StoreLoad 屏障,以及为什么 DCL 模式中 instance 必须用 volatile 防止指令重排序导致的半初始化对象泄漏;剖析 CAS 的底层实现(LOCK CMPXCHG 指令)与 ABA 问题的成因;最后对比 AtomicLong 的 CAS 热点与 LongAdder 的分片计数设计,理解"无锁优于轻锁、轻锁优于重锁"的并发优化层级。

从 G1 的可预测停顿模型出发,解析 Region 化设计与 Remembered Set 的维护机制,深入 ZGC 染色指针与读屏障如何支撑并发整理,对比 Shenandoah Brooks Pointer 的技术路线,梳理分代 ZGC 的演进逻辑与低延迟 GC 选型决策树。

从并发标记的 STW 困境出发,阐述三色标记算法的状态流转与不变性,推导漏标发生的充要条件,通过时序图对比 CMS 增量更新与 G1 原始快照 SATB 的破局路径,剖析写屏障的底层实现与代价,建立并发模式失败的排查框架。

从类加载的五个阶段出发,剖析双亲委派模型的安全隔离本质与加载流程,深入 Tomcat 多应用隔离、SPI 逆向委托、OSGi 模块化三大打破双亲委派的经典场景,厘清线程上下文类加载器的设计意图,并解读 Spring Boot Loader 对嵌套 JAR 结构的加载扩展。