[ISSUE #10374] Support listener to get changed instances (#10905)

* For #10374, Support listener to get changed instances

* Update AbstractNamingChangeListener,NamingChaneEventTest

* Update InstancesDiffTest

* Rename NamingChangeEventTest
This commit is contained in:
Dale Lee 2023-08-07 09:29:05 +08:00 committed by GitHub
parent 1f5dbf0e79
commit c026d2f3ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 412 additions and 18 deletions

View File

@ -25,6 +25,7 @@ import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.monitor.MetricsMonitor;
import com.alibaba.nacos.client.naming.backups.FailoverReactor;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.client.naming.event.InstancesDiff;
import com.alibaba.nacos.common.lifecycle.Closeable;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.utils.ConvertUtils;
@ -160,16 +161,17 @@ public class ServiceInfoHolder implements Closeable {
return oldService;
}
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
boolean changed = isChangedServiceInfo(oldService, serviceInfo);
InstancesDiff diff = getServiceInfoDiff(oldService, serviceInfo);
if (StringUtils.isBlank(serviceInfo.getJsonFromServer())) {
serviceInfo.setJsonFromServer(JacksonUtils.toJson(serviceInfo));
}
MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
if (changed) {
if (diff.hasDifferent()) {
NAMING_LOGGER.info("current ips:({}) service: {} -> {}", serviceInfo.ipCount(), serviceInfo.getKey(),
JacksonUtils.toJson(serviceInfo.getHosts()));
NotifyCenter.publishEvent(new InstancesChangeEvent(notifierEventScope, serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), serviceInfo.getHosts()));
serviceInfo.getClusters(), serviceInfo.getHosts(), diff));
DiskCache.write(serviceInfo, cacheDir);
}
return serviceInfo;
@ -179,18 +181,20 @@ public class ServiceInfoHolder implements Closeable {
return null == serviceInfo.getHosts() || (pushEmptyProtection && !serviceInfo.validate());
}
private boolean isChangedServiceInfo(ServiceInfo oldService, ServiceInfo newService) {
private InstancesDiff getServiceInfoDiff(ServiceInfo oldService, ServiceInfo newService) {
InstancesDiff instancesDiff = new InstancesDiff();
if (null == oldService) {
NAMING_LOGGER.info("init new ips({}) service: {} -> {}", newService.ipCount(), newService.getKey(),
JacksonUtils.toJson(newService.getHosts()));
return true;
instancesDiff.setAddedInstances(newService.getHosts());
return instancesDiff;
}
if (oldService.getLastRefTime() > newService.getLastRefTime()) {
NAMING_LOGGER.warn("out of date data received, old-t: {}, new-t: {}", oldService.getLastRefTime(),
newService.getLastRefTime());
return false;
return instancesDiff;
}
boolean changed = false;
Map<String, Instance> oldHostMap = new HashMap<>(oldService.getHosts().size());
for (Instance host : oldService.getHosts()) {
oldHostMap.put(host.toInetAddr(), host);
@ -231,23 +235,23 @@ public class ServiceInfoHolder implements Closeable {
}
if (newHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("new ips({}) service: {} -> {}", newHosts.size(), newService.getKey(),
JacksonUtils.toJson(newHosts));
instancesDiff.setAddedInstances(newHosts);
}
if (remvHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("removed ips({}) service: {} -> {}", remvHosts.size(), newService.getKey(),
JacksonUtils.toJson(remvHosts));
instancesDiff.setRemovedInstances(remvHosts);
}
if (modHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("modified ips({}) service: {} -> {}", modHosts.size(), newService.getKey(),
JacksonUtils.toJson(modHosts));
instancesDiff.setModifiedInstances(modHosts);
}
return changed;
return instancesDiff;
}
@Override

View File

@ -41,12 +41,15 @@ public class InstancesChangeEvent extends Event {
private final List<Instance> hosts;
public InstancesChangeEvent(String eventScope, String serviceName, String groupName, String clusters, List<Instance> hosts) {
private InstancesDiff instancesDiff;
public InstancesChangeEvent(String eventScope, String serviceName, String groupName, String clusters, List<Instance> hosts, InstancesDiff diff) {
this.eventScope = eventScope;
this.serviceName = serviceName;
this.groupName = groupName;
this.clusters = clusters;
this.hosts = hosts;
this.instancesDiff = diff;
}
public String getServiceName() {
@ -65,6 +68,10 @@ public class InstancesChangeEvent extends Event {
return hosts;
}
public InstancesDiff getInstancesDiff() {
return instancesDiff;
}
@Override
public String scope() {
return this.eventScope;

View File

@ -18,9 +18,9 @@ package com.alibaba.nacos.client.naming.event;
import com.alibaba.nacos.api.naming.listener.AbstractEventListener;
import com.alibaba.nacos.api.naming.listener.EventListener;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.client.naming.listener.NamingChangeEvent;
import com.alibaba.nacos.common.JustForTest;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.listener.Subscriber;
@ -130,8 +130,8 @@ public class InstancesChangeNotifier extends Subscriber<InstancesChangeEvent> {
private com.alibaba.nacos.api.naming.listener.Event transferToNamingEvent(
InstancesChangeEvent instancesChangeEvent) {
return new NamingEvent(instancesChangeEvent.getServiceName(), instancesChangeEvent.getGroupName(),
instancesChangeEvent.getClusters(), instancesChangeEvent.getHosts());
return new NamingChangeEvent(instancesChangeEvent.getServiceName(), instancesChangeEvent.getGroupName(),
instancesChangeEvent.getClusters(), instancesChangeEvent.getHosts(), instancesChangeEvent.getInstancesDiff());
}
@Override

View File

@ -0,0 +1,80 @@
/*
* Copyright 1999-2023 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.client.naming.event;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.common.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* The differences in instances compared to the last callback.
*
* @author lideyou
*/
public class InstancesDiff {
private final List<Instance> addedInstances = new ArrayList<>();
private final List<Instance> removedInstances = new ArrayList<>();
private final List<Instance> modifiedInstances = new ArrayList<>();
public List<Instance> getAddedInstances() {
return addedInstances;
}
public void setAddedInstances(Collection<Instance> addedInstances) {
this.addedInstances.clear();
if (CollectionUtils.isNotEmpty(addedInstances)) {
this.addedInstances.addAll(addedInstances);
}
}
public List<Instance> getRemovedInstances() {
return removedInstances;
}
public void setRemovedInstances(Collection<Instance> removedInstances) {
this.removedInstances.clear();
if (CollectionUtils.isNotEmpty(removedInstances)) {
this.removedInstances.addAll(removedInstances);
}
}
public List<Instance> getModifiedInstances() {
return modifiedInstances;
}
public void setModifiedInstances(Collection<Instance> modifiedInstances) {
this.modifiedInstances.clear();
if (CollectionUtils.isNotEmpty(modifiedInstances)) {
this.modifiedInstances.addAll(modifiedInstances);
}
}
/**
* Check if any instances have changed.
* @return true if there are instances that have changed
*/
public boolean hasDifferent() {
return CollectionUtils.isNotEmpty(this.addedInstances)
|| CollectionUtils.isNotEmpty(this.removedInstances)
|| CollectionUtils.isNotEmpty(this.modifiedInstances);
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 1999-2023 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.client.naming.listener;
import com.alibaba.nacos.api.naming.listener.AbstractEventListener;
import com.alibaba.nacos.api.naming.listener.Event;
/**
* Listener for NamingChangeEvent.
*
* @author lideyou
*/
public abstract class AbstractNamingChangeListener extends AbstractEventListener {
@Override
public final void onEvent(Event event) {
if (event instanceof NamingChangeEvent) {
onChange((NamingChangeEvent) event);
}
}
/**
* Callback when instances have changed.
* @param event NamingChangeEvent
*/
public abstract void onChange(NamingChangeEvent event);
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 1999-2023 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.client.naming.listener;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.event.InstancesDiff;
import com.alibaba.nacos.common.utils.CollectionUtils;
import java.util.List;
/**
* Naming Event with instance change information.
*
* @author lideyou
*/
public class NamingChangeEvent extends NamingEvent {
private final InstancesDiff instancesDiff;
public NamingChangeEvent(String serviceName, List<Instance> instances, InstancesDiff instancesDiff) {
super(serviceName, instances);
this.instancesDiff = instancesDiff;
}
public NamingChangeEvent(String serviceName, String groupName, String clusters, List<Instance> instances, InstancesDiff instancesDiff) {
super(serviceName, groupName, clusters, instances);
this.instancesDiff = instancesDiff;
}
public boolean isAdded() {
return CollectionUtils.isNotEmpty(this.instancesDiff.getAddedInstances());
}
public boolean isRemoved() {
return CollectionUtils.isNotEmpty(this.instancesDiff.getRemovedInstances());
}
public boolean isModified() {
return CollectionUtils.isNotEmpty(this.instancesDiff.getModifiedInstances());
}
public List<Instance> getAddedInstances() {
return this.instancesDiff.getAddedInstances();
}
public List<Instance> getRemovedInstances() {
return this.instancesDiff.getRemovedInstances();
}
public List<Instance> getModifiedInstances() {
return this.instancesDiff.getModifiedInstances();
}
}

View File

@ -34,7 +34,9 @@ public class InstancesChangeEventTest {
List<Instance> hosts = new ArrayList<>();
Instance ins = new Instance();
hosts.add(ins);
InstancesChangeEvent event = new InstancesChangeEvent(eventScope, serviceName, groupName, clusters, hosts);
InstancesDiff diff = new InstancesDiff();
diff.setAddedInstances(hosts);
InstancesChangeEvent event = new InstancesChangeEvent(eventScope, serviceName, groupName, clusters, hosts, diff);
Assert.assertEquals(eventScope, event.scope());
Assert.assertEquals(serviceName, event.getServiceName());
Assert.assertEquals(clusters, event.getClusters());
@ -42,5 +44,11 @@ public class InstancesChangeEventTest {
List<Instance> hosts1 = event.getHosts();
Assert.assertEquals(hosts.size(), hosts1.size());
Assert.assertEquals(hosts.get(0), hosts1.get(0));
InstancesDiff diff1 = event.getInstancesDiff();
Assert.assertTrue(diff1.hasDifferent());
Assert.assertEquals(diff.getAddedInstances().size(), diff1.getAddedInstances().size());
Assert.assertEquals(diff.getAddedInstances().get(0), diff.getAddedInstances().get(0));
Assert.assertEquals(diff.getRemovedInstances().size(), diff1.getRemovedInstances().size());
Assert.assertEquals(diff.getModifiedInstances().size(), diff1.getModifiedInstances().size());
}
}

View File

@ -49,7 +49,9 @@ public class InstancesChangeNotifierTest {
List<Instance> hosts = new ArrayList<>();
Instance ins = new Instance();
hosts.add(ins);
InstancesChangeEvent event = new InstancesChangeEvent(eventScope, name, group, clusters, hosts);
InstancesDiff diff = new InstancesDiff();
diff.setAddedInstances(hosts);
InstancesChangeEvent event = new InstancesChangeEvent(eventScope, name, group, clusters, hosts, diff);
Assert.assertEquals(true, instancesChangeNotifier.scopeMatches(event));
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 1999-2023 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.client.naming.event;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.builder.InstanceBuilder;
import org.junit.Assert;
import org.junit.Test;
import java.util.Collections;
public class InstancesDiffTest {
@Test
public void testGetDiff() {
String serviceName = "testService";
Instance addedIns = InstanceBuilder.newBuilder()
.setServiceName(serviceName)
.setClusterName("a").build();
Instance removedIns = InstanceBuilder.newBuilder()
.setServiceName(serviceName)
.setClusterName("b").build();
Instance modifiedIns = InstanceBuilder.newBuilder()
.setServiceName(serviceName)
.setClusterName("c").build();
InstancesDiff instancesDiff = new InstancesDiff();
instancesDiff.setAddedInstances(Collections.singletonList(addedIns));
instancesDiff.setRemovedInstances(Collections.singletonList(removedIns));
instancesDiff.setModifiedInstances(Collections.singletonList(modifiedIns));
Assert.assertTrue(instancesDiff.hasDifferent());
Assert.assertEquals(addedIns, instancesDiff.getAddedInstances().get(0));
Assert.assertEquals(removedIns, instancesDiff.getRemovedInstances().get(0));
Assert.assertEquals(modifiedIns, instancesDiff.getModifiedInstances().get(0));
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright 1999-2023 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.client.naming.listener;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.event.InstancesDiff;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
public class NamingChangeEventTest {
private MockNamingEventListener eventListener;
private InstancesDiff instancesDiff;
@Before
public void setUp() throws Exception {
eventListener = new MockNamingEventListener();
instancesDiff = new InstancesDiff();
instancesDiff.setAddedInstances(Arrays.asList(new Instance(), new Instance(), new Instance()));
instancesDiff.setRemovedInstances(Arrays.asList(new Instance(), new Instance()));
instancesDiff.setModifiedInstances(Arrays.asList(new Instance()));
}
@Test
public void testNamingChangeEventWithSimpleConstructor() {
NamingChangeEvent event = new NamingChangeEvent("serviceName", Collections.EMPTY_LIST, instancesDiff);
assertEquals("serviceName", event.getServiceName());
assertNull(event.getGroupName());
assertNull(event.getClusters());
assertTrue(event.getInstances().isEmpty());
assertTrue(event.isAdded());
assertEquals(3, event.getAddedInstances().size());
assertTrue(event.isRemoved());
assertEquals(2, event.getRemovedInstances().size());
assertTrue(event.isModified());
assertEquals(1, event.getModifiedInstances().size());
eventListener.onEvent(event);
assertNull(event.getServiceName());
assertNull(event.getGroupName());
assertNull(event.getClusters());
assertNull(event.getInstances());
assertFalse(event.isAdded());
assertEquals(0, event.getAddedInstances().size());
assertFalse(event.isRemoved());
assertEquals(0, event.getRemovedInstances().size());
assertFalse(event.isModified());
assertEquals(0, event.getRemovedInstances().size());
}
@Test
public void testNamingChangeEventWithFullConstructor() {
NamingChangeEvent event = new NamingChangeEvent("serviceName", "group", "clusters", Collections.EMPTY_LIST, instancesDiff);
assertEquals("serviceName", event.getServiceName());
assertEquals("group", event.getGroupName());
assertEquals("clusters", event.getClusters());
assertTrue(event.getInstances().isEmpty());
assertTrue(event.isAdded());
assertEquals(3, event.getAddedInstances().size());
assertTrue(event.isRemoved());
assertEquals(2, event.getRemovedInstances().size());
assertTrue(event.isModified());
assertEquals(1, event.getModifiedInstances().size());
eventListener.onEvent(event);
assertNull(event.getServiceName());
assertNull(event.getGroupName());
assertNull(event.getClusters());
assertNull(event.getInstances());
assertFalse(event.isAdded());
assertEquals(0, event.getAddedInstances().size());
assertFalse(event.isRemoved());
assertEquals(0, event.getRemovedInstances().size());
assertFalse(event.isModified());
assertEquals(0, event.getRemovedInstances().size());
}
@Test
public void testGetChanges() {
NamingChangeEvent event = new NamingChangeEvent("serviceName", Collections.EMPTY_LIST, instancesDiff);
assertTrue(event.isAdded());
assertEquals(3, event.getAddedInstances().size());
event.getAddedInstances().clear();
assertFalse(event.isAdded());
assertEquals(0, event.getAddedInstances().size());
assertTrue(event.isRemoved());
assertEquals(2, event.getRemovedInstances().size());
event.getRemovedInstances().clear();
assertFalse(event.isRemoved());
assertEquals(0, event.getRemovedInstances().size());
assertTrue(event.isModified());
assertEquals(1, event.getModifiedInstances().size());
event.getModifiedInstances().clear();
assertFalse(event.isModified());
assertEquals(0, event.getRemovedInstances().size());
}
private static class MockNamingEventListener extends AbstractNamingChangeListener {
@Override
public void onChange(NamingChangeEvent event) {
assertNull(getExecutor());
event.setServiceName(null);
event.setGroupName(null);
event.setClusters(null);
event.setInstances(null);
event.getAddedInstances().clear();
event.getRemovedInstances().clear();
event.getModifiedInstances().clear();
}
}
}