springCloud服务实时上下线(基于akka集群服务)

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这个坑我有点不知道怎么填.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值