故事是这样的,最近我开发了一个java服务,小伙伴开发了一个python服务,我们的服务都注册到Nacos。(Nacos是一个服务注册和发现中间件,注册到Nacos的服务通过服务名字就可以互相调用了,彼此不需要知晓对方服务所在的IP和端口)。服务调用的代码大概是这样的:
@FeignClient(name = "execute-code-service")
public interface ExecuteTestCodeClient {
@PostMapping("/api/v1/execute/code")
ExecuteCodeResponse executeCode(@RequestBody ExecuteCodeRequest request);
}
execute-code-service就是我的小伙伴的服务名。
昨天下午,小伙伴开心的告诉我说:“我的服务已经测试OK了,并且注册到了Nacos”。并且附上了两张截图,看上去那么完美。
于是,我开始测试了。
和之前无数次的联调一样,这次依然没那么顺。
按钮一点,一个空指针出现了。
各种debug,分析源码,发现空指针出现在服务发现组件的com.alibaba.cloud.nacos.ribbon.NacosServerIntrospector类:
public boolean isSecure(Server server) {
return server instanceof NacosServer ? Boolean.valueOf((String)((NacosServer)server).getMetadata().get("secure")) : super.isSecure(server);
}
((NacosServer)server).getMetadata()获取到的结果是null, 所以空指针了。
为何我的java服务调用别的java服务不会出问题,调用python服务就出问题了?(潜意识在甩锅)
对比下Nacos中心java服务和python服务的元数据:
Nacos里注册的python服务空白的metadata是那么的显眼。
python服务空白的metadata为何是null呢?看看python服务注册nacos的代码,
def add_naming_instance(self, ..., metadata=None,...)
def _build_metadata(self, metadata, params):
if metadata:
if isinstance(metadata, dict):
params["metadata"] = json.dumps(metadata)
else:
params["metadata"] = metadata
很显然,如果注册时候没有传递metadata, 默认是None, 后续的_build_metadata方法也没有对metadata为None的情况进行处理。
可以说,是Java服务(ribbon)的服务发现机制和Python服务的服务注册机制共同的缺陷导致了这个bug。
那么,问题如何修改呢?
方案1:
服务发现方:(NacosServer)server).getMetadata()增加空值判断;
方案2:
服务注册方:metadata赋默认值,metadata=None修改为metadata={}
方案3:
Java服务配置ribbon的IsSecure参数,让服务发现流程跳过问题代码。
ribbon:
IsSecure: false
问题解决了,我们不妨多想想,为什么会发生这个bug?
本质还是服务提供方和服务调用方对于空值处理都不太规范。
看看阿里出品的《Java开发手册》里是怎么说的:
联想到出现bug的代码可能就是阿里的开发人员写的(Nacos是阿里出品的中间件),真是有点小讽刺。
见贤思齐,见不贤而内自省之。还是要多学习别人的长处,做好自己的项目。太多的线上bug来自这些看似低级的空指针问题,作为一个体面的程序员,从处理好空指针做起吧!