✨ 简易redis注册中心
This commit is contained in:
parent
842496a534
commit
530efb65d7
7
pom.xml
7
pom.xml
|
@ -222,6 +222,13 @@
|
|||
<version>${easyexcel.verion}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<String, String> opsForSet = stringRedisTemplate.opsForSet();
|
||||
opsForSet.add(SERVICES_KEY, serviceId);
|
||||
|
||||
// 服务实例
|
||||
HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
|
||||
String value = new ObjectMapper().writeValueAsString(redisServiceInstance);
|
||||
opsForHash.put(getInstancesKey(serviceId), instanceId, value);
|
||||
|
||||
// 服务实例的过期时间
|
||||
ZSetOperations<String, String> 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<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
|
||||
opsForHash.delete(getInstancesKey(serviceId), instanceId);
|
||||
|
||||
// 服务实例的过期时间
|
||||
ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
|
||||
opsForZSet.remove(getInstanceExpiresKey(serviceId), instanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 服务id 获取其实例列表
|
||||
*
|
||||
* @param serviceId 服务id
|
||||
* @return List<ServiceInstance> 服务实例列表
|
||||
* @throws JsonProcessingException json 序列化异常
|
||||
*/
|
||||
public List<ServiceInstance> getInstances(String serviceId) throws JsonProcessingException {
|
||||
List<ServiceInstance> serviceInstances = new ArrayList<>();
|
||||
HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
|
||||
Map<String, String> entries = opsForHash.entries(getInstancesKey(serviceId));
|
||||
for (Map.Entry<String, String> entry : entries.entrySet()) {
|
||||
RedisServiceInstance redisServiceInstance = objectMapper.readValue(entry.getValue(), RedisServiceInstance.class);
|
||||
serviceInstances.add(redisServiceInstance);
|
||||
}
|
||||
return serviceInstances;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取服务列表
|
||||
*
|
||||
* @return List<String> 服务列表
|
||||
*/
|
||||
public List<String> getServices() {
|
||||
// 服务列表
|
||||
SetOperations<String, String> opsForSet = stringRedisTemplate.opsForSet();
|
||||
Set<String> 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<String, String> opsForSet = stringRedisTemplate.opsForSet();
|
||||
opsForSet.add(SERVICES_KEY, serviceId);
|
||||
// 服务实例的过期时间
|
||||
ZSetOperations<String, String> 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<Void> redisScript = new DefaultRedisScript<>(REMOVE_EXPIRED_INSTANCES_SCRIPT);
|
||||
stringRedisTemplate.execute(redisScript,
|
||||
Arrays.asList(getInstanceExpiresKey(serviceId), getInstancesKey(serviceId)),
|
||||
String.valueOf(System.currentTimeMillis())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, String> 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<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<ServiceInstance> getInstances(String serviceId) {
|
||||
return redisRegisterManagement.getInstances(serviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getServices() {
|
||||
return redisRegisterManagement.getServices();
|
||||
|
||||
}
|
||||
}
|
|
@ -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<Registration> {
|
||||
|
||||
private final RedisRegistration redisRegistration;
|
||||
|
||||
protected RedisAutoServiceRegistration(ServiceRegistry<Registration> 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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<String, String> 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<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.context = applicationContext;
|
||||
}
|
||||
}
|
|
@ -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<Registration> {
|
||||
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> T getStatus(Registration registration) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
// 遍历所有的网络接口
|
||||
while (networkInterfaces.hasMoreElements()){
|
||||
NetworkInterface networkInterface = networkInterfaces.nextElement();
|
||||
// 在所有的接口下再遍历IP
|
||||
Enumeration<InetAddress> 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue