北京大学李友焕

 

java编程河流图软件使用教程 河流图指标_链表

java编程河流图软件使用教程 河流图指标_加锁_02

java编程河流图软件使用教程 河流图指标_数据_03


1 问题背景

随着移动互联网的发展,移动端的应用实时产生着大量的数据,这些实时的数据包含着很多有价值的信息。针对这些高速产生的数据进行高效而精准的分析显然具有非常重大的意义。人们其实可以将这些实时数据建模成图数据流的模型加以分析。现实世界的对象可以建模成点,对象与对象之间的关系和行为建模成边,而这些数据必然是反应具体对象及其彼此间的行为,因此图数据流模型能够很好地适应这一场景。本文研究在图数据流下的精准的子图查询,即子图同构。根据实际中存在的大量需求,在子图查询(子图同构)中,本文率先引入了边的时间限制,即要求对应的结果中部分边之间的时间先后顺序满足既定的限制。

 

java编程河流图软件使用教程 河流图指标_数据_04

图 1网络通信中的查询样例

图 1展示了一个信息窃取的流程(攻击模式)。首先,一台即将受害的机器客户端访问一个带有病毒的网页(

java编程河流图软件使用教程 河流图指标_链表_05

时间);其次,这个访问操作导致了带病毒的网页服务器向客户端发送恶意脚本(

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_06

时间)以建立同僵尸网络中的C&C服务器的通信;之后,这个受害客户端则会在

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_07

时间向僵尸网络背后的C&C服务器进行僵尸注册,并从那儿获取到相应的指令(

java编程河流图软件使用教程 河流图指标_链表_08

时间);最后,这个客户端会根据更详细的指令在

java编程河流图软件使用教程 河流图指标_链表_09

时间将重要的数据传输给C&C服务器,完成了数据窃取的一般过程。其实,可以很明显地发现,上述例子中的几个关键行为所发生的时间遵从严格的先后顺序,只有满足这个顺序这个过程才有可能成立,即 

java编程河流图软件使用教程 河流图指标_加锁_10

,毕竟主机要先中毒才有可能服从信息窃取的指令。因此,一个攻击模式,不但需要在结构上建模成对应的查询图结构 (Q) ,也需要能够提供针对交互行为先后的时序限制。如果能够在网络通信的数据流中及时定位到这个行为模式 (即精确地子图同构的定位),那么就能够将符合这种模式的攻击行为及时侦测出并进行后续的甄别和目标C&C服务群的定位。美国知名通讯公司 Verizon 分析了过去十年中发生的 100,000 例安全事故,他们发现, 90% 的安全事故符合10种攻击模式之一。这些模式能够很自然而然地建模成子图模型,例如入侵攻击 (路径查询),DoS攻击 (并行路径查询) 以及恶意软件扩散 (树查询)。

本文提出了高效的数据结构和算法来解决所提出的问题。在此基础之上,本文提出了两个非常重要而且具有挑战性的优化策略:第一个是在时间代价小幅优化的同时,大幅降低空间开销;第二个是高效的并发锁机制实现多线程并发加速。在大规模的真实数据集和人工生成的数据集上的实验显示本文所提出的方法具有高效的性能和良好的效果。

 

2 问题定义

首先是图数据流,定义为一个无线边序列,边上的点有其标签和id,边有其唯一的时间戳,同时引入时间区间来定义图数据流上的窗口,窗口中的所有边组成了图数据流对应时刻的快照。

java编程河流图软件使用教程 河流图指标_链表_11

图 2窗口大小为 9的图数据流示例

 

java编程河流图软件使用教程 河流图指标_数据_12

 

图 3窗口大小为9的时间窗下对应的图数据流示例

其次是查询图,除了图结构之外,还用边集上的偏序关系定义了边的时序关系。

java编程河流图软件使用教程 河流图指标_链表_13

图 4示例查询图

 最后就是核心的问题定义,即实时维护当前时刻快照中满足一下两个条件的子图:(1)同构于查询图;(2)边先后关系符合查询图边偏序关系限制的所有子图。

3 方法框架

本文的方法框架主要包含三部分。首先,本文将讨论基于扩展链表的基本解决方案:充分利用时序限制的特征来大量过滤中间结果,进而高效地实时维护匹配解。其次,本文在基本方法之上,利用匹配存储树对未过滤的中间结果进行索引,实现了大幅压缩空间的同时还优化了更新操作的时间代价。最后本文将在压缩的中间解之上,设计多线程并发的机制来提高该方法在图数据流下的吞吐量。

