SpringCloud基于AP架构的实现,系统内各种缓存导致服务上线无法实时响应到网关中,在不刻意调整各种缓存配置的情况下,这里基于akka集群的节点状态来实时同步服务上下线状态到网关或各依赖服务中.
具体实现原理为:
eureka注册中心修改
1.eureka注册中心服务添加@EventListener的处理逻辑,获知到服务节点注册或者下线事件通知(EurekaInstanceCanceledEvent和EurekaInstanceCanceledEvent),缓存该事件对应服务节点的InstanceInfo.
2.eureka注册中心服务开放http接口(/instanceInfo),允许其他服务节点(如网关节点)通过该http接口查找指定服务id对应的InstanceInfo(即第一步缓存的InstanceInfo)
客户端节点修改
1.监听spring的WebServerInitializedEvent事件,并获取当前服务对应的InstanceInfo,将instanceInfo对应的服务id作为akka节点的角色,在服务启动之后将自身注册到akka集群中,
2.添加akka集群节点变更事件的监听器,响应集群节点加入退出事件,获取变更事件对应节点的角色作为服务id进行处理
1.如果事件为UP事件,获取到服务id后调用注册中心的instanceInfo接口,获取注册到eureka中对应服务的instanceInfo数据
2.将(2.1)获取到的InstanceInfo对象转换成ribbon底层使用的Server实例,并通过ribbon的调度器实例将该Server实时同步进其在线服务缓存中.
ribbon底层使用的是ZoneAwareLoadBalancer作为实际调度器,其会按照配置的ribbon.ServerListRefreshInterval的时间间隔从EurekaClient实例中获取指定服务id对应的在线服务列表.
这里通过反射调用其updateAllServerList将最新上线的节点设置进底层缓存中,同时因为ZoneAwareLoadBalancer的定时任务是全量更新一个List,所以为避免定时任务在从注册中心拉回一个空list(此时新注册的服务还未事实反应到eureka在线服务缓存中),这里通过覆盖ZoneAwareLoadBalancer中的ServerListFilter来规避这个情况,在ZoneAwareLoadBalancer的定时任务拉回在线服务列表后,在复写的ServerListFilter中重新设置上客户端获知的所有最新在线服务节点对应的Server.
具体代码为:
eureka注册中心代码:
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.converters.EntityBodyConverter;
import lombok.extern.slf4j.Slf4j;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 23, 2020 4:57:32 PM
* @Desc 些年若许,不负芳华.
*
*/
@Slf4j
@RestController
public class ServiceCacheController {
private ConcurrentHashMap<String, InstanceInfo> instanceInfoMap = new ConcurrentHashMap<String, InstanceInfo>();
private EntityBodyConverter converter = new EntityBodyConverter();
@RequestMapping("/instanceInfo")
public void instanceInfo(@RequestParam String hostPortServiceName, HttpServletResponse resp) {
try {
log.info("for key:{} and instanceInfo:{}", hostPortServiceName,
this.instanceInfoMap.get(hostPortServiceName));
converter.write(this.instanceInfoMap.get(hostPortServiceName), resp.getOutputStream(),
MediaType.APPLICATION_JSON_TYPE);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 服务下线事件
*
* @param event
*/
@EventListener
public void listenDown(EurekaInstanceCanceledEvent event) {
log.info("服务ID:" + event.getServerId().toLowerCase() + "\t" + "服务实例:" + event.getAppName() + "\t服务下线");
this.instanceInfoMap.remove(event.getServerId().toLowerCase());
}
/**
* 服务上线线事件
*
* @param event
*/
@EventListener
public void listenUp(EurekaInstanceRegisteredEvent event) {
log.info("服务ID:" + event.getInstanceInfo().getInstanceId().toLowerCase() + "\t服务注册");
this.instanceInfoMap.put(event.getInstanceInfo().getInstanceId().toLowerCase(), event.getInstanceInfo());
}
}
客户端代码:
1.入口配置注解:
@EnableAkkaServiceMonitor,配置在springBoot的启动类上,用于初始和启动监控服务
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import com.zmeng.rinascimento.dumas.cloud.serviceMonitor.RibbonBalanceChangeHandler;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 11, 2020 6:13:46 PM
* @Desc 些年若许,不负芳华.
*
*/
@Documented
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Import({ AkkaServiceMonitorConfig.class, RibbonBalanceChangeHandler.class })
public @interface EnableAkkaServiceMonitor {
String serviceName(); // 当前微服务注册的名称
String role() default "client";
}
2.akka服务注册类
用于或者注解内容并将akka启动类注册进spring容器
import java.util.Map;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import com.zmeng.rinascimento.dumas.cloud.serviceMonitor.AkkaMonitorService;
import lombok.extern.slf4j.Slf4j;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 22, 2020 11:44:55 AM
* @Desc 些年若许,不负芳华.
*
* 初始化基于akka的注册服务监控
*
*/
@Slf4j
@Configuration
public class AkkaServiceMonitorConfig implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 扫描注解
Map<String, Object> annotationAttributes = importingClassMetadata
.getAnnotationAttributes(EnableAkkaServiceMonitor.class.getName());
String serviceName = annotationAttributes.get("serviceName").toString();
String roleName = annotationAttributes.get("role").toString();
log.info("serviceName:{} and roleName:{}", serviceName, roleName);
this.registerClientConfiguration(registry, serviceName, roleName);
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object serviceName, Object roleName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(AkkaMonitorService.class);
builder.addConstructorArgValue(roleName);
builder.addConstructorArgValue(serviceName);
registry.registerBeanDefinition(serviceName + ".AkkaMonitorService", builder.getBeanDefinition());
}
}
3.akka集群启动类
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.Behaviors;
import lombok.extern.slf4j.Slf4j;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 22, 2020 11:28:26 AM
* @Desc 些年若许,不负芳华.
*
* akka Monitor Service
*
*/
@Slf4j
public class AkkaMonitorService implements ApplicationListener<WebServerInitializedEvent> {
@Autowired
private RibbonBalanceChangeHandler balanceChangeHandler;
@Autowired
Environment environment;
@Autowired
private ApplicationInfoManager manager;
@Value("${spring.profiles}")
private String profiles;
private String role;
private String serviceName;
private ActorSystem<Void> system;
public AkkaMonitorService(String role, String serviceName) {
this.role = role;
this.serviceName = serviceName;
log.info("AkkaMonitorService init:{}-{}", this.role, this.serviceName);
}
private Behavior<Void> rootBehavior() {
return Behaviors.setup(context -> {
// Create an actor that handles cluster domain events
if (!role.contentEquals("eureka")) {
context.spawn(ClusterListener.create(this.balanceChangeHandler), "ClusterListener");
}
return Behaviors.empty();
});
}
@PreDestroy
private void shutdown() {
if (this.system != null) {
this.system.terminate();
}
}
private void startup() {
InstanceInfo info = this.manager.getInfo();
String serverId = info.getInstanceId();
log.info("serverId:{}", serverId.toLowerCase());
Map<String, Object> overrides = new HashMap<>();
// overrides.put("akka.remote.artery.canonical.port", port);
overrides.put("akka.cluster.roles", Collections.singletonList(serverId.toLowerCase()));
Config config = ConfigFactory.parseMap(overrides).withFallback(ConfigFactory.load("monitor_" + this.profiles));
// Create an Akka system
system = ActorSystem.create(rootBehavior(), "serviceMonitorSystem", config);
log.info("system is:{}", system);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
this.startup();
}
}
4.Ribbon底层调度器响应服务节点上下线的处理器
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import javax.annotation.PostConstruct;
import javax.inject.Provider;
import org.dumas.common.utils.GU;
import org.reflections.ReflectionUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.ribbon.RibbonProperties;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.eureka.ZoneUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerListFilter;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import lombok.extern.slf4j.Slf4j;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 22, 2020 11:42:13 AM
* @Desc 些年若许,不负芳华.
*
* 处理从ioc容器中获取指定服务对应的ILoadBalacne,并处理相关上下线操作
*
*/
@Slf4j
public class RibbonBalanceChangeHandler implements ApplicationContextAware, ServiceChangeHandler {
private ApplicationContext applicationContext;
@Autowired
private Provider<EurekaClient> eurekaClientProvider;
@Value("${eureka.client.serviceUrl.defaultZone}")
private String eurekaServerUrl;
@Value("${ribbon.eureka.approximateZoneFromHostname:false}")
private boolean approximateZoneFromHostname = false;
private SpringClientFactory springClientFactory;
private InstanceInfoTaker infoTaker;
private LoadingCache<String, RibbonProperties> ribbonPropertiesCache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, RibbonProperties>() {
@Override
public RibbonProperties load(String key) throws Exception {
return initRibbonProperties(key);
}
});
private LoadingCache<String, ILoadBalancer> loadBalancerCache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, ILoadBalancer>() {
@Override
public ILoadBalancer load(String key) throws Exception {
return findILoadBalancer(key);
}
});
private Method invokeMethod;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public ILoadBalancer findILoadBalancer(String serviceName) {
ILoadBalancer loadBalancer = springClientFactory.getLoadBalancer(serviceName);
if (loadBalancer instanceof DynamicServerListLoadBalancer) {
((DynamicServerListLoadBalancer) loadBalancer).setFilter(new CheckIsOnlineServerListFilter<Server>(
((DynamicServerListLoadBalancer) loadBalancer).getFilter()));
}
return loadBalancer;
}
public RibbonProperties initRibbonProperties(String serviceName) {
return RibbonProperties.from(this.springClientFactory.getClientConfig(serviceName));
}
public static void main(String[] args) throws MalformedURLException {
Optional<Method> findAny = ReflectionUtils.getAllMethods(DynamicServerListLoadBalancer.class,
method -> method.getName().contentEquals("updateAllServerList")).stream().findAny();
System.out.println(findAny);
}
@PostConstruct
public void onPostConstruct() {
this.invokeMethod = ReflectionUtils.getAllMethods(DynamicServerListLoadBalancer.class,
method -> method.getName().contentEquals("updateAllServerList")).stream().findAny().get();
this.invokeMethod.setAccessible(true);
this.infoTaker = new InstanceInfoTaker(this.eurekaServerUrl.replace("/eureka", "/instanceInfo"));
this.springClientFactory = this.applicationContext.getBean(SpringClientFactory.class);
}
private LoadingCache<String, InstanceInfo> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, InstanceInfo>() {
@Override
public InstanceInfo load(String key) throws Exception {
return infoTaker.findIntanceInfo(key);
}
});
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void onChange(String serverId, boolean isOnline) {
InstanceInfo info = this.cache.getUnchecked(serverId);
if (isOnline) {
info.setStatus(InstanceStatus.UP);
} else {
info.setStatus(InstanceStatus.DOWN);
}
ILoadBalancer loadBalancer = this.loadBalancerCache.getUnchecked(info.getAppName().toLowerCase());
ZoneAwareLoadBalancer balancer = (ZoneAwareLoadBalancer) loadBalancer;
List<DiscoveryEnabledServer> changeToServer = this.changeToServer(info);
updateServerFilter(balancer, changeToServer.get(0), isOnline);
log.info("===================update before:{}--{}", serverId, balancer.hashCode());
Lock lock = balancer.lockAllServerList(true);
try {
List<Server> newServerList = new ArrayList<Server>(balancer.getAllServers());
newServerList.addAll(changeToServer);
if (balancer.getFilter() != null) {
newServerList = balancer.getFilter().getFilteredListOfServers(newServerList);
}
log.info("newServerList are:{}", newServerList);
try {
this.invokeMethod.invoke(balancer, newServerList);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return;
}
} finally {
lock.unlock();
}
log.info("===================update after:{}", serverId);
List<Server> allServer = loadBalancer.getAllServers();
if (GU.notNullAndEmpty(allServer)) {
for (Server server : allServer) {
// 如果服务有设置zone,此处获取的可能并不是所有的实例
log.info("ALL:{}:{}", server.getHostPort(), info.getAppName());
}
}
List<Server> reachableServers = loadBalancer.getReachableServers();
if (GU.notNullAndEmpty(reachableServers)) {
for (Server server : reachableServers) {
// 如果服务有设置zone,此处获取的可能并不是所有的实例
log.info("CURRENT:{}:{}", server.getHostPort(), info.getAppName());
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void updateServerFilter(ZoneAwareLoadBalancer balancer, DiscoveryEnabledServer discoveryEnabledServer,
boolean isOnline) {
log.info("server:{}", discoveryEnabledServer);
ServerListFilter filter = balancer.getFilter();
if (filter instanceof CheckIsOnlineServerListFilter) {
((CheckIsOnlineServerListFilter) filter).serverChange(discoveryEnabledServer.getId(),
discoveryEnabledServer, isOnline);
}
}
private List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers, String serviceName) {
List<DiscoveryEnabledServer> result = new ArrayList<>();
RibbonProperties ribbon = ribbonPropertiesCache.getUnchecked(serviceName);
boolean isSecure = ribbon.isSecure(true);
boolean shouldUseIpAddr = ribbon.isUseIPAddrForServer();
for (DiscoveryEnabledServer server : servers) {
result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr, this.approximateZoneFromHostname));
}
return result;
}
public List<DiscoveryEnabledServer> changeToServer(InstanceInfo instanceInfo) {
DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false, true);
// Get availabilty zone for this instance.
EurekaClientConfig clientConfig = eurekaClientProvider.get().getEurekaClientConfig();
String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
String instanceZone = InstanceInfo.getZone(availZones, instanceInfo);
server.setZone(instanceZone);
return setZones(Arrays.asList(server), instanceInfo.getAppName().toLowerCase());
}
}
class DomainExtractingServer extends DiscoveryEnabledServer {
private String id;
@Override
public String getId() {
return id;
}
@Override
public void setId(String id) {
this.id = id;
}
DomainExtractingServer(DiscoveryEnabledServer server, boolean useSecurePort, boolean useIpAddr,
boolean approximateZoneFromHostname) {
// host and port are set in super()
super(server.getInstanceInfo(), useSecurePort, useIpAddr);
if (server.getInstanceInfo().getMetadata().containsKey("zone")) {
setZone(server.getInstanceInfo().getMetadata().get("zone"));
} else if (approximateZoneFromHostname) {
setZone(ZoneUtils.extractApproximateZone(server.getHost()));
} else {
setZone(server.getZone());
}
setId(extractId(server));
setAlive(server.isAlive());
setReadyToServe(server.isReadyToServe());
}
private String extractId(Server server) {
if (server instanceof DiscoveryEnabledServer) {
DiscoveryEnabledServer enabled = (DiscoveryEnabledServer) server;
InstanceInfo instance = enabled.getInstanceInfo();
if (instance.getMetadata().containsKey("instanceId")) {
return instance.getHostName() + ":" + instance.getMetadata().get("instanceId");
}
}
return super.getId();
}
}
5.复写的ServerListFilter类
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerListFilter;
import lombok.extern.slf4j.Slf4j;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 24, 2020 2:57:52 PM
* @Desc 些年若许,不负芳华.
*
*/
@Slf4j
public class CheckIsOnlineServerListFilter<T extends Server> implements ServerListFilter<T> {
private Map<String, T> serverState = new ConcurrentHashMap<>();
private ServerListFilter<T> superFilter;
public CheckIsOnlineServerListFilter(ServerListFilter<T> superFilter) {
this.superFilter = superFilter;
}
@Override
public List<T> getFilteredListOfServers(List<T> servers) {
log.debug("server:{} and current:{}", servers, serverState);
Set<String> collect = servers.stream().map(Server::getId).filter(ins -> this.serverState.containsKey(ins))
.collect(Collectors.toSet());
this.serverState.values().stream().filter(ser -> !collect.contains(ser.getId())).forEach(server -> {
servers.add(server);
});
List<T> filteredListOfServers = this.superFilter.getFilteredListOfServers(servers);
return filteredListOfServers;
}
public void serverChange(String serverId, T server, boolean isOnline) {
if (isOnline) {
this.serverState.put(serverId, server);
} else {
this.serverState.remove(serverId);
}
}
}
6.akka集群事件监听器
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.AbstractBehavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.Receive;
import akka.cluster.ClusterEvent;
import akka.cluster.Member;
import akka.cluster.typed.Cluster;
import akka.cluster.typed.Subscribe;
import lombok.extern.slf4j.Slf4j;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 22, 2020 11:28:18 AM
* @Desc 些年若许,不负芳华.
*
*/
@Slf4j
public final class ClusterListener extends AbstractBehavior<ClusterListener.Event> {
interface Event {
}
// internal adapted cluster events only
private static final class ReachabilityChange implements Event {
final ClusterEvent.ReachabilityEvent reachabilityEvent;
ReachabilityChange(ClusterEvent.ReachabilityEvent reachabilityEvent) {
this.reachabilityEvent = reachabilityEvent;
}
}
private static final class MemberChange implements Event {
final ClusterEvent.MemberEvent memberEvent;
MemberChange(ClusterEvent.MemberEvent memberEvent) {
this.memberEvent = memberEvent;
}
}
public static Behavior<Event> create(ServiceChangeHandler balanceChangeHandler) {
return Behaviors.setup(context -> new ClusterListener(context, balanceChangeHandler));
}
private ServiceChangeHandler balanceChangeHandler;
private ClusterListener(ActorContext<Event> context, ServiceChangeHandler balanceChangeHandler) {
super(context);
this.balanceChangeHandler = balanceChangeHandler;
Cluster cluster = Cluster.get(context.getSystem());
// selfMember = cluster.selfMember();
ActorRef<ClusterEvent.MemberEvent> memberEventAdapter = context.messageAdapter(ClusterEvent.MemberEvent.class,
MemberChange::new);
cluster.subscriptions().tell(Subscribe.create(memberEventAdapter, ClusterEvent.MemberEvent.class));
ActorRef<ClusterEvent.ReachabilityEvent> reachabilityAdapter = context
.messageAdapter(ClusterEvent.ReachabilityEvent.class, ReachabilityChange::new);
cluster.subscriptions().tell(Subscribe.create(reachabilityAdapter, ClusterEvent.ReachabilityEvent.class));
}
@Override
public Receive<Event> createReceive() {
return newReceiveBuilder().onMessage(ReachabilityChange.class, this::onReachabilityChange)
.onMessage(MemberChange.class, this::onMemberChange).build();
}
private Behavior<Event> onReachabilityChange(ReachabilityChange event) {
if (event.reachabilityEvent instanceof ClusterEvent.UnreachableMember) {
log.info("Member detected as unreachable: {}", event.reachabilityEvent.member());
} else if (event.reachabilityEvent instanceof ClusterEvent.ReachableMember) {
log.info("Member back to reachable: {}", event.reachabilityEvent.member());
}
return this;
}
private Behavior<Event> onMemberChange(MemberChange event) {
if (event.memberEvent instanceof ClusterEvent.MemberUp) {
log.info("Member is up: {}", event.memberEvent.member());
this.memberChangeNotify(event.memberEvent.member(), true);
} else if (event.memberEvent instanceof ClusterEvent.MemberRemoved) {
log.info("Member is removed: {} after {}", event.memberEvent.member(),
((ClusterEvent.MemberRemoved) event.memberEvent).previousStatus());
this.memberChangeNotify(event.memberEvent.member(), false);
}
return this;
}
private void memberChangeNotify(Member member, boolean online) {
String serverId = member.getRoles().stream().findAny().get();
this.balanceChangeHandler.onChange(serverId, online);
}
}
7.其余辅助类
import java.io.InputStream;
import javax.ws.rs.core.MediaType;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.converters.EntityBodyConverter;
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 23, 2020 5:19:07 PM
* @Desc 些年若许,不负芳华.
*
* 从eureka中获取指定服务id的InstanceInfo
*
*/
public class InstanceInfoTaker {
private String url;
private EntityBodyConverter converter = new EntityBodyConverter();
private static final String format = "%s?hostPortServiceName=%s";
public InstanceInfoTaker(String url) {
this.url = url;
}
public InstanceInfo findIntanceInfo(String serverId) {
try {
HttpResponse<InputStream> asBinary = Unirest.get(String.format(format, this.url, serverId)).asBinary();
if (asBinary.getStatus() == 200) {
return (InstanceInfo) converter.read(asBinary.getBody(), InstanceInfo.class,
MediaType.APPLICATION_JSON_TYPE);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws UnirestException {
InstanceInfoTaker instanceInfoTaker = new InstanceInfoTaker(
"http://127.0.0.1:8761/instanceInfo");
InstanceInfo findIntanceInfo = instanceInfoTaker.findIntanceInfo("127.0.0.1-gateway:5555");
System.out.println(findIntanceInfo);
}
}
/**
*
* @say little Boy, don't be sad.
* @name Rezar
* @time Jul 22, 2020 11:53:54 AM
* @Desc 些年若许,不负芳华.
* 服务节点上下线事件处理回调接口
*/
public interface ServiceChangeHandler {
public void onChange(String serverId, boolean online);
}
akka配置:
分别对应monitor_${spring.profiles}.conf
三个环境的文件:dev/test/prod
monitor_dev.conf
monitor_test.conf
monitor_prod.conf
akka {
loglevel = info
actor {
provider = cluster
}
remote {
artery {
canonical.hostname = "客户端服务节点的ip"
canonical.port = 25251
}
}
cluster {
seed-nodes = [
"akka://serviceMonitorSystem@172.20.2.97:25251"
]
}
}
种子节点(seed-nodes)一般配置在网关节点就行(重启可能性小),akka节点的canonical.port最好配置,对akka集群如何在节点宕机后仍然正常服务有点不清楚.所以服务部署的时候需要使用kill pid,别使用kill -9 pid,kill pid允许服务处理关闭事件,akka这个坑我有点不知道怎么填.