<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ethan S. Chen</title><link>https://ethanschen.github.io/</link><description>Recent content on Ethan S. Chen</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Ethan S. Chen</copyright><lastBuildDate>Sun, 08 Jun 2025 12:00:00 +0800</lastBuildDate><atom:link href="https://ethanschen.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>时间规划</title><link>https://ethanschen.github.io/life/2025-06-08/</link><pubDate>Sun, 08 Jun 2025 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/life/2025-06-08/</guid><description>&lt;h2 id="整体的时间规划">整体的时间规划
&lt;/h2>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1749381136/2025-06-08-%E6%97%B6%E9%97%B4%E5%8D%A0%E6%AF%94_zp0mt7.png"
loading="lazy"
alt="时间规划"
>&lt;/p>
&lt;p>以周为单位，分割自己的时间安排&lt;/p>
&lt;ul>
&lt;li>主业工作&lt;/li>
&lt;li>个人开源项目探索&lt;/li>
&lt;li>参与开源项目维护&lt;/li>
&lt;li>文学阅读&lt;/li>
&lt;/ul>
&lt;h2 id="个人开源项目探索">个人开源项目探索
&lt;/h2>&lt;h3 id="行动路线图">行动路线图
&lt;/h3>&lt;ul>
&lt;li>Day 1-3：选择一个方向，用 100 行代码实现 MVP。&lt;/li>
&lt;li>Day 4-5：在 Reddit/Python 论坛发起讨论，收集反馈。&lt;/li>
&lt;li>Day 6-7：根据反馈迭代代码，发布 0.1.0 版到 PyPI。&lt;/li>
&lt;li>持续运营：每两周发布一个版本，同步撰写技术博客解析设计思路。&lt;/li>
&lt;/ul>
&lt;h3 id="向独立项目过渡的路线图">向独立项目过渡的路线图
&lt;/h3>&lt;p>阶段 1：成为知名项目的核心贡献者（6-12 个月）
目标：在 2-3 个高活跃度项目（如 prometheus/client_python、requests）中进入贡献者排行榜 Top 10。
行动：
每月解决 1-2 个中等难度 Issue。
主动 Review 其他人的 PR，积累社区信任。&lt;/p>
&lt;p>阶段 2：孵化实验性项目（1-2 个月）
目标：开发一个小型工具解决你在贡献过程中发现的痛点。
示例：
问题：prometheus_client 缺少与 logging 模块的深度集成。
项目：prometheus-logging-handler：将 Python 日志自动转为 Prometheus 指标。
差异化：支持自动统计 ERROR 日志频率、按日志来源（module）分维度统计。&lt;/p>
&lt;p>阶段 3：推广与生态整合（持续迭代）
目标：让项目成为父生态的推荐工具（如进入 Prometheus 官方文档的“推荐客户端”列表）。
行动：
在父项目的社区会议/邮件列表中宣传你的工具。
提交 PR 将你的项目添加到父项目的生态页（如 Prometheus 生态）。&lt;/p></description></item><item><title>读《活着》有感</title><link>https://ethanschen.github.io/life/2025-01-17-read-and-thoughts/</link><pubDate>Fri, 17 Jan 2025 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/life/2025-01-17-read-and-thoughts/</guid><description>&lt;p>高中时我是很喜欢文学的，那也是我目前为止的文学巅峰，每个课间操我都会从下楼的队伍中偷溜出到一楼的图书馆里去看书，所以有幸进行了大量阅读。饶是如此，有位作者的书我却始终没有翻开过，那便是余华。在后来的十数年中，有非常多的机会，余华老师的书就在我的手边，我也鬼使神差的没有拜读。&lt;/p>
&lt;p>最近又养成习惯读书，顺着微信读书总榜Top200读下来，第四本就是余华老师的《活着》，花了3个工作日的下班时间和1个周末的3个小时，便也读完了。有些感想，便写下这篇读后感记录一番。&lt;/p>
&lt;p>倒叙来讲，我读的版本最后附着很多报社对该书的评价，落款好部分都是一九九八年了，包括书的结尾落款也是一个“一九九二年一月三日”，令我有所感触的是突然意识到早在我未出生时，便有很多传世之作已然落成，甚至该说此生我能有幸拜读的巨著其实都早已写就并出版多年，躺在不知道哪里，等着我能走到它身边去翻开它。这个认知实在令我太过惊喜了，原来我想要认知的世界，我所有的迷茫，绝大多数早已有人想明白并以各种形式把自己的思想记录了下来。&lt;strong>我所要做的就是在书海里找到它，这与花费自己有限的一生去探索来说，实在是过于简单。&lt;/strong>&lt;/p>
&lt;p>书往前翻，是福贵这充满苦难的一生，而且是究极的苦难——死亡，与这般苦难相比，即使是书中描写没有粮食吃的恐怖景象都显得好像是幸福的日子。于福贵经历而言，我因为缺乏工作经验而感到的惶恐与压力，因为缺乏人生经验而感到的矛盾与痛苦，实在是显得有种小孩子过家家的幼稚感。我所经历的事情，所面临的困难，其实即使是以最差的结果去估计，也不过是丢脸、被批评，甚至连挨打挨饿都绝无可能。所以我的担惊受怕完全是多余的，&lt;strong>努力去做好就行，结果不好也没关系。&lt;/strong>&lt;/p></description></item><item><title>Kubernetes生态下的软件调试方法——以Provider为例</title><link>https://ethanschen.github.io/tech/2025-01-04-kubernetes-debugging-methods/</link><pubDate>Sat, 04 Jan 2025 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/tech/2025-01-04-kubernetes-debugging-methods/</guid><description>&lt;h2 id="背景">背景
&lt;/h2>&lt;p>在公司的时候，有很多调试环境部署有K8S集群供开发自验证，&lt;/p>
&lt;p>但在家参与一些开源项目的时候，因为无法使用公司环境，导致缺少可以自验证的环境。&lt;/p>
&lt;p>以前一般使用MiniKube或Docker部署K8S集群等方式构建测试环境，&lt;/p>
&lt;p>但缺点是对机器的要求比较高，我的17年8G运行内存的Mac只能是堪堪运行起集群，&lt;/p>
&lt;p>再要在上面做一些调试，开个IDE，开个浏览器等，十分费劲。&lt;/p>
&lt;h2 id="趋势">趋势
&lt;/h2>&lt;p>升级机器配置，我也有过这个想法，但不免几年就需要升级一次，费钱费时间，且机器95%以上的时间都是闲置的，并不划算。&lt;/p>
&lt;p>所以利用付费的云化资源+轻量的终端进行开发工作，越来越成为我近年来的一种趋势。&lt;/p>
&lt;p>本文也是基于这一种思想构建的一种调试方法，供君参考。&lt;/p>
&lt;h2 id="更进一步">更进一步
&lt;/h2>&lt;p>单纯的云化资源并不能很好的控制成本，借鉴企业的的做法，弹性伸缩才是降低成本的要点。&lt;/p>
&lt;p>所以我们在使用云化资源的时候也要贯彻这一种做法，&lt;/p>
&lt;p>尤其在需要使用K8S集群这类资源开销更大的业务时，&lt;/p>
&lt;p>使用Terraform在测试时自动化创建出基础设施，是我们应该提倡的。&lt;/p>
&lt;h2 id="以provider为例">以Provider为例
&lt;/h2>&lt;p>上周在工作中遇到一个Kubernetes Provider的社区Bug，因为公司的业务版本发布在即，所以优先采用打补丁的方式完成修复。&lt;/p>
&lt;p>但计划在业余时间帮助社区修复该Bug，修复时发现缺少自验证的环境，于是开始探索下面使用AWS的EKS构建测试环境的方法。&lt;/p>
&lt;h3 id="登录aws控制台">登录AWS控制台
&lt;/h3>&lt;p>登录AWS的&lt;a class="link" href="https://ap-southeast-2.console.aws.amazon.com/eks/home?region=ap-southeast-2#/clusters" target="_blank" rel="noopener"
>EKS&lt;/a>服务，本文以美国（俄亥俄州）为例&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735983171/2025-01-04-EKS%e6%9c%8d%e5%8a%a1_cy4fgp.png"
loading="lazy"
alt="EKS服务"
>&lt;/p>
&lt;h3 id="安装terraform">安装Terraform
&lt;/h3>&lt;p>参考Terraform的&lt;a class="link" href="https://developer.hashicorp.com/terraform/install" target="_blank" rel="noopener"
>Install教程&lt;/a>，在AWS临时终端中安装Terraform&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo yum install -y yum-utils shadow-utils
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo yum -y install terraform
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735983311/2025-01-04-%e5%ae%89%e8%a3%85Terraform_em4i3f.png"
loading="lazy"
alt="安装Terraform"
>&lt;/p>
&lt;h3 id="部署eks集群">部署EKS集群
&lt;/h3>&lt;p>参考Terraform的&lt;a class="link" href="https://developer.hashicorp.com/terraform/tutorials/kubernetes/eks" target="_blank" rel="noopener"
>部署EKS集群教程&lt;/a>，通过AWS终端部署EKS集群&lt;/p>
&lt;h4 id="初始化工作空间">初始化工作空间
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone https://github.com/hashicorp-education/learn-terraform-provision-eks-cluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> learn-terraform-provision-eks-cluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">terraform init
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735983578/2025-01-04-%e5%88%9d%e5%a7%8b%e5%8c%96%e5%b7%a5%e4%bd%9c%e7%a9%ba%e9%97%b4_hizkxy.png"
loading="lazy"
alt="初始化工作空间"
>&lt;/p>
&lt;h4 id="部署集群">部署集群
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">terraform apply -auto-approve
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>预计花费10分钟完成集群部署，部署后可以在ESK集群管理页面看到该集群&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735992135/2025-01-04-%e9%83%a8%e7%bd%b2%e5%ae%8c%e6%88%90_dxddod.png"
loading="lazy"
alt="部署完成"
>&lt;/p>
&lt;h4 id="验证集群功能">验证集群功能
&lt;/h4>&lt;p>配置kubectl&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">aws eks --region &lt;span class="k">$(&lt;/span>terraform output -raw region&lt;span class="k">)&lt;/span> update-kubeconfig --name &lt;span class="k">$(&lt;/span>terraform output -raw cluster_name&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>查看集群信息&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl cluster-info
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>查看node信息&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl get nodes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735992522/2025-01-04-%e9%aa%8c%e8%af%81%e9%9b%86%e7%be%a4%e5%8a%9f%e8%83%bd_cr1jhi.png"
loading="lazy"
alt="验证集群功能"
>&lt;/p>
&lt;h4 id="部署deployment资源">部署Deployment资源
&lt;/h4>&lt;p>参考Terraform的&lt;a class="link" href="https://developer.hashicorp.com/terraform/tutorials/kubernetes/kubernetes-provider" target="_blank" rel="noopener"
>管理K8S资源&lt;/a>，通过Terraform部署测试服务到集群&lt;/p>
&lt;p>创建工作目录，编辑kubernetes.tf文件&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir learn-terraform-deploy-nginx-kubernetes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> learn-terraform-deploy-nginx-kubernetes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vim kubernetes.tf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>初始化工作目录&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">terraform init
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>修改kubernetes.tf，增加Deployment资源后Apply&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">vim kubernetes.tf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">terraform apply -auto-approve
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>查看Deployment资源&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">kubectl get deployments
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736005342/2025-01-04-%e6%9f%a5%e7%9c%8bDeployment%e8%b5%84%e6%ba%90_kyl29m.png"
loading="lazy"
alt="查看Deployment资源"
>&lt;/p>
&lt;h4 id="去部署deployment资源">去部署Deployment资源
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/learn-terraform-deploy-nginx-kubernetes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">terraform destroy -auto-approve
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736005590/2025-01-04-%e5%8e%bb%e9%83%a8%e7%bd%b2Deployment%e8%b5%84%e6%ba%90_v0rvjx.png"
loading="lazy"
alt="去部署Deployment资源"
>&lt;/p>
&lt;h3 id="调试provider">调试Provider
&lt;/h3>&lt;p>参考Kubernetes Provider项目的&lt;a class="link" href="https://github.com/hashicorp/terraform-provider-kubernetes/blob/main/_about/CONTRIBUTING.md" target="_blank" rel="noopener"
>贡献指南&lt;/a>&lt;/p>
&lt;h4 id="安装go">安装Go
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">brew install go@1.22
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735985374/2025-01-04-%e5%ae%89%e8%a3%85Go_avfvmb.png"
loading="lazy"
alt="安装Go"
>&lt;/p>
&lt;h4 id="克隆provider项目">克隆Provider项目
&lt;/h4>&lt;p>Fork Provider项目到自己的仓库下，再克隆到本地&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone https://github.com/&amp;lt;YOUR-USERNAME&amp;gt;/terraform-provider-kubernetes.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> terraform-provider-kubernetes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735985599/2025-01-04-%e5%85%8b%e9%9a%86Provider%e9%a1%b9%e7%9b%ae_qkkqhd.png"
loading="lazy"
alt="克隆Provider项目"
>&lt;/p>
&lt;h4 id="修改代码">修改代码
&lt;/h4>&lt;p>即修改terraform-provider-kubernetes代码&lt;/p>
&lt;h4 id="编译provider">编译Provider
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">bash scripts/build.sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735989908/2025-01-04-%e7%bc%96%e8%af%91Provider_ndtjop.png"
loading="lazy"
alt="编译Provider"
>&lt;/p>
&lt;h4 id="替换二进制文件">替换二进制文件
&lt;/h4>&lt;p>将编译好的Provider上传到AWS终端，替换原二进制文件&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736006202/2025-01-04-%e6%9b%bf%e6%8d%a2%e4%ba%8c%e8%bf%9b%e5%88%b6%e6%96%87%e4%bb%b6_gvdhgl.png"
loading="lazy"
alt="替换二进制文件"
>&lt;/p>
&lt;h4 id="验证provider修改">验证Provider修改
&lt;/h4>&lt;p>执行可以验证到修改的操作，例如再次部署Deployment资源&lt;/p>
&lt;h3 id="去部署eks集群">去部署EKS集群
&lt;/h3>&lt;p>验证完毕，别忘了销毁集群，因为它时刻都在计费！&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/learn-terraform-provision-eks-cluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">terraform destroy -auto-approve
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736009409/2025-01-05-%e5%8e%bb%e9%83%a8%e7%bd%b2%e5%ae%8c%e6%88%90_pfmvpl.png"
loading="lazy"
alt="去部署完成"
>&lt;/p>
&lt;h2 id="总结">总结
&lt;/h2>&lt;p>到此，本文介绍的方法就讲完了，欢迎各位一试～&lt;/p></description></item><item><title>续记、续想🤔️</title><link>https://ethanschen.github.io/life/2025-01-02-work-and-thoughts/</link><pubDate>Thu, 02 Jan 2025 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/life/2025-01-02-work-and-thoughts/</guid><description>&lt;p>书接上回，&lt;/p>
&lt;p>继续写下自己的思考。&lt;/p>
&lt;h2 id="工作记录">工作记录
&lt;/h2>&lt;p>先是工作记录，虽说24.12.0版本已经发布，&lt;/p>
&lt;p>但我仍在一个不走一般版本节奏的项目里，&lt;/p>
&lt;p>且10号就是首次联调的时间，在那之前需要解决的问题还有很多，&lt;/p>
&lt;p>今天一天都在带着几个项目组里的同事与其他部门同事对齐问题，修复并验证问题。&lt;/p>
&lt;p>等反应过来，就已经晚上8点30分，到下班时间了。&lt;/p>
&lt;p>诚然，解决问题的时候使用番茄钟可以提高效率，&lt;/p>
&lt;p>但我也确实在研究SDI卡PXE的时候钻牛角尖了，浪费太多时间才开始寻求相关业务同事的帮助。&lt;/p>
&lt;h2 id="996怪圈">996怪圈
&lt;/h2>&lt;p>再着继续讲下【996怪圈】，&lt;/p>
&lt;p>我第一次接触到996这个名次就是在鼎鼎大名的 996.icu 网站大火的时候，&lt;/p>
&lt;p>不出意外，这个网站也被墙了。&lt;/p>
&lt;p>当时对996的看法也就纯当茶余饭后的同事谈资而已，&lt;/p>
&lt;p>没有想过这种工作制度对自己有何影响，&lt;/p>
&lt;p>也可能是当时我并没有进入到这样的一种工作制中，&lt;/p>
&lt;p>不像现在，我整已经在这种工作制中3年多。&lt;/p>
&lt;p>再次看到聊这个内容就是在编程随想的博文了&lt;/p>
&lt;p>&amp;ndash; &lt;a class="link" href="https://program-think.blogspot.com/2019/04/996-Working-Hour-System.html" target="_blank" rel="noopener"
>“996工作制”只不过是【劫贫济富】的缩影——“马云奇葩言论”随想&lt;/a>&lt;/p>
&lt;p>其中提到&lt;/p>
&lt;blockquote>
&lt;p>大多数人都知道——加班意味着业余时间减少。业余时间减少也就意味着：你更加没有时间去自学，去提升自己的能力。&lt;/p>
&lt;p>如果你的能力得不到提升，你在人力市场上的【议价能力/谈判筹码】也就得不到提升。然后你就不得不继续接受这种变态的工作时间。&lt;/p>
&lt;p>俺把这称之为【996怪圈】——它是一个恶性循环（恶性正反馈），你陷入其中，并越来越无法自拔。&lt;/p>
&lt;/blockquote>
&lt;p>我十分赞同，也是第一次理性的审视我正在从事的工作对我的影响，&lt;/p>
&lt;p>此前虽然模糊间一直有类似的想法，但并未真正的提出来并认真思考过。&lt;/p>
&lt;p>也提到&lt;/p>
&lt;blockquote>
&lt;p>首先，大部分人的工作都【不是】自己的兴趣所在。
其次，超长的工作时间，使得你必须长时间面对自己不感兴趣的工作内容，所以你必须动用“自控力”以完成自己的工作。
最后，当你忙碌了一天，终于回家的时候，很可能&lt;strong>你的自控力已消耗殆尽&lt;/strong>。
结论就是：如果你的工作不是你的兴趣所在，长时间加班之后，回到家里，你很难再有动力去学习其它新技能。
超长加班导致的【自控力损耗】，同样会让你处于（俺前面提到的）【996怪圈】。&lt;/p>
&lt;/blockquote>
&lt;p>我不能更赞同了，其实我可以完全可以接受超长时间的工作，&lt;/p>
&lt;p>前提是我觉得这个很有意义，我感兴趣，&lt;/p>
&lt;p>如果是这样，我想我应该会干到很晚，睡几个小时，&lt;/p>
&lt;p>睡醒立马继续干，这也是我的理想状态 ——热情工作时全身心投入，阶段性休息时完全放松。&lt;/p>
&lt;p>编程随想还在他的另一篇博文中提到了如何跳出【996怪圈】&lt;/p>
&lt;p>&amp;ndash; &lt;a class="link" href="https://program-think.blogspot.com/2020/12/Study-and-Life.html" target="_blank" rel="noopener"
>学习与人生——700篇博文之感悟&lt;/a> 其中的【如何跳出“996怪圈”？】小结&lt;/p>
&lt;p>对他写的跳出【996怪圈】的方法，我想来时比较有道理的，&lt;/p>
&lt;p>但感觉是“道”层面上的，&lt;/p>
&lt;p>对我而言，还缺乏“术”层面的指导，没法实操。&lt;/p>
&lt;p>不过我想，这其实也正是我可以好好考虑、好好设计的地方。&lt;/p>
&lt;p>借今天的思考，&lt;/p>
&lt;p>把结论写在个人网站首页，&lt;/p>
&lt;p>尽早推演出跳出【996怪圈】的术，然后证道，走向理想工作～&lt;/p></description></item><item><title>新年伊始的记录与思考</title><link>https://ethanschen.github.io/life/2025-01-01-new-year-reflection/</link><pubDate>Wed, 01 Jan 2025 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/life/2025-01-01-new-year-reflection/</guid><description>&lt;h2 id="早上">早上
&lt;/h2>&lt;p>元旦，出门去取前几日送到4S店维修漆面的车。&lt;/p>
&lt;p>刚出小区，看着路旁的小河，开始感叹年复一年时光流逝。&lt;/p>
&lt;p>突的回想，实际上记不起来太多，只能模糊的感觉到自己在成长，&lt;/p>
&lt;p>但也怀疑是否很多时间都因为忘记目标而被浪费。&lt;/p>
&lt;p>跳出来想，也许看似浪费&lt;strong>时间的时常记录和反思&lt;/strong>才是捷径，&lt;/p>
&lt;p>因为这种记录能给予后来阅读的自己力量，令“其”道心不变，坚定地走下去。&lt;/p>
&lt;h2 id="中午">中午
&lt;/h2>&lt;p>中午，在浙江图书馆门口遇到传统的爆米花，第一次见，来了兴趣拍了几张。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735748510/2025-01-01-%e6%b5%99%e5%9b%be%e9%97%a8%e5%8f%a3%e7%88%86%e7%b1%b3%e8%8a%b1_j3rlth.jpg"
loading="lazy"
alt="浙图门口爆米花"
>&lt;/p>
&lt;h2 id="接下来的八小时">接下来的八小时
&lt;/h2>&lt;p>接下来的八个多小时，沉浸在这个我初次探访的图书馆的自修室，&lt;/p>
&lt;p>内容是由一开始的“为&lt;code>hashicorp/terraform-provider-kubernetes&lt;/code>开源项目修复一个上周工作中遇到的Bug”&lt;/p>
&lt;p>到“给&lt;code>hashicorp/terraform-provider-kubernetes&lt;/code>开源项目提该Bug的Issue并与社区成员确认修复方案是否可行”&lt;/p>
&lt;p>再到“使用AWS的EKS搭建terraform-provider-kubernetes测试环境”，&lt;/p>
&lt;p>我的开发工作就是这样，&lt;/p>
&lt;p>总是一直入栈，然后再一步步出栈，才能完成。&lt;/p>
&lt;p>但很不幸，之前没有记录的时候，对于开源这类没有外部Push的任务，&lt;/p>
&lt;p>往往在经历一周的工作后就不知道被我忘到哪里去了。&lt;/p>
&lt;p>到家后复盘，其实任务本身倒是其次，&lt;/p>
&lt;p>而我对待这些任务的态度则更为重要。&lt;/p>
&lt;h2 id="反思">反思
&lt;/h2>&lt;p>今天反思后想到的几个点：&lt;/p>
&lt;ol>
&lt;li>需要延长每日的工作时间，不为别的，而是需要在本职工作外有时间投入其它，避免陷入“996”怪圈&lt;/li>
&lt;li>本职工作占据的时间太长，且很多时候压力太大，经常让我陷于其中而忘了职业生涯这盘大棋，这需要每日警醒&lt;/li>
&lt;li>记录本身很耗费时间，尤其在刚开始做这个事情的时候，但时间一长不经能让我阶段性复盘调整，也是我以后向上最扎实的土壤&lt;/li>
&lt;/ol>
&lt;p>暂且记这几点。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1735748789/2025-01-01-%e6%b5%99%e5%9b%be%e6%97%81%e6%99%af%e5%8c%ba%e5%b0%8f%e9%81%93_p6xn9v.jpg"
loading="lazy"
alt="浙图旁景区小道"
>&lt;/p></description></item><item><title>阅读笔记&lt;一></title><link>https://ethanschen.github.io/life/2024-10-20-read-notes/</link><pubDate>Sun, 20 Oct 2024 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/life/2024-10-20-read-notes/</guid><description>&lt;h2 id="中国人的收入到底有多高网易httpswebarchiveorgweb20210130085955httpswww163comdyarticleflg3uh8p0521agaehtml">中国人的收入到底有多高@&lt;a class="link" href="https://web.archive.org/web/20210130085955/https://www.163.com/dy/article/FLG3UH8P0521AGAE.html" target="_blank" rel="noopener"
>网易&lt;/a>
&lt;/h2>&lt;blockquote>
&lt;p>&lt;strong>中国有多少穷人？&lt;/strong>&lt;/p>
&lt;p>　　总理李克强给了一个算得上官方权威的回答：中国有「6亿中低收入及以下人群，他们平均每个月的收入也就1000元左右」。这个数字让很多人感到惊讶，他们难以想象「每个月1000元是怎么活下来的」。
　　后续媒体报道引用统计数据来说明总理回答的严谨性。如根据国家统计局公布的住户抽样调查数据，2019 年收入最低的 40% 家庭的年人均可支配收入为11579元，月人均可支配收入为965元。也就是说，中国平均每月可支配收入在1000元左右的人群超过了 40%。
　　更常见的引用数据来自北京师范大学中国收入分配研究院，他们的《中国家庭收入调查》（Chinese Household Income Project，简称 CHIPs）通常被视为国内最权威的收入分配研究。CHIPs 的数据也同样印证了总理回答的准确：
&lt;em>中国有 39.1% 的人口月收入低于1000元，换算成人口数即为5.47亿人，同时月收入在1000-1090元的人口为5250万人，因此1090元以下的总人口为6亿人，占全国人口比重为 42.85%。&lt;/em>
……
网民依据 CHIPs 的表格还注意到另一个问题：中国有多少富人？「答案」似乎更加令人惊讶，他们发现：&lt;strong>原来人均可支配收入超过每月两万的只有70万人，占总人口比例 0.05%&lt;/strong> 。
　　这不是平常所说的「中产阶级」吗？「中产阶级」的人数居然都只有这么点？到底是哪里出了问题？&lt;/p>
&lt;p>　　&lt;strong>「月收入1000」的含义&lt;/strong>&lt;/p>
&lt;p>　　看新闻的大多数人并没有弄明白总理所说的「月收入」的意思，因而高估了那「6亿」人的贫穷程度，同时低估了那「70万」人的富裕程度。
　　总理说的「每个月的收入」，指的是「家庭人均可支配月收入」。
　　其中「可支配收入」不易误解，已经算得上一个常见的概念，它可以粗略地理解为一个居民在税后拿到的各项收入，无论这份收入是工资、开店办厂得来的，还是拿到的补助、补偿、捐助，甚至包括「单位发的」实物福利。
　　但「家庭人均」才意味着这些数据的实际统计方法：它是由调查人员追踪访问得到的一个家庭的可支配收入，再除以家庭成员的人数，得到的「人均收入」。
　　比如说，一对中西部农村夫妇外出到工厂打工，家中留守一对子女，由一位老人照顾，这样的家庭就有五名成员，工厂工资3000元出头，一平均，他们全家都是月收入1000元左右的中低收入人群。
　　而基于现实考虑，上有老下有小的农村家庭也不太能放任所有壮年劳动力全部外出，更常见的情形应该是妻子在老家务农，丈夫跨省务工。这样即使丈夫成为了熟练工乃至工匠，每月能挣到六七千，算得上较高收入的打工者，其家庭人均月收入仍然在「1000元左右」。
　　根据北师大 CHIPs 的调查数据，不难为这「六亿人」作一个画像：他们绝大多数来自农村、中西部地区，家庭规模在各阶层中是最大的 （户均 4.59 人） ，能挣钱拿工资的比例却是最低的 （37.37%） ，与上面讲到的两种例子高度相符，再加上学历普遍不高，也限制了他们务工挣钱的上限。&lt;/p>
&lt;p>　　&lt;strong>「月收入两万」的含义&lt;/strong>&lt;/p>
&lt;p>　　那要做到「人均可支配收入」超过两万，这样的家庭又该富到什么程度呢？
　　考虑到国内富裕家庭较多生育二胎，可以以一个四口之家为例，在社保、个税缴纳规范的城市里，一对夫妻挣钱，养两个孩子，也就是夫妻双方的&lt;strong>税后&lt;/strong>月收入都在4万元以上，两人加一块，&lt;strong>税前&lt;/strong>年收入至少要超过125万。如果这对夫妻的收入并不均衡，比如妻子税后月收入「只」有2万元，则丈夫&lt;strong>税前&lt;/strong>月薪必须超过8.3万，属于百万年薪的「金领」阶层。
　　他们当然是凤毛麟角一般的有钱人。&lt;/p>
&lt;p>　　&lt;strong>真实的「中产」&lt;/strong>&lt;/p>
&lt;p>　　即使看了前面的解释，很多人大概还是会诧异，觉得中低收入人群比他想象的要多，而「金领」阶层远比他想象的要少。
　　这也不奇怪，&lt;strong>我们日常在舆论里，见到的多是关于「中产阶级」的迷思性描述，再加上为「中产」量身打造的消费宣传，很容易拉高了大众对中国人收入状况的估计。&lt;/strong>
　　到底赚多少能算中产？其实在大约十年前，学术界就有一个相对合理的国内中产阶级标准——家庭&lt;strong>年收入&lt;/strong>超过8000美元，在今天折合人民币约5.5万。多少有点巧合，按照西南财经大学中国家庭金融调查与研究中心发布的数据，这刚好是2018年中国家庭收入的中位数。
　　不过在今天来看，一个家庭只有这样的年收入显然有些寒酸，他们在生活方式上应该更接近总理说的那六亿人，而与大众印象里的「中产」相去甚远。
　　但能够符合「中产」印象的人实在是太少了。人们把上万月薪视作「中产」的基本门槛，然而根据中国家庭追踪调查2018年数据，哪怕是全家人&lt;strong>税后月收入&lt;/strong>加起来能超过一万元，都已经超过了全国 87.5% 的家庭。如果夫妻双方的&lt;strong>税后工资&lt;/strong>（社保、个税规范缴纳）均超过一万元，那他们必定属于中国前 5% 的高收入人群。
　　未婚青年可能对家庭收入的概念缺乏感知，中国家庭追踪调查里还有一项「全国城镇劳动力主要工作收入」，同样可以颠覆很多人对自己收入的认识：一个月税后挣到5000元以上 （包括奖金） ，就超过了全国 80% 的工薪族。至于税后工资过万，那可以超过 97.5%。
　　显然，绝大多数中国人的收入水平，都与电影、电视剧甚至媒体热炒的公共话题对不上号&lt;/p>
&lt;p>&amp;hellip;&amp;hellip;&lt;/p>
&lt;/blockquote>
&lt;p>如上文分析，【家庭人均可支配】的说法让我们高估【不足1000元】的贫困程度，实际上这可能是我们周边非常普通的一个外出务工家庭的收入水平。&lt;/p>
&lt;h2 id="how-law-enforcement-gets-around-your-smartphones-encryptionars-technicahttpsarstechnicacominformation-technology202101how-law-enforcement-gets-around-your-smartphones-encryption">How law enforcement gets around your smartphone’s encryption@&lt;a class="link" href="https://arstechnica.com/information-technology/2021/01/how-law-enforcement-gets-around-your-smartphones-encryption/" target="_blank" rel="noopener"
>Ars Technica&lt;/a>
&lt;/h2>&lt;blockquote>
&lt;p>&amp;hellip;&amp;hellip;&lt;/p>
&lt;p>When an iPhone has been off and boots up, all the data is in a state Apple calls 【Complete Protection】. The user must unlock the device before anything else can really happen, and the device&amp;rsquo;s privacy protections are very high. You could still be forced to unlock your phone, of course, but existing forensic tools would have a difficult time pulling any readable data off it. Once you&amp;rsquo;ve unlocked your phone that first time after reboot, though, a lot of data moves into a different mode—Apple calls it &amp;ldquo;Protected Until First User Authentication&amp;rdquo;, but researchers often simply call it 【After First Unlock】（注：简称 AFU）.&lt;/p>
&lt;p>If you think about it, your phone is almost always in the AFU state. You probably don&amp;rsquo;t restart your smartphone for days or weeks at a time, and most people certainly don&amp;rsquo;t power it down after each use. (For most, that would mean hundreds of times a day.) So how effective is AFU security? That&amp;rsquo;s where the researchers started to have concerns.&lt;/p>
&lt;p>The main difference between Complete Protection and AFU relates to how quick and easy it is for applications to access the keys to decrypt data. When data is in the Complete Protection state, the keys to decrypt it are stored deep within the operating system and encrypted themselves. But once you unlock your device the first time after reboot, lots of encryption keys start getting stored in quick access memory, even while the phone is locked. At this point an attacker could find and exploit certain types of security vulnerabilities in iOS to grab encryption keys that are accessible in memory and decrypt big chunks of data from the phone.&lt;/p>
&lt;p>&amp;hellip;&amp;hellip;&lt;/p>
&lt;/blockquote>
&lt;p>如上文分析，政府执法机构破解手机其实比多数人想象中的更容易，不论是iOS还是Android。在【开机且解锁过一次】的状态下，即使手机屏幕已锁定，也很容易破解。关键在于，开机第一次解锁之后，全盘加密的【密钥】就会位于【内存】中。此时，“手机取证软件”只要能利用某种系统漏洞 or 软件漏洞，拿到内存中的“全盘加密密钥”，就 OK 啦。作为对比，如果是在【关机】状态下，破解的难度就大得多（但依然有可能破解）。&lt;/p>
&lt;h2 id="博弈论换位思考">博弈论【换位思考】
&lt;/h2>&lt;p>需要站在“对手”的角度进行思考，才能看清局面，从而更好地选择自己的策略。&lt;/p>
&lt;h2 id="华为高管声称鸿蒙-v3-将不再基于-android--solidothttpswwwsolidotorgstorysid67099">华为高管声称：鸿蒙 V3 将不再基于 Android @ &lt;a class="link" href="https://www.solidot.org/story?sid=67099" target="_blank" rel="noopener"
>Solidot&lt;/a>
&lt;/h2>&lt;blockquote>
&lt;p>华为消费者业务软件部总裁王成录今年初曾表示，鸿蒙不是 Android 或 iOS 的拷贝。但对鸿蒙 V2 的分析表明，它事实上是 Android 的拷贝，华为甚至连 Android 的名字都没有从系统中替换掉。
对此王成录在接受采访时回应称：“并不是所有 Android 代码都是 Google 开发的，绝大部分代码来自开源社区。鸿蒙也会吸收社区的优秀技术和代码，用了 AOSP（Android 开源项目）的开源代码，就判断鸿蒙是 Android 换了皮，说明这类吐槽者没有太准确理解什么是开源。今年10月，鸿蒙第三阶段的开源代码会上线，来自 AOSP 社区的、由 Google 贡献的代码几乎没有了。”&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>华为高管的辩解，属于“偷换概念”（稻草人谬误）。&lt;/strong>
之前网民指责“鸿蒙是安卓换皮”，因为鸿蒙大量使用了 AOSP（Android Open Source Project）的代码。在【源代码】层面，“鸿蒙系统”与“安卓系统”实在太相似了。
华为高管的忽悠在于——先把命题“鸿蒙使用安卓（AOSP）的代码”偷偷替换为“鸿蒙使用谷歌（Google）的代码”。然后再针对后一个命题进行反驳。这就是典型的【稻草人谬误】。
华为高管采用这种诡辨式的逻辑把戏，反而让人觉得：他们心虚。&lt;/p></description></item><item><title>阅读节选</title><link>https://ethanschen.github.io/life/2024-08-30-read-excerpts/</link><pubDate>Fri, 30 Aug 2024 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/life/2024-08-30-read-excerpts/</guid><description>&lt;h2 id="阅读节选">阅读节选
&lt;/h2>&lt;p>我是觉得人一定要有自己的基本盘，这非常重要。比如我现在问很多人，以后还有什么目标、理想吗？他们都说没有。我是一直有的，甚至知道自己以后十年、二十年、三十年、四十年的目标。始终有一个目标，也许你到不了，但是一直在路上，你在这个过程中是有满足感的，所以我觉得，能在新的价值体系里找到一个新的目标去追求，非常重要。这个目标必须相对具象，而且真的和你对自己的情感认同、价值认同有关联。
有时也许你不知道自己的目标在哪儿，但是只要你完成眼前的这个目标，到达一个更高的平台，就能看到新的东西，那一刻，你就会突然发现自己要的是什么。
我们要有一个成长的目标和方向，它不是一个轻浮的点，而是用它构筑起来一个你愿意活在其中的系统，这也是我曾经所说的心理张力的来源。我们要在自己定义的价值体系里，体验到不断积累、丰富、前进、变化的感觉，只要这种体验能贯穿在生命里，无论你做什么，你都会在不可避免的生命之苦之外，体验到活着的感觉，并且想要好好活着。&lt;/p>
&lt;p>—— 节选自《多谈谈问题》&lt;/p>
&lt;p>当精神分析告诉我，我的原生家庭决定了我的宿命，我就非常生气。对我来说，我作为一个学过哲学的人，最大的荣誉就是捍卫自由意志。我所有作品都会涉及人类有自由意志这样一个基本观念。一个思考哲学问题的人，如果摒弃了自由意志，对我来说是不可想象的。**我认为一个人文主义者应该坚定地同机械主义、决定论、宿命论做最持久的斗争，因为这不仅关乎自由意志，更关乎人类的道德。决定论就是一个悖论，它会架空人类的道德。自由意志是不可知的，它是神秘的、超验的。**为什么聊弗洛伊德的观念？因为我觉得这是僭越人类的自由意志。从这个角度来说，我去读阿德勒，他的作品能给我们带来更宏大、更有希望的图景，因为你的选择决定了你的当下，决定了你的未来。&lt;/p>
&lt;p>—— 节选自《多谈谈问题》&lt;/p>
&lt;p>我们犯的第一个错误是选错了试图改变的事情。为了更好地理解我的意思，你可以考虑把改变发生的进程分为三个层次，就像洋葱一样。
第一层是改变你的结果。这个层次事关改变你的结果：减肥、出版书籍、赢得冠军。你设定的大多数目标与这个层次的改变相关。
第二层是改变你的过程。这一层次涉及改变你的习惯和体系：定时去健身房锻炼、定期整理你的办公桌以提高工作效率，以及按时练习冥想。你养成的大多数习惯与这一层次有关。
第三层是改变你的身份。这一层有关改变你的信念：你的世界观、你的自我形象，以及你对自己和他人所做的判断。你持有的大多数信念、假设和偏见与这个层次相关。
结果意味着你得到了什么，过程意味着你做了什么，身份则关系到你的信念。当谈到培养持久习惯，以及创设改进1%的体系时，问题不在于这层比那层“更好”或“更差”。所有层级的变化都各有用处，关键是改变的方向。
许多人开始改变自己的习惯时，把注意力集中在自己想要达到的目标上。这会导致他们养成基于最终结果的习惯。正确的做法是培养基于自己身份的习惯。借助于这种方式，我们的着眼点是我们希望成为什么样的人。
真正的行为上的改变是身份的改变。&lt;/p>
&lt;p>—— 节选自《原子习惯》(中文版)&lt;/p></description></item><item><title>我在Dubbo源码里学到了如何保证虚拟节点均匀分布</title><link>https://ethanschen.github.io/tech/2021-06-13-virtual-nodes-distribution/</link><pubDate>Sun, 13 Jun 2021 12:00:00 +0800</pubDate><guid>https://ethanschen.github.io/tech/2021-06-13-virtual-nodes-distribution/</guid><description>&lt;h2 id="背景">背景
&lt;/h2>&lt;p>上周更新了一篇文章《&lt;a class="link" href="https://www.ethans.space/post/tech-2021-06-05-consistent-hash/" target="_blank" rel="noopener"
>一致性Hash算法与虚拟节点&lt;/a>》，阅读和收藏人数挺多的。&lt;/p>
&lt;p>今天有朋友问了我一个问题，虚拟节点如何保证均匀分布？&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736087686/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83_dwiox7.png"
loading="lazy"
alt="虚拟节点保证均匀分布"
>&lt;/p>
&lt;p>我不假思索的回答，&lt;/p>
&lt;p>不需要保证虚拟节点的均匀分布，&lt;/p>
&lt;p>虚拟节点用以保证相对的均匀靠得是量变产生质变，&lt;/p>
&lt;p>就像我文末提到的，在实际场景中，虚拟节点的个数只有3个是远远不够的。&lt;/p>
&lt;p>例如Dubbo中用到一致性hash算法时，默认的虚拟节点个数是160个，&lt;/p>
&lt;p>假设我们有四个服务节点需要创建虚拟节点，那就会有 4 * 160 = 640 个虚拟节点，&lt;/p>
&lt;p>在这样大量的基数下，必然他们的分布就会呈一种相对均匀的状态。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736087931/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE2_wr327p.webp"
loading="lazy"
alt="虚拟节点保证均匀分布图2"
>&lt;/p>
&lt;p>回答完我感觉很满意，不愧是我！&lt;/p>
&lt;p>可转念一想，再想，三想，&lt;/p>
&lt;p>好像不是这么一回事，&lt;/p>
&lt;p>别说是有640个节点了，就算有6400个，64000个节点又如何呢？&lt;/p>
&lt;p>在极小极小的概率下，如果hash算法不能保证映射的均匀性，&lt;/p>
&lt;p>他们依然可能落在十分聚集的一小块区域中。&lt;/p>
&lt;p>反推一下，既然一致性hash算法作为一个成熟并拥有很多应用场景的算法，&lt;/p>
&lt;p>不可能如此不严谨，所以hash算法本身应该是可以保证映射的一致性的。&lt;/p>
&lt;p>“应该”？这么不确定可不行，我得好好补补空白，确实之前没有去了解过hash算法到底实现了个啥。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088024/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE3_qkhq81.webp"
loading="lazy"
alt="虚拟节点保证均匀分布图3"
>&lt;/p>
&lt;p>东查西查，终于有了答案。&lt;/p>
&lt;p>百度百科对Hash的名次解释如下：&lt;/p>
&lt;blockquote>
&lt;p>Hash，一般翻译做散列、杂凑，或音译为哈希，是把任意长度的输入（又叫做预映射pre-image）通过散列算法变换成固定长度的输出，该输出就是散列值。&lt;/p>
&lt;/blockquote>
&lt;p>哦，原来就是散列函数啊！&lt;/p>
&lt;p>那就简单了，散列函数我本科学过啊！&lt;/p>
&lt;p>脑海一番搜索，糟糕，我真的学过吗？唯一的印象就是学过。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088081/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE4_zhnbds.gif"
loading="lazy"
alt="虚拟节点保证均匀分布图4"
>&lt;/p>
&lt;p>没事，学过忘了没关系，再学就完事了。&lt;/p>
&lt;p>以下是干货，&lt;/p>
&lt;p>一个合格的散列函数包含三个特征：&lt;/p>
&lt;ul>
&lt;li>单向性：容易计算输入的散列结果，但是从散列结果无法倒推出输入；&lt;/li>
&lt;li>抗冲突性：很难找到两个不同的输入散列结果相同；&lt;/li>
&lt;li>映射分布均匀性和差分分布均匀性：散列结果中 bit 位上的 0 和 1 的数量应当大致相等；改变输入内容的 1 个 bit 信息会导致散列结果一半以上的 bit 位变化（雪崩效应）。&lt;/li>
&lt;/ul>
&lt;p>真刺激，到这是不是就完事了，散列函数保证了映射的分布均匀性。&lt;/p>
&lt;p>我劝你要不要再回去看两遍映射分布均匀性和差分分布均匀性的内容。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088121/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE5_khjqop.webp"
loading="lazy"
alt="虚拟节点保证均匀分布图5"
>&lt;/p>
&lt;p>看完了吗？&lt;/p>
&lt;p>再想想《&lt;a class="link" href="https://www.ethans.space/post/tech-2021-06-05-consistent-hash/" target="_blank" rel="noopener"
>一致性Hash算法与虚拟节点&lt;/a>》中的hash环，&lt;/p>
&lt;p>有没有顿时反应过来，这边又TM不严谨了。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088192/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE6_inpjvs.jpg"
loading="lazy"
alt="虚拟节点保证均匀分布图6"
>&lt;/p>
&lt;p>说好的散列结果中bit位上 0 和 1 的数量大致相等，这要是映射到hash环上，&lt;/p>
&lt;p>最小值岂不是 00000000000000001111111111111111，&lt;/p>
&lt;p>最大值岂不是 11111111111111110000000000000000，&lt;/p>
&lt;p>（别数数字，我随便打的，你懂我意思吧）&lt;/p>
&lt;p>那在hash环上不就出现了一段真空地带了吗？&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088246/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE7_lfzplr.webp"
loading="lazy"
alt="虚拟节点保证均匀分布图7"
>&lt;/p>
&lt;p>虽然实际上真空地带比图上的还小，只有 65536/4294967295 约等于 百万分之十五，&lt;/p>
&lt;p>但这微小的缺口无疑将带来无法估计的灾难，尤其是在实际生产环境中，&lt;/p>
&lt;p>你永远不知道一个微小的漏洞可能会造成多大的后果。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088306/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE8_plgeb3.webp"
loading="lazy"
alt="虚拟节点保证均匀分布图8"
>&lt;/p>
&lt;p>不行，“革命还未成功，同志仍需努力。”&lt;/p>
&lt;p>我应该离胜利不远了，&lt;/p>
&lt;p>正好，最近在学Dubbo，&lt;/p>
&lt;p>里面的负载均衡就用到了一致性hash算法，&lt;/p>
&lt;p>我直接掏出源码看看不就知道了。&lt;/p>
&lt;p>Dubbo仓库地址：&lt;a class="link" href="https://github.com/apache/dubbo" target="_blank" rel="noopener"
>https://github.com/apache/dubbo&lt;/a>&lt;/p>
&lt;p>找到 ConsistentHashLoadBalance 类，&lt;/p>
&lt;p>这边我将我们最关心的代码贴上来，&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">replicaNumber&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getMD5&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">address&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kt">long&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">virtualInvokers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">invoker&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中 replicaNumber 就是我们要创建的虚拟节点数目（副本数），&lt;/p>
&lt;p>可以看到，代码中巧妙的通过两层 for 循环，&lt;/p>
&lt;p>令外层循环上限除以 4，内层循环 4 次，使得总副本数目保持原水平，&lt;/p>
&lt;p>如 （160/4）* 4 = 160。&lt;/p>
&lt;p>为什么要有这种安排呢？&lt;/p>
&lt;p>让我们继续往下看，&lt;/p>
&lt;p>首先让实际服务地址加上 i 形成新的字符串，再对其取 MD5 值，&lt;/p>
&lt;p>也就是说一个 MD5 值实际上是要生成 4 个副本位置的。&lt;/p>
&lt;p>问题一个接着一个，&lt;/p>
&lt;p>为什么要让一个 MD5 值生成 4 个副本位置？&lt;/p>
&lt;p>一来节约了计算的成本，&lt;/p>
&lt;p>二来实际上一个 MD5 值确实够生成 4 个副本位置了。&lt;/p>
&lt;p>别忘了，我们的 hash 环只有 2 的 32 次方 - 1 个位置，&lt;/p>
&lt;p>而一个 MD5 值却有 16 个字节，16 * 8 = 128 个bit，&lt;/p>
&lt;p>这意味着只要我们切分得当，一个 MD5 值确实可以生成 4 个hash环中的位置值。&lt;/p>
&lt;p>让我们继续看看上面贴出来的代码中的 hash 方法实现&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">long&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">number&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(((&lt;/span>&lt;span class="kt">long&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">3&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">number&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0xFF&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&amp;lt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">24&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="kt">long&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">number&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0xFF&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&amp;lt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">16&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="kt">long&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">number&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0xFF&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&amp;lt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">8&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">number&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0xFF&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">0xFFFFFFFFL&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我来演示一下第一个副本位置值的生成过程，&lt;/p>
&lt;p>假设我们的address是 127.0.0.1:20880，i 为0。&lt;/p>
&lt;p>那么此时我们的 digest 就是 127.0.0.1:208800 取 MD5值，其 16 进制为&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">f74f8dee9c0d89ddc47b2926374ec3b5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在 byte 数组中为（展示二进制）&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11110111,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 01001111,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 10001101,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11101110,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 10011100,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 00001101,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 10001001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11011101,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11000100,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 01111011,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 00101001,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 00100110,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 00110111,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 01001110,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 11000011,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 10110101,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>分解上面那个略微复杂但有规律可循的 hash 方法，&lt;/p>
&lt;p>首先计算&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="o">((&lt;/span>long&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>digest&lt;span class="o">[&lt;/span>&lt;span class="m">3&lt;/span> + number * 4&lt;span class="o">]&lt;/span> &lt;span class="p">&amp;amp;&lt;/span> 0xFF&lt;span class="o">)&lt;/span> &amp;lt;&amp;lt; 24&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此刻，number为0，&lt;/p>
&lt;p>即原式变为&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="o">((&lt;/span>long&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="m">11101110&lt;/span> &lt;span class="p">&amp;amp;&lt;/span> 0xFF&lt;span class="o">)&lt;/span> &amp;lt;&amp;lt; 24&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果为&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="m">11101110&lt;/span> &lt;span class="m">00000000&lt;/span> &lt;span class="m">00000000&lt;/span> &lt;span class="m">00000000&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>依次计算&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="o">((&lt;/span>long&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>digest&lt;span class="o">[&lt;/span>&lt;span class="m">2&lt;/span> + number * 4&lt;span class="o">]&lt;/span> &lt;span class="p">&amp;amp;&lt;/span> 0xFF&lt;span class="o">)&lt;/span> &lt;span class="s">&amp;lt;&amp;lt; 16)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">((long) (digest[1&lt;/span> + number * 4&lt;span class="o">]&lt;/span> &lt;span class="p">&amp;amp;&lt;/span> 0xFF&lt;span class="o">)&lt;/span> &lt;span class="s">&amp;lt;&amp;lt; 8)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">((long) (digest[1 + number * 4] &amp;amp; 0xFF) &amp;lt;&amp;lt; 8&lt;/span>&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>结果分别为&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="m">00000000&lt;/span> &lt;span class="m">10001101&lt;/span> &lt;span class="m">00000000&lt;/span> &lt;span class="m">00000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">00000000&lt;/span> &lt;span class="m">00000000&lt;/span> &lt;span class="m">01001111&lt;/span> &lt;span class="m">00000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">00000000&lt;/span> &lt;span class="m">00000000&lt;/span> &lt;span class="m">00000000&lt;/span> &lt;span class="m">11110111&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>进行 或运算，结果为&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="m">11101110&lt;/span> &lt;span class="m">10001101&lt;/span> &lt;span class="m">01001111&lt;/span> &lt;span class="m">11110111&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>再与 0xFFFFFFFFL 进行 与运算，保证结果为 32 位。&lt;/p>
&lt;p>到这里，我们的第一个副本位置终于确定啦&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="m">11101110&lt;/span> &lt;span class="m">10001101&lt;/span> &lt;span class="m">01001111&lt;/span> &lt;span class="m">11110111&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>你可以依葫芦画瓢的由同一个 MD5 值生成另外三个副本位置，&lt;/p>
&lt;p>我将另外三个副本位置附上，你可以像上数学课时一样，&lt;/p>
&lt;p>当作参考答案，确保自己的思路是正确的。&lt;/p>
&lt;p>另外三个副本位置如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="m">10011100&lt;/span> &lt;span class="m">00001101&lt;/span> &lt;span class="m">10001001&lt;/span> &lt;span class="m">11011101&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">11000100&lt;/span> &lt;span class="m">01111011&lt;/span> &lt;span class="m">00101001&lt;/span> &lt;span class="m">00100110&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">00110111&lt;/span> &lt;span class="m">01001110&lt;/span> &lt;span class="m">11000011&lt;/span> &lt;span class="m">10110101&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="总结">总结
&lt;/h2>&lt;p>撒花，终于算完了，我们可以来总结一下了。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088391/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE9_cxqzwv.webp"
loading="lazy"
alt="虚拟节点保证均匀分布图9"
>&lt;/p>
&lt;p>在Dubbo的源码中，&lt;/p>
&lt;p>我们发现，实现一致性hash算法时，&lt;/p>
&lt;p>用于计算副本位置的定位算法，实际上每个位置只需要 MD5 值的 四分之一，&lt;/p>
&lt;p>而上面提到的合格的散列函数应该（大致）包含一半bit为 0 一半为 1 大于 四分之一，&lt;/p>
&lt;p>所以定位算法产生的结果覆盖了 0 ~ （2 的 32 次方 -1），&lt;/p>
&lt;p>即 hash 环上所有位置，&lt;/p>
&lt;p>又因为散列函数的雪崩效应，实现了映射分布的均匀性，&lt;/p>
&lt;p>即虚拟节点的均匀分布。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736088447/2021-06-13-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9%E4%BF%9D%E8%AF%81%E5%9D%87%E5%8C%80%E5%88%86%E5%B8%83%E5%9B%BE10_prm5vp.webp"
loading="lazy"
alt="虚拟节点保证均匀分布图10"
>&lt;/p>
&lt;p>怎么样？同学，对我这个答案还满意吗？&lt;/p>
&lt;p>所以说，同一个问题，&lt;/p>
&lt;p>或许我们可以“不假思索”的回答，&lt;/p>
&lt;p>但有时候我们也可以沉下心来好好研究研究。&lt;/p></description></item><item><title>一致性Hash算法与虚拟节点</title><link>https://ethanschen.github.io/tech/2021-06-05-consistent-hash/</link><pubDate>Sat, 05 Jun 2021 01:07:08 +0800</pubDate><guid>https://ethanschen.github.io/tech/2021-06-05-consistent-hash/</guid><description>&lt;h2 id="缘起">缘起
&lt;/h2>&lt;p>今天我们来说说“一致性Hash”算法，以及虚拟节点。&lt;/p>
&lt;p>这并不是一个难理解的概念，希望一篇文章下来，你能完全吃透。&lt;/p>
&lt;p>在网站系统发展初期，前辈工程师探索出了数据库这一系统核心组件，&lt;/p>
&lt;p>数据的持久化被与系统本身解耦开，独立发展且愈加可靠。&lt;/p>
&lt;p>时间往后推移，随着互联网的普及，一个系统需要承载的用户数量指数级增长，&lt;/p>
&lt;p>开发者不得不横向扩展服务器，通过负载均衡技术，使用户分散到各个服务器上。&lt;/p>
&lt;p>随着服务器的增多，可靠的数据库系统也不堪重负，&lt;/p>
&lt;p>开发者不得不将数据库中的数据通过“分库分表”技术，切分到不同的数据库中，&lt;/p>
&lt;p>减轻单一数据库系统的压力。&lt;/p>
&lt;p>那么问题来了，如何知道我们需要的数据在哪个数据库中？&lt;/p>
&lt;p>没错。hash！&lt;/p>
&lt;p>正如我们在 HashMap 中做的一样，对参数取 hash 值，再对 hash 值取模，&lt;/p>
&lt;p>就可以既均匀切分存储数据，又知道数据在哪个库中。&lt;/p>
&lt;h2 id="简单hash">简单hash
&lt;/h2>&lt;p>举个例子，现在有A，B，C，D共4个库，和参数为1，2，3……9，10共10个数据。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077122/2021-06-05-%E7%AE%80%E5%8D%95Hash_qmbf9a.webp"
loading="lazy"
alt="简单Hash"
>&lt;/p>
&lt;p>我们简化hash算法为乘以1，&lt;/p>
&lt;p>即 (1*1)%4=1，参数为1的数据落在A库中。&lt;/p>
&lt;p>即 (2*1)%4=2，参数为2的数据落在B库中。&lt;/p>
&lt;p>即 (3*1)%4=3，参数为3的数据落在C库中。&lt;/p>
&lt;p>即 (4*1)%4=0，参数为4的数据落在D库中。&lt;/p>
&lt;p>……&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077373/2021-06-05-%E7%AE%80%E5%8D%95Hash%E5%9B%BE2_wgcdar.webp"
loading="lazy"
alt="简单Hash图2"
>&lt;/p>
&lt;p>嗯！我很满意，一切井然有序。&lt;/p>
&lt;p>当系统需要参数为2的数据时，只需要通过定位算法 (2*1)%4=2 便知道数据存在B库中。&lt;/p>
&lt;p>当系统需要参数为9的数据时，只需要通过定位算法 (9*1)%4=1 便知道数据存在A库中。&lt;/p>
&lt;p>可好景不长，正当系统稳定健康运行的时候，&lt;/p>
&lt;p>B库不知道出现什么问题，失去了连接，系统中只剩下A，C，D共3个库。&lt;/p>
&lt;p>这可真糟糕，但是我们还不知道会发生什么事情，对系统的影响有多大。&lt;/p>
&lt;p>让我们先把目光聚集到局部上面来分析一下，&lt;/p>
&lt;p>参数为2,6和10的数据存在B库上，影响应该是最大的，&lt;/p>
&lt;p>如果现在系统需要参数为2的数据，那么它会通过定位算法 (2*1)%3=2 找到C库上。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077285/2021-06-05-%E7%AE%80%E5%8D%95Hash%E5%9B%BE3_iemdmd.webp"
loading="lazy"
alt="简单Hash图3"
>&lt;/p>
&lt;p>但很遗憾，C上并没有存储参数为2的数据。&lt;/p>
&lt;p>值得庆幸的是，原来数据是有副本的，失去连接的B库数据并没有丢失，而是在一个更大的主库中，&lt;/p>
&lt;p>只要给系统一些时间，主库中对应B库的数据，就会根据定位算法被重新分配到A，C，D库中。&lt;/p>
&lt;p>分配如下，&lt;/p>
&lt;p>即 (2*1)%3=2，参数为2的数据落在C库中。&lt;/p>
&lt;p>即 (6*1)%3=0，参数为6的数据落在D库中。&lt;/p>
&lt;p>即 (10*1)%3=1，参数为10的数据落在A库中。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077435/2021-06-05-%E7%AE%80%E5%8D%95Hash%E5%9B%BE4_lufbom.webp"
loading="lazy"
alt="简单Hash图4"
>&lt;/p>
&lt;p>好了，现在原来B库中的数据被重新分配到A，C，D库。&lt;/p>
&lt;p>当系统需要取数据的时候，只需要通过参数根据定位算法，&lt;/p>
&lt;p>到对应的库中读取即可。&lt;/p>
&lt;p>如果现在你以为万事大吉，那可就太早了。&lt;/p>
&lt;p>别忘了我们刚刚是聚焦到局部来分析状况的。&lt;/p>
&lt;p>当我们目光拉远时，我们发现，&lt;/p>
&lt;p>不止是参数为2,6,10的数据被重新分配，&lt;/p>
&lt;p>几乎所有的数据都被重新分配了！&lt;/p>
&lt;p>因为，&lt;/p>
&lt;p>(1*1)%3=1，参数为1的数据落在A库中。&lt;/p>
&lt;p>(2*1)%3=2，参数为2的数据落在C库中。&lt;/p>
&lt;p>(3*1)%3=0，参数为3的数据落在D库中。&lt;/p>
&lt;p>……&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077525/2021-06-05-%E7%AE%80%E5%8D%95Hash%E5%9B%BE5_g7cgue.webp"
loading="lazy"
alt="简单Hash图5"
>&lt;/p>
&lt;p>真是糟糕透顶！&lt;/p>
&lt;p>原本是想节约提高性能，结果凭空需要浪费这么多计算资源在重新分配数据上。&lt;/p>
&lt;p>该怎么办呢？&lt;/p>
&lt;p>诶，对，一致性Hash算法要大显身手了！&lt;/p>
&lt;h2 id="一致性hash算法">一致性Hash算法
&lt;/h2>&lt;p>一致性Hash算法通过一个 Hash 环，巧妙的让影响降到很低。&lt;/p>
&lt;p>假设存在一个 Hash 环，它一圈的范围是 0 到 2^32-1，&lt;/p>
&lt;p>先对数据库A，B，C和D的标识做 hash 运算，即上面的定位算法，&lt;/p>
&lt;p>确定4个库在 Hash 环上的位置，&lt;/p>
&lt;p>再通过定位算法将得出参数为1，2，3……9，10共10个数据在 Hash环上的位置，&lt;/p>
&lt;p>这边我们不展示过程，只展示结果。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077577/2021-06-05-%E4%B8%80%E8%87%B4%E6%80%A7Hash_mmbfui.webp"
loading="lazy"
alt="一致性Hash"
>&lt;/p>
&lt;p>一致性Hash算法规定我们将数据存在定位到的 Hash 环上位置顺时针遇到的第一个节点（数据库）。&lt;/p>
&lt;p>即，&lt;/p>
&lt;p>参数为1,4和8的数据存在数据库A中，&lt;/p>
&lt;p>参数为5的数据存在数据库B中，&lt;/p>
&lt;p>参数为3和7的数据存在数据库C中，&lt;/p>
&lt;p>参数为2,6,9和10的数据存在数据库D中。&lt;/p>
&lt;p>此时，其实我们已经不惧怕数据库宕机的情况了，&lt;/p>
&lt;p>假设我们的数据库B又一次失去连接。&lt;/p>
&lt;p>会发生什么情况呢？&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077641/2021-06-05-%E4%B8%80%E8%87%B4%E6%80%A7Hash%E5%9B%BE2_bckrcm.webp"
loading="lazy"
alt="一执行Hash图2"
>&lt;/p>
&lt;p>影响十分有限，只有数据库A和B在 Hash 环上的位置之间的数据，才受到了影响。&lt;/p>
&lt;p>如图，参数为5的数据，需要重新从主库中复制到数据库C中，以保证系统需要参数为5的数据时可以顺利读取到。&lt;/p>
&lt;p>而对于其他参数值的数据，并没有受到影响。&lt;/p>
&lt;p>以上描述的是节点减少的情况，实际上在节点增加的时候，&lt;/p>
&lt;p>一致性Hash算法依然可以保持大部分节点的稳定，不需要改变。&lt;/p>
&lt;p>在这里我不做赘述，但你参考上面，独立思考一下。&lt;/p>
&lt;p>一致性 Hash 算法如果只是到这里，实际上还引入了一个新的问题——数据倾斜。&lt;/p>
&lt;p>即我们假设数据落在 Hash 环上每个位置的概率是一致的，&lt;/p>
&lt;p>但实际上，每个节点覆盖的 Hash 环上的大小并不相等，&lt;/p>
&lt;p>甚至可能有几倍的差距。&lt;/p>
&lt;p>例如，在上面A，C，D库的基础上，我们添加了一个节点——数据库E。&lt;/p>
&lt;p>它的位置如图所示。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077713/2021-06-05-%E4%B8%80%E8%87%B4%E6%80%A7Hash%E5%9B%BE3_cm8ar0.webp"
loading="lazy"
alt="一执行Hash图3"
>&lt;/p>
&lt;p>因为它（数据库E）与上一个节点（数据库D）距离太近，导致没有任何一个数据落到它上面，&lt;/p>
&lt;p>而正是与他相近特别近的上一个节点（数据库D），却存储了4个数据，&lt;/p>
&lt;p>这就是数据倾斜，有些节点承载了很重的任务，有些节点却悠闲悠闲。&lt;/p>
&lt;h2 id="虚拟节点">虚拟节点
&lt;/h2>&lt;p>为了解决数据倾斜的问题，我们还需要加入虚拟节点这一策略。&lt;/p>
&lt;p>即，将每个数据库都通过定位算法生成几个在 Hash 环上的位置，&lt;/p>
&lt;p>每个位置都承担上面节点的功能，&lt;/p>
&lt;p>区别在于原来每个数据库对应一个节点，&lt;/p>
&lt;p>现在每个数据库会对应若干个节点，&lt;/p>
&lt;p>这就是虚拟节点。&lt;/p>
&lt;p>&lt;img src="https://res.cloudinary.com/dbsadrsxp/image/upload/v1736077771/2021-06-05-%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9_pmdari.webp"
loading="lazy"
alt="虚拟节点"
>&lt;/p>
&lt;p>为了避免图过于混乱，这边我标出 E 数据库的 3 个虚拟节点，&lt;/p>
&lt;p>可以从图中看出，现在&lt;/p>
&lt;p>E#1有 0 个数据，&lt;/p>
&lt;p>E#2有 1 个数据（参数为5的数据），&lt;/p>
&lt;p>E#3有 2 个数据（参数为6和9的数据）。&lt;/p>
&lt;p>而实际上，不管数据定位后归属与E#1、E#2还是E#3，&lt;/p>
&lt;p>在实际的数据存储和读取时，都是在数据库 E。&lt;/p>
&lt;p>不难发现，在添加了虚拟节点的策略之后，&lt;/p>
&lt;p>数据倾斜的情况得到了改善。&lt;/p>
&lt;p>这就是完整的一致性Hash算法与虚拟节点啦！&lt;/p>
&lt;p>记住，在实际应用中，3个虚拟节点是不够的，你需要更多的虚拟节点，以保证节点的负载更加均衡。&lt;/p></description></item><item><title>Archives</title><link>https://ethanschen.github.io/archives/</link><pubDate>Tue, 16 Jun 1998 00:00:00 +0000</pubDate><guid>https://ethanschen.github.io/archives/</guid><description/></item><item><title>Links</title><link>https://ethanschen.github.io/links/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://ethanschen.github.io/links/</guid><description>&lt;p>😄 本页是 Ethan S. Chen 的朋友们～&lt;/p>
&lt;p>如果你也想在下面留有你的网站，可以按以下格式留言：&lt;/p>
&lt;ul>
&lt;li>名称：Ethan S. Chen’s Blog&lt;/li>
&lt;li>描述：沉舟侧畔千帆过，病树前头万木春&lt;/li>
&lt;li>邮箱：&lt;em>&lt;strong>@&lt;/strong>&lt;/em>.***&lt;/li>
&lt;li>地址：https://www.ethans.space&lt;/li>
&lt;li>头像：https://res.cloudinary.com/dbsadrsxp/image/upload/t_Profile/v1736068974/2025-01-05-%E5%8F%B0%E6%B9%BE%E6%8D%B7%E8%BF%90%E4%B8%AA%E4%BA%BA%E7%85%A7_qtsgsm.jpg&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>请注意，如果你的网站存在以下情况，站长可能无法将你的网站添加到友链中&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>使用免费域名或未加密域名的&lt;/li>
&lt;li>网站中含有大量非原创内容的&lt;/li>
&lt;li>包含违反国家法律法规内容的&lt;/li>
&lt;/ol>
&lt;/blockquote></description></item><item><title>Resume</title><link>https://ethanschen.github.io/resume/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://ethanschen.github.io/resume/</guid><description>&lt;h2 id="联系方式">联系方式
&lt;/h2>&lt;ul>
&lt;li>手机：&lt;em>&lt;strong>-&lt;/strong>&lt;/em>&lt;em>-&lt;/em>***&lt;/li>
&lt;li>邮箱：&lt;a class="link" href="mailto:***@***.***" >&lt;em>&lt;strong>@&lt;/strong>&lt;/em>.***&lt;/a>&lt;/li>
&lt;li>微信：&lt;a class="link" href="https://www.ethans.space/personal/wechat/" target="_blank" rel="noopener"
>***&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="个人信息">个人信息
&lt;/h2>&lt;ul>
&lt;li>男/1998&lt;/li>
&lt;li>本科/福建农林大学软件工程系&lt;/li>
&lt;li>工作年限: 5年&lt;/li>
&lt;li>技术博客: &lt;a class="link" href="https://www.ethans.space" target="_blank" rel="noopener"
>https://www.ethans.space&lt;/a>&lt;/li>
&lt;li>期望城市: 杭州、上海&lt;/li>
&lt;/ul>
&lt;h2 id="工作经历">工作经历
&lt;/h2>&lt;h3 id="华为云计算技术有限公司-2021年8月--至今-devopsod岗">华为云计算技术有限公司（ 2021年8月 ~ 至今 ）（DevOps，OD岗）
&lt;/h3>&lt;h4 id="k8s部署自动化及机型适配">K8S部署自动化及机型适配
&lt;/h4>&lt;ul>
&lt;li>背景：自动化开局工具支持K8S自动化部署，同时完成K8S适配新机型。&lt;/li>
&lt;li>贡献：
&lt;ul>
&lt;li>任务拆解，进度跟踪，每周组织例会识别风险、编写项目周报&lt;/li>
&lt;li>组织特性代码检视，组织架构师方案讨论，上TMG会议评审技术方案&lt;/li>
&lt;li>协调资源解决项目阻塞点，主动推进项目，对排期负责&lt;/li>
&lt;li>组织项目联调，组织项目ShowCase&lt;/li>
&lt;li>培养新人完成开发任务，培养新人承接组织项目联调职能&lt;/li>
&lt;li>&lt;strong>版本优秀交付奖&lt;/strong>、&lt;strong>域内红事件&lt;/strong>&lt;/li>
&lt;li>&lt;strong>完成现网局点交付，达成交付SLA&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="openstack组件云原生化">OpenStack组件云原生化
&lt;/h4>&lt;ul>
&lt;li>背景：保留自研运维系统的OpenStack组件云原生化改造&lt;/li>
&lt;li>技术栈：Kubernetes、Prometheus、Grafana、OpenStack、Docker、Python&lt;/li>
&lt;li>贡献：
&lt;ul>
&lt;li>组织方案讨论，参加技术评审，输出详细设计&lt;/li>
&lt;li>特性开发，UT开发，构建CI自动化工程&lt;/li>
&lt;li>输出最佳实践，组织跨部门ShowCase&lt;/li>
&lt;li>支持各部门OpenStack组件云原生化，目前已上车&lt;strong>30+组件&lt;/strong>&lt;/li>
&lt;li>持续维护与加固该方案，已服务2年+&lt;/li>
&lt;li>参与现网改造及上线，目前已有&lt;strong>8个局点&lt;/strong>完成改造&lt;/li>
&lt;li>总计&lt;strong>已上线4000+容器实例&lt;/strong>，占总实例30%&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="rabbitmq技术支撑及漏洞修复">RabbitMQ技术支撑及漏洞修复
&lt;/h4>&lt;ul>
&lt;li>背景：作为RabbitMQ的Owner，负责现网重大变更保障、紧急问题恢复及疑难问题攻关。&lt;/li>
&lt;li>技术栈：RabbitMQ、Linux、Oslo-Messaging、pdb调试、Ansible、Jekins&lt;/li>
&lt;li>贡献：
&lt;ul>
&lt;li>保障22年版本升级平稳完成，保障3场公有云局点容灾演练平稳完成&lt;/li>
&lt;li>处理数十起生产环境紧急问题，快速恢复环境，阻止影响进一步恶化&lt;/li>
&lt;li>处理数百个测试环境问题，支撑周边服务的同时保证产品漏洞的及时发现与修复&lt;/li>
&lt;li>负责RabbitMQ的迭代预研，完成RabbitMQ切Pulsar的POC项目&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="其他项目">其他项目
&lt;/h4>&lt;ul>
&lt;li>Kafka、Redis、Nginx及自研运维系统测试及现网问题支撑&lt;/li>
&lt;li>组织测试及现网问题Oncall排班，组织issue复盘与培训&lt;/li>
&lt;li>负责团队CI事务跟踪，负责团队安全事务处理&lt;/li>
&lt;li>K8S的Kuberhealthy拨测能力构建&lt;/li>
&lt;li>……&lt;/li>
&lt;/ul>
&lt;h3 id="福建福链科技有限公司-2020年5月--2021年7月-技术研发工程师">福建福链科技有限公司（ 2020年5月 ~ 2021年7月 ）（技术研发工程师）
&lt;/h3>&lt;h4 id="区块链存证平台">区块链存证平台
&lt;/h4>&lt;ul>
&lt;li>背景：基于自研区块链实现的数据存证平台&lt;/li>
&lt;li>技术栈：Java、SpringBoot、Git、MySQL、Oracle、SQL Server、Tomcat、Samba、Nginx、ElasticSearch&lt;/li>
&lt;li>贡献：
&lt;ul>
&lt;li>预研、设计、开发与交付的产品全流程&lt;/li>
&lt;li>作为一款To B商用系统，已顺利交付三套&lt;/li>
&lt;li>预研实现数据生产者模块，通过全量同步与增量订阅结合的方式将MySQL、Oracle、SQL Server、文件目录中的数据发送至RocketMQ&lt;/li>
&lt;li>预研实现数据消费者模块，完成数据在区块链与ElasticSearch的并行存证&lt;/li>
&lt;li>负责部署应用至Tomcat服务器，配置Nginx代理、反向代理，部署Samba文件服务&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="开源项目">开源项目
&lt;/h2>&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/prometheus/client_python" target="_blank" rel="noopener"
>prometheus/client_python&lt;/a>&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>23年7月，公司出于安全因素需要exporter支持HTTPS/TLS，在确认社区未支持该特性后，首先通过patch的方式实现该功能，保障项目按时交付;再利用业余时间提交&lt;a class="link" href="https://github.com/prometheus/client_python/pull/946" target="_blank" rel="noopener"
>PR&lt;/a>到社区，经过与社区核心成员的73次讨论、更新9次代码、耗时4个月将该特性合并到主干;因为该重要特性的合入，社区专门发布了&lt;a class="link" href="https://pypi.org/project/prometheus-client/0.19.0" target="_blank" rel="noopener"
>0.19.0&lt;/a>版本;也因为该特性的合入，下游项目IBM ZHMC Exporter维护者特意发感谢邮件致谢。&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>&lt;a class="link" href="http://www.insect.org.cn/CN/10.16380/j.kcxb.2021.05.008" target="_blank" rel="noopener"
>基于F3Net显著性目标检测的蝴蝶图像前背景自动分割&lt;/a>&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>基于F3Net算法模型进行迁移学习，使模型特适应于鳞翅目昆虫数据集，最终成果应用在七项常用显著性目标检测指标中均达最佳。&lt;/p>
&lt;/blockquote>
&lt;h2 id="技术栈">技术栈
&lt;/h2>&lt;ul>
&lt;li>开发语言: Python/Go/Java/Bash&lt;/li>
&lt;li>云计算: Kubernetes/Docker/Prometheus/Grafana/OpenStack&lt;/li>
&lt;li>中间件: RabbitMQ/Pulsar/Redis/Kafka/RocketMQ/Nginx/Elasticsearch/Tomcat&lt;/li>
&lt;li>数据库: MySQL/PostgreSQL/SQLite&lt;/li>
&lt;li>后端开发: Spring Boot/Django&lt;/li>
&lt;li>前端开发: Vue/Next.js/Bootstrap/Android应用/IOS应用/微信小程序/微信公众号&lt;/li>
&lt;li>其他: Git/Ansible/Jenkins/Jupyter/Matlab/Unity3D&lt;/li>
&lt;/ul>
&lt;h2 id="致谢">致谢
&lt;/h2>&lt;p>感谢您花时间阅读我的简历，期待能有机会和您共事~&lt;/p></description></item><item><title>Search</title><link>https://ethanschen.github.io/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://ethanschen.github.io/search/</guid><description/></item></channel></rss>