四处溜达 發表於 2025-7-11 10:39:00

从moved、ask到智能客户端:一次彻底搞懂RedisCluster的数据路由

<p>本文已收录在Github,<strong>关注我,紧跟本系列专栏文章,咱们下篇再续!</strong></p>
<ul>
<li>🚀 魔都架构师 | 全网30W技术追随者</li>
<li>🔧 大厂分布式系统/数据中台实战专家</li>
<li>🏆 主导交易系统百万级流量调优 &amp; 车联网平台架构</li>
<li>🧠 AIGC应用开发先行者 | 区块链落地实践者</li>
<li>🌍 以技术驱动创新,我们的征途是改变世界!</li>
<li>👉 实战干货:编程严选网</li>
</ul>
<h2 id="1-moved重定向">1 moved重定向</h2>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/other/1097393/202507/1097393-20250711103924889-1674662742.png" class="lazyload"></p>
<p>每个节点通信共享Redis Cluster中槽和集群中对应节点的关系。</p>
<ol>
<li>客户端向Redis Cluster的任一节点发送命令</li>
<li>接收命令的节点再计算自己的槽和对应节点</li>
</ol>
<p>如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端:</p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/other/1097393/202507/1097393-20250711103925459-523975386.png" class="lazyload"></p>
<pre><code class="language-bash">127.0.0.1:6379&gt; cluster keyslot hello
(integer) 866   # 直接返回
</code></pre>
<p>键槽计算公式:</p>
<pre><code class="language-bash">slot = CRC16(key) mod 16384
</code></pre>
<p>如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常</p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/other/1097393/202507/1097393-20250711103925971-1470033337.png" class="lazyload"></p>
<ol start="3">
<li>客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息</li>
<li>客户端向目标节点发送命令,获取命令执行结果</li>
</ol>
<h3 id="重定向流程">重定向流程</h3>
<ol>
<li>客户端向节点6379发送 'set php best' 命令</li>
<li>节点6379计算键'php'对应槽位9244</li>
<li>槽位9244不在节点6379管理范围内(0~5460)</li>
<li>返回MOVED重定向: 'moved 9244 127.0.0.1:6380'</li>
<li>客户端根据重定向信息连接节点6380</li>
<li>节点6380成功执行命令并返回OK</li>
</ol>
<p>键槽信息:</p>
<pre><code class="language-java">CRC16('php') mod 16384 = 9244
</code></pre>
<p><mark>客户端不会自动找到目标节点执行命令,需要二次执行</mark></p>
<h2 id="2-ask重定向">2 ask重定向</h2>
<p>集群伸缩时,需数据迁移。</p>
<p>当客户端访问某key,节点告诉客户端key在源节点,再去源节点访问时,却发现key已迁移到目标节点,就会返回ask。</p>
<p>![](/Users/javaedge/Library/Application Support/typora-user-images/image-20250710161135067.png)</p>
<ol>
<li>客户端向目标节点发送命令,目标节点中的槽已经迁移到其它节点</li>
<li>目标节点会返回ask转向给客户端</li>
<li>客户端向新节点发送Asking命令</li>
<li>再向新节点发送命令</li>
<li>新节点执行命令,把命令执行结果返回给客户端</li>
</ol>
<h3 id="为啥不用moved重定向">为啥不用MOVED重定向?</h3>
<p>虽然MOVED意味着我们认为哈希槽由另一节点永久提供,且应对指定节点尝试下一个查询,所以ASK意味着仅将下一个查询发送到指定节点。</p>
<p>需要这样做,是因为下一个关于哈希槽的查询可能是关于仍在A中的键,因此我们始终希望客户端尝试A,然后在需要时尝试B。由于只有16384个可用的哈希槽中有一个发生,因此集群层面的性能下降可接受。</p>
<h2 id="3-moved-vs-ask">3 moved V.S ask</h2>
<p>虽然都是客户端重定向,但是:</p>
<ul>
<li>moved:槽已确定转移</li>
<li>ask:槽还在迁移中</li>
</ul>
<h2 id="4-智能客户端">4 智能客户端</h2>
<p>为追求性能。</p>
<h3 id="41-设计思路">4.1 设计思路</h3>
<ul>
<li>从集群中选一个可运行节点,用<code>Cluster slots</code>初始化槽和节点映射</li>
<li>将Cluster slots结果映射在本地,为每个节点创建JedisPool,之后即可进行数据读写操作</li>
</ul>
<h3 id="42-注意">4.2 注意</h3>
<ul>
<li>每个JedisPool缓存了slot和节点node的关系</li>
<li>key和slot:对key进行CRC16,hash后与16383取余得到的结果就是slot槽</li>
<li>JedisCluster启动时,已知key、slot和node之间关系,可找到目标节点</li>
<li>JedisCluster对目标节点发送命令,目标节点直接响应给JedisCluster</li>
<li>如果JedisCluster与目标节点连接出错,则JedisCluster会知道连接的节点是一个错误的节点</li>
<li>此时JedisCluster会随机节点发送命令,随机节点返回moved异常给JedisCluster</li>
<li>JedisCluster会重新初始化slot与node节点的缓存关系,再向新的目标节点发命令,目标命令执行命令并向JedisCluster响应</li>
<li>若命令发送次数超过5次,抛"Too many cluster redirection!"</li>
</ul>
<h3 id="架构设计">架构设计</h3>
<p>基图:</p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/other/1097393/202507/1097393-20250711103926460-1627464667.png" class="lazyload"></p>
<p>全图:</p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/other/1097393/202507/1097393-20250711103927011-903684459.png" class="lazyload"></p>
<blockquote>
<p>本文由博客一文多发平台 OpenWrite 发布!</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/JavaEdge/p/18978395
頁: [1]
查看完整版本: 从moved、ask到智能客户端:一次彻底搞懂RedisCluster的数据路由