<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go on 小盒子的技术分享</title><link>https://xiaobox.github.io/tags/go/</link><description>Recent content in Go on 小盒子的技术分享</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 29 Mar 2025 05:33:55 +0000</lastBuildDate><atom:link href="https://xiaobox.github.io/tags/go/index.xml" rel="self" type="application/rss+xml"/><item><title>Java老兵的十字路口：坚守还是突围？</title><link>https://xiaobox.github.io/p/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/</link><pubDate>Sat, 29 Mar 2025 05:33:55 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/cover.jpg" alt="Featured image of post Java老兵的十字路口：坚守还是突围？" /&gt;&lt;h2 id="java在技术江湖中的现状稳固基石还是夕阳西下"&gt;&lt;a href="#java%e5%9c%a8%e6%8a%80%e6%9c%af%e6%b1%9f%e6%b9%96%e4%b8%ad%e7%9a%84%e7%8e%b0%e7%8a%b6%e7%a8%b3%e5%9b%ba%e5%9f%ba%e7%9f%b3%e8%bf%98%e6%98%af%e5%a4%95%e9%98%b3%e8%a5%bf%e4%b8%8b" class="header-anchor"&gt;&lt;/a&gt;Java在技术江湖中的现状：稳固基石还是夕阳西下？
&lt;/h2&gt;&lt;p&gt;Java 作为企业级开发的常青树，至今在大量核心系统中扮演着中流砥柱的角色。然而，资深 Java 开发者也明显感受到技术环境的变化。一方面，在银行、政府、互联网巨头等复杂业务场景中，Java 的地位依然稳固；大量遗留系统和核心业务仍运行在 Java 上，短期内很难被完全替换。以优酷的版权管理系统为例，这套长达10年的老系统采用了过时的技术框架，积累了 81万行 Java 代码和大量“没人敢动”的if-else逻辑，可谓技术债累累。像这样的遗留系统，全面重构风险极高，只能在业务推动下逐步演进。因此，在许多传统领域，Java 作为“老码农”的看家本领，仍是不可或缺的基石。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/001-40dd5948.png"&gt;&lt;/p&gt;
&lt;p&gt;另一方面，新兴业务和初创项目对技术栈的选择更加多元。近年Go语言等后起之秀在性能、并发和开发效率上表现出色，成为云原生时代的“宠儿”。不少互联网大厂开始在核心服务中引入 Go：例如谷歌、滴滴、Uber、腾讯等都用 Go 开发高并发、高性能的服务。业界一度流传着“Java 老旧笨重、Go 崭新酷炫”的声音。面对这种冲击，不少资深 Java 工程师难免焦虑：Java 会不会像当年的 COBOL 一样淡出主流舞台？&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/002-28c85ce1.png"&gt;&lt;/p&gt;
&lt;p&gt;事实证明，这种担忧有些过度。Java 拥有数百万开发者和完整生态，并非轻易就能被取代的工具。正如有分析指出，Go 的崛起为行业提供了新选择，但并不是对 Java 的简单替代；两种语言各有优势，未来将长期共存。换言之，Java 依旧是技术江湖里的定海神针，只是江湖规矩变了——老兵们需要适应新玩法。&lt;/p&gt;
&lt;h2 id="java-面临的核心挑战笨重背后的突围"&gt;&lt;a href="#java-%e9%9d%a2%e4%b8%b4%e7%9a%84%e6%a0%b8%e5%bf%83%e6%8c%91%e6%88%98%e7%ac%a8%e9%87%8d%e8%83%8c%e5%90%8e%e7%9a%84%e7%aa%81%e5%9b%b4" class="header-anchor"&gt;&lt;/a&gt;Java 面临的核心挑战：笨重背后的突围
&lt;/h2&gt;&lt;p&gt;资深 Java 开发者在项目实践中遇到的挑战，往往并非语言本身跑不动，而是架构转型带来的“不适感”。近年来微服务、云原生风潮兴起，Java 传统的开发模式在新环境下面临诸多掣肘。&lt;/p&gt;
&lt;h3 id="微服务部署压力"&gt;&lt;a href="#%e5%be%ae%e6%9c%8d%e5%8a%a1%e9%83%a8%e7%bd%b2%e5%8e%8b%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;微服务部署压力
&lt;/h3&gt;&lt;p&gt;将单体应用拆成数十上百的微服务后，每个服务都需要独立部署运行。Java 应用启动慢、内存占用高的问题被放大。在同样功能下，一个简单的 Go 容器镜像可能只有十几MB，而等价的 Java（如采用 Helidon 框架）镜像初始体积高达 1.4GB！即使使用模块化手段缩减JDK体积（JLink可降至150MB左右），Java应用的容器仍显得臃肿。如此高的基础开销让追求极致弹性的微服务架构如临大敌：部署50个 Java 微服务可能需要远超预期的硬件资源，这正是很多团队转向更轻量语言的原因之一。有开发者调侃：“即便Java性能再好，内存占用是别人的 2～10倍，成本账算下来也让人犹豫。”而且在无服务器（Serverless）场景下，Java 冷启动延迟更是令人头疼——函数实例首次启动可能耗时数秒到十几秒。这一问题长期无法回避，直到 AWS 推出 SnapStart 等黑科技，把 Java 函数冷启动时间缩短到毫秒级，才算勉强止血。但需要注意，这类优化是云厂商额外提供的特殊支持，侧面说明 Java 在边缘计算、FaaS 等领域的先天劣势依然存在。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/003-c6bc61f8.png"&gt;&lt;/p&gt;
&lt;h3 id="启动速度与样板代码"&gt;&lt;a href="#%e5%90%af%e5%8a%a8%e9%80%9f%e5%ba%a6%e4%b8%8e%e6%a0%b7%e6%9d%bf%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;启动速度与样板代码
&lt;/h3&gt;&lt;p&gt;“万事开头难”在 Java 世界格外贴切。传统 Java Web 应用往往需要预热 JVM、加载大量类和配置，启动一个服务动辄数十秒甚至几分钟。这在强调弹性伸缩的云环境下难以接受。此外，Java 以模板代码多著称，同样的业务逻辑，用 Java 写可能需要冗长的类定义和 Getter/Setter，而用 Go/Kotlin 等语言则简洁得多。例如，Go 以极简的语法实现高并发，让开发者专注于业务；相比之下，过去的 Java 代码显得繁琐累赘，不少老兵自己也调侃天天在写“体力活”。虽然 Java 近年通过 Lambda、Streams、Records 等特性在不断精简，但是遗留项目中的大量样板代码依旧是维护负担。缺乏某些现代语言特性（如模式匹配、代数数据类型）也使Java在表达某些逻辑时不够优雅。这些痛点促使部分团队尝试用 Kotlin 等 JVM 语言替换 Java，以期获得更简洁的语法和更少的冗余。&lt;/p&gt;
&lt;h3 id="并发与性能困境"&gt;&lt;a href="#%e5%b9%b6%e5%8f%91%e4%b8%8e%e6%80%a7%e8%83%bd%e5%9b%b0%e5%a2%83" class="header-anchor"&gt;&lt;/a&gt;并发与性能困境
&lt;/h3&gt;&lt;p&gt;高并发一直是 Java 的强项，但实现方式却日益受到挑战。传统 Java 使用操作系统线程实现并发，每个线程都对应一定的内存和调度开销，在大规模场景下显得“沉重”。为绕过这个瓶颈，过去几年兴起了基于 Reactive 异步编程的微服务框架，通过单线程事件循环避免线程阻塞。然而异步风格代码复杂度高、调试困难，让许多工程师“叫苦不迭”。好消息是，JDK 19/20 引入了虚拟线程（Project Loom）并在 JDK 21 成为正式特性。虚拟线程是由 JVM 管理的超轻量线程，实现了 Go 协程式的并发模型。这意味着开发者可以用以往同步阻塞的简单代码，实现过去需要复杂回调/响应式才能处理的高并发任务。正如 Java 架构师 Brian Goetz 所言：Loom 的出现有望“一举终结Reactive编程的必要”——因为过去Reactive是为解决线程不足的权宜之计。虚拟线程让我们能够创建成千上万个并发任务而不必担心线程耗尽，大幅降低了编写和维护高并发代码的心智负担。然而，新事物也伴随不确定性：老项目若迁移到虚拟线程模型，需要评估线程本地变量、同步机制等是否还能正常工作；调优方式也与传统线程有所不同。目前虚拟线程虽强大，但毕竟是新特性，在大型生产环境中的考验还不充分。Java 老兵们既期待它带来性能飞跃，也需要保持一份观望和谨慎。&lt;/p&gt;
&lt;h3 id="graalvm-与原生执行"&gt;&lt;a href="#graalvm-%e4%b8%8e%e5%8e%9f%e7%94%9f%e6%89%a7%e8%a1%8c" class="header-anchor"&gt;&lt;/a&gt;GraalVM 与原生执行
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/004-3147eb4e.png"&gt;&lt;/p&gt;
&lt;p&gt;为了解决启动慢、内存高的问题，Java 社区近年另一“大招”是 GraalVM 原生镜像。通过提前编译(AOT)，可以将 Java 应用直接打包成本地可执行文件，启动时间和内存占用都有数量级的优化。这项技术被视为让 Java 重返边缘计算和 Serverless 舞台的希望：原生镜像下，一个 Spring Boot 微服务的“Hello World”容器镜像可小到 ~几十MB；运行时不需要JVM，冷启动延迟大幅降低。实际测试中，采用 GraalVM 原生镜像的 Java 服务在某些基准下性能甚至超越 Go：平均延迟仅0.25毫秒，每秒处理事务达到82426次，吞吐率是 Go 实现的两倍多！这种结果令人振奋，仿佛看到了 Java 打了一场漂亮的翻身仗。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/005-846f3af1.png"&gt;&lt;/p&gt;
&lt;p&gt;然而，理想很丰满，现实有时比较骨感。将复杂应用迁移到 GraalVM 原生镜像并非易事。例如，反射、动态代理等机制需要额外配置支持，许多成熟库在AOT编译下可能行为异常。构建原生镜像的过程也比较繁琐，往往需要调整代码、引入特定插件，并忍受较长的编译时间。调试诊断也更具挑战——原生应用无法使用 JVM 的丰富调试工具，需要新的手段排查问题。此外，引入 GraalVM 还意味着团队需要掌握一套新的知识体系。在的总结中作者就指出：“将 Spring Boot 应用打包为 Native 镜像并非没有挑战”，直接迁移复杂项目可能遇到种种坑，需要开发者充分评估和测试。因此，GraalVM 不是万能灵药，而是有门槛的新武器：用得好，Java 如虎添翼；用不好，反而可能引入新的不稳定因素。&lt;/p&gt;
&lt;p&gt;综上，资深 Java 开发者面对的不是“Java 不行了”，而是如何让 Java 行得更轻、更快、更优雅。JVM 社区显然没有躺在功劳簿上吃老本，而是在微服务、云原生时代积极求变。从 Spring Boot 3 对原生镜像的支持，到 JDK 连续的功能升级（如Records、Pattern Matching等），Java 正在努力破除“笨重”的刻板印象。老兵们需要做的，是与时俱进地拥抱这些变化，用新工具、新思路来武装自己的Java技能库。&lt;/p&gt;
&lt;h2 id="用舍之道哪些场景放弃-java哪些场景坚持-java"&gt;&lt;a href="#%e7%94%a8%e8%88%8d%e4%b9%8b%e9%81%93%e5%93%aa%e4%ba%9b%e5%9c%ba%e6%99%af%e6%94%be%e5%bc%83-java%e5%93%aa%e4%ba%9b%e5%9c%ba%e6%99%af%e5%9d%9a%e6%8c%81-java" class="header-anchor"&gt;&lt;/a&gt;用舍之道：哪些场景放弃 Java，哪些场景坚持 Java？
&lt;/h2&gt;&lt;p&gt;技术选型从来都不是非黑即白，对于 Java 的去留更是如此。究竟在哪些业务场景下应该考虑放弃 Java？哪些场景下 Java 仍是首选？结合真实案例和趋势，我们可以做出以下判断：&lt;/p&gt;
&lt;h3 id="场景一边缘计算与serverless--谨慎使用-java"&gt;&lt;a href="#%e5%9c%ba%e6%99%af%e4%b8%80%e8%be%b9%e7%bc%98%e8%ae%a1%e7%ae%97%e4%b8%8eserverless--%e8%b0%a8%e6%85%8e%e4%bd%bf%e7%94%a8-java" class="header-anchor"&gt;&lt;/a&gt;场景一：边缘计算与Serverless – 谨慎使用 Java。
&lt;/h3&gt;&lt;p&gt;对于运行环境受限、对启动延迟敏感的场景，选择 Java 需要非常慎重。典型如物联网设备、边缘网关、函数计算等，这些环境往往内存有限且要求冷启动极快。在这些场合，运行一个庞大的 JVM 显然不如直接使用原生语言（C/C++、Rust）或轻量脚本语言（JavaScript、Python）来得高效。过去不少团队在实现事件驱动的小型服务时，就倾向于用 Node.js 或 Python 编写——不是因为Java不能实现功能，而是因为Java在每次调用都要“热身”的开销让人难以忍受。虽然有 GraalVM Native Image 可以大幅优化，但对小型团队而言，引入它的复杂度可能得不偿失。因此，对于边缘和无服务函数等应用，除非有充足理由和相应优化手段，否则倾向于选择更轻便的技术栈。当然，规则也非绝对：如果业务逻辑需要调用大量现有的 Java 类库（例如进行某种算法运算，而相关库只有Java实现），那么即便在 Serverless 环境下通过原生镜像等方式使用 Java 也是可以考虑的。总的来说，在这些场景，“能不用Java就不用”是较为实际的指导原则。&lt;/p&gt;
&lt;h3 id="场景二高性能微服务--视情况取舍"&gt;&lt;a href="#%e5%9c%ba%e6%99%af%e4%ba%8c%e9%ab%98%e6%80%a7%e8%83%bd%e5%be%ae%e6%9c%8d%e5%8a%a1--%e8%a7%86%e6%83%85%e5%86%b5%e5%8f%96%e8%88%8d" class="header-anchor"&gt;&lt;/a&gt;场景二：高性能微服务 – 视情况取舍
&lt;/h3&gt;&lt;p&gt;在互联网分布式系统中，每种语言都有用武之地。如果团队主要目标是极致的性能和资源利用率，并且成员对 Go/Rust 等语言驾轻就熟，那么把部分微服务用新语言实现未尝不可。例如某些网关服务、实时通信服务，行业里确有用 Rust 或 Go 重写后延迟降低、内存减半的成功案例。特别是低延迟、高并发的基础设施组件（消息队列、代理服务器等），很多开源项目早就避开 Java 转投 Go/Rust 怀抱，这是技术基因所致（Java 更擅长业务逻辑，系统编程领域C系语言传统更强）。但是，对于业务逻辑复杂的微服务，Java 仍然具有难以替代的优势：强大的生态提供了各种中间件客户端、成熟的 ORM 和事务框架、安全完备的验证和监控工具等等。这些“全家桶”式的支持使Java在开发业务系统时如鱼得水，大大减少了造轮子的成本。如果纯粹为了追新把此类服务改用另一种语言，可能会发现需要重建许多Java自带的轮子，得不偿失。因此，我们的立场是：对性能极限有追求的核心组件，可以考虑非Java实现以挖掘潜力；但大多数微服务尤其是业务导向的微服务，Java 依然是稳妥且高效的选择。况且随着Quarkus、Micronaut等专为云环境优化的Java框架出现，以及JVM自身的持续优化，Java 微服务的“笨重”正在被削平。正如某次测试所示，在较大的机器上，Java 的吞吐甚至可与 Go 持平甚至略胜一筹——只要用对了方式，Java 完全能胜任高性能微服务。&lt;/p&gt;
&lt;h3 id="场景三大型核心系统--坚定拥抱-java"&gt;&lt;a href="#%e5%9c%ba%e6%99%af%e4%b8%89%e5%a4%a7%e5%9e%8b%e6%a0%b8%e5%bf%83%e7%b3%bb%e7%bb%9f--%e5%9d%9a%e5%ae%9a%e6%8b%a5%e6%8a%b1-java" class="header-anchor"&gt;&lt;/a&gt;场景三：大型核心系统 – 坚定拥抱 Java
&lt;/h3&gt;&lt;p&gt;对于那些 复杂度高、生命周期长、需要强一致性和可靠性的核心业务，Java 无疑仍是值得长期信赖的编程语言。例如银行的核心账务系统、航空公司的订票系统、阿里的电商交易中台等，这些系统往往经历多年演化，业务规则繁多且严谨，需要大量业内验证过的中间件支撑。Java 的严格类型体系和成熟框架在这里如鱼得水。特别是在金融、政府等对稳定性要求极高的领域，“Java + 大型商用中间件”的组合几乎是默认标配。从技术债的角度考虑，这类系统虽然也面临老旧架构的问题，但重构时通常还是在 Java 体系内升级（比如从 Struts 升级到 Spring Boot，或引入分布式事务框架等），而不会轻易迁移到一门全新的语言上。这不仅因为重写成本高，更因为 Java 多年沉淀的安全性和可靠性难以替代。可以说，在复杂业务长跑中，Java 是一匹稳健的“长途马”，跑得也许不算最快，但足够稳当，生态中现成的工具能够覆盖方方面面，让架构师和开发者更安心。基于这些原因，我们坚定认为：在复杂业务和核心系统场景下，坚持使用 Java 是明智之举。即便引入新的技术插件，也是作为补充而非颠覆，比如用 Python 做小部分AI预测，再把结果喂给Java主系统等等。Java 老兵在这些战场上大可发挥深厚经验，将系统设计得健壮且易于维护，为业务保驾护航。&lt;/p&gt;
&lt;p&gt;归纳来说，用舍有道，视需而定：Java 并非万能，同样也远未过时。关键在于根据项目需求选择最合适的工具。在前沿领域不妨多尝试新语言新架构，以保持竞争力；而在关系到企业命脉的长线工程上，Java 依然值得我们托付。&lt;/p&gt;
&lt;h2 id="java老兵的自我进化坚守阵地or华丽转型"&gt;&lt;a href="#java%e8%80%81%e5%85%b5%e7%9a%84%e8%87%aa%e6%88%91%e8%bf%9b%e5%8c%96%e5%9d%9a%e5%ae%88%e9%98%b5%e5%9c%b0or%e5%8d%8e%e4%b8%bd%e8%bd%ac%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;Java老兵的自我进化：坚守阵地or华丽转型？
&lt;/h2&gt;&lt;p&gt;面对风起云涌的技术浪潮，10年以上经验的 Java 老将们该何去何从？是固守舒适圈，还是勇敢拓展边界？以下几点建议或许对处在十字路口的你有所启发：&lt;/p&gt;
&lt;h3 id="拥抱java新特性跟上生态演进"&gt;&lt;a href="#%e6%8b%a5%e6%8a%b1java%e6%96%b0%e7%89%b9%e6%80%a7%e8%b7%9f%e4%b8%8a%e7%94%9f%e6%80%81%e6%bc%94%e8%bf%9b" class="header-anchor"&gt;&lt;/a&gt;拥抱Java新特性，跟上生态演进
&lt;/h3&gt;&lt;p&gt;不要认为“学了十几年Java就没有新东西可学”。相反，Java生态在飞速更新，每年两个版本迭代。老兵们应该主动学习 JDK近几版的新功能（如Records、Sealed Class、Pattern Matching、虚拟线程等），这些特性能显著改善代码质量和性能，使你的技能焕发新生。例如，试着用 Loom 虚拟线程改造一个老的并发模块，体会一下开发模式的简化；或者研究 GraalVM 如何将现有服务无缝打包为原生镜像，了解其中的限制和调优手段。拥抱新技术不仅能提升生产力，也向团队展示了你与时俱进的技术热情。作为Java老兵，切忌固步自封——持续学习是对抗职业倦怠和时代冲击的最好武器。&lt;/p&gt;
&lt;h3 id="拓展多元技能栈成为t型人才"&gt;&lt;a href="#%e6%8b%93%e5%b1%95%e5%a4%9a%e5%85%83%e6%8a%80%e8%83%bd%e6%a0%88%e6%88%90%e4%b8%bat%e5%9e%8b%e4%ba%ba%e6%89%8d" class="header-anchor"&gt;&lt;/a&gt;拓展多元技能栈，成为“T型”人才
&lt;/h3&gt;&lt;p&gt;在保持Java优势的同时，建议横向拓展一到两门其它语言或领域技能。比如，可以尝试学习 Go 或 Python，用它们做些小项目，体会不同语言在思维模型上的差异。再比如，深入了解一下前端技术或移动开发，哪怕不做前端，也能与前端同事更高效协作。这种“T”字型的技能结构（既有一门精深的主力技术，又对相关技术有所涉猎）将使你在团队中更具价值。很多架构师在成为架构师前，都曾是精通数门语言、熟悉多种数据库和中间件的全能型工程师。对Java老兵来说，学习一门新语言还能帮助跳出现有思维框架，把新理念反哺到Java日常开发中。例如，借鉴函数式编程思想优化Java代码，或者用脚本语言编写自动化工具提升开发效率。多元化的技能还为你提供了职业备胎：万一某天真的不想写Java了，你在其他领域的积累也足以支撑转型，不至于手足无措。&lt;/p&gt;
&lt;h3 id="深入业务和架构提升不可替代性"&gt;&lt;a href="#%e6%b7%b1%e5%85%a5%e4%b8%9a%e5%8a%a1%e5%92%8c%e6%9e%b6%e6%9e%84%e6%8f%90%e5%8d%87%e4%b8%8d%e5%8f%af%e6%9b%bf%e4%bb%a3%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;深入业务和架构，提升不可替代性
&lt;/h3&gt;&lt;p&gt;随着工作年限增长，“懂业务、能设计”往往比单纯的编码能力更重要。Java老兵应该充分利用在一个行业浸润多年的经验，去深入理解业务领域 的本质问题，把握业务发展方向。将业务洞察与技术方案相结合，主动参与系统架构设计和重大技术选型，这会让你成为团队中不可或缺的核心人物。很多时候，业务专家+技术专家的复合型人才，比仅仅精通某种语法的程序员更有竞争力。如果你已经是某核心系统的Owner，不妨尝试推进架构优化和性能提升项目，展示自己在宏观层面的掌控力。同时，培养自己的系统设计能力，多研究业界大型系统的架构案例，学习它们如何权衡取舍。当你能从容驾驭分布式事务、异地多活、CQRS 等架构模式时，你的价值早已超越“Java 工程师”的范畴，而成为真正的技术专家。这种升级，无论未来Java的热度如何，都能让你的职业生涯保持上升。&lt;/p&gt;
&lt;h3 id="考虑转型技术管理或其他新领域"&gt;&lt;a href="#%e8%80%83%e8%99%91%e8%bd%ac%e5%9e%8b%e6%8a%80%e6%9c%af%e7%ae%a1%e7%90%86%e6%88%96%e5%85%b6%e4%bb%96%e6%96%b0%e9%a2%86%e5%9f%9f" class="header-anchor"&gt;&lt;/a&gt;考虑转型技术管理或其他新领域
&lt;/h3&gt;&lt;p&gt;并非每个人都要永远写代码。工作十年以上后，你也可以根据兴趣转型，选择最适合自己的道路。如果你热衷带团队和项目把控，可以逐步走向 技术管理 岗位，担任Team Leader、技术经理甚至CTO，把多年经验用于培养新人和决策把关。很多Java老兵在这一阶段选择带领团队，既可传承自己的开发哲学，又能获得管理成就感。又或者，你对某些新兴领域情有独钟，例如 人工智能、大数据、安全 等，不妨利用业余时间学习相关知识，寻求内部调岗或外部机会。资深程序员转做产品经理、解决方案架构师的例子也屡见不鲜——只要有心，完全可以跳出演员阵容，转到幕后编剧或导演的位置上。当然，做出转型决定前需要评估清楚：你的核心竞争力是什么，新领域是否真心喜欢，从头开始是否有心理准备。转型不是逃避，而是为了更长远的发展。无论选择深耕Java栈还是开拓新跑道，持续的学习和热情都是关键驱动力。&lt;/p&gt;
&lt;h2 id="最后"&gt;&lt;a href="#%e6%9c%80%e5%90%8e" class="header-anchor"&gt;&lt;/a&gt;最后
&lt;/h2&gt;&lt;p&gt;想对每一位焦虑中的Java老兵说：技术江湖瞬息万变，但真正的资深工程师价值从不局限于某种语法。Java 之父 James Gosling 曾打比方说：“Java就像一辆可靠的卡车”，或许它没有跑车那样光鲜，但能载着重载货物稳稳前行。这辆卡车如今也在不断改装升级，动力和油耗都在改进。我们作为司机，要做的不是弃车而逃，而是练就更高超的驾驶技巧，并且学会在不同道路上换合适的交通工具。坚守初心并不代表故步自封，拥抱变化也不意味着全盘否定过去。当我们既掌握了Java这门老牌利器，又勇于学习新招式、新套路，在变化的浪潮中依然能找到自己的方向和节奏。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/006-0c6fa2fa.png"&gt;&lt;/p&gt;
&lt;p&gt;10+年开发生涯沉淀下来的经验与智慧，是宝贵的财富。无论Java的流行曲线如何波动，真正优秀的工程师都会不断进化，拓展自己的边界。在这个过程中，我们既要有克制冷静的思考，看清技术演进的本质；也要保持对编程的热爱，不忘初心地享受技术创造的乐趣。愿每一位Java老兵都能在时代洪流中找到属于自己的位置：该出手时果断出手，该坚守时稳如磐石，在新的十年里续写属于你的传奇。&lt;/p&gt;</description></item><item><title>用户主目录下为什么会有这么多乱七八糟的 “点开头”的文件 ？没人管管吗？</title><link>https://xiaobox.github.io/p/2023-11-27-yong-hu-zhu-mu-lu-xia-wei-shen-me-hui-you-zhe-me-duo-luan-qi/</link><pubDate>Mon, 27 Nov 2023 16:00:00 +0000</pubDate><guid>https://xiaobox.github.io/p/2023-11-27-yong-hu-zhu-mu-lu-xia-wei-shen-me-hui-you-zhe-me-duo-luan-qi/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-27-yong-hu-zhu-mu-lu-xia-wei-shen-me-hui-you-zhe-me-duo-luan-qi/cover.jpg" alt="Featured image of post 用户主目录下为什么会有这么多乱七八糟的 “点开头”的文件 ？没人管管吗？" /&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-27-yong-hu-zhu-mu-lu-xia-wei-shen-me-hui-you-zhe-me-duo-luan-qi/001-5c589949.png"&gt;&lt;/p&gt;
&lt;p&gt;你的电脑里在 用户主目录（HOME）下是不是也有这么多乱七八糟的以点开头的文件和文件夹呢？&lt;/p&gt;
&lt;p&gt;我知道他们都是是各个软件在安装和使用时创建的，这玩意一般看不见，因为以 “.” 开头的是隐藏文件，但是随着软件越装越多，这种 &amp;ldquo;.&amp;rdquo; 开头的文件也越来越多，感觉好混乱呀。&lt;/p&gt;
&lt;p&gt;于是查了一下原因：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;在类 Unix 系统中，用户主目录下的以点（.）开头的文件夹通常是隐藏文件夹。这种隐藏的命名约定是为了将这些文件和文件夹从普通的目录列表中隐藏起来，以避免视觉上的混乱。这对于存储用户配置文件、缓存或其他应用程序数据非常有用，因为这些文件夹通常包含对用户不直接有用的信息。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;隐藏文件和文件夹的一个重要目的之一就是为了防止用户在正常使用系统时误删除它们。&lt;/p&gt;
&lt;p&gt;话虽这么说，但对于有强迫症的我来说，还是感觉没有“秩序” ，还有没有王法了？难道就没有个规矩来规范一下这个行为吗？&lt;/p&gt;
&lt;p&gt;你别说，还真有！&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;随着软件的安装和使用，用户主目录下的隐藏文件和文件夹可能会变得相当杂乱。为了解决这个问题，XDG Base Directory Specification 提供了一个标准化的方法，以规范用户数据、配置和缓存文件的存放位置，从而提高系统的整体组织性。这个规范旨在减少用户主目录下以点开头的直接子目录的数量，使之更加清晰和有序。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;XDG ? 这是啥？为什么叫这个名字？&lt;/p&gt;
&lt;p&gt;XDG 最初是 &amp;ldquo;X Desktop Group&amp;rdquo; 的缩写，指的是一个早期的 X Window System 桌面环境的协作组。这个规范最早由 XDG 组织提出，后来被纳入了 Freedesktop.org，一个致力于协调自由桌面软件项目的合作社区。尽管 &amp;ldquo;X Desktop Group&amp;rdquo; 这个名称不再准确反映规范的用途，但 &amp;ldquo;XDG&amp;rdquo; 作为一个术语仍然被广泛使用。&lt;/p&gt;
&lt;h3 id="xdg-base-directory-specification"&gt;&lt;a href="#xdg-base-directory-specification" class="header-anchor"&gt;&lt;/a&gt;XDG Base Directory Specification
&lt;/h3&gt;&lt;p&gt;具体的规范内容在这儿：https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html&lt;/p&gt;
&lt;p&gt;该规范通过定义一个或多个与文件所在位置相关的基本目录来定义应在何处查找这些文件。&lt;/p&gt;
&lt;p&gt;其实就是定义了一套指向应用程序的环境变量，这些变量指明的就是这些程序应该存储的基准目录。而变量的具体值取决于用户，若用户未指定，将由程序本身指向一个默认目录，该默认目录也应该遵从标准，而不是用户主目录。&lt;/p&gt;
&lt;p&gt;比如：&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-27-yong-hu-zhu-mu-lu-xia-wei-shen-me-hui-you-zhe-me-duo-luan-qi/002-86c6ebb5.png"&gt;&lt;/p&gt;
&lt;p&gt;当然不同操作系统的位置可能不一样&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2023-11-27-yong-hu-zhu-mu-lu-xia-wei-shen-me-hui-you-zhe-me-duo-luan-qi/003-6a6268fe.png"&gt;&lt;/p&gt;
&lt;p&gt;最主要的就三个要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 $XDG_DATA_HOME 中写入用户特定数据&lt;/li&gt;
&lt;li&gt;在 $XDG_CONFIG_HOME 中写入配置文件&lt;/li&gt;
&lt;li&gt;在 $XDG_CACHE_HOME 中写入缓存文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面是一个简单的示例，演示如何在 Go 语言中使用 XDG 规范：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;path/filepath&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 获取 XDG_DATA_HOME 环境变量，如果不存在则使用默认值&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgDataHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_DATA_HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgDataHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgDataHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.local&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;share&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 获取 XDG_CONFIG_HOME 环境变量，如果不存在则使用默认值&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgConfigHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_CONFIG_HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgConfigHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgConfigHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 获取 XDG_CACHE_HOME 环境变量，如果不存在则使用默认值&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgCacheHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_CACHE_HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgCacheHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgCacheHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HOME&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.cache&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 打印结果&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_DATA_HOME: %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgDataHome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_CONFIG_HOME: %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgConfigHome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;XDG_CACHE_HOME: %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xdgCacheHome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然而，仍然存在一些特定的应用程序或开发者选择在用户主目录下创建自己的隐藏文件夹，而不一定遵循 XDG 规范。这可能是因为某些应用程序在 XDG 规范之前就已经存在，或者开发者有其他特定的理由(也许压根就不知道有这个规范)。&lt;/p&gt;</description></item><item><title>Kubernetes监控体系总结</title><link>https://xiaobox.github.io/p/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/</link><pubDate>Mon, 15 Nov 2021 12:21:02 +0000</pubDate><guid>https://xiaobox.github.io/p/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/cover.jpg" alt="Featured image of post Kubernetes监控体系总结" /&gt;&lt;h2 id="基本概念"&gt;&lt;a href="#%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5" class="header-anchor"&gt;&lt;/a&gt;基本概念
&lt;/h2&gt;&lt;h3 id="cadvisor"&gt;&lt;a href="#cadvisor" class="header-anchor"&gt;&lt;/a&gt;cAdvisor
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/001-46ee78b3.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Docker 是一个开源的应用容器引擎，让开发者可以打包他们的应用以及依赖包到一个可移植的容器中，然后发布到任何流行的 Linux/Windows/Mac 机器上。容器镜像正成为一个新的标准化软件交付方式。为了能够获取到 Docker 容器的运行状态，用户可以通过 Docker 的 stats 命令获取到当前主机上运行容器的统计信息，可以查看容器的 CPU 利用率、内存使用量、网络 IO 总量以及磁盘 IO 总量等信息。&lt;/p&gt;
&lt;p&gt;显然如果我们想对监控数据做存储以及可视化的展示，那么 docker 的 stats 是不能满足的。&lt;/p&gt;
&lt;p&gt;为了解决 docker stats 的问题（存储、展示），谷歌开源的 cadvisor 诞生了，cadvisor 不仅可以搜集一台机器上所有运行的容器信息，还提供基础查询界面和 http 接口，方便其他组件如 Prometheus 进行数据抓取，或者 cAdvisor + influxDB + grafana 搭配使用。cAdvisor 可以对节点机器上的资源及容器进行实时监控和性能数据采集，包括 CPU 使用情况、内存使用情况、网络吞吐量及文件系统使用情况&lt;/p&gt;
&lt;p&gt;监控原理&lt;/p&gt;
&lt;p&gt;cAdvisor 使用 Go 语言开发，利用 Linux 的 cgroups 获取容器的资源使用信息，在 K8S 中集成在 Kubelet 里作为默认启动项，官方标配。&lt;/p&gt;
&lt;p&gt;Docker 是基于 Namespace、Cgroups 和联合文件系统实现的&lt;/p&gt;
&lt;p&gt;Cgroups 不仅可以用于容器资源的限制，还可以提供容器的资源使用率。不管用什么监控方案，底层数据都来源于 Cgroups&lt;/p&gt;
&lt;p&gt;Cgroups 的工作目录 /sys/fs/cgroup 下包含了 Cgroups 的所有内容。Cgroups 包含了很多子系统，可以对 CPU，内存，PID，磁盘 IO 等资源进行限制和监控。&lt;/p&gt;
&lt;p&gt;cAdvisor 运行原理，如下图&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/002-3731be65.jpg"&gt;&lt;/p&gt;
&lt;h3 id="prometheus"&gt;&lt;a href="#prometheus" class="header-anchor"&gt;&lt;/a&gt;Prometheus
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/003-9dea7ba3.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Prometheus 是一套开源的监控报警系统。主要特点包括多维数据模型、灵活查询语句 PromQL 以及数据可视化展示等&lt;/p&gt;
&lt;p&gt;架构图&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/004-b737ebd1.jpg"&gt;&lt;/p&gt;
&lt;p&gt;基本原理&lt;/p&gt;
&lt;p&gt;Prometheus 的基本原理是通过 HTTP 协议周期性抓取被监控组件的状态，任意组件只要提供对应的 HTTP 接口就可以接入监控。不需要任何 SDK 或者其他的集成过程。这样做非常适合做虚拟化环境监控系统，比如 VM、Docker、Kubernetes 等。输出被监控组件信息的 HTTP 接口被叫做 exporter 。目前互联网公司常用的组件大部分都有 exporter 可以直接使用，比如 Varnish、Haproxy、Nginx、MySQL、Linux 系统信息（包括磁盘、内存、CPU、网络等等）。&lt;/p&gt;
&lt;p&gt;服务过程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus Daemon 负责定时去目标上抓取 metrics（指标）数据，每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。Prometheus 支持通过配置文件、文本文件、Zookeeper、Consul、DNS SRV Lookup 等方式指定抓取目标。Prometheus 采用 PULL 的方式进行监控，即服务器可以直接通过目标 PULL 数据或者间接地通过中间网关来 Push 数据。&lt;/li&gt;
&lt;li&gt;Prometheus 在本地存储抓取的所有数据，并通过一定规则进行清理和整理数据，并把得到的结果存储到新的时间序列中。&lt;/li&gt;
&lt;li&gt;Prometheus 通过 PromQL 和其他 API 可视化地展示收集的数据。Prometheus 支持很多方式的图表可视化，例如 Grafana、自带的 Promdash 以及自身提供的模版引擎等等。Prometheus 还提供 HTTP API 的查询方式，自定义所需要的输出。&lt;/li&gt;
&lt;li&gt;PushGateway 支持 Client 主动推送 metrics 到 PushGateway，而 Prometheus 只是定时去 Gateway 上抓取数据。&lt;/li&gt;
&lt;li&gt;Alertmanager 是独立于 Prometheus 的一个组件，可以支持 Prometheus 的查询语句，提供十分灵活的报警方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="operator"&gt;&lt;a href="#operator" class="header-anchor"&gt;&lt;/a&gt;Operator
&lt;/h3&gt;&lt;p&gt;Operator 是 CoreOS 推出的旨在简化复杂有状态应用管理的框架，它是一个感知应用状态的控制器，通过扩展 Kubernetes API 来自动创建、管理和配置应用实例。&lt;/p&gt;
&lt;p&gt;Operator 基于 CustomResourceDefinition(CRD) 扩展了新的应用资源，并通过控制器来保证应用处于预期状态。比如 etcd operator 通过下面的三个步骤模拟了管理 etcd 集群的行为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 Kubernetes API 观察集群的当前状态；&lt;/li&gt;
&lt;li&gt;分析当前状态与期望状态的差别；&lt;/li&gt;
&lt;li&gt;调用 etcd 集群管理 API 或 Kubernetes API 消除这些差别。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/005-15eafb88.jpg"&gt;&lt;/p&gt;
&lt;h3 id="prometheus-operator"&gt;&lt;a href="#prometheus-operator" class="header-anchor"&gt;&lt;/a&gt;Prometheus Operator
&lt;/h3&gt;&lt;p&gt;为了在 Kubernetes 能够方便的管理和部署 Prometheus，我们使用 ConfigMap 了管理 Prometheus 配置文件。每次对 Prometheus 配置文件进行升级时，我们需要手动移除已经运行的 Pod 实例，从而让 Kubernetes 可以使用最新的配置文件创建 Prometheus。而如果当应用实例的数量更多时，通过手动的方式部署和升级 Prometheus 过程繁琐并且效率低下。&lt;/p&gt;
&lt;p&gt;从本质上来讲 Prometheus 属于是典型的有状态应用，而其又包含了一些自身特有的运维管理和配置管理方式。而这些都无法通过 Kubernetes 原生提供的应用管理概念实现自动化。为了简化这类应用程序的管理复杂度，CoreOS 率先引入了 Operator 的概念，并且首先推出了针对在 Kubernetes 下运行和管理 Etcd 的 Etcd Operator。并随后推出了 Prometheus Operator。&lt;/p&gt;
&lt;p&gt;从概念上来讲 Operator 就是针对管理特定应用程序的，在 Kubernetes 基本的 Resource 和 Controller 的概念上，以扩展 Kubernetes api 的形式。帮助用户创建，配置和管理复杂的有状态应用程序。从而实现特定应用程序的常见操作以及运维自动化。&lt;/p&gt;
&lt;p&gt;在 Kubernetes 中我们使用 Deployment、DamenSet，StatefulSet 来管理应用 Workload，使用 Service，Ingress 来管理应用的访问方式，使用 ConfigMap 和 Secret 来管理应用配置。我们在集群中对这些资源的创建，更新，删除的动作都会被转换为事件 (Event)，Kubernetes 的 Controller Manager 负责监听这些事件并触发相应的任务来满足用户的期望。这种方式我们成为声明式，用户只需要关心应用程序的最终状态，其它的都通过 Kubernetes 来帮助我们完成，通过这种方式可以大大简化应用的配置管理复杂度。&lt;/p&gt;
&lt;p&gt;而除了这些原生的 Resource 资源以外，Kubernetes 还允许用户添加自己的自定义资源 (Custom Resource)。并且通过实现自定义 Controller 来实现对 Kubernetes 的扩展。&lt;/p&gt;
&lt;p&gt;如下所示，是 Prometheus Operator 的架构示意图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/006-69c36f88.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Prometheus 的本职就是一组用户自定义的 CRD 资源以及 Controller 的实现，Prometheus Operator 负责监听这些自定义资源的变化，并且根据这些资源的定义自动化的完成如 Prometheus Server 自身以及配置的自动化管理工作。&lt;/p&gt;
&lt;p&gt;简言之，Prometheus Operator 能够帮助用户自动化的创建以及管理 Prometheus Server 以及其相应的配置。&lt;/p&gt;
&lt;h3 id="hpa"&gt;&lt;a href="#hpa" class="header-anchor"&gt;&lt;/a&gt;HPA
&lt;/h3&gt;&lt;p&gt;Horizontal Pod Autoscaler ，K8S 中的一个概念，可以自动调整 Pod 的数量，以达到指定的目标值。&lt;/p&gt;
&lt;p&gt;Pod 水平自动扩缩（Horizontal Pod Autoscaler） 可以基于 CPU 利用率自动扩缩 ReplicationController、Deployment、ReplicaSet 和 StatefulSet 中的 Pod 数量。除了 CPU 利用率，也可以基于其他应程序提供的 &lt;code&gt;自定义度量指标&lt;/code&gt;来执行自动扩缩。Pod 自动扩缩不适用于无法扩缩的对象，比如 DaemonSet。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/007-8dbad0a8.jpg"&gt;&lt;/p&gt;
&lt;h3 id="heapster"&gt;&lt;a href="#heapster" class="header-anchor"&gt;&lt;/a&gt;Heapster
&lt;/h3&gt;&lt;p&gt;Heapster 是容器集群监控和性能分析工具，天然的支持 Kubernetes 和 CoreOS。&lt;/p&gt;
&lt;p&gt;Heapster 首先从 K8S Master 获取集群中所有 Node 的信息，然后通过这些 Node 上的 kubelet 获取有用数据，而 kubelet 本身的数据则是从 cAdvisor 得到。所有获取到的数据都被推到 Heapster 配置的后端存储中，并还支持数据的可视化。现在后端存储 + 可视化的方法，如 InfluxDB + grafana。&lt;/p&gt;
&lt;p&gt;Heapster 可以收集 Node 节点上的 cAdvisor 数据，还可以按照 kubernetes 的资源类型来集合资源，比如 Pod、Namespace 域，可以分别获取它们的 CPU、内存、网络和磁盘的 metric。默认的 metric 数据聚合时间间隔是 1 分钟。&lt;/p&gt;
&lt;p&gt;注意 ：Kubernetes 1.11 不建议使用 Heapster，就 SIG Instrumentation 而言，这是为了转向新的 Kubernetes 监控模型的持续努力的一部分。仍使用 Heapster 进行自动扩展的集群应迁移到 metrics-server 和自定义指标 API。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/008-cd80d35d.jpg"&gt;&lt;/p&gt;
&lt;h3 id="metrics-server"&gt;&lt;a href="#metrics-server" class="header-anchor"&gt;&lt;/a&gt;Metrics Server
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/009-f72ed8af.jpg"&gt;&lt;/p&gt;
&lt;p&gt;kubernetes 集群资源监控之前可以通过 &lt;code&gt;heapster&lt;/code&gt; 来获取数据，在 &lt;code&gt;1.11&lt;/code&gt; 开始开始逐渐废弃 &lt;code&gt;heapster&lt;/code&gt; 了，采用 &lt;code&gt;metrics-server&lt;/code&gt; 来代替，&lt;code&gt;metrics-server&lt;/code&gt; 是集群的核心监控数据的聚合器，它从 kubelet 公开的 Summary API 中采集指标信息，&lt;code&gt;metrics-server&lt;/code&gt; 是扩展的 APIServer，依赖于 kube-aggregator，因为我们需要在 APIServer 中开启相关参数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Metrics Server&lt;/code&gt; 并不是 kube-apiserver 的一部分，而是通过 Aggregator 这种插件机制，在独立部署的情况下同 kube-apiserver 一起统一对外服务的。&lt;/p&gt;
&lt;p&gt;Aggregator&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;通过聚合层扩展 Kubernetes API使用聚合层（Aggregation Layer），用户可以通过额外的 API 扩展 Kubernetes， 而不局限于 Kubernetes 核心 API 提供的功能。这里的附加 API 可以是现成的解决方案比如 metrics server, 或者你自己开发的 API。聚合层不同于 定制资源（Custom Resources）。后者的目的是让 kube-apiserver 能够认识新的对象类别（Kind）。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;聚合层聚合层在 kube-apiserver 进程内运行。在扩展资源注册之前，聚合层不做任何事情。要注册 API，用户必须添加一个 APIService 对象，用它来“申领” Kubernetes API 中的 URL 路径。自此以后，聚合层将会把发给该 API 路径的所有内容（例如 /apis/myextension.mycompany.io/v1/…） 转发到已注册的 APIService。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;APIService 的最常见实现方式是在集群中某 Pod 内运行 扩展 API 服务器。如果你在使用扩展 API 服务器来管理集群中的资源，该扩展 API 服务器（也被写成“extension-apiserver”） 一般需要和一个或多个控制器一起使用。apiserver-builder 库同时提供构造扩展 API 服务器和控制器框架代码。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这里，Aggregator APIServer 的工作原理，可以用如下所示的一幅示意图来表示清楚 ：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/010-8e03d754.jpg"&gt;&lt;/p&gt;
&lt;p&gt;因为 k8s 的 api-server 将所有的数据持久化到了 etcd 中，显然 k8s 本身不能处理这种频率的采集，而且这种监控数据变化快且都是临时数据，因此需要有一个组件单独处理他们，于是 metric-server 的概念诞生了。&lt;/p&gt;
&lt;p&gt;Metrics server 出现后，新的 Kubernetes 监控架构将变成下图的样子&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心流程（黑色部分）：这是 Kubernetes 正常工作所需要的核心度量，从 Kubelet、cAdvisor 等获取度量数据，再由 metrics-server 提供给 Dashboard、HPA 控制器等使用。&lt;/li&gt;
&lt;li&gt;监控流程（蓝色部分）：基于核心度量构建的监控流程，比如 Prometheus 可以从 metrics-server 获取核心度量，从其他数据源（如 Node Exporter 等）获取非核心度量，再基于它们构建监控告警系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/011-c53d6287.jpg"&gt;&lt;/p&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;metrics-sevrer 的数据存在内存中。&lt;/li&gt;
&lt;li&gt;metrics-server 主要针对 node、pod 等的 cpu、网络、内存等系统指标的监控&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="kube-state-metrics"&gt;&lt;a href="#kube-state-metrics" class="header-anchor"&gt;&lt;/a&gt;kube-state-metrics
&lt;/h3&gt;&lt;p&gt;已经有了 cadvisor、heapster、metric-server，几乎容器运行的所有指标都能拿到，但是下面这种情况却无能为力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我调度了多少个 replicas？现在可用的有几个？&lt;/li&gt;
&lt;li&gt;多少个 Pod 是 running/stopped/terminated 状态？&lt;/li&gt;
&lt;li&gt;Pod 重启了多少次？&lt;/li&gt;
&lt;li&gt;我有多少 job 在运行中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而这些则是 kube-state-metrics 提供的内容，它基于 client-go 开发，轮询 Kubernetes API，并将 Kubernetes 的结构化信息转换为 metrics。&lt;/p&gt;
&lt;p&gt;kube-state-metrics 与 metrics-server 对比&lt;/p&gt;
&lt;p&gt;我们服务在运行过程中，我们想了解服务运行状态，pod 有没有重启，伸缩有没有成功，pod 的状态是怎么样的等，这时就需要 kube-state-metrics，它主要关注 deployment,、node 、 pod 等内部对象的状态。而 metrics-server 主要用于监测 node，pod 等的 CPU，内存，网络等系统指标。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;metric-server（或 heapster）是从 api-server 中获取 cpu、内存使用率这种监控指标，并把他们发送给存储后端，如 influxdb 或云厂商，他当前的核心作用是：为 HPA 等组件提供决策指标支持。&lt;/li&gt;
&lt;li&gt;kube-state-metrics 关注于获取 k8s 各种资源的最新状态，如 deployment 或者 daemonset，之所以没有把 kube-state-metrics 纳入到 metric-server 的能力中，是因为他们的关注点本质上是不一样的。metric-server 仅仅是获取、格式化现有数据，写入特定的存储，实质上是一个监控系统。而 kube-state-metrics 是将 k8s 的运行状况在内存中做了个快照，并且获取新的指标，但他没有能力导出这些指标&lt;/li&gt;
&lt;li&gt;换个角度讲，kube-state-metrics 本身是 metric-server 的一种数据来源，虽然现在没有这么做。&lt;/li&gt;
&lt;li&gt;另外，像 Prometheus 这种监控系统，并不会去用 metric-server 中的数据，他都是自己做指标收集、集成的（Prometheus 包含了 metric-server 的能力），但 Prometheus 可以监控 metric-server 本身组件的监控状态并适时报警，这里的监控就可以通过 kube-state-metrics 来实现，如 metric-serverpod 的运行状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/012-0394707a.jpg"&gt;&lt;/p&gt;
&lt;h3 id="custom-metrics-apiserver"&gt;&lt;a href="#custom-metrics-apiserver" class="header-anchor"&gt;&lt;/a&gt;custom-metrics-apiserver
&lt;/h3&gt;&lt;p&gt;kubernetes 的监控指标分为两种&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Core metrics（核心指标）：从 Kubelet、cAdvisor 等获取度量数据，再由 metrics-server 提供给 Dashboard、HPA 控制器等使用。&lt;/li&gt;
&lt;li&gt;Custom Metrics（自定义指标）：由 Prometheus Adapter 提供 API custom.metrics.k8s.io，由此可支持任意 Prometheus 采集到的指标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是官方 metrics 的项目介绍：&lt;/p&gt;
&lt;p&gt;Resource Metrics API（核心 api）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heapster&lt;/li&gt;
&lt;li&gt;Metrics Server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Custom Metrics API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus Adapter&lt;/li&gt;
&lt;li&gt;Microsoft Azure Adapter&lt;/li&gt;
&lt;li&gt;Google Stackdriver&lt;/li&gt;
&lt;li&gt;Datadog Cluster Agent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;核心指标只包含 node 和 pod 的 cpu、内存等，一般来说，核心指标作 HPA 已经足够，但如果想根据自定义指标：如请求 qps/5xx 错误数来实现 HPA，就需要使用自定义指标了，目前 Kubernetes 中自定义指标一般由 Prometheus 来提供，再利用 &lt;code&gt;k8s-prometheus-adpater&lt;/code&gt; 聚合到 apiserver，实现和核心指标（metric-server) 同样的效果。&lt;/p&gt;
&lt;p&gt;HPA 请求 metrics 时，kube-aggregator(apiservice 的 controller) 会将请求转发到 adapter，adapter 作为 kubernentes 集群的 pod，实现了 Kubernetes resource metrics API 和 custom metrics API，它会根据配置的 rules 从 Prometheus 抓取并处理 metrics，在处理（如重命名 metrics 等）完后将 metric 通过 custom metrics API 返回给 HPA。最后 HPA 通过获取的 metrics 的 value 对 Deployment/ReplicaSet 进行扩缩容。&lt;/p&gt;
&lt;p&gt;adapter 作为 extension-apiserver（即自己实现的 pod)，充当了代理 kube-apiserver 请求 Prometheus 的功能。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-15-kubernetes-jian-kong-ti-xi-zong-jie/013-bee9d39d.jpg"&gt;&lt;/p&gt;
&lt;p&gt;其实 k8s-prometheus-adapter 既包含自定义指标，又包含核心指标，即如果安装了 prometheus，且指标都采集完整，k8s-prometheus-adapter 可以替代 metrics server。&lt;/p&gt;
&lt;h2 id="prometheus-部署方案"&gt;&lt;a href="#prometheus-%e9%83%a8%e7%bd%b2%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;Prometheus 部署方案
&lt;/h2&gt;&lt;p&gt;prometheus operator&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/prometheus-operator/prometheus-operator" target="_blank" rel="noopener"
 &gt;https://github.com/prometheus-operator/prometheus-operator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;kube-prometheus&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/prometheus-operator/kube-prometheus" target="_blank" rel="noopener"
 &gt;https://github.com/prometheus-operator/kube-prometheus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在集群外部署&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.qikqiak.com/post/monitor-external-k8s-on-prometheus/" target="_blank" rel="noopener"
 &gt;https://www.qikqiak.com/post/monitor-external-k8s-on-prometheus/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;kube-prometheus 既包含了 Operator，又包含了 Prometheus 相关组件的部署及常用的 Prometheus 自定义监控，具体包含下面的组件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Prometheus Operator：创建 CRD 自定义的资源对象&lt;/li&gt;
