图数据库 | 5、图数据库三大组件之一 之 图计算 (下)

 书接上文:图数据库 | 4、图数据库三大组件之一 ——图计算 (上)-CSDN博客

结合计算效率来评估与设计图计算所需的数据结构

存储低效性或许是相邻矩阵或关联矩阵等数据结构的最大缺点,尽管它有着O(1)的访问时间复杂度。例如通过数组下标定位任何一条边或顶点所需的时间是恒定的O(1),相比而言,相邻链表对于存储空间的需求要小得多,在工业界中的应用也更为广泛。例如Facebook的社交图谱(其底层的技术架构代码为Tao/Dragon)采用的就是相邻链表的方式,链表中每个顶点表示一个人,而每个顶点下的链表表示这个人的朋友或关注者。

这种设计方式很容易被理解,但是它可能会遇到热点问题,例如如果一个顶点有1万个邻居,那么链表的长度有10 000步,遍历这个链表的时间复杂度为O(10 000)。在链表上的增删改查操作都是一样的复杂度,更准确地说,平均复杂度为O(5000)。另一个角度来看,链表的并发能力很糟糕,你无法对一个链表进行并发(写)操作。事实上,Facebook的架构中限定了一个用户的朋友不能超过6000人,微信中也有类似的朋友人数限制。

现在,让我们思考一个方法,一种数据结构可以平衡以下两件事情。

·存储空间:相对可控的、占用更小的存储空间来存放更大量的数据。

·访问速度:低访问延迟,并且对并发访问友好。

在存储维度,当面对稀疏的图或网络时,我们要尽量避免使用利用率低下的数据结构,因为大量的空数据占用了大量的空闲空间。

以相邻矩阵为例,它只适合用于拓扑结构非常密集的图,例如全连通图(所谓全连通指的是图中任意两个顶点都直接关联)​。前面提到的有向图,如果全部连通,则至少存在30条有向边(2×6×5/2),若还存在自己指向自己的边,则存在36条边,那么用相邻矩阵表达的数据结构是节省存储空间的。

然而,在实际应用场景中,绝大多数的图都是非常稀疏的[我们用公式“图的密度=(边数/全联通图的边数)×100%”来衡量,大多数图的密度远低于5%],因此相邻矩阵就显得很低效了。

另一方面,真实世界的图大多是多边图,即每对顶点间可能存在多条边。例如交易网络中的多笔转账关系,这种多边图不适合采用矩阵数据结构来表达(或者说矩阵只适合作为第一层数据结构,它还需要指向其他外部数据结构来表达多边的问题)​。

相邻链表在存储空间上是大幅节省的,然而链表数据结构存在访问延迟大、并发访问不友好等问题,因此突破点应该在于如何设计可以支持高并发、低延迟访问的数据结构。在这里,我们尝试设计并采用一种新的数据结构,它具有如下特点:

·访问图中任一顶点的时耗为O(1);

·访问图中任意边的延迟为O(2)或O(1)。

以上时耗的复杂度假设可以通过某种哈希函数来实现,最简单的例如通过点或边的ID对应的数组下标来访问具体的点、边元素来实现。顶点定位的时间复杂度为O(1);边仅需定位out-node和in-node,时耗为O(2)。在C++中,面向以上特点的数据结构最简单的实现方式是采用向量数组(array of vectors)来表达点和边:

vector <pair<int,int>> a_of_v[n];

动态向量数组可以实现极低的访问延迟,且存储空间浪费很小,但并不能解决以下几个问题:

·并发访问支持;

·数据删除时的额外代价(例如存储空白空间回填等)​。

在工业界中,典型的高性能哈希表的实现有谷歌的SparseHash库,它实现了一种叫作dense_hash_map的哈希表。在C++标准11中实现了unordered_map,它是一种锁链式的哈希表,通过牺牲一定的存储空间来得到快速寻址能力。但以上两种实现的问题是,它们都没有和底层的硬件(CPU内核)并发算力同步的扩张能力,换句话说是一种单线程哈希表实现,任何时刻只有单读或单写进程占据全部的表资源,这或许可以算作对底层资源的一种浪费。

