采用Java配置类的方式配置Nacos,动态注册微服务IP
关键词:SpringBoot、SpringCloud、Nacos、内网穿透、微服务、注册中心、Java配置类、动态IP
一、问题
最近开发项目遇到一个挺恶心的问题,就是云服务器的内网穿透问题。大家知道采用nacos注册中心的时候,微服务会向注册中心注册自己的服务,同时提供自己的IP供其他微服务来访问。下图展示了微服务向注册中心注册时的服务列表。
在这个列表的右边点击详情可以看到具体注册的微服务详细信息,如下图就是微服务pizhiku-resources-9500的具体注册信息。
这个是微服务的情况,我又在不同的云服务提供商的服务器上启动了一个微服务pizhiku-resources-file-upload-9505
可以看到这两个微服务都是向注册中心注册的自己的内网IP,所以虽然在注册中心看到了微服务,但是其他微服务通过网关是无法访问的。网关配置:
二、解决
这里我直接放解决方法,详细原理会在下文阐述。
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、保存编译运行
控制台输出:
注册中心:
如果运行在云服务器上:
至此问题解决
三、原理
核心就是要让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配置类,
那么这个东西是怎么找到的呢?
可以这样找:
按住ctrl+鼠标左键点击响应的配置
然后就进到配置这个属性的类中
翻到最上面,单击类前的小图标,选择导航到SpringBean声明
这时候就可以看到那里声明了这个类
单击这个类就可以找到相关的自动配置类NacosDiscoveryAutoConfiguration
上面这个NacosDiscoveryProperties配置类的Bean是可以点出响应的set方法的
这样就可以采用Java配置类的方式配置nacos配置。
同理所有包含spring开头包的yml配置都可以这样找到配置类。
这是倒着找,其实正常如果熟悉springBoot框架加载流程的同学就会直接去找XXXAutoConfiguration,所有带默认的配置基本上都是有一个自动配置类,直接打开就可以看到可以配哪些属性、怎么配。本质上是yml给这些配置类对象的属性赋值。
四、收获
这次探索主要还是自己对springboot框架不熟悉,其实最开始想到了他肯定是有个XXXAutoConfiguration自动配置类,但是双shift搜索nacos的时候有一堆自动配置类:
现在看来一个一个看应该是能找到的,因为见名知意嘛,yml的discovery就是nacosDiscovery相关的配置类么。
还是要去真实过几遍框架的流程,只会用不知道原理遇到奇怪的问题不好解决,也不利于个人发展。因为代码都不复杂,核心的是思想,思想很重要。
遇到问题还是要多搜索之后再去询问,因为其实某度哇、某些开发者平台上能找到的问题,都不是问题。官方文档上写的有的也不能叫做问题,前者有具体的解决方案,后者只能说没有仔细学习文档。
待常规方法搜索找不到答案的时候,再求助于真实开发的人,因为人类很大一部分智慧是保存在每个具体人的大脑中,而不是写在书上网上,这叫群体流体智慧。这就是所谓的经验。
所以我们再准备回答他人提出的问题的时候,就应该假定对方以及在常规搜索路径上无法获取答案,才会求助与你。而不是像一个键盘侠一样恢复个“没营养的问题”、“百度上有”,之类的不负责任的话。更有甚者随便截个类名就说自己找到了,也不敢发文章,仔细问才发现他连文档都没看过。这样的网络键盘侠的行为,我没要引以为戒。