Talk about how to extend the namespace function based on eureka metadata

foreword

Recently, the friend department took over the supplier's microservice project and came to operate and maintain it. The technology stack of that microservice is springcloud Netflix, which just matches the microservice technology stack of the friend department. At that time, the idea of ​​​​the friend department was that since they all had the same technical system, those basic service governance components such as the registration center shared the same set. However, during the implementation process, it was found that some serviceIds of the microservice project services provided by the supplier were exactly the same as the serviceId names of their existing service serviceIds in the friend department. This is a problem, eureka service discovery is identified by serviceId

The strategy of the friend department is to change the serviceId of the supplier’s microservice project. For example, if the original serviceId is user, change it to xxx-user. However, after the adjustment, it was found that some microservices of the supplier's microservices would report errors. Later, I learned that the supplier only provided application layer code, and they did not provide some core code bases. Friends and their departments also considered replacing the serviceId of their existing services, but later found that it was not feasible, because friends and their microservices also exported some capabilities to other departments. If they are changed, they must notify the relevant parties to make changes. Side will involve some non-technical factors such as communication costs.

Later, my friend shared the plan with me. First of all, is the essence of the problem caused by the same serviceId? At first glance, it seems so. Let's consider this issue from another angle, whether it can also be said that the isolation is not done well enough. So I told my friend that if you switch eureka to nacos, the switching cost for your department will not be great. My friend said that although the code layer can be realized only by switching jars and configurations, because of version reasons, if they switch, they can only use the capabilities of nacos version 1. Secondly, other departments do not have high requirements for the performance of the registration center, but they have relatively high requirements for the stability of the registration center. If they switch, they need to do a lot of testing work. Just because of this isolation, he prefers to deploy multiple eureka .

After chatting, my friends are complaining about eureka, why can't I set up a namespace like nacos or k8s for isolation. Based on my friend's idea, I told him that I will help you expand it so that eureka also has the ability to imitate nacos namespace

Implementation ideas

Note: This article uses the microservice version springcloud Hoxton.SR3 of my friend’s company to explain

The core logic of the implementation: use the metadata that the registry has, that is, metaMap, and expand with the service discovery capabilities of the registry

core implementation logic

1. Metadata extension

a. Create a new extended configuration class

@ConfigurationProperties(prefix = "eureka.instance.ext")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EurekaInstanceProperties {
    
    

    private String namespace = Constant.META_INFO_DEAFULT_NAMESPACE;

    private String group = Constant.META_INFO_DEAFULT_GROUP;

    private boolean loadBalanceAllowCross;

}

b. Metadata extension filling

public class EurekaInstanceSmartInitializingSingleton implements SmartInitializingSingleton, ApplicationContextAware {
    
    


    private ApplicationContext applicationContext;

    @Override
    public void afterSingletonsInstantiated() {
    
    
        EurekaInstanceProperties eurekaInstanceProperties = applicationContext.getBean(EurekaInstanceProperties.class);
        EurekaInstanceConfigBean bean = applicationContext.getBean(EurekaInstanceConfigBean.class);
        Map<String, String> metadataMap = bean.getMetadataMap();
        metadataMap.put(Constant.META_INFO_KEY_NAMESPACE,eurekaInstanceProperties.getNamespace());
        metadataMap.put(Constant.META_INFO_KEY_GROUP,eurekaInstanceProperties.getGroup());

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        this.applicationContext = applicationContext;
    }
}

2. Eureka server panel modification

a. Logic transformation of status.ftlh page

Create a new /templates/eureka folder under the src/main/resource directory of the project eureka server, and copy the original status.ftlh of eureka, because we want to modify this status.ftlh. The original location of status.ftlh is placed in


Add the following content in status.ftlh

b. Fine-tune the content of EurekaController

EurekaController is the controller used by eureka service for panel display. You can build a class exactly like EurekaController in the eureka server project, which looks like


Note: You can also customize a controller yourself, anyway, this controller is used for page rendering

Fine-tune the following method

org.springframework.cloud.netflix.eureka.server.EurekaController#populateApps