在高性能云计算环境下,通过并发计算可以获得更高的系统吞吐率,这也意味着底层的数据结构是支持并发的,并且能利用多核CPU、每核多线程,并能利用多机协同,针对一个逻辑上的大数据集进行并发处理。传统的哈希实现几乎都是单线程、单任务的,意味着它们采用的是阻塞式设计,第二个线程或任务如果试图访问同一个资源池,它会被阻塞而等待,以至于无法(实时)完成任务。

从上面的单写单读向前进化,很自然的一个小目标是单写多读,我们称之为single-writer-multiple-reader的并发哈希,它允许多个读线程去访问同一个资源池里的关键区域。当然,这种设计只允许任何时刻最多存在一个写的线程。

在单写多读的设计实现中通常会使用一些技术手段,具体如下:

·versioning:版本号记录。

·RCU(Read-Copy-Update)​:读-拷贝-更新。

·open-addressing:开放式寻址。在Linux操作系统的内核中首先使用了RCU技术来支持多读,在MemC3/Cuckoo哈希实现中则使用了开放式寻址技术,如图2-7、图2-8所示。

图2-7 Cuckoo哈希的键被映射到了2个桶中以及使用了1个版本计数器
图2-8 随机放置与基于BFS的双向集合关联式哈希

沿着上面的思路继续向前迭代,我们当然希望可以实现多读多写的真正意义上的高并发数据结构。但是,这个愿景似乎与ACID(数据强一致性)的要求相违背——在商用场景中,多个任务或线程在同一时间对同一个数据进行写、读等操作可能造成数据不一致而导致混乱的问题。

下面把以上的挑战和问题细化后逐一解决。实现可扩展的高并发哈希数据结构需要克服上面提到的几个主要问题:

·无阻塞或无锁式设计;

·精细颗粒度的访问控制。

要突破并实现上面提到的两条,两者都和并发访问控制高度相关,有如下要点需要考量。

1)核心区域(访问控制)​。

·大小:保持足够小。

·执行时间(占用时间)​:保持足够短。

2)通用数据访问。

·避免不必要的访问。

·避免无意识的访问。

3)并发控制。

·精细颗粒度的锁实现:例如lock-striping(条纹锁)​。

·推测式上锁机制:例如交易过程中的合并锁机制(transactional lock elision)​。

对于一个高并发系统而言,它通常至少包含如下三套机制协同工作才能实现充分并发,此三者在图数据库、图计算与存储引擎系统的设计中更是缺一不可。

·并发的基础架构;
·并发的数据结构;
·并发的算法实现。

并发的基础架构包含硬件和软件的基础架构,例如英特尔中央处理器的TSX(Transactional Synchronization Extensions,交易同步扩展)功能是硬件级别的在英特尔64位架构上的交易型内存支持。在软件层面,应用程序可以把一段代码声明为一笔交易,而在这段代码执行期间的操作为原子操作。像TSX这样的功能可以实现平均140%的性能加速。这也是英特尔推出的相对于其他X86架构处理器的一种竞争优势。当然这种硬件功能对于代码而言不完全是透明的,它在一定程度上也增加了编程的复杂度和程序的跨平台迁移复杂度。此外,软件层面更多考量的是操作系统本身对于高并发的支持,通常我们认为Linux操作系统在内核到库级别对于并发的支持要好于Windows操作系统,尽管这个并不绝对,但很多底层软件实现(例如虚拟化、容器等)降低了上层应用程序对底层硬件的依赖。

另一方面,有了并发的数据结构,在代码编程层面,依然需要设计代码逻辑、算法逻辑来充分利用和释放并发的数据处理能力。特别是对于图数据集和图数据结构而言,并发对程序员来说是一种思路的转变,充分利用并发能力,在同样的硬件资源基础、同样的数据结构基础、同样的编程语言实现上,可能会获得成百上千倍的性能提升,永远不要忽视并发图计算的意义和价值。

图2-9展示了在Ultipa Graph上一款高性能、高并发实时图数据库服务器,通过高并发架构、数据结构以及算法实现了高性能K邻操作。

