springboot 网关实时刷新服务列表实现

2021-11-04 18:46:02
实现原理:

	每个微服务spring会启动单独的AnnotatioinApplicationContext,该context会自动初始ILoadBalancer等类

	其中ILoadBalancer包含
	1.ServerList: 
		ServerList可用于获取实际的eureka中的服务列表
	2.ServerListUpdater:
		用于定时调用ServerList的刷新方法获取最新的服务列表更新ILoadBalancer中的Server
		
	这里通过 LoadBalancerConfiguration 配置类,在postCons
	
	@Configuration
	@Slf4j
	public class LoadBalancerConfiguration {  
		
		@Autowired
		private SpringClientFactory clientFactory;
		@Value("${ribbon.eureka.approximateZoneFromHostname:false}")
		private boolean approximateZoneFromHostname = false;
		
		
		@PostConstruct
		public void postCons() {
			this.clientFactory.setConfigurations(
					Arrays.asList(
							new RibbonClientSpecification(
									"default.ILoadBalancer.visiter", 
									new Class<?>[] {LoadBalancerConfiguration.class})));
		}
	
		private Optional<EurekaHttpClient> flectReadEurekaHttpClient(
				EurekaClient eurekaClient) {
			if (AopUtils.isAopProxy(eurekaClient)) {
				try {
					eurekaClient = (EurekaClient) AopTargetUtils.getTarget(eurekaClient);
				} catch (Exception e) {
					log.error("error get target from aop proxy:{}", e);
				}
			}
			EurekaHttpClient queryClient = null;
			if (eurekaClient instanceof DiscoveryClient) {
				DiscoveryClient discoveryClient = (DiscoveryClient) eurekaClient;
				try {
					Object eurekaTransport = 
							FieldUtils.readField(
									discoveryClient,
									"eurekaTransport",
									true);
					queryClient =
							(EurekaHttpClient) FieldUtils.readField(
									eurekaTransport, 
									"queryClient",
									true);
					log.info("success flect read EurekaHttpClient:{}", queryClient);
				} catch (Exception e) {
					log.error("error flect read EurekaHttpClient:{}", e);
				}
			}
			return Optional.ofNullable(queryClient);
		}
		
		@Bean
		@Lazy
		public ServerList<?> ribbonServerList(
				IClientConfig config,
				Provider<EurekaClient> eurekaClientProvider) {
			EurekaClient eurekaClient = eurekaClientProvider.get();
			Optional<EurekaHttpClient> flectReadEurekaHttpClient = 
					this.flectReadEurekaHttpClient(eurekaClient);
			if (!flectReadEurekaHttpClient.isPresent()) {
				log.warn("can not read EurekaHttpClient from this kind eurekaClient:{}", eurekaClient);
				return null;
			}
			CustomerDiscoveryEnabledNIWSServerList discoveryServerList = 
					new CustomerDiscoveryEnabledNIWSServerList(
							config, 
							eurekaClientProvider);
			EurekaHttpClient eurekaHttpClient = flectReadEurekaHttpClient.get();
			CustomerDomainExtractingServerList serverList = 
					new CustomerDomainExtractingServerList(
							discoveryServerList, 
							eurekaHttpClient,
							config,
							this.approximateZoneFromHostname);
			return serverList;
		}
	
	
	}
	
	这个方法里,向clientFactory(SpringClientFactory)中设置了默认配置类,用于自定义初始化ServerList的ribbonServerList方法
	
	自定义ServerList的目的有:
	
	1.获取到EurekaHttpClient,用于将EurekaClientInstanceInfo的instanceId转回注册EureKaServer成功的InstanceInfo
	2.通过DiscoveryEnabledNIWSServerList的createServer方法,把EurekaServer获取到的InstanceInfo类型数据转为ILoadBalancer使用的Server
	
	所以流程为:
	
	1.任一系统启动的时候,监听 spring 启动后的 WebServerInitializedEvent 事件,该事件发布后,当前实例已经注册到 eureka server
	2.WebServerInitializedEvent事件的处理方法中,获取ApplicationInfoManager中标识当前eureka客户端信息的InstanceInfo
		
			@Autowired
			private ApplicationInfoManager manager;
		
			@Override
			public String serverId() {
				InstanceInfo info = this.manager.getInfo();
				return info.getInstanceId();
			}
			获取到 instanceId
			
	3.通过事件广播途径,告知集群当前系统启动完成,实例id为 instanceId
		如:ServerOnlineEvent:
			private String serviceName;
			private String instanceId;
	
	4.网关服务监听集群实例变更事件ServerOnlineEvent
		获取变更服务(serviceName)当前仍然在线的服务列表(instanceId[]),发布ServiceServerListChangedEvent
	
		@Data
		@NoArgsConstructor
		@AllArgsConstructor
		public class ServiceServerListChangedEvent {
			
			private String serviceName;
			private List<ApplicationInstanceInfoServerId> instanceServerInfoIds;
		
		}
	
	5.ServiceInstanceInfoChangedListener在网关服务内部监听ServiceServerListChangedEvent
		通过SpringClientFactory获取对应 serviceName 对应的ILoadBalancer负载器
		构建LoadBalancerVisitor
		
		LoadBalancerVisitor 进行的操作有:
		
		1.停用ILoadBalancer默认的ServerListUpdater,完全切换到事件监听上
		2.调用CustomerDomainExtractingServerList的createServer方法,将 instanceId 转为 具体注册
			在Eureka Server中的InstanceInfo,并继续由CustomerDiscoveryEnabledNIWSServerList转为
			负载器内部使用的DiscoveryEnabledServer
		3.调用ILoadBalancer的setServersList更新服务列表
		
	完
	
	weike-gateway当前使用akka集群监控服务列表的变更事件
		
