<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Morven's Blog</title><link>https://morvencao.github.io/</link><description>Recent content on Morven's Blog</description><generator>Hugo</generator><language>cn</language><lastBuildDate>Sat, 07 Oct 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://morvencao.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Navigating the Cloud: KubeConf 2023 Journey</title><link>https://morvencao.github.io/posts/kubeconf-2023-trip/</link><pubDate>Sat, 07 Oct 2023 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/kubeconf-2023-trip/</guid><description>&lt;p&gt;I had the honor of attending KubeCon + CloudNativeCon + Open Source Summit China 2023 in Shanghai, an enriching experience that united the brightest minds in the tech industry. The conference delved into cutting-edge advancements and sparked insightful discussions on Cloud Native technologies, Artificial Intelligence (AI), Edge Computing, WebAssembly, Service Mesh, and pioneering strategies for reducing our carbon footprint.&lt;/p&gt;
&lt;h2 id="key-highlights"&gt;Key Highlights&lt;/h2&gt;
&lt;h3 id="cloud-native-ecosystem"&gt;Cloud Native Ecosystem&lt;/h3&gt;
&lt;p&gt;Witnessed the evolution of Cloud Native technologies, focusing on Kubernetes, containerization, multicluster orchestration, multicloud deployment, and serverless computing. A key highlight was the in-depth exploration of eBPF technology within Cloud Native architecture. Experts demonstrated its capacity to enhance network performance and security, enabling fine-grained control and unmatched visibility into network events. This underscored eBPF&amp;rsquo;s pivotal role in shaping Cloud Native architectures.&lt;/p&gt;</description></item><item><title>Linux与Git联手创造多重宇宙</title><link>https://morvencao.github.io/posts/git-like-os-fs/</link><pubDate>Mon, 25 Sep 2023 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/git-like-os-fs/</guid><description>&lt;p&gt;自Linux诞生以来，一直以其简洁灵活的特性受到开发者的热烈追捧。Linux坚守Unix的&lt;a href="https://en.wikipedia.org/wiki/KISS_principle"&gt;KISS(Keep It Simple, Stupid)&lt;/a&gt;设计原则，但随着新的计算场景不断涌现，一些问题也逐渐浮现，其中之一便是操作系统生命周期的管理。&lt;/p&gt;
&lt;p&gt;回顾历史，开发者们为解决这个问题尝试了各种各样的方法，其中包括包管理工具和漏洞自动修复工具等等。但是，是否还存在更出色的解决方案呢？当然存在，其中之一就是建立一个与Git类似的操作系统。想象一下，将操作系统的生命周期管理类比为Git仓库的管理，我们可以随意切换到不同的版本，创建新的分支，然后将它们合并到主分支，或者回滚到任何特定的版本。这样一来，我们就可以随心所欲地管理操作系统的生命周期，就像在创建多重宇宙一样。&lt;/p&gt;
&lt;h2 id="为什么要重新造轮子"&gt;为什么要重新造轮子&lt;/h2&gt;
&lt;p&gt;我们常说“less is more”。如果你的智能手机已经提供了GPS地图应用程序，你可能就不会再购买物理GPS导航器了。同样地，如果你的Linux系统能够提供高效且易于使用的即插即用功能，那么为什么还需要配置和维护额外的工具呢？&lt;/p&gt;
&lt;p&gt;让我们深思熟虑一下那些声称支持操作系统回滚、增量更新、一致性或变更历史跟踪的解决方案所涉及的复杂性（以及成本）。如果我们能够仅仅通过操作系统本身的功能就能够实现这些目标，那岂不是更理想？当然，前提是即插即用的功能至少要与额外工具提供的功能一样出色。接下来，我们将详细了解这种全新的操作系统生命周期管理方法是如何实现这一目标的。&lt;/p&gt;
&lt;p&gt;这个创新的轮子被称为&lt;a href="https://ostreedev.github.io/ostree/"&gt;libostree&lt;/a&gt;，当然，许多人也习惯称其为OSTree。OSTree的模型类似于Git，因为它对文件进行了校验和，并采用内容寻址的对象存储方式。但与Git不同的是，OSTree通过硬链接来“checkout”文件，因此需要将它们保持不变，以防止数据损坏。&lt;/p&gt;
&lt;p&gt;在我们深入研究OSTree之前，让我们先了解一些相关的基础知识。&lt;/p&gt;
&lt;h2 id="chroot-监狱"&gt;chroot 监狱&lt;/h2&gt;
&lt;p&gt;多年前，一些Linux开发者认识到在操作系统核心功能的开发中需要极高的谨慎，以防意外破坏环境。为了确保他们可以在一个安全的沙盒中进行开发，就像拥有时光机一样，可以随时回到原始环境，他们开发了一种在同一系统中快速创建新的操作系统“实例”的方式。这种方法允许他们在一个相对独立的环境中进行开发，从而安全地进行实验，而无需担心对主系统的影响。此外，这种方法还具有其他优点，如共享数据、使用系统安装的应用程序和特定硬件等。&lt;/p&gt;
&lt;p&gt;或许你会提到虚拟化和容器技术也可以实现类似的隔离和环境复制功能。但问题是，虚拟机和容器的创建过程相对较慢，且在多个“版本”之间共享数据、应用程序和硬件有时会变得复杂。更重要的是，回滚操作通常不容易。因此，开发者们决定采用chroot技术来实现这种特殊的超能力。&lt;/p&gt;
&lt;p&gt;“chroot”是早期Unix引入的一项操作，它改变了进程的“根目录”，从而可以创建一个隔离的“监狱”环境，以限制进程只能访问该环境内的资源。这种技术可用于创建进程的沙盒，防止它们对chroot目录之外的数据进行恶意更改，或者作为轻量级虚拟机的替代方案。尽管容器技术也使用了“namespace”等更复杂的机制来实现隔离，但chroot技术同样可以提供类似的隔离效果，而且更加轻量。&lt;/p&gt;
&lt;p&gt;回到操作系统的生命周期管理问题，核心思想是允许从不同的chroot目录引导，这样开发者可以在一个chroot监狱环境中开发新功能，可以访问一些共享数据和其他应用程序。如果新环境出现问题，只需回到“原始”的chroot监狱，而不会影响其他环境。然而，仅仅使用chroot还不足够，因为要实现这些功能，需要与不同的引导加载程序组件（如GRUB、内核初始化文件、文件系统挂载等）协同工作。此外，还需要考虑如何执行诸如平台更新等操作，这就是libostree的核心功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2023/09/25/I8u1t4KlMJHVv3B.webp" alt="rhel-for-edge-chroot-directories.png"&gt;&lt;/p&gt;
&lt;h2 id="类git的可引导文件系统"&gt;类Git的可引导文件系统&lt;/h2&gt;
&lt;p&gt;首先，让我们来看libostree的官方定义：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Libostree is both a shared library and suite of command line tools that combines a “git-like” model for committing and downloading bootable filesystem trees, along with a layer for deploying them and managing the bootloader configuration.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;简而言之，libostree是一个共享库和一组命令行工具，它结合了一种“类似于Git”的模型，用于提交和下载可引导的文件系统树，同时还提供了用于部署它们和管理引导加载程序配置的层次。&lt;/p&gt;
&lt;p&gt;换句话说，libostree用于管理可引导的操作系统生命周期，它采用了类似于Git版本控制系统的工作方式。与Git一样，libostree使用校验和来管理文件，将操作系统的不同版本存储为一组不可变的对象。这些对象可以像源代码一样进行版本控制和回滚。这使得libostree能够轻松地管理不同版本的操作系统，并实现类似Git的分支、合并和回滚操作，为操作系统的生命周期管理提供了强大的工具。&lt;/p&gt;
&lt;p&gt;然而，正如你所指出的，使用chroot创建不同“版本”的根文件系统可能会导致存储空间的浪费，因为相同的文件会重复出现。为了解决这个问题，需要一种方式来在不复制或共享文件的情况下，在不同的根文件夹中存储相同的文件，并能够跟踪版本更改。&lt;/p&gt;
&lt;p&gt;这就是类似Git的思想发挥作用的地方。Git是一个由哈希标识的对象组成的数据库，它使用键值数据存储概念来存储数据。Git具有四种不同类型的对象，用于表示文件的内容（blob）、目录结构（tree）、版本信息（commit）和标签（tag）。&lt;/p&gt;
&lt;p&gt;虽然“blobs”和“trees”足以表示完整的文件系统，但“commits”包含对描述存储库根目录的“tree”对象的引用，从而提供了完整的版本控制系统。如果两个不同版本之间的文件内容没有更改，它们可以指向相同的“blob”，而不需要复制文件内容。&lt;/p&gt;
&lt;p&gt;libostree不仅仅是Git的翻版，它借鉴了Git的概念，并以非常相似的方式应用这些概念，但在具体实现上有所不同。Git主要设计用于源代码仓库版本控制，因此它的功能更侧重于“文本文件”。相反，libostree需要处理混合版本控制，包括对“文本文件”和“二进制文件”的优化，因此它的功能更广泛，不仅局限于文本文件。&lt;/p&gt;
&lt;h2 id="有效地复制文件系统"&gt;“有效地”复制文件系统&lt;/h2&gt;
&lt;p&gt;我们已经理解了使用chroot技术创建可引导的根文件系统环境，并希望实现类似Git的版本控制系统，以便有效管理不同版本的文件系统。现在让我们来谈谈如何实现“有效地”复制文件系统，这涉及到Linux中的硬链接。&lt;/p&gt;
&lt;p&gt;在Linux中，有两种类型的文件链接：软链接（符号链接）和硬链接。软链接是一种特殊的文件，它指向另一个常规文件，而硬链接则是不同文件名直接指向相同数据和属性（inode）的文件。这两种链接类型之间有一个关键区别，使得硬链接更适合类似Git的版本控制用例。使用硬链接，即使你删除了“目标”文件，数据仍然可以访问，而软链接在目标文件被删除后将不再有效。在我们的情况下，我们需要在同一磁盘分区上拥有多个“文件副本”，并且这些副本必须是独立的，以防止删除一个文件影响到其他版本。&lt;/p&gt;
&lt;p&gt;libostree使用硬链接来“checkout”文件，这意味着它可以在不复制文件内容的情况下创建多个文件系统版本，这是非常高效的。但是，硬链接也带来了一个问题。假设你有两个操作系统的“快照”（我们将它们称为“部署A”和“部署B”），它们是相同的。然后，你在“部署B”上进行了一些二进制文件的更改，但后来发现这些更改导致问题。你决定回到“部署A”，但问题是，“部署B”中的更改已经影响了“部署A”，因为它们实际上共享了相同的硬链接文件。这使得文件必须是不可变的，否则一个版本的更改将影响到其他版本。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，操作系统基于只读文件系统构建，并在启动时使用符号链接来选择可用的操作系统根文件系统“快照/镜像/部署”。每当你需要进行更改时，会创建一个全新的根文件系统的副本，但不需要复制所有文件内容，因此速度非常快。只有发生更改的文件将成为新的“常规”文件，而其他文件将保持为硬链接或在新版本中被删除。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2023/09/25/8Xt9HyUnmoVlbJ7.webp" alt="rhel-for-edge-deployment-changes.png"&gt;&lt;/p&gt;</description></item><item><title>关于 WebAssembly，我们需要知道的事</title><link>https://morvencao.github.io/posts/knowledge-about-wasm/</link><pubDate>Fri, 27 Jan 2023 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/knowledge-about-wasm/</guid><description>&lt;h2 id="webassembly-是什么"&gt;WebAssembly 是什么？&lt;/h2&gt;
&lt;p&gt;WebAssembly 简称 WASM，从字面上看，它是由 Web 和 Assembly 组成，可以理解为网络/浏览器汇编，这也表明这项技术的由来，即运行在浏览器内的汇编代码。但是，随着技术的迭代，WebAssembly 早已不局限于它最初的设计愿景。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;WebAssembly 不完全是汇编语言，而是一种类似汇编字节码的指令格式标准，由 W3C 的 &lt;a href="https://www.w3.org/wasm/"&gt;WASM 工作组&lt;/a&gt;和 &lt;a href="https://bytecodealliance.org/"&gt;ByteCode Aliance(字节码联盟)&lt;/a&gt;共同维护。它更像 LLVM-IR 那种比汇编语言更高一些抽象的中间语言，开发者不需要手写 WASM，而是选择使用其他高级语言（如 C、C++、Rust、Go、Python等）编写并编译为 WASM。&lt;/li&gt;
&lt;li&gt;WebAssembly 已经不只局限于运行在浏览器上面，如今随着 WASM 生态圈的不断扩展，涌现出了各种与 WASM 兼容的&lt;a href="https://github.com/appcypher/awesome-wasm-runtimes"&gt;运行时&lt;/a&gt;，这些运行时的出现使得 WASM 可以运行在浏览器之外的客户端与服务器端这样的沙盒环境中。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="为什么需要-webassembly-"&gt;为什么需要 WebAssembly ？&lt;/h2&gt;
&lt;p&gt;不管是在浏览器还是 Node.js 这样的服务端中，JavaScript 都可以胜任，为什么还需要 WASM ？&lt;/p&gt;
&lt;p&gt;主要是因为 JavaScript 的性能问题，本质上，JavaScript 没有静态的变量类型，导致执行引擎所做的编译优化(JIT Compiler)很可能失效。举个例子，JavaScript 代码中定义了一个函数并且包含一个局部变量，开始是它被赋予 Array 类型的值，执行到下一行代码时它又被赋予 Object 类型的值，JIT 编译器所做的优化也就失效了。也正是因为 JavaScript 本身的设计缺陷，导致这门编程语言的发展史变成了填坑史，即使是围绕 JavaScript 的开发生态越来越庞大，它在面对大型复杂项目是还是有点儿捉襟见肘。&lt;/p&gt;
&lt;p&gt;当然，开发者为了解决 JavaScript 的性能问题也做了各种尝试，除了 WASM 还有一种优化 JavaScript 的优化子集：&lt;a href="http://asmjs.org/"&gt;asm.js&lt;/a&gt;。与 WASM 一样，asm.js 也是一种编译目标，可读性比 WebAssembly 好，但是开发者必须强制使用静态类型，这导致并不是所有的开发者都能够接受；其次 acm.js 的代码还是需要经过“获取-解析-编译-优化”这些耗时的过程，而 WASM 则不用这些步骤，WASM 已经是原生的字节码，而且 WASM 也是静态类型的，这使得大多数优化在其初始编译时就已完成，所以 WebAssembly 比 asm.js 与 原生 JavaScript 快很多。&lt;/p&gt;</description></item><item><title>写在「2022」年年末</title><link>https://morvencao.github.io/posts/2022-retrospect/</link><pubDate>Sun, 25 Dec 2022 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/2022-retrospect/</guid><description>&lt;p&gt;岁月不居，时节如流，转眼间就到了圣诞节了。一般来说每年这时候是比较轻松自在的阶段，然而，面对国内疫情放开后的第一波的冲击，即使再加倍小心，还是中招了。&lt;/p&gt;
&lt;p&gt;目前，经过大约一周的休整，身体基本已经恢复到感染前的水平。作为一个没有接种过疫苗的人，希望我与奥密克戎的抗争周期可以供大家参考，希望大家能更多得了解新冠。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/12/28/rWyR3dZmpN6jF7o.png" alt="ncov-test-postive.png"&gt;&lt;/p&gt;
&lt;p&gt;第一天，也就是12月20号晚上，开始发烧，但其实早在发烧前几天都伴有轻微头痛眩晕，以为自己没休息好，也就没太在意，直到发烧开始，头痛加剧，到晚上10点钟体温超过39度，心跳急促，辗转一晚几乎没睡着，看着窗外日光变成灯光，灯光变成日光。整晚亲身经历着嗓子如何变得干涩，味觉逐渐退化的过程，这应该是最难熬的一天。&lt;/p&gt;
&lt;p&gt;第二天，体温降到38度以下，但由于前一晚基本睡睡醒醒，醒醒睡睡，导致第二天起来之后全身酸痛，头昏脑胀，喉咙痛痒，恶心厌食，主要靠维C泡腾片冲水喝，这一天我足足喝了大约3升左右的水。到了下午左右所有症状开始好转一点儿，于是开始睡觉，醒来时已经到了第二天凌晨5点。&lt;/p&gt;
&lt;p&gt;第三天，体温基本恢复正常，头痛也有所缓解，但是腰酸背痛，特别是竖脊肌附近的肌群，同时，喉咙干涩有痰，偶尔咳嗽。稍微有了些胃口，于是自己煮了碗菠菜挂面补充了一下。&lt;/p&gt;
&lt;p&gt;第四天，全部症状都有所缓解，但开始干咳不停，味觉慢慢开始恢复，眼睛重新闪烁出光芒。&lt;/p&gt;
&lt;p&gt;第五天，偶尔咳嗽，鼻子干涩，但抗原测试还是阳性。&lt;/p&gt;
&lt;p&gt;总的来说，如果要我对这次奥密克戎感染的剧烈程度做一个评价的话，我认为它不能简单用一个“大号流感”来形容。这次西安地区主要的流行株是奥密克戎BF.7，它的整个发病过程猛烈但病程短，需要有至少正常的身体来度过真个发病周期，所以这个冬天对于老年人真的是个巨大的挑战。&lt;/p&gt;
&lt;p&gt;关于药物的使用，需要按照个人的体质酌情处理。我之前感冒有对药物过敏的先例，于是这次没有使用任何药物，发烧的前两天自己熬制了生姜葱白汤来热饮，效果还可以。最后需要说的是，一旦转阳之后最好身边有人照顾，尤其是发烧阶段，神志不清，万一遇到紧急状况很难处理。&lt;/p&gt;
&lt;p&gt;现在奥密克戎的传播能力进化得非常强，基本不靠飞沫，面对面讲话就可能被感染，真是防不胜防，一般的医用外科口罩有些难以抵挡。希望大家尽量不感染，也尽可能晚感染，也许，当我们积累了更多的对付奥密克戎的经验之后，真个发病周期就不需要这么难受了。&lt;/p&gt;
&lt;p&gt;总之，不管是2022年以及即将到来的2023年，健康都变得无比重要，也希望这场疫情能够随着政策的变动而淡出我们的生活。最后愿凛冬散尽，星河长明。&lt;/p&gt;</description></item><item><title>关于 eBPF，我们需要知道的事</title><link>https://morvencao.github.io/posts/knowledge-about-ebpf/</link><pubDate>Sat, 01 Oct 2022 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/knowledge-about-ebpf/</guid><description>&lt;h2 id="什么是-ebpf"&gt;什么是 eBPF&lt;/h2&gt;
&lt;p&gt;eBPF 全称为 Extended Berkeley Packet Filter，源于 BPF（Berkeley Packet Filter）, 从其命名可以看出来，它适用于网络报文过滤的功能模块。但是，eBPF已经进化为一个通用的执行引擎，其本质为内核中一个类似于虚拟机的功能模块。eBPF 允许开发者编写在内核中运行的自定义代码，并动态加载到内核，附加到某个处罚 eBPF 程序执行的内核事件上，而不再需要重新编译新的内核模块，可以根据需要动态加载和卸载 eBPF 程序。由此开发者可以基于 eBPF 开发各种网络、可观察性以及安全方面的工具，正因为如此，才让 eBPF 在云原生盛行的当下如鱼得水。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/10/09/oG4R2fCb5rcHwZq.jpg" alt="what-is-ebpf.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; 最初的 BPF 广泛使用在类 unix 的内核中，而重新设计开发的 eBPF 最早集成在 3.18 的 linux 内核中，此后 BPF 就被被称为经典 BPF，也就是 cBPF（classic BPF），如今的 linux 内核不在运行 cBPF ，内核会将加载的 cBPF 字节码透明地转换成 eBPF 再执行。&lt;/p&gt;
&lt;h2 id="ebpf-是如何工作的"&gt;eBPF 是如何工作的&lt;/h2&gt;
&lt;p&gt;一般来说，eBPF 程序包括两部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;eBPF 程序本身&lt;/li&gt;
&lt;li&gt;使用 eBPF 的应用程序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先说使用 eBPF 的应用程序，它运行在用户空间，通过系统调用来加载 eBPF 程序，将其 attach 到某个出发此 eBPF 的内核事件上。如今的内核版本已经支持将 eBPF 程序可以加载到很多类型的内核事件上，最典型的是内核收到网络数据包的事件，这也是 BPF 最初的设计初衷，网络报文的过滤。除此之外，还可以将 eBPF 程序附加到和黑函数的入口（kprobe）以及跟踪点（trace point）等上面。eBPF 的应用程序有时候也需要读取从 eBPF 程序中传回的统计信息，事件报告等。&lt;/p&gt;</description></item><item><title>为什么音乐多是由12个音组成</title><link>https://morvencao.github.io/posts/why-12-notes-in-music/</link><pubDate>Thu, 03 Feb 2022 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/why-12-notes-in-music/</guid><description>&lt;p&gt;一直有一个疑问困扰着我，为什么大部分音乐都是由12个音组成？当然，为了严谨起见，这里所说的音乐并不包括中国传统音乐中的所谓「五声七音十二律」，但其实十二律和现在主流乐音体系中「十二平均律」是相通的。音乐本就无国界，这是一件相当神奇的事情，另外一个例子是全世界各种族的人类听到大三和弦就会感到很愉悦，一旦将大三和弦的中音降低半个音高就会变成小三和弦，这时候听者往往会感到悲伤。&lt;/p&gt;
&lt;p&gt;言归正传，作为一个理科生，我还是想从物理学和一点点生理学的角度来研讨一下为什么现代音乐普遍是由12个音谱成。可能有人就要说了钢琴不是有88个键吗？难道不应该是至少有88个音吗？实际上，但从物理学的角度来看，音高就是音的频率高低，那么可用的音就会无穷无尽。但问题是，音的频率只是区分不同音的一个角度，如果从生理学的角度来看，音的频率反而不是很重要，比如，我们将某音乐作品的所有音都按照固定的比率升高之后演奏，就会发现发出的音乐还是和以前一样和谐。所以说，重要的不是音的频率，而是这些频率之间的音程。当我们从C调切换为D调来演奏《小星星》的时候，会发现它还是《小星星》。旋律本身不是一些列音的组合，而是一些列音程的组合。&lt;/p&gt;
&lt;p&gt;由此来看，钢琴上的键虽然很多，但是这些键发出的音都是按照固定的比率分组重复，仔细观察就会发现每个分组都包括12个音，其中包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;7个自然音：&lt;strong&gt;C-D-E-F-G-A-B&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;5个由自然音变化而来的演化音：&lt;strong&gt;C#-D#-F#-G#-A#&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些音的音高关系以及在键盘上的布局如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/03/6eZzncwkfGDd7rE.jpg" alt="music-12-notes.jpg"&gt;&lt;/p&gt;
&lt;p&gt;回到上面的问题，为什么会选择这12个音呢？&lt;/p&gt;
&lt;p&gt;单从物理学的角度很难回答这个问题，因为音乐本身就是很主观的，之所以选择这12个音是因为经过千万年的演化，大部分人类都认同这12个音以及对应的音程是最和谐悦耳最具音乐性的。说白了就是用这12个音谱成的旋律最适合演奏。&lt;/p&gt;
&lt;p&gt;为什么这12个音最适合演奏？&lt;/p&gt;
&lt;p&gt;要回答这个问题，就要引出&lt;a href="https://en.wikipedia.org/wiki/Octave"&gt;Octave(八度)&lt;/a&gt;的概念。钢琴键盘上的所有键是由不同版本的12个音分组组成，我们称之为音组，相邻音组的同名音，我们称之为「八度」。八度是音乐中最和谐最重要的音程，相距八度的音听起来很自洽，它们是相同音的较高或较低表现形式，在乐音体系中称之为&lt;a href="https://en.wikipedia.org/wiki/Octave#Equivalence"&gt;Octave Equivalence(八度相等)&lt;/a&gt;。从钢琴上看，我们会看到多个A布局在键盘上相邻八度的位置上。而且这种名为八度的音高差异，对应于频率的差异，并非一个固定的赫兹数，而是一个固定的频率比率，该比率为2:1。钢琴中央A音高对应之频率为440Hz，其高八度之A音对应的频率为880Hz；其低八度之A音对应的频率则为220Hz。对于弦乐器，比如吉他，弦长减半，则频率加倍，也就是说弦长一半的音是另一弦的高八度音。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/03/9LeND5ZfsyWFTCl.jpg" alt="music-octave.jpg"&gt;&lt;/p&gt;
&lt;p&gt;现在世界上主流的音律系统包括西方的十二音律系统（后面会提及）都是首先确定八度，在用各种方式把八度分成不同的音。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/04/RhxIg6CzEmko2s7.jpg" alt="music-tune-system.jpg"&gt;&lt;/p&gt;
&lt;p&gt;这其中把八度分成12个音的方式可以很方便的演奏下面最重要的几个音程：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/04/7IuDYGklgMf89bn.jpg" alt="music-intervals.jpg"&gt;&lt;/p&gt;
&lt;p&gt;其中八度音已经介绍过了，我们快速过一下其他音程，然后再看看它们在键盘上的位置。&lt;/p&gt;
&lt;p&gt;除了八度音之外被广泛认为最和谐的两个音程是纯五度与纯四度，其实越是和谐的音程，它们组成音的频率越是具备简单的关系，比如八度音的频率比是2:1，而纯五度与纯四度音的频率比分别是3：2与4:3。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/04/UoJrw3Y2ytCF6DW.jpg" alt="music-intervals-p5-p4.jpg"&gt;&lt;/p&gt;
&lt;p&gt;注意这里不要混淆了因果，我们并不是因为音频率比简单而选择它们，而是因为音程越和谐，音之间的频率比越简单。这并不是巧合，从物理学的角度来看，两个音的频率比越简单，这两个音的波长就会完美的排列甚至同步。所以说，音频率的完美同步才是这些音程和谐的根本原因。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/04/SJrPCbsOcnox8aW.jpg" alt="music-interval-consonant-reason.jpg"&gt;&lt;/p&gt;
&lt;p&gt;人们对于八度、纯五度与纯四度之外的其他音程和谐程度的排序会略有不同，然而大部分遵循下面这张图，纵坐标代表和谐程度，每个音程的位置越高代表越和谐：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/04/7IuDYGklgMf89bn.jpg" alt="music-intervals.jpg"&gt;&lt;/p&gt;
&lt;p&gt;还是列出这些主要和谐音程的频率比，如下表所示，从表中我们也很容易越和谐的音程其组成音的频率比越简单：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: center"&gt;音程&lt;/th&gt;
 &lt;th style="text-align: center"&gt;根音的乘阶&lt;/th&gt;
 &lt;th style="text-align: center"&gt;频率比&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: center"&gt;八度&lt;/td&gt;
 &lt;td style="text-align: center"&gt;2&lt;/td&gt;
 &lt;td style="text-align: center"&gt;2:1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: center"&gt;纯五度&lt;/td&gt;
 &lt;td style="text-align: center"&gt;1.5&lt;/td&gt;
 &lt;td style="text-align: center"&gt;3:2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: center"&gt;纯四度&lt;/td&gt;
 &lt;td style="text-align: center"&gt;1.33&lt;/td&gt;
 &lt;td style="text-align: center"&gt;4:3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: center"&gt;大三度&lt;/td&gt;
 &lt;td style="text-align: center"&gt;1.25&lt;/td&gt;
 &lt;td style="text-align: center"&gt;5:4&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: center"&gt;小三度&lt;/td&gt;
 &lt;td style="text-align: center"&gt;1.2&lt;/td&gt;
 &lt;td style="text-align: center"&gt;6:5&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: center"&gt;大六度&lt;/td&gt;
 &lt;td style="text-align: center"&gt;1.6&lt;/td&gt;
 &lt;td style="text-align: center"&gt;8:5&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: center"&gt;小六度&lt;/td&gt;
 &lt;td style="text-align: center"&gt;1.66&lt;/td&gt;
 &lt;td style="text-align: center"&gt;5:3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;除了上表所列的和谐音程之外的剩下音程普遍被认为是不和谐的音程，但这并不代表这些音程没有用，虽然说“不和谐”看起来应该尽量避免使用，但是没有“不和谐”音程的音乐往往缺乏戏剧效果或者悬念，所以在某些电影音乐中经常能见到所谓的“不和谐”音程来增加张弛度，最典型的例子就是诺兰电影御用音乐制作大神汉斯-季默的作品。&lt;/p&gt;
