前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【一课专栏】解构1 - 起底QName

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

作者头像
SDNLAB
发布2018-09-25 16:38:11
2.8K1
发布2018-09-25 16:38:11
举报
文章被收录于专栏:SDNLABSDNLAB

大家好,我是耿兴元,欢迎收听新一期的《解构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.只要是人写的代码就可能出现故障,对开源代码要保持怀疑,碰到问题积极的查看日志,跟踪代码进行分析,问题就会在你手里被解决

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

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

本文分享自 SDNLAB 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档