The fine-tuning content is as follows

	for (InstanceInfo info : app.getInstances()) {
    
    
				String id = info.getId();
				String url = info.getStatusPageUrl();
				Map<String, String> metadata = info.getMetadata();
				String group = StringUtils.isEmpty(metadata.get("group")) ? "default" : metadata.get("group");
				String namespace = StringUtils.isEmpty(metadata.get("namespace")) ? "default" : metadata.get("namespace");
				String metaInfo = url + "_" + namespace + "_" + group;
			
				List<Pair<String, String>> list = instancesByStatus
						.computeIfAbsent(status, k -> new ArrayList<>());
				list.add(new Pair<>(id, metaInfo));
			}
		
			for (Map.Entry<InstanceInfo.InstanceStatus, List<Pair<String, String>>> entry : instancesByStatus
					.entrySet()) {
    
    
				List<Pair<String, String>> value = entry.getValue();
				InstanceInfo.InstanceStatus status = entry.getKey();
				LinkedHashMap<String, Object> instanceData = new LinkedHashMap<>();
		

				for (Pair<String, String> p : value) {
    
    
					LinkedHashMap<String, Object> instance = new LinkedHashMap<>();
					instances.add(instance);
					instance.put("id", p.first());
					String metaInfo = p.second();
					String[] metaInfoArr = metaInfo.split("_");
					String url = metaInfoArr[0];
					instance.put("url", url);
					String namespace = metaInfoArr[1];
					instance.put("namespace", namespace);
					String group = metaInfoArr[2];
					instance.put("group", group);
					boolean isHref = url != null && url.startsWith("http");
					instance.put("isHref", isHref);
				
		}
		model.put("apps", apps);

c. Panel display after transformation

Note: The client side of eureka needs to be configured as follows


3. Service Discovery Transformation

a. Rewrite com.netflix.loadbalancer.ServerList

Refer to eureka's service discovery configuration class

	@Bean
	@ConditionalOnMissingBean
	public ServerList<?> ribbonServerList(IClientConfig config,
			Provider<EurekaClient> eurekaClientProvider) {
    
    
		if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
    
    
			return this.propertiesFactory.get(ServerList.class, config, serviceId);
		}
		DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
				config, eurekaClientProvider);
		DomainExtractingServerList serverList = new DomainExtractingServerList(
				discoveryServerList, config, this.approximateZoneFromHostname);
		return serverList;
	}

We can find that we only need to modify DiscoveryEnabledNIWSServerList

@Slf4j
public class CustomDiscoveryEnabledNIWSServerList extends DiscoveryEnabledNIWSServerList {
    
    

    private final Provider<EurekaClient> eurekaClientProvider;
    private final EurekaInstanceProperties eurekaInstanceProperties;

    public CustomDiscoveryEnabledNIWSServerList(IClientConfig clientConfig, Provider<EurekaClient> eurekaClientProvider,EurekaInstanceProperties eurekaInstanceProperties) {
    
    
        this.eurekaClientProvider = eurekaClientProvider;
        this.eurekaInstanceProperties = eurekaInstanceProperties;
        initWithNiwsConfig(clientConfig);
    }

    @Override
    public List<DiscoveryEnabledServer> getInitialListOfServers(){
    
    
        List<DiscoveryEnabledServer> initialListOfServers = super.getInitialListOfServers();

        return selectListOfServersByMetaInfo(initialListOfServers);

    }

    @Override
    public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
    
    
        List<DiscoveryEnabledServer> updatedListOfServers = super.getUpdatedListOfServers();
        return selectListOfServersByMetaInfo(updatedListOfServers);

    }

    private List<DiscoveryEnabledServer> selectListOfServersByMetaInfo(List<DiscoveryEnabledServer> discoveryEnabledServerList){
    
    
        List<DiscoveryEnabledServer> discoveryEnabledServersByMetaInfo = new ArrayList<>();
        if(!CollectionUtils.isEmpty(discoveryEnabledServerList)){
    
    
            for (DiscoveryEnabledServer discoveryEnabledServer : discoveryEnabledServerList) {
    
    
                Map<String, String> metadata = discoveryEnabledServer.getInstanceInfo().getMetadata();
                String namespace = metadata.get(Constant.META_INFO_KEY_NAMESPACE);
                String group = metadata.get(Constant.META_INFO_KEY_GROUP);
                if(eurekaInstanceProperties.getNamespace().equals(namespace) &&
                        eurekaInstanceProperties.getGroup().equals(group)){
    
    
                    discoveryEnabledServersByMetaInfo.add(discoveryEnabledServer);
                }

            }
        }

        if(CollectionUtils.isEmpty(discoveryEnabledServersByMetaInfo) &&
                eurekaInstanceProperties.isLoadBalanceAllowCross()){
    
    
            log.warn("not found enabledServerList in namespace : 【{}】 and group : 【{}】. will select default enabledServerList by isLoadBalanceAllowCross is {}",eurekaInstanceProperties.getNamespace(),eurekaInstanceProperties.getGroup(),eurekaInstanceProperties.isLoadBalanceAllowCross());
            return discoveryEnabledServerList;
        }

        return discoveryEnabledServersByMetaInfo;
    }


}

b. Configure our rewritten ServerList

@Configuration
public class EurekaClientAutoConfiguration extends EurekaRibbonClientConfiguration{
    
    

    @Value("${ribbon.eureka.approximateZoneFromHostname:false}")
    private boolean approximateZoneFromHostname = false;

    @RibbonClientName
    private String serviceId = "client";;


