SpringBoot采用Java配置类的方式配置Nacos,动态注册微服务IP

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
简介: 本文是采用Java配置类的方式配置微服务的Nacos服务注册,并动态的配置spring.cloud.discovery.ip属性以打到微服务内网穿透注册至Nacos注册中心。

采用Java配置类的方式配置Nacos,动态注册微服务IP

关键词:SpringBoot、SpringCloud、Nacos、内网穿透、微服务、注册中心、Java配置类、动态IP

一、问题

最近开发项目遇到一个挺恶心的问题,就是云服务器的内网穿透问题。大家知道采用nacos注册中心的时候,微服务会向注册中心注册自己的服务,同时提供自己的IP供其他微服务来访问。下图展示了微服务向注册中心注册时的服务列表。

image-20220710005133910.png

在这个列表的右边点击详情可以看到具体注册的微服务详细信息,如下图就是微服务pizhiku-resources-9500的具体注册信息。

image-20220710010037706.png

这个是微服务的情况,我又在不同的云服务提供商的服务器上启动了一个微服务pizhiku-resources-file-upload-9505

image-20220710010621410.png

可以看到这两个微服务都是向注册中心注册的自己的内网IP,所以虽然在注册中心看到了微服务,但是其他微服务通过网关是无法访问的。网关配置:

image-20220710010907059.png

二、解决

这里我直接放解决方法,详细原理会在下文阐述。

1、在你的云服务器上启动一个Nginx,并添加以下配置:

server {
    # ....
    # 返回访问者的IP
     location /getIp{
            return 200 "$remote_addr";
      }
}

2、在微服务中注释掉yml里服务发现的相关配置:

这里的配置中心在后面配置类用到了,当然也可以自己定义自定义配置。

spring:
  application:
    name: pizhiku-resources-9500
  profiles:
    # dev test prod
    active: dev
  cloud:
    nacos:
#      discovery:
#        server-addr: 120.***.***.77:8848
#        namespace: b9134034-bca4-4979-bc14-f9e7de10924a
    # 配置中心
      config:
        server-addr: 120.***.***.77:8848
        file-extension: yaml
        namespace: b9134034-bca4-4979-bc14-f9e7de10924a
        max-retry: 10

3、在微服务中添加以下配置类:

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

/**
 * @author IPMan
 * @description: Nacos服务发现配置类
 * @date 2022/7/10
 */
@Configuration
public class NacosDiscoveryConfig {
    /**
     * nacosConfigServerAaddr yml中配置的nacos配置中心地址
     */
    @Value("${spring.cloud.nacos.config.server-addr}")
    String nacosConfigServerAaddr;
    /**
     * nacosConfignamespace yml中配置的nacos配置中心命名空间
     */
    @Value("${spring.cloud.nacos.config.namespace}")
    String nacosConfignamespace;
    /**
     * getServerInternetIP 通过Nginx获取本机外网IP,需要Nginx配合配置
     * @author IPMan
     * @date 2022/7/10
     *
     * @return java.lang.String 返回本机外网IP
     */
    private String getServerInternetIP(){
        //通过配置中心地址构造查询IP请求地址
        String url="http://"+nacosConfigServerAaddr.split(":")[0]+"/getIp";
        //调试输出,这里不推荐err的方式输出,这样仅为测试使用,推荐采用日志实现或者不输出
        System.err.println(url);
        //外网IP
        String internetIP="";
        //这里一步完成了,构造一个RestTemplate对象,通过对指定URL执行GET请求来获取响应实体
        ResponseEntity<String> response =
                new RestTemplate()
                        .getForEntity(url, String.class);
        //从响应实体对象中获取内容
        internetIP = response.getBody();
        //调试输出,这里不推荐err的方式输出,这样仅为测试使用,推荐采用日志实现或者不输出
        System.err.println(internetIP);
        return internetIP;
    }

    /**
     * nacosProperties Nacos 服务发现配置类,代替yml中spring.cloud.nacos.discovery:配置
     * @author IPMan
     * @date 2022/7/10
     *
     * @return com.alibaba.cloud.nacos.NacosDiscoveryProperties
     */
    @Bean
    public NacosDiscoveryProperties nacosProperties() {
        //new一个nacos服务发现配置对象
        NacosDiscoveryProperties properties = new NacosDiscoveryProperties();
        //设置发现注册的IP,即注册中心详情中的IP,这里很关键,默认是Inet4Address.getLocalHost(),即如果包含子网,则获取的是子网IP
        properties.setIp(getServerInternetIP());
        //设置注册中心地址
        properties.setServerAddr(nacosConfigServerAaddr);
        //设置注册中心命名空间
        properties.setNamespace(nacosConfignamespace);
        return properties;
    }

