From 530efb65d76946264c4168a9c453f0788cd3280e Mon Sep 17 00:00:00 2001 From: hccake Date: Fri, 29 Jan 2021 18:20:15 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E7=AE=80=E6=98=93redis=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E4=B8=AD=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 + .../register/HeartBeatAuoConfiguration.java | 20 ++ .../register/RedisHeartbeatDetection.java | 51 +++++ .../RedisRegisterAutoConfiguration.java | 23 +++ .../register/RedisRegisterManagement.java | 160 ++++++++++++++++ .../register/RedisServiceInstance.java | 88 +++++++++ .../RedisDiscoveryAutoConfiguration.java | 19 ++ .../discovery/RedisDiscoveryClient.java | 36 ++++ .../RedisAutoServiceRegistration.java | 46 +++++ .../registry/RedisDiscoveryProperties.java | 19 ++ .../register/registry/RedisRegistration.java | 180 ++++++++++++++++++ .../registry/RedisServiceRegistry.java | 81 ++++++++ ...RedisServiceRegistryAutoConfiguration.java | 47 +++++ .../framework/register/util/InetUtil.java | 50 +++++ .../register/util/NamedThreadFactory.java | 64 +++++++ 15 files changed, 891 insertions(+) create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/HeartBeatAuoConfiguration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/RedisHeartbeatDetection.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterAutoConfiguration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterManagement.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/RedisServiceInstance.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryAutoConfiguration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryClient.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisAutoServiceRegistration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisDiscoveryProperties.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisRegistration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistry.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistryAutoConfiguration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/util/InetUtil.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/register/util/NamedThreadFactory.java diff --git a/pom.xml b/pom.xml index 39f97ab43a..4a49c16953 100644 --- a/pom.xml +++ b/pom.xml @@ -222,6 +222,13 @@ ${easyexcel.verion} + + + org.springframework.cloud + spring-cloud-commons + 3.0.0 + + diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/HeartBeatAuoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/HeartBeatAuoConfiguration.java new file mode 100644 index 0000000000..78a3d02b4c --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/HeartBeatAuoConfiguration.java @@ -0,0 +1,20 @@ +package cn.iocoder.dashboard.framework.register; + +import cn.iocoder.dashboard.framework.register.registry.RedisRegistration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Hccake 2021/1/29 + * @version 1.0 + */ +@Configuration(proxyBeanMethods = false) +public class HeartBeatAuoConfiguration { + + + @Bean + public RedisHeartbeatDetection redisHeartbeatDetection(RedisRegisterManagement redisRegisterManagement, RedisRegistration redisRegistration){ + return new RedisHeartbeatDetection(redisRegisterManagement, redisRegistration); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisHeartbeatDetection.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisHeartbeatDetection.java new file mode 100644 index 0000000000..9878156d00 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisHeartbeatDetection.java @@ -0,0 +1,51 @@ +package cn.iocoder.dashboard.framework.register; + +import cn.iocoder.dashboard.framework.register.registry.RedisRegistration; +import cn.iocoder.dashboard.framework.register.util.NamedThreadFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * 心跳检测线程 + * TODO 目前是在每一个客户端都维护一个线程,可剥离出来放在一个中心去处理 + * @author Hccake 2021/1/29 + * @version 1.0 + */ +@Slf4j +@RequiredArgsConstructor +public class RedisHeartbeatDetection { + private final RedisRegisterManagement redisRegisterManagement; + private final RedisRegistration redisRegistration; + + private final ScheduledExecutorService detectionExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("RedisRegistryExpireTimer", true)); + + private ScheduledFuture detectionFuture; + + @PostConstruct + public void init(){ + // 心跳检测线程 + int detectionInterval = 60 * 1000; + this.detectionFuture = detectionExecutor.scheduleWithFixedDelay(() -> { + try { + redisRegisterManagement.removeExpiredInstances(redisRegistration.getServiceId()); // Extend the expiration time + } catch (Throwable t) { // Defensive fault tolerance + log.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t); + } + }, detectionInterval, detectionInterval, TimeUnit.MILLISECONDS); + } + + + @PreDestroy + public void destroy(){ + detectionFuture.cancel(true); + detectionExecutor.shutdown(); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterAutoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterAutoConfiguration.java new file mode 100644 index 0000000000..57e3ec560f --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterAutoConfiguration.java @@ -0,0 +1,23 @@ +package cn.iocoder.dashboard.framework.register; + +import cn.iocoder.dashboard.framework.register.registry.RedisDiscoveryProperties; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author Hccake 2021/1/27 + * @version 1.0 + */ +@RequiredArgsConstructor +@Configuration(proxyBeanMethods = false) +public class RedisRegisterAutoConfiguration { + private final RedisDiscoveryProperties redisDiscoveryProperties; + + @Bean + public RedisRegisterManagement redisRegisterManagement(ObjectMapper objectMapper, StringRedisTemplate stringRedisTemplate){ + return new RedisRegisterManagement(objectMapper, stringRedisTemplate, redisDiscoveryProperties); + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterManagement.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterManagement.java new file mode 100644 index 0000000000..c6ac492c3b --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterManagement.java @@ -0,0 +1,160 @@ +package cn.iocoder.dashboard.framework.register; + +import cn.iocoder.dashboard.framework.register.registry.RedisDiscoveryProperties; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.SetOperations; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.data.redis.core.script.DefaultRedisScript; + +import java.util.*; + +/** + * redis注册中心的数据操作管理 + * TODO 原子操作,移除过期实例同时移除无实例的服务 + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@RequiredArgsConstructor +public class RedisRegisterManagement { + private final ObjectMapper objectMapper; + private final StringRedisTemplate stringRedisTemplate; + private final RedisDiscoveryProperties redisDiscoveryProperties; + + /** + * 服务列表,SET 类型,member 是 serviceId + */ + private final static String SERVICES_KEY = "redis-register-services"; + + /** + * 服务实例列表,HASH类型,field 是 instanceId, value 是实例信息 + */ + private final static String SERVICES_INSTANCE_KEY_PREFIX = "redis-register-service-instance:"; + + private static String getInstancesKey(String serviceId) { + return SERVICES_INSTANCE_KEY_PREFIX + serviceId; + } + + /** + * 服务实例心跳维护,ZSET 类型,member instanceId, value 是过期时间 + */ + private final static String SERVICES_INSTANCE_EXPIRES_KEY_PREFIX = "redis-register-service-instance-expires:"; + + private static String getInstanceExpiresKey(String serviceId) { + return SERVICES_INSTANCE_EXPIRES_KEY_PREFIX + serviceId; + } + + + /** + * 添加一个服务实例 + * + * @param redisServiceInstance redis服务实例对象 + * @throws JsonProcessingException json 序列化异常 + */ + public void addServiceInstance(RedisServiceInstance redisServiceInstance) throws JsonProcessingException { + String serviceId = redisServiceInstance.getServiceId(); + String instanceId = redisServiceInstance.getInstanceId(); + + // 服务列表 + SetOperations opsForSet = stringRedisTemplate.opsForSet(); + opsForSet.add(SERVICES_KEY, serviceId); + + // 服务实例 + HashOperations opsForHash = stringRedisTemplate.opsForHash(); + String value = new ObjectMapper().writeValueAsString(redisServiceInstance); + opsForHash.put(getInstancesKey(serviceId), instanceId, value); + + // 服务实例的过期时间 + ZSetOperations opsForZSet = stringRedisTemplate.opsForZSet(); + opsForZSet.add(getInstanceExpiresKey(serviceId), instanceId, System.currentTimeMillis() + redisDiscoveryProperties.getHeartbeatInterval()); + + } + + + /** + * 移除一个服务实例 + * + * @param registration 注册数据 + */ + public void removeServiceInstance(Registration registration) { + String serviceId = registration.getServiceId(); + String instanceId = registration.getInstanceId(); + + // 服务实例 + HashOperations opsForHash = stringRedisTemplate.opsForHash(); + opsForHash.delete(getInstancesKey(serviceId), instanceId); + + // 服务实例的过期时间 + ZSetOperations opsForZSet = stringRedisTemplate.opsForZSet(); + opsForZSet.remove(getInstanceExpiresKey(serviceId), instanceId); + } + + /** + * 根据 服务id 获取其实例列表 + * + * @param serviceId 服务id + * @return List 服务实例列表 + * @throws JsonProcessingException json 序列化异常 + */ + public List getInstances(String serviceId) throws JsonProcessingException { + List serviceInstances = new ArrayList<>(); + HashOperations opsForHash = stringRedisTemplate.opsForHash(); + Map entries = opsForHash.entries(getInstancesKey(serviceId)); + for (Map.Entry entry : entries.entrySet()) { + RedisServiceInstance redisServiceInstance = objectMapper.readValue(entry.getValue(), RedisServiceInstance.class); + serviceInstances.add(redisServiceInstance); + } + return serviceInstances; + } + + + /** + * 获取服务列表 + * + * @return List 服务列表 + */ + public List getServices() { + // 服务列表 + SetOperations opsForSet = stringRedisTemplate.opsForSet(); + Set members = opsForSet.members(SERVICES_KEY); + return members == null ? new ArrayList<>() : new ArrayList<>(members); + } + + /** + * 延迟实例过期时间 + * @param serviceId 服务ID + * @param instanceId 实例ID + */ + public void deferInstanceExpired(String serviceId, String instanceId) { + // 服务列表更新,因为可能被别的线程移除了 + SetOperations opsForSet = stringRedisTemplate.opsForSet(); + opsForSet.add(SERVICES_KEY, serviceId); + // 服务实例的过期时间 + ZSetOperations opsForZSet = stringRedisTemplate.opsForZSet(); + opsForZSet.add(getInstanceExpiresKey(serviceId), instanceId, System.currentTimeMillis() + redisDiscoveryProperties.getHeartbeatInterval()); + } + + + private final static String REMOVE_EXPIRED_INSTANCES_SCRIPT = "local instanceIds = redis.call('zrangeByScore', KEYS[1], 0, ARGV[1])\n" + + "if next(instanceIds) ~= nil then\n" + + " redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1])\n" + + " redis.call('hdel', KEYS[2], unpack(instanceIds))\n" + + "end"; + + /** + * 移除过期实例 + */ + public void removeExpiredInstances(String serviceId) { + DefaultRedisScript redisScript = new DefaultRedisScript<>(REMOVE_EXPIRED_INSTANCES_SCRIPT); + stringRedisTemplate.execute(redisScript, + Arrays.asList(getInstanceExpiresKey(serviceId), getInstancesKey(serviceId)), + String.valueOf(System.currentTimeMillis()) + ); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisServiceInstance.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisServiceInstance.java new file mode 100644 index 0000000000..849cc2f577 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisServiceInstance.java @@ -0,0 +1,88 @@ +package cn.iocoder.dashboard.framework.register; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Setter; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; + +import java.net.URI; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@Setter +@JsonIgnoreProperties({"scheme", "uri"}) +public class RedisServiceInstance implements ServiceInstance { + + private String serviceId; + + private String instanceId; + + private String host; + + private int port; + + private boolean secure; + + private Map metadata = new LinkedHashMap<>(); + + public RedisServiceInstance(){ + } + + @Override + public String getServiceId() { + return serviceId; + } + + @Override + public String getInstanceId() { + return this.instanceId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + @Override + public String getHost() { + return host; + } + + @Override + public int getPort() { + return port; + } + + @Override + public boolean isSecure() { + return secure; + } + + @Override + public URI getUri() { + return DefaultServiceInstance.getUri(this); + } + + @Override + public Map getMetadata() { + return metadata; + } + + @Override + public String getScheme() { + return this.getUri().getScheme(); + } + + public void setUri(URI uri) { + this.host = uri.getHost(); + this.port = uri.getPort(); + String scheme = uri.getScheme(); + if ("https".equals(scheme)) { + this.secure = true; + } + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryAutoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryAutoConfiguration.java new file mode 100644 index 0000000000..5c152ac0a3 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryAutoConfiguration.java @@ -0,0 +1,19 @@ +package cn.iocoder.dashboard.framework.register.discovery; + +import cn.iocoder.dashboard.framework.register.RedisRegisterManagement; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 服务发现自动配置类 + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@Configuration(proxyBeanMethods = false) +public class RedisDiscoveryAutoConfiguration { + + @Bean + public RedisDiscoveryClient redisDiscoveryClient(RedisRegisterManagement redisRegisterManagement){ + return new RedisDiscoveryClient(redisRegisterManagement); + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryClient.java b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryClient.java new file mode 100644 index 0000000000..90c0ea9546 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryClient.java @@ -0,0 +1,36 @@ +package cn.iocoder.dashboard.framework.register.discovery; + +import cn.iocoder.dashboard.framework.register.RedisRegisterManagement; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; + +import java.util.List; + +/** + * 服务发现客户端 + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@RequiredArgsConstructor +public class RedisDiscoveryClient implements DiscoveryClient { + private final RedisRegisterManagement redisRegisterManagement; + + @Override + public String description() { + return "Redis Discovery Client"; + } + + @SneakyThrows + @Override + public List getInstances(String serviceId) { + return redisRegisterManagement.getInstances(serviceId); + } + + @Override + public List getServices() { + return redisRegisterManagement.getServices(); + + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisAutoServiceRegistration.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisAutoServiceRegistration.java new file mode 100644 index 0000000000..29d007224f --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisAutoServiceRegistration.java @@ -0,0 +1,46 @@ +package cn.iocoder.dashboard.framework.register.registry; + +import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; + +/** + * @author Hccake 2021/1/26 + * @version 1.0 + */ +public class RedisAutoServiceRegistration extends AbstractAutoServiceRegistration { + + private final RedisRegistration redisRegistration; + + protected RedisAutoServiceRegistration(ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties properties, RedisRegistration registration) { + super(serviceRegistry, properties); + this.redisRegistration = registration; + } + + /** + * @return The object used to configure the registration. + */ + @Override + protected Object getConfiguration() { + return null; + } + + /** + * @return True, if this is enabled. + */ + @Override + protected boolean isEnabled() { + return true; + } + + @Override + protected RedisRegistration getRegistration() { + return redisRegistration; + } + + @Override + protected RedisRegistration getManagementRegistration() { + return null; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisDiscoveryProperties.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisDiscoveryProperties.java new file mode 100644 index 0000000000..066c053b8a --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisDiscoveryProperties.java @@ -0,0 +1,19 @@ +package cn.iocoder.dashboard.framework.register.registry; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@Data +@ConfigurationProperties("spring.cloud.redis.discovery") +public class RedisDiscoveryProperties { + + /** + * 心跳间隔时间 + */ + private int heartbeatInterval = 60 * 1000; + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisRegistration.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisRegistration.java new file mode 100644 index 0000000000..72f46e4e98 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisRegistration.java @@ -0,0 +1,180 @@ +package cn.iocoder.dashboard.framework.register.registry; + +import cn.iocoder.dashboard.framework.register.RedisRegisterManagement; +import cn.iocoder.dashboard.framework.register.util.InetUtil; +import cn.iocoder.dashboard.framework.register.util.NamedThreadFactory; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.ManagementServerPortUtils; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.net.InetAddress; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@Slf4j +@Setter +public class RedisRegistration implements Registration, ServiceInstance, ApplicationContextAware { + + private static final String MANAGEMENT_SCHEME = "management.scheme"; + + private static final String MANAGEMENT_ADDRESS = "management.address"; + + private static final String MANAGEMENT_PORT = "management.port"; + + private static final String MANAGEMENT_CONTEXT_PATH = "management.context-path"; + + private static final String KEY_HEALTH_PATH = "health.path"; + + private final RedisRegisterManagement redisRegisterManagement; + + private final RedisDiscoveryProperties redisDiscoveryProperties; + + private ApplicationContext context; + + @Value("${spring.application.name}") + private String applicationName; + + @Value("${server.port: 8080}") + private Integer port; + + + private String host; + + private Map metadata = new HashMap<>(); + + private final ScheduledExecutorService expireExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("RedisRegistryExpireTimer", true)); + + private ScheduledFuture expireFuture; + + + public RedisRegistration(RedisRegisterManagement redisRegisterManagement, RedisDiscoveryProperties redisDiscoveryProperties) { + this.redisRegisterManagement = redisRegisterManagement; + this.redisDiscoveryProperties = redisDiscoveryProperties; + } + + @PostConstruct + public void init() { + + Environment env = context.getEnvironment(); + + Integer managementPort = ManagementServerPortUtils.getPort(context); + if (null != managementPort) { + metadata.put(MANAGEMENT_PORT, managementPort.toString()); + String contextPath = env + .getProperty("management.server.servlet.context-path"); + String address = env.getProperty("management.server.address"); + if (!StringUtils.hasText(contextPath)) { + metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath); + } + if (!StringUtils.hasText(address)) { + metadata.put(MANAGEMENT_ADDRESS, address); + } + } + + // 过期时间延长线程 + int heartbeatInterval = redisDiscoveryProperties.getHeartbeatInterval(); + this.expireFuture = expireExecutor.scheduleWithFixedDelay(() -> { + try { + redisRegisterManagement.deferInstanceExpired(this.getServiceId(), this.getInstanceId()); // Extend the expiration time + } catch (Throwable t) { // Defensive fault tolerance + log.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t); + } + }, heartbeatInterval / 2, heartbeatInterval / 2, TimeUnit.MILLISECONDS); + } + + + @PreDestroy + public void destroy(){ + expireFuture.cancel(true); + expireExecutor.shutdown(); + } + + /** + * @return The service ID as registered. + */ + @Override + public String getServiceId() { + return applicationName; + } + + @Override + public String getInstanceId() { + return this.host + '-' + this.applicationName + '-' + this.port; + } + + /** + * @return The hostname of the registered service instance. + */ + @Override + public String getHost() { + try { + if (this.host == null) { + InetAddress address = InetUtil.getLocalHostLANAddress(); + if (address != null){ + this.host = address.getHostAddress(); + } + } + return host; + } catch (Exception e) { + e.printStackTrace(); + } + return host; + } + + /** + * @return The port of the registered service instance. + */ + @Override + public int getPort() { + return port; + } + + /** + * @return Whether the port of the registered service instance uses HTTPS. + */ + @Override + public boolean isSecure() { + return false; + } + + /** + * @return The service URI address. + */ + @Override + public URI getUri() { + return null; + } + + /** + * @return The key / value pair metadata associated with the service instance. + */ + @Override + public Map getMetadata() { + return metadata; + } + + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.context = applicationContext; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistry.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistry.java new file mode 100644 index 0000000000..2c3f57dec9 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistry.java @@ -0,0 +1,81 @@ +package cn.iocoder.dashboard.framework.register.registry; + +import cn.iocoder.dashboard.framework.register.RedisRegisterManagement; +import cn.iocoder.dashboard.framework.register.RedisServiceInstance; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint; + +/** + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@Slf4j +@RequiredArgsConstructor +public class RedisServiceRegistry implements ServiceRegistry { + private final RedisRegisterManagement redisRegisterManagement; + + /** + * Registers the registration. A registration typically has information about an + * instance, such as its hostname and port. + * + * @param registration registration meta data + */ + @SneakyThrows + @Override + public void register(Registration registration) { + RedisServiceInstance redisServiceInstance = new RedisServiceInstance(); + String serviceId = registration.getServiceId(); + redisServiceInstance.setServiceId(serviceId); + redisServiceInstance.setHost(registration.getHost()); + redisServiceInstance.setPort(registration.getPort()); + redisServiceInstance.setMetadata(registration.getMetadata()); + redisServiceInstance.setInstanceId(registration.getInstanceId()); + redisRegisterManagement.addServiceInstance(redisServiceInstance); + } + + /** + * Deregisters the registration. + * + * @param registration registration meta data + */ + @Override + public void deregister(Registration registration) { + redisRegisterManagement.removeServiceInstance(registration); + } + + /** + * Closes the ServiceRegistry. This is a lifecycle method. + */ + @Override + public void close() { + } + + /** + * Sets the status of the registration. The status values are determined by the + * individual implementations. + * + * @param registration The registration to update. + * @param status The status to set. + * @see ServiceRegistryEndpoint + */ + @Override + public void setStatus(Registration registration, String status) { + + } + + /** + * Gets the status of a particular registration. + * + * @param registration The registration to query. + * @return The status of the registration. + * @see ServiceRegistryEndpoint + */ + @Override + public T getStatus(Registration registration) { + return null; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistryAutoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistryAutoConfiguration.java new file mode 100644 index 0000000000..ae2fb1b05e --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistryAutoConfiguration.java @@ -0,0 +1,47 @@ +package cn.iocoder.dashboard.framework.register.registry; + +import cn.iocoder.dashboard.framework.register.RedisRegisterManagement; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Hccake 2021/1/26 + * @version 1.0 + */ +@EnableConfigurationProperties(RedisDiscoveryProperties.class) +@Configuration(proxyBeanMethods = false) +@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class, + AutoServiceRegistrationAutoConfiguration.class }) +@RequiredArgsConstructor +public class RedisServiceRegistryAutoConfiguration { + private final RedisRegisterManagement redisRegisterManagement; + private final RedisDiscoveryProperties redisDiscoveryProperties; + + @Bean + public RedisServiceRegistry redisServiceRegistry() { + return new RedisServiceRegistry(redisRegisterManagement); + } + + @Bean + public RedisRegistration redisRegistration() { + return new RedisRegistration(redisRegisterManagement, redisDiscoveryProperties); + } + + @Bean + public RedisAutoServiceRegistration registration( + RedisServiceRegistry redisServiceRegistry, + AutoServiceRegistrationProperties autoServiceRegistrationProperties, + RedisRegistration redisRegistration) { + return new RedisAutoServiceRegistration(redisServiceRegistry, + autoServiceRegistrationProperties, redisRegistration); + } + + + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/util/InetUtil.java b/src/main/java/cn/iocoder/dashboard/framework/register/util/InetUtil.java new file mode 100644 index 0000000000..5ddf2f9923 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/util/InetUtil.java @@ -0,0 +1,50 @@ +package cn.iocoder.dashboard.framework.register.util; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +/** + * @author Hccake 2021/1/26 + * @version 1.0 + */ +public class InetUtil { + + public static InetAddress getLocalHostLANAddress() throws Exception { + try { + InetAddress candidateAddress = null; + + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + // 遍历所有的网络接口 + while (networkInterfaces.hasMoreElements()){ + NetworkInterface networkInterface = networkInterfaces.nextElement(); + // 在所有的接口下再遍历IP + Enumeration inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddr = inetAddresses.nextElement(); + // 排除loopback类型地址 + if (inetAddr.isLoopbackAddress()) { + continue; + } + if (inetAddr.isSiteLocalAddress()) { + // 如果是site-local地址,就是它了 + return inetAddr; + } else if (candidateAddress == null) { + // site-local类型的地址未被发现,先记录候选地址 + candidateAddress = inetAddr; + } + } + + } + + if (candidateAddress != null) { + return candidateAddress; + } + // 如果没有发现 non-loopback地址.只能用最次选的方案 + return InetAddress.getLocalHost(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/util/NamedThreadFactory.java b/src/main/java/cn/iocoder/dashboard/framework/register/util/NamedThreadFactory.java new file mode 100644 index 0000000000..af62fbf5d1 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/register/util/NamedThreadFactory.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 cn.iocoder.dashboard.framework.register.util; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Copy form Dubbo + * InternalThreadFactory. + */ +public class NamedThreadFactory implements ThreadFactory { + + protected static final AtomicInteger POOL_SEQ = new AtomicInteger(1); + + protected final AtomicInteger mThreadNum = new AtomicInteger(1); + + protected final String mPrefix; + + protected final boolean mDaemon; + + protected final ThreadGroup mGroup; + + public NamedThreadFactory() { + this("pool-" + POOL_SEQ.getAndIncrement(), false); + } + + public NamedThreadFactory(String prefix) { + this(prefix, false); + } + + public NamedThreadFactory(String prefix, boolean daemon) { + mPrefix = prefix + "-thread-"; + mDaemon = daemon; + SecurityManager s = System.getSecurityManager(); + mGroup = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup(); + } + + @Override + public Thread newThread(Runnable runnable) { + String name = mPrefix + mThreadNum.getAndIncrement(); + Thread ret = new Thread(mGroup, runnable, name, 0); + ret.setDaemon(mDaemon); + return ret; + } + + public ThreadGroup getThreadGroup() { + return mGroup; + } +}