3.1.  时序连通查询图

本节利用查询中的时序特征,提出了无效边的概念来大量减少无用的维护代价。一般而言,对于一条数据边

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_14

来说,

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_14

是无效的当且仅当无论未来还有那些更新发生,

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_14

都不可能出现在Q的任意一个最终匹配中。所以无效边所产生的部分解自然也不可能形成最终的匹配解,应该尽早丢弃。

先针对一种特殊类型的查询图来分析其计算,然后将我们的方法扩展到任意图。这种查询图首先存在其边序列的一个排列,这个排列里每个前缀序列组成的子图都是连通的,并且这个排列里,位置在前的边,必须要求时序先于位置靠后的边,即这个排列先后恰好是这个图所有边的时序限制先后,时序是全序关系,这个排列记为前缀连通全序序列。如图5(a)所示。

java编程河流图软件使用教程 河流图指标_链表_17

图 5时序连通查询图及其扩展链表

针对这种查询图,我们建立扩展链表。假设查询图有k条边,则扩展链表是含有k个元素,第i个元素存储着所有查询第i个前缀子查询的所有实时匹配解。如图5(b)所示。

我们可以证明一下算法正确:给定一个查询图及其扩展链表,当一条新来的边到来时,所需要进行的更新操作如下:

(1)如果新边不匹配任何查询边,则删除新边;

(2)如果新边匹配排列的第1条边,则将其加入到扩展链表第一个元素中;

(3)如果新边匹配第i条边且i>1 ,那么,将新边同第i-1个扩展链表元素进行联结操作,如果这个联结操作的结果非空,则其结果加入到第i个扩展链表元素中;如果结果为空,则将新边直接丢弃。

当一条数据边过期 (即从时间窗中滑出),则需要将扩展链表中,所有包含该边的过期的部分解移除。具体做法是,通过从左往右扫扩展链表的每个一元素,直到找到一个最右的、包含过期部分解的元素为止,将所有的过期部分解删除。

3.2.  任意图查询

我们首先将查询图分解为多个子查询,确保子查询是前缀时序连通的,将每个子查询分别计算后,再将所有子查询的结果按既定的联结方式联结起来。我们可以证明分解方法中,子查询数量越少往往后续的计算效率越高。

java编程河流图软件使用教程 河流图指标_加锁_18

图 7示例查询图Q的分解示例

java编程河流图软件使用教程 河流图指标_数据_19

图 8示例查询图(非时序连通)对应的数据结构与数据流图

 

4 匹配存储树的时空优化

这是本文介绍两种主要优化策略中的第一种。我们可以看到,实时计算的过程其实是部分解由小到大的增长过程,直接增长到扩展链表的最后元素即为匹配解。因此,我们用树结构来索引部分解,避免不同元素间,很多部分解的重叠存在。如图所示,部分解的增长不再是后续的扩展链表元素里的新增,而是“树枝”的伸长。

java编程河流图软件使用教程 河流图指标_链表_20

图 9扩展链表的匹配存储树

java编程河流图软件使用教程 河流图指标_数据_21

图 10扩展链表的匹配存储树

 

5 多线程并发机制

5.1.  并发机制的需求

处于性能的考虑,本文所提出的算法可以(也应该)用多线程的方式来加速。多线程访问公共的数据结构,即扩展链表时,不可避免需要引入并发管理的机制。在匹配存储树上的并发管理是具有很大挑战的,毕竟不同的部分解可能有公共的部分,容易导致不同线程的访问冲突。

java编程河流图软件使用教程 河流图指标_数据_22

图 11冲突发生示例

 

本节提出了细粒度的并发管理来提高算法的吞吐量,并且保证了严格的一致性。因为存储匹配树是将不同的部分解重叠存储的,这部分并发计算的挑战很大。本节的并发策略在实际应用过程中,除了锁机制的开销外,只需对删除操作进行细微的修改就能应用在匹配存储树的访问上,而且这部分修改只需要几乎可以忽略的时间代价。

为了提出我们的并发机制,我们先提出了流一致性

定义(流一致性)。给定一个带时间窗

java编程河流图软件使用教程 河流图指标_链表_23