图2-10展示的是在700万“点+边”规模且高度联通的一个图数据集上,通过高密度并发实现的鲁汶社区识别算法的效果,毫秒级完成鲁汶社区识别算法的全量数据的迭代运算(engine time)且1~2s内完成数据库回写以及磁盘结果文件回写等一系列复杂操作(total time)​。

图2-10 鲁汶社区识别算法

 表2-3很好地示意了不同版本系统性能所出现的指数级差异,是两位图灵奖获得者大卫·帕特森(David Patterson)与约翰·轩尼诗(John Hennessey)于2018年在图灵会议的演讲中所展示的。

表2-3 用不同版本的系统进行矩阵乘法的速度比较

·以基于Python实现的系统的数据处理速度为基准;

·C/C++系统的处理速度为其47倍;

·并发实现的C/C++系统的处理速度为其366倍;

·增加了内存访问优化的、并发实现的C/C++系统,处理速度为其6727倍;

·利用了X86 CPU的AVX(高级矢量扩展)指令集的系统,处理速度为其62 806倍。

回顾前面的鲁汶社区识别算法,如果提升6万倍的性能,时间从T+1(约10万s)变为约1.7s,就可以实现完全实时。这种指数级的性能提升与时耗的相应减少所带来的商业价值是不言而喻的。

图2-11形象地解析了如何在图中实现BFS算法并发。以基于BFS的K邻算法为例,为大家解读如何实现高并发。 

图2-11 K邻并发算法示意图

 ①在图中定位起始顶点(图中的中心顶点A)​,计算其直接关联的具有唯一性的邻居数量。如果K=1,直接返回邻居数量;否则,执行下一步。

②K≥2,确定参与并发计算的资源量,并根据第一步中返回的邻居数量决定每个并发线程(任务)所需处理的任务量大小,进入第三步。

③每个任务进一步以分而治之的方式,计算当前面对的(被分配)顶点的邻居数量,以递归的方式前进,直到满足深度为K或者无新的邻居顶点可以被返回而退出,算法结束。

基于以上的算法描述,我们再来回顾一下图2-11中的实现效果,当K邻计算深度为1~2层的时候,内存计算引擎在微秒级内完成计算。从第3层开始,返回的邻居数量呈现指数级快速上涨(2-Hop邻居数量约200,3-Hop邻居数量约8000,4-Hop邻居数量接近5万)的趋势,这就意味着计算复杂度也等比上涨。但是,通过饱满的并发操作,系统的时延保持在了相对低的水平,并呈现了线性甚至亚线性的增长趋势(而不是指数级增长趋势)​,特别是在搜索深度为第6~17层的区间内,系统时延稳定在约200ms。第17层返回的邻居数量为0,由于此时全图(联通子图)已经遍历完毕,没有找到任何深度达到17层的顶点邻居,因此返回结果集合大小为0。

我们做一个1:1的对标,同样的数据集在同样的硬件配置的公有云服务器上用经典的图数据库Neo4j来做同样的K邻操作,效果如下:

·1-Hop:约200ms,为Ultipa的1/1000;

·从5-Hop开始,几乎无法实时返回(系统内存资源耗尽前未能返回结果)​;

·K邻的结果默认情况下没有去重,有大量重复邻居顶点在结果集中;

·随着搜索深度的增加,返回时间和系统消耗呈现指数级(超线性)增长趋势;

·最大并发为400%(4线程并发)​,远低于Ultipa的6400%并发规模。

基于Neo4j的实验(图2-12)​,我们只进行到7-Hop后就不得不终止了,因为7跳的时候系统耗时超过10s,从8跳开始Neo4j几乎不可能返回结果。而最大的问题是计算结果并不正确,这种不正确包含两个维度:重复顶点未被去重、顶点深度计算错误。

图2-12 Neo4j的图遍历(K邻去重)查询