附录:

1.
	import javax.inject.Provider;

	import com.netflix.appinfo.InstanceInfo;
	import com.netflix.client.config.CommonClientConfigKey;
	import com.netflix.client.config.DefaultClientConfigImpl;
	import com.netflix.client.config.IClientConfig;
	import com.netflix.discovery.EurekaClient;
	import com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList;
	import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
	
	/**
	 * 
	 * @say little Boy, don't be sad.
	 * @name Rezar
	 * @time 2021-11-04 11:50:47
	 * @Desc 些年若许,不负芳华.
	 *
	 */
	public class CustomerDiscoveryEnabledNIWSServerList extends DiscoveryEnabledNIWSServerList {
	
		private boolean isSecure;
		private boolean shouldUseIpAddr;
	
		@SuppressWarnings("deprecation")
		public CustomerDiscoveryEnabledNIWSServerList(
				IClientConfig clientConfig,
				Provider<EurekaClient> eurekaClientProvider) {
			super(clientConfig, eurekaClientProvider);
			this.isSecure = 
					Boolean.parseBoolean("" + clientConfig.getProperty(CommonClientConfigKey.IsSecure, "false"));
			this.shouldUseIpAddr = 
					clientConfig.getPropertyAsBoolean(
							CommonClientConfigKey.UseIPAddrForServer,
							DefaultClientConfigImpl.DEFAULT_USEIPADDRESS_FOR_SERVER);
		}
	
		DiscoveryEnabledServer createServer(
				InstanceInfo instanceInfo) {
			return super.createServer(
					instanceInfo, 
					isSecure, 
					shouldUseIpAddr);
		}
	
	}
		
