前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >带中文的yaml交给nacos配置中心管理,结果起不来了

带中文的yaml交给nacos配置中心管理,结果起不来了

作者头像
低级知识传播者
发布2023-08-30 13:12:14
1.3K0
发布2023-08-30 13:12:14
举报
文章被收录于专栏:低级Java知识传播者

问题现象

最近同事开发了一个项目,spring boot技术栈,前期开发一般使用本地配置文件,即application.yml这种,文件里包含中文注释。本地用idea调试,一点问题没有。现在准备集成nacos作为配置中心,所以就把application.yml的内容拷贝到nacos,然后重新启动应用,结果报错了,就是很多人初次使用yaml格式的时候,应该都遇到过,就这么一个问题吧,挡了我一下午:

org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1

image-20230628212857841

image-20230628212916768

定位过程

格式检查

看到这个错误,上网一搜,解决方案很多,基本是说:yml格式没对,或者是删除yml文件中的中文。

我呢,先是找了一堆在线校验yaml格式的网站,把我的文件内容拷进去,都说格式正常。

网站这里也分享两个:

https://onlineyamltools.com/validate-yaml

https://www.yamllint.com/

然后呢,因为以前遇到这个错,也没有仔细分析过,都是肉眼处理,比如直接把中文删了,或者空格弄一弄对齐一下。这次不想这么简单粗暴了,想看看到底他么啥问题,对症下药。

然后,在线网站不是分析了没问题吗,但是问题还在,我想是不是文件里有tab、空白符的混用导致的,想着idea装个yaml插件,功能估计更强,按照下载量排序,装了snakeYaml这个鬼插件,结果idea重启直接失败了,还害我重启了一次电脑,后面才发现插件好几年没更新了,然后果断卸载这鬼插件。

异常端点debug

后面想着还是debug一下算了,就打了个异常断点。

image-20230628214623405

重启服务,然后进入了异常断点:

image-20230628214823850

然后翻到上一帧,看到里面有两个字符串局部变量,变量里是文件内容,但是都只有一部分的样子,比如下图,有个变量是停在了xxljob的配置那里。由于对这些代码不理解,我以为是这一行的配置有问题,但是肉眼又看不出来,想着看看网络包算了,反正现在也没啥思路,看看从nacos拿到的是啥。

image-20230628215050350

wireshark查看网络包

直接本地wireshark抓包,抓本机和nacos服务器之间的8848端口流量即可

image-20230628215609618

这里可以简单看看,上图,首先是登录nacos,获取到一个token;

代码语言:javascript
复制
POST /nacos/v1/auth/users/login?encoding=UTF-8&username=xxx HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Accept-Charset: UTF-8
User-Agent: Java/1.8.0_202
Host: xxx:8848
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 23
password=xxx
HTTP/1.1 200 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Security-Policy: script-src 'self'
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDQU9LTCIsImV4cCI6MTY4Nzk1OTUyMn0.kGYMSgf_TF6OGHdacZXNSwKM_ir2sHs8RcCXrIA56KQ
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 28 Jun 2023 08:38:42 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{"accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDQU9LTCIsImV4cCI6MTY4Nzk1OTUyMn0.kGYMSgf_TF6OGHdacZXNSwKM_ir2sHs8RcCXrIA56KQ","tokenTtl":18000,"globalAdmin":false,"username":"xxx"}

后续拿token去获取数据,这里会请求好几次,这块没看源码,应该是和配置相关:

代码语言:javascript
复制
spring:
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        username: xxx
        password: xxx
        enabled: true
        file-extension: yaml #文件扩展名
        server-addr: xxxx:8848
        namespace: 6f51bbf8-e378-4c36-b7c4-xxxxx

一开始是请求默认配置,如:

代码语言:javascript
复制
GET /nacos/v1/cs/configs?dataId=test-data-id

接下来是:

代码语言:javascript
复制
GET /nacos/v1/cs/configs?dataId=test-data-id.yaml

再接下来是带profile的:

代码语言:javascript
复制
GET /nacos/v1/cs/configs?dataId=test-data-id-dev.yaml

我在nacos只配置了dataId=test-data-id-dev.yaml,所以前面两个都是404,只有第三个请求有数据。

在wireshark中查看数据包,发现内容都是对的,UTF8编码的中文,完全可以显示:

image-20230628220417336

然后,十六进制我也仔细看了,没啥问题:

image-20230628220619978

迷茫期

接下来,暂时不知道排查方向了,网上看了会文章,又debug了一会,还是没思路。不过网上文章不少是说编码问题的。

我也就检查了下我的启动命令,结果大吃一惊:

image-20230628220854372

按照我这么多年战斗在编码一线的习惯,一般都不会使用GBK,这里怎么是GBK呢?看了下idea里新项目的默认配置:

image-20230628221233555

