diff --git a/README.md b/README.md index c085617b..49aad9ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Govern Service On Redis (Service Discovery and Configuration Service) +> [中文文档](./README.zh-CN.md) + *Govern Service* is a lightweight, low-cost service registration, service discovery, and configuration service SDK. By using Redis in the existing infrastructure (I believe you have already deployed Redis), it doesn’t need to bring extra to the operation and maintenance deployment. Cost and burden. With the high performance of Redis, *Govern Service* @@ -11,7 +13,7 @@ process cache refresh, with unparalleled QPS performance and real-time consisten ### Gradle ``` kotlin - val governVersion = "0.8.0"; + val governVersion = "0.8.2"; implementation("me.ahoo.govern:spring-cloud-starter-config:${governVersion}") implementation("me.ahoo.govern:spring-cloud-starter-discovery:${governVersion}") ``` @@ -28,7 +30,7 @@ process cache refresh, with unparalleled QPS performance and real-time consisten 4.0.0 demo - 0.8.0 + 0.8.2 diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 00000000..64350ae6 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,173 @@ +# 基于 Redis 的服务治理平台(服务注册/发现 & 配置中心) + +*Govern Service* 是一个轻量级、低成本的服务注册、服务发现、 配置服务 SDK,通过使用现有基础设施中的 Redis (相信你已经部署了Redis),不用给运维部署带来额外的成本与负担。 借助于 Redis 的高性能, * +Govern Service* 提供了超高TPS&QPS。*Govern Service* 结合本地进程缓存策略 + *Redis PubSub*,实现实时进程缓存刷新,兼具无与伦比的QPS性能、进程缓存与 Redis 的实时一致性。 + +## 安装 + +### Gradle + +``` kotlin + val governVersion = "0.8.2"; + implementation("me.ahoo.govern:spring-cloud-starter-config:${governVersion}") + implementation("me.ahoo.govern:spring-cloud-starter-discovery:${governVersion}") +``` + +### Maven + +```xml + + + + + 4.0.0 + demo + + 0.8.2 + + + + + me.ahoo.govern + spring-cloud-starter-config + ${govern.version} + + + me.ahoo.govern + spring-cloud-starter-discovery + ${govern.version} + + + + +``` + +### bootstrap.yml (Spring-Cloud-Config) + +```yaml +spring: + application: + name: govern-rest-api + cloud: + govern: + namespace: dev + config: + config-id: ${spring.application.name}.yml + redis: + mode: standalone + url: redis://localhost:6379 +``` + +## REST-API Server (``Optional``) + +```shell +bin/rest-api +``` + +> http://localhost:8080/swagger-ui/index.html#/ + +### Namespace + +![rest-api-namespace](./docs/rest-api-namespace.png) + +- /v1/namespaces + - GET +- /v1/namespaces/{namespace} + - PUT + - GET +- /v1/namespaces/current + - GET +- /v1/namespaces/current/{namespace} + - PUT + +### Config + +![rest-api-config](./docs/rest-api-config.png) + +- /v1/namespaces/{namespace}/configs + - GET +- /v1/namespaces/{namespace}/configs/{configId} + - GET + - PUT + - DELETE +- /v1/namespaces/{namespace}/configs/{configId}/versions + - GET +- /v1/namespaces/{namespace}/configs/{configId}/versions/{version} + - GET +- /v1/namespaces/{namespace}/configs/{configId}/to/{targetVersion} + - PUT + +### Service + +![rest-api-service](./docs/rest-api-service.png) + +- /v1/namespaces/{namespace}/services/ + - GET +- /v1/namespaces/{namespace}/services/{serviceId}/instances + - GET + - PUT +- /v1/namespaces/{namespace}/services/{serviceId}/instances/{instanceId} + - DELETE +- /v1/namespaces/{namespace}/services/{serviceId}/instances/{instanceId}/metadata + - PUT + +## JMH 基准测试 + +- The development notebook : MacBook Pro (M1) +- All benchmark tests are carried out on the development notebook. +- Deploying Redis with docker on the development notebook. + +``` shell +gradle jmh +``` + +### ConfigService + +``` +# JMH version: 1.28 +# VM version: JDK 11.0.11, OpenJDK 64-Bit Server VM, 11.0.11+9-LTS +# VM invoker: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java +# VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/ahoo/govern-service/config/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant +# Blackhole mode: full + dont-inline hint +# Warmup: 1 iterations, 10 s each +# Measurement: 1 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 50 threads, will synchronize iterations +# Benchmark mode: Throughput, ops/time + +Benchmark Mode Cnt Score Error Units +ConsistencyRedisConfigServiceBenchmark.getConfig thrpt 555275866.836 ops/s +RedisConfigServiceBenchmark.getConfig thrpt 57397.188 ops/s +RedisConfigServiceBenchmark.setConfig thrpt 56882.673 ops/s +``` + +### ServiceDiscovery + +``` +# JMH version: 1.29 +# VM version: JDK 11.0.11, OpenJDK 64-Bit Server VM, 11.0.11+9-LTS +# VM invoker: /Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java +# VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/ahoo/work/ahoo-git/govern-service/discovery/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant +# Blackhole mode: full + dont-inline hint +# Warmup: 1 iterations, 10 s each +# Measurement: 1 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 50 threads, will synchronize iterations +# Benchmark mode: Throughput, ops/time + +Benchmark Mode Cnt Score Error Units +ConsistencyRedisServiceDiscoveryBenchmark.getInstances thrpt 567329996.255 ops/s +ConsistencyRedisServiceDiscoveryBenchmark.getServices thrpt 1929377291.635 ops/s +RedisServiceDiscoveryBenchmark.getInstances thrpt 43760.035 ops/s +RedisServiceDiscoveryBenchmark.getServices thrpt 60953.971 ops/s +RedisServiceRegistryBenchmark.deregister thrpt 63133.011 ops/s +RedisServiceRegistryBenchmark.register thrpt 53957.797 ops/s +RedisServiceRegistryBenchmark.renew thrpt 67116.116 ops/s +``` + +## TODO + +1. Dashboard +2. Grayscale Publishing diff --git a/core/src/test/java/me/ahoo/govern/core/util/ScriptTest.java b/core/src/test/java/me/ahoo/govern/core/util/ScriptTest.java new file mode 100644 index 00000000..ef78a5cd --- /dev/null +++ b/core/src/test/java/me/ahoo/govern/core/util/ScriptTest.java @@ -0,0 +1,74 @@ +package me.ahoo.govern.core.util; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.ScriptOutputType; +import io.lettuce.core.api.sync.RedisCommands; +import me.ahoo.govern.core.TestRedisClient; +import org.junit.jupiter.api.*; + +import java.util.Objects; + +/** + * @author ahoo wang + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ScriptTest { + private RedisClient redisClient; + private RedisCommands redisCommands; + + @BeforeAll + private void init() { + redisClient = TestRedisClient.createClient(); + redisCommands = redisClient.connect().sync(); + } + + @Test + void testReturnBoolean() { + Boolean negativeOne = redisCommands.eval("return -1;", ScriptOutputType.BOOLEAN); + Assertions.assertFalse(negativeOne); + Boolean zero = redisCommands.eval("return 0;", ScriptOutputType.BOOLEAN); + Assertions.assertFalse(zero); + Boolean one = redisCommands.eval("return 1;", ScriptOutputType.BOOLEAN); + Assertions.assertTrue(one); + Boolean two = redisCommands.eval("return 2;", ScriptOutputType.BOOLEAN); + Assertions.assertFalse(two); + + Boolean fa = redisCommands.eval("return false;", ScriptOutputType.BOOLEAN); + Assertions.assertFalse(fa); + Boolean tr = redisCommands.eval("return true;", ScriptOutputType.BOOLEAN); + Assertions.assertTrue(tr); + } + + @Test + void testReturnLong() { + Long negativeOne = redisCommands.eval("return -1;", ScriptOutputType.INTEGER); + Assertions.assertEquals(-1, negativeOne); + Long zero = redisCommands.eval("return 0;", ScriptOutputType.INTEGER); + Assertions.assertEquals(0, zero); + Long one = redisCommands.eval("return 1;", ScriptOutputType.INTEGER); + Assertions.assertEquals(1, one); + Long two = redisCommands.eval("return 2;", ScriptOutputType.INTEGER); + Assertions.assertEquals(2, two); + } + + @Test + void testReturnStatus() { + Object status = redisCommands.eval("return 'ok';", ScriptOutputType.STATUS); + + Assertions.assertEquals("ok", status); + } + + @Test + void testReturnValue() { + Object val = redisCommands.eval("return 'ok';", ScriptOutputType.VALUE); + + Assertions.assertEquals("ok", val); + } + + @AfterAll + private void destroy() { + if (Objects.nonNull(redisClient)) { + redisClient.shutdown(); + } + } +} diff --git a/discovery/src/main/java/me/ahoo/govern/discovery/ServiceDiscovery.java b/discovery/src/main/java/me/ahoo/govern/discovery/ServiceDiscovery.java index 377585d1..384e783d 100644 --- a/discovery/src/main/java/me/ahoo/govern/discovery/ServiceDiscovery.java +++ b/discovery/src/main/java/me/ahoo/govern/discovery/ServiceDiscovery.java @@ -1,6 +1,3 @@ -/* - * This Java source file was generated by the Gradle 'init' task. - */ package me.ahoo.govern.discovery; import java.util.List; @@ -22,6 +19,6 @@ public interface ServiceDiscovery { CompletableFuture getInstance(String namespace, String serviceId, String instanceId); - CompletableFuture getInstanceTtl(String namespace, String serviceId, String instanceId); + CompletableFuture getInstanceTtl(String namespace, String serviceId, String instanceId); } diff --git a/discovery/src/main/java/me/ahoo/govern/discovery/ServiceInstance.java b/discovery/src/main/java/me/ahoo/govern/discovery/ServiceInstance.java index 047acf00..29b04493 100644 --- a/discovery/src/main/java/me/ahoo/govern/discovery/ServiceInstance.java +++ b/discovery/src/main/java/me/ahoo/govern/discovery/ServiceInstance.java @@ -10,10 +10,10 @@ * @author ahoo wang */ public class ServiceInstance extends Instance { - + public static final ServiceInstance NOT_FOUND = new ServiceInstance(); private int weight = 1; private boolean ephemeral = true; - private int ttlAt = -1; + private long ttlAt = -1; private Map metadata = new LinkedHashMap<>(); public int getWeight() { @@ -40,11 +40,11 @@ public void setMetadata(Map metadata) { this.metadata = metadata; } - public int getTtlAt() { + public long getTtlAt() { return ttlAt; } - public void setTtlAt(int ttlAt) { + public void setTtlAt(long ttlAt) { this.ttlAt = ttlAt; } diff --git a/discovery/src/main/java/me/ahoo/govern/discovery/redis/ConsistencyRedisServiceDiscovery.java b/discovery/src/main/java/me/ahoo/govern/discovery/redis/ConsistencyRedisServiceDiscovery.java index bc039033..0827ef0d 100644 --- a/discovery/src/main/java/me/ahoo/govern/discovery/redis/ConsistencyRedisServiceDiscovery.java +++ b/discovery/src/main/java/me/ahoo/govern/discovery/redis/ConsistencyRedisServiceDiscovery.java @@ -8,9 +8,11 @@ import me.ahoo.govern.discovery.*; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import java.util.stream.Collectors; @@ -25,7 +27,7 @@ public class ConsistencyRedisServiceDiscovery implements ServiceDiscovery { private final ServiceIdxListener serviceIdxListener; private final InstanceListener instanceListener; - private final ConcurrentHashMap>> serviceMapInstances; + private final ConcurrentHashMap>> serviceMapInstances; private final ConcurrentHashMap>> namespaceMapServices; public ConsistencyRedisServiceDiscovery(ServiceDiscovery delegate, MessageListenable messageListenable) { @@ -61,20 +63,56 @@ public CompletableFuture> getInstances(String serviceId) { public CompletableFuture> getInstances(String namespace, String serviceId) { return serviceMapInstances.computeIfAbsent(NamespacedServiceId.of(namespace, serviceId), (_serviceId) -> addListener(namespace, serviceId). - thenCompose(nil -> delegate.getInstances(serviceId)) + thenCompose(nil -> delegate.getInstances(namespace, serviceId) + .thenApply(serviceInstances -> new CopyOnWriteArrayList(serviceInstances))) ) .thenApply(serviceInstances -> serviceInstances.stream().filter(instance -> !instance.isExpired()) .collect(Collectors.toList())); } + public CompletableFuture getInstance0(String namespace, String serviceId, String instanceId) { + var namespacedServiceId = NamespacedServiceId.of(namespace, serviceId); + + var instancesFuture = serviceMapInstances.get(namespacedServiceId); + + if (Objects.isNull(instancesFuture)) { + return CompletableFuture.completedFuture(null); + } + + return instancesFuture.thenApply(serviceInstances -> { + if (Objects.isNull(serviceInstances)) { + return null; + } + var cachedInstanceOp = serviceInstances.stream().filter(itc -> itc.getInstanceId().equals(instanceId)).findFirst(); + + return cachedInstanceOp.orElse(ServiceInstance.NOT_FOUND); + }); + } + @Override public CompletableFuture getInstance(String namespace, String serviceId, String instanceId) { - return delegate.getInstance(namespace, serviceId, instanceId); + return getInstance0(namespace, serviceId, instanceId).thenCompose(instance -> { + if (ServiceInstance.NOT_FOUND.equals(instance)) { + return CompletableFuture.completedFuture(null); + } + if (Objects.isNull(instance)) { + return delegate.getInstance(namespace, serviceId, instanceId); + } + return CompletableFuture.completedFuture(instance); + }); } @Override - public CompletableFuture getInstanceTtl(String namespace, String serviceId, String instanceId) { - return delegate.getInstanceTtl(namespace, serviceId, instanceId); + public CompletableFuture getInstanceTtl(String namespace, String serviceId, String instanceId) { + return getInstance0(namespace, serviceId, instanceId).thenCompose(instance -> { + if (ServiceInstance.NOT_FOUND.equals(instance)) { + return CompletableFuture.completedFuture(null); + } + if (Objects.isNull(instance)) { + return delegate.getInstanceTtl(namespace, serviceId, instanceId); + } + return CompletableFuture.completedFuture(instance.getTtlAt()); + }); } @VisibleForTesting @@ -122,10 +160,84 @@ public void onMessage(Topic topic, String channel, String message) { var instanceId = DiscoveryKeyGenerator.getInstanceIdOfKey(namespace, instanceKey); var instance = InstanceIdGenerator.DEFAULT.of(instanceId); var serviceId = instance.getServiceId(); - /** - * TODO 目前使用的是相应ServiceId的实例整体替换,按照 message :{@link ServiceEventType} 分解,单个实例替换 - */ - serviceMapInstances.put(NamespacedServiceId.of(namespace, serviceId), delegate.getInstances(namespace, serviceId)); + + var namespacedServiceId = NamespacedServiceId.of(namespace, serviceId); + + var instancesFuture = serviceMapInstances.get(namespacedServiceId); + + if (Objects.isNull(instancesFuture)) { + if (log.isInfoEnabled()) { + log.info("onMessage@InstanceListener - topic:[{}] - channel:[{}] - message:[{}] instancesFuture is null.", topic, channel, message); + } + return; + } + + var cachedInstances = instancesFuture.join(); + if (Objects.isNull(cachedInstances)) { + if (log.isInfoEnabled()) { + log.info("onMessage@InstanceListener - topic:[{}] - channel:[{}] - message:[{}] cachedInstances is null.", topic, channel, message); + } + return; + } + + var cachedInstance = cachedInstances.stream() + .filter(itc -> itc.getInstanceId().equals(instanceId)) + .findFirst().orElse(ServiceInstance.NOT_FOUND); + + if (ServiceEventType.REGISTER.equals(message)) { + if (log.isInfoEnabled()) { + log.info("onMessage@InstanceListener - topic:[{}] - channel:[{}] - message:[{}] add registered Instance.", topic, channel, message); + } + var registeredInstance = delegate.getInstance(namespace, serviceId, instanceId).join(); + if (ServiceInstance.NOT_FOUND.equals(cachedInstance)) { + cachedInstances.add(registeredInstance); + } else { + cachedInstance.setSchema(registeredInstance.getSchema()); + cachedInstance.setIp(registeredInstance.getIp()); + cachedInstance.setPort(registeredInstance.getPort()); + cachedInstance.setEphemeral(registeredInstance.isEphemeral()); + cachedInstance.setTtlAt(registeredInstance.getTtlAt()); + cachedInstance.setWeight(registeredInstance.getWeight()); + cachedInstance.setMetadata(registeredInstance.getMetadata()); + } + return; + } + + if (ServiceInstance.NOT_FOUND.equals(cachedInstance)) { + if (log.isWarnEnabled()) { + log.warn("onMessage@InstanceListener - topic:[{}] - channel:[{}] - message:[{}] not found cached Instance.", topic, channel, message); + } + return; + } + + switch (message) { + case ServiceEventType.RENEW: { + if (log.isInfoEnabled()) { + log.info("onMessage@InstanceListener - topic:[{}] - channel:[{}] - message:[{}] setTtlAt.", topic, channel, message); + } + var nextTtlAt = delegate.getInstanceTtl(namespace, serviceId, instanceId).join(); + cachedInstance.setTtlAt(nextTtlAt); + break; + } + case ServiceEventType.SET_METADATA: { + if (log.isInfoEnabled()) { + log.info("onMessage@InstanceListener - topic:[{}] - channel:[{}] - message:[{}] setMetadata.", topic, channel, message); + } + var nextInstance = delegate.getInstance(namespace, serviceId, instanceId).join(); + cachedInstance.setMetadata(nextInstance.getMetadata()); + break; + } + case ServiceEventType.DEREGISTER: + case ServiceEventType.EXPIRED: { + if (log.isInfoEnabled()) { + log.info("onMessage@InstanceListener - topic:[{}] - channel:[{}] - message:[{}] remove instance.", topic, channel, message); + } + cachedInstances.remove(cachedInstance); + break; + } + default: + throw new IllegalStateException("Unexpected value: " + message); + } } } } diff --git a/discovery/src/main/java/me/ahoo/govern/discovery/redis/RedisServiceDiscovery.java b/discovery/src/main/java/me/ahoo/govern/discovery/redis/RedisServiceDiscovery.java index 66d015a8..676f68a1 100644 --- a/discovery/src/main/java/me/ahoo/govern/discovery/redis/RedisServiceDiscovery.java +++ b/discovery/src/main/java/me/ahoo/govern/discovery/redis/RedisServiceDiscovery.java @@ -64,10 +64,10 @@ public CompletableFuture getInstance(String namespace, String s } @Override - public CompletableFuture getInstanceTtl(String namespace, String serviceId, String instanceId) { - return DiscoveryRedisScripts.loadDiscoveryGetInstance(redisCommands) + public CompletableFuture getInstanceTtl(String namespace, String serviceId, String instanceId) { + return DiscoveryRedisScripts.loadDiscoveryGetInstanceTtl(redisCommands) .thenCompose(sha -> { - RedisFuture redisFuture = redisCommands.evalsha(sha, ScriptOutputType.INTEGER, namespace, serviceId, instanceId); + RedisFuture redisFuture = redisCommands.evalsha(sha, ScriptOutputType.INTEGER, namespace, serviceId, instanceId); return redisFuture; }); } diff --git a/discovery/src/main/resources/discovery_get_instance.lua b/discovery/src/main/resources/discovery_get_instance.lua index b4995084..5c62798e 100644 --- a/discovery/src/main/resources/discovery_get_instance.lua +++ b/discovery/src/main/resources/discovery_get_instance.lua @@ -12,11 +12,11 @@ local function ensureNotExpired(instanceIdxKey, instanceId) local instanceTtl = redis.call("ttl", instanceKey); -- -2: The key doesn't exist | -1: The key is fixed | >0: ttl(second) if instanceTtl == -2 then - redis.call("srem", instanceIdxKey, instanceId); - redis.call("del", instanceKey); - redis.call("publish", instanceKey, "expired"); + local removed = redis.call("srem", instanceIdxKey, instanceId); + if removed > 0 then + redis.call("publish", instanceKey, "expired"); + end end - return instanceTtl; end diff --git a/discovery/src/main/resources/discovery_get_instance_ttl.lua b/discovery/src/main/resources/discovery_get_instance_ttl.lua index ced7fb44..ea539b33 100644 --- a/discovery/src/main/resources/discovery_get_instance_ttl.lua +++ b/discovery/src/main/resources/discovery_get_instance_ttl.lua @@ -12,19 +12,19 @@ local function ensureNotExpired(instanceIdxKey, instanceId) local instanceTtl = redis.call("ttl", instanceKey); -- -2: The key doesn't exist | -1: The key is fixed | >0: ttl(second) if instanceTtl == -2 then - redis.call("srem", instanceIdxKey, instanceId); - redis.call("del", instanceKey); - redis.call("publish", instanceKey, "expired"); + local removed = redis.call("srem", instanceIdxKey, instanceId); + if removed > 0 then + redis.call("publish", instanceKey, "expired"); + end end - return instanceTtl; end local instanceTtl = ensureNotExpired(instanceIdxKey, instanceId); if instanceTtl == -1 or instanceTtl == -2 then return instanceTtl; -else - local nowTime = redis.call('time')[1]; - local ttlAt = nowTime + instanceTtl; - return ttlAt; end +local nowTime = redis.call('time')[1]; +local ttlAt = nowTime + instanceTtl; +return ttlAt; + diff --git a/discovery/src/main/resources/discovery_get_instances.lua b/discovery/src/main/resources/discovery_get_instances.lua index f0720eb5..afddcd11 100644 --- a/discovery/src/main/resources/discovery_get_instances.lua +++ b/discovery/src/main/resources/discovery_get_instances.lua @@ -14,11 +14,11 @@ local function ensureNotExpired(instanceIdxKey, instanceId) local instanceTtl = redis.call("ttl", instanceKey); -- -2: The key doesn't exist | -1: The key is fixed | >0: ttl(second) if instanceTtl == -2 then - redis.call("srem", instanceIdxKey, instanceId); - redis.call("del", instanceKey); - redis.call("publish", instanceKey, "expired"); + local removed = redis.call("srem", instanceIdxKey, instanceId); + if removed > 0 then + redis.call("publish", instanceKey, "expired"); + end end - return instanceTtl; end diff --git a/discovery/src/main/resources/registry_register.lua b/discovery/src/main/resources/registry_register.lua index e968e60b..6f596ede 100644 --- a/discovery/src/main/resources/registry_register.lua +++ b/discovery/src/main/resources/registry_register.lua @@ -18,6 +18,7 @@ local instanceIdxKey = namespace .. ":svc_itc_idx:" .. serviceId; local instanceKey = namespace .. ":svc_itc:" .. instanceId; local added = redis.call("sadd", instanceIdxKey, instanceId); + if added == 1 then redis.call("publish", serviceIdxKey, "register"); redis.call("sadd", serviceIdxKey, serviceId); diff --git a/discovery/src/main/resources/service_stat.lua b/discovery/src/main/resources/service_stat.lua index b4d38bbb..8241fea2 100644 --- a/discovery/src/main/resources/service_stat.lua +++ b/discovery/src/main/resources/service_stat.lua @@ -14,11 +14,11 @@ local function ensureNotExpired(instanceIdxKey, instanceId) local instanceTtl = redis.call("ttl", instanceKey); -- -2: The key doesn't exist | -1: The key is fixed | >0: ttl(second) if instanceTtl == -2 then - redis.call("srem", instanceIdxKey, instanceId); - redis.call("del", instanceKey); - redis.call("publish", instanceKey, "expired"); + local removed = redis.call("srem", instanceIdxKey, instanceId); + if removed > 0 then + redis.call("publish", instanceKey, "expired"); + end end - return instanceTtl; end diff --git a/discovery/src/test/java/me/ahoo/govern/discovery/BaseOnRedisClientTest.java b/discovery/src/test/java/me/ahoo/govern/discovery/BaseOnRedisClientTest.java index 46a50ef6..e0c74ffa 100644 --- a/discovery/src/test/java/me/ahoo/govern/discovery/BaseOnRedisClientTest.java +++ b/discovery/src/test/java/me/ahoo/govern/discovery/BaseOnRedisClientTest.java @@ -3,11 +3,12 @@ import io.lettuce.core.RedisClient; import io.lettuce.core.api.StatefulRedisConnection; import lombok.var; -import me.ahoo.govern.core.Consts; import me.ahoo.govern.core.util.RedisScripts; import org.junit.jupiter.api.*; import java.util.Objects; +import java.util.UUID; +import java.util.function.Consumer; /** * @author ahoo wang @@ -26,7 +27,26 @@ private void initRedis() { } protected void clearTestData(String namespace) { - RedisScripts.clearTestData(namespace,redisConnection.async()).join(); + RedisScripts.clearTestData(namespace, redisConnection.async()).join(); + } + + protected ServiceInstance createRandomInstance() { + var randomInstance = new ServiceInstance(); + randomInstance.setServiceId(UUID.randomUUID().toString()); + randomInstance.setSchema("http"); + randomInstance.setIp("127.0.0.1"); + randomInstance.setPort(8080); + randomInstance.setInstanceId(InstanceIdGenerator.DEFAULT.generate(randomInstance)); + randomInstance.getMetadata().put("from", "test"); + return randomInstance; + } + + + protected void registerRandomInstanceFinal(String namespace, ServiceRegistry serviceRegistry, Consumer doTest) { + var randomInstance = createRandomInstance(); + serviceRegistry.register(namespace, randomInstance).join(); + doTest.accept(randomInstance); + serviceRegistry.deregister(namespace, randomInstance); } diff --git a/discovery/src/test/java/me/ahoo/govern/discovery/ConsistencyRedisServiceDiscoveryTest.java b/discovery/src/test/java/me/ahoo/govern/discovery/ConsistencyRedisServiceDiscoveryTest.java index f4447eb0..319929b3 100644 --- a/discovery/src/test/java/me/ahoo/govern/discovery/ConsistencyRedisServiceDiscoveryTest.java +++ b/discovery/src/test/java/me/ahoo/govern/discovery/ConsistencyRedisServiceDiscoveryTest.java @@ -3,13 +3,11 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.var; -import me.ahoo.govern.core.Consts; import me.ahoo.govern.core.listener.RedisMessageListenable; import me.ahoo.govern.discovery.redis.ConsistencyRedisServiceDiscovery; import me.ahoo.govern.discovery.redis.RedisServiceDiscovery; import me.ahoo.govern.discovery.redis.RedisServiceRegistry; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,15 +43,46 @@ private void init() { @Test public void getServices() { - var serviceIds = consistencyRedisServiceDiscovery.getServices(namespace).join(); - Assertions.assertNotNull(serviceIds); + registerRandomInstanceFinal(namespace, redisServiceRegistry, (instance -> { + var serviceIds = consistencyRedisServiceDiscovery.getServices(namespace).join(); + Assertions.assertNotNull(serviceIds); + Assertions.assertTrue(serviceIds.contains(instance.getServiceId())); + })); } - @Test public void getInstances() { - var instances = consistencyRedisServiceDiscovery.getInstances(namespace, testInstance.getServiceId()).join(); - Assertions.assertNotNull(instances); + registerRandomInstanceFinal(namespace, redisServiceRegistry, (instance -> { + var instances = consistencyRedisServiceDiscovery.getInstances(namespace, instance.getServiceId()).join(); + Assertions.assertNotNull(instances); + + var expectedInstance = instances.stream().findFirst().get(); + Assertions.assertNotNull(expectedInstance); + Assertions.assertEquals(instance.getServiceId(), expectedInstance.getServiceId()); + Assertions.assertEquals(instance.getInstanceId(), expectedInstance.getInstanceId()); + })); + } + + @Test + public void getInstance() { + registerRandomInstanceFinal(namespace, redisServiceRegistry, (instance -> { + var actualInstance = consistencyRedisServiceDiscovery.getInstance(namespace, instance.getServiceId(), instance.getInstanceId()).join(); + Assertions.assertEquals(instance.getServiceId(), actualInstance.getServiceId()); + Assertions.assertEquals(instance.getInstanceId(), actualInstance.getInstanceId()); + })); + } + + @Test + public void getInstanceWithCache() { + registerRandomInstanceFinal(namespace, redisServiceRegistry, (instance -> { + consistencyRedisServiceDiscovery.getInstances(namespace, instance.getServiceId()).join(); + var actualInstance = consistencyRedisServiceDiscovery.getInstance(namespace, instance.getServiceId(), instance.getInstanceId()).join(); + Assertions.assertEquals(instance.getServiceId(), actualInstance.getServiceId()); + Assertions.assertEquals(instance.getInstanceId(), actualInstance.getInstanceId()); + + var cachedInstance = consistencyRedisServiceDiscovery.getInstance(namespace, instance.getServiceId(), instance.getInstanceId()).join(); + Assertions.assertEquals(cachedInstance, actualInstance); + })); } private final static int SLEEP_FOR_WAIT_MESSAGE = 1; diff --git a/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceDiscoveryTest.java b/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceDiscoveryTest.java index 361c5cd1..4c709f70 100644 --- a/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceDiscoveryTest.java +++ b/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceDiscoveryTest.java @@ -18,13 +18,10 @@ public class RedisServiceDiscoveryTest extends BaseOnRedisClientTest { private final static String namespace = "test_svc"; private RedisServiceDiscovery redisServiceDiscovery; - - private ServiceInstance serviceInstance; private RedisServiceRegistry redisServiceRegistry; @BeforeAll private void init() { - serviceInstance = TestServiceInstance.TEST_INSTANCE; var registryProperties = new RegistryProperties(); redisServiceRegistry = new RedisServiceRegistry(registryProperties, redisConnection.async()); redisServiceDiscovery = new RedisServiceDiscovery(redisConnection.async()); @@ -34,14 +31,34 @@ private void init() { @Test public void getServices() { - var serviceIds = redisServiceDiscovery.getServices(namespace).join(); - Assertions.assertNotNull(serviceIds); + registerRandomInstanceFinal(namespace, redisServiceRegistry, (instance -> { + var serviceIds = redisServiceDiscovery.getServices(namespace).join(); + Assertions.assertNotNull(serviceIds); + Assertions.assertTrue(serviceIds.contains(instance.getServiceId())); + })); } @Test public void getInstances() { - var instances = redisServiceDiscovery.getInstances(namespace, "test_fixed_service").join(); - Assertions.assertNotNull(instances); + registerRandomInstanceFinal(namespace, redisServiceRegistry, (instance -> { + var instances = redisServiceDiscovery.getInstances(namespace, instance.getServiceId()).join(); + Assertions.assertNotNull(instances); + Assertions.assertEquals(1, instances.size()); + + var expectedInstance = instances.stream().findFirst().get(); + Assertions.assertEquals(instance.getServiceId(), expectedInstance.getServiceId()); + Assertions.assertEquals(instance.getInstanceId(), expectedInstance.getInstanceId()); + })); + } + + + @Test + public void getInstance() { + registerRandomInstanceFinal(namespace, redisServiceRegistry, (instance -> { + var actualInstance = redisServiceDiscovery.getInstance(namespace, instance.getServiceId(), instance.getInstanceId()).join(); + Assertions.assertEquals(instance.getServiceId(), actualInstance.getServiceId()); + Assertions.assertEquals(instance.getInstanceId(), actualInstance.getInstanceId()); + })); } diff --git a/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceRegistryTest.java b/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceRegistryTest.java index 56e4290f..858852c8 100644 --- a/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceRegistryTest.java +++ b/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceRegistryTest.java @@ -2,7 +2,6 @@ import lombok.SneakyThrows; import lombok.var; -import me.ahoo.govern.core.Consts; import me.ahoo.govern.discovery.redis.RedisServiceRegistry; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -22,7 +21,6 @@ public class RedisServiceRegistryTest extends BaseOnRedisClientTest { @BeforeAll private void init() { - testInstance = TestServiceInstance.TEST_INSTANCE; testFixedInstance = TestServiceInstance.TEST_FIXED_INSTANCE; var registryProperties = new RegistryProperties(); @@ -64,11 +62,10 @@ public void deregister() { private final static int REPEATED_SIZE = 60000; - @SneakyThrows -// @Test + @Test public void registerRepeatedSync() { - for (int i = 0; i < REPEATED_SIZE; i++) { - redisServiceRegistry.register(testInstance).join(); + for (int i = 0; i < 20; i++) { + redisServiceRegistry.register(namespace, testInstance).join(); } } diff --git a/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceStatisticTest.java b/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceStatisticTest.java index 0ddf7fc4..cc105853 100644 --- a/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceStatisticTest.java +++ b/discovery/src/test/java/me/ahoo/govern/discovery/RedisServiceStatisticTest.java @@ -8,8 +8,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.util.UUID; - /** * @author ahoo wang */ @@ -28,13 +26,7 @@ private void init() { @Test void statService() { - var getServiceStatInstance = new ServiceInstance(); - getServiceStatInstance.setServiceId(UUID.randomUUID().toString()); - getServiceStatInstance.setSchema("http"); - getServiceStatInstance.setIp("127.0.0.1"); - getServiceStatInstance.setPort(8080); - getServiceStatInstance.setInstanceId(InstanceIdGenerator.DEFAULT.generate(getServiceStatInstance)); - getServiceStatInstance.getMetadata().put("from", "test_getServiceStats"); + var getServiceStatInstance = createRandomInstance(); redisServiceRegistry.register(namespace, getServiceStatInstance).join(); redisServiceStatistic.statService(namespace).join(); diff --git a/docker/rest-api/Dockerfile b/docker/rest-api/Dockerfile index 4c803e98..73fa5d9f 100644 --- a/docker/rest-api/Dockerfile +++ b/docker/rest-api/Dockerfile @@ -1,6 +1,6 @@ # docker build --build-arg JDK_VERSION=armv7l-centos-jdk-11.0.8_10-slim --build-arg GOVERN_VERSION=0.7.2 -t ahoowang/govern-service:0.7.2 . ARG JDK_VERSION=alpine -ARG GOVERN_VERSION=0.8.1 +ARG GOVERN_VERSION=0.8.2 ARG GOVERN_SERVICE_HOME=/govern-service FROM adoptopenjdk/openjdk11:${JDK_VERSION} AS base diff --git a/gradle.properties b/gradle.properties index 5eae1619..00ab5960 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=me.ahoo.govern -version=0.8.1 +version=0.8.2 description=Govern Service On Redis website=https://github.com/Ahoo-Wang/govern-service diff --git a/rest-api/build.gradle.kts b/rest-api/build.gradle.kts index ab0bff5a..efc80c09 100644 --- a/rest-api/build.gradle.kts +++ b/rest-api/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation("io.springfox:springfox-boot-starter") implementation(project(":spring-cloud-starter-config")) implementation(project(":spring-cloud-starter-discovery")) + implementation("com.google.guava:guava") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.cloud:spring-cloud-starter-openfeign") compileOnly("org.projectlombok:lombok:${rootProject.ext.get("lombokVersion")}") diff --git a/rest-api/src/main/java/me/ahoo/govern/rest/config/AppConfig.java b/rest-api/src/main/java/me/ahoo/govern/rest/config/AppConfig.java new file mode 100644 index 00000000..d52d1a9d --- /dev/null +++ b/rest-api/src/main/java/me/ahoo/govern/rest/config/AppConfig.java @@ -0,0 +1,11 @@ +package me.ahoo.govern.rest.config; + +import org.springframework.context.annotation.Configuration; + +/** + * @author ahoo wang + */ +@Configuration +public class AppConfig { + +} diff --git a/rest-api/src/main/java/me/ahoo/govern/rest/job/StatServiceJob.java b/rest-api/src/main/java/me/ahoo/govern/rest/job/StatServiceJob.java index 758c0cac..043974f5 100644 --- a/rest-api/src/main/java/me/ahoo/govern/rest/job/StatServiceJob.java +++ b/rest-api/src/main/java/me/ahoo/govern/rest/job/StatServiceJob.java @@ -31,22 +31,17 @@ public void doStatService() { } var currentNamespace = NamespacedContext.GLOBAL.getNamespace(); var namespaces = namespaceService.getNamespaces().join(); - if (namespaces.isEmpty()) { - if (log.isInfoEnabled()) { - log.info("doStatService - namespaces isEmpty."); - } + if (!namespaces.contains(currentNamespace)) { namespaceService.setNamespace(currentNamespace).join(); - return; } - if (namespaces.size() == 1 && !namespaces.contains(currentNamespace)) { - namespaceService.setNamespace(currentNamespace).join(); - } + if (!namespaces.isEmpty()) { + var statFutures = namespaces.stream().map(namespace -> serviceStatistic.statService(namespace)) + .toArray(size -> new CompletableFuture[size]); - var statFutures = namespaces.stream().map(namespace -> serviceStatistic.statService(namespace)) - .toArray(size -> new CompletableFuture[size]); + CompletableFuture.allOf(statFutures).join(); + } - CompletableFuture.allOf(statFutures).join(); if (log.isInfoEnabled()) { log.info("doStatService - end."); }