2.
	import java.util.concurrent.TimeUnit;

	import org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList;
	
	import com.netflix.appinfo.InstanceInfo;
	import com.netflix.client.config.IClientConfig;
	import com.netflix.discovery.shared.transport.EurekaHttpClient;
	import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
	
	import lombok.extern.slf4j.Slf4j;
	
	/**
	 * 
	 * @say little Boy, don't be sad.
	 * @name Rezar
	 * @time 2021-11-04 12:03:59
	 * @Desc 些年若许,不负芳华.
	 *
	 */
	@Slf4j
	public class CustomerDomainExtractingServerList extends DomainExtractingServerList {
		
		private CustomerDiscoveryEnabledNIWSServerList serverList;
		private EurekaHttpClient queryClient;
	
		public CustomerDomainExtractingServerList(
				CustomerDiscoveryEnabledNIWSServerList list, 
				EurekaHttpClient registrationClient,
				IClientConfig clientConfig,
				boolean approximateZoneFromHostname) {
			super(list, clientConfig, approximateZoneFromHostname);
			this.serverList = list;
			this.queryClient = registrationClient;
		}
		
		DiscoveryEnabledServer createServer(
				String instanceId) {
			if (queryClient == null) {
				return null;
			}
			try {
				log.info("try getInstance by instanceId:{}", instanceId);
				InstanceInfo instanceInfo = 
						this.instanceInfo(instanceId, 0);
				log.info("for instanceId:{} and instanceInfo:{}", instanceId, instanceInfo);
				return this.serverList.createServer(instanceInfo);
			} catch (Exception e) {
				log.error("error createServer:{}", e);
			}
			return null;
		}
		
		private InstanceInfo instanceInfo(
				String instanceId,
				int curTimes) {
			if (curTimes >= 3) {
				return null;
			}
			try {
				return this.queryClient.getInstance(instanceId)
						.getEntity();
			} catch (Exception e) {
				log.error("error getInstance:{}", e);
			}
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				log.warn("InterruptedException:{}", e);
			}
			return instanceInfo(instanceId, curTimes + 1);
		}
	}
	
3.
	import java.util.ArrayList;
	import java.util.HashSet;
	import java.util.List;
	import java.util.Set;
	import java.util.function.Function;
	import java.util.stream.Collectors;
	
	import com.netflix.loadbalancer.ServerListFilter;
	import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
	import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
	import com.zmeng.weike.gateway.infa.serverRealTimeUpdate.vo.ApplicationInstanceInfoServerId;
	
	import lombok.Getter;
	import lombok.extern.slf4j.Slf4j;
	
	/**
	 * 
	 * @say little Boy, don't be sad.
	 * @name Rezar
	 * @time 2021-11-03 03:10:59
	 * @Desc 些年若许,不负芳华.
	 *
	 */
	@Slf4j
	public class LoadBalancerVisitor {
		
		@Getter
		private String serviceId;
		private ZoneAwareLoadBalancer<DiscoveryEnabledServer> balancer;
		private Function<ApplicationInstanceInfoServerId, DiscoveryEnabledServer> serverApply;
		
		private Set<DiscoveryEnabledServer> allOnlineInstanceServer = new HashSet<>();
		
		public LoadBalancerVisitor(
				ZoneAwareLoadBalancer<DiscoveryEnabledServer> balancer) {
			balancer.setFilter(
					buildCustomerVisitableFilter(
							balancer.getName(),
							balancer.getFilter()));
			// 停用刷新
			balancer.stopServerListRefreshing();
			this.serviceId = balancer.getName();
			this.balancer = balancer;
			CustomerDomainExtractingServerList serverList = 
					(CustomerDomainExtractingServerList) this.balancer.getServerListImpl();
			this.serverApply = 
					serverId -> serverList.createServer(serverId.getInstanceInfoServerId());
			log.info(
					"visitor configed for service name:{}",
					balancer.getName());
		}
		
		public synchronized void serverListChanged(
				List<ApplicationInstanceInfoServerId> onlineServerList) {
			this.allOnlineInstanceServer = 
					onlineServerList.stream()
					.map(this.serverApply::apply)
					.collect(Collectors.toSet());
			
			this.balancer.setServersList(
					new ArrayList<>(this.allOnlineInstanceServer));
			log.info(
					"service:{} online server list:{}",
					this.serviceId, 
					allOnlineInstanceServer);
		}
		
		private ServerListFilter<DiscoveryEnabledServer> buildCustomerVisitableFilter(
				String clientName,
				ServerListFilter<DiscoveryEnabledServer> filter) {
			return new ServerListFilter<DiscoveryEnabledServer>() {
				@Override
				public List<DiscoveryEnabledServer> getFilteredListOfServers(
						List<DiscoveryEnabledServer> servers) {
					log.info("ServerListFilter clientName:{}-{}", clientName, allOnlineInstanceServer);
					return filter.getFilteredListOfServers(new ArrayList<>(allOnlineInstanceServer));
				}
			};
		} 
	}
	
