Skip to content

Commit 4a6357e

Browse files
fuyuwei01SkyeBeFreeman
authored andcommitted
feat: support shortest response time load balance. (#619)
1 parent 534cab9 commit 4a6357e

File tree

17 files changed

+605
-3
lines changed

17 files changed

+605
-3
lines changed

polaris-common/polaris-config-default/src/main/resources/conf/default-config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ consumer:
239239
loadbalancer:
240240
#描述: 负载均衡类型(已注册的负载均衡插件名)
241241
type: weightedRandom
242+
#描述: 负载均衡插件配置
243+
plugin:
244+
#描述: 最短响应时间负载均衡的窗口滑动周期
245+
shortestResponseTime:
246+
slidePeriod: 30000ms
242247
#描述:节点熔断相关配置
243248
circuitBreaker:
244249
#描述: 是否启用本地节点熔断功能

polaris-common/polaris-config/src/main/java/com/tencent/polaris/api/config/consumer/LoadBalanceConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public interface LoadBalanceConfig extends PluginConfig, Verifier {
4343
*/
4444
String LOAD_BALANCE_WEIGHTED_ROUND_ROBIN = "weightedRoundRobin";
4545

46+
String LOAD_BALANCE_SHORTEST_RESPONSE_TIME = "shortestResponseTime";
4647
/**
4748
* 轮询负载均衡插件名
4849
*/

polaris-common/polaris-model/src/main/java/com/tencent/polaris/api/pojo/InstanceLocalValue.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ public interface InstanceLocalValue {
6565
*/
6666
void setDetectResult(DetectResult detectResult);
6767

68+
/**
69+
* 获取实例统计信息
70+
*
71+
* @return InstanceStatistic
72+
*/
73+
InstanceStatistic getInstanceStatistic();
74+
75+
/**
76+
* 设置实例统计信息
77+
*
78+
* @param instanceStatistic
79+
*/
80+
void setInstanceStatistic(InstanceStatistic instanceStatistic);
6881
/**
6982
* 获取插件数据
7083
*
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Tencent is pleased to support the open source community by making polaris-java available.
3+
*
4+
* Copyright (C) 2021 Tencent. All rights reserved.
5+
*
6+
* Licensed under the BSD 3-Clause License (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://opensource.org/licenses/BSD-3-Clause
11+
*
12+
* Unless required by applicable law or agreed to in writing, software distributed
13+
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations under the License.
16+
*/
17+
18+
package com.tencent.polaris.api.pojo;
19+
20+
import java.util.concurrent.atomic.AtomicLong;
21+
22+
public class InstanceStatistic {
23+
24+
/**
25+
* 总调用次数统计
26+
*/
27+
private final AtomicLong totalCount;
28+
/**
29+
* 成功调用次数统计
30+
*/
31+
private final AtomicLong succeededCount;
32+
/**
33+
* 总调用耗时统计
34+
*/
35+
private final AtomicLong totalElapsed;
36+
/**
37+
* 成功调用耗时统计
38+
*/
39+
private final AtomicLong succeededElapsed;
40+
/**
41+
* 最后一次成功调用耗时
42+
*/
43+
private final AtomicLong lastSucceededElapsed;
44+
/**
45+
* 最大调用耗时
46+
*/
47+
private final AtomicLong maxElapsed;
48+
/**
49+
* 失败调用最大耗时
50+
*/
51+
private final AtomicLong failedMaxElapsed;
52+
/**
53+
* 成功调用最大耗时
54+
*/
55+
private final AtomicLong succeededMaxElapsed;
56+
57+
public InstanceStatistic() {
58+
this(0, 0, 0, 0, 0, 0, 0, 0);
59+
}
60+
public InstanceStatistic(long totalCount, long succeededCount, long totalElapsed, long succeededElapsed,
61+
long lastSucceededElapsed, long maxElapsed, long failedMaxElapsed, long succeededMaxElapsed) {
62+
this.totalCount = new AtomicLong(totalCount);
63+
this.succeededCount = new AtomicLong(succeededCount);
64+
this.totalElapsed = new AtomicLong(totalElapsed);
65+
this.succeededElapsed = new AtomicLong(succeededElapsed);
66+
this.lastSucceededElapsed = new AtomicLong(lastSucceededElapsed);
67+
this.maxElapsed = new AtomicLong(maxElapsed);
68+
this.failedMaxElapsed = new AtomicLong(failedMaxElapsed);
69+
this.succeededMaxElapsed = new AtomicLong(succeededMaxElapsed);
70+
}
71+
public void count(long elapsed, boolean success) {
72+
totalCount.incrementAndGet();
73+
totalElapsed.addAndGet(elapsed);
74+
maxElapsed.set(Math.max(maxElapsed.get(), elapsed));
75+
if (success) {
76+
succeededCount.incrementAndGet();
77+
succeededElapsed.addAndGet(elapsed);
78+
lastSucceededElapsed.set(elapsed);
79+
succeededMaxElapsed.set(Math.max(succeededMaxElapsed.get(), elapsed));
80+
} else{
81+
failedMaxElapsed.addAndGet(elapsed);
82+
}
83+
}
84+
public long getTotalCount() {
85+
return totalCount.get();
86+
}
87+
88+
public long getSucceededCount() {
89+
return succeededCount.get();
90+
}
91+
92+
public long getTotalElapsed() {
93+
return totalElapsed.get();
94+
}
95+
96+
public long getSucceededElapsed() {
97+
return succeededElapsed.get();
98+
}
99+
100+
public long getLastSucceededElapsed() {
101+
return lastSucceededElapsed.get();
102+
}
103+
104+
public long getMaxElapsed() {
105+
return maxElapsed.get();
106+
}
107+
108+
public long getFailedMaxElapsed() {
109+
return failedMaxElapsed.get();
110+
}
111+
112+
public long getSucceededMaxElapsed() {
113+
return succeededMaxElapsed.get();
114+
}
115+
116+
@Override
117+
public String toString() {
118+
return "InstanceStatistic{" +
119+
"totalCount=" + totalCount +
120+
", succeededCount=" + succeededCount +
121+
", totalElapsed=" + totalElapsed +
122+
", succeededElapsed=" + succeededElapsed +
123+
", lastSucceededElapsed=" + lastSucceededElapsed +
124+
", maxElapsed=" + maxElapsed +
125+
", failedMaxElapsed=" + failedMaxElapsed +
126+
", succeededMaxElapsed=" + succeededMaxElapsed +
127+
'}';
128+
}
129+
}

polaris-common/polaris-model/src/main/java/com/tencent/polaris/client/pojo/DefaultInstanceLocalValue.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
2121
import com.tencent.polaris.api.pojo.DetectResult;
2222
import com.tencent.polaris.api.pojo.InstanceLocalValue;
23+
import com.tencent.polaris.api.pojo.InstanceStatistic;
2324
import com.tencent.polaris.api.pojo.StatusDimension;
2425
import java.util.Collection;
2526
import java.util.Map;
@@ -42,6 +43,12 @@ public class DefaultInstanceLocalValue implements InstanceLocalValue {
4243

4344
private final Map<Integer, Object> pluginValues = new ConcurrentHashMap<>();
4445

46+
private final AtomicReference<InstanceStatistic> instanceStatistic = new AtomicReference<>();
47+
48+
public DefaultInstanceLocalValue() {
49+
instanceStatistic.set(new InstanceStatistic());
50+
}
51+
4552
@Override
4653
public Collection<StatusDimension> getStatusDimensions() {
4754
return circuitBreakerStatus.keySet();
@@ -75,10 +82,20 @@ public AtomicReference<CircuitBreakerStatus> apply(StatusDimension statusDimensi
7582
value.set(status);
7683
}
7784

85+
@Override
7886
public void setDetectResult(DetectResult detectResult) {
7987
this.detectResult.set(detectResult);
8088
}
8189

90+
@Override
91+
public InstanceStatistic getInstanceStatistic() {
92+
return this.instanceStatistic.get();
93+
}
94+
95+
@Override
96+
public void setInstanceStatistic(InstanceStatistic instanceStatistic) {
97+
this.instanceStatistic.set(instanceStatistic);
98+
}
8299
@Override
83100
public Object getPluginValue(int pluginId, Function<Integer, Object> create) {
84101
if (null == create) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Tencent is pleased to support the open source community by making polaris-java available.
3+
*
4+
* Copyright (C) 2021 Tencent. All rights reserved.
5+
*
6+
* Licensed under the BSD 3-Clause License (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://opensource.org/licenses/BSD-3-Clause
11+
*
12+
* Unless required by applicable law or agreed to in writing, software distributed
13+
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations under the License.
16+
*/
17+
18+
package com.tencent.polaris.discovery.client.stat;
19+
20+
import static com.tencent.polaris.api.exception.ErrorCode.INSTANCE_NOT_FOUND;
21+
import static com.tencent.polaris.api.exception.ErrorCode.SERVICE_NOT_FOUND;
22+
import static com.tencent.polaris.api.plugin.registry.InstanceProperty.PROPERTY_INSTANCE_STATISTIC;
23+
import static com.tencent.polaris.api.pojo.RetStatus.RetSuccess;
24+
25+
import com.tencent.polaris.api.exception.PolarisException;
26+
import com.tencent.polaris.api.plugin.registry.LocalRegistry;
27+
import com.tencent.polaris.api.plugin.registry.ResourceFilter;
28+
import com.tencent.polaris.api.pojo.Instance;
29+
import com.tencent.polaris.api.pojo.InstanceGauge;
30+
import com.tencent.polaris.api.pojo.InstanceStatistic;
31+
import com.tencent.polaris.api.pojo.ServiceEventKey;
32+
import com.tencent.polaris.api.pojo.ServiceEventKey.EventType;
33+
import com.tencent.polaris.api.pojo.ServiceInstances;
34+
import com.tencent.polaris.api.pojo.ServiceKey;
35+
import com.tencent.polaris.api.utils.CollectionUtils;
36+
import com.tencent.polaris.client.pojo.InstanceByProto;
37+
import com.tencent.polaris.logging.LoggerFactory;
38+
import java.util.List;
39+
import org.slf4j.Logger;
40+
41+
public class InstancesStatisticUpdater {
42+
43+
private static final Logger LOG = LoggerFactory.getLogger(InstancesDetectTask.class);
44+
45+
private final LocalRegistry localRegistry;
46+
47+
private static final String POLARIS_NAMESPACE = "Polaris";
48+
49+
public InstancesStatisticUpdater(LocalRegistry localRegistry) {
50+
this.localRegistry = localRegistry;
51+
}
52+
53+
public void updateInstanceStatistic(InstanceGauge result) throws PolarisException {
54+
ServiceKey serviceKey = new ServiceKey(result.getNamespace(), result.getService());
55+
ServiceEventKey serviceEventKey = new ServiceEventKey(serviceKey, EventType.INSTANCE);
56+
ServiceInstances serviceInstances = localRegistry.getInstances(new ResourceFilter(serviceEventKey, true, true));
57+
// 如果调用的是北极星内部的服务,则不统计
58+
if (serviceKey.getNamespace().equals(POLARIS_NAMESPACE)) {
59+
return;
60+
}
61+
if (serviceInstances == null) {
62+
LOG.warn("[InstanceStatisticUpdater]: " + "service: " + serviceKey.getService() + " in namespace: "
63+
+ serviceKey.getNamespace() + " not found");
64+
return;
65+
}
66+
if (CollectionUtils.isEmpty(serviceInstances.getInstances())) {
67+
LOG.warn("[InstanceStatisticUpdater]: " + "service: " + serviceKey.getService() + " in namespace: "
68+
+ serviceKey.getNamespace() + " has no instance");
69+
return;
70+
}
71+
72+
List<Instance> instances = serviceInstances.getInstances();
73+
InstanceByProto targetInstance = null;
74+
for (Instance instance : instances) {
75+
if (instance.getHost().equals(result.getHost()) && instance.getPort() == result.getPort()) {
76+
targetInstance = (InstanceByProto) instance;
77+
break;
78+
}
79+
}
80+
if (targetInstance != null) {
81+
InstanceStatistic instanceStatistic = targetInstance.getInstanceLocalValue().getInstanceStatistic();
82+
instanceStatistic.count(result.getDelay(), RetSuccess.equals(result.getRetStatus()));
83+
LOG.debug(
84+
"[InstanceStatisticUpdater]: " + targetInstance.getHost() + ":" + targetInstance.getPort() + ":"
85+
+ result.getPort() + ": Delay: " + result.getDelay() + "TotalCount"
86+
+ instanceStatistic.getTotalCount() + "TotalElapsed"
87+
+ instanceStatistic.getTotalElapsed());
88+
} else {
89+
LOG.warn("[InstanceStatisticUpdater]: " + result.getHost() + ":" + result.getPort() + ": not found");
90+
}
91+
}
92+
}

polaris-discovery/polaris-discovery-client/src/main/java/com/tencent/polaris/discovery/client/stat/ServiceCallResultCollector.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.tencent.polaris.api.config.consumer.OutlierDetectionConfig;
2121
import com.tencent.polaris.api.config.consumer.OutlierDetectionConfig.When;
22+
import com.tencent.polaris.api.plugin.registry.LocalRegistry;
2223
import com.tencent.polaris.api.pojo.InstanceGauge;
2324
import com.tencent.polaris.api.pojo.ServiceKey;
2425
import com.tencent.polaris.client.api.SDKContext;
@@ -48,12 +49,15 @@ public class ServiceCallResultCollector implements ServiceCallResultListener {
4849

4950
private Set<ServiceKey> calledServiceSet = new HashSet<>();
5051

52+
private InstancesStatisticUpdater instancesStatisticUpdater;
5153
@Override
5254
public synchronized void init(SDKContext sdkContext) {
5355
if (!state.compareAndSet(0, 1)) {
5456
return;
5557
}
5658
outlierDetectionConfig = sdkContext.getConfig().getConsumer().getOutlierDetection();
59+
LocalRegistry localRegistry = sdkContext.getExtensions().getLocalRegistry();
60+
instancesStatisticUpdater = new InstancesStatisticUpdater(localRegistry);
5761
if (outlierDetectionConfig.getWhen() != When.never) {
5862
detectTaskExecutors = Executors.newSingleThreadScheduledExecutor();
5963
long checkPeriodMs = outlierDetectionConfig.getCheckPeriod();
@@ -68,6 +72,7 @@ public void onServiceCallResult(InstanceGauge result) {
6872
if (outlierDetectionConfig.getWhen() == When.after_call) {
6973
calledServiceSet.add(new ServiceKey(result.getNamespace(), result.getService()));
7074
}
75+
instancesStatisticUpdater.updateInstanceStatistic(result);
7176
}
7277

7378
@Override

polaris-discovery/polaris-discovery-factory/pom.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@
100100
<artifactId>loadbalancer-roundrobin</artifactId>
101101
<version>${project.version}</version>
102102
</dependency>
103-
103+
<dependency>
104+
<groupId>com.tencent.polaris</groupId>
105+
<artifactId>loadbalancer-shortest-response-time</artifactId>
106+
<version>${project.version}</version>
107+
</dependency>
104108
<!-- 依赖节点级熔断插件-->
105109
<dependency>
106110
<groupId>com.tencent.polaris</groupId>

polaris-plugins/polaris-plugin-api/src/main/java/com/tencent/polaris/api/plugin/registry/InstanceProperty.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public class InstanceProperty {
3838
*/
3939
public static final String PROPERTY_DETECT_RESULT = "detectResult";
4040

41+
/**
42+
* 属性标签,标识实例统计信息
43+
*/
44+
public static final String PROPERTY_INSTANCE_STATISTIC = "instanceStatistic";
45+
4146
private final Instance instance;
4247

4348
private final Map<String, Object> properties;

polaris-plugins/polaris-plugins-loadbalancer/loadbalancer-ringhash/src/main/java/com/tencent/polaris/plugins/loadbalancer/ringhash/ConsistentHashLoadBalance.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.tencent.polaris.api.config.consumer.LoadBalanceConfig;
2121
import com.tencent.polaris.api.control.Destroyable;
22+
import com.tencent.polaris.api.exception.ErrorCode;
2223
import com.tencent.polaris.api.exception.PolarisException;
2324
import com.tencent.polaris.api.plugin.IdAwarePlugin;
2425
import com.tencent.polaris.api.plugin.PluginType;
@@ -55,8 +56,13 @@ public class ConsistentHashLoadBalance extends Destroyable implements LoadBalanc
5556

5657
@Override
5758
public Instance chooseInstance(Criteria criteria, ServiceInstances instances) throws PolarisException {
58-
if (instances == null || CollectionUtils.isEmpty(instances.getInstances())) {
59-
return null;
59+
if (instances == null) {
60+
throw new PolarisException(ErrorCode.INSTANCE_NOT_FOUND,
61+
"instances is null");
62+
}
63+
if (CollectionUtils.isEmpty(instances.getInstances())) {
64+
throw new PolarisException(ErrorCode.INSTANCE_NOT_FOUND,
65+
"no instance found, serviceKey: " + instances.getServiceKey());
6066
}
6167
TreeMap<Integer, Instance> ring = flowCache.loadPluginCacheObject(getId(), instances,
6268
obj -> buildConsistentHashRing((ServiceInstances) obj));

0 commit comments

Comments
 (0)