K邻操作中返回的应该是最短路径条件下的邻居,那么如果在第一层的直接邻居中已经被返回的顶点,不可能也不应该出现在第二层或第三层或其他层级的邻居列表中。目前市场上的一些图数据库产品在K邻的实现中并没有完全遵循BFS的原则(或者是实现算法的代码逻辑存在错误)​,也没有实现去重,甚至没有办法返回(任意深度)全部的邻居。在更大的数据集中,例如Twitter的15亿条边、6000万顶点、26GB大小的社交数据集中,K邻操作的挑战更大,我们已知的很多开源甚至商业化的图数据库都无法在其上完成深度(≥3)的K邻查询。 

到这里,我们来总结一下图数据结构的演化:更高的吞吐率可以通过更高的并发来实现,而这可以贯穿数据的全生命周期,如数据导入和加载、数据转换、数据计算(无论是K邻、路径还是……)以及基于批处理的操作、图算法等。

另外,内存消耗也是一个不可忽略的存储要素。尽管业内不少有识之士指出内存就是新的硬盘,它的性能指数级高于固态硬盘或磁盘,但是,它并不是没有成本的,因此谨慎使用内存是必要的。减少内存消耗的策略有:基于数据加速的数据建模;数据压缩与数据去重;算法实现与代码编程中避免过多的数据膨胀、数据拷贝等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/13297.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

由播客转向个人定制的音频频道(1)平台搭建

项目的背景 最近开始听喜马拉雅播客的内容&#xff0c;但是发现许多不方便的地方。 休息的时候收听喜马拉雅&#xff0c;但是还需要不断地选择喜马拉雅的内容&#xff0c;比较麻烦&#xff0c;而且黑灯操作反而伤眼睛。 喜马拉雅为代表的播客平台都是VOD 形式的&#xff0…

被抛弃的八股文之keep-alive

还记得在我毕业面试时&#xff0c;经常看到碰到的面试题中都有着TCP中的keep-alive和Http中的keep-alive有什么区别。但是现在的八股文中已经再也见不到了&#xff08;燕子&#xff0c;我们还会再见吗&#xff09; 话说回来&#xff0c;这两个不同的协议中&#xff0c;keep-ali…

衡石分析平台系统分析人员手册-指标管理

指标管理​ 指标平台通过业务主题管理指标&#xff0c;对指标进行授权使用。在指标管理中业务管理员根据业务情况创建相关的主题&#xff0c;将与业务相关的指标添加到主题中&#xff0c;对指标进行上下线管理&#xff0c;将主题及其下面的指标授权给平台内其他用户使用。 本…

【万码优才,等你到来】一款针对程序员求职的平台

hello&#xff0c;大家好我是万码优才推荐官→Aic山鱼&#xff0c;在面对广大程序员找工作的前期我为大家推荐一款超牛的求职平台 ——万码优才 针对当前的求职情况山鱼君也做了一写总结与分析&#xff0c;也结合了其他求职平台给出了“为什么要使用万码优才 这个平台”的原因 …

echarts bar3D画出圆角立方体模拟建筑

结果展示 重点 bar3D中圆角属性&#xff1a;roundCap: true //开启圆角&#xff08;echarts官方文档中没有&#xff09;bevelSize: .6 //圆角程度barSize: 12.5 //立方体大小半球形使用 surface 类型,曲线方程如下 parametricEquation: {u: {min: 0,max: Math.PI,step: Ma…

从建立TRUST到实现FAIR:可持续海洋经济的数据管理

1. 引言 随着我们对信息管理方式的信任&#xff0c;我们的社会对数字化数据的以来呈指数级增长。为了跟上大数据的需求&#xff0c;通过不断的努力和持续实践&#xff0c;对“good”数据管理方式的共识也在不断发展和演变。 加拿大正在建设国家基础设施和服务以及研究数据管理…

CTF-RE 从0到N: perl 逆向

WMCTF2020 easy_re 1.寻找字符串Script 2.通过下一个call 3.将rax的值解析为字符串

RecyclerView详解——(二)优劣,ItemDecoration,SnapHelper