    @Autowired
    private PropertiesFactory propertiesFactory;



    @Bean
    @Primary
    public ServerList<?> ribbonServerList(IClientConfig config,
                                    Provider<EurekaClient> eurekaClientProvider, EurekaInstanceProperties properties) {
    
    
        if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
    
    
            return this.propertiesFactory.get(ServerList.class, config, serviceId);
        }
        DiscoveryEnabledNIWSServerList discoveryServerList = new CustomDiscoveryEnabledNIWSServerList(
                config, eurekaClientProvider,properties);
        DomainExtractingServerList serverList = new DomainExtractingServerList(
                discoveryServerList, config, this.approximateZoneFromHostname);
        return serverList;
    }
}

c. Modify the default configuration of ribbonclient

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaClientAutoConfiguration.class)
public class RibbonEurekaAutoConfiguration {
    
    

}

test

Example services: gateway, consumer service, provider service, eureka

The relevant eureka configuration content is as follows

1. Gateway:

Gateway occupied port: 8000

eureka:
  instance:
    instance-id: ${
    
    spring.application.name}:${
    
    spring.cloud.client.ip-address}:${
    
    spring.application.instance_id:${
    
    server.port}}
    prefer-ip-address: ${
    
    PREFER_IP:true}  #是否选择IP注册
 #   ip-address: ${IP_ADDRESS:localhost}   #指定IP地址注册
    lease-renewal-interval-in-seconds: 5  #续约更新时间间隔(默认30秒),使得eureka及时剔除无效服务
    lease-expiration-duration-in-seconds: 10 #续约到期时间(默认90秒)
    hostname: ${
    
    HOSTNAME:${
    
    spring.application.name}}
    ext:
      namespace: dev
      group: lybgeek

  client:
    service-url:
      defaultZone: ${
    
    EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:http://localhost:8761/eureka/}
      #缩短延迟向服务端注册的时间、默认40s
    initial-instance-info-replication-interval-seconds: 10
    #提高Eureka-Client端拉取Server注册信息的频率,默认30s
    registry-fetch-interval-seconds: 5

2. Consumers

Consumer 1: Occupying port: 6614

eureka:
  instance:
    instance-id: ${
    
    spring.application.name}:${
    
    spring.cloud.client.ip-address}:${
    
    spring.application.instance_id:${
    
    server.port}}
    prefer-ip-address: ${
    
    PREFER_IP:true}  #是否选择IP注册
 #   ip-address: ${IP_ADDRESS:localhost}   #指定IP地址注册
    lease-renewal-interval-in-seconds: 5  #续约更新时间间隔(默认30秒),使得eureka及时剔除无效服务
    lease-expiration-duration-in-seconds: 10 #续约到期时间(默认90秒)
    hostname: ${
    
    HOSTNAME:${
    
    spring.application.name}}
    ext:
      group: lybgeek
      namespace: dev
  client:
    service-url:
      defaultZone: ${
    
    EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:http://localhost:8761/eureka/}
      #缩短延迟向服务端注册的时间、默认40s
    initial-instance-info-replication-interval-seconds: 10
    #提高Eureka-Client端拉取Server注册信息的频率,默认30s
    registry-fetch-interval-seconds: 5

Consumer 2: Occupying port: 6613

eureka:
  instance:
    instance-id: ${
    
    spring.application.name}:${
    
    spring.cloud.client.ip-address}:${
    
    spring.application.instance_id:${
    
    server.port}}
    prefer-ip-address: ${
    
    PREFER_IP:true}  #是否选择IP注册
 #   ip-address: ${IP_ADDRESS:localhost}   #指定IP地址注册
    lease-renewal-interval-in-seconds: 5  #续约更新时间间隔(默认30秒),使得eureka及时剔除无效服务
    lease-expiration-duration-in-seconds: 10 #续约到期时间(默认90秒)
    hostname: ${
    
    HOSTNAME:${
    
    spring.application.name}}
    ext:
      group: lybgeek6613
      namespace: dev
  client:
    service-url:
      defaultZone: ${
    
    EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:http://localhost:8761/eureka/}
      #缩短延迟向服务端注册的时间、默认40s
    initial-instance-info-replication-interval-seconds: 10
    #提高Eureka-Client端拉取Server注册信息的频率,默认30s
    registry-fetch-interval-seconds: 5

Control layer example

@RestController
@RequestMapping("instance")
public class InstanceInfoController {
    
    

    @InstancePort
    private String port;

    @InstanceName
    private String instanceName;

    @Autowired
    private InstanceServiceFeign instanceServiceFeign;

    @GetMapping("list")
    public List<InstanceInfo> list(){
    
    
        List<InstanceInfo> instanceInfos = new ArrayList<>();
        InstanceInfo comsumeInstanceInfo = InstanceInfo.builder()
                .port(port).name(instanceName).build();
        instanceInfos.add(comsumeInstanceInfo);
        InstanceInfo providerInstanceInfo = null;
        try {
    
    
            providerInstanceInfo = instanceServiceFeign.getInstanceInfo();
            instanceInfos.add(providerInstanceInfo);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }


        return instanceInfos;

    }
}

3. Provider

Provider 1: Occupied port: 6605

eureka:
  instance:
    instance-id: ${
    
    spring.application.name}:${
    
    spring.cloud.client.ip-address}:${
    
    spring.application.instance_id:${
    
    server.port}}
    prefer-ip-address: ${
    
    PREFER_IP:true}  #是否选择IP注册
 #   ip-address: ${
    
    IP_ADDRESS:localhost}   #指定IP地址注册
    lease-renewal-interval-in-seconds: 5  #续约更新时间间隔(默认30秒),使得eureka及时剔除无效服务
    lease-expiration-duration-in-seconds: 10 #续约到期时间(默认90秒)
    hostname: ${
    
    HOSTNAME:${
    
    spring.application.name}}
    ext:
      namespace: dev
      group: lybgeek
  client:
    service-url:
      defaultZone: ${
    
    EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:http://localhost:8761/eureka/}
      #缩短延迟向服务端注册的时间、默认40s
    initial-instance-info-replication-interval-seconds: 10
    #提高Eureka-Client端拉取Server注册信息的频率,默认30s
    registry-fetch-interval-seconds: 5

Provider 2: Occupying port: 6604

eureka:
  instance:
    instance-id: ${
    
    spring.application.name}:${
    
    spring.cloud.client.ip-address}:${
    
    spring.application.instance_id:${
    
    server.port}}
    prefer-ip-address: ${
    
    PREFER_IP:true}  #是否选择IP注册
 #   ip-address: ${
    
    IP_ADDRESS:localhost}   #指定IP地址注册
    lease-renewal-interval-in-seconds: 5  #续约更新时间间隔(默认30秒),使得eureka及时剔除无效服务
    lease-expiration-duration-in-seconds: 10 #续约到期时间(默认90秒)
    hostname: ${
    
    HOSTNAME:${
    
    spring.application.name}}
    ext:
      namespace: dev
      group: lybgeek6613
  client:
    service-url:
      defaultZone: ${
    
    EUREKA_CLIENT_SERVICEURL_DEFAULTZONE:http://localhost:8761/eureka/}
      #缩短延迟向服务端注册的时间、默认40s
    initial-instance-info-replication-interval-seconds: 10
    #提高Eureka-Client端拉取Server注册信息的频率,默认30s
    registry-fetch-interval-seconds: 5

Control layer example

@RestController
@RequestMapping(InstanceServiceFeign.PATH)
public class InstanceServiceFeignImpl implements InstanceServiceFeign {
    
    

    @InstancePort
    private String port;

    @InstanceName
    private String instanceName;


    @Override
    public InstanceInfo getInstanceInfo() {
    
    
        return InstanceInfo.builder()
                .name(instanceName).port(port).build();
    }
}

Access the eureka panel


Access through the gateway will find that no matter how many times you visit, the gateway can only hit the service whose namespace is dev and group is lybgeek, indicating that the isolation effect takes effect


When our service and other services do not belong to the same namespace or group, we can configure load-balance-allow-cross: true to achieve cross-namespace and group access. The configuration is as follows

eureka:
  instance:
    instance-id: ${
    
    spring.application.name}:${
    
    spring.cloud.client.ip-address}:${
    
    spring.application.instance_id:${
    
    server.port}}
    prefer-ip-address: ${
    
    PREFER_IP:true}  #是否选择IP注册
 #   ip-address: ${IP_ADDRESS:localhost}   #指定IP地址注册
    lease-renewal-interval-in-seconds: 5  #续约更新时间间隔(默认30秒),使得eureka及时剔除无效服务
    lease-expiration-duration-in-seconds: 10 #续约到期时间(默认90秒)
    hostname: ${
    
    HOSTNAME:${
    
    spring.application.name}}
    ext:
      namespace: dev
      group: lybgeek123
      load-balance-allow-cross: true

Let's visit again through the gateway


Observe the console, you will find a warning

Summarize

This article mainly imitates some ideas of nacos and expands eureka. In fact, the functions of the registration center are similar, especially after the integration of springcloud, there are basically fixed routines. This article is just a way to realize the isolation of eureka service discovery. It can also be realized by customizing the load balancing strategy through the zone and region of eureka itself. Finally, eureka instance actually has namespace attributes, but it is only integrated in springcloud and ignored

demo link

https://github.com/lyb-geek/springboot-cloud-metadata-ext

Guess you like

Origin blog.csdn.net/kingwinstar/article/details/128018295