Skip to content

Commit 71b0003

Browse files
authored
[SCB-2807]add new instance isolation feature based on instance-isolation-consumer (#3932)
1 parent 49da1b6 commit 71b0003

File tree

8 files changed

+185
-21
lines changed

8 files changed

+185
-21
lines changed

handlers/handler-governance/src/main/java/org/apache/servicecomb/handler/governance/ConsumerInstanceIsolationHandler.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,21 @@
1717

1818
package org.apache.servicecomb.handler.governance;
1919

20+
import java.time.Duration;
2021
import java.util.concurrent.CompletableFuture;
2122
import java.util.concurrent.CompletionStage;
2223
import java.util.function.Supplier;
2324

25+
import javax.ws.rs.core.Response.Status;
26+
2427
import org.apache.servicecomb.core.Handler;
2528
import org.apache.servicecomb.core.Invocation;
2629
import org.apache.servicecomb.core.governance.MatchType;
30+
import org.apache.servicecomb.foundation.common.event.EventManager;
2731
import org.apache.servicecomb.foundation.common.utils.BeanUtils;
2832
import org.apache.servicecomb.governance.handler.InstanceIsolationHandler;
2933
import org.apache.servicecomb.governance.marker.GovernanceRequestExtractor;
30-
import org.apache.servicecomb.registry.api.MicroserviceKey;
31-
import org.apache.servicecomb.registry.api.event.MicroserviceInstanceChangedEvent;
32-
import org.apache.servicecomb.registry.api.event.ServiceCenterEventBus;
34+
import org.apache.servicecomb.governance.policy.CircuitBreakerPolicy;
3335
import org.apache.servicecomb.swagger.invocation.AsyncResponse;
3436
import org.apache.servicecomb.swagger.invocation.Response;
3537
import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
@@ -57,6 +59,12 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except
5759
DecorateCompletionStage<Response> dcs = Decorators.ofCompletionStage(next);
5860
GovernanceRequestExtractor request = MatchType.createGovHttpRequest(invocation);
5961

62+
CircuitBreakerPolicy circuitBreakerPolicy = instanceIsolationHandler.matchPolicy(request);
63+
if (circuitBreakerPolicy != null && circuitBreakerPolicy.isForceOpen()) {
64+
asyncResp.consumerFail(new InvocationException(Status.SERVICE_UNAVAILABLE,
65+
"Policy " + circuitBreakerPolicy.getName() + " forced open and deny requests"));
66+
return;
67+
}
6068
addCircuitBreaker(dcs, request);
6169

6270
dcs.get().whenComplete((r, e) -> {
@@ -67,7 +75,7 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except
6775

6876
if (e instanceof CallNotPermittedException) {
6977
LOGGER.warn("instance isolation circuitBreaker is open by policy : {}", e.getMessage());
70-
ServiceCenterEventBus.getEventBus().post(createMicroserviceInstanceChangedEvent(invocation));
78+
EventManager.post(createInstanceIsolatedEvent(circuitBreakerPolicy, request));
7179
// return 503 so that consumer can retry
7280
asyncResp.complete(
7381
Response.failResp(new InvocationException(503, "instance isolation circuitBreaker is open.",
@@ -78,13 +86,10 @@ public void handle(Invocation invocation, AsyncResponse asyncResp) throws Except
7886
});
7987
}
8088

81-
private Object createMicroserviceInstanceChangedEvent(Invocation invocation) {
82-
MicroserviceInstanceChangedEvent event = new MicroserviceInstanceChangedEvent();
83-
MicroserviceKey key = new MicroserviceKey();
84-
key.setAppId(invocation.getAppId());
85-
key.setServiceName(invocation.getMicroserviceName());
86-
event.setKey(key);
87-
return event;
89+
private Object createInstanceIsolatedEvent(CircuitBreakerPolicy circuitBreakerPolicy,
90+
GovernanceRequestExtractor requestExtractor) {
91+
return new InstanceIsolatedEvent(requestExtractor.instanceId(),
92+
Duration.parse(circuitBreakerPolicy.getWaitDurationInOpenState()));
8893
}
8994

9095
private void addCircuitBreaker(DecorateCompletionStage<Response> dcs, GovernanceRequestExtractor request) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.servicecomb.handler.governance;
18+
19+
import java.time.Duration;
20+
21+
public class InstanceIsolatedEvent {
22+
private final String instanceId;
23+
24+
private final Duration waitDurationInHalfOpenState;
25+
26+
public InstanceIsolatedEvent(String instanceId, Duration waitDurationInHalfOpenState) {
27+
this.instanceId = instanceId;
28+
this.waitDurationInHalfOpenState = waitDurationInHalfOpenState;
29+
}
30+
31+
public String getInstanceId() {
32+
return instanceId;
33+
}
34+
35+
public Duration getWaitDurationInHalfOpenState() {
36+
return waitDurationInHalfOpenState;
37+
}
38+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.servicecomb.handler.governance;
19+
20+
import java.util.HashMap;
21+
import java.util.Iterator;
22+
import java.util.Map;
23+
import java.util.Map.Entry;
24+
25+
import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
26+
import org.apache.servicecomb.foundation.common.event.EventManager;
27+
import org.apache.servicecomb.registry.api.registry.MicroserviceInstance;
28+
import org.apache.servicecomb.registry.discovery.DiscoveryContext;
29+
import org.apache.servicecomb.registry.discovery.DiscoveryFilter;
30+
import org.apache.servicecomb.registry.discovery.DiscoveryTreeNode;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
33+
34+
import com.google.common.eventbus.Subscribe;
35+
import com.netflix.config.DynamicPropertyFactory;
36+
37+
public class InstanceIsolationDiscoveryFilter implements DiscoveryFilter {
38+
private static final Logger LOGGER = LoggerFactory.getLogger(InstanceIsolationDiscoveryFilter.class);
39+
40+
private static final String KEY_ISOLATED = "isolated";
41+
42+
private final Object lock = new Object();
43+
44+
private final Map<String, Long> isolatedInstances = new ConcurrentHashMapEx<>();
45+
46+
public InstanceIsolationDiscoveryFilter() {
47+
EventManager.register(this);
48+
}
49+
50+
@Subscribe
51+
public void onInstanceIsolatedEvent(InstanceIsolatedEvent event) {
52+
synchronized (lock) {
53+
for (Iterator<String> iterator = isolatedInstances.keySet().iterator(); iterator.hasNext(); ) {
54+
Long duration = isolatedInstances.get(iterator.next());
55+
if (System.currentTimeMillis() - duration > 0) {
56+
iterator.remove();
57+
}
58+
}
59+
60+
isolatedInstances.put(event.getInstanceId(),
61+
System.currentTimeMillis() + event.getWaitDurationInHalfOpenState().toMillis());
62+
LOGGER.info("isolate instance {} for {}ms", event.getInstanceId(),
63+
event.getWaitDurationInHalfOpenState().toMillis());
64+
}
65+
}
66+
67+
@Override
68+
public boolean enabled() {
69+
return DynamicPropertyFactory.getInstance().getBooleanProperty(
70+
"servicecomb.loadbalance.filter.instance.isolation.enabled", true).get();
71+
}
72+
73+
@Override
74+
public int getOrder() {
75+
return Short.MAX_VALUE - 1;
76+
}
77+
78+
@Override
79+
public boolean isGroupingFilter() {
80+
return true;
81+
}
82+
83+
@Override
84+
public DiscoveryTreeNode discovery(DiscoveryContext context, DiscoveryTreeNode parent) {
85+
Map<String, MicroserviceInstance> instances = parent.data();
86+
if (isolatedInstances.isEmpty() || instances.isEmpty()) {
87+
return parent;
88+
}
89+
90+
boolean changed = false;
91+
Map<String, MicroserviceInstance> result = new HashMap<>(instances.size());
92+
for (Entry<String, MicroserviceInstance> item : instances.entrySet()) {
93+
Long duration = isolatedInstances.get(item.getKey());
94+
if (duration == null) {
95+
result.put(item.getKey(), item.getValue());
96+
continue;
97+
}
98+
99+
if (System.currentTimeMillis() - duration < 0) {
100+
changed = true;
101+
continue;
102+
}
103+
104+
synchronized (lock) {
105+
isolatedInstances.remove(item.getKey());
106+
LOGGER.info("try to recover instance {}", item.getKey());
107+
}
108+
result.put(item.getKey(), item.getValue());
109+
}
110+
111+
if (!changed || result.size() == 0) {
112+
return parent;
113+
}
114+
115+
// Create new child. And all later DiscoveryFilter will re-calculate based on this result.
116+
DiscoveryTreeNode child = new DiscoveryTreeNode().subName(parent, KEY_ISOLATED).data(result);
117+
parent.child(KEY_ISOLATED, child);
118+
return child;
119+
}
120+
}

handlers/handler-governance/src/main/resources/META-INF/services/org.apache.servicecomb.registry.discovery.DiscoveryFilter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
# limitations under the License.
1616
#
1717

18+
org.apache.servicecomb.handler.governance.InstanceIsolationDiscoveryFilter

handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/event/IsolationServerEvent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import org.apache.servicecomb.core.Invocation;
2121
import org.apache.servicecomb.foundation.common.event.AlarmEvent;
2222
import org.apache.servicecomb.loadbalance.ServiceCombServerStats;
23-
import org.apache.servicecomb.loadbalance.filterext.IsolationDiscoveryFilter;
23+
import org.apache.servicecomb.loadbalance.filterext.IsolationServerListFilterExt;
2424
import org.apache.servicecomb.registry.api.registry.MicroserviceInstance;
2525

2626
public class IsolationServerEvent extends AlarmEvent {
@@ -52,7 +52,7 @@ public class IsolationServerEvent extends AlarmEvent {
5252

5353
public IsolationServerEvent(Invocation invocation, MicroserviceInstance instance,
5454
ServiceCombServerStats serverStats,
55-
IsolationDiscoveryFilter.Settings settings, Type type, Endpoint endpoint) {
55+
IsolationServerListFilterExt.Settings settings, Type type, Endpoint endpoint) {
5656
super(type);
5757
this.microserviceName = invocation.getMicroserviceName();
5858
this.endpoint = endpoint;

handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationDiscoveryFilter.java renamed to handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/filterext/IsolationServerListFilterExt.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
/**
4040
* Isolate instances by error metrics
4141
*/
42-
public class IsolationDiscoveryFilter implements ServerListFilterExt {
42+
public class IsolationServerListFilterExt implements ServerListFilterExt {
4343

44-
private static final Logger LOGGER = LoggerFactory.getLogger(IsolationDiscoveryFilter.class);
44+
private static final Logger LOGGER = LoggerFactory.getLogger(IsolationServerListFilterExt.class);
4545

4646
private final DynamicBooleanProperty emptyProtection = DynamicPropertyFactory.getInstance()
4747
.getBooleanProperty(EMPTY_INSTANCE_PROTECTION, false);
@@ -65,7 +65,7 @@ public int getOrder() {
6565
return ORDER_ISOLATION;
6666
}
6767

68-
public IsolationDiscoveryFilter() {
68+
public IsolationServerListFilterExt() {
6969
emptyProtection.addCallback(() -> {
7070
boolean newValue = emptyProtection.get();
7171
LOGGER.info("{} changed from {} to {}", EMPTY_INSTANCE_PROTECTION, emptyProtection, newValue);

handlers/handler-loadbalance/src/main/resources/META-INF/services/org.apache.servicecomb.loadbalance.ServerListFilterExt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
# limitations under the License.
1616
#
1717

18-
org.apache.servicecomb.loadbalance.filterext.IsolationDiscoveryFilter
18+
org.apache.servicecomb.loadbalance.filterext.IsolationServerListFilterExt
1919
org.apache.servicecomb.loadbalance.filterext.ZoneAwareDiscoveryFilter

handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationDiscoveryFilterTest.java renamed to handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/filter/IsolationServerListFilterExtTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import org.apache.servicecomb.loadbalance.ServiceCombServer;
2929
import org.apache.servicecomb.loadbalance.ServiceCombServerStats;
3030
import org.apache.servicecomb.loadbalance.TestServiceCombServerStats;
31-
import org.apache.servicecomb.loadbalance.filterext.IsolationDiscoveryFilter;
31+
import org.apache.servicecomb.loadbalance.filterext.IsolationServerListFilterExt;
3232
import org.apache.servicecomb.registry.api.registry.MicroserviceInstance;
3333
import org.apache.servicecomb.registry.cache.CacheEndpoint;
3434
import org.junit.jupiter.api.AfterEach;
@@ -42,9 +42,9 @@
4242
import mockit.Mocked;
4343

4444
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
45-
public class IsolationDiscoveryFilterTest {
45+
public class IsolationServerListFilterExtTest {
4646

47-
private IsolationDiscoveryFilter filter;
47+
private IsolationServerListFilterExt filter;
4848

4949
private List<ServiceCombServer> servers;
5050

@@ -73,7 +73,7 @@ public void before() {
7373
ServiceCombLoadBalancerStats.INSTANCE.getServiceCombServerStats(serviceCombServer);
7474
}
7575

76-
filter = new IsolationDiscoveryFilter();
76+
filter = new IsolationServerListFilterExt();
7777
TestServiceCombServerStats.releaseTryingChance();
7878
}
7979

0 commit comments

Comments
 (0)