This is an automated email from the ASF dual-hosted git repository.
dengliming pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shenyu-website.git
The following commit(s) were added to refs/heads/main by this push:
new db5d996f0e1 feat: Upgrade SPI-SourceCode-Analysis-LoadBalance-SPI to
v2.5.0 (#835)
db5d996f0e1 is described below
commit db5d996f0e1477b5ea512bceff8d0665f2dfa4f9
Author: RayayChung <[email protected]>
AuthorDate: Thu Dec 15 07:09:03 2022 +0800
feat: Upgrade SPI-SourceCode-Analysis-LoadBalance-SPI to v2.5.0 (#835)
Co-authored-by: ray <[email protected]>
---
.../DataSync-SourceCode-Analysis-Http-Data-Sync.md | 2 +-
blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md | 273 ++++++++++++-------
.../SPI-SourceCode-Analysis-LoadBalance-SPI.md | 295 +++++++++++++--------
.../loadBalancer-class-diagram.png | Bin 0 -> 371805 bytes
4 files changed, 365 insertions(+), 205 deletions(-)
diff --git a/blog/DataSync-SourceCode-Analysis-Http-Data-Sync.md
b/blog/DataSync-SourceCode-Analysis-Http-Data-Sync.md
index e70d92de8c4..626c4fbecab 100644
--- a/blog/DataSync-SourceCode-Analysis-Http-Data-Sync.md
+++ b/blog/DataSync-SourceCode-Analysis-Http-Data-Sync.md
@@ -128,7 +128,7 @@ public class HttpSyncDataConfiguration {
- `@Configuration`: indicates that this is a configuration class.
- `@ConditionalOnClass(HttpSyncDataService.class)`: conditional annotation
indicating that the class `HttpSyncDataService` is to be present.
- `@ConditionalOnProperty(prefix = "shenyu.sync.http", name = "url")`:
conditional annotation to have the property `shenyu.sync.http.url` configured.
-- `@EnableConfigurationProperties(value = HttpConfig.class)`:
`@EnableConfigurationProperties(value = HttpConfig.class)`: indicates that the
annotation `@ConfigurationProperties(prefix = "shenyu.sync.http")` on
`HttpConfig` will take effect, and the configuration class `HttpConfig` will be
injected into the Ioc container.
+- `@EnableConfigurationProperties(value = HttpConfig.class)`: indicates that
the annotation `@ConfigurationProperties(prefix = "shenyu.sync.http")` on
`HttpConfig` will take effect, and the configuration class `HttpConfig` will be
injected into the Ioc container.
#### 2.2 Property initialization
diff --git a/blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
b/blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
index b21b946aa2d..a7b2ba665fe 100644
--- a/blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
+++ b/blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
@@ -1,5 +1,5 @@
---
-title: LoadBalance SPI Source Code Analysis
+title: LoadBalancer SPI Source Code Analysis
author: Huihui Yin
author_title: Apache ShenYu Contributor
author_url: https://github.com/changanjennifer/
@@ -8,73 +8,116 @@ tags: [load balance,SPI,Apache ShenYu]
Gateway applications need to support a variety of load balancing strategies,
including `random`,`Hashing`, `RoundRobin` and so on. In `Apache Shenyu`
gateway, it not only realizes such traditional algorithms, but also makes
smoother traffic processing for the entry of server nodes through detailed
processing such as traffic `warm-up,` so as to obtain better overall stability.
In this article, let's walk through how `Apache Shenyu` is designed and
implemented this part of the function.
-> This article based on `shenyu-2.4.0` version of the source code analysis.
+> This article based on `shenyu-2.5.0` version of the source code analysis.
[TOC]
-## LoadBalance `SPI`
+## LoadBalancer `SPI`
-The implementation of `LoadBalance` is in ***shenyu-plugin-divide*** module.
It has based on its `SPI` creation mechanism. The core interface code is shown
as follows. This interface well explains the concept: load balancing is to
select the most appropriate node from a series of server nodes. Routing,
traffic processing and load balancing is the basic function of `LoadBalance`
`SPI`.
+The implementation of `LoadBalancer` is in ***shenyu-loadbalancer*** module.
It has based on its `SPI` creation mechanism. The core interface code is shown
as follows. This interface well explains the concept: load balancing is to
select the most appropriate node from a series of server nodes. Routing,
traffic processing and load balancing is the basic function of `LoadBalancer`
`SPI`.
```java
@SPI
-public interface LoadBalance {
+public interface LoadBalancer {
/**
+ * this is select one for upstream list.
+ *
* @param upstreamList upstream list
* @param ip ip
- * @return divide upstream
+ * @return upstream
*/
- DivideUpstream select(List<DivideUpstream> upstreamList, String ip);
+ Upstream select(List<Upstream> upstreamList, String ip);
}
```
-Where `upstreamList` represents the server nodes list available for routing.
`DivideUpstream` is the data structure of server node, the important elements
including `protocol`, `upstreamUrl` , `weight`, `timestamp`, `warmup`.
+Where `upstreamList` represents the server nodes list available for routing.
`Upstream` is the data structure of server node, the important elements
including `protocol`, `upstreamUrl` , `weight`, `timestamp`,
`warmup`、`healthy`.
```java
-public class DivideUpstream implements Serializable {
- private String upstreamHost;
+public class Upstream {
/**
- * this is http protocol.
+ * protocol.
*/
- private String protocol;
- private String upstreamUrl;
- private int weight;
+ private final String protocol;
+
+ /**
+ * url.
+ */
+ private String url;
+
+ /**
+ * weight.
+ */
+ private final int weight;
+
+ /**
+ * false close, true open.
+ */
+ private boolean status;
+
+ /**
+ * startup time.
+ */
+ private final long timestamp;
+
+ /**
+ * warmup.
+ */
+ private final int warmup;
+
+ /**
+ * healthy.
+ */
+ private boolean healthy;
+
+ /**
+ * lastHealthTimestamp.
+ */
+ private long lastHealthTimestamp;
+
/**
- * false close/ true open.
+ * lastUnhealthyTimestamp.
*/
- @Builder.Default
- private boolean status = true;
- private long timestamp;
- private int warmup;
+ private long lastUnhealthyTimestamp;
+
+ /**
+ * group.
+ */
+ private String group;
+
+ /**
+ * version.
+ */
+ private String version;
}
+
```
-## Design of LoadBalance module
+## Design of LoadBalancer module
-The class diagram of `LoadBalance` module`is`shown as follows.
+The class diagram of `LoadBalancer` module`is`shown as follows.
-
+
-We can draw the outline of `LoadBalance` module from the class diagram:
+We can draw the outline of `LoadBalancer` module from the class diagram:
-1. The abstract class `AbstractLoadBalance` implements the SPI `LoadBalance`
interface,and supplies the template methods for selection related, such as
select(), selector(),and gives the calculation of weight.
+1. The abstract class `AbstractLoadBalancer` implements the SPI `LoadBalancer`
interface,and supplies the template methods for selection related, such as
select(), selector(),and gives the calculation of weight.
-2. Three implementation classes which inherit `AbstractLoadBalance` to realize
their own logic:
+2. Three implementation classes which inherit `AbstractLoadBalancer` to
realize their own logic:
- - `RandomLoadBalance` - Weight Random
- - `HashLoadBalance` - Consistent Hashing
- - `RoundRobinLoadBalance` -Weight Round Robin per-packet
+ - `RandomLoadBalancer` - Weight Random
+ - `HashLoadBalancer` - Consistent Hashing
+ - `RoundRobinLoadBalancer` -Weight Round Robin per-packet
-3. The utility class `LoadBalanceUtil` provides public static method to be
called.
+3. The factory class `LoadBalancerFactory` provides public static method to be
called.
The implementation classes and algorithms are configurable. According to
its specification, by adding profile in `SHENYU_DIERECTORY` directory, the
data in profile should be *key*=*value-class* format, where the *value-class*
will be load by the `Apache Shenyu SPI` class loader, and *key* value should be
an `name` defined in `LoadBalanceEnum.`
```properties
-random=org.apache.shenyu.plugin.divide.balance.spi.RandomLoadBalance
-roundRobin=org.apache.shenyu.plugin.divide.balance.spi.RoundRobinLoadBalance
-hash=org.apache.shenyu.plugin.divide.balance.spi.HashLoadBalance
+random=org.apache.shenyu.loadbalancer.spi.RandomLoadBalancer
+roundRobin=org.apache.shenyu.loadbalancer.spi.RoundRobinLoadBalancer
+hash=org.apache.shenyu.loadbalancer.spi.HashLoadBalancer
```
`The code of LoadBalanceEnum` is as follows:
@@ -102,22 +145,23 @@ public enum LoadBalanceEnum {
}
```
-## AbstractLoadBalance
+## AbstractLoadBalancer
-This abstract class implements the `LoadBalance` interface and define the
abstract method `doSelect()` to be processed by the implementation classes. In
the template method `select()`, It will do validation first then call the
`doSelect()` method.
+This abstract class implements the `LoadBalancer` interface and define the
abstract method `doSelect()` to be processed by the implementation classes. In
the template method `select()`, It will do validation first then call the
`doSelect()` method.
```java
- /**
+public abstract class AbstractLoadBalancer implements LoadBalancer {
+ /**
* Do select divide upstream.
*
* @param upstreamList the upstream list
* @param ip the ip
* @return the divide upstream
*/
- protected abstract DivideUpstream doSelect(List<DivideUpstream>
upstreamList, String ip);
+ protected abstract Upstream doSelect(List<Upstream> upstreamList, String
ip);
@Override
- public DivideUpstream select(final List<DivideUpstream> upstreamList,
final String ip) {
+ public Upstream select(final List<Upstream> upstreamList, final String ip)
{
if (CollectionUtils.isEmpty(upstreamList)) {
return null;
}
@@ -126,47 +170,86 @@ This abstract class implements the `LoadBalance`
interface and define the abstra
}
return doSelect(upstreamList, ip);
}
+}
```
When the `timestamp` of server node is not null, and the interval between
current time and `timestamp` is within the traffic warm-up time, the formula
for weight calculation is.
$$ {1-1}
ww = min(1,uptime/(warmup/weight))
$$
-It can be seen from the formula that the final weight(`ww`) is proportional to
the original-`weight` value. The closer the time interval is to the `warmup`
time, the greater the final `ww`. That is, the longer the waiting time of the
request, the higher the final `weight`. When there is no `timestamp` or other
conditions, the `ww` is equal to the `weight` value of `DivideUpstream` object.
+It can be seen from the formula that the final weight(`ww`) is proportional to
the original-`weight` value. The closer the time interval is to the `warmup`
time, the greater the final `ww`. That is, the longer the waiting time of the
request, the higher the final `weight`. When there is no `timestamp` or other
conditions, the `ww` is equal to the `weight` value of `Upstream` object.
The central of thinking about *warm-up*is to avoid bad performance when
adding new server and the new `JVMs` starting up.
Let's see how the load balancing with `Random`, `Hashing` and `RoundRobin`
strategy is implemented.
-## RandomLoadBalance
+## RandomLoadBalancer
-The `RandomLoadBalance` can handle two situations:
+The `RandomLoadBalancer` can handle two situations:
1. Each node without weight, or every node has the same weight, randomly
choose one.
2. Server Nodes with different weight, choose one randomly by weight.
-Following is the `random()` method of `RandomLoadBalance`. When traversing
server node list, if the randomly generated value is less than the weight of
node, then the current node will be chosen. If after one round traversing,
there's is no server node match, then it will return the first item of the
list. The `getWeight(DivideUpstream upstream)` is defined in
`AbstractLoadBalance` class.
+Following is the `random()` method of `RandomLoadBalancer`. When traversing
server node list, if the randomly generated value is less than the weight of
node, then the current node will be chosen. If after one round traversing,
there is no server node match, then it will choose one randomly. The
`getWeight(final Upstream upstream)` is defined in `AbstractLoadBalancer` class.
```java
- private DivideUpstream random(final int totalWeight, final
List<DivideUpstream> upstreamList) {
- // If the weights are not the same and the weights are greater than 0,
then random by the total number of weights
+ @Override
+ public Upstream doSelect(final List<Upstream> upstreamList, final String
ip) {
+ int length = upstreamList.size();
+ // every upstream has the same weight?
+ boolean sameWeight = true;
+ // the weight of every upstream
+ int[] weights = new int[length];
+ int firstUpstreamWeight = getWeight(upstreamList.get(0));
+ weights[0] = firstUpstreamWeight;
+ // init the totalWeight
+ int totalWeight = firstUpstreamWeight;
+ int halfLengthTotalWeight = 0;
+ for (int i = 1; i < length; i++) {
+ int currentUpstreamWeight = getWeight(upstreamList.get(i));
+ if (i <= (length + 1) / 2) {
+ halfLengthTotalWeight = totalWeight;
+ }
+ weights[i] = currentUpstreamWeight;
+ totalWeight += currentUpstreamWeight;
+ if (sameWeight && currentUpstreamWeight != firstUpstreamWeight) {
+ // Calculate whether the weight of ownership is the same.
+ sameWeight = false;
+ }
+ }
+ if (totalWeight > 0 && !sameWeight) {
+ return random(totalWeight, halfLengthTotalWeight, weights,
upstreamList);
+ }
+ return random(upstreamList);
+ }
+
+ private Upstream random(final int totalWeight, final int
halfLengthTotalWeight, final int[] weights, final List<Upstream> upstreamList) {
+ // If the weights are not the same and the weights are greater than 0,
then random by the total number of weights.
int offset = RANDOM.nextInt(totalWeight);
+ int index = 0;
+ int end = weights.length;
+ if (offset >= halfLengthTotalWeight) {
+ index = (weights.length + 1) / 2;
+ offset -= halfLengthTotalWeight;
+ } else {
+ end = (weights.length + 1) / 2;
+ }
// Determine which segment the random value falls on
- for (DivideUpstream divideUpstream : upstreamList) {
- offset -= getWeight(divideUpstream);
+ for (; index < end; index++) {
+ offset -= weights[index];
if (offset < 0) {
- return divideUpstream;
+ return upstreamList.get(index);
}
}
- return upstreamList.get(0);
+ return random(upstreamList);
}
```
-## HashLoadBalance
+## HashLoadBalancer
-In `HashLoadBalance`, it takes the advantages of [consistent
hashing](https://en.wikipedia.org/wiki/Consistent_hashing) , that maps both the
input traffic and the servers to a unit circle, or name as *hash ring*. For
the requested`ip` address, with its hash value to find the node closest in
clockwise order as the node to be routed. Let's see how consistent hashing is
implemented in `HashLoadBalance`.
+In `HashLoadBalancer`, it takes the advantages of [consistent
hashing](https://en.wikipedia.org/wiki/Consistent_hashing) , that maps both the
input traffic and the servers to a unit circle, or name as *hash ring*. For
the requested`ip` address, with its hash value to find the node closest in
clockwise order as the node to be routed. Let's see how consistent hashing is
implemented in `HashLoadBalancer`.
-As to the hash algorithms, `HashLoadBalance` uses `MD5` hash, which has the
advantage of mixing the input in an unpredictable but deterministic way. The
output is a 32-bit integer. the code is shown as follows:
+As to the hash algorithms, `HashLoadBalancer` uses `MD5` hash, which has the
advantage of mixing the input in an unpredictable but deterministic way. The
output is a 32-bit integer. the code is shown as follows:
```java
private static long hash(final String key) {
@@ -191,22 +274,20 @@ private static long hash(final String key) {
}
```
-Importantly, how to generate the hash ring and avoid skewness? Let's
the`doSelect()` method in`HashLoadBalance` as follows:
+Importantly, how to generate the hash ring and avoid skewness? Let's
the`doSelect()` method in`HashLoadBalancer` as follows:
```java
private static final int VIRTUAL_NODE_NUM = 5;
@Override
- public DivideUpstream doSelect(final List<DivideUpstream> upstreamList,
final String ip) {
- final ConcurrentSkipListMap<Long, DivideUpstream> treeMap = new
ConcurrentSkipListMap<>();
- for (DivideUpstream address : upstreamList) {
- for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
- long addressHash = hash("SOUL-" + address.getUpstreamUrl() +
"-HASH-" + i);
- treeMap.put(addressHash, address);
- }
- }
- long hash = hash(String.valueOf(ip));
- SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
+ public Upstream doSelect(final List<Upstream> upstreamList, final String
ip) {
+ final ConcurrentSkipListMap<Long, Upstream> treeMap = new
ConcurrentSkipListMap<>();
+ upstreamList.forEach(upstream -> IntStream.range(0,
VIRTUAL_NODE_NUM).forEach(i -> {
+ long addressHash = hash("SHENYU-" + upstream.getUrl() + "-HASH-" +
i);
+ treeMap.put(addressHash, upstream);
+ }));
+ long hash = hash(ip);
+ SortedMap<Long, Upstream> lastRing = treeMap.tailMap(hash);
if (!lastRing.isEmpty()) {
return lastRing.get(lastRing.firstKey());
}
@@ -224,9 +305,9 @@ In the above code section, after the hash ring is
generated, it uses `tailMap(K
Consistent hashing resolved the poor scalability of the traditional hashing by
modular operation.
-## RoundRobinLoadBalance
+## RoundRobinLoadBalancer
-The original Round-robin selection is to select server nodes one by one from
the candidate list. Whenever some nodes has crash ( ex, cannot be connected
after 1 minute), it will be removed from the candidate list, and do not attend
the next round, until the server node is recovered and it will be add to the
candidate list again. In `RoundRobinLoadBalance`,the weight Round Robin
per-packet schema is implemented.
+The original Round-robin selection is to select server nodes one by one from
the candidate list. Whenever some nodes has crash ( ex, cannot be connected
after 1 minute), it will be removed from the candidate list, and do not attend
the next round, until the server node is recovered and it will be add to the
candidate list again. In `RoundRobinLoadBalancer`,the weight Round Robin
per-packet schema is implemented.
In order to work in concurrent system, it provides an inner static class
`WeigthRoundRobin` to store and calculate the rolling selections of each server
node. Following is the main section of this class( removed remark )
@@ -234,6 +315,7 @@ In order to work in concurrent system, it provides an inner
static class `Weigth
protected static class WeightedRoundRobin {
private int weight;
+
private final AtomicLong current = new AtomicLong(0);
private long lastUpdate;
@@ -277,29 +359,29 @@ In the second level of the map, the embedded static
class - `WeighedRoundRobin`
```java
@Override
-public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final
String ip) {
- String key = upstreamList.get(0).getUpstreamUrl();
+public Upstream doSelect(final List<Upstream> upstreamList, final String ip) {
+ String key = upstreamList.get(0).getUrl();
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
- if (map == null) {
+ if (Objects.isNull(map)) {
methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<>(16));
map = methodWeightMap.get(key);
}
int totalWeight = 0;
long maxCurrent = Long.MIN_VALUE;
long now = System.currentTimeMillis();
- DivideUpstream selectedInvoker = null;
- WeightedRoundRobin selectedWRR = null;
- for (DivideUpstream upstream : upstreamList) {
- String rKey = upstream.getUpstreamUrl();
+ Upstream selectedInvoker = null;
+ WeightedRoundRobin selectedWeightedRoundRobin = null;
+ for (Upstream upstream : upstreamList) {
+ String rKey = upstream.getUrl();
WeightedRoundRobin weightedRoundRobin = map.get(rKey);
int weight = getWeight(upstream);
- if (weightedRoundRobin == null) {
+ if (Objects.isNull(weightedRoundRobin)) {
weightedRoundRobin = new WeightedRoundRobin();
weightedRoundRobin.setWeight(weight);
map.putIfAbsent(rKey, weightedRoundRobin);
}
if (weight != weightedRoundRobin.getWeight()) {
- //weight changed
+ // weight changed.
weightedRoundRobin.setWeight(weight);
}
long cur = weightedRoundRobin.increaseCurrent();
@@ -307,13 +389,13 @@ public DivideUpstream doSelect(final List<DivideUpstream>
upstreamList, final St
if (cur > maxCurrent) {
maxCurrent = cur;
selectedInvoker = upstream;
- selectedWRR = weightedRoundRobin;
+ selectedWeightedRoundRobin = weightedRoundRobin;
}
totalWeight += weight;
}
...... //erase the section which handles the time-out upstreams.
if (selectedInvoker != null) {
- selectedWRR.sel(totalWeight);
+ selectedWeightedRoundRobin.sel(totalWeight);
return selectedInvoker;
}
// should not happen here
@@ -332,7 +414,8 @@ For the above example LIST, assumes the `weight` array is
[20,50,30]. the fol
In each round, it will choose the server node with max `current` value.
- Round1:
- - Traverse the server node list, initialize the `weightedRoundRobin`
instance of each server node or update the `weight` value of server nodes
object `DivideUpstream`
+ - Traverse the server node list, initialize the `weightedRoundRobin`
instance of each server node or update the `weight` value of server nodes
object `Upstream`
+ - Traverse the server node list, initialize the `weightedRoundRobin`
instance of each server node or update the `weight` value of server nodes
object `Upstream`
- say, in this case, after traverse, the `current` array of the node list
changes to [20, 50,30],so according to rule, the node Stream-50 would be
chosen, and then the static object `WeightedRoundRobin` of Stream-50 executes
`sel(-total)` , the `current` array is now [20,-50, 30].
- Round 2: after traverse, the `current` array should be [40,0,60], so the
Stream-30 node would be chosen, `current` array is now [40,0,-40].
- Round 3: after traverse, `current` array changes to [60,50,-10],
Stream-20 would be chosen,and `current` array is now [-40,50,-10].
@@ -342,7 +425,7 @@ When there is any inconsistence or some server crashed, for
example, the lists s
```Java
if (!updateLock.get() && upstreamList.size() != map.size() &&
updateLock.compareAndSet(false, true)) {
try {
- // copy -> modify -> update reference
+ // copy -> modify -> update reference.
ConcurrentMap<String, WeightedRoundRobin> newMap = new
ConcurrentHashMap<>(map);
newMap.entrySet().removeIf(item -> now -
item.getValue().getLastUpdate() > recyclePeriod);
methodWeightMap.put(key, newMap);
@@ -350,40 +433,38 @@ When there is any inconsistence or some server crashed,
for example, the lists s
updateLock.set(false);
}
}
-
- if (selectedInvoker != null) {
- selectedWRR.sel(totalWeight);
+ if (Objects.nonNull(selectedInvoker)) {
+ selectedWeightedRoundRobin.sel(totalWeight);
return selectedInvoker;
}
- // should not happen here
+ // should not happen here.
return upstreamList.get(0);
```
-## LoadBalanceUtils
+## LoadBalancerFactory
-In this class, a static method calling `LoadBalance` is provided,
where`ExtensionLoader` is the entry point of `Apache Shenyu SPI`. That is to
say, `LoadBalance` module is configurable and extensible. The `algorithm`
variable in this static method is the `name` enumeration type defined in
`LoadBalanceEnum`.
+In this class, a static method calling `LoadBalancer` is provided,
where`ExtensionLoader` is the entry point of `Apache Shenyu SPI`. That is to
say, `LoadBalancer` module is configurable and extensible. The `algorithm`
variable in this static method is the `name` enumeration type defined in
`LoadBalanceEnum`.
```java
/**
- * Selector divide upstream.
+ * Selector upstream.
*
* @param upstreamList the upstream list
* @param algorithm the loadBalance algorithm
* @param ip the ip
- * @return the divide upstream
+ * @return the upstream
*/
- public static DivideUpstream selector(final List<DivideUpstream>
upstreamList, final String algorithm, final String ip) {
- LoadBalance loadBalance =
ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
+ public static Upstream selector(final List<Upstream> upstreamList, final
String algorithm, final String ip) {
+ LoadBalancer loadBalance =
ExtensionLoader.getExtensionLoader(LoadBalancer.class).getJoin(algorithm);
return loadBalance.select(upstreamList, ip);
}
```
-## Using of LoadBalance module
+## Using of LoadBalancer module
-In the above section, we describe the `LoadBalance` `SPI` and three
implementation classes. Let's take a look at how the `LoadBalance` to be used
in `Apache Shenyu`.
[DividePlugin](http://shenyu.apache.org/docs/plugin-center/http-handle/divide-plugin)
is a `plugin` in `Apache Shenyu` responsible for routing `http` request. when
enable to use this `plugin`, it will transfer traffic according to selection
data and rule data, and deliver to next plugin downstream.
+In the above section, we describe the `LoadBalancer` `SPI` and three
implementation classes. Let's take a look at how the `LoadBalancer` to be used
in `Apache Shenyu`.
[DividePlugin](http://shenyu.apache.org/docs/plugin-center/http-handle/divide-plugin)
is a `plugin` in `Apache Shenyu` responsible for routing `http` request. when
enable to use this `plugin`, it will transfer traffic according to selection
data and rule data, and deliver to next plugin downstream.
```java
-@SneakyThrows
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final
ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) {
......
@@ -394,24 +475,24 @@ The type of second parameter of `doExecute()` is
`ShenyuPluginChain`, which repr
In `doExecute()` of `DividePlugin`, first verify the size of `header`,
content length, etc, then preparing for load balancing.
-Following is a code fragment using`LoadBalance` in the `doExecute()` method:
+Following is a code fragment using`LoadBalancer` in the `doExecute()` method:
```java
- // find the routing server node list
- List<DivideUpstream> upstreamList =
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
+ // find the routing server node list
+ List<Upstream> upstreamList =
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
...
// the requested ip
- String ip =
Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
+ String ip =
Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
//calling the Utility class and invoke the LoadBalance processing.
- DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList,
ruleHandle.getLoadBalance(), ip);
+ Upstream upstream = LoadBalancerFactory.selector(upstreamList,
ruleHandle.getLoadBalance(), ip);
```
- In the above code, the output of`ruleHandle.getLoadBalance()` is the `name`
variable defined in `LoadBalanceEnum`, that is `random`, `hash`, `roundRobin`,
etc. It is very convenient to use `LoadBalance` by `LoadBalanceUtils`. When
adding more `LoadBalance` implementing classes, the interface in `plugin`
module will not be affect at all.
+ In the above code, the output of`ruleHandle.getLoadBalance()` is the `name`
variable defined in `LoadBalanceEnum`, that is `random`, `hash`, `roundRobin`,
etc. It is very convenient to use `LoadBalancer` by `LoadBalancerFactory`.
When adding more `LoadBalancer` implementing classes, the interface in
`plugin` module will not be effect at all.
## Summary
-After reading through the code of `LoadBalance` module, from the design
perspective, it is concluded that this module has the following
characteristics:
+After reading through the code of `LoadBalancer` module, from the design
perspective, it is concluded that this module has the following
characteristics:
1. Extensibility: Interface oriented design and implemented on `Apache Shenyu
SPI` mechanism, it can be easily extended to other dynamic load balancing
algorithms (for example, least connection, fastest mode, etc), and supports
cluster processing.
2. Scalability: Every load balancing implementation, weighted Random,
consistency Hashing and weighted `RoundRobin` can well support increase or
decrease cluster overall capacity.
diff --git
a/i18n/zh/docusaurus-plugin-content-blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
b/i18n/zh/docusaurus-plugin-content-blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
index 843db7b55f6..875fd1fc59c 100644
---
a/i18n/zh/docusaurus-plugin-content-blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
+++
b/i18n/zh/docusaurus-plugin-content-blog/SPI-SourceCode-Analysis-LoadBalance-SPI.md
@@ -1,5 +1,5 @@
---
-title: LoadBalance SPI 代码分析
+title: LoadBalancer SPI 代码分析
author: Huihui Yin
author_title: Apache ShenYu Contributor
author_url: https://github.com/changanjennifer/
@@ -8,73 +8,115 @@ tags: [load balance,SPI,Apache ShenYu]
网关应用需要支持多种负载均衡的方案,包括随机选择、Hash、轮询等方式。`Apache
Shenyu`网关中不仅实现了传统网关的这些均衡策略,还通过流量预热(warmup)等细节处理,对服务器节点的加入,做了更平滑的流量处理,获得了更好的整体稳定性。让我们来看看Shenyu是是如何设计和实现这部分功能的。
-> 本文基于`shenyu-2.4.0`版本进行源码分析.
+> 本文基于`shenyu-2.5.0`版本进行源码分析.
[TOC]
-## LoadBalance `SPI`
+## LoadBalancer `SPI`
-`LoadBalance` SPI
定义在***shenyu-plugin-divide***模组中,以下是这个核心接口的代码,这个接口很好的诠释了这样一个理念:负载均衡是在一系列服务器节点中选出最合适的节点,也就是选择策略。做流量转发、路由和负载均衡是`LoadBalance
SPI`的基本功能
+`LoadBalancer` SPI
定义在***shenyu-loadbalancer***模组中,以下是这个核心接口的代码,这个接口很好的诠释了这样一个理念:负载均衡是在一系列服务器节点中选出最合适的节点,也就是选择策略。做流量转发、路由和负载均衡是`LoadBalance
SPI`的基本功能
```java
@SPI
-public interface LoadBalance {
+public interface LoadBalancer {
/**
+ * this is select one for upstream list.
+ *
* @param upstreamList upstream list
* @param ip ip
- * @return divide upstream
+ * @return upstream
*/
- DivideUpstream select(List<DivideUpstream> upstreamList, String ip);
+ Upstream select(List<Upstream> upstreamList, String ip);
}
```
-接口中,upstreamList是可选路由的一组服务器节点,`DivideUpstream`
是服务器节点的数据结构,它包括的重要元素有:协议、upstreamUrl 、权重、时间戳,warmup等。
+接口中,upstreamList是可选路由的一组服务器节点,`Upstream` 是服务器节点的数据结构,它包括的重要元素有:协议、url
、权重、时间戳,warmup,健康状态等。
```java
-public class DivideUpstream implements Serializable {
- private String upstreamHost;
+public class Upstream {
/**
- * this is http protocol.
+ * protocol.
*/
- private String protocol;
- private String upstreamUrl;
- private int weight;
+ private final String protocol;
+
+ /**
+ * url.
+ */
+ private String url;
+
+ /**
+ * weight.
+ */
+ private final int weight;
+
+ /**
+ * false close, true open.
+ */
+ private boolean status;
+
+ /**
+ * startup time.
+ */
+ private final long timestamp;
+
+ /**
+ * warmup.
+ */
+ private final int warmup;
+
+ /**
+ * healthy.
+ */
+ private boolean healthy;
+
+ /**
+ * lastHealthTimestamp.
+ */
+ private long lastHealthTimestamp;
+
+ /**
+ * lastUnhealthyTimestamp.
+ */
+ private long lastUnhealthyTimestamp;
+
/**
- * false close/ true open.
+ * group.
*/
- @Builder.Default
- private boolean status = true;
- private long timestamp;
- private int warmup;
+ private String group;
+
+ /**
+ * version.
+ */
+ private String version;
}
```
-## Design of LoadBalance module
+## Design of LoadBalance module`
-图1是`LoadBalance`模组的类图:
+图1是`LoadBalancer`模组的类图:
-
+
从类图上可以看出`LoadBalance`的设计概要:
-1. 抽象类`AbstractLoadBalance`继承自`LoadBalance` SPI接口,并提供选择的模板方法,及权重计算。
+1. 抽象类`AbstractLoadBalancer`继承自`LoadBalancer` SPI接口,并提供选择的模板方法,及权重计算。
-2. 三个实做类继承`AbstractLoadBalance`, 实现各自的逻辑处理。
+2. 三个实做类继承`AbstractLoadBalancer`, 实现各自的逻辑处理。
- - `RandomLoadBalance` -加权随机选择 Weight Random
- - `HashLoadBalance` - 一致性Hash
- - `RoundRobinLoadBalance` -加权轮询(Weight Round Robin per-packet)
+ - `RandomLoadBalancer` -加权随机选择 Weight Random
+ - `HashLoadBalancer` - 一致性Hash
+ - `RoundRobinLoadBalancer` -加权轮询(Weight Round Robin per-packet)
-3. 由Util类`LoadBalanceUtil` 实现对外的静态调用方法。
+3. 由工厂类`LoadBalancerFactory` 实现对外的静态调用方法。
另外根据`Apache Sheny
SPI`规范,在`SHENYU_DIERECTORY`中的添加profile,配置`LoadBalance`的实现类,配置key=class形式,左边的operator要和`LoadBalanceEnum`中的定义一致。
```properties
-random=org.apache.shenyu.plugin.divide.balance.spi.RandomLoadBalance
-roundRobin=org.apache.shenyu.plugin.divide.balance.spi.RoundRobinLoadBalance
-hash=org.apache.shenyu.plugin.divide.balance.spi.HashLoadBalance
+random=org.apache.shenyu.loadbalancer.spi.RandomLoadBalancer
+roundRobin=org.apache.shenyu.loadbalancer.spi.RoundRobinLoadBalancer
+hash=org.apache.shenyu.loadbalancer.spi.HashLoadBalancer
```
`LoadBalanceEnum`的定义如下:
@@ -102,22 +144,23 @@ public enum LoadBalanceEnum {
}
```
-## AbstractLoadBalance
+## AbstractLoadBalancer
-这个抽象类实做了`LoadBalance`接口, 定义了抽象方法`doSelect()`留给实作类处理,在模板方法`select()`
中先进行校验,之后调用由实作类实现的`doSelect()`方法。
+这个抽象类实做了`LoadBalancer`接口, 定义了抽象方法`doSelect()`留给实作类处理,在模板方法`select()`
中先进行校验,之后调用由实作类实现的`doSelect()`方法。
```java
- /**
+public abstract class AbstractLoadBalancer implements LoadBalancer {
+ /**
* Do select divide upstream.
*
* @param upstreamList the upstream list
* @param ip the ip
* @return the divide upstream
*/
- protected abstract DivideUpstream doSelect(List<DivideUpstream>
upstreamList, String ip);
+ protected abstract Upstream doSelect(List<Upstream> upstreamList, String
ip);
@Override
- public DivideUpstream select(final List<DivideUpstream> upstreamList,
final String ip) {
+ public Upstream select(final List<Upstream> upstreamList, final String ip)
{
if (CollectionUtils.isEmpty(upstreamList)) {
return null;
}
@@ -126,49 +169,88 @@ public enum LoadBalanceEnum {
}
return doSelect(upstreamList, ip);
}
+}
```
权重的处理方法`getWeight()`的逻辑是:当有时间戳,并且当前时间与时间戳间隔在流量预热warmup时间内,权重计算的公式为:
$$ {1-1}
ww = min(1,uptime/(warmup/weight))
$$
-从公式可以看出,最终的权值,与设置的weigth成正比,时间间隔越接近warmup时间,权重就越大。也就是说等待的时间越长,被分派的权重越高。没有时间戳时等其他情况下,返回`DivideUpstream`设置的`weight`值。
+从公式可以看出,最终的权值,与设置的weight成正比,时间间隔越接近warmup时间,权重就越大。也就是说等待的时间越长,被分派的权重越高。没有时间戳时等其他情况下,返回`Upstream`设置的`weight`值。
考虑流量预热(warmup)的核心思想是避免在添加新服务器和启动新JVM时网关性能不佳。
下面我们看一下三个实做类的实现。
-## RandomLoadBalance
+## RandomLoadBalancer
-这里随机`LoadBalance` 可以处理两种情况:
+这里随机`LoadBalancer` 可以处理两种情况:
1. 没有权重:所有服务器都没有设定权重,或者权重都一样, 会随机选择一个。
2. 有权重:服务器设定有不同的权重,会根据权重,进行随机选择。
-下面是有权重时的随机选择代码`random()`: 遍历服务器列表,当产生的随机值小于某个服务器权重时,这个服务器被选中。
若遍历后没有满足条件,就返回服务器列表的第一个。这里`getWeight(DivideUpstream upstream)`
方法是在`AbstractLoadBalance` 中定义的,按公式计算权重。
+下面是有权重时的随机选择代码`random()`:
遍历全部服务器列表,当随机值小于某个服务器权重时,这个服务器被选中(这里提前计算了前一半服务器的权重和,如果随机值大于`halfLengthTotalWeight`,则遍历从`(weights.length
+ 1) / 2`开始,提高了小效率)。 若遍历后没有满足条件,就在全部服务器列表中随机选择一个返回。这里`getWeight(final Upstream
upstream)` 方法是在`AbstractLoadBalancer` 中定义的,按公式计算权重。
```java
- private DivideUpstream random(final int totalWeight, final
List<DivideUpstream> upstreamList) {
- // If the weights are not the same and the weights are greater than 0,
then random by the total number of weights
- int offset = RANDOM.nextInt(totalWeight);
- // Determine which segment the random value falls on
- for (DivideUpstream divideUpstream : upstreamList) {
- offset -= getWeight(divideUpstream);
- if (offset < 0) {
- return divideUpstream;
- }
+@Override
+public Upstream doSelect(final List<Upstream> upstreamList, final String ip) {
+ int length = upstreamList.size();
+ // every upstream has the same weight?
+ boolean sameWeight = true;
+ // the weight of every upstream
+ int[] weights = new int[length];
+ int firstUpstreamWeight = getWeight(upstreamList.get(0));
+ weights[0] = firstUpstreamWeight;
+ // init the totalWeight
+ int totalWeight = firstUpstreamWeight;
+ int halfLengthTotalWeight = 0;
+ for (int i = 1; i < length; i++) {
+ int currentUpstreamWeight = getWeight(upstreamList.get(i));
+ if (i <= (length + 1) / 2) {
+ halfLengthTotalWeight = totalWeight;
+ }
+ weights[i] = currentUpstreamWeight;
+ totalWeight += currentUpstreamWeight;
+ if (sameWeight && currentUpstreamWeight != firstUpstreamWeight) {
+ // Calculate whether the weight of ownership is the same.
+ sameWeight = false;
+ }
+ }
+ if (totalWeight > 0 && !sameWeight) {
+ return random(totalWeight, halfLengthTotalWeight, weights,
upstreamList);
+ }
+ return random(upstreamList);
+}
+
+private Upstream random(final int totalWeight, final int
halfLengthTotalWeight, final int[] weights, final List<Upstream> upstreamList) {
+ // If the weights are not the same and the weights are greater than 0,
then random by the total number of weights.
+ int offset = RANDOM.nextInt(totalWeight);
+ int index = 0;
+ int end = weights.length;
+ if (offset >= halfLengthTotalWeight) {
+ index = (weights.length + 1) / 2;
+ offset -= halfLengthTotalWeight;
+ } else {
+ end = (weights.length + 1) / 2;
+ }
+ // Determine which segment the random value falls on
+ for (; index < end; index++) {
+ offset -= weights[index];
+ if (offset < 0) {
+ return upstreamList.get(index);
}
- return upstreamList.get(0);
}
+ return random(upstreamList);
+}
```
-因此,当采用`RandomLoadBalance`时,是按权重随机分派服务器的。
+因此,当采用`RandomLoadBalancer`时,是按权重随机分派服务器的。
-## HashLoadBalance
+## HashLoadBalancer
-`Apache Shenyu`的`HashLoadBalance`
中采用了一致性hash算法,使用有序hash环,将key与服务器节点的hash映射缓存起来。对于请求的ip地址,计算出其`hash`值,
在hash环上顺时针查找距离这个key的hash值最近的节点,将其作为要路由的节点。一致性hash解决了传统取余hash算法的可伸缩性差的问题。
+`Apache Shenyu`的`HashLoadBalancer`
中采用了一致性hash算法,使用有序hash环,将key与服务器节点的hash映射缓存起来。对于请求的ip地址,计算出其`hash`值,
在hash环上顺时针查找距离这个key的hash值最近的节点,将其作为要路由的节点。一致性hash解决了传统取余hash算法的可伸缩性差的问题。
-`HashLoadBalance`中的采用的是加密的单向MD5散列函数,这个hash函数会hash后产生不可预期但确定性的()的结果,输出为32-bit的长整数。`hash`代码如下:
+`HashLoadBalancer`中的采用的是加密的单向MD5散列函数,这个hash函数会hash后产生不可预期但确定性的()的结果,输出为32-bit的长整数。`hash`代码如下:
```java
private static long hash(final String key) {
@@ -193,22 +275,20 @@ private static long hash(final String key) {
}
```
-再看一下`HashLoadBalance`的选择函数`doSelect()`的实现:
+再看一下`HashLoadBalancer`的选择函数`doSelect()`的实现:
```java
private static final int VIRTUAL_NODE_NUM = 5;
@Override
- public DivideUpstream doSelect(final List<DivideUpstream> upstreamList,
final String ip) {
- final ConcurrentSkipListMap<Long, DivideUpstream> treeMap = new
ConcurrentSkipListMap<>();
- for (DivideUpstream address : upstreamList) {
- for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
- long addressHash = hash("SOUL-" + address.getUpstreamUrl() +
"-HASH-" + i);
- treeMap.put(addressHash, address);
- }
- }
- long hash = hash(String.valueOf(ip));
- SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
+ public Upstream doSelect(final List<Upstream> upstreamList, final String
ip) {
+ final ConcurrentSkipListMap<Long, Upstream> treeMap = new
ConcurrentSkipListMap<>();
+ upstreamList.forEach(upstream -> IntStream.range(0,
VIRTUAL_NODE_NUM).forEach(i -> {
+ long addressHash = hash("SHENYU-" + upstream.getUrl() + "-HASH-" +
i);
+ treeMap.put(addressHash, upstream);
+ }));
+ long hash = hash(ip);
+ SortedMap<Long, Upstream> lastRing = treeMap.tailMap(hash);
if (!lastRing.isEmpty()) {
return lastRing.get(lastRing.firstKey());
}
@@ -222,9 +302,9 @@ private static long hash(final String key) {
上述代码中,生成hash环之后,就是调用`ConcurrentSkipListMap`的`tailMap()`方法,找到大于等于请求的ip的hash值的子集,这个子集的第一个就是要路由的服务器节点。采用了合适的数据结构,这里的代码看上去是不是特别的简洁流畅?
-## RoundRobinLoadBalance
+## RoundRobinLoadBalancer
-Round-robin轮询方法的原始定义是顺序循环将请求依次循环地连接到每个服务器。当某个服务器发生故障(例如:一分钟连接不上的服务器),从候选队列中取出,不参与下一次的轮询,直到其恢复正常。在
`RoundRobinLoadBalance`中实现的是组内加权轮询(`Weight Round Robin per-packet`)方法:
+Round-robin轮询方法的原始定义是顺序循环将请求依次循环地连接到每个服务器。当某个服务器发生故障(例如:一分钟连接不上的服务器),从候选队列中取出,不参与下一次的轮询,直到其恢复正常。在
`RoundRobinLoadBalancer`中实现的是组内加权轮询(`Weight Round Robin per-packet`)方法:
为了计算和存储每个服务器节点的轮询次数,在这个类中定义了一个静态内部类`WeigthRoundRobin`,我们先看一下它的主要代码(去掉了注释):
@@ -232,6 +312,7 @@ Round-robin轮询方法的原始定义是顺序循环将请求依次循环地连
protected static class WeightedRoundRobin {
private int weight;
+
private final AtomicLong current = new AtomicLong(0);
private long lastUpdate;
@@ -274,29 +355,29 @@ private final ConcurrentMap<String, ConcurrentMap<String,
WeightedRoundRobin>> m
```java
@Override
-public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final
String ip) {
- String key = upstreamList.get(0).getUpstreamUrl();
+public Upstream doSelect(final List<Upstream> upstreamList, final String ip) {
+ String key = upstreamList.get(0).getUrl();
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
- if (map == null) {
+ if (Objects.isNull(map)) {
methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<>(16));
map = methodWeightMap.get(key);
}
int totalWeight = 0;
long maxCurrent = Long.MIN_VALUE;
long now = System.currentTimeMillis();
- DivideUpstream selectedInvoker = null;
- WeightedRoundRobin selectedWRR = null;
- for (DivideUpstream upstream : upstreamList) {
- String rKey = upstream.getUpstreamUrl();
+ Upstream selectedInvoker = null;
+ WeightedRoundRobin selectedWeightedRoundRobin = null;
+ for (Upstream upstream : upstreamList) {
+ String rKey = upstream.getUrl();
WeightedRoundRobin weightedRoundRobin = map.get(rKey);
int weight = getWeight(upstream);
- if (weightedRoundRobin == null) {
+ if (Objects.isNull(weightedRoundRobin)) {
weightedRoundRobin = new WeightedRoundRobin();
weightedRoundRobin.setWeight(weight);
map.putIfAbsent(rKey, weightedRoundRobin);
}
if (weight != weightedRoundRobin.getWeight()) {
- //weight changed
+ // weight changed.
weightedRoundRobin.setWeight(weight);
}
long cur = weightedRoundRobin.increaseCurrent();
@@ -304,13 +385,13 @@ public DivideUpstream doSelect(final List<DivideUpstream>
upstreamList, final St
if (cur > maxCurrent) {
maxCurrent = cur;
selectedInvoker = upstream;
- selectedWRR = weightedRoundRobin;
+ selectedWeightedRoundRobin = weightedRoundRobin;
}
totalWeight += weight;
}
...... //erase the section which handles the time-out upstreams.
if (selectedInvoker != null) {
- selectedWRR.sel(totalWeight);
+ selectedWeightedRoundRobin.sel(totalWeight);
return selectedInvoker;
}
// should not happen here
@@ -339,7 +420,7 @@ public DivideUpstream doSelect(final List<DivideUpstream>
upstreamList, final St
```Java
if (!updateLock.get() && upstreamList.size() != map.size() &&
updateLock.compareAndSet(false, true)) {
try {
- // copy -> modify -> update reference
+ // copy -> modify -> update reference.
ConcurrentMap<String, WeightedRoundRobin> newMap = new
ConcurrentHashMap<>(map);
newMap.entrySet().removeIf(item -> now -
item.getValue().getLastUpdate() > recyclePeriod);
methodWeightMap.put(key, newMap);
@@ -347,42 +428,40 @@ public DivideUpstream doSelect(final List<DivideUpstream>
upstreamList, final St
updateLock.set(false);
}
}
-
- if (selectedInvoker != null) {
- selectedWRR.sel(totalWeight);
+ if (Objects.nonNull(selectedInvoker)) {
+ selectedWeightedRoundRobin.sel(totalWeight);
return selectedInvoker;
}
- // should not happen here
+ // should not happen here.
return upstreamList.get(0);
```
-## LoadBalanceUtils
+## LoadBalancerFactory
-在这个类中,提供了调用`LoadBalance`的静态方法, 其中`ExtensionLoader` 是`Apache
Shenyu`的`SPI`执行入口。也就是说,LoadBalance模组是可配置、可扩展的。这个静态方法中的`algorithm`变量是`LoadBalanceEnum`中定义`name`枚举类型。
+在这个工厂类中,提供了调用`LoadBalancer`的静态方法, 其中`ExtensionLoader` 是`Apache
Shenyu`的`SPI`执行入口。也就是说,LoadBalancer模组是可配置、可扩展的。这个静态方法中的`algorithm`变量是`LoadBalanceEnum`中定义`name`枚举类型。
```java
- /**
- * Selector divide upstream.
- *
- * @param upstreamList the upstream list
- * @param algorithm the loadBalance algorithm
- * @param ip the ip
- * @return the divide upstream
- */
- public static DivideUpstream selector(final List<DivideUpstream>
upstreamList, final String algorithm, final String ip) {
- LoadBalance loadBalance =
ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
- return loadBalance.select(upstreamList, ip);
- }
+/**
+ * Selector upstream.
+ *
+ * @param upstreamList the upstream list
+ * @param algorithm the loadBalance algorithm
+ * @param ip the ip
+ * @return the upstream
+ */
+public static Upstream selector(final List<Upstream> upstreamList, final
String algorithm, final String ip) {
+ LoadBalancer loadBalance =
ExtensionLoader.getExtensionLoader(LoadBalancer.class).getJoin(algorithm);
+ return loadBalance.select(upstreamList, ip);
+}
```
-## Using of LoadBalance module
+## Using of LoadBalancer module
-上面说明了`LoadBalance` SPI接口及三个实作类。下面看一下`LoadBalance`在`Apache
Shenyu`中是如何被调用的。`DividePlugin`是路由选择插件,所有的Http请求都由该插件进行负载均衡处理。当请求头rpcType =
http, 且开启该插件时,它将根据请求参数匹配规则,最终交由下游插件进行响应式代理调用。
+上面说明了`LoadBalancer` SPI接口及三个实作类。下面看一下`LoadBalancer`在`Apache
Shenyu`中是如何被调用的。`DividePlugin`是路由选择插件,所有的Http请求都由该插件进行负载均衡处理。当请求头rpcType =
http, 且开启该插件时,它将根据请求参数匹配规则,最终交由下游插件进行响应式代理调用。
在`DividePlugin`的`doExecute`方法中,先对要转发的请求的Header大小、content长度等做校验,
```java
-@SneakyThrows
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final
ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) {
......
@@ -391,28 +470,28 @@ protected Mono<Void> doExecute(final ServerWebExchange
exchange, final ShenyuPlu
接口方法的第二个参数是`ShenyuPluginChain` 类型,代表`plugin`的调用链,具体可参见`Apache Sheyu`
的`plugin`的调用机制。第三个`SelectorData`类型的参数是选择器, 第四个是`RuldData`类型,代表规则。分别请查看对应的代码。
- 下面给出了`doExecute`()方法中,有关`LoadBalance`调用的代码片段:
+ 下面给出了`doExecute`()方法中,有关`LoadBalancer`调用的代码片段:
```java
//取到要路由的服务器节点列表。
- List<DivideUpstream> upstreamList =
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
+ List<Upstream> upstreamList =
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
...
//取到请求的ip
- String ip =
Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
+ String ip =
Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
- //调用Util方法,执行LoadBalance处理
- DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList,
ruleHandle.getLoadBalance(), ip);
+ //调用Util方法,执行LoadBalancer处理
+ Upstream upstream = LoadBalancerFactory.selector(upstreamList,
ruleHandle.getLoadBalance(), ip);
```
这里`UpstreamCacheManager` 是缓存的要路由的服务器节点 ,
`ruleHandle.getLoadBalance()`取到的是`LoadBalanceEnum`定义的枚举name, 如`random, hash,
roundRobin`等.
- 经过封装,调用负载均衡功能非常的方便。 未来增加新的`LoadBalance`类,这些调用的`Plugin`代码完全不需要变更。
+ 经过封装,调用负载均衡功能非常的方便。 未来增加新的`LoadBalancer`类,这些调用的`Plugin`代码完全不需要变更。
## Summary
-经过上面的代码解读,从设计角度总结`LoadBalance` 模组具有如下的特点:
+经过上面的代码解读,从设计角度总结`LoadBalancer` 模组具有如下的特点:
1. 可扩展性:面向接口的设计,及基于Apache Shenyu
SPI的实现,使得系统具有良好的可扩展性。可以方便的扩展为其他的动态的负载均衡算法,如最少连接方式(least connection)、最快模式(
fastest)。并支持集群处理,具有良好的可扩展性。
-2. 可伸缩性: 采用的一致性hash `LoadBalance`、权重随机和权重轮询,都可以无缝支持集群扩容或缩容。
+2. 可伸缩性:采用的一致性hash、权重随机和权重轮询算法,都可以无缝支持集群扩容或缩容。
3. 流量预热等更细致的设计,能带来整体上更为平滑的负载均衡。
diff --git
a/static/img/activities/code-analysis-loadbalance-spi/loadBalancer-class-diagram.png
b/static/img/activities/code-analysis-loadbalance-spi/loadBalancer-class-diagram.png
new file mode 100644
index 00000000000..895070012dc
Binary files /dev/null and
b/static/img/activities/code-analysis-loadbalance-spi/loadBalancer-class-diagram.png
differ