blob: 7400d48e9ad4e22ee82eeb477c55537fa1fa6530 [file] [log] [blame]
<!doctype html>
<html lang="zh" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="generator" content="Docusaurus v2.0.0-beta.4">
<link rel="alternate" type="application/rss+xml" href="/zh/blog/rss.xml" title="Apache ShenYu Blog RSS Feed">
<link rel="alternate" type="application/atom+xml" href="/zh/blog/atom.xml" title="Apache ShenYu Blog Atom Feed">
<link rel="search" type="application/opensearchdescription+xml" title="Apache ShenYu" href="/zh/opensearch.xml">
<link rel="alternate" type="application/rss+xml" href="/zh/news/rss.xml" title="Apache ShenYu Blog RSS Feed">
<link rel="alternate" type="application/atom+xml" href="/zh/news/atom.xml" title="Apache ShenYu Blog Atom Feed"><title data-react-helmet="true">One post tagged with &quot;load balance&quot; | Apache ShenYu</title><meta data-react-helmet="true" property="og:title" content="One post tagged with &quot;load balance&quot; | Apache ShenYu"><meta data-react-helmet="true" property="og:url" content="https://shenyu.apache.org//zh/blog/tags/load-balance"><meta data-react-helmet="true" name="docsearch:language" content="zh"><meta data-react-helmet="true" name="docsearch:docusaurus_tag" content="blog_tags_posts"><link data-react-helmet="true" rel="shortcut icon" href="/zh/img/favicon.svg"><link data-react-helmet="true" rel="canonical" href="https://shenyu.apache.org//zh/blog/tags/load-balance"><link data-react-helmet="true" rel="alternate" href="https://shenyu.apache.org//blog/tags/load-balance" hreflang="en"><link data-react-helmet="true" rel="alternate" href="https://shenyu.apache.org//zh/blog/tags/load-balance" hreflang="zh"><link data-react-helmet="true" rel="alternate" href="https://shenyu.apache.org//blog/tags/load-balance" hreflang="x-default"><link data-react-helmet="true" rel="preconnect" href="https://BH4D9OD16A-dsn.algolia.net" crossorigin="anonymous"><link rel="stylesheet" href="/zh/assets/css/styles.b6f9a199.css">
<link rel="preload" href="/zh/assets/js/runtime~main.ba3d74b1.js" as="script">
<link rel="preload" href="/zh/assets/js/main.404c2b61.js" as="script">
</head>
<body>
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}()</script><div id="__docusaurus">
<div><a href="#" class="skipToContent_OuoZ">Skip to main content</a></div><nav class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Navigation bar toggle" class="navbar__toggle clean-btn" type="button" tabindex="0"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/zh/"><img src="/zh/img/logo.svg" alt="Apache ShenYu Logo" class="themedImage_TMUO themedImage--light_4Vu1 navbar__logo"><img src="/zh/img/logo-light.svg" alt="Apache ShenYu Logo" class="themedImage_TMUO themedImage--dark_uzRr navbar__logo"></a></div><div class="navbar__items navbar__items--right"><a class="navbar__item navbar__link" href="/zh/download">下载</a><a class="navbar__item navbar__link" href="/zh/document">文档</a><a class="navbar__item navbar__link" href="/zh/community/contributor-guide">社区</a><a class="navbar__item navbar__link" href="/zh/team">团队</a><a class="navbar__item navbar__link" href="/zh/event">事件</a><a class="navbar__item navbar__link" href="/zh/news">新闻</a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/zh/blog">博客</a><a class="navbar__item navbar__link" href="/zh/users">用户</a><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a class="navbar__item navbar__link">ASF</a><ul class="dropdown__menu"><li><a href="https://www.apache.org/" target="_blank" rel="noopener noreferrer" class="dropdown__link">Foundation</a></li><li><a href="https://www.apache.org/licenses/" target="_blank" rel="noopener noreferrer" class="dropdown__link">License</a></li><li><a href="https://www.apache.org/events/current-event" target="_blank" rel="noopener noreferrer" class="dropdown__link">Events</a></li><li><a href="https://www.apache.org/security/" target="_blank" rel="noopener noreferrer" class="dropdown__link">Security</a></li><li><a href="https://www.apache.org/foundation/sponsorship.html" target="_blank" rel="noopener noreferrer" class="dropdown__link">Sponsorship</a></li><li><a href="https://www.apache.org/foundation/policies/privacy.html" target="_blank" rel="noopener noreferrer" class="dropdown__link">Privacy</a></li><li><a href="https://www.apache.org/foundation/thanks.html" target="_blank" rel="noopener noreferrer" class="dropdown__link">Thanks</a></li></ul></div><a href="https://github.com/apache/shenyu" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link"><span>GitHub<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a href="#" class="navbar__item navbar__link"><span><svg t="1631348384596" class="icon" viewBox="0 0 1024 1024" version="1.1" style="vertical-align:text-bottom;margin-right:5px" p-id="557" width="20" height="20"><path d="M547.797333 638.208l-104.405333-103.168 1.237333-1.28a720.170667 720.170667 0 0 0 152.490667-268.373333h120.448V183.082667h-287.744V100.906667H347.605333v82.218666H59.818667V265.386667h459.178666a648.234667 648.234667 0 0 1-130.304 219.946666 643.242667 643.242667 0 0 1-94.976-137.728H211.541333a722.048 722.048 0 0 0 122.453334 187.434667l-209.194667 206.378667 58.368 58.368 205.525333-205.525334 127.872 127.829334 31.232-83.84m231.424-208.426667h-82.218666l-184.96 493.312h82.218666l46.037334-123.306667h195.242666l46.464 123.306667h82.218667l-185.002667-493.312m-107.690666 287.744l66.56-178.005333 66.602666 178.005333z" fill="currentColor" p-id="558"></path></svg><span>简体中文</span></span></a><ul class="dropdown__menu"><li><a href="/blog/tags/load-balance" target="_self" rel="noopener noreferrer" class="dropdown__link" style="text-transform:capitalize">English</a></li><li><a href="/zh/blog/tags/load-balance" target="_self" rel="noopener noreferrer" class="dropdown__link dropdown__link--active" style="text-transform:capitalize">简体中文</a></li></ul></div><div class="react-toggle toggle_2i4l react-toggle--disabled"><div class="react-toggle-track" role="button" tabindex="-1"><div class="react-toggle-track-check"><span class="toggle_iYfV">🌜</span></div><div class="react-toggle-track-x"><span class="toggle_iYfV">🌞</span></div><div class="react-toggle-thumb"></div></div><input type="checkbox" class="react-toggle-screenreader-only" aria-label="Switch between dark and light mode"></div><div class="searchBox_Bc3W"><button type="button" class="DocSearch DocSearch-Button" aria-label="搜索"><span class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">搜索</span></span><span class="DocSearch-Button-Keys"></span></button></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div class="main-wrapper blog-wrapper blog-tags-post-page"><div class="container margin-vert--lg"><div class="row"><main class="col col--9 col--offset-1"><header class="margin-bottom--xl"><h1>One post tagged with &quot;load balance&quot;</h1><a href="/zh/blog/tags">View All Tags</a></header><article class="margin-bottom--xl"><header><h2 class="blogPostTitle_d4p0"><a href="/zh/blog/SPI-SourceCode-Analysis-LoadBalance-SPI">LoadBalancer SPI 代码分析</a></h2><div class="blogPostData_-Im+ margin-vert--md"><time datetime="2024-04-20T01:09:53.309Z">2024年4月20日</time> · One min read</div><div class="avatar margin-vert--md"><div class="avatar__intro"><div class="avatar__name"><a href="https://github.com/changanjennifer/" target="_blank" rel="noopener noreferrer">Huihui Yin</a></div><small class="avatar__subtitle">Apache ShenYu Contributor</small></div></div></header><div class="markdown"><p>​ 网关应用需要支持多种负载均衡的方案,包括随机选择、Hash、轮询等方式。<code>Apache Shenyu</code>网关中不仅实现了传统网关的这些均衡策略,还通过流量预热(warmup)等细节处理,对服务器节点的加入,做了更平滑的流量处理,获得了更好的整体稳定性。让我们来看看Shenyu是是如何设计和实现这部分功能的。</p><blockquote><p>本文基于<code>shenyu-2.5.0</code>版本进行源码分析.</p></blockquote><p>[TOC]</p><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="loadbalancer-spi"></a>LoadBalancer <code>SPI</code><a class="hash-link" href="#loadbalancer-spi" title="Direct link to heading">#</a></h2><p><code>LoadBalancer</code> SPI 定义在<strong><em>shenyu-loadbalancer</em></strong>模组中,以下是这个核心接口的代码,这个接口很好的诠释了这样一个理念:负载均衡是在一系列服务器节点中选出最合适的节点,也就是选择策略。做流量转发、路由和负载均衡是<code>LoadBalance SPI</code>的基本功能</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@SPI</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public interface LoadBalancer {</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * this is select one for upstream list.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> *</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param upstreamList upstream list</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param ip ip</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @return upstream</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Upstream select(List&lt;Upstream&gt; upstreamList, String ip);</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>接口中,upstreamList是可选路由的一组服务器节点,<code>Upstream</code> 是服务器节点的数据结构,它包括的重要元素有:协议、url 、权重、时间戳,warmup,健康状态等。</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">public class Upstream {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * protocol.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final String protocol;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * url.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private String url;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * weight.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final int weight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * false close, true open.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private boolean status;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * startup time.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final long timestamp;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * warmup.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final int warmup;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * healthy.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private boolean healthy;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * lastHealthTimestamp.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private long lastHealthTimestamp;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * lastUnhealthyTimestamp.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private long lastUnhealthyTimestamp;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * group.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private String group;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * version.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private String version;</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="design-of-loadbalance-module"></a>Design of LoadBalance module`<a class="hash-link" href="#design-of-loadbalance-module" title="Direct link to heading">#</a></h2><p>图1是<code>LoadBalancer</code>模组的类图:</p><p><img alt="loadbalancer-class-diagram" src="/zh/assets/images/loadBalancer-class-diagram-7fa8d2dd07f1210da7d25fa787b69d5f.png"></p><p>从类图上可以看出<code>LoadBalance</code>的设计概要:</p><ol><li><p>抽象类<code>AbstractLoadBalancer</code>继承自<code>LoadBalancer</code> SPI接口,并提供选择的模板方法,及权重计算。</p></li><li><p>三个实做类继承<code>AbstractLoadBalancer</code>, 实现各自的逻辑处理。</p><ul><li><code>RandomLoadBalancer</code> -加权随机选择 Weight Random</li><li><code>HashLoadBalancer</code> - 一致性Hash</li><li><code>RoundRobinLoadBalancer</code> -加权轮询(Weight Round Robin per-packet)</li></ul></li><li><p>由工厂类<code>LoadBalancerFactory</code> 实现对外的静态调用方法。</p><p>另外根据<code>Apache Sheny SPI</code>规范,在<code>SHENYU_DIERECTORY</code>中的添加profile,配置<code>LoadBalance</code>的实现类,配置key=class形式,左边的operator要和<code>LoadBalanceEnum</code>中的定义一致。</p></li></ol><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI properties"><pre tabindex="0" class="prism-code language-properties codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">random=org.apache.shenyu.loadbalancer.spi.RandomLoadBalancer</span></span><span class="token-line" style="color:#393A34"><span class="token plain">roundRobin=org.apache.shenyu.loadbalancer.spi.RoundRobinLoadBalancer</span></span><span class="token-line" style="color:#393A34"><span class="token plain">hash=org.apache.shenyu.loadbalancer.spi.HashLoadBalancer</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p><code>LoadBalanceEnum</code>的定义如下:</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">public enum LoadBalanceEnum {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Hash load balance enum.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> HASH(1, &quot;hash&quot;, true),</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Random load balance enum.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> RANDOM(2, &quot;random&quot;, true),</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Round robin load balance enum.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> ROUND_ROBIN(3, &quot;roundRobin&quot;, true);</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final int code;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final String name;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final boolean support;</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="abstractloadbalancer"></a>AbstractLoadBalancer<a class="hash-link" href="#abstractloadbalancer" title="Direct link to heading">#</a></h2><p>这个抽象类实做了<code>LoadBalancer</code>接口, 定义了抽象方法<code>doSelect()</code>留给实作类处理,在模板方法<code>select()</code> 中先进行校验,之后调用由实作类实现的<code>doSelect()</code>方法。</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">public abstract class AbstractLoadBalancer implements LoadBalancer {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> /**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Do select divide upstream.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> *</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param upstreamList the upstream list</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param ip the ip</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @return the divide upstream</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> protected abstract Upstream doSelect(List&lt;Upstream&gt; upstreamList, String ip);</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public Upstream select(final List&lt;Upstream&gt; upstreamList, final String ip) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (CollectionUtils.isEmpty(upstreamList)) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return null;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (upstreamList.size() == 1) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return upstreamList.get(0);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return doSelect(upstreamList, ip);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>权重的处理方法<code>getWeight()</code>的逻辑是:当有时间戳,并且当前时间与时间戳间隔在流量预热warmup时间内,权重计算的公式为:
$$ {1-1}
ww = min(1,uptime/(warmup/weight))
$$
从公式可以看出,最终的权值,与设置的weight成正比,时间间隔越接近warmup时间,权重就越大。也就是说等待的时间越长,被分派的权重越高。没有时间戳时等其他情况下,返回<code>Upstream</code>设置的<code>weight</code>值。</p><p>考虑流量预热(warmup)的核心思想是避免在添加新服务器和启动新JVM时网关性能不佳。</p><p>下面我们看一下三个实做类的实现。</p><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="randomloadbalancer"></a>RandomLoadBalancer<a class="hash-link" href="#randomloadbalancer" title="Direct link to heading">#</a></h2><p>这里随机<code>LoadBalancer</code> 可以处理两种情况:</p><ol><li>没有权重:所有服务器都没有设定权重,或者权重都一样, 会随机选择一个。</li><li>有权重:服务器设定有不同的权重,会根据权重,进行随机选择。</li></ol><p>下面是有权重时的随机选择代码<code>random()</code>: 遍历全部服务器列表,当随机值小于某个服务器权重时,这个服务器被选中(这里提前计算了前一半服务器的权重和,如果随机值大于<code>halfLengthTotalWeight</code>,则遍历从<code>(weights.length + 1) / 2</code>开始,提高了小效率)。 若遍历后没有满足条件,就在全部服务器列表中随机选择一个返回。这里<code>getWeight(final Upstream upstream)</code> 方法是在<code>AbstractLoadBalancer</code> 中定义的,按公式计算权重。</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public Upstream doSelect(final List&lt;Upstream&gt; upstreamList, final String ip) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int length = upstreamList.size();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // every upstream has the same weight?</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> boolean sameWeight = true;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // the weight of every upstream</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int[] weights = new int[length];</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int firstUpstreamWeight = getWeight(upstreamList.get(0));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> weights[0] = firstUpstreamWeight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // init the totalWeight</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int totalWeight = firstUpstreamWeight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int halfLengthTotalWeight = 0;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> for (int i = 1; i &lt; length; i++) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int currentUpstreamWeight = getWeight(upstreamList.get(i));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (i &lt;= (length + 1) / 2) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> halfLengthTotalWeight = totalWeight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> weights[i] = currentUpstreamWeight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> totalWeight += currentUpstreamWeight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (sameWeight &amp;&amp; currentUpstreamWeight != firstUpstreamWeight) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // Calculate whether the weight of ownership is the same.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> sameWeight = false;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (totalWeight &gt; 0 &amp;&amp; !sameWeight) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return random(totalWeight, halfLengthTotalWeight, weights, upstreamList);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return random(upstreamList);</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain">private Upstream random(final int totalWeight, final int halfLengthTotalWeight, final int[] weights, final List&lt;Upstream&gt; upstreamList) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // If the weights are not the same and the weights are greater than 0, then random by the total number of weights.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int offset = RANDOM.nextInt(totalWeight);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int index = 0;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int end = weights.length;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (offset &gt;= halfLengthTotalWeight) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> index = (weights.length + 1) / 2;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> offset -= halfLengthTotalWeight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } else {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> end = (weights.length + 1) / 2;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // Determine which segment the random value falls on</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> for (; index &lt; end; index++) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> offset -= weights[index];</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (offset &lt; 0) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return upstreamList.get(index);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return random(upstreamList);</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>因此,当采用<code>RandomLoadBalancer</code>时,是按权重随机分派服务器的。</p><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="hashloadbalancer"></a>HashLoadBalancer<a class="hash-link" href="#hashloadbalancer" title="Direct link to heading">#</a></h2><p><code>Apache Shenyu</code><code>HashLoadBalancer</code> 中采用了一致性hash算法,使用有序hash环,将key与服务器节点的hash映射缓存起来。对于请求的ip地址,计算出其<code>hash</code>值, 在hash环上顺时针查找距离这个key的hash值最近的节点,将其作为要路由的节点。一致性hash解决了传统取余hash算法的可伸缩性差的问题。</p><p><code>HashLoadBalancer</code>中的采用的是加密的单向MD5散列函数,这个hash函数会hash后产生不可预期但确定性的()的结果,输出为32-bit的长整数。<code>hash</code>代码如下:</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">private static long hash(final String key) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // md5 byte</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> MessageDigest md5;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> try {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> md5 = MessageDigest.getInstance(&quot;MD5&quot;);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } catch (NoSuchAlgorithmException e) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> throw new ShenyuException(&quot;MD5 not supported&quot;, e);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> md5.reset();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> byte[] keyBytes;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> keyBytes = key.getBytes(StandardCharsets.UTF_8);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> md5.update(keyBytes);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> byte[] digest = md5.digest();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // hash code, Truncate to 32-bits</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> long hashCode = (long) (digest[3] &amp; 0xFF) &lt;&lt; 24</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> | ((long) (digest[2] &amp; 0xFF) &lt;&lt; 16)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> | ((long) (digest[1] &amp; 0xFF) &lt;&lt; 8)</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> | (digest[0] &amp; 0xFF);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return hashCode &amp; 0xffffffffL;</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>再看一下<code>HashLoadBalancer</code>的选择函数<code>doSelect()</code>的实现:</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain"> private static final int VIRTUAL_NODE_NUM = 5;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> @Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> public Upstream doSelect(final List&lt;Upstream&gt; upstreamList, final String ip) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> final ConcurrentSkipListMap&lt;Long, Upstream&gt; treeMap = new ConcurrentSkipListMap&lt;&gt;();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> upstreamList.forEach(upstream -&gt; IntStream.range(0, VIRTUAL_NODE_NUM).forEach(i -&gt; {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> long addressHash = hash(&quot;SHENYU-&quot; + upstream.getUrl() + &quot;-HASH-&quot; + i);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> treeMap.put(addressHash, upstream);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> long hash = hash(ip);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> SortedMap&lt;Long, Upstream&gt; lastRing = treeMap.tailMap(hash);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (!lastRing.isEmpty()) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return lastRing.get(lastRing.firstKey());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return treeMap.firstEntry().getValue();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>这个方法中,生成带虚拟服务器节点的hash环, 一个实际节点会生成5个虚拟节点,因此整个hash环的均匀性大大增加,降低数据倾斜的发生。</p><p>为了实现hash环的有序性及顺时针查找功能,代码中使用Java 的<a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentSkipListMap.html" target="_blank" rel="noopener noreferrer">ConcurrentSkipListMap</a> 来存储带虚拟节点的服务器节点及其hash值, 它既能保证线程安全,又能保证数据的有序性,支持高并发。 另外,<code>ConcurrentSkipListMap</code>提供了一个<code>tailMap(K fromKey)</code>方法,可从<code>map</code>中查找比<code>fromKey</code>大的值的集合,但并不需要遍历整个数据结构。</p><p>上述代码中,生成hash环之后,就是调用<code>ConcurrentSkipListMap</code><code>tailMap()</code>方法,找到大于等于请求的ip的hash值的子集,这个子集的第一个就是要路由的服务器节点。采用了合适的数据结构,这里的代码看上去是不是特别的简洁流畅?</p><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="roundrobinloadbalancer"></a>RoundRobinLoadBalancer<a class="hash-link" href="#roundrobinloadbalancer" title="Direct link to heading">#</a></h2><p>Round-robin轮询方法的原始定义是顺序循环将请求依次循环地连接到每个服务器。当某个服务器发生故障(例如:一分钟连接不上的服务器),从候选队列中取出,不参与下一次的轮询,直到其恢复正常。在 <code>RoundRobinLoadBalancer</code>中实现的是组内加权轮询(<code>Weight Round Robin per-packet</code>)方法:</p><p>为了计算和存储每个服务器节点的轮询次数,在这个类中定义了一个静态内部类<code>WeigthRoundRobin</code>,我们先看一下它的主要代码(去掉了注释):</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">protected static class WeightedRoundRobin {</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private int weight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private final AtomicLong current = new AtomicLong(0);</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> private long lastUpdate;</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> void setWeight(final int weight) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.weight = weight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> current.set(0);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> long increaseCurrent() {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return current.addAndGet(weight);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> void sel(final int total) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> current.addAndGet(-1 * total);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> void setLastUpdate(final long lastUpdate) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> this.lastUpdate = lastUpdate;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>请重点关注这几个方法:</p><ul><li><p><code>setWeight(final int weight)</code> ,为对象设定权重,并将current重置为0.</p></li><li><p><code>increaseCurrent()</code> : 对<code>AtomicLong</code>类型的对象<code>current</code>,累加其权重值。</p></li><li><p><code>sel(final int total)</code>: <code>current</code>减去传入的 <code>total</code>值。</p></li></ul><p>下面我们看一下带权重的轮询过程是如何实现的。
首先定义了一个<code>ConcurrentMap</code>类型对象<code>methodWeightMap</code> 两层对象来存储服务器列表与其各个明细节点的轮询资料。</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">private final ConcurrentMap&lt;String, ConcurrentMap&lt;String, WeightedRoundRobin&gt;&gt; methodWeightMap = new ConcurrentHashMap&lt;&gt;(16);</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>这个map对象第一层的key为当前服务器列表的第一个节点的<code>upstreamUrl</code>, 第二个对象<code>ConcurrentMap&lt;String, WeightedRoundRobin&gt;</code>存储了组内各个服务器节点的轮询情况,内层Map的key为组内每个服务器的<code>upstreamUrl</code><code>Map</code>对象使用<code>JUC</code><code>ConcurrentHashMap</code>,不仅存取高效,而且线程安全,支持高并发。</p><p>内层map的每个节点对应的<code>WeighedRoundRobin</code>作为静态内部类能确保线程安全,并实现组内的加权轮询选择功能。下面是这个类的<code>doSelect()</code>方法的代码。</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public Upstream doSelect(final List&lt;Upstream&gt; upstreamList, final String ip) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> String key = upstreamList.get(0).getUrl();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> ConcurrentMap&lt;String, WeightedRoundRobin&gt; map = methodWeightMap.get(key);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (Objects.isNull(map)) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> methodWeightMap.putIfAbsent(key, new ConcurrentHashMap&lt;&gt;(16));</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> map = methodWeightMap.get(key);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int totalWeight = 0;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> long maxCurrent = Long.MIN_VALUE;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> long now = System.currentTimeMillis();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Upstream selectedInvoker = null;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> WeightedRoundRobin selectedWeightedRoundRobin = null;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> for (Upstream upstream : upstreamList) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> String rKey = upstream.getUrl();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> WeightedRoundRobin weightedRoundRobin = map.get(rKey);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> int weight = getWeight(upstream);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (Objects.isNull(weightedRoundRobin)) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> weightedRoundRobin = new WeightedRoundRobin();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> weightedRoundRobin.setWeight(weight);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> map.putIfAbsent(rKey, weightedRoundRobin);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (weight != weightedRoundRobin.getWeight()) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // weight changed.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> weightedRoundRobin.setWeight(weight);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> long cur = weightedRoundRobin.increaseCurrent();</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> weightedRoundRobin.setLastUpdate(now);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (cur &gt; maxCurrent) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> maxCurrent = cur;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectedInvoker = upstream;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectedWeightedRoundRobin = weightedRoundRobin;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> totalWeight += weight;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> ...... //erase the section which handles the time-out upstreams. </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (selectedInvoker != null) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectedWeightedRoundRobin.sel(totalWeight);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return selectedInvoker;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // should not happen here</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return upstreamList.get(0);</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>举例,若服务器组<code>upstreamUrl</code> 分别为: LIST = [upstream-20, upstream-50, upstream-30]时,经过一轮执行后,建立的<code>methodWeightMap</code> 资料如下:</p><p><img alt="methodWeightMap" src="/zh/assets/images/methodWeightMap-90b4a77aedffd8cd88bc12b9551739ad.png"></p><p>假设上述的LIST中,各个服务器节点的权重数组为: [20,50,30], 下图是内部类current 值变化和轮询选择过程:</p><p><img alt="weighted-roundrobin-demo" src="/zh/assets/images/weighted-roundrobin-demo-cec02fd422fb01ef73e882e0966a8cec.png"></p><p>每一轮,选择值current最大的服务器节点:</p><ul><li>Round1:<ul><li>对当前服务器LIST做遍历,当服务器节点的weightedRoundRobin 为null时,current被置为各自的权重; 不为null时,累加各自的权重。</li><li>即:遍历后current 分别为 [20, 50,30] , 会选择Stream-50, Stream-50对应的WeightRoundRobin静态类做 sel(-total)处理,current 更新为[20,-50, 30].</li></ul></li><li>Round 2 遍历后的current是[40,0,60], 会选择Stream-30, current分别更新为[40,0,-40].</li><li>Round 3 遍历后的current是[60,50,-10], 会选择Stream-20,current分别更新为[-40,50,-10].</li></ul><p>中间进行了容错处理, 当服务器的个数与map个数不一样,就对methodWeightMap 加锁做处理。 用先copy 后modify的方式, 把超时的服务器remove掉,即移除掉发生故障的服务器,并更新Map资料。如下是异常时的处理代码:</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI Java"><pre tabindex="0" class="prism-code language-Java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain"> if (!updateLock.get() &amp;&amp; upstreamList.size() != map.size() &amp;&amp; updateLock.compareAndSet(false, true)) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> try {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // copy -&gt; modify -&gt; update reference.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> ConcurrentMap&lt;String, WeightedRoundRobin&gt; newMap = new ConcurrentHashMap&lt;&gt;(map);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> newMap.entrySet().removeIf(item -&gt; now - item.getValue().getLastUpdate() &gt; recyclePeriod);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> methodWeightMap.put(key, newMap);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> } finally {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> updateLock.set(false);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> if (Objects.nonNull(selectedInvoker)) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> selectedWeightedRoundRobin.sel(totalWeight);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return selectedInvoker;</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> // should not happen here.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return upstreamList.get(0);</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="loadbalancerfactory"></a>LoadBalancerFactory<a class="hash-link" href="#loadbalancerfactory" title="Direct link to heading">#</a></h2><p>在这个工厂类中,提供了调用<code>LoadBalancer</code>的静态方法, 其中<code>ExtensionLoader</code><code>Apache Shenyu</code><code>SPI</code>执行入口。也就是说,LoadBalancer模组是可配置、可扩展的。这个静态方法中的<code>algorithm</code>变量是<code>LoadBalanceEnum</code>中定义<code>name</code>枚举类型。</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">/**</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * Selector upstream.</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> *</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param upstreamList the upstream list</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param algorithm the loadBalance algorithm</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @param ip the ip</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> * @return the upstream</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> */</span></span><span class="token-line" style="color:#393A34"><span class="token plain">public static Upstream selector(final List&lt;Upstream&gt; upstreamList, final String algorithm, final String ip) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> LoadBalancer loadBalance = ExtensionLoader.getExtensionLoader(LoadBalancer.class).getJoin(algorithm);</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> return loadBalance.select(upstreamList, ip);</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="using-of-loadbalancer-module"></a>Using of LoadBalancer module<a class="hash-link" href="#using-of-loadbalancer-module" title="Direct link to heading">#</a></h2><p>上面说明了<code>LoadBalancer</code> SPI接口及三个实作类。下面看一下<code>LoadBalancer</code><code>Apache Shenyu</code>中是如何被调用的。<code>DividePlugin</code>是路由选择插件,所有的Http请求都由该插件进行负载均衡处理。当请求头rpcType = http, 且开启该插件时,它将根据请求参数匹配规则,最终交由下游插件进行响应式代理调用。</p><p><code>DividePlugin</code><code>doExecute</code>方法中,先对要转发的请求的Header大小、content长度等做校验,</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain">@Override</span></span><span class="token-line" style="color:#393A34"><span class="token plain">protected Mono&lt;Void&gt; doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) {</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> ......</span></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p>接口方法的第二个参数是<code>ShenyuPluginChain</code> 类型,代表<code>plugin</code>的调用链,具体可参见<code>Apache Sheyu</code><code>plugin</code>的调用机制。第三个<code>SelectorData</code>类型的参数是选择器, 第四个是<code>RuldData</code>类型,代表规则。分别请查看对应的代码。</p><p> 下面给出了<code>doExecute</code>()方法中,有关<code>LoadBalancer</code>调用的代码片段:</p><div class="codeBlockContainer_J+bg"><div class="codeBlockContent_csEI java"><pre tabindex="0" class="prism-code language-java codeBlock_rtdJ thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_1zSZ"><span class="token-line" style="color:#393A34"><span class="token plain"> //取到要路由的服务器节点列表。</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> List&lt;Upstream&gt; upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> ... </span></span><span class="token-line" style="color:#393A34"><span class="token plain"> //取到请求的ip</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();</span></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block">
</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> //调用Util方法,执行LoadBalancer处理</span></span><span class="token-line" style="color:#393A34"><span class="token plain"> Upstream upstream = LoadBalancerFactory.selector(upstreamList, ruleHandle.getLoadBalance(), ip);</span></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_M3SB clean-btn">Copy</button></div></div><p> 这里<code>UpstreamCacheManager</code> 是缓存的要路由的服务器节点 , <code>ruleHandle.getLoadBalance()</code>取到的是<code>LoadBalanceEnum</code>定义的枚举name, 如<code>random, hash, roundRobin</code>等.</p><p> 经过封装,调用负载均衡功能非常的方便。 未来增加新的<code>LoadBalancer</code>类,这些调用的<code>Plugin</code>代码完全不需要变更。</p><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_WiXH" id="summary"></a>Summary<a class="hash-link" href="#summary" title="Direct link to heading">#</a></h2><p>经过上面的代码解读,从设计角度总结<code>LoadBalancer</code> 模组具有如下的特点:</p><ol><li><p>可扩展性:面向接口的设计,及基于Apache Shenyu SPI的实现,使得系统具有良好的可扩展性。可以方便的扩展为其他的动态的负载均衡算法,如最少连接方式(least connection)、最快模式( fastest)。并支持集群处理,具有良好的可扩展性。</p></li><li><p>可伸缩性:采用的一致性hash、权重随机和权重轮询算法,都可以无缝支持集群扩容或缩容。</p></li><li><p>流量预热等更细致的设计,能带来整体上更为平滑的负载均衡。</p></li></ol></div><footer class="row docusaurus-mt-lg"><div class="col"><b>Tags:</b><a class="margin-horiz--sm" href="/zh/blog/tags/load-balance">load balance</a><a class="margin-horiz--sm" href="/zh/blog/tags/spi">SPI</a><a class="margin-horiz--sm" href="/zh/blog/tags/apache-shen-yu">Apache ShenYu</a></div><div class="col text--right"><a aria-label="Read more about LoadBalancer SPI 代码分析" href="/zh/blog/SPI-SourceCode-Analysis-LoadBalance-SPI"><b>Read More</b></a></div></footer></article></main></div></div></div></div>
<script src="/zh/assets/js/runtime~main.ba3d74b1.js"></script>
<script src="/zh/assets/js/main.404c2b61.js"></script>
</body>
</html>