&lt;p&gt;这基本上就是现代音乐主要用到的12个音。可能有人又要问了，为什么不能在这些音中间再加入一些音，比如，在小三度与大三度之间加入一个“中三度音”，也可以在大七度与八度之间加入一个所谓的“超七音”，这样，就可以这样划分八度之间的音：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/03/lPULnqwrbI9BGNs.jpg" alt="music-why-not-more-notes.jpg"&gt;&lt;/p&gt;
&lt;p&gt;事实上，我们并不能这样加入微分音，因为八度之间的音“几乎”是平均分布的，这方便用乐器演奏不同的调，八度之间的这些标准音几乎是平均排列，如果再加入上面所示的两个微分音，就会打破这种平均的局面。即使要加入更多的音到八度之间，我们也得找到一种既能平均分割八度但又能涵盖上面讲到的各个重要音程（纯五度、纯四度、大三度等）的分割方法。最典型的两种分割方法是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;八度19音&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/03/8ObGXEazS6Ws7Vq.png" alt="music-octave-19.png"&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;八度24音&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/03/5SC2QW7DbyXgeB4.png" alt="music-octave-24.png"&gt;&lt;/p&gt;
&lt;p&gt;这两种分割方法都既保证了平均布局又保证涵盖各个重要音程，历史上，有些音乐家确实使用这些音律系统制作了乐器并演奏作品，比如四分音钢琴，也叫做24音阶钢琴，每个八度中包含24个键。可想而知，加入更多的微分音会让乐器更加难以演奏，而且这些新加入的微分音符大部分不太实用，也就是说不值得加入乐器。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;乐器的设计必须在易用性和演奏更多音之间找到平衡。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;几个世纪以来人们经过各种实践得出的结论是，八度12音是这种最优解，它是演奏最和谐和最有用音程的最佳组合方式。有了八度12音，依次增减排列就可以得到钢琴的所有键。&lt;/p&gt;
&lt;p&gt;到这里，我们最初的问题似乎得到了解答。&lt;/p&gt;
&lt;p&gt;但是，等等&amp;hellip;&lt;/p&gt;
&lt;p&gt;还有个问题，之前留了一个“坑”，我们提到过上面这种按照简单频率比来划分八度方式得到的12音的布局“几乎”是平均排列但并不是“严格”平均排列，这会不会造成其他的麻烦呢？&lt;/p&gt;
&lt;p&gt;要回答此问题？需要介绍新的概念 - &lt;strong&gt;律制&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;上面介绍的这种律制一般称之为&lt;strong&gt;均律&lt;/strong&gt;，音程按照完美的频率比布局。但是即使是最简单最完美的频率比布局，也会到来其他的问题。因为乐器如果要按照不同的调演奏作品，音就需要尽可能平局排列，所以即使均律12音间隔近似相等，但并不是完美平均排列。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://s2.loli.net/2022/02/03/rPfXASI9MLuBHDv.png" alt="music-octave-12-not.png"&gt;&lt;/p&gt;
&lt;p&gt;由此带来的问题是，换调演奏同一作品时，根音也跟着发生变化，所以就需要重新调律，但是调律之后就会发生“跑调”的问题。比如A-E之间是完美的3:2关系，所以如果用A大调来演奏音乐听起来会非常悦耳，但是如果在其他调演奏同一音乐，比如Eb大调，听起来就会很不和谐。原因是，虽然A-E的纯五度是完美的3:2关系，但是Eb-Bb的纯五度并不是3:2关系。&lt;/p&gt;</description></item><item><title>嘿，明年见</title><link>https://morvencao.github.io/posts/2021-retrospect/</link><pubDate>Sat, 25 Dec 2021 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/2021-retrospect/</guid><description>&lt;p&gt;四天前是冬至日，北半球的昼夜等分点，意味着接下来朝着不断延伸的白昼行进，那时候我还构思着怎么来完美地跨年，犒劳一下辛苦了一年的自己。然而，两天后西安宣布紧急封城，一下子我们又回到了2020年年初。但是仔细想想，我个人受到封城的影响真的是微不足道，最多也就是得自己买菜做饭，暂时搁置健身计划等。有时候觉得自己挺幸运地选择了一个和实体经济不密切相关的行业，借助于现代科技只需要电脑与网络就可以完成工作，所以有效地规避了疫情反覆带来的影响。反过来讲，也正是因为这点儿幸运，所以不得不更「卷」。&lt;/p&gt;
&lt;p&gt;言归正传，这是一篇正经的年终总结，但是我想水一水，因为重要的事情基本都囊括在上一篇&lt;a href="https://morvencao.github.io/posts/half-year-of-2021/"&gt;博客&lt;/a&gt;中了。从10月到12月也没什么大事件发生，唯一值得记录的是&lt;a href="https://www.bagevent.com/event/7680821"&gt;Kubeconf China 2021&lt;/a&gt;。和去年一样，因为疫情原因，还是通过线上方式举行。这次我们在虚拟的展台介绍了&lt;a href="https://open-cluster-management.io/"&gt;OCM&lt;/a&gt;以及基于OCM的多云网络解决方案。连续两年的线上举行方式，感觉大家的热情都有所削减，也可能是K8s已经到了创新的倦怠期，很少有哪个主题能够吸引大批的目光。不管怎样，我们相信下个KubeConf会有更多关于多云的主题出现。&lt;/p&gt;
&lt;p&gt;接下来的一年，大方向还是基于OCM做更多的尝试。其中一条线是关于多云的可观测性的演化，从基础架构层的观测扩展到用户程序的观测；另一条线是基于多云的服务网格尝试，虽然现在有好几种不同方案备选，但是最终的成型方案还有待商榷。当然这两条线并不是完全独立的，因为服务网格很重要的一个功能就是提供服务的可观测性。&lt;/p&gt;</description></item><item><title>这半年</title><link>https://morvencao.github.io/posts/half-year-of-2021/</link><pubDate>Fri, 01 Oct 2021 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/half-year-of-2021/</guid><description>&lt;p&gt;好久没更新博客了，准确的说上次更新还是在半年多之前换工作之际。&lt;/p&gt;
&lt;p&gt;半年前我的WX通讯录里面增加了很多同行，虽然其中大部分人都是在Istio社区认识的，但在那之前也没有真正互动过。回头想想找工作的那段时间，自由却不自在，好在很多同事、同行给了很多机会，使我顺利找到新工作。之前也一直没时间感谢认可我的同事、同行以及各厂的HR，借此由衷感谢特别是鹅厂与菊厂的大大们赏赐的机会，期待以后能够一起共事。当然，我也特别感激我现在的东家，红帽(Red Hat)给的机会。之所以选择红帽的原因也很简单，熟悉的团队加上熟悉的产品，以至于入职第一天开始提交了code&amp;hellip;&lt;/p&gt;
&lt;p&gt;展开来说，红帽到底是一家怎样的公司，估计很多人知道红帽玩开源很溜。没错，基本上红帽产品的商业模式都是基于开源社区的，当然我们组的产品也不例外，完全开源在Github上面，有兴趣的朋友们请前往&lt;a href="https://github.com/open-cluster-management/"&gt;传送门&lt;/a&gt;。借此，也宣传一下&lt;a href="https://github.com/open-cluster-management-io/"&gt;OCM(Open Cluster Management)&lt;/a&gt;项目，该项目是我们这条产品线RHACM（Red Hat Advanced Cluster Management for Kubernetes）的开源精简版本，目标是构建一个完全开源并且第三方中立的多云管理平台，我们也正在申请将它贡献到CNCF Sandbox里面。话说回来，其实红帽不止在源代码级别是“开放”的，更重要的是这种开放的文化其实是渗透到公司的管理文化中，任何人都可以参与到改进他认为不合适或者不正确的公司政策中去。总体来说，红帽是一家工程师文化特别浓重的公司，这也是经常能在红帽遇到业界大佬的缘由之一吧。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/10/01/f3B8JCZcrwTEasL.jpg" alt="redhat_life.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;那我在其中参与什么样的角色呢？我目前所在的组主要是集中在基于ACM的可观测(Observability)的相关工作，所以这半年其实还是学习到不少监控相关的知识，当然不止是Prometheus+Grafana那一套比较可靠但扩展性不足的方案，我们目前的产品更多围绕Thanos(不是灭霸哦[doge][doge])来构建，后面我们会基于多云的案例扩展更多的功能。上面说的是我目前的主线任务，除此之外我还开了一个副本任务，多云环境中的服务网格。在这方面，红帽多少还是有一些落后，不过未来应该会很快赶上业界的脚步。&lt;/p&gt;
&lt;p&gt;关于这半年的工作和生活的变化，简单聊几句吧。首先，我开始尝试了站立办公，实际上我目前站着工作的时间会和坐着的时间基本上是1比1左右，以前对于站立办公不怎么感冒，自从体验了公司的升降桌之后，发现“真香”。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/10/01/KN8H4B7IGz1qb5S.jpg" alt="rh_desktop.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;另外，还有一个变化是开始了有规律的健身，基本上一周2-3次，有氧与无氧结合，坚持了有三个月左右了吧，计划后面增加无氧的比例。效果还是有的吧！？为了配合健身，通勤方式也发生了变化，我的“死飞”终于上路了，建议还是装个手刹，另外也可以像我一样，错峰出行，保证安全第一。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/10/01/If3uc8RBpJ2Uiag.jpg" alt="rh_bike.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;以上就是我这半年的流水账了。至于未来，还是有一大推事情需要推进，flag就不立了，在没有落地之前的想法都是扯淡。&lt;/p&gt;</description></item><item><title>Farewell, IBM</title><link>https://morvencao.github.io/posts/farewell-ibm/</link><pubDate>Fri, 26 Feb 2021 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/farewell-ibm/</guid><description>&lt;p&gt;Farewell, IBM&lt;/p&gt;
&lt;p&gt;今天是我在IBM的最后一个工作日，上午到公司后整理了私人物品，拿了离职证明，中午和几个同事吃了散伙饭，本来也想着轻轻松松度过在IBM的最后一天，但在归还笔记本和工牌的那一刻还是有些伤感。可能每一个从IBM“毕业”的人都和我有一样的感觉吧，就是那种在职时会花式吐槽它，但真到离开时才发现它真是良心公司。&lt;/p&gt;
&lt;p&gt;我与IBM的缘分从大三就开始了，记得我的第一份实习就是从IBM上海研发中心开始，那时候自己刚从校园走出来，什么都不懂，但当时的mentor还是会耐心地给予指导，同事们也会在生活上尽可能地给我提供帮助，从来不会因为自己是实习生而差别对待，整个工作氛围轻松和谐。对IBM的好感从那时候就开始了，以至于后来研究生期间再去其他互联网公司实习再也没有找到这种感觉。&lt;/p&gt;
&lt;p&gt;正式加入IBM也算是机缘巧合，15年那个夏天我研究生毕业后直接作为pure blue加入IBM的大家庭。从刚入职时的那股兴奋劲儿到后来EPH培训的大开眼界，从科技二路中清大厦到锦业一路软件园，从高性能计算LSF产品到云原生全栈升式ICP……将近六年的时间留下了太多回忆，伴随着这些回忆的便是成长。在IBM的这几年我学习到了以前没有机会接触的东西，特别是在云原生和开源领域，无论从广度还是深度方面来说，我主观上认为自己都有长足的进步。这一点儿真的非常感谢我的老板和mentor，感谢他们提供的宝贵机会。&lt;/p&gt;
&lt;p&gt;天下没有不散的筵席，纵有再多眷恋，仍须策马扬鞭。现在我更愿意以成年人的方式结束我在IBM的六年，毕竟我的目标是星辰大海，LOL～&lt;/p&gt;
&lt;p&gt;在此，也祝愿我的老东家乘风破浪，再上“云端”！&lt;/p&gt;
&lt;p&gt;青山不改，绿水长流，他日江湖再见!&lt;/p&gt;</description></item><item><title>复盘 kubernetes client-go</title><link>https://morvencao.github.io/posts/k8s-client-go/</link><pubDate>Wed, 24 Feb 2021 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/k8s-client-go/</guid><description>&lt;p&gt;其实 Kubernetes 官方提供了&lt;a href="https://kubernetes.io/docs/reference/using-api/client-libraries/"&gt;各种语言的客户端类库&lt;/a&gt;，但是由于 golang 在云原生领域的先天优势，&lt;a href="https://github.com/kubernetes/client-go/"&gt;client-go&lt;/a&gt; 相对来说是使用较多的类库。但是要把 client-go 在一片天文章中讲清楚实属困难，所以本文不可能覆盖到所有的细节，尽量追求将主要框架描述清楚，并且配合代码片段探讨 client-go 常用的接口以及使用方法。&lt;/p&gt;
&lt;h2 id="client-go-主体框架"&gt;client-go 主体框架&lt;/h2&gt;
&lt;p&gt;其实要了解 client-go 的主体功能模块以及各个模块的功能，对于 &lt;a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md"&gt;Kubernetes API 的设计理念&lt;/a&gt;是基础，特别是对于 Kubernetes API 分组与版本转换。还不清楚的同学强烈建议先去阅读我之前的笔记：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/k8s-api-1/"&gt;初识 Kubernetes API 的组织结构&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/k8s-api-2/"&gt;深入 kubernetes API 的源码实现&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们先来瞄一眼 client-go 的主要代码结构，我会给出各个主要部分的核心功能让大家有一个感性的认识：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ tree -L &lt;span style="color:#0000cf;font-weight:bold"&gt;2&lt;/span&gt; client-go
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;client-go
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── discovery &lt;span style="color:#8f5902;font-style:italic"&gt;# 包含dicoveryClient，用于发现k8s所支持GVR(Group/Version,/Resource),&amp;#39;kubectl api-resources&amp;#39;命令正是使用它来列出cluster中的各种资源。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── dynamic &lt;span style="color:#8f5902;font-style:italic"&gt;# 包含dynamicClient，它封装了 RESTClient，可以动态的指定api资源的GVR，结合unstructured.Unstructured类型来访问各种类型的k8s资源(如: Pod,Deploy...)，也可以访问用户自定义资源(CRD)。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── informers &lt;span style="color:#8f5902;font-style:italic"&gt;# 为了减少client对于apiserver的频繁访问，需要informer来缓存apiserver中资源，只有当api资源对象发生变化的时候才收到通知。每种api资源会有自己的informer实现，也是按照api分组与版本来区分。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── kubernetes &lt;span style="color:#8f5902;font-style:italic"&gt;# 主要定义ClientSet，它也对restClient进行了封装，并且包含对各种k8s资源和版本的管理方法。每个api资源有单独的client，而ClientSet则是多个客户端的集合。ClientSet以及每种k8s内置资源的client的所有请求最终还是由restClient发出的；在typed目录包括具体每种k8s内置资源的client实现，也是按照api分组与版本来区分。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── clientset.go
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── typed
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── listers &lt;span style="color:#8f5902;font-style:italic"&gt;# 包含各种k8s内置资源的只读客户端。每种lister都有Get()和List()方法，并且结果都是从缓存中读取的。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── rest &lt;span style="color:#8f5902;font-style:italic"&gt;# 包含真正给apiserver发请求的client，实现了Restful的API，同时支持Protobuf和JSON格式数据。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── scale &lt;span style="color:#8f5902;font-style:italic"&gt;# 只要包含scalClient用于Deploy, RS等的扩/缩容。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── tools &lt;span style="color:#8f5902;font-style:italic"&gt;# 各种类型的工具包，常见的比如获取kubeconfig的方法，以SharedInformer、Reflector、DealtFIFO和Indexer等工具，这些工具主要用于实现client查询和缓存机制，减轻apiserver的负载等。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: 为了简化，不重要的文件与目录没有列出来。&lt;/p&gt;</description></item><item><title>深入 kubernetes API 的源码实现</title><link>https://morvencao.github.io/posts/k8s-api-2/</link><pubDate>Tue, 23 Feb 2021 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/k8s-api-2/</guid><description>&lt;p&gt;估计很多同学像我一样，第一次打开 Github 上面 kubernetes 项目源码的时候就被各种仓库搞晕了，kuberentes 组织下有很多个仓库，包括 kubernetes、client-go、api、apimachinery 等，该从哪儿仓库看起？kubernetes 仓库应该是 kubernetes 项目的核心仓库，它包含 kubernetes 控制平面核心组件的源码；client-go 从名字也不难看出是操作 kubernetes API 的 go 语言客户端；api 与 apimachinery 应该是与 kubernetes API 相关的仓库，但它们俩为啥要分成两个不同的仓库？这些代码仓库之间如何交互？apimachinery仓库中还有 api、apis 两个包，里面定义了各种复杂的接口与实现，清楚这些复杂接口对于扩展 kubernetes API 大有裨益。所以，这篇文章就重点关注 api 与 apimachinery 这两个仓库。&lt;/p&gt;
&lt;h2 id="api"&gt;api&lt;/h2&gt;
&lt;p&gt;我们知道 kubernetes 官方提供了多种多样的的 API 资源类型，它们被定义在 k8s.io/api 这个仓库中，作为 kubernetes API 定义的规范地址。实际上，最开始这个仓库只是 kubernetes 核心仓库的一部分，后来 kubernetes API 被越来越多的其他仓库使用，例如 k8s.io/client-go、k8s.io/apimachinery、k8s.io/apiserver 等，为了避免交叉依赖，所以才把 api 拿出来作为单独的仓库。k8s.io/api 仓库是只读仓库，所有代码都同步自 &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api"&gt;https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api&lt;/a&gt; 核心仓库。&lt;/p&gt;
&lt;p&gt;在 k8s.io/api 仓库定义的kubernetes API 规范中，Pod 作为最基础的资源类型，一个典型的 YAML 形式的序列化 pod 对象如下所示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;v1&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;kind&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;Pod&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;metadata&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;name&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;webserver&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;labels&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;app&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;webserver&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;spec&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;containers&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;- &lt;span style="color:#204a87;font-weight:bold"&gt;name&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;webserver&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;image&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;nginx&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;ports&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;- &lt;span style="color:#204a87;font-weight:bold"&gt;containerPort&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;:&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#0000cf;font-weight:bold"&gt;80&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从编程的角度来看，序列化的 pod 对象最终会被发送到 API-Server 并解码为 Pod 类型的 Go 结构体，同时 YAML 中的各个字段会被赋值给该 Go 结构体。那么，Pod 类型在 Go 语言结构体中是怎么定义的呢？&lt;/p&gt;</description></item><item><title>初识 Kubernetes API 的组织结构</title><link>https://morvencao.github.io/posts/k8s-api-1/</link><pubDate>Mon, 22 Feb 2021 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/k8s-api-1/</guid><description>&lt;p&gt;话说自己入坑云原生也有好几年了，但是对 kubernetes 基础认识却不够深，导致写代码的时候经常需要打开 godoc 或者 kubernetes 源码查看某个接口或者方法的定义。这种快餐式的消费代码方式可以解决常见的问题，但有时候却会被一个简单的问题困扰很久。究其原因，还是没有对 kubernetes 有比较系统的学习，特别对于 kubernetes API 的设计与原理没有较为深入的认识，这也是我们平时扩展 kubernetes 功能绕不开的话题。与此同时，这也是很难讲清楚的一个话题，是因为 kubernetes 经过多个版本的迭代功能已经趋于成熟与复杂，这一点也可以从 Github 平台 kubernetes 组织下的多个仓库也可以看得出来，相信很多人和我一样，看到 kubernetes、client-go、api、apimachinery 等仓库就不知道如何下手。事实上，从 API 入手是比较简单的做法，特别是我们对于 kubernetes 核心组件的功能有了一定的了解之后。&lt;/p&gt;
&lt;p&gt;接下来的几篇笔记，我将由浅入深地学习 kubernetes API 的设计以及背后的原理。我的计划是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初识 kubernetes API 的组织结构&lt;/li&gt;
&lt;li&gt;深入 kubernetes API 的源码实现&lt;/li&gt;
&lt;li&gt;扩展 kubernetes API 的典型方式&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;废话不多说，我们先来认识一下 kubernetes API 的基础结构以及背后的设计原理。&lt;/p&gt;
&lt;h2 id="api-server"&gt;API-Server&lt;/h2&gt;
&lt;p&gt;我们知道 kubernetes 控制层面的核心组件包括 API-Server、 Controller Manager、Scheduler，其中 API-Server 对内与分布式存储系统 etcd 交互实现 kubernetes 资源（例如 pod、namespace、configMap、service 等）的持久化，对外提供通过 RESTFul 的形式提供 &lt;a href="https://kubernetes.io/docs/concepts/overview/kubernetes-api/"&gt;kubernetes API&lt;/a&gt; 的访问接口，除此之外，它还负责 API 请求的&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/"&gt;认证(authN)&lt;/a&gt;、&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/"&gt;授权(authZ)&lt;/a&gt;以及&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/"&gt;验证&lt;/a&gt;。刚提到的“对外”是相对的概念，因为除了像 kubectl 之类的命令行工具之外，kubernetes 的其他组件也会通过各种客户端库来访问 kubernetes API，关于官方提供的各种客户端库请查看&lt;a href="https://kubernetes.io/docs/reference/using-api/client-libraries/"&gt; client-libraries 列表&lt;/a&gt;，其中最典型的是 Go 语言的客户端库 &lt;a href="https://github.com/kubernetes/client-go/"&gt;client-go&lt;/a&gt;。&lt;/p&gt;</description></item><item><title>使用 Go 从零开始实现 CNI</title><link>https://morvencao.github.io/posts/create-your-own-cni-with-golang/</link><pubDate>Thu, 11 Feb 2021 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/create-your-own-cni-with-golang/</guid><description>&lt;p&gt;对于很多刚入坑云原生技术栈的同学来说，容器网络与 Kubernetes 网络一直很“神秘”，也是很多人容器技术上升曲线的瓶颈，但它也是我们深入走进云原生世界绕不过的话题。要彻底地搞清楚容器网络与 Kubernetes 网络，需要了解很多底层的网络概念，如 OSI 七层模型、Linux 网络栈、虚拟网络设备以及 iptables 等。&lt;/p&gt;
&lt;p&gt;早在去年年初，我就对容器网络涉及到的基础概念做了一些列总结，小伙伴们请按需食用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/networking-1-pkg-snd-rcv/"&gt;Linux 数据包的接收与发送过程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/networking-2-virtual-devices/"&gt;Linux 虚拟网络设备&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/from-container-to-pod/"&gt;从 container 到 pod&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/networking-4-docker-sigle-host/"&gt;容器网络(一)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/networking-5-docker-multi-hosts/"&gt;容器网络(二)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://morvencao.github.io/posts/networking-6-k8s-summary/"&gt;浅聊 Kubernetes 网络模型&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是，这些总结还是停留在理论层面，如果不动手实践一下，这些知识的价值会大打折扣，而且很多技术只有在实际使用的时候我们才会发现理论和实践之间巨大的“鸿沟”。所以我其实也一直想着如何使用熟悉的语言来练手这些网络知识，但是因为事情太多而一拖再拖，直到上周我在查看一个 CNI Bug 的时候又快速过了一下&lt;a href="https://github.com/containernetworking/cni/blob/master/SPEC.md"&gt;官方 CNI 规范&lt;/a&gt;，这才有了使用 Go 语言从零开始实现一个 CNI 插件的契机。我的计划是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简单回顾一下容器网络模型以及解读 Kubernetes 官方出品的 CNI 规范；&lt;/li&gt;
&lt;li&gt;使用 Go 语言编写简单的 CNI 插件来实现 Kubernetes overlay 网络，功能包括 pod IP 地址的管理以及容器网络的配置；&lt;/li&gt;
&lt;li&gt;编写 CNI 部署工具并在 Kubernetes 集群里面部署和测试我们的 CNI；&lt;/li&gt;
&lt;li&gt;讨论潜在的问题以及未来的解决方案；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="kubernetes-网络模型"&gt;Kubernetes 网络模型&lt;/h2&gt;
&lt;p&gt;不管是容器网络还是 Kubernetes 网络都需要解决以下两个核心问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;容器/Pod IP 地址的管理&lt;/li&gt;
&lt;li&gt;容器/Pod 之间的相互通信&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;容器/Pod IP 地址的管理包括容器 IP 地址的分配与回收，而容器/Pod 之间的相互通信包括同一主机的容器/Pod 之间和跨主机的容器/Pod 之间通信两种场景。这两个问题也不能完全分开来看，因为不同的解决方案往往要同时考虑以上两点。对于同一主机的容器/Pod 之间的通信来说实现相对容易，实际的挑战在于，不同容器/Pod 完全可能分布在不同的集群节点上，如何实现跨主机节点的通信不是一件容易的事情。&lt;/p&gt;</description></item><item><title>请回答「2020」</title><link>https://morvencao.github.io/posts/the-answer-of-2020/</link><pubDate>Thu, 31 Dec 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/the-answer-of-2020/</guid><description>&lt;p&gt;一转眼2020年就要过去了，依然记得12月初有人在朋友圈里发了这样一条状态更新：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“距离迈进2021剩下不到一个月！”&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;我当时在想，大家应该都对于2020年都已经不再抱有希望，不再发“12月请善待我”之类的状态，而是直接呼唤“2021年”。&lt;/p&gt;
&lt;p&gt;不用多想，2020年大事件的密度应该是前所未有的，我们的感官每天都被这这爆炸的信息量刷新着，如此魔幻的2020让我们见证了多少历史上的第一次。如果要为2020年做一个时间胶囊，里面应该放些什么？口罩？健康码？新冠疫苗？科比的球衣？打工人？月球土壤？&lt;/p&gt;
&lt;p&gt;真的要我回答2020的话，我的关键字会是「波折」。&lt;/p&gt;
&lt;p&gt;早在1月份的时候，在美国的大舅就提醒我们要注意武汉流感的发展，当时觉得也没有什么，反正对于在家工作这件事情也不排斥，甚至对自己的“独处”经验也是十分自信，对于可以自由支配的时间从来都是安排得明明白白。没想到一语成鉴，疫情的发展远远超出预期。到了3月份初，蔬菜水果也出现了供不应求的状况，每次都需要预约抢购，口罩价格不止水涨船高，关键是很难抢购得到，于是3月份出行受到了严重的限制。无奈最终只能求助于大舅从美国邮寄一批口罩回来，然而，等到口罩寄到国内的时候已经到了4月份，那是的疫情已经有所缓和。&lt;/p&gt;
&lt;img src="https://i.loli.net/2020/12/31/2Ebg7peRy9KfPzN.jpg" style="width:60%;"/&gt;
&lt;p&gt;也正是从3月份开始，不争气的智齿开始发炎，情况逐渐开始恶化，每天晚上都感觉有个电钻一直在钻腮帮子，太阳穴扯着神经痛，疼到无法集中化注意力思考，当时的精神状态蔫到极点。但是当时因为疫情原因不方便跑去医院，于是咬着牙坚持到了6月，医生看到拍的片子之后都觉得难以相信我坚持了那么久，最终把掉智齿也就用了不到5分钟（介于画面过于血腥，图片就不放出来了），但是整个人终于解脱了出来，瞬间感觉风轻云快。&lt;/p&gt;
&lt;p&gt;然而，就当我以为这一切就像疫情开始好转的时候，没想到又被现实的巨浪无情地拍打。拔过智齿之后本想休几天假，不巧的是，时间点正好赶上另外一个产品组 Q2 的发布。这里有些背景需要解释一下，我们组所有云原生产品在 Q1 就率先完成了全面拥抱 &lt;a href="https://coreos.com/operators/"&gt;Operator&lt;/a&gt; 的部署安装模式，也开始使用 &lt;a href="https://olm.operatorframework.io/"&gt;OLM(Operator Lifecycle Manager)&lt;/a&gt; 与我们自研的 &lt;a href="https://github.com/IBM/operand-deployment-lifecycle-manager"&gt;ODLM(Operand Deployment Lifecycle Manager)&lt;/a&gt; 来管理所有云原生产品的生命周期。这是一个非常重要的新特性，这意味着所有依赖于我们组云原生产品的上层产品都必须遵循相同的模式。于是我被临时抽调过去辅助他们完成 Q2 的发布版本。本来以为不会太艰难，但是他们产品组的混乱程度远超我的预期，6月份的产品发布被拖到7月底。在这期间打破了我在公司期间最长的加班记录，产品 upload 的前几天我后背开始出现过敏症状，我咬着牙坚持到了产品正式 GA 后的第一个周末去医院检查，才发现自己的了过敏性皮炎。&lt;/p&gt;
&lt;p&gt;说实话，我此前一直对于自己的身体状态很有自信，周末高强度的打几个小时的球都轻轻松松，每天的运动量不够都不会上床休息，虽然偶尔饮食不规律，但从来没有出现过如此疲惫不堪的状况。现在，我要开始认真思考如何“养生”，到了这个年纪，是不是喝啤酒、吃冰淇淋都想放两粒枸杞?&lt;/p&gt;
&lt;p&gt;回到工作，今年能够参与开源社区的时间明显减少了很多，这从我的 Github 提交历史就可以看出来：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2020/12/31/nEvU1ODHe2WxI4N.png" alt="gitcommit-2020.png"&gt;&lt;/p&gt;
&lt;p&gt;主要原因是公司产品策略的变化，我自己也要跟着调整，以前注重的开源方向被挤压得基本没有了空间。这直接导致我在 istio 社区的活跃度明显降低，去年基本可以全勤参加 istio 社区 &lt;a href="https://github.com/istio/community/blob/master/WORKING-GROUPS.md"&gt;ENV WG&lt;/a&gt; 的夏令时周会，今年参加的次数屈指可数。但是，我也花时间接触了不少开源项目，包括迁移并成规模地开始应用 k8s 原生 CICD 系统 &lt;a href="https://github.com/IBM/test-infra"&gt;Prow&lt;/a&gt;，接管了目前已经开源的全部 Common Services 项目与部分 IBM MCM 项目的构建流水线，同时增加了 Prow 多平台构建的能力使之适应 IBM 的云原生产品构建目标。另外，年中的时候因为另外一个产品组的需求，使得我有机会深入了解 &lt;a href="https://konghq.com/kong/"&gt;Kong API网关&lt;/a&gt;的功能与实现，还有它与其他 API 网关在用户场景上的区别，我甚至花了一段时间调研了 k8s 生态圈内的各种主流 API 网关的应用场景与实现细节，但是不久便被叫停。&lt;/p&gt;
&lt;p&gt;2020年，光怪陆离，有人见尘埃，有人见星辰，虽然这一年之于我始终都是乱糟糟的，自己好像什么也没做好，尤其在这兵荒马乱的12月。但是新的转机已经暂露头角，希望这2020年所有的鸡毛蒜皮换成2021年的风和日丽。最后，以一句话祝福自己能在2021年“牛”转乾坤：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;前路浩浩荡荡，万物皆可期待&lt;/p&gt;&lt;/blockquote&gt;</description></item><item><title>浅聊 Kubernetes 网络模型</title><link>https://morvencao.github.io/posts/networking-6-k8s-summary/</link><pubDate>Fri, 10 Apr 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/networking-6-k8s-summary/</guid><description>&lt;p&gt;通过前面的一些列笔记，我们对各种容器网络模型的实现原理已经有了基本的认识，然而真正将容器技术发扬光大的是 Kubernetes 容器编排平台。Kubernetes 通过整合规模庞大的容器实例形成集群，这些容器实例可能运行在异构的底层网络环境中，如何保证这些容器间的互通是实际生产环境中首要考虑的问题之一。&lt;/p&gt;
&lt;h2 id="kubernetes-网络基本要求"&gt;Kubernetes 网络基本要求&lt;/h2&gt;
&lt;p&gt;Kubernetes 对容器技术做了更多的抽象，其中最重要的一点是提出 pod 的概念，pod 是 Kubernetes 资源调度的基本单元，我们可以简单地认为 &lt;a href="https://morvencao.github.io/posts/from-container-to-pod/"&gt;pod 是容器的一种延伸扩展&lt;/a&gt;，从网络的角度来看，pod 必须满足以下条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每一个 pod 都有独特的 IP 地址，所有 pod 都在一个可以直接连通的、扁平的网络空间中；&lt;/li&gt;
&lt;li&gt;同一个 pod 内的所有容器共享同一个网络命名空间；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2020/02/06/qjRy3SpGvOfxWIA.png" alt="pod-netns.png"&gt;&lt;/p&gt;
&lt;p&gt;基于这样的基本要求，我们可以知道：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同一个 pod 内的所有容器之间共享端口，可直接通过 &lt;code&gt;localhost + port&lt;/code&gt; 来访问；&lt;/li&gt;
&lt;li&gt;由于每个 pod 有单独的 IP，所以不需要考虑容器端口与主机端口映射以及端口冲突问题；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;事实上，Kubernetes 进一步确定了对一个合格集群网络的基本要求：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;任意两个 pod 之间其实是可以直接通信的，无需显式地使用 NAT 进行地址的转换；&lt;/li&gt;
&lt;li&gt;任意集群节点与任意 pod 之间是可以直接通信的，无需使用明显的地址转换，反之亦然；&lt;/li&gt;
&lt;li&gt;任意 pod 看到自己的 IP 跟别人看见它所用的 IP 是一样的，中间不能经过地址转换；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;也就是说，必须同时满足以上三点的网络模型才能适用于 Kubernetes，事实上，在早期的 Kubernetes 中，并没有什么网络标准，只是提出了以上基本要求，只有满足这些要求的网络才可以部署 Kubernetes，基于这样的底层网络假设，Kubernetes 设计了&lt;code&gt;pod-deployment-service&lt;/code&gt; 的经典三层服务访问机制。直到1.1发布，Kubernetes 才开始采用全新的 &lt;a href="https://github.com/containernetworking/cni"&gt;CNI(Container Network Interface)&lt;/a&gt; 网络标准。&lt;/p&gt;
&lt;h2 id="cni"&gt;CNI&lt;/h2&gt;
&lt;p&gt;其实，我们在前面介绍容器网络的时候，就提到了 CNI 网络规范，CNI 相对于 &lt;a href="https://github.com/docker/libnetwork/blob/master/docs/design.md"&gt;CNM(Container Network Model)&lt;/a&gt; 对开发者的约束更少、更开放，不依赖于容器运行时。事实上，CNI 规范确实非常简单，详情请移步至：https://github.com/containernetworking/cni/blob/master/SPEC.md&lt;/p&gt;</description></item><item><title>容器网络(二)</title><link>https://morvencao.github.io/posts/networking-5-docker-multi-hosts/</link><pubDate>Fri, 20 Mar 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/networking-5-docker-multi-hosts/</guid><description>&lt;p&gt;上一篇笔记中我们介绍的 bridge 网络模型主要用于解决同一主机间的容器相互访问以及容器对外暴露服务的问题，并没有涉及到怎么解决跨主机的容器之间互相访问的问题。&lt;/p&gt;
&lt;p&gt;对于跨主机容器间的相互访问问题，我们能想到的最直观的解决方案就是直接使用宿主机网络，这时，容器完全复用复用宿主机的网络设备以及协议栈，容器 IP 就是主机 IP，这样，只要宿主机主机能通信，容器也就自然能通信。但是这样，为了暴露容器服务，每个容器需要占用宿主机上的一个端口，通过这个端口和外界通信。所以，就需要手动维护端口的分配，而且不能使不同的容器服务运行在一个端口上，正因为如此，这种容器网络模型很难被推广到生产环境。&lt;/p&gt;
&lt;p&gt;因此解决跨主机通信的可行方案主要是让容器配置与宿主机不一样的 IP 地址，往往是在现有二层或三层网络之上再构建起来一个独立的 overlay 网络，这个网络通常会有自己独立的 IP 地址空间、交换或者路由的实现。由于容器有自己独立配置的 IP 地址，underlay 平面的底层网络设备如交换机、路由器等完全不感知这些 IP 的存在，也就导致容器的 IP 不能直接路由出去实现跨主机通信。&lt;/p&gt;
&lt;p&gt;为了解决容器独立 IP 地址间的访问问题，主要有以下两个思路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;修改底层网络设备配置，加入容器网络 IP 地址的管理，修改路由器网关等，该方式主要和 SDN(Software define networking) 结合。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完全不修改底层网络设备配置，复用原有的 underlay 平面网络解决容器跨主机通信，主要有如下两种方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隧道传输(overlay)：将容器的数据包封装到宿主机网络的三层或者四层数据包中，然后使用宿主机的 IP 或者 TCP/UDP 传输到目标主机，目标主机拆包后再转发给目标容器。overlay 隧道传输常见方案包括 VxLAN、ipip 等，目前使用 Overlay 隧道传输技术的主流容器网络有 Flannel 等。&lt;/li&gt;
&lt;li&gt;修改主机路由：把容器网络加到宿主机网络的路由表中，把宿主机网络设备当作容器网关，通过路由规则转发到指定的主机，实现容器的三层互通。目前通过路由技术实现容器跨主机通信的网络如 Flannel host-gw、Calico 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="技术术语"&gt;技术术语&lt;/h2&gt;
&lt;p&gt;在开始之前，我们总结一些在容器网络介绍文章里面经常看到各种技术术语：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IPAM(IP Address Management)：即 IP 地址管理。IPAM 并不是容器时代特有的词汇，传统的标准网络协议比如 DHCP 其实也是一种 IPAM，负责从 MAC 地址分发 IP 地址；但是到了容器时代我们提到 IPAM，我们特指为每一个容器实例分配和回收 IP 地址，保证一个集群里面的所有容器都分配全局唯一的 IP 地址；主流的做法包括：基于 CIDR 的 IP 地址段分配地或精确为每一个容器分配 IP；&lt;/li&gt;
&lt;li&gt;overlay：在容器时代，就是在主机现有二层（数据链路层）或三层（IP 网络层）基础之上再构建起来一个独立的网络平面，这个 overlay 网络通常会有自己独立的 IP 地址空间、交换或者路由的实现；&lt;/li&gt;
&lt;li&gt;IPIP：一种基于 Linux 网络设备 TUN 实现的隧道协议，允许将三层（IP）网络包封装在另外一个三层网络包之上发送和接收，详情请看&lt;a href="https://morvencao.github.io/posts/networking-3-ipip/"&gt;揭秘 IPIP 隧道&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;IPSec：跟 IPIP 隧道协议类似，是一种点对点的加密通信协议，一般会用到 overlay 网络的数据隧道里；&lt;/li&gt;
&lt;li&gt;VxLAN：最主要是解决 VLAN 支持虚拟网络数量（4096）过少的问题而由 VMware、Cisco、RedHat 等联合提出的解决方案；VxLAN 可以支持在一个 VPC(Virtual Private Cloud) 划分多达1600万个虚拟网络；&lt;/li&gt;
&lt;li&gt;BGP：主干网自治网络的路由协议，当代的互联网由很多小的 AS(Autonomous system)自治网络构成，自治网络之间的三层路由是由 BGP 协议实现的，简单来说，通过 BGP 协议 AS 告诉其他 AS 自己子网里都包括哪些 IP 地址段，自己的 AS 编号以及一些其他的信息；&lt;/li&gt;
&lt;li&gt;SDN(Software-Defined Networking)：一种广义的概念，通过软件方式快速配置网络，往往包括一个中央控制层来集中配置底层基础网络设施；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="docker-原生-overlay"&gt;docker 原生 overlay&lt;/h2&gt;
&lt;p&gt;docker 原生支持 overlay 网络用来解决容器间的跨主机通信问题，事实上，对于 docker 原生支持的 overlay 网络，Laurent Bernaille 在 DockerCon 2017上详细剖析了它的实现原理，甚至还有从头开始一步步实现该网络模型的实践教程，这三篇文章为：&lt;/p&gt;</description></item><item><title>容器网络(一)</title><link>https://morvencao.github.io/posts/networking-4-docker-sigle-host/</link><pubDate>Sun, 08 Mar 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/networking-4-docker-sigle-host/</guid><description>&lt;p&gt;容器网络需要解决的两大核心问题是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;容器 IP 地址的管理&lt;/li&gt;
&lt;li&gt;容器之间的相互通信&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中，容器 IP 地址的管理包括容器 IP 地址的分配与回收，而容器之间的相互通信包括同一主机容器之间和跨主机容器之间通信两种场景。这两个问题也不能完全分开来看，因为不同的解决方案往往要同时考虑以上两点。&lt;/p&gt;
&lt;p&gt;容器网络的发展已经相对成熟，这篇笔记先对主流容器网络模型做一些概述，然后将进一步对典型的容器网络模型展开实践。&lt;/p&gt;
&lt;h2 id="cnm-vs-cni"&gt;CNM vs CNI&lt;/h2&gt;
&lt;p&gt;关于容器网络，docker 与 kubernetes 分别提出了不同的规范标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;docker 采用的 &lt;a href="https://github.com/docker/libnetwork/blob/master/docs/design.md"&gt;CNM&lt;/a&gt;(Container Network Model)&lt;/li&gt;
&lt;li&gt;kubernetes 支持的 &lt;a href="https://github.com/containernetworking/cni"&gt;CNI&lt;/a&gt;模型(Container Network Interface)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CNM 基于 &lt;a href="https://github.com/docker/libnetwork"&gt;libnetwork&lt;/a&gt;，是 docker 内置的模型规范，它的总体架构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2020/01/31/lWUKNw5Tbp3cArC.jpg" alt="cnm-model.jpg"&gt;&lt;/p&gt;
&lt;p&gt;可以看到，CNM 规范主要定义了以下三个组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sandbox: 每个 Sandbox 包一个容器网络栈(network stack)的配置：容器的网口、路由表和 DNS 设置等，Sanbox 可以通过 Linux 网络命名空间 netns 来实现；&lt;/li&gt;
&lt;li&gt;Endpoint: 每个 Sandbox 通过 Endpoint 加入到一个 Network 里，Endpoint 可以通过 Linux 虚拟网络设备 veth 对来实现；&lt;/li&gt;
&lt;li&gt;Network: 一组能相互直接通信的 Endpoint，Network 可以通过 Linux网桥设备 bridge 或 VLAN 等实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以看到，底层实现原理还是我们之前介绍过的 Linux 虚拟网络设备、网络命名空间等。CNM 规范的典型场景是这样的：用户可以创建一个或多个 Network，一个容器 Sandbox 可以通过 Endpoint 加入到一个或多个 Network，同一个 Network 中容器 Sanbox 可以通信，不同 Network 中的容器 Sandbox 隔离。这样就可以实现从容器与网络的解耦，也就是锁，在创建容器之前，可以先创建网络，然后决定让容器加入哪个网络。&lt;/p&gt;</description></item><item><title>揭秘 IPIP 隧道</title><link>https://morvencao.github.io/posts/networking-3-ipip/</link><pubDate>Wed, 12 Feb 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/networking-3-ipip/</guid><description>&lt;p&gt;上一篇笔记中，我们在介绍 Linux 网络设备的时候简单地看到了一种通过 TUN/TAP 设备来实现 VPN 的方式，但是并没有实践 TUN/TAP 虚拟网络设备在 Linux 中具体是怎么发挥功能的。这篇笔记我们就来看看在云计算领域中如何基于TUN设备实现 IPIP 隧道。&lt;/p&gt;
&lt;h2 id="ipip-隧道"&gt;IPIP 隧道&lt;/h2&gt;
&lt;p&gt;我们在之前的笔记中也提到了，TUN 网络设备能将三层（IP 网络数据包）数据包封装在另外一个三层数据包之中，这样通过 TUN 设备发送出来的数据包会像下面这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MAC: xx:xx:xx:xx:xx:xx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IP Header: &amp;lt;new destination IP&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IP Body:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IP: &amp;lt;original destination IP&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TCP: stuff
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HTTP: stuff
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这就是典型的 IPIP 数据包的结构。Linux 原生支持好几种不同的 IPIP 隧道类型，但都依赖于 TUN 网络设备，我们可以通过命令 &lt;code&gt;ip tunnel help&lt;/code&gt; 来查看 IPIP 隧道的相关类型以及支持的操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# ip tunnel help&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Usage: ip tunnel &lt;span style="color:#ce5c00;font-weight:bold"&gt;{&lt;/span&gt; add &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; change &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; del &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; show &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; prl &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; 6rd &lt;span style="color:#ce5c00;font-weight:bold"&gt;}&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; NAME &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; mode &lt;span style="color:#ce5c00;font-weight:bold"&gt;{&lt;/span&gt; ipip &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; gre &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; sit &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; isatap &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; vti &lt;span style="color:#ce5c00;font-weight:bold"&gt;}&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; remote ADDR &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#204a87"&gt;local&lt;/span&gt; ADDR &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt;i&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;o&lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;seq &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt;i&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;o&lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;key KEY &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt;i&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;o&lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;csum &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; prl-default ADDR &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; prl-nodefault ADDR &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; prl-delete ADDR &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; 6rd-prefix ADDR &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; 6rd-relay_prefix ADDR &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; 6rd-reset &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; ttl TTL &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; tos TOS &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt;no&lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;pmtudisc &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; dev PHYS_DEV &lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Where: NAME :&lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; STRING
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ADDR :&lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;{&lt;/span&gt; IP_ADDRESS &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; any &lt;span style="color:#ce5c00;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TOS :&lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;{&lt;/span&gt; STRING &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; 00..ff &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; inherit &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; inherit/STRING &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; inherit/00..ff &lt;span style="color:#ce5c00;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TTL :&lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;{&lt;/span&gt; 1..255 &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; inherit &lt;span style="color:#ce5c00;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; KEY :&lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;{&lt;/span&gt; DOTTED_QUAD &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; NUMBER &lt;span style="color:#ce5c00;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中 &lt;code&gt;mode&lt;/code&gt; 代表不同的 IPIP 隧道类型，Linux 原生共支持5种 IPIP 隧道：&lt;/p&gt;</description></item><item><title>从 container 到 pod</title><link>https://morvencao.github.io/posts/from-container-to-pod/</link><pubDate>Mon, 03 Feb 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/from-container-to-pod/</guid><description>&lt;p&gt;很多人应该像我一样，第一次接触 docker 的概念，都会见到或者听过下面这句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;docker 技术比虚拟机技术更为轻便、快捷，docker 容器本质上是进程&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;甚至我们或多或少都在潜移默化中接受了 container 实现是基于 linux 内核中 namespace 和 cgroup 这两个非常重要的特性。那么，namespace 和 cgroup 到底是怎么来隔离 docker 容器进程的呢？今天我们就来一探究竟。&lt;/p&gt;
&lt;h2 id="container"&gt;container&lt;/h2&gt;
&lt;p&gt;在了解 docker 容器进程怎么隔离之前，我们先来看看 linux 内核中的 namespace 和 cgroup 到底是什么。&lt;/p&gt;
&lt;h3 id="namespace"&gt;namespace&lt;/h3&gt;
&lt;p&gt;如果让我们自己实现一种类似于 vm 一样的虚拟技术，我们首先会想到的是怎么解决每个 vm 的进程与宿主机进程的隔离问题，防止进程权限“逃逸”。2008年发布的 linux 内核v2.6.24带来的命名空间（namespace）特性使得我们可以对 linux 做各种各样的隔离。&lt;/p&gt;
&lt;p&gt;熟悉 chroot 命令的同学都应该大体能猜到 linux 内核中的 namespace 是如何发挥作用的，在 linux 系统中，系统默认的目录结构都是以根目录 &lt;code&gt;/&lt;/code&gt; 开始的，chroot 命令用来以指定的位置作为根目录来运行指令。与此类似，了解 &lt;a href="https://morven.me/notes/the_knowledge_of_linux/"&gt;linux 启动流程&lt;/a&gt;的同学都知道在 linux 启动的时候又一个 pid 为1的 init 进程，其他所有的进程都由此进程衍生而来，init 和其他衍生而来的进程形成以 init 进程为根节点树状结构，在同一个进程树里的进程只要有足够的权限就可以审查甚至终止其他进程。这样，显然会带来安全性问题。而 pid namespace（linux namespace 的一种）允许我们创建单独的进程树，新进程树里面有自己的 pid 为1的进程，该进程一般为创建新进程树的时候指定。pid namespace 使得不同进程树里面的进程不能相互直接访问，提供了进程间的隔离，甚至可以创建嵌套的进程树：&lt;/p&gt;</description></item><item><title>Linux 虚拟网络设备</title><link>https://morvencao.github.io/posts/networking-2-virtual-devices/</link><pubDate>Thu, 30 Jan 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/networking-2-virtual-devices/</guid><description>&lt;p&gt;随着容器逐步取代虚拟机，成为现在云基础架构的标准，这些容器的网络管理模块都离不开 Linux 虚拟网络设备。事实上，了解常用的 Linux 虚拟网络设备对于我们理解容器网络以及其他依赖于容器的基础网络架构实现都大有裨益。现在开始，我们就来看看常见的 Linux 虚拟网络设备有哪些以及它们的典型使用场景。&lt;/p&gt;
&lt;h2 id="虚拟网络设备"&gt;虚拟网络设备&lt;/h2&gt;
&lt;p&gt;通过上一篇笔记我们也知道，网络设备的驱动程序并不直接与内核中的协议栈交互，而是通过内核的网络设备管理模块作为中间桥梁。这样做的好处是，驱动程序不需要了解网络协议栈的细节，协议栈也不需要针对特定驱动处理数据包。&lt;/p&gt;
&lt;p&gt;对于内核网络设备管理模块来说，虚拟设备和物理设备没有区别，都是网络设备，都能配置 IP 地址，甚至从逻辑上来看，虚拟网络设备和物理网络设备都类似于管道，从任意一端接收到的数据将从另外一端发送出去。比如物理网卡的两端分别是协议栈与外面的物理网络，从外面物理网络接收到的数据包会转发给协议栈，相反，应用程序通过协议栈发送过来的数据包会通过物理网卡发送到外面的物理网络。但是对于具体将数据包发送到哪里，怎么发送，不同的网络设备有不同的驱动实现，与内核设备管理模块以及协议栈没关系。&lt;/p&gt;
&lt;p&gt;总的来说，虚拟网络设备与物理网络设备没有什么区别，它们的一端连接着内核协议栈，而另一端的行为是什么取决于不同网络设备的驱动实现。&lt;/p&gt;
&lt;h3 id="tuntap"&gt;TUN/TAP&lt;/h3&gt;
&lt;p&gt;TUN/TAP 虚拟网络设备一端连着协议栈，另外一端不是物理网络，而是另外一个处于用户空间的应用程序。也就是说，协议栈发给 TUN/TAP 的数据包能被这个应用程序读取到，当然应用程序能直接向 TUN/TAP 发送数据包。&lt;/p&gt;
&lt;p&gt;一个典型的使用 TUN/TAP 网络设备的例子如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/02/01/5NYEzLXpmPSg8on.jpg" alt="network-device-tun-tap.jpg"&gt;&lt;/p&gt;
&lt;p&gt;上图中我们配置了一个物理网卡，IP 为&lt;code&gt;18.12.0.92&lt;/code&gt;，而 tun0 为一个 TUN/TAP 设备，IP 配置为&lt;code&gt;10.0.0.12&lt;/code&gt;。数据包的流向为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;应用程序 A 通过 socket A 发送了一个数据包，假设这个数据包的目的 IP 地址是 &lt;code&gt;10.0.0.22&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;socket A 将这个数据包丢给网络协议栈&lt;/li&gt;
&lt;li&gt;协议栈根据本地路由规则和数据包的目的 IP，将数据包由给 tun0 设备发送出去&lt;/li&gt;
&lt;li&gt;tun0 收到数据包之后，将数据包转发给了用户空间的应用程序 B&lt;/li&gt;
&lt;li&gt;应用程序 B 收到数据包之后构造一个新的数据包，将原来的数据包嵌入在新的数据包（IPIP 包）中，最后通过 socket B 将数据包转发出去&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 新数据包的源地址变成了 tun0 的地址，而目的 IP 地址则变成了另外一个地址 &lt;code&gt;18.13.0.91&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;ol start="6"&gt;
&lt;li&gt;socket B 将数据包发给协议栈&lt;/li&gt;
&lt;li&gt;协议栈根据本地路由规则和数据包的目的 IP，决定将这个数据包要通过设备 eth0 发送出去，于是将数据包转发给设备 eth0&lt;/li&gt;
&lt;li&gt;设备 eth0 通过物理网络将数据包发送出去&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们看到发送给&lt;code&gt;10.0.0.22&lt;/code&gt;的网络数据包通过在用户空间的应用程序 B，利用&lt;code&gt;18.12.0.92&lt;/code&gt;发到远端网络的&lt;code&gt;18.13.0.91&lt;/code&gt;，网络包到达&lt;code&gt;18.13.0.91&lt;/code&gt;后，读取里面的原始数据包，再转发给本地的&lt;code&gt;10.0.0.22&lt;/code&gt;。这就是 &lt;a href="https://en.wikipedia.org/wiki/Virtual_private_network"&gt;VPN&lt;/a&gt; 的基本实现原理。&lt;/p&gt;</description></item><item><title>Linux 数据包的接收与发送过程</title><link>https://morvencao.github.io/posts/networking-1-pkg-snd-rcv/</link><pubDate>Mon, 27 Jan 2020 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/networking-1-pkg-snd-rcv/</guid><description>&lt;p&gt;早在农历新年之前，就构思着将近半年重拾的网络基础知识整理成成一个系列，正好赶上新冠疫情在春节假期爆发，闲来无事，于是开始这一系列的笔记。&lt;/p&gt;
&lt;p&gt;我的整体思路是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一篇笔记会简单介绍 Linux 网络数据包接收和发送过程，但不涉及 TCP/IP 协议栈的细节知识；&lt;/li&gt;
&lt;li&gt;接下来总结一下常用的 Linux 虚拟网络设备，同时会结合使用 Linux 网络工具包 &lt;a href="https://en.wikipedia.org/wiki/Iproute2"&gt;iproute2&lt;/a&gt; 来操作这些常用的网络设备；&lt;/li&gt;
&lt;li&gt;有了前面的基础知识，再来看看几种常用的容器网络实现原理；&lt;/li&gt;
&lt;li&gt;最后，我们将探讨一下 k8s 平台中主流的网络模型实现；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;严格来说，这种学习思路其实很“急功近利”，但是，对于不太了解网络基础知识和 Linux 内核原理的同学来说，这反而是一种很有效的方式。但是，私以为学习过程不只应该由浅入深，更应该螺旋向前迭代，温故而知新，才能获益良多。&lt;/p&gt;
&lt;p&gt;废话不多说，我们先开始第一篇笔记的内容。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="数据包的接收过程"&gt;数据包的接收过程&lt;/h2&gt;
&lt;p&gt;为了简化起见，我们以一个 UDP 数据包在物理网卡上处理流程来介绍 Linux 网络数据包的接收和发送过程，我会尽量忽略一些不相关的细节。&lt;/p&gt;
&lt;h3 id="从网卡到内存"&gt;从网卡到内存&lt;/h3&gt;
&lt;p&gt;我们知道，每个网络设备（网卡）有驱动才能工作，驱动在内核启动时需要加载到内核中。事实上，从逻辑上看，&lt;strong&gt;驱动是负责衔接网络设备和内核网络栈的中间模块，每当网络设备接收到新的数据包时，就会触发中断，而对应的中断处理程序正是加载到内核中的驱动程序&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;下面这张图详细地展示了数据包如何从网络设备进入内存，并被处于内核中的驱动程序和网络栈处理的：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2020/01/27/8ZsXoQ1emVSzJlc.jpg" alt="network-receive-data-1.jpg"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据包进入物理网卡，如果目的地址不是该网络设备，且该网络设备没有开启&lt;a href="https://unix.stackexchange.com/questions/14056/what-is-kernel-ip-forwarding"&gt;混杂模式&lt;/a&gt;，该包会被该网络设备丢弃；&lt;/li&gt;
&lt;li&gt;物理网卡将数据包通过 DMA 的方式写入到指定的内存地址，该地址由网卡驱动分配并初始化；&lt;/li&gt;
&lt;li&gt;物理网卡通过硬件中断（IRQ）通知 CPU，有新的数据包到达物理网卡需要处理；&lt;/li&gt;
&lt;li&gt;接下来 CPU 根据中断表，调用已经注册了的中断函数，这个中断函数会调到驱动程序（NIC Driver）中相应的函数；&lt;/li&gt;
&lt;li&gt;驱动先禁用网卡的中断，表示驱动程序已经知道内存中有数据了，告诉物理网卡下次再收到数据包直接写内存就可以了，不要再通知 CPU 了，这样可以提高效率，避免 CPU 不停地被中断；&lt;/li&gt;
&lt;li&gt;启动软中断继续处理数据包。这样做的原因是硬中断处理程序执行的过程中不能被中断，所以如果它执行时间过长，会导致 CPU 没法响应其它硬件的中断，于是内核引入软中断，这样可以将硬中断处理函数中耗时的部分移到软中断处理函数里面来慢慢处理；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="内核处理数据包"&gt;内核处理数据包&lt;/h3&gt;
&lt;p&gt;上一步中网络设备驱动程序会通过触发内核网络模块中的软中断处理函数，内核处理数据包的流程如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2020/01/27/y2SZleoIwtbxDLs.jpg" alt="network-receive-data-2.jpg"&gt;&lt;/p&gt;
&lt;ol start="7"&gt;
&lt;li&gt;对上一步中驱动发出的软中断，内核中的 ksoftirqd 进程会调用网络模块的相应软中断所对应的处理函数，确切地说，这里其实是调用 &lt;code&gt;net_rx_action&lt;/code&gt; 函数；&lt;/li&gt;
&lt;li&gt;接下来 &lt;code&gt;net_rx_action&lt;/code&gt; 调用网卡驱动里的 &lt;code&gt;poll&lt;/code&gt; 函数来一个个地处理数据包；&lt;/li&gt;
&lt;li&gt;而 &lt;code&gt;poll&lt;/code&gt; 函数会让驱动程序读取网卡写到内存中的数据包，事实上，内存中数据包的格式只有驱动知道；&lt;/li&gt;
&lt;li&gt;驱动程序将内存中的数据包转换成内核网络模块能识别的 skb(socket buffer) 格式，然后调用 &lt;code&gt;napi_gro_receive&lt;/code&gt; 函数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;napi_gro_receive&lt;/code&gt; 函数会处理 &lt;a href="https://lwn.net/Articles/358910/"&gt;GRO&lt;/a&gt; 相关的内容，也就是将可以合并的数据包进行合并，这样就只需要调用一次协议栈，然后判断是否开启了 &lt;a href="https://github.com/torvalds/linux/blob/v3.13/Documentation/networking/scaling.txt#L99-L222"&gt;RPS&lt;/a&gt;；如果开启了，将会调用 &lt;code&gt;enqueue_to_backlog&lt;/code&gt; 函数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enqueue_to_backlog&lt;/code&gt; 函数会将数据包放入 &lt;code&gt;input_pkt_queue&lt;/code&gt; 结构体中，然后返回；&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 如果 &lt;code&gt;input_pkt_queue&lt;/code&gt; 满了的话，该数据包将会被丢弃，这个队列的大小可以通过 &lt;code&gt;net.core.netdev_max_backlog&lt;/code&gt; 来配置；&lt;/p&gt;</description></item><item><title>我的「2019」</title><link>https://morvencao.github.io/posts/2019-retrospect/</link><pubDate>Sat, 21 Dec 2019 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/2019-retrospect/</guid><description>&lt;h2 id="tldr"&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;最近我发现自己对于思考和记录的欲望在慢慢消亡，这是件很令我沮丧的事情。“少愤怒而多思考”的人设正在崩塌，我在向着自己厌恶的方向衍化，附和着，愤怒着，可一旦冷却翻篇之后，又恢复到若无其事的状态。归根结底，我没有以前那么坚定了，对「自我」的认同感逐渐淡化，开始被生活的惯性驱动，不去思考为什么，一旦开始随波逐流，就会发现一切原来这么轻松。但始终，我发现自己对于放飞自己充满惶恐，尤其是在进入到放纵之后的“贤者模式”的时候。与其被这种循环往复的矛盾折磨，不如投入到工作当中，至少不会有失落感。所以，这一年我基本放弃“抵抗”，避免思考为什么，开始享受这一年加速的节奏。&lt;/p&gt;
&lt;h2 id="是告别也是开始"&gt;是告别也是开始&lt;/h2&gt;
&lt;p&gt;这一年注定是个说再见的一年，作为一个影迷，这个时代的我们也是幸运的，因为我们有着共同的记忆，漫威宇宙；有时候你会感慨十年真的很短，弹指一挥间，反过来想想，终局之战是集结，是重聚，但也是新时代的开始。这一年我们愤怒，声讨 HBO ，我们会怀念狼家的孩子流离失所的模样，我们期待龙妈身披铠甲力挽狂澜的终章，“问君能有几多愁，恰似六季过后无权游”。但是我们也会惊叹 HBO 用一种阴冷，克制却令人毛骨悚然的方式将切尔诺贝利的悲鸣呈现出来，它时刻提醒着对于自然与规律，我们始终应该保持敬畏之心。这个时代的我们注定是怀旧的，我们怀念 Queen ，怀念佛雷迪，遗憾我们没有亲身经历那段充满爱、痛苦和接纳的音乐之旅；我们想以一部《爱尔兰人》重温当年风华正茂的阿尔帕西诺、德尼罗与乔·佩西共同演绎的黑帮传奇；我们想象着上世纪六七十年代风起云涌的好莱坞，光怪陆离的电影中的电影往事；我们在菲尼克斯肆意癫狂的小丑表演中看 DC 如何在后黑暗骑士时代重新扳回一局；&lt;/p&gt;
&lt;p&gt;当然，2019年我的光影之旅里面也不乏优秀的华语电影。我看到了哪吒的横空出世，《流浪地球》的荡气回肠，《少年的你》的真实无助；好的影视剧也层出不穷，《我们与恶的距离》让我学会重新审视现实，《长安十二时辰》让我领略盛世长安美景的同时也赞叹古人的理想抱负；甚至一些精彩的华语综艺也开始摆脱刻板印象，突出重围，《乐队的夏天》让我享受音乐的同时开始尊重摇滚；《圆桌派》继续教我如何做一个人畜无害的空巢老人；《奇葩说》继续满足着我围观不怕事儿大的幻想。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每一颗苹果都值得被偷吃。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;这一年还有很多没有提及的书影剧，它们不只是茶余饭后的谈资，甚至是我迷茫低落时的一颗启明星。很期待在新的一年我有幸见证更多优秀影视剧的诞生。&lt;/p&gt;
&lt;h2 id="一些小小的成就"&gt;一些小小的成就&lt;/h2&gt;
&lt;p&gt;回顾过去一年，工作上取得了一些小的进步，除了不断完善基础网络层技术栈，也更加深入地钻研了 &lt;a href="https://istio.io/"&gt;Istio&lt;/a&gt; 以及 &lt;a href="https://www.envoyproxy.io/"&gt;Envoy&lt;/a&gt; 。之前不断横向地扩展自己的知识领域，却没有用心向下深入探索，导致面对很多技术话题都可以侃侃而谈，可是遇到实际问题的时候无处下手。于是干脆从现实案例出发，在多耦合的复杂环境中练级打怪。&lt;/p&gt;
&lt;p&gt;上半年处于持续的三线程并发工作状态，一方面完成正常也是份内的 ICP 常规开发任务，另一方面帮助在客户在生产环境实施 &lt;a href="https://github.com/istio/istio"&gt;Istio&lt;/a&gt; ，同时，还要在 &lt;a href="https://github.com/istio"&gt;Istio 社区&lt;/a&gt;推动开源产品的不断演进。&lt;/p&gt;
&lt;p&gt;由于工作资源变动，年中有一段时间在 &lt;a href="https://github.com/kubernetes-sigs"&gt;Kubernetes-Sig&lt;/a&gt; 社区参与开发 [cluster-api] (&lt;a href="https://github.com/kubernetes-sigs/cluster-api"&gt;https://github.com/kubernetes-sigs/cluster-api&lt;/a&gt;)，参与周期并不是很长，很多问题并没有深入研究，只是总体上对以云原生的方式管理 k8s 集群的生命周期有了新的认识。&lt;/p&gt;
&lt;p&gt;九月初开始深入调研 k8s 原生的 CICD 利器 &lt;a href="https://github.com/kubernetes/test-infra/tree/master/prow"&gt;Prow&lt;/a&gt; ，并将部分项目从 Travis 迁移到 Prow ，使用 Prow 的好处除了可充分利用 k8s 自身的编排监控功能管理 CICD 流水线之外，它还提供了各种插件完成传统 CICD 工具（如 Travis, CirleCI 等）没有的命令式自动化工具，对于 k8s 原生项目尤其友好。&lt;/p&gt;
&lt;p&gt;这一年马不停蹄，但仔细想想收获也确实不少，一方面虽然过程曲折，但最终还是帮助客户完成了将 Istio 投入生产环境中，虽然规模不是很大；另一方面，花费了大量时间参与到社区 Istio 的开发与讨论中，从 1.1 到 1.4 ，保证了之前社区承诺的每三个月的大版本顺利更新，当然，最最重要的是，使得 Istio 1.0 发布之初的性能问题得到显著改善，同时， Istio 官方提供的 &lt;a href="https://github.com/istio/operator"&gt;Operator&lt;/a&gt; 也随 &lt;a href="https://istio.io/news/releases/1.4.x/announcing-1.4/change-notes/"&gt;1.4的发布&lt;/a&gt; 正式投入使用，这也是我个人投入时间比较多的项目之一。另外，也和同事一起完成了 &lt;a href="https://github.com/kubernetes-sigs/cluster-api-provider-ibmcloud"&gt;cluster-api-provider-ibmcloud&lt;/a&gt; 第一个版本的孵化与开源。进入十月份，开始将 Prow 投入到生产环境，将公司部分开源项目从 Travis 切换到 Prow 。另外，大约在三月份的时候，收到了机械工业出版社的写本关于 Istio 实践书的邀请，当时我并没有答应，原因一方面是我觉得个人的经验无法承担这样一个角色，另一方面我对于完成这样一本书的意义持保留态度，个人认为真正要深入了解 Istio 最好去看官方文档与源代码。&lt;/p&gt;</description></item><item><title>Istio 的前世今生</title><link>https://morvencao.github.io/posts/istio-history/</link><pubDate>Sun, 07 Jul 2019 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/istio-history/</guid><description>&lt;p&gt;其实要彻底了解 Istio 以及服务网格出现的背景，就得从计算机发展的早期说起。&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b175fca440328.jpg" style="width:60%;"/&gt;
&lt;p&gt;下面这张图展示的的通信模型变种其实从计算机刚出现不久的上世纪50年代开始就得到广泛应用，那个时候，计算机很稀有，也很昂贵，人们手动管理计算机之间的连接，图中绿色的网络栈底层只负责传输电子信号和字节码：&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b176e89f55060.jpg" style="width:60%;"/&gt;
&lt;hr&gt;
&lt;p&gt;随着计算机变得越来越普及，计算机的价格也没那么贵了，计算机之间的连接数量和相互之间通信的数据量出现了疯狂式的增长，人们越来越依赖网络系统，工程师们必须确保他们开发的服务能够满足用户的要求。于是，如何提升系统质量成为人们关注的焦点。计算机需要知道如何找到其他节点，处理同一个通道上的并发连接，与非直接连接的计算机发生通信，通过网络路由数据包、加密流量等等，除此之外，还需要流量控制机制，流量控制可以防止下游服务器给上游服务器发送过多的数据包。&lt;/p&gt;
&lt;p&gt;于是，在一段时期内，开发人员需要在自己的代码里处理上述问题。在下面这张图的示例中，为了确保下游服务器不给其他上游服务造成过载，应用程序需要自己处理流量控制逻辑，于是网络中的流量控制逻辑和业务逻辑就混杂在一起：&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b17d896c84739.jpg" style="width:60%;"/&gt;
&lt;p&gt;幸运的是，到了上世纪60年代末，TCP/IP 协议栈的横空出世解决了可靠传输和流量控制等问题，此后尽管网络逻辑代码依然存在，但已经从应用程序里抽离出来，成为操作系统网络栈的一部分，工程师只需要按照操作系统的调用接口进行编程就可以解决基础的网络传输问题：&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b17c802021262.jpg" style="width:60%;"/&gt;
&lt;p&gt;进入21世纪，计算机越来越普及、也越来越便宜，相互连接的计算机节点越来越多，业界出现了各种网络系统，如分布式代理和面向服务架构：&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b182d8e973698.jpg" style="width:60%;"/&gt;
&lt;p&gt;分布式为我们带来了更高层次的能力和好处，但却也带来了新的挑战。这时候工程师的重心开始转移到应用程序的网络功能上面，这时候的服务之间的对话以“消息”为传输单元，当工程师们通过网络进行调用服务时，必须能为应用程序消息执行超时、重试、确认等操作。&lt;/p&gt;
&lt;p&gt;于是，有工程师是开始尝试使用消息主干网（messaging backbone）集中式地来提供控制应用程序网络功能的模块，如服务发现、负载均衡、重试等等，甚至可以完成诸如协议调解、消息转换、消息路由、编排等功能，因为他们觉得如果可以将这些看似同一层面的内容加入到基础设施中，应用程序或许会更轻量、更精简、更敏捷。这些需求绝对是真实的，&lt;a href="https://en.wikipedia.org/wiki/Enterprise_service_bus"&gt;ESB(Enterprise Service Bus)&lt;/a&gt; 演变并满足了这些需要。ESB 在是2005年被提出的，它的概念特别类似于计算机硬件概念里的 USB, USB 作为电脑中的标准扩展接口，可以连接各种外部设备；而 ESB 则就把路由管理、协议转换、策略控制等通用应用程序网络功能加到现有的集中式消息总线里。&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b17e632692216.jpg" style="width:60%;"/&gt;
&lt;p&gt;这看似行得通！&lt;/p&gt;
&lt;p&gt;可是，在实施 SOA 架构的时候，工程师们发现这种架构有点儿用力过度、矫枉过正了。集中式的消息总线往往会成为架构的瓶颈，用它来进行流量控制、路由、策略执行等并不像我们想象那么容易，加上组织结构过于复杂，强制使用专有的格式，需要业务逻辑实现路由转换和编排等功能，各个服务之间耦合度很高，在敏捷运动的时代背景下，ESB 架构已经无法跟上时代的节奏了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在接下来的几年内，REST 革命和 API 优先的思潮孕育了微服务架构，而以 docker 为代表的容器技术和以 k8s 为代表的容器编排技术的出现促进了微服务架构的落地。事实上，微服务时代可以以 k8s 的出现节点划分为“前微服务时代”和“后微服务时代”：&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b18396f082630.jpg" style="width:60%;"/&gt;
&lt;p&gt;“前微服务时代”基本上是微服务作为用例推动容器技术的发展，而到“后微服务时代”，特别是成为标准的 k8s 其实在驱动和重新定义微服务的最佳实践，容器和 k8s 为微服务架构的落地提供了绝佳的客观条件。&lt;/p&gt;
&lt;p&gt;微服务架构有很多好处，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;快速分配计算资源&lt;/li&gt;
&lt;li&gt;快速部署升级迭代&lt;/li&gt;
&lt;li&gt;易于分配的存储&lt;/li&gt;
&lt;li&gt;易于访问的边界等等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是作为较复杂的分布式系统，微服务架构给运维带来了新的挑战。当工程师开始接尝试微服务架构，必须考虑如何进行微服务治理。狭义的“微服务治理”，关注的是微服务组件之间的连接与通讯，例如服务注册发现、东西向路由流控、负载均衡、熔断降级、遥测追踪等。&lt;/p&gt;
&lt;p&gt;历史总是惊人的相似，面对类似的问题，第一批采用微服务架构的企业遵循着与第一代网络计算机系统类似的策略，也就是说，解决网络通信问题的任务又落在了业务工程师的肩上。&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b184b76f24616.jpg" style="width:60%;"/&gt;
&lt;p&gt;这个时候出现了看到诸如 Netflix &lt;a href="https://netflix.github.io/"&gt;OSS 堆栈&lt;/a&gt;、Twitter &lt;a href="https://github.com/twitter/finagle"&gt;Finagle&lt;/a&gt; 以及赫赫有名的 &lt;a href="https://spring.io/projects/spring-cloud"&gt;Spring Cloud&lt;/a&gt; 这样的框架和类库帮助业务工程师快速开发应用程序级别的网络功能，只需要写少量代码，就可以把服务发现、负载均衡、路由管理、遥测收集、监控告警等这些功能实现：&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233ef9733af85293.jpg" style="width:60%;"/&gt;
&lt;p&gt;但是如果仔细想一下的话，就会发现这样编写微服务程序的问题也很明显。&lt;/p&gt;
&lt;p&gt;这些类库或者框架是特定语言编写的，并且混合在业务逻辑中（或在整个基础设施上层分散的业务逻辑中）。姑且不说类库和框架的学习成本和门槛，我们知道微服务架构问世的一个承诺就是不同的微服务可以采用不同的编程语言来编写，可是当你开始编写代码的时候会发现有些语言还没有提供对应的类库。这是一个尴尬的局面！这个问题非常尖锐，为了解决这个问题，大公司通常选择就是统一编程语言来编写微服务代码。另外的问题是，怎么去做框架升级？框架不可能一开始就完美无缺，所有功能都齐备，没有任何 BUG。升级一般都是逐个版本递进升级，一旦出现客户端和服务器端版本不一致，就要小心维护兼容性。实际上，每做出一次变更都需要进行深度的集成和测试，还要重新部署所有的服务，尽管服务本身并没有发生变化。&lt;/p&gt;
&lt;hr&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d233b185a0f325924.jpg" style="width:60%;"/&gt;
&lt;p&gt;与网络协议栈一样，工程师们急切地希望能够将分布式服务所需要的一些特性放到底层的平台中。这就像工程师基于 HTTP 协议开发非常复杂的应用，无需关心底层 TCP 如何传输控制数据包。在开发微服务时也是类似的，业务工程师们聚焦在业务逻辑上，不需要浪费时间去编写服务基础设施代码或管理系统用到的软件库和框架。把这种想法囊括到之前架构中，就是下边这幅图所描述的样子：&lt;/p&gt;
&lt;img src="https://i.loli.net/2019/07/08/5d235081238ea81414.jpg" style="width:60%;"/&gt;
&lt;p&gt;不过，在网络协议栈中加入这样的一个层是不实际的，貌似可以尝试一下代理的方案！事实上，确实有一些先驱者曾经尝试过使用代理的方案，例如 nginx/haproxy/proxygen 等代理。也就是说，一个服务不会直接与上游服务发生连接，所有的网络流量都会流经代理，代理会拦截服务之间的请求并转发到上游服务。可是，那时候代理的功能非常简陋，很多工程师尝试之后觉得没有办法实现服务的客户端所有需求。&lt;/p&gt;
&lt;p&gt;在这样的诉求下，第一代的 sidecar 出现了，sidecar 扮演的角色和代理很像，但是功能就齐全很多，基本上原来微服务框架在客户端实现的功能都会有对应的功能实现：&lt;/p&gt;</description></item><item><title>一份好吃的 Istio 入门餐</title><link>https://morvencao.github.io/posts/getting-started-with-istio/</link><pubDate>Sun, 23 Jun 2019 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/getting-started-with-istio/</guid><description>&lt;p&gt;前两天 Istio 正式发布 1.2 版本，至此，Istio 社区再次回归每3个月一个大版本加每月一个小版本更新的传统。从 Istio 1.0发布以来，社区在对改进 Istio 一些非功能性的需求（例如性能与扩展性）方面的努力是大家有目共睹的。我觉得是时候写一篇通俗易懂的 Istio 入门文章，让更多的人去体验一下 Istio 在云原生时代所带来的各种益处。&lt;/p&gt;
&lt;h2 id="为什么需要-istio"&gt;为什么需要 Istio&lt;/h2&gt;
&lt;p&gt;说到 Istio，就不得不提到另外一个名词：Service Mesh，中文名为“服务网格”。&lt;/p&gt;
&lt;p&gt;相信很多人对于传统的单体应用以及它所面临的问题很熟悉，一种显而易见的方案是将其分解为多个微服务。这确实能简化微服务的开发，但是不得不说，也带来非常多的挑战，比如对于成百上千的微服务的通信、监控以及安全性的管理并不是一件简单的事。目前为止，针对这些问题确实有一些解决方案（比如 &lt;a href="https://spring.io/projects/spring-cloud"&gt;Spring Cloud&lt;/a&gt;），但基本都是通过类似于类库或者定制化脚本的方式将为服务串联在一起，附带很高的维护升级成本。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/06/24/5d1033052a66711533.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Service Mesh 的出现正是为了解决这一问题。它是位于底层网络与上层服务之间的一层基础设施，提供了一种透明的、与编程语言无关的方式，使网络配置、安全配置以及遥测等操作能够灵活而简便地实现自动化。从本质上说，它解耦了服务的开发与运维工作。如果你是一名开发者，那么部署升级服务的时候不需要担心这些操作会对整个分布式系统带来哪些运维层面的影响；与之对应，如果你是运维人员，那么也可以放心大胆地进行服务之间的运维结构进行变更，而不需要修改服务的源代码。&lt;/p&gt;
&lt;p&gt;而 Istio 真正地将 Service Mesh 的概念发扬光大。它创新性地将控制平面（Control Plane）与数据平面（Data Plane）解耦，通过独立的控制平面可以对 Mesh 获得全局性的可观测性（Observability）和可控制性（Controllability），从而让 Service Mesh 有机会上升到平台的高度，这对于对于希望提供面向微服务架构基础设施的厂商，或是企业内部需要赋能业务团队的平台部门都具有相当大的吸引力。&lt;/p&gt;
&lt;p&gt;为什么会需要这样的设计呢？&lt;/p&gt;
&lt;p&gt;我们先来看一个单独的一个微服务，正常情况下不管单个微服务部署在一个物理机上，亦或一个容器里，甚至一个 k8s 的 pod 里面，它的基本数据流向不外乎所有的 Inbound 流量作为实际业务逻辑的输入，经过微服务处理的输出作为 Outbound 流量：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/06/24/5d108aa2db1e428844.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;随着应用的衍进与扩展，微服务的数量快速增长，整个分布式系统变得“失控”，没有一种通用可行的方案来监控、跟踪这些独立、自治、松耦合的微服务组件之间的通信；对于服务的发现和负载均衡，虽说像 k8s 这样的平台通过提供 apiserver 与 kube-proxy 下发 iptables 规则来实现了基本的部署、升级和有限的运行流量管理能力，但是本质上 k8s 提供的滚动升级都是依赖于 replicas 实例数的伸缩来调整，没办法实现按照版本的灰度发布或者金丝雀发布，不能满足高并发应用下高级的服务应用层特性；至于更加复杂的熔断、限流、降级等需求。&lt;/p&gt;
&lt;p&gt;看起来不通过应用侵入式编程几乎不可能实现。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/06/24/5d1057e6e164054939.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;但是，真的是这样吗？我们来换一个思路，想象一下，如果我们在部署微服务的同时部署一个 sidecar，这个 sidecar 充当代理的功能，它会拦截所有流向微服务业务逻辑的流量，做一些预处理（解包然后抽取、添加或者修改特定字段再封包）之后在将流量转发给微服务，微服务处理的输出也要经过 sidecar 拦截做一些后处理（解包然后抽取、删除或者修改特定字段再封包），最后将流量分发给外部：&lt;/p&gt;</description></item><item><title>MacBookPro 开启 HiDPI</title><link>https://morvencao.github.io/posts/enable-minitor-hidpi/</link><pubDate>Fri, 12 Apr 2019 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/enable-minitor-hidpi/</guid><description>&lt;p&gt;上周旧笔记本（2015-Mid MacBookPro）由于自己的疏忽导致背包内水杯漏水而浸液，导致开机黑屏，基本无法使用。因为是主力机，所以无奈只能硬着头皮换了最新款的 MacBookPro（2018-Mid）。不换不知道，原本以为很快的数据和配置迁移消耗了我一整晚的时间。可能是因为近些年来苹果品控的下降，对于最新 MacBookPro 没多少好感，拿到新本后令人发狂的蝶式键盘加上鸡肋的 TouchBar，让新旧本之间的过渡期再次延长，于是决定还是继续使用外接键盘和鼠标，并将自己之前的显示器作为主显示器。&lt;/p&gt;
&lt;p&gt;将外接显示器连接上之后，很快就会发现整体显示模糊，即使4K显示屏也不能达到期望的 Retina 显示效果。网上搜索一番之后才发现原因是新本没有开启 HiDPI。&lt;/p&gt;
&lt;h2 id="何为-hidpi"&gt;何为 HiDPI&lt;/h2&gt;
&lt;p&gt;我们知道，高分辨率意味着更小的字体和图标，而 HiDPI 可以用软件的方式实现单位面积内的高密度像素。通过开启 HiDPI 渲染，可以在保证分辨率不变的情况下，使得字体和图标变大。一句话概括就是就是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;高 PPI(硬件) + HiDPI 渲染(软件) = 更细腻的显示效果(Retina)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="如何开启-hidpi"&gt;如何开启 HiDPI&lt;/h2&gt;
&lt;p&gt;关于如何开启 HiDPI，Google 搜索之后会有很多方案，但是因为系统的不断升级，有的不够全面，有的过于繁琐。在此针对我目前的笔记本（MacBookPro 2018-Mid）给出一个相对简洁的方案。主要包含三个步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关闭 SIP&lt;/li&gt;
&lt;li&gt;终端命令&lt;/li&gt;
&lt;li&gt;开启 SIP&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="备份"&gt;备份&lt;/h3&gt;
&lt;p&gt;实际的操作的过程会更改部分系统文件，因此在操作前要确保对文件进行备份：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开终端并进入到 &lt;code&gt;/System/Library/Displays/Contents/Resources&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;拷贝 &lt;code&gt;Overrides&lt;/code&gt; 文件夹到其他目录一份&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="关闭-sip"&gt;关闭 SIP&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 关闭 SIP 有风险，确保所有操作完整之后再次打开 SIP，否则对系统文件的保护将不存在&lt;/p&gt;&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;打开终端并输入 &lt;code&gt;csrutil status&lt;/code&gt;，如果结果为 &lt;code&gt;enabled&lt;/code&gt; 则表明 SIP 为开启状态&lt;/li&gt;
&lt;li&gt;关机之后再按电源键后长按 &lt;code&gt;command + R&lt;/code&gt; 直至出现苹果 LOGO&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;Utils-&amp;gt;Terminal&lt;/code&gt; 打开终端，并输入 &lt;code&gt;csrutil disable&lt;/code&gt;，之后关掉终端&lt;/li&gt;
&lt;li&gt;重启并正常开机&lt;/li&gt;
&lt;li&gt;打开终端并输入 &lt;code&gt;csrutil status&lt;/code&gt;，确保结果是 &lt;code&gt;disable&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="运行设置脚本"&gt;运行设置脚本&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 为了简化配置过程，我使用了 Github 上开源的&lt;a href="https://github.com/syscl/Enable-HiDPI-OSX"&gt;自动脚本&lt;/a&gt;来开启 HiDPI，如果想手动来配置，请访问：https://comsysto.github.io/Display-Override-PropertyList-File-Parser-and-Generator-with-HiDPI-Support-For-Scaled-Resolutions/&lt;/p&gt;</description></item><item><title>Go 模块化编程</title><link>https://morvencao.github.io/posts/golang-module/</link><pubDate>Tue, 26 Feb 2019 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/golang-module/</guid><description>&lt;p&gt;Go 语言在去年八月份发布的1.11版本中增加了对模块化编程的支持以及内置的基于模块的依赖管理工具。Go 语言的模块（module）是文件树中的包（package）的集合，其中模块根目录包含的 &lt;code&gt;go.mod&lt;/code&gt; 文件定义了模块的导入路径（import path）、Go 语言的版本以及模块的其他依赖性要求。每个模块的依赖性要求被列为单独的一个模块路径并指定相应的模块版本，只有满足了所有依赖性要求的模块才能被成功构建。&lt;/p&gt;
&lt;p&gt;使用 Go 语言自带的模块化编程能力，就不需要将 Go 语言的代码放入到 &lt;code&gt;$GOPATH/src&lt;/code&gt; 中，也就是过去的 GOPATH 模式，实际上，我们可以在 &lt;code&gt;$GOPATH/src&lt;/code&gt; 外的任何目录下使用 &lt;code&gt;go mod init&lt;/code&gt; 创建 Go 项目并初始化 Go 模块。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 为了兼容性，在 Go 语言1.11与1.12中，Go 命令仍然可以在旧的 GOPATH 模式下运行，从 Go 语言1.13开始，模块模式(&lt;code&gt;GO111MODULE=on&lt;/code&gt;)将成为默认模式。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="gopath-的前世今生"&gt;GOPATH 的前世今生&lt;/h2&gt;
&lt;p&gt;而 Go 语言支持模块化编程之前，一般我们的 Go 项目使用需要使用 GOPATH 模式，也就是说需要将 Go 语言的代码放入到 &lt;code&gt;$GOPATH/src&lt;/code&gt; 中。典型的 GOPATH 目录结构包含必须包含以下三个子文件夹：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GOPATH
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── bin // binaries
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── pkg // cache
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;|── src // go source code
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── github.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── rsc.io
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用 &lt;code&gt;go get&lt;/code&gt; 命令获取依赖也会自动下载到 &lt;code&gt;$GOPATH/src&lt;/code&gt; 中：&lt;/p&gt;</description></item><item><title>创建最小 Docker 镜像</title><link>https://morvencao.github.io/posts/create-the-smallest-docker-image/</link><pubDate>Sun, 20 Jan 2019 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/create-the-smallest-docker-image/</guid><description>&lt;p&gt;如果你熟悉 &lt;a href="https://www.docker.com/"&gt;docker&lt;/a&gt;，你可能知道 docker 镜像存储使用 &lt;a href="https://en.wikipedia.org/wiki/Union_mount"&gt;Union FS&lt;/a&gt; 的分层存储技术。在构建一个 docker 镜像时，会一层一层构建，前一层是后一层的基础，每一层构建完成之后就不会再改变。正是因为这一点，我们在构建 docker 镜像的时候，要特别小心，每一层尽量只包含需要的东西，构建应用额外的东西尽量在构建结束的时候删除。举例来说，比如你在构建一个 Go 语言编写的简单应用程序的时候，原则上只需要一个 Go 编译出来的二进制文件，没有必要保留构建的工具以及环境。&lt;/p&gt;
&lt;p&gt;docker 官方提供了一个特殊的空镜像 scratch，使用这个镜像意味着我们不需要任何的已有镜像为基础，直接将我们自定义的指令作为镜像的第一层。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;FROM&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#4e9a06"&gt;scratch&lt;/span&gt;&lt;span style="color:#a40000"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a40000"&gt;&lt;/span&gt;...&lt;span style="color:#a40000"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实际上，我们可以创建自己的 scratch 镜像：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ tar cv --files-from /dev/null &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; docker import - scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;那么，问题来了，我们可以使用 scratch 镜像为基础制作哪些镜像呢？答案是所有不需要任何依赖库的可执行文件都可以以 scratch 为基础镜像来制作。具体来说，对于 linux 下静态编译的程序来说，并不需要操作系统提供的运行时支持，所有需要的一切都已经在可执行文件中包含了，比如使用 Go 语言开发的很多应用会使用直接 &lt;code&gt;FROM scratch&lt;/code&gt; 的方式制作镜像，这样最终的镜像体积非常小。&lt;/p&gt;
&lt;p&gt;下面是一个简单的 Go 语言开发的 web 程序代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;package&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;main&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;import&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;	&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;	&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;func&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;main&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;()&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;	&lt;/span&gt;&lt;span style="color:#000"&gt;http&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;HandleFunc&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;func&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;w&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;http&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;ResponseWriter&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;r&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;*&lt;/span&gt;&lt;span style="color:#000"&gt;http&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Request&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;		&lt;/span&gt;&lt;span style="color:#000"&gt;fmt&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Fprintf&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;w&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;Hello, you&amp;#39;ve requested: %s\n&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;r&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;URL&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Path&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;	&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;})&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;	&lt;/span&gt;&lt;span style="color:#000"&gt;http&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;ListenAndServe&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;:80&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;nil&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们可以使用 &lt;code&gt;go build&lt;/code&gt; 来编译此程序，并以 scratch 为基础制作 docker 镜像，dockerfile 如下：&lt;/p&gt;</description></item><item><title>重拾少年时期的「信仰」</title><link>https://morvencao.github.io/posts/2018-summary/</link><pubDate>Fri, 28 Dec 2018 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/2018-summary/</guid><description>&lt;blockquote&gt;
&lt;p&gt;不必太纠结于当下，也不必太忧虑未来，当你经历过一些事情的时候，眼前的风景已经和从前不一样了。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;窗外的霓虹灯让我有点儿走神，在下高铁回家的出租车上，我被出租车司机套路了年龄。本来以为他会说看起来这么年轻之类的客套话，但是他接下来说的话让我有点儿猝不及防！&lt;/p&gt;
&lt;blockquote&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;p&gt;我其实很清楚这种执念会将自己带完何处？固执，腐败，糜烂！我要放下哪些愚蠢的想法，重新认识自己，不去思考为什么？踏踏实实做好每一件事情，享受小成就带给自己的快感。虽然我又一次以为我找到了事物的真相，但是在这个片刻，我似乎看大了少年时代的自己，我是心满意足的。&lt;/p&gt;
&lt;p&gt;所以最终2018年的年底，我要结束了这种纠结的状态，把自己重新放到“野外”，用一种新的视角去看自己，看世界。去按照自己希望的方式，改造自己，做一次升级。&lt;/p&gt;
&lt;p&gt;因此，在2019年，我要更高效的提升自己的能力，做更多具备稀缺性和自我认同的事情，重拾少年时期的「信仰」！&lt;/p&gt;</description></item><item><title>Docker 知识点拾遗</title><link>https://morvencao.github.io/posts/docker-wiki/</link><pubDate>Tue, 13 Nov 2018 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/docker-wiki/</guid><description>&lt;p&gt;&lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; 是一个划时代的产品，它彻底地释放了计算机虚拟化的威力，极大的提高了应用的部署、测试和分发的效率。虽然我们几乎每天都在使用 docker，但还是有一些很容易被忽略得的 docker 知识点，今天，我们就集中起来看看。&lt;/p&gt;
&lt;h2 id="容器与传统虚拟机"&gt;容器与传统虚拟机&lt;/h2&gt;
&lt;p&gt;经常有人说“ docker 是一种性能非常好的虚拟机”，这种说法是错误的。docker 相比于传统虚拟机的技术来说更为轻便，具体表现在 docker 不是在宿主机上虚拟出一套硬件并运行完整的操作系统，然后再在其上运行所需的应用进程，而是直接在 docker 容器里面的进程直接运行在宿主的内核中，docker 会做文件系统、网络以及进程隔离等，容器不用进行硬件和内核的虚拟。这样一来 docker 会相对于传统虚拟机来说“体积更轻、跑的更快，相同宿主机下可创建的数量更多”。&lt;/p&gt;
&lt;p&gt;docker 不是虚拟机，容器中的应用都应该以前台执行，而不是像虚拟机、物理机里面那样，用 systemd 去启动后台服务，容器内没有后台服务的概念。举个例子，常有人在 dockerfile 里面这样写：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;CMD&lt;/span&gt; service nginx start&lt;span style="color:#a40000"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后发现容器执行后就立即退出了，甚至在容器内去使用 &lt;code&gt;systemctl&lt;/code&gt; 命令来管理服务，结果却发现根本执行不了。这就是没有区分容器和虚拟机的差异，依旧以传统虚拟机的角度去理解容器。对于 docker 容器而言，其启动程序就是容器的应用进程，容器就是为了主进程而存在的，主进程退出，容器就失去了存在的意义，其它辅助进程不是它需要关心的东西。而使用 CMD 指令 &lt;code&gt;service nginx start&lt;/code&gt; 则是以后台守护进程形式启动 nginx 服务，事实上，&lt;code&gt;service nginx start&lt;/code&gt; 最终会被 docker 引擎转化为 &lt;code&gt;[ &amp;quot;sh&amp;quot;, &amp;quot;-c&amp;quot;, &amp;quot;service nginx start&amp;quot;]&lt;/code&gt; 命令，因此主进程实际上是 &lt;code&gt;sh&lt;/code&gt;。那么当 &lt;code&gt;service nginx start&lt;/code&gt; 命令结束后， &lt;code&gt;sh&lt;/code&gt; 也就结束了，&lt;code&gt;sh&lt;/code&gt; 作为主进程退出了，自然就会令容器退出。&lt;/p&gt;
&lt;p&gt;正确的做法是直接执行 &lt;code&gt;nginx&lt;/code&gt; 可执行文件，并且要求以前台形式运行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;CMD&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;[&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt; &lt;span style="color:#4e9a06"&gt;&amp;#34;-g&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt; &lt;span style="color:#4e9a06"&gt;&amp;#34;daemon off;&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;]&lt;/span&gt;&lt;span style="color:#a40000"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="分层存储"&gt;分层存储&lt;/h2&gt;
&lt;p&gt;我们知道完整的操作系统由内核空间和用户空间组成。从存储的角度来看，内核空间主要是指需要引导程序加载和启动的内核程序，用户空间的核心则是 &lt;a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03.html"&gt;rootfs&lt;/a&gt; 。而 docker 容器启动并不会虚拟出新内核，而是共享宿主机的内核，所以对于 docker 镜像与容器而言，我们主要关注的存储结构是 rootfs 。不同 Linux 发行版的主要区别也是 rootfs 。比如以前的 Ubuntu 使用 UpStart 系统管理服务，apt 管理软件包；而 CentOS 使用 systemd 和 yum ，但是这些都是在用户空间上的区别，Linux 内核的差别不大。&lt;/p&gt;</description></item><item><title>Go 反射机制</title><link>https://morvencao.github.io/posts/golang-reflection/</link><pubDate>Wed, 12 Sep 2018 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/golang-reflection/</guid><description>&lt;p&gt;很多语言都支持反射，Go 语言也不例外。那么什么是反射呢？总的来说，反射就是计算机程序语言可以在运行时动态访问、检查和修改任意对象本身状态和行为的能力。不同语言的反射特性有着不同的工作方式，有些语言还不支持反射特性。我们今天主要来看看在 Go 语言中，反射是怎么工作的。&lt;/p&gt;
&lt;p&gt;在开始之前，先安利一篇 Go 官方出品的介绍反射机制的博客：&lt;a href="https://blog.golang.org/laws-of-reflection"&gt;The Laws of Reflection&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这篇笔记是我个人对于 Go 官方博客关于反射机制的总结与补充，强烈食用本文之前去看看官方博客。&lt;/p&gt;
&lt;h2 id="反射的适用场景"&gt;反射的适用场景&lt;/h2&gt;
&lt;p&gt;需要说明的是，Go 语言是一门静态类型的编译型语言，也就是在编译过程中，编译器就能发现一些类型错误，但是它并不能发现反射相关代码的错误，这种错误往往需要在运行时才能被发现，所以一旦反射代码出错，将直接导致程序 panic 而退出。此外，反射相关代码的可读性往往较差，执行效率也比正常 Go 语言代码低不少。综上，除非以下特殊情况必须使用反射外，我们应经量避免使用 Go 语言的反射特性。&lt;/p&gt;
&lt;p&gt;一般来说，适用于反射的场景主要包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;某段函数或者方法需要处理的数据类型不确定，会包含一些列的可能类型，这个时候我们就需要使用反射动态获取要处理的数据类型，基于数据类型的不同调用不同的处理程序。一个非常典型的例子是我们通过反序列化得到一个类型为 &lt;code&gt;map[string]interface{}&lt;/code&gt; 的数据结构，然后通过反射机制递归地获取内部每个字段的类型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在某些场景下我们需要根据特定的条件决定调用哪个函数，也就是说需要在运行期间获取函数以及函数运行所需的参数来动态地调用函数。典型的例子是现在主流的 RPC 框架的实现机制，RPC 服务器端维护函数名到函数反射值的映射，RPC 客户端通过网络传递函数名、参数列表给 RPC 服务器端后，RPC 服务器解析为反射值，调用执行函数，最后再将函数的返回值打包通过网络返回给 RPC 客户端。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 当然，可能还有其他适用于反射的应用场景，这里只是罗列常用的使用场景。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="反射的实现基础"&gt;反射的实现基础&lt;/h2&gt;
&lt;p&gt;首先需要明确一点，Go 反射的实现基础是类型。在 Go 语言中，每个变量都有一个&lt;strong&gt;静态类型&lt;/strong&gt;，也就是在编译阶段就需要检查的类型，比如 &lt;code&gt;int&lt;/code&gt;、&lt;code&gt;string&lt;/code&gt;、&lt;code&gt;map&lt;/code&gt;、&lt;code&gt;struct&lt;/code&gt; 等。需要注意的是，这个静态类型是指变量声明的时候指定的类型，并不一定不是底层真正存储的数据类型，例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;type&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;MyInt&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;int&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;i&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;int&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;j&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;MyInt&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面的代码中，虽然变量 &lt;code&gt;i&lt;/code&gt; 和 &lt;code&gt;j&lt;/code&gt; 的真正存储的数据类型都是 &lt;code&gt;int&lt;/code&gt;，但是对于编译器来说，它们是不同的类型，要进行显示的类型转换才能相互赋值。&lt;/p&gt;
&lt;p&gt;有的变量可能除了&lt;strong&gt;静态类型&lt;/strong&gt;之外，还会有&lt;strong&gt;动态类型&lt;/strong&gt;。所谓动态类型就是指变量值实际存储的类型信息，举例来说：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;r&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;io&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Reader&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000"&gt;tty&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;os&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;OpenFile&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;/dev/tty&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;os&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;O_RDWR&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#0000cf;font-weight:bold"&gt;0&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;if&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;nil&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;return&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;nil&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000"&gt;r&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;tty&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;empty&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;interface&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000"&gt;empty&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;tty&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;首先声明 &lt;code&gt;r&lt;/code&gt; 的类型是 &lt;code&gt;io.Reader&lt;/code&gt;，也就是说这是 &lt;code&gt;r&lt;/code&gt; 的静态类型，此时它的动态类型为 &lt;code&gt;nil&lt;/code&gt;，并且它的动态值也是 &lt;code&gt;nil&lt;/code&gt;。接下来的 &lt;code&gt;r=tty&lt;/code&gt; 语句，将 &lt;code&gt;r&lt;/code&gt; 的动态类型变成 &lt;code&gt;*os.File&lt;/code&gt;，动态值则变成非空，表示打开的文件对象。这时，&lt;code&gt;r&lt;/code&gt; 可以用 &lt;code&gt;&amp;lt;value,type&amp;gt;&lt;/code&gt; 对来表示为：&lt;code&gt;&amp;lt;tty, *os.File&amp;gt;&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go 模板渲染</title><link>https://morvencao.github.io/posts/golang-template/</link><pubDate>Fri, 24 Aug 2018 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/golang-template/</guid><description>&lt;p&gt;随着近几年 Restful 架构的盛行，前后端分离大行其道，模板渲染也由后端转移到了前端，后端只需要提供资源数据即可，这样导致类似于 JSP、PHP 等传统服务端模板脚本语言几乎问人问津了。但是在 Go 语言中，模板渲染技术不只局限于服务端标记语言（如 HTML）的渲染，GO 语言经常使用模版语言来处理譬如插入特定数据的文本转化等，虽然没有正则表达式那么灵活，但是渲染效率远优于正则表达式，而且使用起来也更简单。对于某些云计算的场景十分友好。今天，我们就来详细聊一聊 Go 语言模板渲染的技术细节。&lt;/p&gt;
&lt;h2 id="运行机制"&gt;运行机制&lt;/h2&gt;
&lt;p&gt;模板的渲染技术本质上都是一样的，一句话来说明就是&lt;strong&gt;字符串模板和结构化数据&lt;/strong&gt;的结合，再详细地讲就是将定义好的模板应用于结构化的数据，使用注解语法引用数据结构中的元素（例如 Struct 中的特定字段，Map 中的键值）并显示它们的值。模板在执行过程中遍历数据结构并且设置当前光标（&lt;code&gt;.&lt;/code&gt; 往往表示当前的作用域）标识当前位置的元素。&lt;/p&gt;
&lt;p&gt;类似于 Python 语言的 &lt;a href="http://jinja.pocoo.org/"&gt;jinja&lt;/a&gt;与 NodeJS 语言中的 &lt;a href="http://jade-lang.com/"&gt;jade&lt;/a&gt;等模版引擎，Go 语言模板引擎的运行机制也是类似：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建模板对象&lt;/li&gt;
&lt;li&gt;解析模板字串&lt;/li&gt;
&lt;li&gt;加载数据渲染模板&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/03/31/5ca036206c6f3.jpg" alt=""&gt;&lt;/p&gt;
&lt;h2 id="go-模板渲染核心包"&gt;Go 模板渲染核心包&lt;/h2&gt;
&lt;p&gt;Go语言提供了两个标准库用来处理模板渲染 &lt;code&gt;text/template&lt;/code&gt; 和 &lt;code&gt;html/template&lt;/code&gt;，它们的接口几乎一摸一样，但处理的模板数据不同。其中 &lt;code&gt;text/template&lt;/code&gt; 用来处理普通文本的模板渲染，而 &lt;code&gt;html/template&lt;/code&gt; 专门用来渲染格式化 HTML 字符串。&lt;/p&gt;
&lt;p&gt;下面的例子我们使用 &lt;code&gt;text/template&lt;/code&gt; 来处理普通文本模板的渲染：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;package&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;main&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;import&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;	&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;	&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;text/template&amp;#34;&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;type&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;Student&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;struct&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;ID&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;uint&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;Name&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;string&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;func&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;main&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;()&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;stu&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;Student&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#0000cf;font-weight:bold"&gt;0&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;jason&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;tmpl&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;template&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;New&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;).&lt;/span&gt;&lt;span style="color:#000"&gt;Parse&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;The name for student {{.ID}} is {{.Name}}&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;if&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;nil&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87"&gt;panic&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;tmpl&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Execute&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;os&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Stdout&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;stu&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;if&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;nil&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87"&gt;panic&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;err&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上述代码第4行引入 &lt;code&gt;text/template&lt;/code&gt; 来处理普通文本模板渲染，第14行定义一个模板对象 &lt;code&gt;test&lt;/code&gt; 来解析变量 &lt;code&gt;&amp;quot;The name for student {{.ID}} is {{.Name}}&amp;quot;&lt;/code&gt; 模板字符串，第16行使用定义好的结构化数据来渲染模版到标准输出。&lt;/p&gt;</description></item><item><title>Go 上下文</title><link>https://morvencao.github.io/posts/golang-context/</link><pubDate>Tue, 12 Jun 2018 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/golang-context/</guid><description>&lt;p&gt;从 Go 语言从1.7开始，正式将 &lt;a href="golang.org/x/net/context"&gt;context&lt;/a&gt; 即“上下文”包引入官方标准库。事实上，我们在 Go 语言编程中经常遇到“上下文”，不论是在一般的服务器代码还是复杂的并发处理程序中，它都起到很重要的作用。今天，我们就来深入探讨一下它的实现以及最佳实践。&lt;/p&gt;
&lt;p&gt;官方文档对于 context 包的解释是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;简单来说，“上下文”可以理解为程序单元的一个运行状态、现场、快照。其中上下是指存在上下层的传递，上层会把内容传递给下层，而程序单元则指的是 Go 协程。每个 Go 协程在执行之前，都要先知道程序当前的执行状态，通常将这些执行状态封装在一个“上下文”变量中，传递给要执行的 Go 协程中。而 context 包是专门用来简化处理针对单个请求的多个 Go 协程与请求截止时间、取消信号以及请求域数据等相关操作的。一个常遇到的例子是在 Go 语言实现的服务器程序中，每个网络请求一般需要创建单独的Go 协程进行处理，这些 Go 协程有可能涉及到多个 API 的调用，进而可能会开启其他的 Go 协程；由于这些 Go 协程都是在处理同一个网络请求，所以它们往往需要访问一些共享的资源，比如用户认证令牌环、请求截止时间等；而且如果请求超时或者被取消后，所有的 Go 协程都应该马上退出并且释放相关的资源。使用上下文可以让 Go 语言开发者方便地实现这些 Go 协程之间的交互操作，跟踪并控制这些 Go 协程，并传递请求相关的数据、取消 Go 协程的信号或截止日期等。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/01/30/3EGqicXUKPL8NVB.jpg" alt="go-context.jpg"&gt;&lt;/p&gt;
&lt;h2 id="上下文的数据结构"&gt;上下文的数据结构&lt;/h2&gt;
&lt;p&gt;context 包中核心数据结构是一种嵌套的结构或者说是单向继承的结构。基于最初的上下文（也叫做“根上下文”），开发者可以根据使用场景的不同定义自己的方法和数据来继承“根上下文”；正是上下文这种分层的组织结构，允许开发者在每一层上下文中定义一些不同的特性，这种层级式的组织也使得上下文易于扩展、职责清晰。&lt;/p&gt;
&lt;p&gt;context 包中最基础的数据结构是 &lt;code&gt;Context&lt;/code&gt; 接口：&lt;/p&gt;</description></item><item><title>Go 协程与管道</title><link>https://morvencao.github.io/posts/golang-channel/</link><pubDate>Thu, 26 Apr 2018 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/golang-channel/</guid><description>&lt;p&gt;说到 Go 语言，不得不提 Go 语言的并发编程。Go 从语言层面增加了对并发编程的良好支持，不像 Python、Java 等其他语言使用 Thread 库来新建线程，同时使用线程安全队列库来共享数据。Go 语言对于并发编程的支持依赖于 Go 语言的两个基础概念：Go 协程（Routine）和Go 管道（Channel）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 也许我们还对并发(Concurrency)和并行(Parallelism)傻傻分不清楚，在这里再次强调两者的不同点：&lt;/p&gt;
&lt;p&gt;Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.&lt;/p&gt;
&lt;p&gt;也就是说，并发是在同一时间处理多件事情，往往是通过编程的手段，目的是将 CPU 的利用率提到最高；而并行是在同一时间做多件事情，需要多核 CPU 的支持。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="go-协程"&gt;Go 协程&lt;/h2&gt;
&lt;p&gt;Go 协程是 Go 语言并行编程的核心概念之一，是比 Thread 更轻量级的并发单元，完全处于用户态并由 Go 语言运行时管理，最小 Go 协程只需极少的栈内存(大约是4~5KB)，这样十几个Go 协程的规模可能体现在底层就是五六个线程的大小，最高同时运行成千上万个并发任务；同时，Go 语言内部实现了 Go 协程之间的内存共享使得它比 Thread 更高效，更易用，我们不必再使用类似于晦涩难用的线程安全队列库来同步数据。&lt;/p&gt;
&lt;h3 id="创建-go-协程"&gt;创建 Go 协程&lt;/h3&gt;
&lt;p&gt;要创建一个Go 协程，我们只需要在函数调⽤语句前添加 &lt;code&gt;go&lt;/code&gt; 关键字，Go 语言的调度器会自动将其安排到合适的系统线程上执行。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;go&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;f&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;x&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;y&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;z&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;会启动一个新的 Go 协程并执行 &lt;code&gt;f(x, y, z)&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go 接口与组合</title><link>https://morvencao.github.io/posts/golang-interface-and-composition/</link><pubDate>Mon, 26 Feb 2018 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/golang-interface-and-composition/</guid><description>&lt;h2 id="go-接口与鸭子类型"&gt;Go 接口与鸭子类型&lt;/h2&gt;
&lt;p&gt;什么是“鸭子类型”？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;以上引用自维基百科的解释描述了什么是“鸭子类型”，所谓鸭子类型，是动态编程语言的一种对象推断策略，它更关注对象如何被使用，而不是对象的类型本身。&lt;/p&gt;
&lt;p&gt;传统的静态语言，如 Java、C++ 必须显示地声明类型实现了某个接口，之后，才能将类型用在任何需要这个接口的地方，否则编译都不会通过，这也是静态语言比动态语言更安全的原因。但是 Go 语言作为一门“现代”静态语言，使用的是动态编程语言的对象推断策略，它更关注对象能如何被使用，而不是对象的类型本身。也就是说，Go 语言引入了动态语言的便利，同时又会进行静态语言的类型检查，因此，它采用了折中的做法：不要求类型显示地声明实现了某个接口，只要实现了相关的方法即可，编译器就能检测到。&lt;/p&gt;
&lt;p&gt;举个例子，下面的代码片段先定义一个接口，和使用此接口作为函数的参数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;type&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;IGreeting&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;interface&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;greeting&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;()&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;func&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;sayHello&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;i&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;IGreeting&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;i&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;greeting&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;()&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下来再定义两个结构体：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;type&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;A&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;struct&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;func&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;a&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;A&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;greeting&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;()&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;fmt&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Println&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;Hi, I am A!&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;type&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;B&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;struct&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;func&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#000"&gt;b&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;B&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;greeting&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;()&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt; &lt;/span&gt;&lt;span style="color:#000"&gt;fmt&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;Println&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#4e9a06"&gt;&amp;#34;Hi, I am B!&amp;#34;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;)&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;&lt;span style="color:#f8f8f8;text-decoration:underline"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最后，在 &lt;code&gt;main()&lt;/code&gt; 函数里调用 &lt;code&gt;sayHello()&lt;/code&gt; 函数：&lt;/p&gt;</description></item><item><title>又是冬至日</title><link>https://morvencao.github.io/posts/thoughts_in_winter_solstice/</link><pubDate>Fri, 22 Dec 2017 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/thoughts_in_winter_solstice/</guid><description>&lt;p&gt;冬至已到，也该向2017告别了。&lt;/p&gt;
&lt;p&gt;最近酝酿着写写2017年，但到真正提笔的时候却无从下手。2017年，我一直很努力，却总是停留在原地。该离开的终究没有留下来，该坚持的还是半途而废。&lt;/p&gt;
&lt;p&gt;2017发生了很多事，心情一直处于低谷，时常在深邃的夜晚开始怀疑自己当初的选择。理想、信念和情感&amp;hellip;所有种种陪伴了自己青春岁月的精神支撑，在这冰冷冷的，麻木的，毫无人性的现实世界里显得沧桑而无力。可能是自己过于“玻璃心”，也经常被周围的人添加“不成熟”的标签。&lt;/p&gt;
&lt;p&gt;不是我无法承受现实世界的洗礼，我只是想按照自己的方式来生活。仔细想想，这些年虽然变化不少，但自己始终是个理想主义斗士。&lt;/p&gt;
&lt;p&gt;以前的我有自己的原则，却没有太多的阅历，此外，行动力也不太够，但是仍然对这个世界充满向往。
而现在的我，却因为短期内见过太多社会的黑暗面，变得无法坚持自己的信念而愤世嫉俗，因为我内心的良知始终让我无法和这个美好而丑陋的世界和解。&lt;/p&gt;
&lt;p&gt;我在无数个夜晚想象着拥有一台时光机器，带着自己回到过去，用一种对世界纯粹的热情感染早已麻木的自己，提醒自己世界上还有正义，原则还有信念等一众美好的东西。&lt;/p&gt;
&lt;p&gt;在接下来的时间内，我期待用自己的经历和行动力，告诫尚处于十字路口的自己坚定内心的选择，而不是在理想的分崩离析中不断回味那些外表逐渐模糊但内在却深入骨髓而不能磨灭的印记。&lt;/p&gt;
&lt;p&gt;不管2017年对我来说是多么 tough 的一年，毫无疑问，它也将会成为我永远都铭记和感恩的一年。&lt;/p&gt;
&lt;p&gt;希望在即将到来的2018年里，不要再掉进自己思想的漩涡里，重新拿回了生活的主导权，不忘初心，坚持梦想。&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code&gt; 人生几回仿往事，山形依旧枕寒流。 
 ——刘禹锡《西塞山怀志》
&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;</description></item><item><title>十年，火影</title><link>https://morvencao.github.io/posts/ten-years-for-naruto/</link><pubDate>Tue, 10 Oct 2017 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/ten-years-for-naruto/</guid><description>&lt;p&gt;&lt;img src="https://i.loli.net/2019/03/17/5c8de5d5427e6.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;去年听到火影忍者漫画完结的时候，心里是极其复杂的。急于见证万年吊车尾主人公鸣人的结局，却也充满对于十年青春陪伴的不舍。&lt;/p&gt;
&lt;p&gt;第一次见到火影是一个黄头发中二少年踩着树杈飞来飞去，身后跟着几个小屁孩嘴里大叫着“佐助”。当时觉得这种需要按着牛顿棺材板才能看的动漫实在无聊，但是正值高中紧张复习期间，偶尔借用同桌的“最黑科技”纽曼 MP4 看几集缓减一下复习的压力。&lt;/p&gt;
&lt;p&gt;对火影路转粉应该是在“复制忍者”旗木卡卡西与原雾隐村“忍刀七人众”之一的桃地再不斩在天桥大决战。当时和几个小伙伴周末躲在教室偷用教室的多媒体反复播放那几集，俨然被各种各样的结印手势以及高潮迭起的热血剧情吸引得神魂颠倒。一方是年仅12岁时就成为上忍木叶天才旗木卡卡西，加上可以复制了上千种忍术的开挂写轮眼；另一方是雾隐村“忍刀七人众”之一、无声杀人术技巧之高超的“鬼人”桃地再不斩。两人实力相近，却有着不同的目标，经过几番焦灼战斗，最后在漫天的飞雪中，当再不斩死在白的身边时，流着泪说道：“如果可以的话，真想和你去同一个地方。”与此同时，一颗雪花的结晶则因为白刚刚死去时残留的体温而在白的眼角融化流下，仿佛印证了再不斩那句“白，是你在哭泣么？”再配上背景音乐高梨康治的《Sadness and Sorrow》，​这成为火影里最初也是最让人感动以及震撼的画面。&lt;/p&gt;
&lt;p&gt;其实，火影的战斗场景非常丰富，除了高速飞行和冲刺、拳脚过招、立体镜头、火焰爆发等，还会有很多战斗的细节与伏笔。超燃的热血剧情加上富有逻辑性的战斗场景设计完全让高中时期的我们进入了节奏。随着剧情的展开，久而久之，开始对“万年吊车尾”鸣人的未来越来越感兴趣。鸣人，见证了白心甘情愿为再不斩牺牲，感同身受地与我爱罗诉说着相同的过去，将佐助视为最重要的羁绊，为了对小樱的承诺，拼尽全力追回佐助，独自练习着更强的忍术，为自己的父母深深地感到骄傲，更想用爱感化九尾，他所经历的一切，都为他贯彻自己的忍道。正是印证了鼬所说的“不是当上火影的人才能得到认可，只有得到大家的认可才能当上火影”。&lt;/p&gt;
&lt;p&gt;其实不止主人公，火影里面每个人物都是栩栩如生。
实力又神秘、帅气又低调、漫不经心又可靠、冷酷中还有温暖的天才忍者旗木卡卡西；
高冷而睿智、隐秘而伟大、残忍却温柔、怀揣光明于黑暗中独自行走的最完美忍者宇智波鼬；
猥琐好色而真性情、荒诞不羁却坚韧刚强、洒脱随和而正气凛然、推动整个火影进程的自来也老师。&lt;/p&gt;
&lt;p&gt;我想火影完结对我来说不是件过于悲伤的事情，尽管那天听到消息时我有种不可填补的缺失感，但是关于鸣人的故事暂时告一段落，但新的故事还在延续，更何况我们还有高梨康治大师级的音乐。每个人都有自己的人生，无论振作拼搏，还是颓废懈怠，时间终会带走一切，我们能留下的，只是种子，就像火影的种子早已埋在心底，何时发芽？也许明天，也许就是现在。&lt;/p&gt;</description></item><item><title>毕业这两年</title><link>https://morvencao.github.io/posts/two-years-after-graduation/</link><pubDate>Tue, 20 Jun 2017 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/two-years-after-graduation/</guid><description>&lt;p&gt;步入6月，一场青春的盛宴，如期而至，这场盛宴的主题，是毕业。我虽是这场盛宴之外的人，但看到微博上在校同学们的种种分享，仿佛是在提醒自己：你都毕业两年了！&lt;/p&gt;
&lt;p&gt;是啊，我毕业都两年了！&lt;/p&gt;
&lt;p&gt;想到这个，心中难免感慨万千，但是要谈谈这万千感慨，却又不知从何说起，真有一种情深而何以往之的感觉！&lt;/p&gt;
&lt;p&gt;毕业这两年，让我渐渐感到陌生的，是自己。我似乎已经越来越不清楚自己是谁，越来越不知道该往何方。我时而追忆大学时代的那个自己，以寻求一点自我的慰藉。当慰藉醒来，我才猛然发现，和过去的自己相比，我当前的生命轨迹，正在不断下坡的过程。&lt;/p&gt;
&lt;p&gt;大学时的那个我，总得而言，是一个乐于奉献、敢于担当的人。从大一入学之处欢呼雀跃，奔走于各个社团之间，到研究生逃离实验室，和几个“疯狂的哥们儿”没日没夜地呆在破烂不堪的创业楼里编写爬虫系统，那时候感觉自己还能“输得起”，所以做什么事情都不惧困难，在别人看来那是“不务正业”，自己却乐此不彼。&lt;/p&gt;
&lt;p&gt;但是从毕业到现在的这两年的工作经历，平平淡淡，没有输入，我不敢直视这是我自己。当然，并不是我现在的处境有多么的不堪，而是精神上“斗志泯灭”。回头看看那个曾经有着纯高理想追求，并定会为之奋斗的自己，如今正处于一种“望风披靡”，追求“息事宁人”的状态。&lt;/p&gt;
&lt;p&gt;毕业这两年，我一直就职于同一家公司，两年前的今天，我期待着可以顺风顺水地“大干一番”，毕竟公司肯定期待纯粹的技术出生，坚持技术路线的新鲜血液。的确，我投入了极大的热情，也获得了良好的发展。但是由于某种原因，公司一直处于”不稳定期“，自己也努力适应公司公司的转变策略。这样的好处是很明显的，经过几次的变化，虽然职位再三调整，但自己已经适应公司工作环境，也接触了目前各个产品线上的研发同事，合作也非常愉快，显然，公司需要这样能跟着公司战略转变的员工。然而，经过几轮的变化，自己心态早已从之前的“主动学习”，“积极探索”转变到“随波追流”，追求“完成自己分内工作就行”。我不知道这种变化是一种普遍现象，还是我自己开始濒临堕落边缘。&lt;/p&gt;
&lt;p&gt;对工作没有了开始时的热情，问题到底处在哪里？整个而言，我现在仍然处于一个迷茫期，前路漫漫，不知所以。&lt;/p&gt;
&lt;p&gt;虽然很多的事还没有想清楚，但并不意味着活得糊涂。我这两年虽然让自己的一些有点沉寂了，但是很多东西，我依然在坚持。坚持对善的向往，坚持对独立人格的向往，坚持读书学习思考，时刻保持着对人生观与价值观的自信。暂时得不到太多，只能好好坚持一些自认为可贵的东西，唯有坚持，才有改变的可能。&lt;/p&gt;</description></item><item><title>从零开始认识 iptables</title><link>https://morvencao.github.io/posts/iptables-wiki/</link><pubDate>Sat, 20 May 2017 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/iptables-wiki/</guid><description>&lt;p&gt;在使用 Linux 的过程中，很多人和我一样经常接触 iptables，但却只知道它是用来设置 Linux 防火墙的工具，不知道它具体是怎么工作的。今天，我们就从零开始认识一下 Linux 系统下 iptables 的具体工作原理。&lt;/p&gt;
&lt;p&gt;iptables 是 Linux 上常用的防火墙软件 netfilter 项目的一部分，所以要讲清楚 iptables，我们先理一理什么是防火墙？&lt;/p&gt;
&lt;h2 id="什么是防火墙"&gt;什么是防火墙&lt;/h2&gt;
&lt;p&gt;简单来说，防火墙是一种网络隔离工具，部署于主机或者网络的边缘，目标是对于进出主机或者本地网络的网络报文根据事先定义好的规则做匹配检测，规则匹配成功则对相应的网络报文做定义好的处理（允许，拒绝，转发，丢弃等）。防火墙根据其管理的范围来分可以将其划分为&lt;strong&gt;主机防火墙&lt;/strong&gt;和&lt;strong&gt;网络防火墙&lt;/strong&gt;；根据其工作机制来区分又可分为&lt;strong&gt;包过滤型防火墙（netfilter）&lt;strong&gt;和&lt;/strong&gt;代理服务器（Proxy）&lt;/strong&gt;。我们接下来在这篇笔记中主要说说&lt;strong&gt;包过滤型防火墙（netfilter）&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 也有人将 &lt;a href="https://en.wikipedia.org/wiki/TCP_Wrappers"&gt;TCP Wrappers&lt;/a&gt; 也划分为防火墙的一种，它是根据服务程序软件的名称来处理网络数据包的工具。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="包过滤型防火墙"&gt;包过滤型防火墙&lt;/h2&gt;
&lt;p&gt;包过滤型防火墙主要依赖于 Linux 内核软件 netfilter，它是一个 Linux 内核“安全框架”，而 iptables 是内核软件 netfilter 的配置工具，工作于用户空间。iptables/netfilter 组合就是 Linux 平台下的过滤型防火墙，并且这个防火墙软件是免费的，可以用来替代商业防火墙软件，来完成网络数据包的过滤、修改、重定向以及网络地址转换（nat）等功能。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 在有些 Linux 发行版上，我们可以使用 &lt;code&gt;systemctl start iptables&lt;/code&gt; 来启动 iptables 服务，但需要指出的是，iptables 并不是也不依赖于守护进程，它只是利用 Linux 内核提供的功能。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Linux 网络管理员通过配置 iptables 规则以及对应的网路数据包处理逻辑，当网络数据包符合这样的规则时，就做执行预先定义好的相应处理逻辑。可以简单的总结为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IF network_pkg match rule; THEN
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; handler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FI
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中规则可以包括匹配数据报文的源地址、目的地址、传输层协议（TCP/UDP/ICMP/..）以及应用层协议（HTTP/FTP/SMTP/..）等，处理逻辑就是根据规则所定义的方法来处理这些数据包，如放行（accept），拒绝（reject），丢弃（drop）等。&lt;/p&gt;
&lt;p&gt;而 netfilter 是工作于内核空间当中的一系列网络协议栈的钩子（hook），为内核模块在网络协议栈中的不同位置注册回调函数。也就是说，在数据包经过网络协议栈的不同位置时做相应的由 iptables 配置好的处理逻辑。
netfilter 中的五个钩子（这里也称为五个关卡）分别为：PRE_ROUTING/INPUT/FORWARD/OUTPUT/POST_ROUTING，网络数据包的流向图如下图所示：&lt;/p&gt;</description></item><item><title>编写健壮的 Shell 脚本</title><link>https://morvencao.github.io/posts/how-to-write-robust-shell-script/</link><pubDate>Mon, 06 Feb 2017 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/how-to-write-robust-shell-script/</guid><description>&lt;p&gt;编写 Shell 脚本应该是程序员必须掌握的技能。因为 Shell 脚本简单易上手的特性，在日常工作中，我们经常使用它来自动化应用的测试部署、环境的搭建清理等。其实在编写运行 Shell 脚本的时候也会遇到各种坑，稍不注意就会导致 Shell 脚本因为各种原因不能正常执行。实际上，编写健壮可靠的 Shell 脚本也是有很多技巧的，今天我们就来探讨一下。&lt;/p&gt;
&lt;h2 id="设置-shell-的默认执行环境参数"&gt;设置 Shell 的默认执行环境参数&lt;/h2&gt;
&lt;p&gt;在执行 Shell 脚本的时候，通常都会创建一个新的 Shell ，比如，当我们执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bash script.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们指明使用 &lt;code&gt;bash&lt;/code&gt; 会创建一个新的 Shell 来执行 &lt;code&gt;script.sh&lt;/code&gt; ，同时给定了这个执行环境默认的各种参数。 &lt;code&gt;set&lt;/code&gt; 命令可以用来修改 Shell 环境的运行参数，不带任何参数的 &lt;code&gt;set&lt;/code&gt; 命令，会显示所有的环境变量和 Shell 函数。对于所有可以定制的运行参数，请查看&lt;a href="https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html"&gt;官方手册&lt;/a&gt;，我们重点介绍其中最常用的四个。&lt;/p&gt;
&lt;h3 id="跟踪命令的执行"&gt;跟踪命令的执行&lt;/h3&gt;
&lt;p&gt;默认情况下， Shell 脚本执行后只显示运行结果，不会展示结果是哪一行代码的输出，如果多个命令连续执行，它们的运行结果就会连续输出，导致很难分清一串结果是什么命令产生的。 &lt;code&gt;set -x&lt;/code&gt; 用来在运行结果之前，先输出执行的那一行命令，行首以 &lt;code&gt;+&lt;/code&gt; 表示是命令而非命令输出，同时，每个命令的参数也会展开，这样我们可以清晰地看到每个命令的运行实参，这对于 Shell 脚本的 debug 来说非常友好。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;&lt;/span&gt;&lt;span style="color:#204a87"&gt;set&lt;/span&gt; -x
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000"&gt;v&lt;/span&gt;&lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#0000cf;font-weight:bold"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87"&gt;echo&lt;/span&gt; &lt;span style="color:#000"&gt;$v&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87"&gt;echo&lt;/span&gt; &lt;span style="color:#4e9a06"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# output:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# + v=5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# + echo 5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# 5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# + echo hello&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# hello&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;set -x&lt;/code&gt; 还有另一种写法： &lt;code&gt;set -o xtrace&lt;/code&gt; 。&lt;/p&gt;</description></item><item><title>后会无期，2016</title><link>https://morvencao.github.io/posts/farewell-2016/</link><pubDate>Sun, 18 Dec 2016 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/farewell-2016/</guid><description>&lt;p&gt;2016年，很长又很短。
大学毕业，拼命折腾。
概括起来，三言两语便是全部，往细里谈，三天三夜也不够。
但我还是要写，有些情感，只能用文字表达，有些思考，在笔尖之下才会刻骨铭心。&lt;/p&gt;
&lt;h2 id="生活"&gt;生活&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;从参加工作到现在，差不多一年多了。这期间的大部分时间我都处于“独处”的状态。每天8点准时起床，洗脸刷牙，偶尔去小区外面买个早餐，然后步行到公司。下班不用急着回家，独自呆在电脑前听听歌、看看书。有时候公司周围吃个晚饭，差不多八九点收拾东西回家，天气好的话去球场打打球。回到家后洗澡、刷知乎、追美剧。似乎这一切都靠着惯性驱使着。&lt;/p&gt;
&lt;p&gt;我其实对于“独处”是有一定经验的。除了碍于情面上的应酬，大多时候我都喜欢一个人呆着。我比较享受这种独自生活的状态。看书，听歌，看电影，玩手机，或者沏一杯茶然后发呆愣神的呆着，我都很喜欢。“独处”的时候一定要设立一个小目标或者愿景，不管是看一部电影，还是读一本书的一个章节，甚至是完成程序的一个功能；尽量不要再这个目标完成之前去主动打断它。此外，不要在独处的时候做重复无意义的事情，比如刷短视频。&lt;/p&gt;
&lt;h2 id="工作"&gt;工作&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;一直在思考，自己到底适合做什么样的开发岗位。从大学本科阶段到研究生，我一直主动涉猎各种计算机的知识，打造自己的技术栈。从最初的汇编语言与 C、到面向对象语言、再到函数式编程，从小型单片机与操作系统到大型分布式系统缓存/并发的设计与实现。我好像从来没有停止过获取新的知识，总想着扩展自己的技能树，却没有沉下心去专攻某项技能。总关注于横向领域的扩充，却忽略技能纵向深度的延伸。&lt;/p&gt;
&lt;p&gt;从这方面来讲，2016年确实是等待以及转身的重要节点。因为经历多次工作角色的转变，我不确定什么职位是最适合自己的。说实话，之前没有想过自己会从事前端开发，毕竟发展速度太快，每天都有新的轮子出现，难免不会迷失在其中。但是真正接过这个角色之后，就不要想那么多，我才27岁又不是72岁，既然入了这一行，就要有这一行的“匠人”精神。我一直也没认为 IT 这行多么了不起，我们都只不过是个普通的“手艺人”，经历多年打磨自己的大脑，只有足够优秀才能制作出好的作品。但也正是因为如此，我们可以尽情发挥自己的创意。&lt;/p&gt;
&lt;p&gt;从2016年4月开始，开始弥补自己前段领域的空白：从 html 到 jade，从 css 到 less/sass，从 javascript 到 typescript，从 jQuery 到 angular，从 grunt 到 webpack。虽然自己之前也略有涉猎前端知识，但对完整的前端技术栈知识略知皮毛。从最初的简单地写写页面到现在轻松构建整个项目框架，偶尔写个“轮子”提供产品的个性化解决方案。回过头来看，这一年一路走来，发现原来一切其实都不难，只要愿意沉下心去思考、研究，虽然踩了很多坑，但也收获颇丰。现在大部分人对于前段的认识还停留在“刀耕火种”的阶段，其实这几年前端领域的快速发展导致前端工程化已经成为不可逆转的趋势。&lt;/p&gt;
&lt;h2 id="对2017年的展望"&gt;对2017年的展望&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;总的来说，2016年对我来说，还算满意。没有忘记自己的初心，也在不断获取想得到的东西，这比迷失自我的成功更难得吧。希望2017年继续保持这种状态，勿骄勿燥。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多接触移动端开发领域，不管是 iOS 还是 Android&lt;/li&gt;
&lt;li&gt;系统学习 UI/UX 方面的知识&lt;/li&gt;
&lt;li&gt;多读些历史人文方面的书籍&lt;/li&gt;
&lt;li&gt;保持健康的生活习惯&lt;/li&gt;
&lt;li&gt;多维度提升自己的能力&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Webpack 使用小结</title><link>https://morvencao.github.io/posts/webpack-summary/</link><pubDate>Sun, 20 Nov 2016 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/webpack-summary/</guid><description>&lt;p&gt;分而治之是软件工程领域的重要思想，对于复杂度日益增加的前端也同样适用。一般前端团队选择合适的框架之后就要开始考虑开发维护的效率问题。而模块化是目前前端领域比较流行的分而治之手段。&lt;/p&gt;
&lt;p&gt;javascript 模块化已经有很多规范和工具，例如 &lt;code&gt;CommonJS/AMD/requireJS/CMD/ES6 Module&lt;/code&gt; ，在上篇文章中有详细的介绍。 CSS 模块化基本要依靠 &lt;code&gt;Less&lt;/code&gt; , &lt;code&gt;Sass&lt;/code&gt; 以及 &lt;code&gt;Stylus&lt;/code&gt; 等于处理器的 &lt;code&gt;import/minxin&lt;/code&gt; 特性实现。而 html 以及 html 模版和其他资源比如图片的模块化怎么去处理呢？&lt;/p&gt;
&lt;p&gt;这也正是 webpack 要解决的问题之一。严格来说， webpack 是一个模块打包工具，它既不像 requireJS 和 seaJS 这样的模块加载器，也不像 grunt 和 gulp 这样优化前端开发流程的构建工具，像是两类工具的集合却又远不止如此。&lt;/p&gt;
&lt;p&gt;总的来说， Webpack 是一个模块打包工具，它将 js 、 css 、 html 以及图片等都视为模块资源，这些模块资源必然存在某种依赖关系， webpack 就是通过静态分析各种模块文件之间的依赖关系，通过不同种类的 Loader 将所有模块打包成起来。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://webpack.github.io/assets/what-is-webpack.png" alt=""&gt;&lt;/p&gt;
&lt;h2 id="webpack-vs-gulp"&gt;webpack VS gulp&lt;/h2&gt;
&lt;p&gt;严格来说， gulp 与 webpack 并没有可比性。 gulp 应该和 grunt 属于同一类工具，能够优化前端工作流程，比如压缩合并 js 、css ，预编译 typescript 、 sass 等。也就是说，我们可以根据需要配置插件，就可以将之前需要手动完成的任务自动化。 webpack 作为模块打包工具，可以和 browserify 相提并论，两者都是预编译模块化解决方案。相比 requireJS 、 seaJS 这类“在线”模块化方案更加智能。因为是“预编译”，所以不需要在浏览器中加载解释器。另外，你可以直接在本地编写 js ，不管是 AMD/CMD/ES6 风格的模块化，都编译成浏览器认识的 js 。&lt;/p&gt;</description></item><item><title>Javascript 模块化开发</title><link>https://morvencao.github.io/posts/developing-modular-javascript/</link><pubDate>Sun, 16 Oct 2016 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/developing-modular-javascript/</guid><description>&lt;p&gt;随着互联网时代的到来，前端技术更新速度越来越快。起初只要在 &lt;code&gt;script&lt;/code&gt; 标签中嵌入几行代码就能实现一些基本的用户交互，到现在随着 Ajax、jQuery、MVC 以及 MVVM 的发展，Javascript 代码量变得日益庞大复杂。&lt;/p&gt;
&lt;p&gt;网页越来越像桌面程序，需要一个团队分工协作、进度管理、单元测试等等。开发者不得不使用软件工程的方法，管理网页的业务逻辑。Javascript 模块化开发，已经成为一个迫切的需求。理想情况下，开发者只需要实现核心的业务逻辑，其他都可以加载别人已经写好的模块。&lt;/p&gt;
&lt;p&gt;但是，Javascript 不是一种模块化编程语言，它不支持“类”（class），更别提“模块”（module）了。直到前不久 ES6 正式定稿，Javascript 才开始正式支持“类”和“模块”，但还需要很长时间才能完全投入实用。&lt;/p&gt;
&lt;h2 id="什么是模块化"&gt;什么是模块化&lt;/h2&gt;
&lt;p&gt;模块是任何大型应用程序架构中不可缺少的一部分，一个模块就是实现特定功能的代码区块或者文件。模块可以使我们清晰地分离和组织项目中的代码单元。在项目开发中，通过移除依赖、松耦合可以使应用程序的可维护性更强。有了模块，开发者就可以更方便地使用别人的代码，想要什么功能，就加载什么模块。模块开发需要遵循一定的规范，否则就会混乱不堪。&lt;/p&gt;
&lt;p&gt;Javascript 社区做了很多努力，在现有的运行环境中，实现&amp;quot;模块&amp;quot;的效果。本文总结了当前 Javascript 模块化编程的最佳实践，说明如何投入实用。&lt;/p&gt;
&lt;h2 id="javascript-模块化基本写法"&gt;Javascript 模块化基本写法&lt;/h2&gt;
&lt;p&gt;在第一部分，将讨论基于传统 Javascript 语法的模块化写法。&lt;/p&gt;
&lt;h3 id="原始写法"&gt;原始写法&lt;/h3&gt;
&lt;p&gt;模块就是实现特定功能的一组方法。只要把不同的函数（以及记录状态的变量）简单地放在一起，就算是一个模块：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;function&lt;/span&gt; &lt;span style="color:#000"&gt;func1&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#8f5902;font-style:italic"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;function&lt;/span&gt; &lt;span style="color:#000"&gt;func2&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#8f5902;font-style:italic"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面的函数 &lt;code&gt;func1()&lt;/code&gt; 和 &lt;code&gt;func2()&lt;/code&gt;，组成一个模块。使用的时候，直接调用就行了。这种做法的缺点很明显：&amp;ldquo;污染&amp;quot;了全局变量，无法保证不与其他模块发生变量名冲突，而且模块成员之间看不出直接关系。&lt;/p&gt;
&lt;h3 id="对象写法"&gt;对象写法&lt;/h3&gt;
&lt;p&gt;为了解决上面的缺点，可以把模块写成一个对象，所有的模块成员都放到这个对象里面：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#000"&gt;moduleA&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#204a87;font-weight:bold"&gt;new&lt;/span&gt; &lt;span style="color:#204a87"&gt;Object&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#000"&gt;_count&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#0000cf;font-weight:bold"&gt;0&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#000"&gt;func1&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#204a87;font-weight:bold"&gt;function&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#8f5902;font-style:italic"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;&lt;/span&gt;	&lt;span style="color:#000;font-weight:bold"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#000"&gt;func2&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#204a87;font-weight:bold"&gt;function&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#8f5902;font-style:italic"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;&lt;/span&gt;	&lt;span style="color:#000;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面的函数 &lt;code&gt;func1()&lt;/code&gt; 和&lt;code&gt;func2()&lt;/code&gt;，都封装在 &lt;code&gt;moduleA&lt;/code&gt; 对象里，使用的时候，就是调用这个对象的属性。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000"&gt;moduleA&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;func1&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但是，这样的写法会暴露所有模块成员、内部状态可以被外部改写。比如，外部代码可以直接改变内部计数器的值：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000"&gt;moduleA&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;.&lt;/span&gt;&lt;span style="color:#000"&gt;_count&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#0000cf;font-weight:bold"&gt;3&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="立即执行函数写法"&gt;立即执行函数写法&lt;/h3&gt;
&lt;p&gt;使用&amp;quot;立即执行函数&amp;rdquo;（Immediately-Invoked Function Expression，IIFE），可以达到不暴露私有成员的目的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#000"&gt;moduleA&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;(&lt;/span&gt;&lt;span style="color:#204a87;font-weight:bold"&gt;function&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#000"&gt;_count&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#0000cf;font-weight:bold"&gt;0&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#000"&gt;func1&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#204a87;font-weight:bold"&gt;function&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#8f5902;font-style:italic"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;&lt;/span&gt;	&lt;span style="color:#000;font-weight:bold"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#204a87;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#000"&gt;func2&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#204a87;font-weight:bold"&gt;function&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#8f5902;font-style:italic"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;&lt;/span&gt;	&lt;span style="color:#000;font-weight:bold"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#204a87;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#000"&gt;func1&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#000"&gt;func1&lt;/span&gt;&lt;span style="color:#000;font-weight:bold"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#000"&gt;func2&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#000"&gt;func2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#000;font-weight:bold"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;})();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用上面的写法，外部代码无法读取内部的 &lt;code&gt;_count&lt;/code&gt; 变量：&lt;/p&gt;</description></item><item><title>Pied Pier 中的无损压缩算法</title><link>https://morvencao.github.io/posts/data-compression-in-pied-pier/</link><pubDate>Sat, 18 Jun 2016 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/data-compression-in-pied-pier/</guid><description>&lt;p&gt;最近终于追完了 HBO 的自制喜剧《硅谷》&lt;a href="https://www.imdb.com/title/tt2575988/"&gt;第三季&lt;/a&gt;。《硅谷》应该算是一部非常小众的美剧了，主要讲述了湾区几个 IT 男创业的故事，剧情并没有过多围绕他们怎么写代码，而是把关注点聚焦在创业想法的诞生过程以及初期公司的成立以及与风投斡旋过程中的戏剧冲突上，让“内行人”啼笑皆非。每季的后几集都有点儿燃，原本“改变世界”之类现实生活中的烂梗，却是最触动内心的！&lt;/p&gt;
&lt;p&gt;《硅谷》之所以与众不同，还因为剧中的很多理论都很值得推敲。其中最重要的大概是第二季第八集中 Richard 提出的所谓 &lt;strong&gt;middle-out&lt;/strong&gt; 数据压缩算法，也正是基于此算法，才诞生了后来的 Pied Pier。为了便于理解，我们先来了解一下数据压缩算法的基本原理、信息熵以及霍夫曼编码。&lt;/p&gt;
&lt;h2 id="数据压缩的原理"&gt;数据压缩的原理&lt;/h2&gt;
&lt;p&gt;数据压缩原理很简单，概括起来就是找到那些重复出现的数据，然后用用更短的符号替代，从而达到缩短数据大小的目的。&lt;/p&gt;
&lt;p&gt;例如，我有一段文本 &lt;code&gt;ABCDABCDABCDABCDABCDABCD&lt;/code&gt;，显然我们使用 &lt;code&gt;6ABCD&lt;/code&gt; 也能替代原来的数据，因为可以根据 &lt;code&gt;6ABCD&lt;/code&gt; 推算出原文本 &lt;code&gt;ABCDABCDABCDABCDABCDABCD&lt;/code&gt;，这样的好处是数据从原来的24字节变成了5字节，数据压缩比为“5/24”约等于“20.8”。事实上，只要保证对应关系，可以用任意字符代替那些重复出现的字符串。这又让我想到了现在移动互联网时代广泛使用的 &lt;a href="https://en.wikipedia.org/wiki/Emoji"&gt;Emoji&lt;/a&gt;，我们可以使用一个简单的 Emoji 表情来表达原来需要多个字表达的意思。&lt;/p&gt;
&lt;p&gt;本质上，所谓“压缩”就是找出文件数据内容的概率分布，将那些出现概率高的部分代替成更短的形式。所以，内容越是重复的文件，就可以压缩地越小。比如 &lt;code&gt;ABCDABCDABCDABCDABCDABCD&lt;/code&gt;可以压缩成 &lt;code&gt;6ABCD&lt;/code&gt;。与之对应地，如果数据的内容毫无重复，就很难压缩。极端情况就是，遇到那些均匀分布的随机字符串，往往连一个字符都压缩不了。比如，任意排列的10个阿拉伯数字 &lt;code&gt;5271839406&lt;/code&gt;，就是无法压缩的；再比如，无理数 &lt;code&gt;π&lt;/code&gt; 也很难压缩。&lt;/p&gt;
&lt;p&gt;总结一下，压缩就是消除冗余的过程，用更精简的形式表达相同的复杂内容。可以想象，压缩过一次以后，文件中的重复字符串将大幅减少。好的压缩算法，可以将冗余降到最低，以至于再也没有办法进一步压缩。所以，压缩已经压缩过的文件（递归压缩），通常是没有意义的。&lt;/p&gt;
&lt;h2 id="数据压缩的极限"&gt;数据压缩的极限&lt;/h2&gt;
&lt;p&gt;我们可以从数学上用反证法证明数据压缩是有极限的，也就是不可能无限压缩一份数据而保证内容不丢失。&lt;/p&gt;
&lt;p&gt;假定任何文件都可以压缩到 n 个二进制位以内，那么最多有 2&lt;sup&gt;n&lt;/sup&gt; 种不同的压缩结果。这就是说，如果有 2&lt;sup&gt;n&lt;/sup&gt;+1 个文件，必然至少有两个文件会产生同样的压缩结果。这就意味着这两个文件不可能无损地还原。因此，得到证明，并非所有文件都可以压缩到 n 个二进制位以下。既然 n 是一个基于压缩文件数据确定的数字，那么这个 n 到底是多少？&lt;/p&gt;
&lt;p&gt;按照我们前面关于数据压缩原理的介绍，我们知道数据压缩可以分解成两个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;得到数据内容的概率分布，哪些部分出现的次数多，哪些部分出现的次数少&lt;/li&gt;
&lt;li&gt;对数据内容进行编码，用较短的符号替代那些重复出现的部分&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于一封确定的数据文件来说，它的概率分布是确定的，不同的压缩算法主要是因为第二部编码方式的不同，最优的压缩算法，当然是最短的符号表示作为替代原数据内容。&lt;/p&gt;
&lt;p&gt;我们使用数学归纳法来来演算一下 n 的值：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最简单的情况，我们要压缩的数据只有一部分；这一部分只有两个值，那么一个二进制数就可以表示；这一部分只有三个值，那么就需要两个二进制数来表示；这一部分有 n 个不同的值，那么就需要 log&lt;sub&gt;2&lt;/sub&gt;n 个二进制位来表示；&lt;/li&gt;
&lt;li&gt;假设在数据文件各个字符均匀出现的情况下，一个字符在某一部分中出现的概率是 p，也就是说这一部分可能会出现 1/p 种不同的情况，那么，这一部分就需要至少 log&lt;sub&gt;2&lt;/sub&gt;(1/p) 个二进制位来表示；&lt;/li&gt;
&lt;li&gt;推广开来，如果文件有 n 个部分组成，每个部分在文件中的出现概率分别为 p1、p2、&amp;hellip;pn，那么替代符号占据的二进制最少为下面这个式子：&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;log&lt;sub&gt;2&lt;/sub&gt;(1/p&lt;sub&gt;1&lt;/sub&gt;) + log&lt;sub&gt;2&lt;/sub&gt;(1/p&lt;sub&gt;2&lt;/sub&gt;) + ... + log&lt;sub&gt;2&lt;/sub&gt;(1/p&lt;sub&gt;n&lt;/sub&gt;)&lt;/p&gt;</description></item><item><title>JSON Web Token</title><link>https://morvencao.github.io/posts/jwt-quick-start/</link><pubDate>Wed, 20 Apr 2016 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/jwt-quick-start/</guid><description>&lt;p&gt;近几年，前后端分离大行其道，在典型的前后端分离的应用架构中，后端主要作为 Model 层，为前端提供数据访问的 API，前后端之间的通信需要在不可信（Zero Trust）的异构网络之间进行，为了保证数据安全可靠地在客户端与服务端之间传输，实现客户端认证就显得非常重要。而 HTTP 协议本身是无状态的，实现服务端的客户端认证的基础是记录客户端和服务端的对话状态。&lt;/p&gt;
&lt;p&gt;我们最熟悉的认证客户端的方式就是基于 session/cookie 的状态记录方式，基本流程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端向服务器端发送用户名和密码&lt;/li&gt;
&lt;li&gt;服务器验证通过后，创建新的对话（session）并保存保存相关数据，比如用户角色、登录时间等&lt;/li&gt;
&lt;li&gt;服务器向客户端返回一个 SESSIONID，写入客户端的 Cookie&lt;/li&gt;
&lt;li&gt;客户端随后的每一次请求，都会通过 Cookie，将 SESSIONID 传回服务器&lt;/li&gt;
&lt;li&gt;服务器收到 SESSIONID，取出相应 session 并与保存的 session 信息进行对比，由此得知用户的身份&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/07/13/5d29cd435e80182826.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;这种认证方式最大的问题是，没有分布式架构，无法支持横向扩展。如果使用一个服务器，该模式完全没有问题。但是，如果它是服务器群集或面向服务的跨域体系结构，则需要一个统一的 session 数据库库来保存会话数据实现共享，这样负载均衡下的每个服务器才可以正确的验证用户身份。&lt;/p&gt;
&lt;p&gt;举例来说，某企业同时有两个不同的网站 A 和网站 B 提供服务，如何做到用户只需要登录其中一个网站，然后它就会自动登录到另一个网站？&lt;/p&gt;
&lt;p&gt;一种解决方案是使用持久化 session 的基础设施，例如 Redis，写入 session 数据到持久层。收到新的客户端请求后，服务端从持久层查找对应的 session 信息。这种方案的优点在于架构清晰，而缺点是架构修改比较费劲，整个服务的验证逻辑层都需要重写，工作量相对较大。而且由于依赖于持久层的数据库或者类似系统，会有单点故障风险，如果持久层失败，整个认证体系都会挂掉。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/07/13/5d29c64a39f9615138.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;有没有别的方案呢？这就是我们即将要学习的 JWT(JSON Web Token) 认证方式。&lt;/p&gt;
&lt;h2 id="什么是-jwt"&gt;什么是 JWT&lt;/h2&gt;
&lt;p&gt;JWT 另辟蹊径，基于令牌（token）认证客户端，也就是说只需要在每次客户端请求的HTTP头部附上对应的 token，服务器端负责去检查 token 的签名来确保 token 没有被篡改，这样通过客户端保存数据，而不是服务器保存会话数据，每个请求都被发送回服务器端认证。&lt;/p&gt;
&lt;p&gt;根据&lt;a href="https://jwt.io/introduction/"&gt;官方&lt;/a&gt;的定义，JWT 是一套开放的标准（&lt;a href="https://tools.ietf.org/html/rfc7519"&gt;RFC 7519&lt;/a&gt;），它定义了一套简洁且安全的方案，可以在客户端和服务器之间传输 JSON 格式的 token 信息。&lt;/p&gt;
&lt;h2 id="jwt-的工作原理"&gt;JWT 的工作原理&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://i.bmp.ovh/imgs/2019/07/20f54328d6bd6e9e.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;JWT 服务端认证的基本原理是在服务器身份验证之后，将生成一个 JSON 对象并将其发送回客户端，如下所示：&lt;/p&gt;</description></item><item><title>写在「2015」年年末</title><link>https://morvencao.github.io/posts/2015-retrospect/</link><pubDate>Tue, 29 Dec 2015 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/2015-retrospect/</guid><description>&lt;p&gt;2015年马上就要过去了。一年一年是如此的相似，一年一年却又如此的不同。每到年底总想写点什么，算是给自己的一个总结、一个回忆、一个自我述说的契机。
2015年像很多年份一样是平常的，按部就班地数着日子过去了，虽然忙碌，但也收获颇多。2015年又是不平常的，因为2015年是我正式告别学校踏入&amp;quot;江湖&amp;quot;的第一年。2015年对我来说，关键字有「驾证」、「毕业」以及「入职」。&lt;/p&gt;
&lt;h2 id="驾证"&gt;驾证&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;之前总是一拖再拖，这次真是没法再推了。于是拉了宿舍一哥们儿入坑，组团报名。银子花出去之后便有了动力。从科目一理论到科目二场地考，再到科目三大路考，每周一到周四5点多爬起来赶班车去驾校练车。中午赶回来吃过饭休息之后再去实验室赶论文。还好，全部科目的考试都是一把过，算是对自己的慰藉吧。那几个月下来，认识了很多学车的同学，大多是即将毕业离开南京的学生，而且还认识了个南艺学珠宝设计的妹子（坏笑&amp;hellip;）。那段日子也确实是痛并快乐着，导致拿到驾照之后得了学车后遗症，每天早上天还没亮就睡不着了。学车本不是什么难事儿，只要安排得到，拿到驾照还是挺容易的，也不用为了考试顺利而铤而走险，贿赂教练或考官。&lt;/p&gt;
&lt;h2 id="毕业"&gt;毕业&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;又毕业了，这一次是真的毕业了。研究生毕业答辩明显比本科毕业答辩阶段严格得多，所以投入了更多的时间在论文上。庆幸自己有个好导师，选题也不错，所以答辩比较顺利。毕业典礼没有本科那么正式，辅导员只是选了一些同学去参加，毕业合影部分同学的脸也是p上去的。临近毕业那段时间各种手续要办，又是档案又是户口，不过总能抽出时间和室友开黑。直到离校当晚，拉着行李箱准备走出鼓楼校区，走在广州路熙熙攘攘的人群中才知道，这次不是回家，这次真要离开了，下次再来不知道是什么时候了。&lt;/p&gt;
&lt;h2 id="入职"&gt;入职&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;毕业后回家休息不到一个月正式入职。入职前一周收到 HR 发给我的入职材料列表，发现缺了“无犯罪记录”证明，当时懵了，因为这材料需要回学校保卫科去办。还好，联系到了在南京的同学和辅导员，虽然麻烦，但还是很快就搞定了。接下来就是正式入职，因为之前也有在 IBM 上海实习的经历，对 IBM 的总体情况和公司文化比较了解，所以很快就融入到新的工作环境中。但是计划赶不上变化，我的 hiring manager 所在的部门研发计划有变，所以就被 transfer 到其他组，不过这几个产品组相关性非常高，虽然在技术栈上稍有不同，但对于我来说还是可以轻松应对的。IBM 西安这边主要做的主要是高性能计算相关的产品，核心产品要两款，其他产品都是作为其上层附属产品。技术的选择上 java 多一些，核心产品上使用 C&amp;amp;C++ 多一些。令我不太满意的地方是，工作中对业务的熟悉程度远大于对技术的熟练上，这可能是大商业公司的通病。&lt;/p&gt;
&lt;h2 id="2016的期待"&gt;2016的期待&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;又到了许愿时间了，要做也要做有梦想的咸鱼～～&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;希望能去自己感兴趣的组，做自己感兴趣的事儿；&lt;/li&gt;
&lt;li&gt;希望抽出更多的时间看书，学习以及写博客；&lt;/li&gt;
&lt;li&gt;希望能多在工作中和外籍同事交流，锻炼自己的口语；&lt;/li&gt;
&lt;li&gt;希望坚持体育锻炼，一周跑两次步，周末打一次球；&lt;/li&gt;
&lt;li&gt;希望感情能够稳定下来；&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Ban&amp;Pick 机制与陪审团</title><link>https://morvencao.github.io/posts/ban-pick-in-dota-and-jury/</link><pubDate>Fri, 09 Oct 2015 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/ban-pick-in-dota-and-jury/</guid><description>&lt;p&gt;Dota(Defense of the Ancients) 最初只是由《魔兽争霸3：冰封王座》的一个 RPG 地图衍变而来，是一款支持多人即时对战的战略游戏。最早的 Dota 地图则在混乱之治时代就出现了，一位叫做 Euls 的玩家制作了第一张 Dota 地图：“Roc Dota”，随后，经过多个玩家进一步完善，以及 IceFrog 的多次修正和更新，游戏最终定格为两个阵营，玩家需要操作英雄，通过摧毁对方遗迹建筑来取得最终的胜利。这种多人在线的竞技模式后来被称为“Dota 类游戏”，对之后产生的多个竞技类游戏产生了深远的影响。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/03/17/5c8de2ebd6b5b.jpg" alt=""&gt;&lt;/p&gt;
&lt;h2 id="banpick-机制"&gt;Ban&amp;amp;Pick 机制&lt;/h2&gt;
&lt;p&gt;如何在比赛中选出一套让比赛的双方都能接受的英雄阵容，同时又要体现出竞技游戏的对抗性和平衡性。一般来说，游戏的对抗性表现为游戏元素的多元化。Dota 这款游戏共有102名英雄，有两个阵营各选五名英雄分辨占据近卫和天灾两方，分三路进行对抗。当然五名英雄的职责各不相同，Carry 位通常在地图的优势路发育，同时要有清晰的大局观，在比赛的后期发挥主力作用。Solo 位，一般在中路对抗，该位置要求选手在中前期有很强的带节奏能力。Ganker，也称为抗压位，游走于各路，配合队友完成击杀对方英雄。剩余的两个位置称为辅助位，主要配合团队控制视野，帮助队友打出优势，从而在战略上压制对方。&lt;/p&gt;
&lt;p&gt;假定 Dota 的英雄完全按照其所处的位置排列，（实战中完全不这样，经常原本12号位的英雄随着版本更迭称为辅助，反之亦然）如此每个位置的英雄也就约20名，如果我们再放弃掉一些不常用的英雄的话，实际让选手能够顺畅选择的英雄其实不多。这时，如何让场上选手得以公平竞技打出激情，就必须通过一套规则，选出一套让比赛双方均能够接受的阵容，从而增加比赛的观赏性。那么，选手们应当如何选取自己心仪的英雄呢？&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/03/17/5c8de34e8f156.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Dota 的设计者 IceFrog 最终采用了一套所谓的“Ban&amp;amp;Pick”的机制，这套机制最早是由千年之前一个在法国的英国人的发明。&lt;/p&gt;
&lt;h2 id="陪审制"&gt;陪审制&lt;/h2&gt;
&lt;p&gt;1135年，英国国王亨利一世去世，根据一份协定，英国国王的继承者将由自幼生长在法国的青年亨利二世于1154年担任。亨利二世是一位非常有野心的国王，开创了一个时代，人称“金雀花王朝”。可刚上任的他作为一个“外国人”，只要想在一个地区内树立权威，那么司法权则应当是极为重要的手段之一。在当时，高贵的法国贵族又如何听得懂英格兰的那些土话呢？如果听不懂别人说什么，作为一国之君，又如何来审理民间纠纷以确立自己的声望？亨利二世又能以怎样的方式，设计出一套完美的制度？&lt;/p&gt;
&lt;p&gt;最开始的时候，每当出现土地方面的诉讼时，会由法院发给争议双方一纸令状(令状或由司法大臣发布)，命令争议双方找来十二名和本案无关的人士在上帝面前发誓之后，做出对案件事实方面的裁决。而亨利二世则带人组成巡回法院，当他巡回到该地之后，对案件做出裁决。毕竟，由自己亲手提拔的大臣是懂法语的，自己审完案子下发，再由手下将其翻译成英语传达，是再好不过的事了。&lt;/p&gt;
&lt;p&gt;但是，那十二个与本案无关的人，是怎么选出来的？&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/03/17/5c8de36e810ef.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;陪审员的选择一开始是随机选，而且选的比较多。然后双方会有机会了解备选的人员，并且通过辩论来决定谁去谁留。当然，双方都会尽量留下对自己有利的陪审员（Pick），主要是根据职业、性别、年龄来判断是否会同情被告。还有几个机会可以不必给出理由而否定某一个陪审员（Ban）。最后确定一个名单。名单上的人就必须去，因为这个是公民义务的一部分，除非有特殊原因，要是故意不去那就要被起诉。同时，还会有几个备用人选，也必须去，以防有人生病或者出事来不了。
还有一个是选择陪审员的时候会尽量避免选择某些职业，比如律师、法学院的学生、老师、教授等等。主要是排除类似“权威”或者习惯教育指导别人的人，以防以一人之力左右整个陪审团的意见。&lt;/p&gt;
&lt;p&gt;对 Dota 这款游戏来说，情况也正是如此，IceFrog 必须采取一套制度，让选手们首选去掉那些对自己特别不利的英雄，从而不会让先选英雄的一方率先抢走版本当中最热门也是最厉害的英雄，以降低游戏的对抗程度。因为对 Dota 来说，选手们竞技环境越是公平，游戏的对抗程度越高，游戏的活力也就越持久。&lt;/p&gt;
&lt;p&gt;不过这套制度并非一经制定就一成不变，而是会根据玩家不断的反馈与游戏平衡的调整随之改进。Dota 的 Ban&amp;amp;Pick 规则方式的更迭几乎和 Dota 的游戏版本更迭频率一致。从 Dota 6.28X版本开始到现在的 Dota 6.83，每一代版本非但在游戏本身的平衡性上做出了重大调整，在 Ban&amp;amp;Pick 规则上，IceFrog 也从未掉以轻心。不论是 Ban&amp;amp;Pick 时间还是顺序，甚至于到底 Ban 掉多少名英雄，也是历经多个版本才最终确立。&lt;/p&gt;</description></item><item><title>大话「一致性哈希」</title><link>https://morvencao.github.io/posts/consistent-hash/</link><pubDate>Mon, 24 Aug 2015 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/consistent-hash/</guid><description>&lt;p&gt;伴随着系统规模的扩增，出现了很多分布式应用。比如在分布式系统中为了保证 Redis 的高可用需要搭建 Redis 对数据进行分槽处理，再比如 MySql 数据库存储的数据达到一定规模的时候需要对数据库分库操作。除了这些典型的分布式应用之外，比如我们要开发一款分布式作业调度系统，实际的执行作业的节点拥有不同的配置（CPU、内存以及 GPU 等），同时也分布在不同的地域，我们如何保证的作业尽可能调度到正确的节点，同时在新增节点或者某节点宕机的情况下保证尽可能减少对作业调度的影响呢？&lt;/p&gt;
&lt;p&gt;说白了，上面所述的问题就是如何把数据对象按照一定的规则映射到不同的机器上，我们能想到的最直观的方式就是哈希函数，借助于哈希函数，我们能够准确地知道数据对象需要映射到哪个节点。但是普通的哈希函数可以解决我们在分布式系统中遇到的数据对象映射的问题吗？&lt;/p&gt;
&lt;h2 id="简单哈希"&gt;简单哈希&lt;/h2&gt;
&lt;p&gt;我们还是以上面所说的分布式作业调度系统为例，联想到 Java 中哈希表 hashmap 的实现原理，通过哈希函数对作业数据对象的键来计算哈希值，最简单的哈希函数就是通过作业数据对象对作业执行节点总数执行取模运算，得到的哈希值可以看作是“哈希桶”，也就是作业应该调度到的节点。&lt;/p&gt;
&lt;p&gt;这看起来很简单地就解决了数据对象的映射问题，但是如果随着系统吞吐量的增加，我们需要重新增加一些新的计算节点到集群中，此时我们再次计算数据对象的哈希值的时候，取模运算的底数已经发生了变化，这会导致数据对象映射的结果也发生变化，也就是说作业调度的节点发生了变化，这对于一些对配置有特殊需要的作业来说，影响是巨大的。所以，我们需要想办法让这种情况不发生，这种情况发生的根本是哈希算法本身的特性导致的，直接使用取模的话这是无法避免的。&lt;/p&gt;
&lt;p&gt;最简单的哈希算法通过求模运算来计算关键字的哈希值，然后将关键字映射到有限的地址空间中，以便于快速检索关键字。由于简单哈希算法只是采用了求模运算，使得它存在很多缺陷:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;增删节点更新效率低：如果整个系统中的存储节点数量发生变化，哈希算法的哈希函数也需要变化。例如，增加或删除一个节点是，哈希函数将变化为 &lt;code&gt;hash(key)%(N±1)&lt;/code&gt;，这种变化使得所有关键字映射的地址空间都发生变化，也就是说，整个系统所有对象的映射地址都需要重新计算，这样系统就无法对外界请求做出正常的响应，将导致系统处于瘫痪状态；&lt;/li&gt;
&lt;li&gt;平衡性差：采用简单哈希算法的系统没有考虑不同节点间性能的差异，导致系统平衡性差。如果新添加的节点由于硬件性能的提升具有更强的计算能力，该算法不能有效地利用节点性能差异提高系统的负载；&lt;/li&gt;
&lt;li&gt;单调性不足：新的节点加入到系统中时，哈希函数的结果应能够保证之前的已分配映射地址的数据对象可以被映射到原地址空间。简单哈希算法不能满足单调性，一旦节点的数量发生变化，那么所有的数据对象映射的地址都会发生变化；&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="一致性哈希"&gt;一致性哈希&lt;/h2&gt;
&lt;p&gt;一致性哈希算法对简单哈希算法的缺陷进行了修正，保证系统在删除或者增加一个节点时，尽量不改变已有的数据对象到地址空间的映射关系，也就是尽量满足哈希算法的单调性。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;环形哈希空间&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一致性哈希算法将所有数据对象的键值映射到一个32位长的地址空间值中，即 &lt;code&gt;0~2^32-1&lt;/code&gt; 的数值空间，该地址空间首尾相连形成一个环状，即一个首(0)尾(2^32-1)相接的闭合圆环，如下面所示:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2020/12/19/rgu2VPbU9tRdSza.png" alt="consistent-hash-1.png"&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;把数值对象映射到哈希空间&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把所有的数值对象映射到环形哈希空间。考虑这样的4个数值对象：&lt;code&gt;object1~object4&lt;/code&gt;，通过哈希函数计算出的哈希值作为环形地址空间中的地址，这样4个数值对象在环上的分布如图所示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash(object1) = key1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;......
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash(object4) = key4;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://i.loli.net/2020/12/19/vDJG6LOrimbVy8j.png" alt="consistent-hash-2.png"&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;把机器节点映射到哈希空间：对于机器节点也使用相同的哈希函数映射到环形地址空间。一致性哈希对于数值对象和机器节点使用相同的哈希函数映射到同一个环形哈希空间中。然后沿着环形地址空间按照顺时针方向将所有的数据对象存储到最近的节点上：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;目前存在 &lt;code&gt;node A&lt;/code&gt;, &lt;code&gt;node B&lt;/code&gt;, &lt;code&gt;node C&lt;/code&gt; 共3台机器节点，通过哈希函数的计算，它们在环形地址空间上的映射结果将如图所示，可以看出这些机器节点在环形哈希空间中，基于对应的哈希值排序。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash(node A) = key A;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;......
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash(node C) = key C;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://i.loli.net/2020/12/19/3Pnfysc45LeiqWu.png" alt="consistent-hash-3.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 对于机器节点哈希值的计算，哈希函数的输入可以选择机器的 IP 地址或者机器名的字符串签名。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;从图中可以看出，所有的数值对象与机器节点处于同一环形哈希空间中，从数值对象映射到的位置开始顺时针查找对应的存储节点，并将数值对象保存到找到的第一个机器节点上。这样 &lt;code&gt;object1&lt;/code&gt; 存储到了 &lt;code&gt;node A&lt;/code&gt; 中，&lt;code&gt;object3&lt;/code&gt; 存储到了 &lt;code&gt;node B&lt;/code&gt;中，&lt;code&gt;object2&lt;/code&gt; 和&lt;code&gt; object4&lt;/code&gt; 都存储到了 &lt;code&gt;node C&lt;/code&gt; 中。处于这样的集群环境中，环形地址空间不会变更，通过计算每个数值对象的哈希地址空间值，就可以定位到对应的机器节点，也就是实际存储数值对象的机器。&lt;/p&gt;</description></item><item><title>SSH 协议以及端口转发</title><link>https://morvencao.github.io/posts/ssh-wiki/</link><pubDate>Thu, 19 Mar 2015 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/ssh-wiki/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Secure_Shell"&gt;SSH&lt;/a&gt; 估计是每台 Linux 机器的标配了。日程工作中程序员写完代码之后，很少在本机上直接部署测试，经常需要通过 SSH 登录到远程 Linux 主机上进行验证。实际上 SSH 具备多种功能，不仅仅是远程登录这么简单，今天我们就详细探讨一下 SSH 协议以及它的高级功能。&lt;/p&gt;
&lt;h2 id="ssh-原理"&gt;SSH 原理&lt;/h2&gt;
&lt;p&gt;SSH 是一种网络协议，用于计算机之间的加密登录，也就是说这种登陆是安全的。SSH 协议之所以安全，是因为它基于非对称加密，基本的过程可以描述为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端通过 &lt;code&gt;SSH user@remote-host&lt;/code&gt; 发起登录远程主机的请求&lt;/li&gt;
&lt;li&gt;远程主机收到请求之后，将自己的公钥发给客户端&lt;/li&gt;
&lt;li&gt;客户端使用这个公钥加密登录密码之后发给远程主机&lt;/li&gt;
&lt;li&gt;远程主机使用自己的私钥解密登陆请求，获得登录密码，如果正确，则建立 SSH 通道，之后所有客户端和远程主机的数据传输都要使用加密进行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;看似很完美是吗？其实有个问题，如果有人中途拦截了登录请求，将自己伪造的公钥发送给客户端，客户端其实并不能识别这个公钥的可靠性，因为 SSH 协议并不像 HTTPS 协议那样，公钥是包含在证书中心颁发的证书之中。所以有攻击者（中间人攻击）拦截客户端到远程主机的登陆请求，伪造公钥来获取远程主机的登录密码，SSH 协议的安全机制就荡然无存了。&lt;/p&gt;
&lt;p&gt;说实话，SSH 协议本身确实无法阻止这种攻击形式，最终还是依靠终端用户自身来识别并规避风险。&lt;/p&gt;
&lt;p&gt;比如，我们在第一次登录某一台远程主机的时候，会得到如下提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ssh user@remote-host
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The authenticity of host &lt;span style="color:#4e9a06"&gt;&amp;#39;10.30.110.230 (10.30.110.230)&amp;#39;&lt;/span&gt; can&lt;span style="color:#a40000"&gt;&amp;#39;&lt;/span&gt;t be established.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ECDSA key fingerprint is SHA256:RIXlybA1rgf4mbnWvLuOMAxGRQQFgfnM2+YbYLT7aQA.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Are you sure you want to &lt;span style="color:#204a87;font-weight:bold"&gt;continue&lt;/span&gt; connecting &lt;span style="color:#ce5c00;font-weight:bold"&gt;(&lt;/span&gt;yes/no&lt;span style="color:#ce5c00;font-weight:bold"&gt;)&lt;/span&gt;?
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个提示的意思是说无法确定远程主机的真实性，只能得到它的公钥指纹 (fingerprint) 信息，需要你确认是否信任这个返回的公钥。这里所说的“指纹”是非对称加密公钥的 MD5 哈希结果。我们知道为了保证非对称加密私钥的安全性，一般非对称加密公钥设置基本都不小于1024位，很难直接让终端用户去确认完整的非对称加密公钥，于是通过 MD5 哈希函数将之转化为128位的指纹，就很容易识别了。实际上，有很多网络应用程序都是用非对称加密公钥指纹来让终端用户识别公钥的可靠性。&lt;/p&gt;
&lt;p&gt;具体怎么决定是依赖于终端用户了，所以推荐的做法是将远程主机的公钥保存在合适的地方方便核对。&lt;/p&gt;
&lt;p&gt;接下来，如果用户决定接受这个返回的公钥，系统会继续提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Warning: Permanently added &lt;span style="color:#4e9a06"&gt;&amp;#39;10.30.110.230&amp;#39;&lt;/span&gt; &lt;span style="color:#ce5c00;font-weight:bold"&gt;(&lt;/span&gt;RSA&lt;span style="color:#ce5c00;font-weight:bold"&gt;)&lt;/span&gt; to the list of known hosts.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;紧接着就是用户输入密码来登录远程主机。同时远程主机的公钥会被保存在 &lt;code&gt;$HOME/.ssh/known_hosts&lt;/code&gt; 这个文件中，下次登录的时候就不需要用户再次核对指纹了。每个 SSH 用户都有自己的 &lt;code&gt;known_hosts&lt;/code&gt; 文件，此外系统也有一个这样的全局配置文件，通常是 &lt;code&gt;/etc/ssh/ssh_known_hosts&lt;/code&gt; ，保存一些对所有用户都可信赖的远程主机的公钥。&lt;/p&gt;</description></item><item><title> Linux 的启动流程</title><link>https://morvencao.github.io/posts/linux-os-start-process/</link><pubDate>Tue, 10 Feb 2015 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/linux-os-start-process/</guid><description>&lt;p&gt;和 Window 等其他操作系统一样，Linux 的启动也分为两个阶段：&lt;strong&gt;引导（boot）&lt;strong&gt;和&lt;/strong&gt;启动（startup）&lt;/strong&gt;。&lt;strong&gt;引导阶段&lt;/strong&gt;开始于打开电源开关，接下来板载程序 &lt;a href="https://en.wikipedia.org/wiki/BIOS"&gt;BIOS&lt;/a&gt; 开始于上电自检过程，结束于内核初始化完成。&lt;strong&gt;启动阶段&lt;/strong&gt;接管剩余的工作，直到操作系统初始化完成进入可操作状态，并能够执行用户的功能性任务。&lt;/p&gt;
&lt;p&gt;我们不花过多篇幅讲解引导阶段硬件板载程序加载运行的过程。事实上，由于在板载程序上很多行为基本上固定的，程序员很难介入，所以接下来主要讲讲主板的引导程序如何加载内核以及控制权交给 Linux 操作系统之后各个服务的启动流程。&lt;/p&gt;
&lt;h2 id="grub-引导加载程序"&gt;GRUB 引导加载程序&lt;/h2&gt;
&lt;p&gt;所谓引导程序，一个用于计算机寻找操作系统内核并加载其到内存的智能程序，通常位于硬盘的第一个扇区，并由 BIOS 载入内存执行。为什么需要引导程序，而不是直接由 BIOS 加载操作系统？原因是 BOIS 只会自动加载硬盘的第一个扇区512字节的内容，而操作系统的大小远远大于这个值，所以才会先加载引导程序，然后通过引导加载程序加载操作系统到内存中。&lt;/p&gt;
&lt;p&gt;目前，各个 Linux 发行版主流的引导程序是 GRUB(GRand Unified BootLoader)。GRUB 主要有以下几个作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拥有一个可以让用户选择到底启动哪个系统的的菜单界面&lt;/li&gt;
&lt;li&gt;可以调用其他的启动引导程序，来实现多系统引导&lt;/li&gt;
&lt;li&gt;加载操作系统的内核程序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GRUB1 现在已经逐步被弃用，在大多数现代发行版上它已经被 GRUB2 所替换。GRUB2 通过 &lt;code&gt;/boot/grub2/grub.cfg&lt;/code&gt; 进行配置，最终 GRUB 定位和加载 Linux 内核程序到内存中，并转移控制权到内核程序。&lt;/p&gt;
&lt;h2 id="内核程序"&gt;内核程序&lt;/h2&gt;
&lt;p&gt;内核程序的相关文件位于 &lt;code&gt;/boot&lt;/code&gt; 目录下，这些内核文件均带有前缀 &lt;code&gt;vmlinuz&lt;/code&gt; 。内核文件都是以一种自解压的压缩格式存储以节省空间，选定的内核程序自解压完成并加载到内存中开始执行。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8f5902;font-style:italic"&gt;# ll /boot/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;total &lt;span style="color:#0000cf;font-weight:bold"&gt;152404&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;drwxr-xr-x &lt;span style="color:#0000cf;font-weight:bold"&gt;4&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;4096&lt;/span&gt; Nov &lt;span style="color:#0000cf;font-weight:bold"&gt;29&lt;/span&gt; 15:34 ./
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;drwxr-xr-x &lt;span style="color:#0000cf;font-weight:bold"&gt;22&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;335&lt;/span&gt; Jan &lt;span style="color:#0000cf;font-weight:bold"&gt;16&lt;/span&gt; 12:22 ../
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw-r--r-- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;190587&lt;/span&gt; Aug &lt;span style="color:#0000cf;font-weight:bold"&gt;10&lt;/span&gt; &lt;span style="color:#0000cf;font-weight:bold"&gt;2018&lt;/span&gt; config-3.2.0-3-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw-r--r-- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;190611&lt;/span&gt; Oct &lt;span style="color:#0000cf;font-weight:bold"&gt;2&lt;/span&gt; 12:22 config-3.2.0-4-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;drwxr-xr-x &lt;span style="color:#0000cf;font-weight:bold"&gt;5&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;4096&lt;/span&gt; Nov &lt;span style="color:#0000cf;font-weight:bold"&gt;29&lt;/span&gt; 15:33 grub/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw-r--r-- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;39413114&lt;/span&gt; Nov &lt;span style="color:#0000cf;font-weight:bold"&gt;29&lt;/span&gt; 15:33 initrd.img-3.2.0-3-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw-r--r-- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;39423838&lt;/span&gt; Nov &lt;span style="color:#0000cf;font-weight:bold"&gt;29&lt;/span&gt; 15:32 initrd.img-3.2.0-4-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw-r--r-- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;255&lt;/span&gt; Oct &lt;span style="color:#0000cf;font-weight:bold"&gt;2&lt;/span&gt; 12:22 retpoline-3.2.0-3-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw-r--r-- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;255&lt;/span&gt; Oct &lt;span style="color:#0000cf;font-weight:bold"&gt;24&lt;/span&gt; 08:31 retpoline-3.2.0-4-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw------- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;3902569&lt;/span&gt; Aug &lt;span style="color:#0000cf;font-weight:bold"&gt;10&lt;/span&gt; &lt;span style="color:#0000cf;font-weight:bold"&gt;2018&lt;/span&gt; System.map-3.2.0-3-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw------- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;3904838&lt;/span&gt; Oct &lt;span style="color:#0000cf;font-weight:bold"&gt;2&lt;/span&gt; 12:22 System.map-3.2.0-4-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw------- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;7159744&lt;/span&gt; Aug &lt;span style="color:#0000cf;font-weight:bold"&gt;10&lt;/span&gt; &lt;span style="color:#0000cf;font-weight:bold"&gt;2018&lt;/span&gt; vmlinuz-3.2.0-3-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-rw------- &lt;span style="color:#0000cf;font-weight:bold"&gt;1&lt;/span&gt; root root &lt;span style="color:#0000cf;font-weight:bold"&gt;7166688&lt;/span&gt; Oct &lt;span style="color:#0000cf;font-weight:bold"&gt;2&lt;/span&gt; 12:22 vmlinuz-3.2.0-4-amd64
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="init-进程"&gt;init 进程&lt;/h2&gt;
&lt;p&gt;一旦内核文件自解压完成，Linux 操作系统开始启动运行第一个程序 &lt;code&gt;/sbin/init&lt;/code&gt; ，它的作用是初始化操作系统环境。由于 &lt;code&gt;init&lt;/code&gt; 进程是操作系统第一个运行的程序，它的 pid 为&lt;code&gt;1&lt;/code&gt;。其他所有进程都从它衍生而来，所以 init 进程是所有其他进程的祖先。事实上 &lt;code&gt;init&lt;/code&gt; 以守护进程方式存在：&lt;/p&gt;</description></item><item><title>回首「2014」</title><link>https://morvencao.github.io/posts/2014-backward-glance/</link><pubDate>Fri, 26 Dec 2014 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/2014-backward-glance/</guid><description>&lt;p&gt;一年将尽，一年伊始，兜兜转转，2014年就这样走近年末，不禁让人措手不及。似乎是一样的时光、一样的节奏、一样的人物，变换着不同的场景、不同的面孔、不同的心情，重重叠叠过去了一年。过去的一年，虽然忙碌，但也收获不少。如果一定要说一些年度关键词，那就是「学分与课程」、「百度实习」以及「校招」。&lt;/p&gt;
&lt;h2 id="学分与课程"&gt;学分与课程&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;从去年的9月份到今年的五月初，忙碌的两个学期，选择修完了研究生的大部分课程，这样五月份以后就可以外出实习。总的来看，研究生的课程是本科课程的扩展与深入。比如《高级数据库》这门课，本科也开，所讲内容大部分是数据库基本知识，包括关系型数据库设计以及 SQL 语句优化。研究生阶段则更深入，更倾向于数据库读写性能分析，各种关系型数据库横向对比，动手实践特殊类型的数据库设计，当然也包括对非关系型数据库的探讨。其他课程与此类似，包括高级网络、分布式设计与分析、数据挖掘与知识发现等等，基本上覆盖了计算机相关专业的主要课程。也利用研一空闲时间，复习了数据结构和算法，刷 &lt;a href="http://leetcode.com/"&gt;LeetCode&lt;/a&gt;，填本科阶段挖下的坑。到五月份主要课程都已修完，貌似还多修了两门课。研究生的成绩普遍较高，平均分达到了90分左右（现在才意识到了要好好学习，哈哈～）。研究生阶段还有一门研讨课，不过我选的研讨课自己不是特别感兴趣，所以也没有花太多的时间在上面，水水而已。&lt;/p&gt;
&lt;h2 id="百度实习"&gt;百度实习&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;关于今年的实习，本来想专门写一篇文章总结一下，后来由于找工作忙碌就搁置了（其实都是借口），这里就简单总结一下。今年4月末拿到百度网页搜索部的实习Offer，之前听说百度实习基本能留下正式工作，但是当时选择去百度实习的原因不是想拿百度的正式 Offer，而是想去体验一下国内顶级的互联网公司，同时也增加自己的项目经验，为下半年的找工作打下基础。先说说面试吧，百度的实习面试分为两轮技术面，HR 都会跟你预约面试时间，这一点还是非常赞的。第一轮面试的大部分是基础数据结构与算法，当然也问了一些关于 C++ 的问题，比较轻松。第二轮面试就比较开放了，当时我的面试官就是后来我实习的 mentor，问题不是特别多，第一个是操作系统内存管理的，接下来，问了我对于 MySQL 的认识，包括 MySQL 读写极限的多少，以及底层一些读写机制等等，最后就是一道算法题，自己回答的不是很好。不过还好，大约一周以后，就收到了 Offer。
接下来正式入职，在百度网页搜索部，我所在的组在上海有4个研发，主要负责抓取工作，包括百度搜索的抓取以及其他各个产品线的抓取。百度为了规范公司各个产品线的抓取，避免不必要的封禁和抓取混乱的现状，特意将抓取平台化，命名为 CSPUB，公司内部各个产品线可以在 CSPUB 上注册，编辑抓取目标然后发起抓取。主要的开发语言是 C++ 和 PHP，自己平时也用 Python 写一些脚本检测线上机器的运行状况。百度确实是个年轻有活力的公司，内部员工干活都很积极，对新技术热情很高，部门内部经常会有一些技术分享，可以学到不少在学校根本不会接触到的架构知识。我的 mentor 是个对工作效率追求极致的人，一度使我感觉跟不上节奏，后来习惯了之后才发现自己受益颇多。到了10月份，由于开始找工作，自己也没有留上海的想法，遂辞职，返校找工作。&lt;/p&gt;
&lt;h2 id="校园招聘"&gt;校园招聘&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;今年最重要的事情应该就是找工作了，可就是这最重要的事儿，我却刚开始就错过了一次绝佳的机会－阿里校招。不过阿里今年的校招也是让人不能理解，9月份之前就开始笔试，而且是线上笔试。我笔试当天晚上才得知消息，为时已晚。不过我对于自己在哪里工作找已有想法，因为女朋友的缘故，我会优先选择回西安工作。所以，当周围的同学都在准备 BAT 面试的时候，我在关注西安为数不多的 IT 公司校招情况。机缘巧合，之前发给 IBM 西安的简历被筛选了，HR 安排我去公司笔试和面试。接下来的一周我在西安参加了笔试一次面试三次，虽然 HR 说十一之后才会有消息，但当时感觉应该十拿九稳了。不出意料，回到南京之后，面试我的 Manager 通过电话给了口头 offer，薪水也还可以，再加上之前有在 IBM 的实习经历，应聘职位也和自己的方向比较相关，再次考虑到在西安 IT 行业不景气的现状，所以也基本定下来了。当然，这时候还没到十月份，校招才刚开始，所以也准备了其他公司的笔试面试，包括百度、大众点评以及一些小公司。其中百度和大众点评都拿到 Offer。在西安呆的一周内错过了腾讯、美团的校招。进入11月份基本周围同学都拿到 Offer，校招也应该结束了。总的来说，校招没必要很纠结，没必要各个公司招聘都参加，那要只会心力交瘁。想清楚自己适合什么样的公司，决定去哪个城市发展，这样自己目标就明确多了，准备起来也有条不紊，自然结果也不会很差。&lt;/p&gt;
&lt;h2 id="新一年的愿望"&gt;新一年的愿望&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;希望将愿望写出来，能够更加有动力去实现。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;希望抓住最后的校园时光，感谢所有的老师，和最好的哥们儿好好告别；&lt;/li&gt;
&lt;li&gt;希望公司入职能去好的team，找到nice的mentor，做自己想做的事情；&lt;/li&gt;
&lt;li&gt;希望能抽出更多的时间看书，不管是技术方面的书籍还是人文历史方面的；&lt;/li&gt;
&lt;li&gt;希望能学点设计方面的知识，尤其是UI，UX方面的；&lt;/li&gt;
&lt;li&gt;希望在工作中锻炼自己的口语，再不要吃老本；&lt;/li&gt;
&lt;li&gt;希望多多参与开源的项目，保持博客更新的频率；&lt;/li&gt;
&lt;li&gt;希望能多陪陪家人出去旅游。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>创建私有 CA 以及颁发数字证书</title><link>https://morvencao.github.io/posts/create-pki-with-openssl/</link><pubDate>Mon, 24 Nov 2014 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/create-pki-with-openssl/</guid><description>&lt;p&gt;在上一篇文章&lt;a href="https://morvencao.github.io/posts/pki-and-digital-certificate/"&gt;深入理解PKI系统的与数字证书&lt;/a&gt;中介绍了 PKI 系统的基本组成以及 CA 认证中心的主要作用，以及 X.509 证书基本标准。今天，我们继续应用已经学习的理论知识构建一套自己的 PKI/CA 数字证书信任体系。&lt;/p&gt;
&lt;h2 id="数字证书生成工具"&gt;数字证书生成工具&lt;/h2&gt;
&lt;p&gt;有以下两种常见的工具来生成 &lt;code&gt;RSA&lt;/code&gt; 公私密钥对:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 有些情形只需要公私密钥对就够了，不需要数字证书，比如私有的 &lt;code&gt;SSH&lt;/code&gt; 服务，但是对于一些要求身份认证的情形，则需要对公钥进行数字签名形成数字证书。&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssh-keygen&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssl genrsa&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际上 &lt;code&gt;ssh-keygen&lt;/code&gt; 底层也是使用 &lt;code&gt;openssl&lt;/code&gt; 提供的库来生成密钥对。&lt;/p&gt;
&lt;h3 id="ssh-keygen"&gt;ssh-keygen&lt;/h3&gt;
&lt;p&gt;举例来说，要使用 &lt;code&gt;ssh-keygen&lt;/code&gt; 生成2048位 RSA 密钥对，只需要执行以下命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ssh-keygen -b &lt;span style="color:#0000cf;font-weight:bold"&gt;2048&lt;/span&gt; -t rsa -f foo_rsa
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Generating public/private rsa key pair.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Enter passphrase &lt;span style="color:#ce5c00;font-weight:bold"&gt;(&lt;/span&gt;empty &lt;span style="color:#204a87;font-weight:bold"&gt;for&lt;/span&gt; no passphrase&lt;span style="color:#ce5c00;font-weight:bold"&gt;)&lt;/span&gt;: 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Enter same passphrase again: 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Your identification has been saved in foo_rsa.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Your public key has been saved in foo_rsa.pub.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The key fingerprint is:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;b8:c4:5f:2a:94:fd:b9:56:9d:5b:fd:96:02:5a:7e:b7 user@oasis
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;The key&lt;span style="color:#a40000"&gt;&amp;#39;&lt;/span&gt;s randomart image is:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+--&lt;span style="color:#ce5c00;font-weight:bold"&gt;[&lt;/span&gt; RSA 2048&lt;span style="color:#ce5c00;font-weight:bold"&gt;]&lt;/span&gt;----+
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; . + &lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; * S . . ..&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; o o + +. o o&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; o o *.. oo&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; . ..o o.oo&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt; .. . oE.&lt;span style="color:#000;font-weight:bold"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+-----------------+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中 &lt;code&gt;-b&lt;/code&gt; 指定密钥位数，一般指定2048以上， &lt;code&gt;-t&lt;/code&gt; 指定密钥类型，如 &lt;code&gt;rsa/dsa/ecdsa&lt;/code&gt;&lt;/p&gt;</description></item><item><title>PKI 系统的与 CA 中心</title><link>https://morvencao.github.io/posts/pki-and-digital-certificate/</link><pubDate>Wed, 12 Nov 2014 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/pki-and-digital-certificate/</guid><description>&lt;p&gt;在上一篇文章&lt;a href="https://morvencao.github.io/posts/digital-signature-and-digital-certificate/"&gt;数字签名与数字证书&lt;/a&gt;中介绍了数字签名、数字证书的一些基础知识，但是并没有提到数字证书如何进行管理，比如数字证书的文件格式、数字证书的申请以及轮换等知识，这篇文章将介绍数字证书的管理。&lt;/p&gt;
&lt;p&gt;说到数字证书的管理，不得不提到一个专有名词： PKI(Publick Key Infrastructure) ，翻译过来就是公钥基础设施，是一种遵循既定标准的密钥管理平台，它能够为所有网络应用提供加密和数字签名等密码服务及所必需的密钥和证书管理体系。简单来说，可以理解为利用之前提到过的公私钥非对称加密技术为应用提供加密和数字签名等密码服务以及与之相关的密钥和证书管理体系。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PKI 既不是一个协议，也不是一个软件，它是一个标准，在这个标准之下发展出的为了实现安全基础服务目的的技术统称为 PKI 。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="pki的组成"&gt;PKI的组成&lt;/h2&gt;
&lt;p&gt;PKI 作为一个实施标准，包含一系列的组件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CA(Certificate Authority) 中心&lt;/strong&gt;：是 PKI 的”核心”，即数字证书的申请及签发机关， CA 必须具备权威性的特征，它负责管理 PKI 结构下的所有用户(包括各种应用程序)的证书的签发，把用户的公钥和用户的其他信息捆绑在一起，在网上验证用户的身份， CA 还要负责用户证书的黑名单登记和黑名单发布；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;X.509 目录服务器&lt;/strong&gt;：X.500 目录服务器用于&amp;quot;发布&amp;quot;用户的证书和黑名单信息，用户可通过标准的 LDAP 协议查询自己或其他人的证书和下载黑名单信息；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于 SSL(Secure Socket Layer) 的安全 web 服务器&lt;/strong&gt;：Secure Socket Layer(SSL) 协议最初由 Netscape 企业发展，现已成为网络用来鉴别网站和网页浏览者身份，以及在浏览器使用者及网页服务器之间进行加密通讯的全球化标准；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Web 安全通信平台&lt;/strong&gt;：Web 有 Web 客户端和 Web 服务端两部分，通过具有高强度密码算法的 SSL 协议保证客户端和服务器端数据的机密性、完整性以及身份验证；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自开发安全应用系统&lt;/strong&gt;：自开发安全应用系统是指各行业自开发的各种具体应用系统，例如银行、证券的应用系统等。完整的 PKI 包括:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;认证政策的制定&lt;/li&gt;
&lt;li&gt;认证规则&lt;/li&gt;
&lt;li&gt;运作制度的制定&lt;/li&gt;
&lt;li&gt;所涉及的各方法律关系内容&lt;/li&gt;
&lt;li&gt;技术的实现等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="证书中心"&gt;证书中心&lt;/h2&gt;
&lt;p&gt;认证中心也就是 CA ，是一个负责发放和管理数字证书的权威机构，它作为电子商务交易中受信任的第三方，承担公钥体系中公钥的合法性检验的责任。 CA 为每个使用公开密钥的用户发放一个数字证书，以实现公钥的分发并证明其合法性。作为 PKI 的核心部分， CA 实现了 PKI 中一些很重要的功能:&lt;/p&gt;</description></item><item><title>数字签名与数字证书</title><link>https://morvencao.github.io/posts/digital-signature-and-digital-certificate/</link><pubDate>Mon, 06 Oct 2014 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/digital-signature-and-digital-certificate/</guid><description>&lt;p&gt;在之前的&lt;a href="https://morvencao.github.io/posts/cryptology-basic/"&gt;密码学笔记&lt;/a&gt;中主要是了解了密码学的基础知识，包括两种加密算法的原理，上篇笔记结束的时候引入了在非对称加密算法中数字证书（&lt;code&gt;Digital Certificate&lt;/code&gt;）的概念。这篇笔记将继续探讨什么是数字证书，不过在了解它之前，先得知道什么是数字签名（&lt;code&gt;Digital Signature&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;关于数字签名和数字证书的概念，有一篇非常经典的&lt;a href="http://www.youdzone.com/signature.html"&gt;文章&lt;/a&gt;做了详细的介绍，本文的大部分内容来自于那篇文章。&lt;/p&gt;
&lt;h2 id="数字证书与数字签名"&gt;数字证书与数字签名&lt;/h2&gt;
&lt;p&gt;Bob 生成了自己的公钥、私钥对，将私钥自己保存，公钥分发给了他的朋友们： Pat Susan 与 Daug&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/01/26/WZFXjmgS2aGcYry.jpg" alt="public-key-and-private-key.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Susan 要给 Bob 写一封保密的信件，写完后用 Bob 的公钥加密，就可以达到保密的效果。 Bob 收到信件之后用自己的私钥来解密，就可以看到信件的内容。这里假设 Bob 的私钥没有泄露（私钥是十分敏感的信息，一定要注意保管），即使信件被别人截获，信件内容也无法解密，也就是说这封信的内容不会有第三个人知道。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/01/26/iEfklACYPxg6hKG.jpg" alt="comminucation-with-public-private-key-pair.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Bob 给 Susan 回信，决定采用&amp;quot;数字签名&amp;quot;：他写完信之后后先用哈希函数，生成信件的摘要（&lt;code&gt;Digest&lt;/code&gt;），然后，再使用自己的私钥，对这个摘要进行加密，生成&amp;quot;数字签名&amp;quot;（&lt;code&gt;Digital Signature&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/01/26/e17WqOQNCm3cldy.jpg" alt="digital-signature.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Bob 将这个数字签名，附在信件里面，一起发给 Susan：&lt;/p&gt;
&lt;p&gt;Susan 收到信件之后，对信件本身使用相同的哈希函数，得到当前信件内容的摘要，同时，取下数字签名，用 Bob 的公钥解密，得到原始信件的摘要，如果两者相同就说明信件的内容没有被修改过。由此证明，这封信确实是 Bob 发出的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2021/01/26/DWJklALOB3QngxE.jpg" alt="digital-signature-validation.jpg"&gt;&lt;/p&gt;
&lt;p&gt;但是，更复杂的情况出现了。 Daug 想欺骗 Susan ，他伪装成 Bob 制作了一对公钥、私钥对，并将公钥分发给 Susan ， Susan 此时实际保存的是 Daug 的公钥，但是还以为这是 Bob 的公钥。因此， Daug 就可以冒充 Bob ，用自己的私钥做成&amp;quot;数字签名&amp;quot;写信给 Susan ，而 Susan 用假的 Bob 公钥进行解密。一切看起来完美无缺？&lt;/p&gt;
&lt;p&gt;Susan 觉得有些不对劲，因为她并不确定这个公钥是不是真正属于 Bob 的。于是她想到了一个办法，要求 Bob 去找&amp;quot;证书中心&amp;quot;（&lt;code&gt;Certificate Authority&lt;/code&gt;），为公钥做认证。证书中心用证书中心的私钥，对 Bob 的公钥和一些相关信息一起加密，生成&amp;quot;数字证书&amp;quot;（&lt;code&gt;Digital Certificate&lt;/code&gt;）。&lt;/p&gt;</description></item><item><title>密码学基础</title><link>https://morvencao.github.io/posts/cryptology-basic/</link><pubDate>Tue, 16 Sep 2014 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/cryptology-basic/</guid><description>&lt;h2 id="密码学"&gt;密码学&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Cryptography"&gt;密码学（Cryptography）&lt;/a&gt;是研究编制密码和破译密码的技术科学。研究密码变化的客观规律，应用于编制密码以保守通信秘密的，称为编码学；应用于破译密码以获取通信情报的，称为破译学，总称密码学。&lt;/p&gt;
&lt;p&gt;密码是通信双方按约定的法则进行信息特殊变换的一种重要保密手段。依照这些法则，变明文为密文，称为加密变换；变密文为明文，称为脱密变换。密码在早期仅对文字或数码进行加、脱密变换，随着通信技术的发展，对语音、图像、数据等都可实施加、脱密变换。&lt;/p&gt;
&lt;h2 id="密码算法"&gt;密码算法&lt;/h2&gt;
&lt;p&gt;什么是密码算法（&lt;code&gt;Cryptography Algorithm&lt;/code&gt;），通常是指加、解密过程所使用的信息变换规则，是用于信息加密和解密的数学函数。对明文进行加密时所采用的规则称作加密算法，而对密文进行解密时所采用的规则称作解密算法。加密算法和解密算法的操作通常都是在一组密钥的控制下进行的。&lt;/p&gt;
&lt;p&gt;什么是密钥？密钥（&lt;code&gt;Secret Key&lt;/code&gt;）是密码算法中的一个可变参数，通常是一组满足一定条件的随机序列。用于加密算法的叫做加密密钥，用于解密算法的叫做解密密钥，加密密钥和解密密钥可能相同，也可能不相同。&lt;/p&gt;
&lt;p&gt;加密算法根据密钥的不同分为两类，对称加密算法(&lt;code&gt;Symmetric-key Encryption Algorithm&lt;/code&gt;)和非对称加密算法(&lt;code&gt;Asymmetric-key Encryption Algorithm&lt;/code&gt;)。&lt;/p&gt;
&lt;p&gt;目前，通用的单钥加密算法为 DES(Data Encryption Standard) ，通用的双钥加密算法为 RSA(Rivest-Shamir-Adleman) ，都产生于上个世纪70年代。&lt;/p&gt;
&lt;h3 id="对称加密"&gt;对称加密&lt;/h3&gt;
&lt;p&gt;首先，让我们先从一个情景开始讲起。&lt;/p&gt;
&lt;p&gt;比如张三学习比李四好，李四就想在考试的时候让张三“帮助”一下自己，当然，他们俩不可能像我们平常对话一样说，第一题选 A ，第二题选 B 等等，为什么？因为监考老师明白他俩在谈论什么，也就是说这种沟通交流方式属于“明文”，所以李四就想：“需要发明一种只有我和张三明白的交流方式”，那李四做了什么呢？李四去找张三说：“当我连续咳嗽三声的时候你看我，然后如果我摸了下左耳朵，说明你可以开始给我传答案了，如果没反应，那说明我真的是在咳嗽&amp;hellip;”， 然后，怎么传答案呢？很简单，“你摸左耳朵代表 A ， 摸右耳朵代表 B ，左手放下代表 C ，右手放下代表 D ，”好了，这就是他们的“加密算法”，将信息的一种形式（A, B, C, D），这里我们称为“明文”，转换成了另一种形式（摸左耳朵，摸右耳朵，放左手，放右手），这里称为“密文”，经过这种转换，很显然监考老师不会明白这些“密文”，这样，张三和李四就通过“密文”形式实现了信息的交换。&lt;/p&gt;
&lt;p&gt;对称加密算法也叫单钥加密，加密和解密过程都用同一套密钥。历史上，人类传统的加密方法都是前一种，比如二战期间德军用的 Enigma 电报密码，莫尔斯电码都可以看作是一种单钥加密算法。&lt;/p&gt;
&lt;p&gt;结合前面的例子对应一下，密钥就是“将（A, B, C, D）转换成（摸左耳朵，摸右耳朵，放左手，放右手）”这么一个规则。&lt;/p&gt;
&lt;blockquote&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;img src="https://i.loli.net/2021/01/26/a2IlAUjkiDMt7vq.jpg" alt="symmetric-key-encryption-algorithm.jpg"&gt;&lt;/p&gt;
&lt;p&gt;目前常见的对称加密算法有：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DES, 3DES, AES, Blowfish, IDEA, RC5, RC6...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="非对称加密"&gt;非对称加密&lt;/h3&gt;
&lt;p&gt;非对称加密算法也称为双钥加密，加密和解密过程用的是两套密钥。非对称加密是一种比对称加密更加优秀的加密算法。对称加密的密钥只有一把，所以密钥的保存变得很重要。一旦密钥泄漏，密码也就被破解。而在非对称加密的情况下，密钥有两把，一把是公开的公钥，还有一把是不公开的私钥。&lt;/p&gt;
&lt;p&gt;非对称加密的原理如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;公钥和私钥是一一对应的关系，有一把公钥就必然有一把与之对应的、独一无二的私钥，反之亦成立；&lt;/li&gt;
&lt;li&gt;所有的（公钥, 私钥）对都是不同的；&lt;/li&gt;
&lt;li&gt;用公钥可以解开私钥加密的信息，反之亦成立；&lt;/li&gt;
&lt;li&gt;同时生成公钥和私钥应该相对比较容易，但是从公钥推算出私钥，应该是很困难或者是不可能的；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在对称加密体系中，&lt;strong&gt;公钥用来加密信息，私钥用来数字签名&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如，李四想给张三发送密文，于是李四开始给张三发消息：&lt;/p&gt;</description></item><item><title>字符编码的前世今生</title><link>https://morvencao.github.io/posts/character-encoding/</link><pubDate>Sun, 12 May 2013 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/character-encoding/</guid><description>&lt;p&gt;字符编码问题看似无关紧要，常常被忽略，但是如果对字符编码知识没有一个系统完整的认识，在实际编码过程中我们就会遇到各种“坑”。今天，我们就来详细看看字符编码。&lt;/p&gt;
&lt;h2 id="一切的起源"&gt;一切的起源&lt;/h2&gt;
&lt;p&gt;字符编码主要是解决如何使用计算机的方式来表达特定的字符，但是有计算机基础理论知识的人都知道，计算机内部所有的数据都是基于二进制，每个二进制位（bit）有&lt;code&gt;0&lt;/code&gt;和&lt;code&gt;1&lt;/code&gt;两种状态，我们可以组合多个二进位来表达更大的数值，例如八个二进制位就可以组合出256种状态，这被称为一个字节（byte）。这就是说，我们可以用一个字节来映射表示256种不同的状态，如果每一个状态对应一个符号，就是256个符号，从 &lt;code&gt;00000000&lt;/code&gt; 到 &lt;code&gt;11111111&lt;/code&gt;，这样就建立了最初的计算机数值到自然语言字符最基本的映射关系。上个世纪60年代，美国国家标准协会 ANSI 制定了一个标准，规定了常用字符的集合以及每个字符对应的编号，这就是字符编码最初的形态 ASCII 字符集，也称为 ASCII 码。ASCII 码规定了英语字符与二进制位之间的对应关系。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.loli.net/2019/03/26/5c99c497ce83b.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;ASCII码一共规定了128个字符的编码（包括32个不能打印出来的控制符号），比如空格 &lt;code&gt;SPACE&lt;/code&gt; 的ASCII 码是32（二进制表示为 &lt;code&gt;00100000&lt;/code&gt;），大写的字母 &lt;code&gt;A&lt;/code&gt; 的ASCII 码是65（二进制表示为 &lt;code&gt;01000001&lt;/code&gt;）。这128个符号只需要占用了一个字节的后面7位，最前面的一位统一规定为&lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;按照 ASCII 字符集编码和解码就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备，只需要在 ASCII 字符集中依次找到字符对应的字节，然后直接将该字节写入存储设备即可，解码二进制流就是相反的过程。&lt;/p&gt;
&lt;h2 id="各种-oem-编码的衍生"&gt;各种 OEM 编码的衍生&lt;/h2&gt;
&lt;p&gt;当计算机发展起来的时候，人们逐渐发现，ASCII 字符集里的128个字符不能满足他们的需求。在英语国家，128个字符编码足矣，但是对于非英语国家，人们无法在 ASCII 字符集中找到他们的基本字符。比如，在法语中，字母上方有注音符号，它就无法用 ASCII 码表示。于是有些人就在想，ASCII 字符只是使用了一个字节的前128个变换，后面的128位完全可以利用起来，于是一些欧洲国家就决定，利用字节中闲置的最高位编入新的符号。比如，法语中的 &lt;code&gt;é&lt;/code&gt; 的编码为130（二进制 &lt;code&gt;10000010&lt;/code&gt;）。这样一来，这些欧洲国家使用的编码体系，可以表示最多256个符号。&lt;/p&gt;
&lt;p&gt;但是，这里又出现了新的问题。不同的国家有不同的字母，因此，哪怕它们都使用256个符号的编码方式，代表的字母却不一样。比如，130在法语编码中代表了 &lt;code&gt;é&lt;/code&gt;，在希伯来语编码中却代表了字母 &lt;code&gt;Gimel&lt;/code&gt; (&lt;code&gt;ג&lt;/code&gt;)，在俄语编码中又会代表另一个符号。不同的 OEM 字符集导致人们无法跨机器传播交流各种信息。例如甲发了一封简历 &lt;code&gt;résumés&lt;/code&gt; 给乙，结果乙看到的却是 &lt;code&gt;rגsumגs&lt;/code&gt;，因为 &lt;code&gt;é&lt;/code&gt; 字符在甲机器上的 OEM 字符集中对应的字节是 &lt;code&gt;0×82&lt;/code&gt;，而在乙的机器上，由于使用的 OEM 字符集不同，对 &lt;code&gt;0×82&lt;/code&gt; 字节解码后得到的字符却是 &lt;code&gt;ג&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但是尽管出现了不同的 OEM 编码，所有这些编码方式中，0到127表示的符号是一样的，不一样的只是128到255这一段代表的字符。&lt;/p&gt;
&lt;p&gt;至于亚洲国家的文字，使用的符号就更多了，汉字就多达10万左右。一个字节只能表示256种符号，肯定是不够的，就必须使用多个字节表达一个符号。比如，简体中文常见的编码方式是 GB2312，使用两个字节表示一个汉字，所以理论上最多可以表示 256x256，也就是65536个汉字。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 中文编码的问题很复杂，这篇笔记不作深入讨论，但需要指出的是虽然都是用多个字节表示一个符号，但是 GB 类的汉字编码与后文的 unicode 和 UTF-8 编码方案是没有关系的。&lt;/p&gt;</description></item><item><title>IBM 实习总结</title><link>https://morvencao.github.io/posts/internship-at-ibm/</link><pubDate>Mon, 01 Apr 2013 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/internship-at-ibm/</guid><description>&lt;p&gt;今天上午和新来的实习生交接了自己的工作，中午约同事们一起吃了午饭，之后很快办完了离职手续，自己为期6个多月的 IBM 实习也画上了句号，不管是不是完美，对于我自己来说，大学四年的第一份实习无疑对我价值颇高。这篇文章我主要说说我在 IBM 的实习经历以及感受。&lt;/p&gt;
&lt;h2 id="实习时间"&gt;实习时间&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;我出去实习的时间比较晚，根据学院规定暑假就可以开实习，时间不能少于6个月，所以最好应该在4、5月份找实习，而我在4、5月份却忙于备考 GRE 和 Toefl，自然也错过了找实习的黄金时间。暑假结束后，我才开始计划找实习。开始一心想着去互联网公司，也正因为一直在等待这样的机会而浪费了不少时间。直达9月份，刷&lt;a href="http://bbs.nju.edu.cn/"&gt;小百合&lt;/a&gt;的时候看到一个不错的实习机会，也就是接下来6个多月我所在的 IBM 上海的 JTC(Java Technology Center) 部门。当时心动的主要原因是这个实习职位所在的组是做 JVM 的，也是自己的兴趣方向，所以果断投了简历。&lt;/p&gt;
&lt;h2 id="面试"&gt;面试&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;IBM 的面试分为技术面和英文面，可能当时急缺实习生，所以面试安排得很紧凑。自己也比较幸运，因为组里有已经毕业的学长 Ray，多少会有加分：）。因为是 JVM 小组，所以技术面都是关于 Java 的，比如 Java 多线程、IO、容器类以及反射机制等，没有问算法题。现在回想起来，这些知识确实在每天的实习工作中都会有所接触。接下来是英文面，主要面试口语。因为是招聘实习生，所以也没有太高的要求，基本的听说读写熟练就没有问题。最后面试官问我有没有问题想问他，我进一步问了关于实习职位的工作内容，当时的面试官，也就是后来我的 mentor，人非常 nice，blabla&amp;hellip;讲了一大堆，虽然当时也不是很懂，但真心觉得很 NB。过了一个周左右，收到面试通过的邮件，几天后搭乘了去上海的动车，开始了我的 IBM 实习。&lt;/p&gt;
&lt;h2 id="工作环境"&gt;工作环境&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;IBM 的工作环境很不错，整个办公室是个很大的开放式的环境，整个部门，从部门老大、到 Manager、到实习生都在开放式的工作区里面工作。我刚去的时候领到了一台旧电脑，是的，实习生用的都是旧电脑，不多这也完全不影响开发，因为基本的开发测试都是在云端，通过 SSH 登录到云端 Linux 环境。也正是从这开始彻底喜欢上了 unix/linux 哲学：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;simple and beautiful&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;对于习惯了 IDE 开发的我一开始会有些不适应，不过后来发现在 Terminal 下工作效率丝毫不逊于 IDE。&lt;/p&gt;
&lt;h2 id="工作内容"&gt;工作内容&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;IBM 比较注重基础性软件研发，特别在中国成立 CDL(China Development Lab)，我所在的部门 JTC 正是属于 CDL，而我所在的小组从事的是 JVM 的开发。IBM 自己研发的 J9VM 与 Oracle 研发的Hotspot VM 齐名，是两大主流的 JVM，为 IBM 许多 Java 产品提供支持，比如 &lt;a href="http://en.wikipedia.org/wiki/IBM_WebSphere"&gt;WebSphere&lt;/a&gt;，以及一些开源的产品如 &lt;a href="http://en.wikipedia.org/wiki/Apache_Harmony"&gt;Apache Harmony&lt;/a&gt;。现在我们组的工作是与加拿大以及印度的同事合作，基于 J9VM 开发 &lt;a href="http://www.ibm.com/developerworks/library/j-multitenant-java/"&gt;Multitennancy JVM&lt;/a&gt;，通过在单一的多租户 JVM 中运行多个应用程序，云系统可以加快应用程序的启动时间，并减少其内存占用。这将作为 IBM Java8 的一个新特性。因为是实习生，所以我的工作大多是于解决 Bug、性能调优以及测试相关。我的 mentor 是个技术大牛，我很庆幸能遇到这样的导师。mentor 对我的帮助不仅是技术上的提高，更多的是工作方式的改进，这些东西在学校的绝对学习不到的。&lt;/p&gt;</description></item><item><title>Big Endian &amp; Little Endian</title><link>https://morvencao.github.io/posts/story-of-big-and-little-endian/</link><pubDate>Tue, 26 Mar 2013 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/posts/story-of-big-and-little-endian/</guid><description>&lt;h2 id="字节序"&gt;字节序&lt;/h2&gt;
&lt;p&gt;谈到字节序，必然要牵扯到两大 CPU 派系。那就是 Motorola 的 PowerPC 系列 CPU 和 Intel 的 x86 系列 CPU 。 PowerPC 系列采用大端 (Big Endian) 方式存储数据，而 x86 系列则采用小端 (Little Endian) 方式存储数据。那么究竟什么是 Big Endian ，什么又是 Little Endian 呢？&lt;/p&gt;
&lt;p&gt;其实 Big Endian 是指低地址存放最高有效字节，而 Little Endian 则是低地址存放最低有效字节。&lt;/p&gt;
&lt;p&gt;文字说明比较抽象，下面举个例子用图像来说：&lt;/p&gt;
&lt;p&gt;Big Endian&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;低地址 高地址
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----------------------------------------&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 12 | 34 | 56 | 78 |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Little Endian&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;低地址 高地址
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----------------------------------------&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| 78 | 56 | 34 | 12 |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从上面两图可以看出，采用 Big Endian 方式存储数据是符合我们人类的思维习惯的，而 Little Endian&amp;hellip;&lt;/p&gt;</description></item><item><title>About Me</title><link>https://morvencao.github.io/about/</link><pubDate>Sat, 16 Feb 2013 00:00:00 +0000</pubDate><guid>https://morvencao.github.io/about/</guid><description>&lt;p&gt;Hi there, This is Morven, graduated from &lt;a href="http://www.nju.edu.cn/"&gt;Nanjing University&lt;/a&gt; with Bachelor and Master degree in 2015, now working as a software engineer at &lt;a href="https://www.redhat.com/"&gt;Red Hat&lt;/a&gt; focusing on cloud technology. I generally use &lt;code&gt;morvencao&lt;/code&gt; as my id on the Internet. I&amp;rsquo;m a strong advocate and believer of OSS(Open Source Software) and passionate about reading, thinking, programming and the possibility of time travel.&lt;/p&gt;
&lt;p&gt;Reach me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="mailto:morvencao@gmail.com"&gt;Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/morvencao"&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/morvencao"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@morvencao"&gt;Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://instagram.com/morvencao"&gt;Instagram&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>