参考资料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
为方便理解与表达,这里把 Nacos 控制台和 Nacos 注册中心称为 Nacos 服务器(就是 web 界面那个),我们编写的业务服务称为 Nacso 客户端;
由于篇幅有限,这里将源码分析分为上下两篇,其中上篇讲获取配置与事件订阅机制,下篇讲长轮询定时机制;
上篇《微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)》中提到,读取 Nacos 服务器里的配置依靠的是 NacosPropertySourceLocator.locate() 方法,我们这次的源码之旅将从这个方法开始;
@Override
public PropertySource<?> locate(Environment env) {
//【断点步入 长轮询定时机制】获取配置服务器实例,这是 Nacos 客户端提供的用于访问实现配置中心基本操作的类
ConfigService configService = nacosConfigProperties.configServiceInstance();
if (null == configService) {
log.warn("no instance of config service found, can't load config from nacos");
return null;
}
long timeout = nacosConfigProperties.getTimeout();
//Nacos 属性源生成器
nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
String name = nacosConfigProperties.getName();
//DataId 前缀(这里是 nacos-config-client)
String dataIdPrefix = nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
//没有配置 DataId 前缀则用 spring.application.name 属性的值
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
//创建复合属性源
CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
//加载共享配置
loadSharedConfiguration(composite);
//加载外部配置
loadExtConfiguration(composite);
//【断点步入】加载 Nacos 服务器上应用程序名对应的的配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
NacosPropertySourceLocator.loadApplicationConfiguration() 方法,根据 Data ID 加载配置;private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) {
//获取配置格式(这里是 yaml)
String fileExtension = properties.getFileExtension();
//获取nacosGroup(这里是 DEFAULT_GROUP)
String nacosGroup = properties.getGroup();
//如果我们配置了前缀,则按前缀获取配置文件(由于我们没配置前缀,这里是 nacos-config-client.yaml 获取不到)
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
for (String profile : environment.getActiveProfiles()) {
//这里是 nacos-config-client-dev.yaml 可以获取
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
//【断点步入】加载配置
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true);
}
}
NacosPropertySourceLocator.loadNacosDataIfPresent() 方法,判断是否更新情况;private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) {
//获取更新的配置,需要注意,NacosContextRefresher 类与事件订阅机制相关,本篇第2点将重点讨论
if (NacosContextRefresher.getRefreshCount() != 0) {
NacosPropertySource ps;
if (!isRefreshable) {
ps = NacosPropertySourceRepository.getNacosPropertySource(dataId);
}
else {
ps = nacosPropertySourceBuilder.build(dataId, group, fileExtension, true);
}
composite.addFirstPropertySource(ps);
}
else {
//【断点步入】如果我们没有更新配置,则走下面代码
NacosPropertySource ps = nacosPropertySourceBuilder.build(dataId, group,fileExtension, isRefreshable);
composite.addFirstPropertySource(ps);
}
}
NacosPropertySourceBuilder.build() 方法,加载并封装配置;NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
//【断点步入】加载 Nacos
Properties p = loadNacosData(dataId, group, fileExtension);
//将获取到的配置封装到 NacosPropertySource
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, propertiesToMap(p), new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource);
return nacosPropertySource;
}
NacosPropertySourceBuilder.loadNacosData() 方法,private Properties loadNacosData(String dataId, String group, String fileExtension) {
//省略其他代码
try {
//【断点步入】根据 dataId、group 等信息获取配置
data = configService.getConfig(dataId, group, timeout);
}
catch (NacosException e) {
log.error("get data from Nacos error,dataId:{}, ", dataId, e);
}
return EMPTY_PROPERTIES;
}
NacosConfigService.getConfigInner() 方法里成功获取到配置;
NacosContextRefresher.onApplicationEvent() 方法实现了对事件 ApplicationReadyEvent(上下文准备完毕事件) 的监听,源码如下://监听 ApplicationReadyEvent 事件
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (this.ready.compareAndSet(false, true)) {
//【断点步入】为应用程序注册 Nacos 监听器
this.registerNacosListenersForApplications();
}
}
NacosContextRefresher.registerNacosListener() 方法来实现 Nacos 监听器的注册,源码如下:private void registerNacosListener(final String group, final String dataId) {
Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
//接受配置变更的回调
@Override
public void receiveConfigInfo(String configInfo) {
refreshCountIncrement();
String md5 = "";
if (!StringUtils.isEmpty(configInfo)) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8"))).toString(16);
}
catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
}
}
refreshHistory.add(dataId, md5);
//发布 RefreshEvent 配置变更事件
applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
}
}
@Override
public Executor getExecutor() {
return null;
}
});
try {
//监听配置
configService.addListener(dataId, group, listener);
}
catch (NacosException e) {
e.printStackTrace();
}
}
applicationContext.publishEvent() 发布一个 RefreshEvent 事件;public void onApplicationEvent(ApplicationEvent event){
if (event instanceof ApplicationReadyEvent) {
//监听 ApplicationReadyEvent 事件
this.handle((ApplicationReadyEvent)event);
} else if (event instanceof RefreshEvent) {
//【断点步入 2.3】监听 RefreshEvent 事件
this.handle((RefreshEvent)event);
}
}
RefreshEventListener.handle() 方法变更配置,源码如下:public void handle(RefreshEvent event) {
if (this.ready.get()) {
log.debug("Event received " + event.getEventDesc());
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
ApplicationReadyEvent 事件;RefreshEvent 事件;
我有一个在Linux服务器上运行的ruby脚本。它不使用rails或任何东西。它基本上是一个命令行ruby脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
如何在Ruby中获取BasicObject实例的类名?例如,假设我有这个:classMyObjectSystem我怎样才能使这段代码成功?编辑:我发现Object的实例方法class被定义为returnrb_class_real(CLASS_OF(obj));。有什么方法可以从Ruby中使用它? 最佳答案 我花了一些时间研究irb并想出了这个:classBasicObjectdefclassklass=class这将为任何从BasicObject继承的对象提供一个#class您可以调用的方法。编辑评论中要求的进一步解释:假设你有对象
是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在