【一课专栏】解构1 - 起底QName

大家好,我是耿兴元,欢迎收听新一期的《解构ODL:从代码到架构设计》。

QName定义

QName是Qualified Name的缩写,中文翻译为限定名,可理解为full name。QName来源于XML, 由XML的名字空间和 XML元素名称组成,构成格式是名字空间(namespace)前缀以及冒号(:)再加一个元素名称(local name)。看下面的例子:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"

version="1.0">

<xsl:template match="foo">

<hr/>

</xsl:template>

</xsl:stylesheet>

xsl是名字空间前缀,template是元素名称,xsl:template 就是一个QName,而template称之为localName。举个例子大家就理解了,比如三国演义中,两将对阵,第一句就是问来将何人,一般回答类似"吾乃常山赵子龙是也"。这里"常山赵子龙"就可以对应为QName,常山对应的就是nameapace,赵子龙对应的就是localName。

为什么要从这个QName在yangtools里的定义讲起呢?从上面我们也可以看到,QName是XML元素的限定名称,是组成XML的最基本的要素,只有理解了它才能进一步描述更复杂的概念和关系。

ODL的yangtools项目里QName的定义与XML里的定义及概念都非常类似,但又不是完全相同的。那有什么不同吗?相较于XML里QName的定义,yangtools里定义的QName增加了YANG模型定义文件里面的revision这个元属性。YANG模型里的节点,类型,RPC或Notification的定义都可以由QName标识。

QName类的定义在yangtools项目的yang-common模块内,即yang/yang-common/目录下。其类定义为 :

public final class QNameModule implements Comparable<QNameModule>, Immutable, Serializable, Identifier, WritableObject {

......

private final @NonNull URI namespace;

private final Revision revision;

......

}

可以看到其定义中包含了两个类成员变量localName和module,module为QNameModule类定义的对象,我们再看看QNameModule类的定义

public final class QNameModule implements Comparable<QNameModule>, Immutable, Serializable, Identifier, WritableObject {

......

private final @NonNull URI namespace;

private final Revision revision;

......

}

我们可以看到上面这个类包含namespace和revision两个成员变量。画下QName类图,供大家参考

为了图示清晰,并没有把这三个类实现的所有接口(比如序列化接口)都画上.通过类图,我们应该能比较清晰的看到这三个类的包含和组成关系了。

QName对象创建

了解了QName类的定义,我们再一起看看如何创建QName对象。从上面类图也能看到QName提供了多个create方法创建QName对象。大家可以灵活使用上述方法创建QName对象。

另外,我们在ODL代码里,经常看到在创建QName后,最后加上intern()方法,类似如下:

private static QName createQName(final String namespace, final String localName) {

return QName.create(namespace, localName).intern();

}

这是为什么呢?我们知道,在QName的定义中,namespace,revision,localName都可以看作是在yang文件中定义的常量字符串,而Java中String类的intern()方法设计的初衷,就是利用字符串常量池重用String对象,以节省内存消耗。我们看一下QName的intern()方法的实现

public QName intern() {

final QNameModule cacheMod = module.intern();

final QName template = cacheMod == module ? this : QName.create(cacheMod,localName.intern());

return INTERNER.intern(template);

}

其中,INTERNER的定义

private static final Interner<QName> INTERNER = Interners.newWeakInterner();

Interners为guava库的类,为什么用guava库呢?因为JDK不同版本(JDK6,7,8)中String实现的intern方法的机制不太一样,而且使用时可能导致某些问题,因此不太建议直接用String的intern方法,而guava库中的Interners类对 intern 做了很多的优化,使用弱引用包装了你传入的字符串类型,所以,这样就不会对内存造成较大的影响, 可以使用该类的 intern(str) 来进行对字符串intern, 解决了直接使用String类中intern()方法可能存在的问题。从这里也可以看到,ODL的yangtools项目里,对于QName这个类的实现上细节的用心。

QName对象比较

从QName类的定义我们看到其实现了Comparable接口,也即实现了compareTo()方法,具体实现如下

public int compareTo(final QName o) {

// compare mandatory localName parameter

int result = localName.compareTo(o.localName);

if (result != 0) {

return result;

}

return module.compareTo(o.module);

}

从这个方法可以看到,两个QName比较,会按照localName先比较,如果localName相同,再继续比较其包含的QNameModule对象,我们再看一下QNameModule的compareTo()方法实现:

public int compareTo(final QNameModule o) {

int cmp = namespace.compareTo(o.namespace);

if (cmp != 0) {

return cmp;

}

return Revision.compare(revision, o.revision);

}

QNameModule比较时,会先比较namespace,如果namespace相同,继续比较revision,其中Revision.compare()的实现最终调用了Java里String类的compareTo()方法,比较的返回值就是String类的compareTo()方法的返回值,即相等时返回0,不等时,返回两个字符串第一个不同的字符的差值。通过以上代码,对于QName的比较的过程及原理,我相信大家应该比较清楚了。

QName中碰到的坑

虽然yangtools项目中,QName类的定义和实现细节确实考虑的很细致,但代码都是人写的,人写的代码都避免不了bug,下面我们就一起看看氮版本之前的ODL版本里,在QName的定义中,一处细节的地方考虑不周导致隐藏了一个坑,这个坑存在于Revision的类定义中。