因为目前手里的项目确实有比较老旧的,用GBK编码的,不知道是不是项目切换过程中,没注意,就切到新项目也还是GBK了,具体也不记得了。

总之呢,这里的编码会影响到debug启动时的-Dfile.encoding参数,我改成UTF-8后,再启动时,就变成了:

-Dfile.encoding=UTF-8

当然啦,我们除了这么改,也可以自己指定一下:

image-20230628224631615

源码分析问题原因

怎么入手分析呢,既然可以本地复现,那就还是用异常端点的方法,端点断住后,从异常栈逐级往上找,看看当前线程是怎么走到这一步的。

nacos获取配置

image-20230628222629510

进入该nacos方法:

代码语言:javascript
复制
public PropertySource<?> locate(Environment env) {
  nacosConfigProperties.setEnvironment(env);
  ConfigService configService = nacosConfigManager.getConfigService();

  long timeout = nacosConfigProperties.getTimeout();
  nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
    timeout);
  String name = nacosConfigProperties.getName();
  // 1 计算配置key
  String dataIdPrefix = nacosConfigProperties.getPrefix();
  if (StringUtils.isEmpty(dataIdPrefix)) {
   dataIdPrefix = name;
  }

  if (StringUtils.isEmpty(dataIdPrefix)) {
   dataIdPrefix = env.getProperty("spring.application.name");
  }

  CompositePropertySource composite = new CompositePropertySource(
    NACOS_PROPERTY_SOURCE_NAME);
  // 2 根据key加载配置
  loadSharedConfiguration(composite);
  loadExtConfiguration(composite);
     // 3
  loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

  return composite;
 }

接下来,进入3处,具体加载配置的地方:

代码语言:javascript
复制
private void loadApplicationConfiguration(
   CompositePropertySource compositePropertySource, String dataIdPrefix,
   NacosConfigProperties properties, Environment environment) {
  String fileExtension = properties.getFileExtension();
  String nacosGroup = properties.getGroup();
  // load directly once by default
  loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
    fileExtension, true);
  // load with suffix, which have a higher priority than the default
  loadNacosDataIfPresent(compositePropertySource,
    dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
  //1 Loaded with profile, which have a higher priority than the suffix
  for (String profile : environment.getActiveProfiles()) {
   String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
   loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
     fileExtension, true);
  }

 }

上述代码,看个大概就行(因为我也没仔细看,无从讲起,不过代码看着还行,基本见名知意),然后在1处,会根据profile去获取配置,具体到我们,就是获取dev profile的配置。

image-20230628223149884

接下来,进入下图,总算拿到配置了:

image-20230628223246657

然后,会开始解析这个data,data的解析,是要交给yaml的专门的lib来做的,而lib呢,是接收一个字节流的,如下:

代码语言:javascript
复制
 com.alibaba.cloud.nacos.parser.NacosDataYamlParser#doParse 
    
 import org.springframework.beans.factory.config.YamlMapFactoryBean;

 protected Map<String, Object> doParse(String data) {
        1、
  YamlMapFactoryBean yamlFactory = new YamlMapFactoryBean();
        2、
  yamlFactory.setResources(new ByteArrayResource(data.getBytes()));

  Map<String, Object> result = new LinkedHashMap<>();
  flattenedMap(result, yamlFactory.getObject(), EMPTY_STRING);
  return result;
 }

可以看到1处,这个类是spring的yaml解析类,不是nacos的;

2处,就是把data变成字节流(data.getBytes()),然后传给1进行解析。

而问题,恰恰出现在这里,这里的data.getBytes(),会采用平台默认字符集,也就是-Dfile.encoding中指定的字符集,因为我们是指定成了GBK,所以字节流就是GBK格式的。

而后续解析yaml的(在异常断点的上一帧),里面是用的UTF-8格式来解字节流,所以就出错了,就报了文章开头的那个错。

image-20230628224003148

我们再仔细看看这个异常的解释:

代码语言:javascript
复制

/**
 * Checked exception thrown when an input byte sequence is not legal for given
 * charset, or an input character sequence is not a legal sixteen-bit Unicode
 * sequence.
 *
 * @since 1.4
 */

public class MalformedInputException

就是说,这个字节流(GBK)在UTF8中找不到对应的合法的字符,所以就报错了。

为啥没有集成nacos的时候没问题

因为上面出问题的代码是nacos的代码,说白了就是nacos代码有点bug,不应该直接使用-Dfile.encoding编码;没集成的时候,走的是spring boot的代码,具体的,大家自行debug下吧,有点晚了,不写了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 低级知识传播者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题现象
  • 定位过程
    • 格式检查
      • 异常端点debug
        • wireshark查看网络包
          • 迷茫期
      • 源码分析问题原因
        • nacos获取配置
        • 为啥没有集成nacos的时候没问题
        相关产品与服务
        云服务器
        云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档