的图数据流

java编程河流图软件使用教程 河流图指标_加锁_24

以及一个查询

java编程河流图软件使用教程 河流图指标_数据_25

。流一致性要求在每个时间点,对

java编程河流图软件使用教程 河流图指标_数据_25

的解的维护需要同数据更新操作(边的插入、删除) 按边的时间戳的先后顺序执行的结果完全一致。

5.2.  锁机制及并发调度

本节提出了一种锁机制,在查询执行算法高效并发的同时保证了流一致性。本节分别将图数据流下的两个基本操作,即边插入和边删除,建模成两种事务。每个事务的时间戳记为对应边的更新时间戳,而边的插入和删除都涉及到一系列的对数据的访问操作,例如对部分解的插入和删除等。显然,需要引入避免冲突的机制来对这些子操作的并发进行支持。

一个直接的办法就是在启动对应的事务时,将这个事务可能涉及到的所有的扩展链表中的元素都加上锁,只有获得所有需要的锁之后才启动事务,执行完事务则释放对应的锁。显然,这种简单的策略会很明显地影响系统的吞吐量。我们应该尽量让不冲突的计算可以同时进行,冲突的计算符合流一致性即可。

对每个边操作(新边的插入和过期边的删除)对应的事务都用一个独立的线程来执行,并有一个唯一的主线程来负责对每个事务的启动。扩展链表中的元素则作为线程彼此需要竞争访问的资源。锁是针对扩展链表中的元素为单元来施加和释放的。要进行一个基本的子操作的时候,需要保证执行线程对这个元素有锁。在对应的元素的计算结束时,对应的锁随即释放。本节的调度逻辑里不会出现死锁,因为每个线程一次加锁只会访问一个元素中的数据,访问完后就立即释放锁,而不涉及到加锁状态下,同时访问不同元素之间的数据。

java编程河流图软件使用教程 河流图指标_链表_27

图 12锁需求的分发

主线程。主线程对每个执行线程的启动负责。在启动一个线程t之前,主线程将会把线程t所需要加锁的对应的锁需求派发到对应的锁需求队列中。具体地说,一个锁需求是一个三元组:线程ID,锁类型以及对应的扩展元素。对于扩展链表中的每一个元素我们都引入一个线程安全的锁需求队列,这个队列里的锁需求按对应的线程时间戳的先后顺序来排列。

因为只有一个主线程在负责执行线程的启动,所以,锁需求的派发如同线程的启动一般都是串行的方式进行的。因此,当一个线程的锁需求派发时,此前的线程对应的锁需求也早已入队,而后续的线程的锁需求还没有入队,所以队列中的锁需求自然而然就是按时间先后排好序的。尽管执行线程的启动是顺序的,但是当线程启动之后,其执行则是并发的。

执行线程的计算。并发地执行插入和删除事务的具体计算跟之前顺序模式的几乎一致,只是在访问每个元素的前后分别需要对这个元素进行相应的加锁和解锁。因此,本节接下来将聚焦在对元素的加锁和解锁过程。值得一提的是目前对部分解的存储仍然是没有匹配存储树的情况下的直接存储的方式,在有匹配存储树的情况则会在后续进行更具体的讨论,显然具有匹配存储树的情况下的并发调度会复杂得多。

当一个线程需要访问(读取、插入和删除)一个元素时,在访问之前,线程需要请求对其加锁。线程能够顺利加锁的前提是要满足以下两个条件:(1)  线程的锁请求目前正是在这个元素的锁请求队列的队首;(2) 当前元素的锁状态同线程想加锁的类型相兼容,也就是说,要么没被加锁,要么已加上的锁和想加的锁都是共享锁。否则,线程必须进入睡眠等待直到已加的锁被释放。

一旦成功地实现了加锁,则对应的锁需求等待列表需要将队首的线程的锁需求出队,而线程则开始针对该元素进行相关的读写计算。当计算结束,线程会释放所加的锁并且试图唤醒锁需求队列当前的队首锁需求对应的线程。而后线程将继续其执行线程所既定的剩余计算任务。可以证明,本节所提出的锁机制下的全局调度符合流一致性要求。

5.3.  匹配存储树的并发访问

