Kubernetes 核心概念
从 Docker 到 Kubernetes 的演进 Docker 的出现彻底改变了应用程序的打包和部署方式。通过容器化技术,开发人员能够将应用程序及其依赖项打包到一个轻量级的、可移植的容器中,确保了"一次构建,到处运行"的一致性体验。然而,随着容器化应用的规模扩大,单机容器管理的局限性逐渐显现。 在单机环境中,Docker 提供了容器生命周期管理、资源隔离、网络配置等基础功能。但当应用规模扩展到多台服务器时,运维团队面临诸多挑战:跨主机的容器调度、服务发现、负载均衡、故障自愈、滚动升级、资源配额等问题变得日益复杂。手工管理几十甚至上百个容器不仅效率低下,而且容易出错。 容器编排平台应运而生。Kubernetes(K8s)作为容器编排领域的领导者,提供了一个完整的平台,用于自动化部署、扩展和管理容器化应用。它解决了多机环境下的核心问题:自动化调度、服务发现、负载均衡、存储编排、自动扩缩容、滚动更新和回滚、自我修复等。Kubernetes 不仅是一个编排工具,更是一个云原生应用的基础设施平台。 Kubernetes 架构 Kubernetes 采用主从架构,由控制...
跨语言超时机制全解析
从"等不起"到"不想等":跨语言超时机制全解析 “A distributed system is one in which the failure of a computer you didn’t even know existed can render your own computer unusable.” —— Leslie Lamport 在分布式系统中,超时是应用与混沌之间最后一道防线。没有超时的 RPC 调用,如同没有刹车的汽车——迟早会撞上墙。 许多 Java 开发者对超时的认知停留在 Future.get(timeout, unit) 这一层,其底层依赖 LockSupport.parkNanos 和自旋等待。然而,翻阅 HSF/Dubbo 的源码会发现,这些 RPC 框架选择的是 HashedWheelTimer(时间轮)。 这就引出了一个值得深究的问题:为什么不直接用 Future.get 的超时版本?时间轮到底解决了什么问题? 事实上,仅 Java 一门语言就存在三种截然不同的超时实现范式。再放眼 Go、JavaS...
Java 线程池笔记
从执行器到线程池(from executor interface to thread pool implementation) Pooling is the grouping together of resources (assets, equipment, personnel, effort, etc.) for the purposes of maximizing advantage or minimizing risk to the users. The term is used in finance, computing and equipment management. ——wikipedia “池化”思想不仅仅能应用在计算机领域,在金融、设备、人员管理、工作管理等领域也有相关的应用。 在计算机领域中的表现为:统一管理IT资源,包括服务器、存储、和网络资源等等。通过共享资源,使用户在低投入中获益。除去线程池,还有其他比较典型的几种使用策略包括: 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。 连接池(Connection Poo...
线程安全与锁优化
版本说明:本文主要基于 JDK 6 ~ JDK 14 的 HotSpot 虚拟机实现。需要注意的是,从 JDK 15 开始,偏向锁已被默认关闭并标记为废弃(JEP 374)。如果你使用的是 JDK 15+,文中关于偏向锁的内容仅作为历史参考。 线程安全 什么是线程安全 “当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。” 相对的线程安全,可以分成五个等级。但在深入讨论线程安全的分类之前,我们需要先理解 Java 内存模型——它是理解线程安全问题的理论基础。 Java 内存模型基础 Java 内存模型(Java Memory Model,JMM)是 Java 语言规范的一部分,定义了多线程程序中共享变量的访问规则。理解 JMM 是理解线程安全问题的基础。 为什么需要内存模型? 现代计算机系统中,CPU 与主内存之间存在巨大的速度差异。为了弥补这一差距,硬件层面引入了多级缓存(L1、L2、L3 Cache)。这带来了一个问...
无锁队列
Java 一读一写(SPSC):Memory Barrier + Volatile 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657/** * Single-Producer Single-Consumer (SPSC) 无锁环形队列。 * * <p>原理说明: * - 仅允许一个线程调用 {@code offer()},一个线程调用 {@code poll()}。 * - 由于没有写竞争,无需 CAS;只需保证写操作对消费者可见。 * - 使用两个 volatile 索引(head/tail)建立 happens-before 关系: * 生产者写入元素 → volatile 写 tail → 消费者 volatile 读 tail → 读取元素。 * - 这本质上利用了 Java 内存模型中的“volatile 写-读”内存屏障(StoreLoad), * ...
Java 并发编程笔记
本文思维导图总览: 下载思维导图源文件 写在前面的话 并发编程最早的实践都在操作系统里。高层语言的并发模型都要基于底层系统对硬件抽象和并发的设计来设计和实现,不能超出操作系统允许的范围。所谓的高级抽象总体上是简化对 OS 底层机制的复杂调用。 并发与异步 本文聚焦并发(Concurrency),即多任务在同一时间段内的交替或并行执行,核心问题是资源共享、线程同步与协作。 **异步(Asynchronous)**是另一维度:调用方发起操作后不等结果返回即继续执行,通过回调、Future或事件机制获取结果。异步可通过单线程事件循环实现,也可依托多线程并发实现。 二者关系:并发关注"多任务如何执行与协调",异步关注"调用是否阻塞等待"。并发编程常涉及异步,但本文不展开异步编程模式(如响应式流、协程),相关内容请参阅《Java 线程池笔记》。 管程 理论和实践之间是有鸿沟的,要弥合这种鸿沟,通常需要我们去学习别人的实践。比如并发的标准设计思想来自于操作系统里的管程(monitor),我们应当学习管程,进而了解标准的并发模型-管理共享变量和线程(并...
常见故障整理
手写 sql if 条件的字段为空则不应该拼接条件,是一个很容易被忽略的编程错误。如果线上发生了这个问题,则可能导致数据同步出错。 极度危险的错误 元素内容必须由格式正确的字符数据或标记组成,这通常是因为>``<``>=``<=类的标签没有经过转义。 防止手写 sql 被注入 所有 condition 用()圈起来。单独在 condition 里面拼装。用 and 来连接这些 condition。 在最外围使用随机化的()来包裹整个 where 的条件,防止有人猜到()的层次。 mybatis 的替换难点 #{} 是预编译处理,${} 是直接替换。直接替换会有 sql 注入的风险。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991...
分布式事务
问题定义 对经典的电商场景而言:下单是个插入操作,扣减金额和库存是个更新操作,操作的模型不同,如果进行分布式的服务拆分,则可能无法在一个本地事务里操作几个模型,涉及跨库事务。 CAP 定义 根据 Eric Brewer 提出的 CAP 理论: Consistency:All Nodes see the same data at the same time。所有节点看到同一份最新数据。Brewer 论文里的 C 严格定义是 linearizability,工程语境下常被泛化为 strong consistency。 Availability:Reads and writes always succeed。非故障节点必须在合理时间内响应。 Partition tolerance:System continues to operate despite arbitrary message loss or failure of part of the system。网络分区时系统继续运行。在云原生环境(K8s 跨可用区、跨 region)这是默认前提,不是可选项。 由此诞生三种设计约束...
系统设计
System Design 总结 面试前你需要了解的16个系统设计知识 《搞定系统设计:面试敲开大厂的门》 从 0 到 100 万用户的扩展 数据库的选择 先把服务器里计算和存储的服务器分离开来。 在如下情况下,可以考虑 NoSQL: 低延迟。 非结构化非关系型。 只需要序列化格式-或者对序列化格式友好。 需要存储海量数据。 《你到底用 NoSQL 来做什么?》 scale up vs scale out 纵向扩展的缺点: 有硬性限制。 没有冗余。 横向扩展对大型应用更合适。 负载均衡器 failover 基于负载均衡器就够了,有了 LB 我们才能装多台服务器。 数据库复制 从换主很容易丢失数据,先要恢复脚本才能实现对数据的合法写和读。 缓存 读写模式 我们常用的缓存策略叫 Cache-Aside Cache,完整介绍在《Caching Strategies and How to Choose the Right One》。 如果是应用自己加在缓存,就是 cache aside;如果缓存自己带有 load 方法-比如 guava 的 loader 实现,则意味着 ...
《编程之美》
序言 下水道井盖为什么是圆的 “下水道井盖是圆的,因为圆形不会掉进井口,而且圆形具有均匀分布压力的优势。” 一个屋子有一个门(门是关闭的)和3盏电灯。屋外有3个开关,分别与这3盏灯相连。你可以随意操纵这些开关,可一旦你将门打开,就不能变换开关了。确定每个开关具体管哪盏灯? 答:将一盏灯开一段时间,再关掉,在剩余2盏灯里随机开一盏,进屋去看,发热的灯对应第一个碰的开关,亮着的灯对应开关开着的开关,灭的灯对应没碰过的开关。 游戏之乐 如何写一个程序让 cpu 占用率保持在 50%? 不要用 if-else 来解决,要把比例转成不同的 worktime。 解法的精确与否其实取决于“多久时间内测度一次已占用的时间”和“睡眠”两类 api 的精度。 基本思路: 先计算一下某个周期内的目标时间: 假设我们使用 1s 为一个完整周期,则这个周期的 50% 为0.5s。 我们就用当前时间 + 0.5s 得到一个真正目标时间,比如当前时间在 15:06秒,目标时间就是15:06.5。如果有毫秒偏差,以此类推。 找一个无限 while 循环,内部只做两件事: 进行一个稍微复杂的数学计算,如开平...