    /**
     * nacosServiceDiscovery nacos 服务发现对象,这个对象构造完成后是无法设置配置的
     * @author IPMan
     * @date 2022/7/10
     *
     * @param discoveryProperties com.alibaba.cloud.nacos.NacosDiscoveryProperties
     * @param nacosServiceManager com.alibaba.cloud.nacos.NacosServiceManager
     * @return com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery
     */
    @Bean
    public NacosServiceDiscovery nacosServiceDiscovery(
            NacosDiscoveryProperties discoveryProperties,
            NacosServiceManager nacosServiceManager) {
        return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager);
    }
}

4、保存编译运行

控制台输出:

image-20220710014951807.png

注册中心:

image-20220710015634975.png

如果运行在云服务器上:

image-20220710015602936.png

至此问题解决

三、原理

核心就是要让SpringBoot在加载Nacos服务发现配置的时候是动态加载值,而不是固定死值。

核心配置spring.cloud.nacos.discovery.ip:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 120.***.77:8848
        namespace: b9134034-bca4-4979-bc14-f9e7de10924a
        ip: ***.***.***.***

这里的实现方法有很多。

  • 通过java -jar -D** 参数传入值
  • 通过.addListeners(new AfterConfigListener());添加spring事件监听器,并设置System.setProperty("spring.cloud.nacos.discovery.ip", internetIP);属性
  • 等待其他方法

-D方式吧,其实很常用,但是如果是多服务器运行的时候,你要配个服务器都手动输入-D参数,当然可以通过自动化部署框架或脚本完成。

第二种采用监听器的方式,详细网址:

【踩坑日记】使用公网ip向nacos注册服务 - 掘金 (juejin.cn)

这种方式确实能够完成,我之前也是使用这种方式,但是不舒服啊,没办法读取yml配置的,也就是说其实他还是写死了的。

所有有了上文采用java配置类的方式配置nacos的想法。

然而网上很多文章包括官方文档都是采用yml或者原生nacosSDK方式连接。

没有找到采用Java配置类的方式配置nacos(官方文档和github我没用细看,粗略看了一下可能有的地方确实没有相关介绍)。

原理就是nacos启动配置时要配置NacosDiscoveryProperties配置类,

image-20220710023324307.png

那么这个东西是怎么找到的呢?

可以这样找:

按住ctrl+鼠标左键点击响应的配置

image-20220710023701998.png

然后就进到配置这个属性的类中

image-20220710023803123.png

翻到最上面,单击类前的小图标,选择导航到SpringBean声明

image-20220710023854246.png

这时候就可以看到那里声明了这个类

image-20220710023955974.png

单击这个类就可以找到相关的自动配置类NacosDiscoveryAutoConfiguration

image-20220710024114790.png

上面这个NacosDiscoveryProperties配置类的Bean是可以点出响应的set方法的

image-20220710024212050.png

这样就可以采用Java配置类的方式配置nacos配置。

同理所有包含spring开头包的yml配置都可以这样找到配置类。

这是倒着找,其实正常如果熟悉springBoot框架加载流程的同学就会直接去找XXXAutoConfiguration,所有带默认的配置基本上都是有一个自动配置类,直接打开就可以看到可以配哪些属性、怎么配。本质上是yml给这些配置类对象的属性赋值。

四、收获

这次探索主要还是自己对springboot框架不熟悉,其实最开始想到了他肯定是有个XXXAutoConfiguration自动配置类,但是双shift搜索nacos的时候有一堆自动配置类:

image-20220710024802586.png

现在看来一个一个看应该是能找到的,因为见名知意嘛,yml的discovery就是nacosDiscovery相关的配置类么。

还是要去真实过几遍框架的流程,只会用不知道原理遇到奇怪的问题不好解决,也不利于个人发展。因为代码都不复杂,核心的是思想,思想很重要。

遇到问题还是要多搜索之后再去询问,因为其实某度哇、某些开发者平台上能找到的问题,都不是问题。官方文档上写的有的也不能叫做问题,前者有具体的解决方案,后者只能说没有仔细学习文档。

待常规方法搜索找不到答案的时候,再求助于真实开发的人,因为人类很大一部分智慧是保存在每个具体人的大脑中,而不是写在书上网上,这叫群体流体智慧。这就是所谓的经验

所以我们再准备回答他人提出的问题的时候,就应该假定对方以及在常规搜索路径上无法获取答案,才会求助与你。而不是像一个键盘侠一样恢复个“没营养的问题”、“百度上有”,之类的不负责任的话。更有甚者随便截个类名就说自己找到了,也不敢发文章,仔细问才发现他连文档都没看过。这样的网络键盘侠的行为,我没要引以为戒。

目录
相关文章
|
10天前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
36 17
|
2天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
6天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
36 4
|
7天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
17 2
|
11天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
Java 数据安全/隐私保护
|
4天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
13天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
3天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
3天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
11 3