本文主要讲述RecyclerView和ListView的区别&#xff0c;ItemDecoration实现分割线&#xff0c;边距和背景&#xff0c;以及SnapHelper的使用。 一、RecyclerView和ListView 1. 性能和视图重用 ListView 使用的是 ViewHolder 模式来实现视图的重用&#xff0c;但需要手动配置…

[运维][Nginx]Nginx学习(2/5)-Nginx高级

Nginx服务器基础配置实例 前面我们已经对Nginx服务器默认配置文件的结构和涉及的基本指令做了详细的阐述。通过这些指令的合理配置&#xff0c;我们就可以让一台Nginx服务器正常工作&#xff0c;并且提供基本的web服务器功能。 接下来我们将通过一个比较完整和最简单的基础配…

动态规划习题其四【力扣】【算法学习day.26】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&am…

candence : 原理图如何导出原理库?

原理图如何导出原理库&#xff1f; 1、打开要需要导出原理图库的工程文件&#xff0c;新建一个原理图库&#xff1a; 2、copy 需要导出的原理图的库文件 3、粘贴到 刚刚新建的原理图库文件中即可 完成 可以一个一个复制&#xff0c;也可以多可一起复制。

二叉树的遍历

普通二叉树的遍历 前序遍历:根 左子树 右子树 中序遍历:左子树 根 右子树 后序遍历:左子树 右子树 根 一颗普通二叉树的实现 #include<stdlib.h> //树的定义 typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left;s…

WebStorm 如何调试 Vue 项目

前言 在日常开发和各种教程中&#xff0c;最常见的 debug 方式就是在代码中插入 console.log 语句&#xff0c;然后在 Chrome 控制台中查看日志。显而易见&#xff0c;插入console.log 的效率不高&#xff0c;那是否有更高效的 debug 方式呢&#xff1f;断点调试允许开发者在代…

timedatectl status显示系统时间相关信息

timedatectl status命令用于显示当前系统的时间和日期相关信息。 下面是每行含义&#xff1a; Local time: 当前系统的本地时间Universal time: 当前系统的协调世界时&#xff08;UTC&#xff09;RTC time: 硬件时钟&#xff08;Real Time Clock&#xff09;的时间Time zone:…

【网页设计】HTML5 和 CSS3 提高

目标 能够说出 3~5 个 HTML5 新增布局和表单标签能够说出 CSS3 的新增特性有哪些 1. HTML5 的新特性 注&#xff1a;该部分所有内容可参考菜鸟教程菜鸟教程 - 学的不仅是技术&#xff0c;更是梦想&#xff01; (runoob.com) HTML5 的新增特性主要是针对于以前的不足&#xf…

09C++结构体

/*结构体属于用户自定义的数据类型&#xff0c; 允许用户存储不同的数据类型, 语法:struct 结构体名{结构体成员列表} ;*/ //struct 结构体名 变量名 #include <iostream> #include <string> using namespace std; struct student { string name; int age;int s…

软件测试之白盒测试(超详细总结)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 白盒测试 白盒测试&#xff08;White Box Testing&#xff09;又称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。白盒测试只测试软件产品的内部结…

【入门篇】数字统计——多语言版

题目跳转&#xff1a;数字统计 题目解析&#xff1a; 这道题目要求统计在给定范围 [L, R] 内所有整数中数字 2 出现的次数。例如&#xff0c;在范围 [2, 22] 中&#xff0c;数字 2 分别在数 2、12、20、21、22 中出现的次数&#xff0c;最终出现了6次。 题目的输入为两个正…

C++初阶——list

一、什么是list list是一个可以在序列的任意位置进行插入和删除的容器&#xff0c;并且可以进行双向迭代。list的底层是一个双向链表&#xff0c;双向链表可以将它们包含的每个元素存储在不同且不相关的存储位置。通过将每个元素与前一个元素的链接和后一个元素的链接关联起来&…

FlinkSql读取kafka数据流的方法(scala)

我的scala版本为2.12 <scala.binary.version>2.12</scala.binary.version> 我的Flink版本为1.13.6 <flink.version>1.13.6</flink.version> FlinkSql读取kafka数据流需要如下依赖&#xff1a; <dependency><groupId>org.apache.flink&…