&lt;li&gt;Highly available Prometheus：创建高可用的 Prometheus&lt;/li&gt;
&lt;li&gt;Highly available Alertmanager：创建高可用的告警组件&lt;/li&gt;
&lt;li&gt;Prometheus node-exporter：创建主机的监控组件&lt;/li&gt;
&lt;li&gt;Prometheus Adapter for Kubernetes Metrics APIs：创建自定义监控的指标工具（例如可以通过 nginx 的 request 来进行应用的自动伸缩）&lt;/li&gt;
&lt;li&gt;kube-state-metrics：监控 k8s 相关资源对象的状态指标&lt;/li&gt;
&lt;li&gt;Grafana：进行图像展示&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="我们的做法"&gt;&lt;a href="#%e6%88%91%e4%bb%ac%e7%9a%84%e5%81%9a%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;我们的做法
&lt;/h2&gt;&lt;p&gt;我们的做法，其实跟 kube-prometheus 的思路差不多，只不过我们没有用 Operator ，是自己将以下这些组件的 yaml 文件用 helm 组织了起来而已：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kube-state-metrics&lt;/li&gt;
&lt;li&gt;prometheus&lt;/li&gt;
&lt;li&gt;alertmanager&lt;/li&gt;
&lt;li&gt;grafana&lt;/li&gt;
&lt;li&gt;k8s-prometheus-adapter&lt;/li&gt;
&lt;li&gt;node-exporter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然 kube-prometheus 也有 helm charts 由 prometheus 社区提供：https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&lt;/p&gt;
&lt;p&gt;这么干的原因是：这样的灵活度是最高的，虽然在第一次初始化创建这些脚本的时候麻烦了些。不过还有一个原因是我们当时部署整个基于 prometheus 的监控体系时，kube-prometheus 这个项目还在早期，没有引起我们的关注。如果在 2021 年年初或 2020 年年底的时候创建的话，可能就会直接上了。&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.opskumu.com/cadvisor.html" target="_blank" rel="noopener"
 &gt;https://blog.opskumu.com/cadvisor.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://prometheus.io/" target="_blank" rel="noopener"
 &gt;https://prometheus.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscale/" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/zh/docs/tasks/run-application/horizontal-pod-autoscale/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/chenqionghe/p/10494868.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/chenqionghe/p/10494868.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.qikqiak.com/post/k8s-operator-101/" target="_blank" rel="noopener"
 &gt;https://www.qikqiak.com/post/k8s-operator-101/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/zh/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/" target="_blank" rel="noopener"
 &gt;https://kubernetes.io/zh/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://segmentfault.com/a/1190000017875641" target="_blank" rel="noopener"
 &gt;https://segmentfault.com/a/1190000017875641&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://segmentfault.com/a/1190000038888544" target="_blank" rel="noopener"
 &gt;https://segmentfault.com/a/1190000038888544&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://yasongxu.gitbook.io/" target="_blank" rel="noopener"
 &gt;https://yasongxu.gitbook.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://mp.weixin.qq.com/s/p4FAFKHi8we4mrD7OIk7IQ" target="_blank" rel="noopener"
 &gt;https://mp.weixin.qq.com/s/p4FAFKHi8we4mrD7OIk7IQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.feisky.xyz/apps/index/operator" target="_blank" rel="noopener"
 &gt;https://kubernetes.feisky.xyz/apps/index/operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://yunlzheng.gitbook.io/prometheus-book/part-iii-prometheus-shi-zhan/operator/what-is-prometheus-operator" target="_blank" rel="noopener"
 &gt;https://yunlzheng.gitbook.io/prometheus-book/part-iii-prometheus-shi-zhan/operator/what-is-prometheus-operator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>