4.
	import java.util.List;
	import java.util.Map;
	import java.util.Optional;
	import java.util.concurrent.ConcurrentHashMap;
	
	import org.springframework.beans.factory.annotation.Autowired;
	import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
	import org.springframework.context.event.EventListener;
	import org.springframework.stereotype.Component;
	
	import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
	import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
	import com.zmeng.weike.gateway.infa.serverRealTimeUpdate.vo.ApplicationInstanceInfoServerId;
	import com.zmeng.weike.gateway.infa.serverRealTimeUpdate.vo.ServiceServerListChangedEvent;
	
	import lombok.extern.slf4j.Slf4j;
	
	/**
	 * 
	 * @say little Boy, don't be sad.
	 * @name Rezar
	 * @time 2021-11-03 03:57:11
	 * @Desc 些年若许,不负芳华.
	 *
	 */
	@Component
	@Slf4j
	public class ServiceInstanceInfoChangedListener {
		
		@Autowired
		private SpringClientFactory clientFactory;
		
		private final Map<String, LoadBalancerVisitor> visitorMap = new ConcurrentHashMap<>();
		
		@SuppressWarnings("unchecked")
		private void configLoadBalancer(
				Object bean) {
			if (bean instanceof ZoneAwareLoadBalancer) {
				ZoneAwareLoadBalancer<DiscoveryEnabledServer> tmp = 
						(ZoneAwareLoadBalancer<DiscoveryEnabledServer>) bean;
				if (this.visitorMap.containsKey(tmp.getName())) {
					return;
				}
				LoadBalancerVisitor visitor = 
						new LoadBalancerVisitor(tmp);
				this.visitorMap.put(visitor.getServiceId(), visitor);
			}
		}
		
		public synchronized Optional<LoadBalancerVisitor> getBalancer(
				String serverId) {
			if (!this.visitorMap.containsKey(serverId)) {
				this.configLoadBalancer(
						this.clientFactory.getLoadBalancer(serverId));
			}
			return Optional.ofNullable(
					this.visitorMap.get(serverId));
		}
		
		@EventListener
		public void onChanged(
				ServiceServerListChangedEvent event) {
			log.info("ServiceServerListChangedEvent:{}", event);
			String serviceId = event.getServiceName();
			List<ApplicationInstanceInfoServerId> onlineServerList = 
					event.getInstanceServerInfoIds();
			this.getBalancer(serviceId)
			.ifPresent(visitor -> {
				visitor.serverListChanged(onlineServerList);
			});
		}
	
	}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个用于构建独立的、生产级别的基于Spring的应用程序的框架,而Spring Security是Spring的一个强大的安全框架,用于身份验证和授权。OAuth2是一种用于授权的开放标准,JWT是一种用于在用户和服务器之间传递安全信息的开放标准。这些技术的整合可以帮助我们构建一个完善的认证服务器,和微服务之间的权限认证系统。 首先,我们可以使用Spring Boot整合Spring Security来构建认证服务器。通过Spring Security提供的各种认证和授权功能,我们可以实现用户的身份验证和授权管理。然后,结合OAuth2的开放标准,我们可以实现用户的授权码、密码、客户端凭证和刷新令牌等授权方式,从而提高系统的安全性和灵活性。 接着,我们可以利用Spring Boot搭建来统一管理微服务之间的访问权限。通过,我们可以对各个微服务进行统一的权限验证和访问控制,从而实现统一的安全管理和监控。 最后,我们可以在微服务中集成JWT来实现基于令牌的安全验证。JWT可以帮助我们在客户端和服务器之间传递安全信息,并通过签名和加密保障信息的安全性。 总之,通过Spring Boot整合Spring Security、OAuth2和JWT,我们可以构建一个完善的认证服务器、和微服务之间的权限认证系统,从而实现系统的安全可靠和权限灵活控制。这将有助于我们构建高效、安全的微服务架构,并为用户提供更加可靠的服务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值