给定一个扩展链表及其存储在匹配存储树上的部分解。对于链表第i个元素, 其中的每个匹配都对应着树中深度为i的节点。因此,对元素的加锁实质上就可以等价于对树中深度为i的那层节点集进行加锁。然而,在匹配存储树中,不同的部分解并不是独立存储的,很多部分解之间共享树中的前缀路径。所以在并发访问的过程中,对一个部分解的操作往往会涉及到对其它部分解的影响,给一致性带来了挑战。

给定两个线程

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28


java编程河流图软件使用教程 河流图指标_链表_29

,假定这两个线程分别启动于时刻

java编程河流图软件使用教程 河流图指标_数据_30


java编程河流图软件使用教程 河流图指标_数据_31

(

java编程河流图软件使用教程 河流图指标_数据_30

<

java编程河流图软件使用教程 河流图指标_数据_31

), 并且

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28

当前正在访问M中的第d1层中的部分解而

java编程河流图软件使用教程 河流图指标_链表_29

则是访问第d2层中的部分解,本节将针对这个情景来讨论什么情况下冲突可能发生。对于这两个线程而言,目前正在访问数据的方式有三种,即插入、删除和读取,两种组合有9种,而两个深度有三种大小关系:

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_36

<

java编程河流图软件使用教程 河流图指标_加锁_37

,

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_36

=

java编程河流图软件使用教程 河流图指标_加锁_37

,以及

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_36

>

java编程河流图软件使用教程 河流图指标_加锁_37

, 。因此,需要讨论的总情况有3*3*3=27 种,然而,可以证明,这27中操作中,只有其中的两种才有可能产生冲突:(1),

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_36

<

java编程河流图软件使用教程 河流图指标_加锁_37


java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28

要读取部分解而

java编程河流图软件使用教程 河流图指标_链表_29

要删除过期部分解。当

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28

在回溯读取路径时需要访问到某个节点,而

java编程河流图软件使用教程 河流图指标_链表_29

却已经删除了,因此产生了不一致性.(2),

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_36

>

java编程河流图软件使用教程 河流图指标_加锁_37


java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28

要将部分解插入到对应元素中并且

java编程河流图软件使用教程 河流图指标_链表_29

要删除部分解。当 

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28

要把新解子节点加入到匹配存储树的时候,

java编程河流图软件使用教程 河流图指标_链表_29

却已经删除了其父节点,由此也会产生不一致性。可见,不一致性的发生往往是在于有线程

java编程河流图软件使用教程 河流图指标_链表_29

过早地删除了一些节点,而这些节点 

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28

虽没有加锁,但却需要访问的。如果调度机制能够令

java编程河流图软件使用教程 河流图指标_链表_29


java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_28

完成其计算之前保持等待,并发度显然会受到明显影响。实际上,只需要保证

java编程河流图软件使用教程 河流图指标_链表_29

想要删除的那些部分解对启动晚于

java编程河流图软件使用教程 河流图指标_链表_29

的线程不可见,而对于更早启动的线程则可见。其实,只需要轻微地修改删除策略即可实现这个目标。具体来说,对于正在执行删除操作的线程

java编程河流图软件使用教程 河流图指标_链表_29

来说,当 

java编程河流图软件使用教程 河流图指标_链表_29

正在匹配存储树中删除深度为

java编程河流图软件使用教程 河流图指标_加锁_37

的节点时,

java编程河流图软件使用教程 河流图指标_链表_29

先不完全删除,而是改为部分删除: 

java编程河流图软件使用教程 河流图指标_链表_29

移除待删节点同兄弟节点的链接以及父节点到自身的链接,并保留其到父节点的链接。如下图所示。在

java编程河流图软件使用教程 河流图指标_链表_29

部分移除所有过期节点之后,

java编程河流图软件使用教程 河流图指标_链表_29

将会最终完全地移除匹配存储树M中的所有过期节点.

java编程河流图软件使用教程 河流图指标_加锁_67

图 13部分移除示例

可以证明,并发访问时,如上修改匹配存储树的删除策略后,所提的并发算法将不会违反流一致性。

 

6 总结

至此,我们介绍了我们首次提出的图数据流时序子图同构的问题以及其基本解决方案还有其上的两种重要优化策略,实验效果比已有工作高出一个数量级,具体数据详见原文,此处不再赘述。

java编程河流图软件使用教程 河流图指标_加锁_02

 

 

java编程河流图软件使用教程 河流图指标_java编程河流图软件使用教程_69