我们知道,在YANG 语言里,revision表示一个YANG module的版本号,格式为日期字符串,即形如2018-06-26。ODL社区早期版本里Revision类的定义为

public abstract class Revision implements Comparable<Revision>, Serializable {

......

private final Date date;

private String str;

......

}

该类中包含了一个java.util.Date类的对象成员,用来表示YANG Module里revision,并基于这个类进行解析和比较。这个定义看起来合情合理,但确埋了一个坑,为了理解为什么这样定义和处理埋了坑,我们和大家一起先学习下java对Date类的定义和处理的机制。

Date 类用来封装当前的日期和时间,其内部Date对象里存的只是一个long型的变量,保存的是自格林威治时间( GMT)1970年1月1日0点至Date对象所表示时刻所经过的毫秒数。所以,如果某一时刻遍布于世界各地的程序员同时执行new Date语句,这些Date对象所存的毫秒数是完全一样的。也就是说,Date里存放的毫秒数是与时区无关的。把Date对象解析为具体的时间时,先读取操作系统当前所设置的时区,然后根据这个时区将把毫秒数解释成该时区的时间。也即同一个Date对象,按不同的时区来格式化,将得到不同时区的时间。

理解了Date定义和处理机制,就容易理解如下场景中产生的问题原因了。一个三节点的ODL控制器集群,三个节点上设置的时区不一致,在通过openflowplugin提供的addFlow这个RPC向openflow交换机下发流表时,有时候会报RPC未实现的错误,导致流表下发不了。经过分析,排查,发现交换机连接正常,RPC也都正常注册了,那问题原因到底是什么呢?后来我们发现只有在出现跨节点RPC调用时才会报错,且在被调用的控制器节点上查找注册的RPC实现时,传入的RPC的QName的revision与YANG模型里实际定义的revison差了一天,导致查找RPC失败,最终返回给调用者RPC未实现的错误。通过代码跟踪总算理清楚了问题出现的整个过程和原因:YANG中定义的revision,在本机解析成Date对象,跨节点调用时,Date对象被序列化后通过网络传输另外一个节点,另一个节点上再反序列化为Date对象,生成Revision,结果再与该节点的注册RPC保存的revison进行比较将得到不一致的结果(请考虑时区因素)。

在最新的ODL版本里,对Revison类的定义已修改为

public abstract class Revision implements Comparable<Revision>, Serializable {

......

private String str;

......

}

虽然这个类本身改动量不大,但由于其是一个非常基础的类,所有使用到该类及该类方法的地方都必须要做出修改,修复该故障最终修改了上百个源码文件。详见链接

https://github.com/opendaylight/yangtools/commit/b212baa59f859732bd3a799425bb420035fe6154#diff-6359a4924ab250168c209e9a9c97f321

通过上面的QName类的定义和实现的解读,对Revision隐含的bug的分析,简单总结一下个人体会和收获:1.技术细节很重要;2.基础类的设计以最简设计为原则;3.只要是人写的代码就可能出现故障,对开源代码要保持怀疑,碰到问题积极的查看日志,跟踪代码进行分析,问题就会在你手里被解决

本期课程就到这里,谢谢大家,我们下期再见!

原文发布于微信公众号 - SDNLAB(SDNLAB)

原文发表时间:2018-09-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

ES7、ES8新特性

概述 JavaScript,作为一门处于高速发展期的开发语言,正在变的越来越完善、稳定。我们必须拥抱这些变化,并且我们需要把ES8加入到我们的技术栈中。 E...

1.9K50
来自专栏Java Web

Java 面试知识点解析(四)——版本特性篇

15950
来自专栏Java技术栈

Java 面试题经典 77 问(含答案)!

19730
来自专栏一个会写诗的程序员的博客

《Kotlin 程序设计》第十二章 Kotlin的多线程

Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, non-blockin...

14010
来自专栏nice_每一天

Java爬虫

jsoup-1.7.3.jar 个人认为爬虫的实现机制: 获取Docume对象—>获取节点—>输出或者持久化

18030
来自专栏HTML5学堂

2015.12.18 HTML5真题练习

HTML5学堂:每天一道题,强壮程序员!今日主要涉及12.17日关于闭包的题目解答,以及一道涉及字符串相关知识的题目。 HTML5真题【2015.12.17】答...

31650
来自专栏我就是马云飞

Retrofit源码模拟

如果要进行网络请求,你可能会这样写一个简单的OKHttp请求 public class CallExector { public static fin...

239100
来自专栏Golang语言社区

动手实现一个JSON验证器(上)

分析 既然要验证JSON的有效性,那么必然需要清楚的知道JSON格式,这个在JSON官网已经给我们画出来了: ? ? ? ? ? 从官方的图上面可以看出,JSO...

53770
来自专栏编舟记

Java内部类的异常处理

最近遇到一个问题,使用Java写某个DSL标记语言X的parser(解析器)Maven插件的时候,对外暴露一个名为Callback的接口和一个待实现的方法get...

8820
来自专栏向治洪

ES7和ES8新特性介绍

概述 JavaScript,作为一门处于高速发展期的开发语言,正在变的越来越完善、稳定。我们必须拥抱这些变化,并且我们需要把ES8加入到我们的技术栈中。 ECM...

69260

扫码关注云+社区

领取腾讯云代金券