<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>唠唠技术 on Ethan S. Chen</title><link>https://ethanschen.github.io/categories/%E5%94%A0%E5%94%A0%E6%8A%80%E6%9C%AF/</link><description>Recent content in 唠唠技术 on Ethan S. Chen</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Ethan S. Chen</copyright><lastBuildDate>Sat, 04 Jan 2025 12:00:00 +0800</lastBuildDate><atom:link href="https://ethanschen.github.io/categories/%E5%94%A0%E5%94%A0%E6%8A%80%E6%9C%AF/index.xml" rel="self" type="application/rss+xml"/><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>我在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></channel></rss>