<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>NebulaGraph (NebulaGraph)</title>
    <link>https://ruby-china.org/NebulaGraph</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>集群通信：从心跳说起 </title>
      <description>&lt;p&gt;​&amp;gt; 本文首发 Nebula Graph 官网：&lt;a href="https://nebula-graph.com.cn/posts/cluster-communication-heartbeat/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.com.cn/posts/cluster-communication-heartbeat/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在用户使用 Nebula Graph 的过程中，经常会遇到各种问题，通常我们都会建议先通过 &lt;code&gt;show hosts&lt;/code&gt; 查看集群状态。可以说，整个 Nebula Graph 的集群状态都是靠心跳机制来构建的。本文将从心跳说起，帮助你了解 Nebula Graph 集群各个节点之间通信的机制。&lt;/p&gt;
&lt;h2 id="什么是心跳？有什么作用？"&gt;什么是心跳？有什么作用？&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-metad-storaged-graphd-v-r-family.png" title="" alt="metad storaged graphd 通信"&gt;&lt;/p&gt;

&lt;p&gt;Nebula Graph 集群一般包含三种节点，graphd 作为查询节点，storaged 作为存储节点，metad 作为元信息节点。本文说的心跳，主要是指 graphd 和 storaged 定期向 metad 上报信息的这个心跳，借助心跳，整个集群完成了以下功能。（相关参数是 &lt;code&gt;heartbeat_interval_secs&lt;/code&gt;）&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;在 Nebula Graph 中经常提及的 raft 心跳则是用于拥有同一个 partition 的多个 storaged 之间的心跳，和本文提的心跳并不相同。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="1. 服务发现"&gt;1. 服务发现&lt;/h3&gt;
&lt;p&gt;当我们启动一个 Nebula Graph 集群时，需要在对应的配置文件中填写 &lt;code&gt;meta_server_addrs&lt;/code&gt;。graphd 和 storaged 在启动之后，就会通过这个 &lt;code&gt;meta_server_addrs&lt;/code&gt; 地址，向对应的 metad 发送心跳。通常来说，graphd 和 storaged 在连接上 metad 前是无法对外进行服务的。当 metad 收到心跳后，会保存相关信息（见下文第 2 点），此时就能够通过 show hosts 看到对应的 storaged 节点，在 2.x 版本中，也能够通过 &lt;code&gt;show hosts graph&lt;/code&gt; 看到 graphd 节点。&lt;/p&gt;
&lt;h3 id="2. 上报节点信息"&gt;2. 上报节点信息&lt;/h3&gt;
&lt;p&gt;在 metad 收到心跳时，会将心跳中的 ip、port、节点类型、心跳时间等等信息保存，以供后续使用（见下文）。&lt;/p&gt;

&lt;p&gt;除此以外 storaged 在自身 leader 数量变化的时候也会上报 leader 信息，在 &lt;code&gt;show hosts&lt;/code&gt; 中看到的 Leader count 和 Leader distribution 就是通过心跳汇报的。&lt;/p&gt;
&lt;h3 id="3. 更新元信息"&gt;3. 更新元信息&lt;/h3&gt;
&lt;p&gt;当客户通过 console 或者各种客户端，对集群的元信息进行更改之后（例如 &lt;code&gt;create/drop space&lt;/code&gt;、&lt;code&gt;create/alter/drop tag/edge&lt;/code&gt;、&lt;code&gt;update configs&lt;/code&gt; 等等），通常在几秒之内，整个集群就都会更新元数据。&lt;/p&gt;

&lt;p&gt;每次 graphd 和 storaged 在心跳的响应中会包含一个 &lt;code&gt;last_update_time&lt;/code&gt;，这个时间是由 metad 返回给各个节点的，用于告知 metad 自身最后一次更新元信息的时间。当 graphd 或者 storaged 发现 metad 的元信息有更新，就会向 metad 获取相应信息（例如 space 信息、schema 信息、配置更改等等）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;我们以创建一个 tag 为例，如果在 graphd/storaged 获取到新创建的这个 tag 信息之前，我们无法插入这个 tag 数据（会报类似 &lt;strong&gt;No schema found&lt;/strong&gt; 这样的错误）。而当通过心跳获取到对应信息并保存至本地缓存后，就能够正常写入数据了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="心跳上报的信息有什么用？"&gt;心跳上报的信息有什么用？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;how hosts&lt;/code&gt;、&lt;code&gt;show parts&lt;/code&gt; 这类命令都是通过 metad 中保存的各个节点心跳信息，组合显示出来的。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;balance data&lt;/code&gt;、&lt;code&gt;balance leader&lt;/code&gt; 等运维命令，需要通过获取当前集群内哪些 storaged 节点是在线状态，实际也是通过 metad 判断最近一次心跳时间是否在阈值之内。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create space&lt;/code&gt;，当用户创建一个 space 时，metad 也需要获取 storaged 的状态，将这个 space 的各个 partition 分配到在线的 storaged 中。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;以用户容易遇到的问题为例：假如我们启动一个 storaged 后，关掉并修改端口号，然后再启动 storaged。如果这个过程足够快，那么通过 &lt;code&gt;show hosts&lt;/code&gt; 能看到两个在线的 storaged。此时，如果新建一个 space，例如 &lt;code&gt;CREATE space test(partition_num=10, replica_factor=1)&lt;/code&gt;，这个 test space 就会分布在前后启动的两个 storage 上。但如果等到在 show hosts 中看到其中一个离线后，再执行 &lt;code&gt;CREATE space test(partition_num=10, replica_factor=1)&lt;/code&gt;，即便离线的 storaged 再启动，也只有一个 storaged 拥有这个 space（创建 test space 时 online 的那个 storaged）。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/heart-beat-online.png" title="" alt="上线"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/heart-beat-offline.png" title="" alt="掉线"&gt;&lt;/p&gt;
&lt;h2 id="心跳的演变历史"&gt;心跳的演变历史&lt;/h2&gt;
&lt;p&gt;在 18-19 年的时候，当时的心跳机制没有这么完善。一方面，无论元信息是否更改，都会从 metad 获取最新的元信息。而通常来说，元信息改动不会很频繁，定期获取元信息有一定的资源浪费。另一方面，想要将一个 storaged 节点加入和移除都是通过类似 &lt;code&gt;add/delete hosts&lt;/code&gt; 这样的命令，采取的是类似白名单的机制。对于其他没有认证过的节点，都无法对外服务，这样做固然也有一些优势，带来的最大问题就是不够友好。&lt;/p&gt;

&lt;p&gt;因此，在 19 年底开始，我们对心跳做了一系列的改动，特别鸣谢社区用户 &lt;a href="/zhanggguoqing" class="user-mention" title="@zhanggguoqing"&gt;&lt;i&gt;@&lt;/i&gt;zhanggguoqing&lt;/a&gt;。经过一段时间的验证踩坑后，基本就形成了现在的形式。&lt;/p&gt;
&lt;h2 id="额外的补充"&gt;额外的补充&lt;/h2&gt;
&lt;p&gt;有关心跳还有一个涉及到的问题就是 &lt;u&gt;cluster.id&lt;/u&gt; 这个文件。它实际是为了防止 storaged 与错误的 metad 通信，大致原理如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;首先，metad 在启动的时候会根据 &lt;code&gt;meta_server_addrs&lt;/code&gt; 这个参数，生成一个 hash 值并保存在本地 kv 中。&lt;/li&gt;
&lt;li&gt;storaged 在启动的时候会尝试从 &lt;u&gt;cluster.id&lt;/u&gt; 这个文件中获取对应 metad 的 hash 值，并附送在心跳中发送（如果文件不存在，则第一次使用 0 代替。收到心跳响应时，将 metad 的 hash 值保存在 &lt;u&gt;cluster.id&lt;/u&gt; 这个文件中，后续一直使用该值）。&lt;/li&gt;
&lt;li&gt;在心跳处理中，metad 会比较本地 hash 值和来自 storaged 心跳请求中的 hash 值，如果不匹配则拒绝。此时，storaged 是无法对外服务的，也就是 &lt;u&gt;Reject wrong cluster host&lt;/u&gt; 这个日志的由来。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以上就是心跳机制大致的介绍，感兴趣的你可以参考下源码实现，GitHub 传送门：&lt;a href="https://github.com/vesoft-inc/nebula-graph" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-graph&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="推荐阅读"&gt;推荐阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.io/cn/posts/how-indexing-works-in-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;分布式图数据库 Nebula Graph 的 Index 实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.io/cn/posts/introduction-to-snapshot-in-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;分布式图数据库 Nebula Graph 中的集群快照实践&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Thu, 01 Apr 2021 18:25:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/41102</link>
      <guid>https://ruby-china.org/topics/41102</guid>
    </item>
    <item>
      <title>[北京 / 上海 / 深圳 / 杭州] 技术布道师 20k-40k 15 薪</title>
      <description>&lt;h2 id="Nebula Graph 社区布道师"&gt;Nebula Graph 社区布道师&lt;/h2&gt;
&lt;p&gt;作为一款&lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;开源的数据库产品&lt;/a&gt;，社区对 Nebula Graph 团队至关重要。用户和贡献者为社区提供了大量的支持和帮助，使得 Nebula Graph 作为一款数据库产品更稳、更快，同时产品生态更丰富。社区布道师的加入，将使得 Nebula Graph 社区更具活力，更富创造力。&lt;/p&gt;

&lt;p&gt;我们在寻找可以和 Nebula Graph 社区一同成长的你，一起撸起袖子，接受挑战，用你的知识、技能、态度创造出属于你的社区影响力。&lt;/p&gt;
&lt;h2 id="关于布道师"&gt;关于布道师&lt;/h2&gt;
&lt;p&gt;作为 Nebula Graph 社区布道师，你的职责是帮助社区开发者使用 Nebula Graph，提升他们的使用体验。目前疫情期间，线下交流较少，你将通过线上渠道跟社区保持联系，同时生产社区需要的优质内容，如技术分享类文章、操作类视频、用户案例等。你的终极目标是帮助 Nebula Graph 使用者，达成我们的使命，即数据智能技术让世界更清晰。&lt;/p&gt;
&lt;h2 id="岗位职责"&gt;岗位职责&lt;/h2&gt;
&lt;p&gt;作为 Nebula Graph 社区布道者，你将：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;通过线上线下的方式跟世界各地的图数据库技术开发者、用户、爱好者交流，帮助他们更好地在各自的应用场景中使用图数据库技术&lt;/li&gt;
&lt;li&gt;编写代码示例，准备大会演讲，制作教学视频，撰写优质的相关技术内容&lt;/li&gt;
&lt;li&gt;在相关的会议和线下分享活动中做主题演讲&lt;/li&gt;
&lt;li&gt;在各个 Nebula Graph 用户交流渠道答疑解惑，帮助用户，包括：官方论坛、Slack、微信群、GitHub、Twitter、Stack Overflow，或用户使用的其他渠道&lt;/li&gt;
&lt;li&gt;向 Nebula Graph dev 团队反馈社区需求和建议，参与产品 roadmap 讨论和制定&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="岗位要求"&gt;岗位要求&lt;/h2&gt;
&lt;p&gt;我们寻找的你：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;是一名资深软件工程师&lt;/li&gt;
&lt;li&gt;擅长写作，能创作出受社区欢迎的技术文章&lt;/li&gt;
&lt;li&gt;能基于你想布道的技术，写出高质量的代码示例和迷你应用&lt;/li&gt;
&lt;li&gt;能根据你的演讲和文章，制作出相应的教学视频&lt;/li&gt;
&lt;li&gt;是一位风趣、自信的演讲者，无论线上还是线下&lt;/li&gt;
&lt;li&gt;具备产品经理对产品的敏锐度，对产品演进方向有自己的想法，并会积极争取主要决策者的支持&lt;/li&gt;
&lt;li&gt;对分布式数据库、图数据库技术了解者优先&lt;/li&gt;
&lt;li&gt;能熟练使用 C++ 者优先&lt;/li&gt;
&lt;li&gt;对分布式图数据库相关技术动态保持关注者优先&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="联系我们"&gt;联系我们&lt;/h2&gt;
&lt;p&gt;有意向的小伙伴可以通过以下方式联系我们：&lt;/p&gt;

&lt;p&gt;email: hire@vesoft.com&lt;/p&gt;

&lt;p&gt;电话：057128120658 转 1&lt;/p&gt;

&lt;p&gt;薪酬区间：20-40k&lt;/p&gt;

&lt;p&gt;Base 地：北京 /上海 /深圳 /杭州 任选&lt;/p&gt;

&lt;p&gt;欢迎你来！&lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Mon, 22 Feb 2021 15:19:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/40936</link>
      <guid>https://ruby-china.org/topics/40936</guid>
    </item>
    <item>
      <title>图数据库 Nebula Graph 在 Boss 直聘的应用</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文首发于 Nebula Graph 官方博客：&lt;a href="https://nebula-graph.com.cn/posts/nebula-graph-risk-control-boss-zhipin/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.com.cn/posts/nebula-graph-risk-control-boss-zhipin/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;摘要：在本文中，BOSS 直聘大数据开发工程师主要分享一些他们内部的技术指标和选型，以及很多小伙伴感兴趣的 Dgraph 对比使用经验。&lt;/p&gt;
&lt;h2 id="业务背景"&gt;业务背景&lt;/h2&gt;
&lt;p&gt;在 Boss 直聘的安全风控技术中，需要用到大规模图存储和挖掘计算，之前主要基于自建的高可用 &lt;a href="https://github.com/neo4j/neo4j" rel="nofollow" target="_blank" title=""&gt;Neo4j&lt;/a&gt; 集群来保障相关应用，而在实时行为分析方面，需要一个支持日增 10 亿关系的图数据库，Neo4j 无法满足应用需求。&lt;/p&gt;

&lt;p&gt;针对这个场景，前期我们主要使用  &lt;a href="https://github.com/dgraph-io/dgraph" rel="nofollow" target="_blank" title=""&gt;Dgraph&lt;/a&gt;，踩过很多坑并和 Dgraph 团队连线会议，在使用 Dgraph 半年后最终还是选择了更贴合我们需求的 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt;。具体的对比 &lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1377" rel="nofollow" target="_blank" title=""&gt;Benchmark&lt;/a&gt; 已经有很多团队在论坛分享了，这里就不再赘述，主要分享一些技术指标和选型，以及很多小伙伴感兴趣的 Dgraph 对比使用经验。&lt;/p&gt;
&lt;h2 id="技术指标"&gt;技术指标&lt;/h2&gt;&lt;h3 id="硬件"&gt;硬件&lt;/h3&gt;
&lt;p&gt;配置如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;处理器：Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz 80(cores)&lt;/li&gt;
&lt;li&gt;内存：DDR4，128G&lt;/li&gt;
&lt;li&gt;存储：1.8T SSD&lt;/li&gt;
&lt;li&gt;网络：万兆&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nebula Graph 部署 5 个节点，按官方建议 3 个 metad / 5 个 graphd / 5 个 storaged&lt;/p&gt;
&lt;h3 id="软件"&gt;软件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Nebula Graph 版本：&lt;a href="https://github.com/vesoft-inc/nebula/releases/tag/v1.1.0" rel="nofollow" target="_blank" title=""&gt;V1.1.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;操作系统：CentOS Linux release 7.3.1611 (Core)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="配置"&gt;配置&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;主要调整的配置和 storage 相关
# 按照文档建议，配置内存的 3 分之 1
--rocksdb_block_cache=40960

# 参数配置减小内存使用
--enable_partitioned_index_filter=true
--max_edge_returned_per_vertex=100000
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="指标"&gt;指标&lt;/h3&gt;
&lt;p&gt;目前安全行为图保存 3 个月行为，近 500 亿边，10 分钟聚合写入一次，日均写入点 3,000 万，日均写入边 5.5 亿，插入延时 &amp;lt;=20 ms。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin01.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin02.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin03.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;p&gt;读延时 &amp;lt;= 100 ms，业务侧接口读延时 &amp;lt;= 200 ms，部分超大请求 &amp;lt; 1 s&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin04.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin05.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;p&gt;当前磁盘空间占用 600G * 5 左右&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin06.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;p&gt;CPU 耗用 500% 左右，内存使用稳定在 60 G 左右&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin07.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;
&lt;h2 id="Dgraph 使用对比"&gt;Dgraph 使用对比&lt;/h2&gt;
&lt;p&gt;目前来说原生分布式图数据库国内选型主要比对 Dgraph 和 Nebula Graph，前者我们使用半年，整体使用对比如下，这些都是我们踩过坑的地方。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-risk-control-boss-zhipin08.png" title="" alt="BOSS 直聘图数据库实践"&gt;&lt;/p&gt;

&lt;p&gt;就我们使用经验，Dgraph 设计理念很好，但是目前还不太满足我们业务需求，GraphQL 的原生支持还是有很大吸引力，但是存储结构决定容易 OOM（边存储也分组的话会优化很多，官方之前计划优化）；另外，采用自己编写的 badger 和 ristretto，目前最大的问题是从官方释放的使用案例来看，未经大规模数据场景验证，在我们实际使用中，大数据量和高 QPS 写入场景下容易出现崩溃和 OOM，且如果采用 SSD 存储海量数据，Dgraph 的磁盘放大和内存占用也需要优化。&lt;/p&gt;

&lt;p&gt;如果没有高 QPS 写入，目前 Dgraph 还是值得一试，对于很多快速原型的场景，作为 GraphQL 原生图数据库使其非常适合做基于图的数据中台，这是目前的一个大趋势，它也上线了自己的云服务，业内标杆 TigerGraph 也在做相关探索，另外事务的完善支持也是它的优势，这块暂时用不到，所以没做相关评测。实测 Dgraph 在线写入并发不高或只是离线导入数据使用的情况下还是很稳定的，如果想借助它的高可用和事务功能，可以尝试下。&lt;/p&gt;

&lt;p&gt;对比来说，Nebula Graph 很优秀，特别是工程化方面，体现在很多细节，可以看出开发团队在实际使用和实现上做较了较好的平衡：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1.支持手动控制数据平衡时机，自动固然很好，但是容易导致很多问题&lt;/li&gt;
&lt;li&gt;2.控制内存占用（enable_partitioned_index_filter 优化和设置单次最大返回边数目），都放在内存固然快，但有时候也需要考虑数据量和性能的平衡&lt;/li&gt;
&lt;li&gt;3.多图物理隔离，多张图实在太有必要&lt;/li&gt;
&lt;li&gt;4.nGQL 最大程度接近最常用 MySQL 语句，2 期兼容 Cypher 更加完美；对比 GraphQL 固然香，但写起复杂图查询真的让人想爆炸，可能还是更加适合做数据中台查询语言&lt;/li&gt;
&lt;li&gt;5.和图计算框架的结合，最近释放的 Spark GraphX 结合算法非常有用，原先我们的图计算都是基于 GraphX 从 Neo4j 抽取后离线计算团伙，后续打算尝试 Nebula Graph 抽取&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里主要从实际经验对比分享，二者都在持续优化，都在快速迭代，建议使用前多看看最新版本 release 说明。&lt;/p&gt;
&lt;h2 id="建议"&gt;建议&lt;/h2&gt;
&lt;p&gt;当前 Nebula Graph 做得很优秀，结合我们现在的需求也提一点点建议：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1.&lt;strong&gt;更多离线算法&lt;/strong&gt;，包括：现有的图神经网络这块的支持，图在线查询多用在分析，真正线上应用目前很多还是图计算离线算完后入库供查询&lt;/li&gt;
&lt;li&gt;2.&lt;strong&gt;Plato 框架的合并支持&lt;/strong&gt;，Spark GraphX 相对计算效率还是低一些，如果能整合腾讯的 Plato 框架更好&lt;/li&gt;
&lt;li&gt;3.借鉴 TigerGraph 和 Dgraph，&lt;strong&gt;支持固化 nGQL 查询语句直接生成服务 REST 端点&lt;/strong&gt;，&lt;strong&gt;HTTP 传入参数即可查询&lt;/strong&gt;，这样可快速生成数据查询接口，不用后台再单独连接数据库写 SQL 提供数据服务&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;目前 Boss 直聘将 Nebula Graph 图数据库应用在安全业务，相关应用已经线上稳定运行大半年，本文分享了一点经验，抛砖引玉，期望更多技术伙伴来挖掘 Nebula 这座宝库。&lt;/p&gt;

&lt;p&gt;Dgraph 遇到的一些问题，供有需要小伙伴参考&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;给 Dgraph 一些 &lt;a href="https://github.com/dgraph-io/dgraph/issues?q=jimwen" rel="nofollow" target="_blank" title=""&gt;issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;给 Dgraph 提交的 &lt;a href="https://github.com/dgraph-io/dgraph/pulls?q=is%3Apr+author%3A%40me+is%3Aclosed" rel="nofollow" target="_blank" title=""&gt;PRs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="参考文章"&gt;参考文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1172" rel="nofollow" target="_blank" title=""&gt;360 的 JanusGraph 到 Nebula Graph 数据迁移&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;本文系 Boss 直聘·安全技术中心 文洲 撰写&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="推荐阅读"&gt;推荐阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/practicing-nebula-graph-webank/" rel="nofollow" target="_blank" title=""&gt;Nebula Graph 在微众银行数据治理业务的实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/why-migrate-janusgraph-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;图数据库选型 ｜ 360 数科的图数据库迁移史&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 23 Dec 2020 14:34:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/40734</link>
      <guid>https://ruby-china.org/topics/40734</guid>
    </item>
    <item>
      <title>Spark Connector Reader 原理与实践</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/Draft/nebula-spark-connector-reader.png" title="" alt="nebula-spark-connector-reader"&gt;&lt;/p&gt;

&lt;p&gt;本文主要讲述如何利用 Spark Connector 进行 Nebula Graph 数据的读取。&lt;/p&gt;
&lt;h2 id="Spark Connector 简介"&gt;Spark Connector 简介&lt;/h2&gt;
&lt;p&gt;Spark Connector 是一个 Spark 的数据连接器，可以通过该连接器进行外部数据系统的读写操作，Spark Connector 包含两部分，分别是 Reader 和 Writer，而本文侧重介绍 Spark Connector Reader，Writer 部分将在下篇和大家详聊。&lt;/p&gt;
&lt;h2 id="Spark Connector Reader 原理"&gt;Spark Connector Reader 原理&lt;/h2&gt;
&lt;p&gt;Spark Connector Reader 是将 Nebula Graph 作为 Spark 的扩展数据源，从 Nebula Graph 中将数据读成 DataFrame，再进行后续的 map、reduce 等操作。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://spark.apache.org/sql/" rel="nofollow" target="_blank" title=""&gt;Spark SQL &lt;/a&gt;允许用户自定义数据源，支持对外部数据源进行扩展。通过 Spark SQL 读取的数据格式是以命名列方式组织的分布式数据集 DataFrame，Spark SQL 本身也提供了众多 API 方便用户对 DataFrame 进行计算和转换，能对多种数据源使用 DataFrame 接口。&lt;/p&gt;

&lt;p&gt;Spark 调用外部数据源包的是 &lt;code&gt;org.apache.spark.sql&lt;/code&gt;，首先了解下 Spark SQL 提供的的扩展数据源相关的接口。&lt;/p&gt;
&lt;h3 id="Basic Interfaces"&gt;Basic Interfaces&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;BaseRelation：表示具有已知 Schema 的元组集合。所有继承 BaseRelation 的子类都必须生成 StructType 格式的 Schema。换句话说，BaseRelation 定义了从数据源中读取的数据在 Spark SQL 的 DataFrame 中存储的数据格式的。&lt;/li&gt;
&lt;li&gt;RelationProvider：获取参数列表，根据给定的参数返回一个新的 BaseRelation。&lt;/li&gt;
&lt;li&gt;DataSourceRegister：注册数据源的简写，在使用数据源时不用写数据源的全限定类名，而只需要写自定义的 shortName 即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Providers"&gt;Providers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;RelationProvider：从指定数据源中生成自定义的 relation。 &lt;code&gt;createRelation()&lt;/code&gt;&amp;nbsp; 会基于给定的 Params 参数生成新的 relation。&lt;/li&gt;
&lt;li&gt;SchemaRelationProvider：可以基于给定的 Params 参数和给定的 Schema 信息生成新的 Relation。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="RDD"&gt;RDD&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;RDD[InternalRow]: 从数据源中 Scan 出来后需要构造成 RDD[Row]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要实现自定义 Spark 外部数据源，需要根据数据源自定义上述部分方法。&lt;/p&gt;

&lt;p&gt;在 Nebula Graph 的 Spark Connector 中，我们实现了将 Nebula Graph 作为 Spark SQL 的外部数据源，通过 &lt;code&gt;sparkSession.read&lt;/code&gt;&amp;nbsp;形式进行数据的读取。该功能实现的类图展示如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://oscimg.oschina.net/oscnet/up-330d94016a546897d01fa1bb67c6e4ca2c9.png" title="" alt=""&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;定义数据源 NebulaRelatioProvider，继承 RelationProvider 进行 relation 自定义，继承 DataSourceRegister 进行外部数据源的注册。&lt;/li&gt;
&lt;li&gt;定义 NebulaRelation 定义 Nebula Graph 的数据 Schema 和数据转换方法。在 &lt;code&gt;getSchema()&lt;/code&gt;&amp;nbsp;方法中连接 Nebula Graph 的 Meta 服务获取配置的返回字段对应的 Schema 信息。&lt;/li&gt;
&lt;li&gt;定义 NebulaRDD 进行 Nebula Graph 数据的读取。 &lt;code&gt;compute()&lt;/code&gt;&amp;nbsp;方法中定义如何读取 Nebula Graph 数据，主要涉及到进行 Nebula Graph 数据 Scan、将读到的 Nebula Graph Row 数据转换为 Spark 的 InternalRow 数据，以 InternalRow 组成 RDD 的一行，其中每一个 InternalRow 表示 Nebula Graph 中的一行数据，最终通过分区迭代的形式将 Nebula Graph 所有数据读出组装成最终的 DataFrame 结果数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="Spark Connector Reader 实践"&gt;Spark Connector Reader 实践&lt;/h2&gt;
&lt;p&gt;Spark Connector 的 Reader 功能提供了一个接口供用户编程进行数据读取。一次读取一个点/边类型的数据，读取结果为 DataFrame。&lt;/p&gt;

&lt;p&gt;下面开始实践，拉取 GitHub 上 Spark Connector 代码：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;-b&lt;/span&gt; v1.0 git@github.com:vesoft-inc/nebula-java.git
&lt;span class="nb"&gt;cd &lt;/span&gt;nebula-java/tools/nebula-spark
mvn clean compile package &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-Dgpg&lt;/span&gt;.skip &lt;span class="nt"&gt;-Dmaven&lt;/span&gt;.javadoc.skip&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将编译打成的包 copy 到本地 Maven 库。&lt;/p&gt;

&lt;p&gt;应用示例如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在 mvn 项目的 pom 文件中加入 &lt;code&gt;nebula-spark&lt;/code&gt; 依赖&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.vesoft&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;nebula-spark&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.1.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在 Spark 程序中读取 Nebula Graph 数据：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// 读取 Nebula Graph 点数据
val vertexDataset: Dataset[Row] &lt;span class="o"&gt;=&lt;/span&gt;
      spark.read
        .nebula&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1:45500"&lt;/span&gt;, &lt;span class="s2"&gt;"spaceName"&lt;/span&gt;, &lt;span class="s2"&gt;"100"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        .loadVerticesToDF&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tag"&lt;/span&gt;, &lt;span class="s2"&gt;"field1,field2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
vertexDataset.show&lt;span class="o"&gt;()&lt;/span&gt;

// 读取 Nebula Graph 边数据
val edgeDataset: Dataset[Row] &lt;span class="o"&gt;=&lt;/span&gt;
      spark.read
        .nebula&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1:45500"&lt;/span&gt;, &lt;span class="s2"&gt;"spaceName"&lt;/span&gt;, &lt;span class="s2"&gt;"100"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        .loadEdgesToDF&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"edge"&lt;/span&gt;, &lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
edgeDataset.show&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nebula(address: String, space: String, partitionNum: String)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;address：可以配置多个地址，以英文逗号分割，如“ip1:45500,ip2:45500”
space: Nebula Graph 的 graphSpace
partitionNum： 设定spark读取Nebula时的partition数，尽量使用创建 Space 时指定的 Nebula Graph 中的 partitionNum，可确保一个Spark的partition读取Nebula Graph一个part的数据。
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;loadVertices(tag: String, fields: String)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;tag：Nebula Graph 中点的 Tag
fields：该 Tag 中的字段，，多字段名以英文逗号分隔。表示只读取 fields 中的字段，&lt;span class="err"&gt;*&lt;/span&gt; 表示读取全部字段
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;loadEdges(edge: String, fields: String)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;edge：Nebula Graph 中边的 Edge
fields：该 Edge 中的字段，多字段名以英文逗号分隔。表示只读取 fields 中的字段，&lt;span class="err"&gt;*&lt;/span&gt; 表示读取全部字段
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="其他"&gt;其他&lt;/h2&gt;
&lt;p&gt;Spark Connector Reader 的 GitHub 代码：&lt;a href="https://github.com/vesoft-inc/nebula-java/tree/master/tools/nebula-spark" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-java/tree/master/tools/nebula-spark&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在此特别感谢半云科技所贡献的 Spark Connector 的 Java 版本&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;[1] &lt;a href="http://sparkdatasourceapi.blogspot.com/2016/10/spark-data-source-api-write-custom.html" rel="nofollow" target="_blank" title=""&gt;Extending Spark Datasource API: write a custom spark datasource&lt;/a&gt;
[2] &lt;a href="https://github.com/apache/spark/tree/master/external" rel="nofollow" target="_blank" title=""&gt;spark external datasource source code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;喜欢这篇文章？来来来，给我们的 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt; 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]&lt;/p&gt;

&lt;p&gt;交流图数据库技术？交个朋友，Nebula Graph 官方小助手微信：&lt;a href="https://www-cdn.nebula-graph.com.cn/nebula-blog/nbot.png" rel="nofollow" target="_blank" title=""&gt;NebulaGraphbot&lt;/a&gt; 拉你进交流群~~&lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Thu, 17 Dec 2020 16:00:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/40709</link>
      <guid>https://ruby-china.org/topics/40709</guid>
    </item>
    <item>
      <title>记一起由 Clang 编译器优化触发的 Crash</title>
      <description>&lt;p&gt;摘要：一个有意思的 Crash 探究过程，Clang 有 GCC 没有&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文首发于 Nebula Graph 官方博客：&lt;a href="https://nebula-graph.com.cn/posts/troubleshooting-crash-clang-compiler-optimization/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.com.cn/posts/troubleshooting-crash-clang-compiler-optimization/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/troubleshooting-crash-clang-compiler-optimization.png" title="" alt="troubleshooting-crash-clang-compiler-optimization"&gt;&lt;/p&gt;

&lt;p&gt;如果有人告诉你，下面的 C++ 函数会导致程序 crash，你会想到哪些原因呢？&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;b2s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果再多给一些描述，比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crash 以一定的概率复现&lt;/li&gt;
&lt;li&gt;Crash 原因是段错误（SIGSEGV）&lt;/li&gt;
&lt;li&gt;现场的 Backtrace 经常是不完整甚至完全丢失的。&lt;/li&gt;
&lt;li&gt;只有优化级别在 -O2 以上才会（更容易）复现&lt;/li&gt;
&lt;li&gt;仅在 Clang 下复现，GCC 复现不了&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;好了，一些老鸟可能已经有线索了，下面给出一个最小化的复现程序和步骤：&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file crash.cpp&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;__attribute__&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;noinline&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;b2s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;union&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b2s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ clang++ -O2 crash.cpp
$ ./a.out
truefalse,d$x4DdzRx

Segmentation fault (core dumped)

$ gdb ./a.out core.3699
Core was generated by `./a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000012cfffff0d4 in ?? ()
(gdb) bt
#0  0x0000012cfffff0d4 in ?? ()
#1  0x00000064fffff0f4 in ?? ()
#2  0x00000078fffff124 in ?? ()
#3  0x000000b4fffff1e4 in ?? ()
#4  0x000000fcfffff234 in ?? ()
#5  0x00000144fffff2f4 in ?? ()
#6  0x0000018cfffff364 in ?? ()
#7  0x0000000000000014 in ?? ()
#8  0x0110780100527a01 in ?? ()
#9  0x0000019008070c1b in ?? ()
#10 0x0000001c00000010 in ?? ()
#11 0x0000002ffffff088 in ?? ()
#12 0xe2ab001010074400 in ?? ()
#13 0x0000000000000000 in ?? ()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 backtrace 信息不完整，说明程序并不是在第一时间 crash 的。面对这种情况，为了快速找出第一现场，我们可以试试 AddressSanitizer（ASan）：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ clang++ -g -O2 -fno-omit-frame-pointer -fsanitize=address crash.cpp
$ ./a.out
=================================================================
==3699==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000552805 at pc 0x0000004ff83a bp 0x7ffd7610d240 sp 0x7ffd7610c9f0
READ of size 133 at 0x000000552805 thread T0
    #0 0x4ff839 in __asan_memcpy (a.out+0x4ff839)
    #1 0x5390a7 in b2s[abi:cxx11](bool) crash.cpp:6
    #2 0x5391be in main crash.cpp:16:18
    #3 0x7faed604df42 in __libc_start_main (/usr/lib64/libc.so.6+0x23f42)
    #4 0x41c43d in _start (a.out+0x41c43d)

0x000000552805 is located 59 bytes to the left of global variable '&amp;lt;string literal&amp;gt;' defined in 'crash.cpp:6:25' (0x552840) of size 6
  '&amp;lt;string literal&amp;gt;' is ascii string 'false'
0x000000552805 is located 0 bytes to the right of global variable '&amp;lt;string literal&amp;gt;' defined in 'crash.cpp:6:16' (0x552800) of size 5
  '&amp;lt;string literal&amp;gt;' is ascii string 'true'
SUMMARY: AddressSanitizer: global-buffer-overflow (/home/dutor.hou/Wdir/nebula-graph/build/bug/a.out+0x4ff839) in __asan_memcpy
Shadow bytes around the buggy address:
…
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从 ASan 给出的信息，我们可以定位到是函数 &lt;code&gt;b2s(bool)&lt;/code&gt; 在读取字符串常量 &lt;code&gt;"true"&lt;/code&gt; 的时候，发生了“全局缓冲区溢出”。好了，我们再次以上帝视角审视一下问题函数和复现程序，“似乎”可以得出结论：因为 &lt;code&gt;b2s&lt;/code&gt; 的布尔类型参数 &lt;code&gt;b&lt;/code&gt; 没有初始化，所以 &lt;code&gt;b&lt;/code&gt; 中存储的是一个 &lt;code&gt;0&lt;/code&gt; 和 &lt;code&gt;1&lt;/code&gt; 之外的值 [1]。那么问题来了，为什么 &lt;code&gt;b&lt;/code&gt; 的这种取值会导致“缓冲区溢出”呢？感兴趣的可以将 &lt;code&gt;b&lt;/code&gt; 的类型由 &lt;code&gt;bool&lt;/code&gt; 改成 &lt;code&gt;char&lt;/code&gt; 或者 &lt;code&gt;int&lt;/code&gt;，问题就可以得到修复。&lt;/p&gt;

&lt;p&gt;想要解答这个问题，我们不得不看下 clang++ 为 &lt;code&gt;b2s&lt;/code&gt; 生成了怎样的指令（之前我们提到 GCC 下没有出现 crash，所以问题可能和代码生成有关）。在此之前，我们应该了解：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;样例程序中，&lt;code&gt;b2s&lt;/code&gt; 的返回值是一个临时的 &lt;code&gt;std::string&lt;/code&gt; 对象，是保存在栈上的&lt;/li&gt;
&lt;li&gt;C++ 11 之后，GCC 的 &lt;code&gt;std::string&lt;/code&gt; 默认实现使用了 SBO（Small Buffer Optimization），其定义大致为 &lt;code&gt;std::string{ char *ptr; size_t size; union{ char buf[16]; size_t capacity}; }&lt;/code&gt;。对于长度小于 &lt;code&gt;16&lt;/code&gt; 的字符串，不需要额外申请内存。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OK，那我们现在来看一下 &lt;code&gt;b2s&lt;/code&gt; 的反汇编并给出关键注解：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) disas b2s
Dump of assembler code for function b2s[abi:cxx11](bool):
   0x00401200 &amp;lt;+0&amp;gt;:     push   %r14
   0x00401202 &amp;lt;+2&amp;gt;:     push   %rbx
   0x00401203 &amp;lt;+3&amp;gt;:     push   %rax
   0x00401204 &amp;lt;+4&amp;gt;:     mov    %rdi,%r14         # 将返回值(string)的起始地址保存到 r14
   0x00401207 &amp;lt;+7&amp;gt;:     mov    $0x402010,%ecx    # 将 "true" 的起始地址保存至 ecx
   0x0040120c &amp;lt;+12&amp;gt;:    mov    $0x402015,%eax    # 将 "false" 的起始地址保存至 eax
   0x00401211 &amp;lt;+17&amp;gt;:    test   %esi,%esi         # “测试” 参数 b 是否非零
   0x00401213 &amp;lt;+19&amp;gt;:    cmovne %rcx,%rax         # 如果 b 非零，则将 "true" 地址保存至 rax
   0x00401217 &amp;lt;+23&amp;gt;:    lea    0x10(%rdi),%rdi   # 将 string 中的 buf 起始地址保存至 rdi
                                                 # （同时也是后面 memcpy 的第一个参数）
   0x0040121b &amp;lt;+27&amp;gt;:    mov    %rdi,(%r14)       # 将 rdi 保存至 string 的 ptr 字段，即 SBO
   0x0040121e &amp;lt;+30&amp;gt;:    mov    %esi,%ebx         # 将 b 的值保存至 ebx
   0x00401220 &amp;lt;+32&amp;gt;:    xor    $0x5,%rbx         # 将 0x5 异或到 rbx（也即 ebx）
                                                 # 注意，如果 rbx 非 0 即 1，那么 rbx 保存的就是 4 或 5，
                                                 # 即 "true" 或 "false" 的长度 
   0x00401224 &amp;lt;+36&amp;gt;:    mov    %rax,%rsi         # 将字符串起始地址保存至 rsi，即 memcpy 的第二个参数
   0x00401227 &amp;lt;+39&amp;gt;:    mov    %rbx,%rdx         # 将字符串的长度保存至 rdx，即 memcpy 的第三个参数
   0x0040122a &amp;lt;+42&amp;gt;:    callq  &amp;lt;memcpy@plt&amp;gt;      # 调用 memcpy
   0x0040122f &amp;lt;+47&amp;gt;:    mov    %rbx,0x8(%r14)    # 将字符串长度保存到 string::size
   0x00401233 &amp;lt;+51&amp;gt;:    movb   $0x0,0x10(%r14,%rbx,1)  # 将 string 以 '\0' 结尾
   0x00401239 &amp;lt;+57&amp;gt;:    mov    %r14,%rax         # 将 string 地址保存至 rax，即返回值
   0x0040123c &amp;lt;+60&amp;gt;:    add    $0x8,%rsp
   0x00401240 &amp;lt;+64&amp;gt;:    pop    %rbx
   0x00401241 &amp;lt;+65&amp;gt;:    pop    %r14
   0x00401243 &amp;lt;+67&amp;gt;:    retq
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里，问题就无比清晰了：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;clang++ 假设了 &lt;code&gt;bool&lt;/code&gt; 类型的值非 &lt;code&gt;0&lt;/code&gt; 即 &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;在编译期，&lt;code&gt;”true”&lt;/code&gt; 和 &lt;code&gt;”false”&lt;/code&gt; 长度已知&lt;/li&gt;
&lt;li&gt;使用异或指令（ &lt;code&gt;0x5 ^ false == 5&lt;/code&gt;, &lt;code&gt;0x5 ^ true == 4&lt;/code&gt;）计算要拷贝的字符串的长度&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;bool&lt;/code&gt; 类型不符合假设时，长度计算错误&lt;/li&gt;
&lt;li&gt;因为 &lt;code&gt;memcpy&lt;/code&gt; 目标地址在栈上（仅对本例而言），因此栈上的缓冲区也可能溢出，从而导致程序跑飞，backtrace 缺失。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;C++ 标准要求 &lt;code&gt;bool&lt;/code&gt;&amp;nbsp;类型至少&lt;u&gt;能够&lt;/u&gt;表示两个状态： &lt;code&gt;true&lt;/code&gt;&amp;nbsp;和 &lt;code&gt;false&lt;/code&gt;&amp;nbsp;，但并没有规定 &lt;code&gt;sizeof(bool)&lt;/code&gt;&amp;nbsp;的大小。但在几乎所有的编译器实现上， &lt;code&gt;bool&lt;/code&gt;&amp;nbsp;都占用一个寻址单位，即字节。因此，从存储角度，取值范围为  &lt;code&gt;0x00-0xFF&lt;/code&gt;，即 &lt;code&gt;256&lt;/code&gt; 个状态。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;喜欢这篇文章？来来来，给我们的 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt; 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]&lt;/p&gt;

&lt;p&gt;交流图数据库技术？交个朋友，Nebula Graph 官方小助手微信：&lt;a href="https://www-cdn.nebula-graph.com.cn/nebula-blog/nbot.png" rel="nofollow" target="_blank" title=""&gt;NebulaGraphbot&lt;/a&gt; 拉你进交流群~~&lt;/p&gt;
&lt;h2 id="推荐阅读"&gt;推荐阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/segmentation-fault-gcc-illegal-instruction-trouble-shooting/" rel="nofollow" target="_blank" title=""&gt;一次 Segmentation Fault 和 GCC Illegal Instruction 编译问题排查&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Thu, 10 Dec 2020 09:58:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/40662</link>
      <guid>https://ruby-china.org/topics/40662</guid>
    </item>
    <item>
      <title>GraphX 在图数据库 Nebula Graph 的图计算实践</title>
      <description>&lt;p&gt;不同来源的异构数据间存在着千丝万缕的关联，这种数据之间隐藏的关联关系和网络结构特性对于数据分析至关重要，图计算就是以图作为数据模型来表达问题并予以解决的过程。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/practice-graphx-nebula-graph-algorithm.png" title="" alt="图计算实践"&gt;&lt;/p&gt;
&lt;h2 id="一、背景"&gt;一、背景&lt;/h2&gt;
&lt;p&gt;随着网络信息技术的飞速发展，数据逐渐向多源异构化方向发展，且不同来源的异构数据之间也存在的千丝万缕的关联，这种数据之间隐藏的关联关系和网络结构特性对于数据分析至关重要。但传统关系型数据库在分析大规模数据关联特性时存在性能缺陷、表达有限等问题，因此有着更强大表达能力的图数据受到业界极大重视，图计算就是以图作为数据模型来表达问题并予以解决的过程。图可以融合多源多类型的数据，除了可以展示数据静态基础特性之外，还可通过图计算展示隐藏在数据之间的图结构特性和点对关联关系，成为社交网络、推荐系统、知识图谱、金融风控、网络安全、文本检索等领域重要的分析手段。&lt;/p&gt;
&lt;h2 id="二、算法应用"&gt;二、算法应用&lt;/h2&gt;
&lt;p&gt;为了支撑大规模图计算的业务需求，&lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 基于 &lt;a href="https://github.com/apache/spark/tree/master/graphx" rel="nofollow" target="_blank" title=""&gt;GraphX&lt;/a&gt; 提供了 &lt;a href="https://en.wikipedia.org/wiki/PageRank" rel="nofollow" target="_blank" title=""&gt;PageRank&lt;/a&gt; 和 &lt;a href="https://en.wikipedia.org/wiki/Louvain_method" rel="nofollow" target="_blank" title=""&gt;Louvain&lt;/a&gt; 社区发现的图计算算法，允许用户通过提交 Spark 任务的形式执行算法应用。此外，用户也可以通过 Spark Connector 编写 Spark 程序调用 GraphX 自带的其他图算法，如 LabelPropagation、ConnectedComponent 等。&lt;/p&gt;
&lt;h3 id="PageRank"&gt;PageRank&lt;/h3&gt;
&lt;p&gt;PageRank 是谷歌提出的用于解决链接分析中网页排名问题的算法，目的是为了对互联网中数以亿计的网页进行排名。&lt;/p&gt;
&lt;h4 id="PageRank 简介"&gt;PageRank 简介&lt;/h4&gt;
&lt;p&gt;美国斯坦福大学的 Larry Page 和 Sergey Brin 在研究网页排序问题时采用学术界评判论文重要性的方法，即看论文的引用量以及引用该论文的论文质量，对应于网页的重要性有两个假设：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;数量假设：如果一个网页 A 被很多其他网页链接到，则该网页比较重要；&lt;/li&gt;
&lt;li&gt;质量假设：如果一个很重要的网页链接到网页 A，则该网页的重要性会被提高。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;并基于这两个假设提出 PageRank 算法。&lt;/p&gt;
&lt;h4 id="PageRank 应用场景"&gt;PageRank 应用场景&lt;/h4&gt;&lt;h5 id="社交应用的相似度内容推荐"&gt;社交应用的相似度内容推荐&lt;/h5&gt;
&lt;p&gt;在对微博、微信等社交平台进行社交网络分析时，可以基于 PageRank 算法根据用户通常浏览的信息以及停留时间实现基于用户的相似度的内容推荐；&lt;/p&gt;
&lt;h5 id="分析用户社交影响力"&gt;分析用户社交影响力&lt;/h5&gt;
&lt;p&gt;在社交网络分析时根据用户的 PageRank 值进行用户影响力分析；&lt;/p&gt;
&lt;h5 id="文献重要性研究"&gt;文献重要性研究&lt;/h5&gt;
&lt;p&gt;根据文献的 PageRank 值评判该文献的质量，PageRank 算法就是基于评判文献质量的想法来实现设计。&lt;/p&gt;

&lt;p&gt;此外 PageRank 在数据分析和挖掘中也有很多的应用。&lt;/p&gt;
&lt;h4 id="算法思路"&gt;算法思路&lt;/h4&gt;
&lt;p&gt;GraphX 的 PageRank 算法是基于 Pregel 计算模型的，该算法流程包括 3 步骤：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;为图中每个节点（网页）设置一个同样的初始 PageRank 值；&lt;/li&gt;
&lt;li&gt;第一次迭代：沿边发送消息，每个节点收到所有关联边上对点的信息，得到一个新的 PageRank 值；&lt;/li&gt;
&lt;li&gt;第二次迭代：用这组新的 PageRank 按不同算法模式对应的公式形成节点自己新的 PageRank。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="Louvain 社区发现"&gt;Louvain 社区发现&lt;/h3&gt;
&lt;p&gt;Louvain 是用来进行社会网络挖掘的社区发现算法，属于图的聚类算法。&lt;/p&gt;
&lt;h4 id="Louvain 算法介绍"&gt;Louvain 算法介绍&lt;/h4&gt;
&lt;p&gt;Louvain 是基于模块度（Modularity）的社区发现算法，通过模块度来衡量一个社区的紧密程度。如果一个节点加入到某一社区中会使得该社区的模块度相比其他社区有最大程度的增加，则该节点就应当属于该社区。如果加入其它社区后没有使其模块度增加，则留在自己当前社区中。&lt;/p&gt;
&lt;h5 id="模块度"&gt;模块度&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;模块度公式&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;模块度 Q 的物理意义：社区内节点的连边数与随机情况下的边数之差，定义函数如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-01.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;其中&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-02.png" title="" alt=""&gt;：节点 i 和节点 j 之间边的权重
&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-03.png" title="" alt=""&gt;：所有与节点 i 相连的边的权重之和
&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-04.png" title="" alt=""&gt;：节点 i 所属的社区
&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-05.png" title="" alt=""&gt;：图中所有边的权重之和&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;模块度公式变形&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在此公式中，只有节点 i 和节点 j 属于同一社区，公式才有意义，所以该公式是衡量的某一社区内的紧密度。对于该公式的简化变形如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-06.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-07.png" title="" alt=""&gt;表示：社区 c 内的边的权重之和
&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-08.png" title="" alt=""&gt; 表示：所有与社区 c 内节点相连的边的权重之和（因为 i 属于社区 c）包括社区内节点与节点 i 的边和社区外节点与节点 i 的边。
&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-09.png" title="" alt=""&gt; 表示：所有与社区 c 内节点相连的边的权重之和（因为 j 属于社区 c）包括社区内节点与节点 j 的边和社区外节点与节点 j 的边。
&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-10.png" title="" alt=""&gt; 代替 &lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-11.png" title="" alt=""&gt; 和 &lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-12.png" title="" alt=""&gt;。（即社区 c 内边权重和 + 社区 c 与其他社区连边的权重和）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;求解模块度变化&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在 Louvain 算法中不需要求每个社区具体的模块度，只需要比较社区中加入某个节点之后的模块度变化，所以需要求解 △Q。&lt;/p&gt;

&lt;p&gt;将节点 i 分配到某一社区中，社区的模块度变化为：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-13.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;其中&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-14.png" title="" alt=""&gt;：社区内所有节点与节点 i 连边权重之和（对应新社区的实际内部权重和乘以 2，因为 &lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-17.png" title="" alt=""&gt; 对于社区内所有的顶点 i，每条边其实被计算了两次）
&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-15.png" title="" alt=""&gt;：所有与节点 i 相连的边的权重之和
故实现算法时只需求 &lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/graphx-16.png" title="" alt=""&gt; 即可。&lt;/p&gt;
&lt;h4 id="Louvain 应用场景"&gt;Louvain 应用场景&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;金融风控&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在金融风控场景中，可以根据用户行为特征进行团伙识别；&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;社交网络&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以基于网络关系中点对之间关联的广度和强度进行社交网络划分；对复杂网络分析、电话网络分析人群之间的联系密切度；&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;推荐系统&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;基于用户兴趣爱好的社区发现，可以根据社区并结合协同过滤等推荐算法进行更精确有效的个性化推荐。&lt;/p&gt;
&lt;h4 id="Louvain 算法思路"&gt;Louvain 算法思路&lt;/h4&gt;
&lt;p&gt;Louvain 算法包括两个阶段，其流程就是这两个阶段的迭代过程。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;阶段一&lt;/strong&gt;：不断地遍历网络图中的节点，通过比较节点给每个邻居社区带来的模块度的变化，将单个节点加入到能够使 Modularity 模块度有最大增量的社区中。
（比如节点 v 分别加入到社区 A、B、C 中，使得三个社区的模块度增量为 -1，1，2，则节点 v 最终应该加入到社区 C 中）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;阶段二&lt;/strong&gt;：对第一阶段进行处理，将属于同一社区的顶点合并为一个大的超点重新构造网络图，即一个社区作为图的一个新的节点。此时两个超点之间边的权重是两个超点内所有原始顶点之间相连的边权重之和，即两个社区之间的边权重之和。&lt;/p&gt;

&lt;p&gt;下面是对第一二阶段的实例介绍。&lt;/p&gt;

&lt;p&gt;​&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/louvain-use-case.png" title="" alt="Louvain 社区算法"&gt;&lt;/p&gt;

&lt;p&gt;第一阶段遍历图中节点加入到其所属社区中，得到中间的图，形成四个社区；&lt;/p&gt;

&lt;p&gt;第二节点对社区内的节点进行合并成一个超级节点，社区节点有自连边，其权重为社区内部所有节点间相连的边的权重之和的 2 倍，社区之间的边为两个社区间顶点跨社区相连的边的权重之和，如红色社区和浅绿色社区之间通过（8,11）、（10，11）、（10,13）相连，所以两个社区之间边的权重为 3。&lt;/p&gt;

&lt;p&gt;&lt;u&gt;注：社区内的权重为所有内部结点之间边权重的两倍，因为 Kin 的概念是社区内所有节点与节点 i 的连边和，在计算某一社区的 Kin 时，实际上每条边都被其两端的顶点计算了一次，一共被计算了两次。&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;整个 Louvain 算法就是不断迭代第一阶段和第二阶段，直到算法稳定（图的模块度不再变化）或者到达最大迭代次数。&lt;/p&gt;
&lt;h2 id="三、算法实践"&gt;三、算法实践&lt;/h2&gt;&lt;h3 id="演示环境"&gt;演示环境&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;三台虚拟机，环境如下：

&lt;ul&gt;
&lt;li&gt;Cpu name: Intel(R) Xeon(R) Platinum 8260M CPU @ 2.30GHz&lt;/li&gt;
&lt;li&gt;Processors：32&lt;/li&gt;
&lt;li&gt;CPU Cores: 16&lt;/li&gt;
&lt;li&gt;Memory Size: 128G&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;软件环境

&lt;ul&gt;
&lt;li&gt;Spark：spark-2.4.6-bin-hadoop2.7 三个节点集群&lt;/li&gt;
&lt;li&gt;yarn V2.10.0：三个节点集群&lt;/li&gt;
&lt;li&gt;Nebula Graph V1.1.0：分布式部署，默认配置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="测试数据"&gt;测试数据&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;创建图空间&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;CREATE SPACE algoTest&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;partition_num&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100, &lt;span class="nv"&gt;replica_factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;创建点边 Schema&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;CREATE TAG PERSON&lt;span class="o"&gt;()&lt;/span&gt;
CREATE EDGE FRIEND&lt;span class="o"&gt;(&lt;/span&gt;likeness double&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;导入数据&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;利用 Exchange 工具将数据离线导入 Nebula Graph。 &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;测试结果&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Spark 任务的资源分配为 &lt;code&gt;--driver-memory=20G  --executor-memory=100G --executor-cores=3&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PageRank 在一亿数据集上的执行时间为 21min（PageRank 算法执行时间）&lt;/li&gt;
&lt;li&gt;Louvain 在一亿数据集上的执行时间为 1.3h（Reader + Louvain 算法执行时间）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="如何使用 Nebula Graph 的算法"&gt;如何使用 Nebula Graph 的算法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;下载 nebula-algorithm 项目并打成 jar 包&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone git@github.com:vesoft-inc/nebula-java.git
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;nebula-java/tools/nebula-algorithm
&lt;span class="nv"&gt;$ &lt;/span&gt;mvn package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;配置项目中的 &lt;code&gt;src/main/resources/application.conf&lt;/code&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;{
  # Spark relation config
  spark: {
    app: {
        # not required, default name is the algorithm that you are going to execute.
        name: PageRank

        # not required
        partitionNum: 12
    }

    master: local

    # not required
    conf: {
        driver-memory: 8g
        executor-memory: 8g
        executor-cores: 1g
        cores-max:6
    }
  }

  # Nebula Graph relation config
  nebula: {
    # metadata server address
    addresses: "127.0.0.1:45500"
    user: root
    pswd: nebula
    space: algoTest
    # partition specified while creating nebula space, if you didn't specified the partition, then it's 100.
    partitionNumber: 100
    # nebula edge type
    labels: ["FRIEND"]

    hasWeight: true
    # if hasWeight is true，then weightCols is required， and weghtCols' order must be corresponding with labels.
    # Noted: the graph algorithm only supports isomorphic graphs,
    #        so the data type of each col in weightCols must be consistent and all numeric types.
    weightCols: [“likeness”]
  }

  algorithm: {
    # the algorithm that you are going to execute，pick one from [pagerank, louvain]
    executeAlgo: louvain
    # algorithm result path
    path: /tmp

    # pagerank parameter
    pagerank: {
        maxIter: 20
        resetProb: 0.15  # default 0.15

    }

    # louvain parameter
    louvain: {
        maxIter: 20
        internalIter: 10
        tol: 0.5
   }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;确保用户环境已安装 Spark 并启动 Spark 服务&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;提交 nebula-algorithm 应用程序：&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spark-submit &lt;span class="nt"&gt;--master&lt;/span&gt; xxx &lt;span class="nt"&gt;--class&lt;/span&gt; com.vesoft.nebula.tools.algorithm.Main /your-jar-path/nebula-algorithm-1.0.1.jar &lt;span class="nt"&gt;-p&lt;/span&gt; /your-application.conf-path/application.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你对上述内容感兴趣，欢迎用 nebula-algorithm 试试^^&lt;/p&gt;
&lt;h2 id="References"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Nebula Graph:&lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GraphX：&lt;a href="https://github.com/apache/spark/tree/master/graphx" rel="nofollow" target="_blank" title=""&gt;https://github.com/apache/spark/tree/master/graphx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spark-connector：&lt;a href="https://github.com/vesoft-inc/nebula-java/tree/master/tools/nebula-spark" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-java/tree/master/tools/nebula-spark&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Exchange：&lt;a href="https://github.com/vesoft-inc/nebula-java/blob/master/doc/tools/exchange/ex-ug-toc.md" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-java/blob/master/doc/tools/exchange/ex-ug-toc.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;nebula-algorithm：&lt;a href="https://github.com/vesoft-inc/nebula-java/tree/master/tools/nebula-algorithm" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-java/tree/master/tools/nebula-algorithm&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;作者有话说：Hi，我是安祺，Nebula Graph 研发工程师，如果你对本文有任何疑问，欢迎来论坛和我交流：&lt;a href="https://discuss.nebula-graph.com.cn/" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="推荐阅读"&gt;推荐阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/stock-interrelation-analysis-jgrapht-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;用图机器学习探索 A 股个股相关性变化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;用 NetworkX + Gephi + Nebula Graph 分析&amp;lt;权力的游戏&amp;gt;人物关系（上篇）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 11 Nov 2020 18:12:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/40568</link>
      <guid>https://ruby-china.org/topics/40568</guid>
    </item>
    <item>
      <title>调试 Docker 容器内部进程</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/debug-docker-processes.png" title="" alt="Docker 调试进程"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;首发于官方博客：&lt;a href="https://nebula-graph.com.cn/posts/debug-nebula-graph-processes-docker/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.com.cn/posts/debug-nebula-graph-processes-docker/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;摘要：本文以 Nebula Graph 进程为例，讲解如何不破坏原有容器的内容，也不用在其中安装任何的工具包前提下，像在本地一样来调试进程&lt;/p&gt;
&lt;h2 id="需求"&gt;需求&lt;/h2&gt;
&lt;p&gt;在开发或者测试过程中，我们经常会用到 &lt;a href="https://github.com/vesoft-inc/nebula-docker-compose" rel="nofollow" target="_blank" title=""&gt;vesoft-inc/nebula-docker-compose&lt;/a&gt; 这个 repo 下的部署方式，因为当初为了尽可能的压缩每个 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 服务的 docker 镜像的体积，所以开发过程中常用的一切工具都没有安装，甚至连编辑器 VIM 都没有。&lt;/p&gt;

&lt;p&gt;这给我们在容器内部定位问题带来一定的难度，因为每次只能去 install 一些工具包，才能开展接下来的工作，甚是费事。其实调试容器内部的进程还有另外一种方式，不需要破坏原有容器的内容，也不用在其中安装任何的工具包就能像在本地一样来调试。&lt;/p&gt;

&lt;p&gt;这种技术在 k8s 环境下其实已经挺常用，就是 sidecar 模式。原理也比较朴素就是再起一个容器然后让这个容器跟你要调试的容器共享相同的 pid/network 的 namespace。这样原容器中的进程和网络空间在调试容器中就能“一览无余”，而在调试容器中安装了你想要的一切顺手工具，接下来的舞台就是留于你发挥了。&lt;/p&gt;
&lt;h2 id="演示"&gt;演示&lt;/h2&gt;
&lt;p&gt;接下来我就演示一下如何操作：&lt;/p&gt;

&lt;p&gt;我们先用上述的 docker-compose 方式在本地部署一套 Nebula Graph 集群，教程见 repo 中的 README。部署好后的结果如下：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
Creating network &lt;span class="s2"&gt;"nebula-docker-compose_nebula-net"&lt;/span&gt; with the default driver
Creating nebula-docker-compose_metad1_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating nebula-docker-compose_metad2_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating nebula-docker-compose_metad0_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating nebula-docker-compose_storaged2_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating nebula-docker-compose_storaged1_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating nebula-docker-compose_storaged0_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating nebula-docker-compose_graphd_1    ... &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose ps
              Name                             Command                       State                                             Ports
&lt;span class="nt"&gt;-----------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
nebula-docker-compose_graphd_1      ./bin/nebula-graphd &lt;span class="nt"&gt;--flag&lt;/span&gt; ...   Up &lt;span class="o"&gt;(&lt;/span&gt;health: starting&lt;span class="o"&gt;)&lt;/span&gt;   0.0.0.0:32907-&amp;gt;13000/tcp, 0.0.0.0:32906-&amp;gt;13002/tcp, 0.0.0.0:3699-&amp;gt;3699/tcp
nebula-docker-compose_metad0_1      ./bin/nebula-metad &lt;span class="nt"&gt;--flagf&lt;/span&gt; ...   Up &lt;span class="o"&gt;(&lt;/span&gt;health: starting&lt;span class="o"&gt;)&lt;/span&gt;   0.0.0.0:32898-&amp;gt;11000/tcp, 0.0.0.0:32896-&amp;gt;11002/tcp, 45500/tcp, 45501/tcp
nebula-docker-compose_metad1_1      ./bin/nebula-metad &lt;span class="nt"&gt;--flagf&lt;/span&gt; ...   Up &lt;span class="o"&gt;(&lt;/span&gt;health: starting&lt;span class="o"&gt;)&lt;/span&gt;   0.0.0.0:32895-&amp;gt;11000/tcp, 0.0.0.0:32894-&amp;gt;11002/tcp, 45500/tcp, 45501/tcp
nebula-docker-compose_metad2_1      ./bin/nebula-metad &lt;span class="nt"&gt;--flagf&lt;/span&gt; ...   Up &lt;span class="o"&gt;(&lt;/span&gt;health: starting&lt;span class="o"&gt;)&lt;/span&gt;   0.0.0.0:32899-&amp;gt;11000/tcp, 0.0.0.0:32897-&amp;gt;11002/tcp, 45500/tcp, 45501/tcp
nebula-docker-compose_storaged0_1   ./bin/nebula-storaged &lt;span class="nt"&gt;--fl&lt;/span&gt; ...   Up &lt;span class="o"&gt;(&lt;/span&gt;health: starting&lt;span class="o"&gt;)&lt;/span&gt;   0.0.0.0:32901-&amp;gt;12000/tcp, 0.0.0.0:32900-&amp;gt;12002/tcp, 44500/tcp, 44501/tcp
nebula-docker-compose_storaged1_1   ./bin/nebula-storaged &lt;span class="nt"&gt;--fl&lt;/span&gt; ...   Up &lt;span class="o"&gt;(&lt;/span&gt;health: starting&lt;span class="o"&gt;)&lt;/span&gt;   0.0.0.0:32903-&amp;gt;12000/tcp, 0.0.0.0:32902-&amp;gt;12002/tcp, 44500/tcp, 44501/tcp
nebula-docker-compose_storaged2_1   ./bin/nebula-storaged &lt;span class="nt"&gt;--fl&lt;/span&gt; ...   Up &lt;span class="o"&gt;(&lt;/span&gt;health: starting&lt;span class="o"&gt;)&lt;/span&gt;   0.0.0.0:32905-&amp;gt;12000/tcp, 0.0.0.0:32904-&amp;gt;12002/tcp, 44500/tcp, 44501/tcp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时我们分两个场景来演示，一个是进程空间，一个是网络空间。首先我们要先有一个顺手的调试镜像，我们就不自己构建了，从 docker hub 中找个已经打包好的用作演示，后期觉得不够用，我们可以维护一份 nebula-debug 的镜像，安装我们想要的所有调试工具，此处先借用社区内的方案 &lt;a href="https://hub.docker.com/r/nicolaka/netshoot" rel="nofollow" target="_blank" title=""&gt;nicolaka/netshoot&lt;/a&gt;。我们先把镜像拉取到本地：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker pull nicolaka/netshoot
&lt;span class="nv"&gt;$ &lt;/span&gt;docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
vesoft/nebula-graphd     nightly             c67fe54665b7        36 hours ago        282MB
vesoft/nebula-storaged   nightly             5c77dbcdc507        36 hours ago        288MB
vesoft/nebula-console    nightly             f3256c99eda1        36 hours ago        249MB
vesoft/nebula-metad      nightly             5a78d3e3008f        36 hours ago        288MB
nicolaka/netshoot        latest              6d7e8891c980        2 months ago        352MB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们先看看直接执行这个镜像会是什么样：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; nicolaka/netshoot bash
bash-5.0# ps
PID   USER     TIME  COMMAND
    1 root      0:00 bash
    8 root      0:00 ps
bash-5.0#
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面显示这个容器看不到任何 Nebula Graph 服务进程的内容，那么我们给其加点参数再看看：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;--pid&lt;/span&gt; container:nebula-docker-compose_metad0_1 &lt;span class="nt"&gt;--cap-add&lt;/span&gt; sys_admin nicolaka/netshoot bash
bash-5.0# ps
PID   USER     TIME  COMMAND
    1 root      0:03 ./bin/nebula-metad &lt;span class="nt"&gt;--flagfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./etc/nebula-metad.conf &lt;span class="nt"&gt;--daemonize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--meta_server_addrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.28.1.1:45500,172.28.1.2:45500,172.28.1.3:45500 &lt;span class="nt"&gt;--local_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.28.1.1 &lt;span class="nt"&gt;--ws_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.28.1.1 &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45500 &lt;span class="nt"&gt;--data_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data/meta &lt;span class="nt"&gt;--log_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/logs &lt;span class="nt"&gt;--v&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15 &lt;span class="nt"&gt;--minloglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
  452 root      0:00 bash
  459 root      0:00 ps
bash-5.0# &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt; /proc/1/net/
total 0
dr-xr-xr-x    6 root     root             0 Sep 18 07:17 &lt;span class="nb"&gt;.&lt;/span&gt;
dr-xr-xr-x    9 root     root             0 Sep 18 06:55 ..
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 anycast6
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 arp
dr-xr-xr-x    2 root     root             0 Sep 18 07:18 bonding
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 dev
...
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 sockstat
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 sockstat6
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 softnet_stat
dr-xr-xr-x    2 root     root             0 Sep 18 07:18 &lt;span class="nb"&gt;stat&lt;/span&gt;
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 tcp
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 tcp6
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 udp
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 udp6
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 udplite
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 udplite6
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 unix
&lt;span class="nt"&gt;-r--r--r--&lt;/span&gt;    1 root     root             0 Sep 18 07:18 xfrm_stat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次有点不一样了，我们看到 metad0 的进程了，并且其 pid 还是 1。看到这个进程再想对其做点啥就好办了，比如能不能直接在 gdb 中 attach 它，由于手边没有带 nebula binary 的对应 image，就留给大家私下探索吧。&lt;/p&gt;

&lt;p&gt;我们已经看到 pid 空间通过指定 &lt;code&gt;--pid container:&amp;lt;container_name|id&amp;gt;&lt;/code&gt;&amp;nbsp;可以共享了，那么我们接下来看看网络的情况，毕竟有时候需要抓个包，执行如下的命令：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash-5.0# netstat &lt;span class="nt"&gt;-tulpn&lt;/span&gt;
Active Internet connections &lt;span class="o"&gt;(&lt;/span&gt;only servers&lt;span class="o"&gt;)&lt;/span&gt;
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;啥也没有，跟预想的有点不一样，我们有 metad0 这个进程不可能一个连接都没有。要想看到这个容器内的网络空间还要再加点参数，像如下方式再启动调试容器：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;--pid&lt;/span&gt; container:nebula-docker-compose_metad0_1 &lt;span class="nt"&gt;--network&lt;/span&gt; container:nebula-docker-compose_metad0_1 &lt;span class="nt"&gt;--cap-add&lt;/span&gt; sys_admin nicolaka/netshoot bash
bash-5.0# netstat &lt;span class="nt"&gt;-tulpn&lt;/span&gt;
Active Internet connections &lt;span class="o"&gt;(&lt;/span&gt;only servers&lt;span class="o"&gt;)&lt;/span&gt;
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 172.28.1.1:11000        0.0.0.0:&lt;span class="k"&gt;*&lt;/span&gt;               LISTEN      -
tcp        0      0 172.28.1.1:11002        0.0.0.0:&lt;span class="k"&gt;*&lt;/span&gt;               LISTEN      -
tcp        0      0 0.0.0.0:45500           0.0.0.0:&lt;span class="k"&gt;*&lt;/span&gt;               LISTEN      -
tcp        0      0 0.0.0.0:45501           0.0.0.0:&lt;span class="k"&gt;*&lt;/span&gt;               LISTEN      -
tcp        0      0 127.0.0.11:33249        0.0.0.0:&lt;span class="k"&gt;*&lt;/span&gt;               LISTEN      -
udp        0      0 127.0.0.11:51929        0.0.0.0:&lt;span class="k"&gt;*&lt;/span&gt;                           -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这回就跟上面的输出不一样了，加了 &lt;code&gt;--network container:nebula-docker-compose_metad0_1&lt;/code&gt;&amp;nbsp;运行参数后，metad0 容器内的连接情况也能看到了，那么想抓包调试就都可以了。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;通过运行另外一个容器，并让其跟想要调试的容器共享 pid/network namespace 是我们能像本地调试的关键。社区里甚至还有人基于上述想法开发了一些小工具进一步方便使用：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/zeromake/docker-debug" rel="nofollow" target="_blank" title=""&gt;Docker-debug&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="推荐阅读"&gt;推荐阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/build-nebula-graph-source-code-with-docker/" rel="nofollow" target="_blank" title=""&gt;使用 Docker 构建 Nebula Graph 源码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 28 Oct 2020 11:47:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/40517</link>
      <guid>https://ruby-china.org/topics/40517</guid>
    </item>
    <item>
      <title>主流开源分布式图数据库 Benchmark</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;本文由美团 NLP 团队高辰、赵登昌撰写
首发于 Nebula Graph 官方论坛：&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1377" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/1377&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h2 id="1. 前言"&gt;1. 前言&lt;/h2&gt;
&lt;p&gt;近年来，深度学习和知识图谱技术发展迅速，相比于深度学习的“黑盒子”，知识图谱具有很强的可解释性，在搜索推荐、智能助理、金融风控等场景中有着广泛的应用。美团基于积累的海量业务数据，结合使用场景进行充分地挖掘关联，逐步建立起包括美食图谱、旅游图谱、商品图谱在内的近十个领域知识图谱，并在多业务场景落地，助力本地生活服务的智能化。  &lt;/p&gt;

&lt;p&gt;为了高效存储并检索图谱数据，相比传统关系型数据库，选择图数据库作为存储引擎，在多跳查询上具有明显的性能优势。当前业界知名的图数据库产品有数十款，选型一款能够满足美团实际业务需求的图数据库产品，是建设图存储和图学习平台的基础。我们结合业务现状，制定了选型的基本条件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;开源项目，对商业应用友好&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;拥有对源代码的控制力，才能保证数据安全和服务可用性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;支持集群模式，具备存储和计算的横向扩展能力&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;美团图谱业务数据量可以达到千亿以上点边总数，吞吐量可达到数万 qps，单节点部署无法满足存储需求。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;能够服务 OLTP 场景，具备毫秒级多跳查询能力&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;美团搜索场景下，为确保用户搜索体验，各链路的超时时间具有严格限制，不能接受秒级以上的查询响应时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;具备批量导入数据能力&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;图谱数据一般存储在 Hive 等数据仓库中。必须有快速将数据导入到图存储的手段，服务的时效性才能得到保证。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们试用了 DB-Engines 网站上排名前 30 的图数据库产品，发现多数知名的图数据库开源版本只支持单节点，不能横向扩展存储，无法满足大规模图谱数据的存储需求，例如：Neo4j、ArangoDB、Virtuoso、TigerGraph、RedisGraph。经过调研比较，最终纳入评测范围的产品为：NebulaGraph（原阿里巴巴团队创业开发）、Dgraph（原 Google 团队创业开发）、HugeGraph（百度团队开发）。&lt;/p&gt;
&lt;h2 id="2. 测试概要"&gt;2. 测试概要&lt;/h2&gt;&lt;h3 id="2.1  硬件配置"&gt;2.1  硬件配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据库实例：运行在不同物理机上的 Docker 容器。&lt;/li&gt;
&lt;li&gt;单实例资源：32 核心，64GB 内存，1TB SSD 存储。【Intel(R) Xeon(R) Gold 5218 CPU @ 2.30GHz】&lt;/li&gt;
&lt;li&gt;实例数量：3&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2.2 部署方案"&gt;2.2 部署方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula v1.0.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Metad 负责管理集群元数据，Graphd 负责执行查询，Storaged 负责数据分片存储。存储后端采用 RocksDB。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;实例 1&lt;/th&gt;
&lt;th&gt;实例 2&lt;/th&gt;
&lt;th&gt;实例 3&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metad&lt;/td&gt;
&lt;td&gt;Metad&lt;/td&gt;
&lt;td&gt;Metad&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graphd&lt;/td&gt;
&lt;td&gt;Graphd&lt;/td&gt;
&lt;td&gt;Graphd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storaged[RocksDB]&lt;/td&gt;
&lt;td&gt;Storaged[RocksDB]&lt;/td&gt;
&lt;td&gt;Storaged[RocksDB]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dgraph-io/dgraph" rel="nofollow" target="_blank" title=""&gt;Dgraph v20.07.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero 负责管理集群元数据，Alpha 负责执行查询和存储。存储后端为 Dgraph 自有实现。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;实例 1&lt;/th&gt;
&lt;th&gt;实例 2&lt;/th&gt;
&lt;th&gt;实例 3&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hugegraph/hugegraph" rel="nofollow" target="_blank" title=""&gt;HugeGraph v0.10.4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HugeServer 负责管理集群元数据和查询。HugeGraph 虽然支持 RocksDB 后端，但不支持 RocksDB 后端的集群部署，因此存储后端采用 HBase。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;实例 1&lt;/th&gt;
&lt;th&gt;实例 2&lt;/th&gt;
&lt;th&gt;实例 3&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HugeServer[HBase]&lt;/td&gt;
&lt;td&gt;HugeServer[HBase]&lt;/td&gt;
&lt;td&gt;HugeServer[HBase]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JournalNode&lt;/td&gt;
&lt;td&gt;JournalNode&lt;/td&gt;
&lt;td&gt;JournalNode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataNode&lt;/td&gt;
&lt;td&gt;DataNode&lt;/td&gt;
&lt;td&gt;DataNode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NodeManager&lt;/td&gt;
&lt;td&gt;NodeManager&lt;/td&gt;
&lt;td&gt;NodeManager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RegionServer&lt;/td&gt;
&lt;td&gt;RegionServer&lt;/td&gt;
&lt;td&gt;RegionServer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZooKeeper&lt;/td&gt;
&lt;td&gt;ZooKeeper&lt;/td&gt;
&lt;td&gt;ZooKeeper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NameNode&lt;/td&gt;
&lt;td&gt;NameNode[Backup]&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;ResourceManager&lt;/td&gt;
&lt;td&gt;ResourceManager[Backup]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HBase Master&lt;/td&gt;
&lt;td&gt;HBase Master[Backup]&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="3. 评测数据集"&gt;3. 评测数据集&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/datasets-meituan.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;社交图谱数据集：&lt;a href="https://github.com/ldbc" rel="nofollow" target="_blank" title=""&gt;https://github.com/ldbc&lt;/a&gt;011

&lt;ul&gt;
&lt;li&gt;生成参数：branch=stable, version=0.3.3, scale=1000&lt;/li&gt;
&lt;li&gt;实体情况：4 类实体，总数 26 亿&lt;/li&gt;
&lt;li&gt;关系情况：19 类关系，总数 177 亿&lt;/li&gt;
&lt;li&gt;数据格式：csv&lt;/li&gt;
&lt;li&gt;GZip 压缩后大小：194 G&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4. 测试结果"&gt;4. 测试结果&lt;/h2&gt;&lt;h3 id="4.1 批量数据导入"&gt;4.1 批量数据导入&lt;/h3&gt;&lt;h4 id="4.1.1 测试说明"&gt;4.1.1 测试说明&lt;/h4&gt;
&lt;p&gt;批量导入的步骤为：&lt;code&gt;Hive 仓库底层 csv 文件 -&amp;gt; 图数据库支持的中间文件 -&amp;gt; 图数据库&lt;/code&gt;。各图数据库具体导入方式如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula&lt;/a&gt;：执行 Spark 任务，从数仓生成 RocksDB 的底层存储 sst 文件，然后执行 sst Ingest 操作插入数据。&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dgraph-io/dgraph" rel="nofollow" target="_blank" title=""&gt;Dgraph&lt;/a&gt;：执行 Spark 任务，从数仓生成三元组 rdf 文件，然后执行 bulk load 操作直接生成各节点的持久化文件。&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hugegraph/hugegraph" rel="nofollow" target="_blank" title=""&gt;HugeGraph&lt;/a&gt;：支持直接从数仓的 csv 文件导入数据，因此不需要数仓 - 中间文件的步骤。通过 loader 批量插入数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="4.1.2 测试结果"&gt;4.1.2 测试结果&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-01.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h4 id="4.1.3 数据分析"&gt;4.1.3 数据分析&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Nebula：数据存储分布方式是主键哈希，各节点存储分布基本均衡。导入速度最快，存储放大比最优。&lt;/li&gt;
&lt;li&gt;Dgraph：原始 194G 数据在内存 392G 的机器上执行导入命令，8.7h 后 OOM 退出，无法导入全量数据。数据存储分布方式是三元组谓词，同一种关系只能保存在一个数据节点上，导致存储和计算严重偏斜。&lt;/li&gt;
&lt;li&gt;HugeGraph：原始 194G 的数据执行导入命令，写满了一个节点 1,000G 的磁盘，造成导入失败，无法导入全量数据。存储放大比最差，同时存在严重的数据偏斜。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4.2 实时数据写入"&gt;4.2 实时数据写入&lt;/h3&gt;&lt;h4 id="4.2.1 测试说明"&gt;4.2.1 测试说明&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;向图数据库插入点和边，测试实时写入和并发能力。

&lt;ul&gt;
&lt;li&gt;响应时间：固定的 50,000 条数据，以固定 qps 发出写请求，全部发送完毕即结束。取客户端从发出请求到收到响应的 Avg、p99、p999 耗时。&lt;/li&gt;
&lt;li&gt;最大吞吐量：固定的 1,000,000 条数据，以递增 qps 发出写请求，Query 循环使用。取 1 分钟内成功请求的峰值 qps 为最大吞吐量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;插入点

&lt;ul&gt;
&lt;li&gt;Nebula&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT VERTEX t_rich_node (creation_date, first_name, last_name, gender, birthday, location_ip, browser_used) VALUES ${mid}:('2012-07-18T01:16:17.119+0000', 'Rodrigo', 'Silva', 'female', '1984-10-11', '84.194.222.86', 'Firefox')
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Dgraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    set {
        &amp;lt;${mid}&amp;gt; &amp;lt;creation_date&amp;gt; "2012-07-18T01:16:17.119+0000" .
        &amp;lt;${mid}&amp;gt; &amp;lt;first_name&amp;gt; "Rodrigo" .
        &amp;lt;${mid}&amp;gt; &amp;lt;last_name&amp;gt; "Silva" .
        &amp;lt;${mid}&amp;gt; &amp;lt;gender&amp;gt; "female" .
        &amp;lt;${mid}&amp;gt; &amp;lt;birthday&amp;gt; "1984-10-11" .
        &amp;lt;${mid}&amp;gt; &amp;lt;location_ip&amp;gt; "84.194.222.86" .
        &amp;lt;${mid}&amp;gt; &amp;lt;browser_used&amp;gt; "Firefox" .
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;HugeGraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g.addVertex(T.label, "t_rich_node", T.id, ${mid}, "creation_date", "2012-07-18T01:16:17.119+0000", "first_name", "Rodrigo", "last_name", "Silva", "gender", "female", "birthday", "1984-10-11", "location_ip", "84.194.222.86", "browser_used", "Firefox")
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;插入边

&lt;ul&gt;
&lt;li&gt;Nebula&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT EDGE t_edge () VALUES ${mid1}-&amp;gt;${mid2}:();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Dgraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    set {
        &amp;lt;${mid1}&amp;gt; &amp;lt;link&amp;gt; &amp;lt;${mid2}&amp;gt; .
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;HugeGraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g.V(${mid1}).as('src').V(${mid2}).addE('t_edge').from('src')
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="4.2.2 测试结果"&gt;4.2.2 测试结果&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;实时写入&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-02.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-03.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h4 id="4.2.3 数据分析"&gt;4.2.3 数据分析&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Nebula：如 4.1.3 节分析所述，Nebula 的写入请求可以由多个存储节点分担，因此响应时间和吞吐量均大幅领先。&lt;/li&gt;
&lt;li&gt;Dgraph：如 4.1.3 节分析所述，同一种关系只能保存在一个数据节点上，吞吐量较差。 &lt;/li&gt;
&lt;li&gt;HugeGraph：由于存储后端基于 HBase，实时并发读写能力低于 RocksDB（Nebula）和 BadgerDB（Dgraph），因此性能最差。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4.3 数据查询"&gt;4.3 数据查询&lt;/h3&gt;&lt;h4 id="4.3.1 测试说明"&gt;4.3.1 测试说明&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;以常见的 N 跳查询返回 ID，N 跳查询返回属性，共同好友查询请求测试图数据库的读性能。

&lt;ul&gt;
&lt;li&gt;响应时间：固定的 50,000 条查询，以固定 qps 发出读请求，全部发送完毕即结束。取客户端从发出请求到收到响应的 Avg、p99、p999 耗时。&lt;/li&gt;
&lt;li&gt;60s 内未返回结果为超时。&lt;/li&gt;
&lt;li&gt;最大吞吐量：固定的 1,000,000 条查询，以递增 qps 发出读请求，Query 循环使用。取 1 分钟内成功请求的峰值 qps 为最大吞吐量。&lt;/li&gt;
&lt;li&gt;缓存配置：参与测试的图数据库都具备读缓存机制，默认打开。每次测试前均重启服务清空缓存。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;N 跳查询返回 ID

&lt;ul&gt;
&lt;li&gt;Nebula&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GO ${n} STEPS FROM ${mid} OVER person_knows_person
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Dgraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 q(func:uid(${mid})) {
   uid
   person_knows_person { #${n}跳数 = 嵌套层数
     uid
   }
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;HugeGraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g.V(${mid}).out().id() #${n}跳数 = out()链长度
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;N 跳查询返回属性

&lt;ul&gt;
&lt;li&gt;Nebula&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GO ${n} STEPS FROM ${mid} OVER person_knows_person YIELDperson_knows_person.creation_date, $$.person.first_name, $$.person.last_name, $$.person.gender, $$.person.birthday, $$.person.location_ip, $$.person.browser_used
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Dgraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  q(func:uid(${mid})) {
    uid first_name last_name gender birthday location_ip browser_used
    person_knows_person { #${n}跳数 = 嵌套层数
      uid first_name last_name gender birthday location_ip browser_used
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;HugeGraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g.V(${mid}).out()  #${n}跳数 = out()链长度
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;共同好友查询语句

&lt;ul&gt;
&lt;li&gt;Nebula&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GO FROM ${mid1} OVER person_knows_person INTERSECT GO FROM ${mid2} OVER person_knows_person
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Dgraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  var(func: uid(${mid1})) {
    person_knows_person {
      M1 as uid
    }
  }
  var(func: uid(${mid2})) {
    person_knows_person {
      M2 as uid
    }
  }
  in_common(func: uid(M1)) @filter(uid(M2)){
    uid
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;HugeGraph&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g.V(${mid1}).out().id().aggregate('x').V(${mid2}).out().id().where(within('x')).dedup()
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="4.3.2 测试结果"&gt;4.3.2 测试结果&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;N 跳查询返回 ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-04.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-05.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N 跳查询返回属性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;单个返回节点的属性平均大小为 200 Bytes。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-06.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-07.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;共同好友
本项未测试最大吞吐量。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/benchmark-meituan-08.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h4 id="4.3.3 数据分析"&gt;4.3.3 数据分析&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在 1 跳查询返回 ID「响应时间」实验中，Nebula 和 DGraph 都只需要进行一次出边搜索。由于 DGraph 的存储特性，相同关系存储在单个节点，1 跳查询不需要网络通信。而 Nebula 的实体分布在多个节点中，因此在实验中 DGraph 响应时间表现略优于 Nebula。&lt;/li&gt;
&lt;li&gt;在 1 跳查询返回 ID「最大吞吐量」实验中，DGraph 集群节点的 CPU 负载主要落在存储关系的单节点上，造成集群 CPU 利用率低下，因此最大吞吐量仅有 Nebula 的 11%。&lt;/li&gt;
&lt;li&gt;在 2 跳查询返回 ID「响应时间」实验中，由于上述原因，DGraph 在 qps=100 时已经接近了集群负载能力上限，因此响应时间大幅变慢，是 Nebula 的 3.9 倍。&lt;/li&gt;
&lt;li&gt;在 1 跳查询返回属性实验中，Nebula 由于将实体的所有属性作为一个数据结构存储在单节点上，因此只需要进行【出边总数 Y】次搜索。而 DGraph 将实体的所有属性也视为出边，并且分布在不同节点上，需要进行【属性数量 X * 出边总数 Y】次出边搜索，因此查询性能比 Nebula 差。多跳查询同理。&lt;/li&gt;
&lt;li&gt;在共同好友实验中，由于此实验基本等价于 2 次 1 跳查询返回 ID，因此测试结果接近，不再详述。&lt;/li&gt;
&lt;li&gt;由于 HugeGraph 存储后端基于 HBase，实时并发读写能力低于 RocksDB（Nebula）和 BadgerDB（Dgraph），因此在多项实验中性能表现均落后于 Nebula 和 DGraph。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5. 结论"&gt;5. 结论&lt;/h2&gt;
&lt;p&gt;参与测试的图数据库中，Nebula 的批量导入可用性、导入速度、实时数据写入性能、数据多跳查询性能均优于竞品，因此我们最终选择了 Nebula 作为图存储引擎。&lt;/p&gt;
&lt;h2 id="6. 参考资料"&gt;6. 参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NebulaGraph Benchmark:&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/782" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/782&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NebulaGraph Benchmark 微信团队：&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1013" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/1013&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DGraph Benchmark:&lt;a href="https://dgraph.io/blog/tags/benchmark/" rel="nofollow" target="_blank" title=""&gt;https://dgraph.io/blog/tags/benchmark/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HugeGraph Benchmark:&lt;a href="https://hugegraph.github.io/hugegraph-doc/performance/hugegraph-benchmark-0.5.6.html" rel="nofollow" target="_blank" title=""&gt;https://hugegraph.github.io/hugegraph-doc/performance/hugegraph-benchmark-0.5.6.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TigerGraph Benchmark:&lt;a href="https://www.tigergraph.com/benchmark/" rel="nofollow" target="_blank" title=""&gt;https://www.tigergraph.com/benchmark/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RedisGraph Benchmark:&lt;a href="https://redislabs.com/blog/new-redisgraph-1-0-achieves-600x-faster-performance-graph-databases/" rel="nofollow" target="_blank" title=""&gt;https://redislabs.com/blog/new-redisgraph-1-0-achieves-600x-faster-performance-graph-databases/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;本次性能测试系美团 NLP 团队高辰、赵登昌撰写，如果你对本文有任意疑问，欢迎来原贴和作者交流：&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1377" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/1377&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 21 Oct 2020 10:47:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/40499</link>
      <guid>https://ruby-china.org/topics/40499</guid>
    </item>
    <item>
      <title>用 Docker swarm 快速部署分布式图数据库 Nebula Graph 集群</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;本文作者系：视野金服工程师 ｜ 吴海胜
首发于 Nebula Graph 论坛：&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1388" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/1388&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="一、前言"&gt;一、前言&lt;/h2&gt;
&lt;p&gt;本文介绍如何使用 Docker Swarm 来部署 &lt;a href="https://0x7.me/swarm2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 集群，并部署客户端负载均衡和高可用。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/docker-swarm-00.png" title="" alt="image"&gt;&lt;/p&gt;
&lt;h2 id="二、nebula 集群搭建"&gt;二、nebula 集群搭建&lt;/h2&gt;&lt;h3 id="2.1 环境准备"&gt;2.1 环境准备&lt;/h3&gt;
&lt;p&gt;机器准备&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;ip&lt;/th&gt;
&lt;th&gt;内存 (Gb)&lt;/th&gt;
&lt;th&gt;cpu(核数)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.1.166&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.1.167&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.1.168&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;在安装前确保所有机器已安装 Docker&lt;/p&gt;
&lt;h3 id="2.2 初始化 swarm 集群"&gt;2.2 初始化 swarm 集群&lt;/h3&gt;
&lt;p&gt;在 192.168.1.166 机器上执行&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker swarm init --advertise-addr 192.168.1.166
Swarm initialized: current node (dxn1zf6l61qsb1josjja83ngz) is now a manager.
To add a worker to this swarm, run the following command:
 docker swarm join \
 --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \
 192.168.1.166:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.3 加入 worker 节点"&gt;2.3 加入 worker 节点&lt;/h3&gt;
&lt;p&gt;根据 init 命令提示内容，加入 swarm worker 节点，在 192.168.1.167 192.168.1.168 分别执行&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker swarm join \
 --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \
 192.168.1.166:2377
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.4 验证集群"&gt;2.4 验证集群&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker node ls

ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
h0az2wzqetpwhl9ybu76yxaen *   KF2-DATA-166        Ready               Active              Reachable           18.06.1-ce
q6jripaolxsl7xqv3cmv5pxji     KF2-DATA-167        Ready               Active              Leader              18.06.1-ce
h1iql1uvm7123h3gon9so69dy     KF2-DATA-168        Ready               Active                                  18.06.1-ce
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.5 配置 docker stack"&gt;2.5 配置 docker stack&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vi docker-stack.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置如下内容&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.6'
services:
  metad0:
    image: vesoft/nebula-metad:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --local_ip=192.168.1.166
      - --ws_ip=192.168.1.166
      - --port=45500
      - --data_path=/data/meta
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-166
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.166:11000/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 11000
        published: 11000
        protocol: tcp
        mode: host
      - target: 11002
        published: 11002
        protocol: tcp
        mode: host
      - target: 45500
        published: 45500
        protocol: tcp
        mode: host
    volumes:
      - data-metad0:/data/meta
      - logs-metad0:/logs
    networks:
      - nebula-net

  metad1:
    image: vesoft/nebula-metad:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --local_ip=192.168.1.167
      - --ws_ip=192.168.1.167
      - --port=45500
      - --data_path=/data/meta
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-167
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.167:11000/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 11000
        published: 11000
        protocol: tcp
        mode: host
      - target: 11002
        published: 11002
        protocol: tcp
        mode: host
      - target: 45500
        published: 45500
        protocol: tcp
        mode: host
    volumes:
      - data-metad1:/data/meta
      - logs-metad1:/logs
    networks:
      - nebula-net

  metad2:
    image: vesoft/nebula-metad:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --local_ip=192.168.1.168
      - --ws_ip=192.168.1.168
      - --port=45500
      - --data_path=/data/meta
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-168
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.168:11000/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 11000
        published: 11000
        protocol: tcp
        mode: host
      - target: 11002
        published: 11002
        protocol: tcp
        mode: host
      - target: 45500
        published: 45500
        protocol: tcp
        mode: host
    volumes:
      - data-metad2:/data/meta
      - logs-metad2:/logs
    networks:
      - nebula-net

  storaged0:
    image: vesoft/nebula-storaged:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --local_ip=192.168.1.166
      - --ws_ip=192.168.1.166
      - --port=44500
      - --data_path=/data/storage
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-166
    depends_on:
      - metad0
      - metad1
      - metad2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.166:12000/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 12000
        published: 12000
        protocol: tcp
        mode: host
      - target: 12002
        published: 12002
        protocol: tcp
        mode: host
    volumes:
      - data-storaged0:/data/storage
      - logs-storaged0:/logs
    networks:
      - nebula-net
  storaged1:
    image: vesoft/nebula-storaged:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --local_ip=192.168.1.167
      - --ws_ip=192.168.1.167
      - --port=44500
      - --data_path=/data/storage
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-167
    depends_on:
      - metad0
      - metad1
      - metad2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.167:12000/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 12000
        published: 12000
        protocol: tcp
        mode: host
      - target: 12002
        published: 12004
        protocol: tcp
        mode: host
    volumes:
      - data-storaged1:/data/storage
      - logs-storaged1:/logs
    networks:
      - nebula-net

  storaged2:
    image: vesoft/nebula-storaged:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --local_ip=192.168.1.168
      - --ws_ip=192.168.1.168
      - --port=44500
      - --data_path=/data/storage
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-168
    depends_on:
      - metad0
      - metad1
      - metad2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.168:12000/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 12000
        published: 12000
        protocol: tcp
        mode: host
      - target: 12002
        published: 12006
        protocol: tcp
        mode: host
    volumes:
      - data-storaged2:/data/storage
      - logs-storaged2:/logs
    networks:
      - nebula-net
  graphd1:
    image: vesoft/nebula-graphd:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --port=3699
      - --ws_ip=192.168.1.166
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-166
    depends_on:
      - metad0
      - metad1
      - metad2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.166:13000/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 3699
        published: 3699
        protocol: tcp
        mode: host
      - target: 13000
        published: 13000
        protocol: tcp
#        mode: host
      - target: 13002
        published: 13002
        protocol: tcp
        mode: host
    volumes:
      - logs-graphd:/logs
    networks:
      - nebula-net

  graphd2:
    image: vesoft/nebula-graphd:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --port=3699
      - --ws_ip=192.168.1.167
      - --log_dir=/logs
      - --v=2
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-167
    depends_on:
      - metad0
      - metad1
      - metad2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.167:13001/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 3699
        published: 3640
        protocol: tcp
        mode: host
      - target: 13000
        published: 13001
        protocol: tcp
        mode: host
      - target: 13002
        published: 13003
        protocol: tcp
#        mode: host
    volumes:
      - logs-graphd2:/logs
    networks:
      - nebula-net
  graphd3:
    image: vesoft/nebula-graphd:nightly
    env_file:
      - ./nebula.env
    command:
      - --meta_server_addrs=192.168.1.166:45500,192.168.1.167:45500,192.168.1.168:45500
      - --port=3699
      - --ws_ip=192.168.1.168
      - --log_dir=/logs
      - --v=0
      - --minloglevel=2
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.hostname == KF2-DATA-168
    depends_on:
      - metad0
      - metad1
      - metad2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://192.168.1.168:13002/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    ports:
      - target: 3699
        published: 3641
        protocol: tcp
        mode: host
      - target: 13000
        published: 13002
        protocol: tcp
#        mode: host
      - target: 13002
        published: 13004
        protocol: tcp
        mode: host
    volumes:
      - logs-graphd3:/logs
    networks:
      - nebula-net
networks:
  nebula-net:
    external: true
    attachable: true
    name: host
volumes:
  data-metad0:
  logs-metad0:
  data-metad1:
  logs-metad1:
  data-metad2:
  logs-metad2:
  data-storaged0:
  logs-storaged0:
  data-storaged1:
  logs-storaged1:
  data-storaged2:
  logs-storaged2:
  logs-graphd:
  logs-graphd2:
  logs-graphd3:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑 nebula.env，加入如下内容&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TZ=UTC
USER=root
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.6 启动 nebula 集群"&gt;2.6 启动 nebula 集群&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker stack deploy nebula -c docker-stack.yml
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三、集群负载均衡及高可用配置"&gt;三、集群负载均衡及高可用配置&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://0x7.me/swarm2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 的客户端目前（&lt;a href="https://github.com/vesoft-inc/nebula/releases/tag/v1.1.0" rel="nofollow" target="_blank" title=""&gt;1.X&lt;/a&gt;）没有提供负载均衡的能力，只是随机选一个 graphd 去连接。所以生产使用的时候要自己做个负载均衡和高可用。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/docker-swarm-01.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;图 3.1&lt;/p&gt;

&lt;p&gt;将整个部署架构分为三层，数据服务层，负载均衡层及高可用层。如图 3.1 所示&lt;/p&gt;

&lt;p&gt;负载均衡层：对 client 请求做负载均衡，将请求分发至下方数据服务层&lt;/p&gt;

&lt;p&gt;高可用层：这里实现的是 haproxy 的高可用，保证负载均衡层的服务从而保证整个集群的正常服务&lt;/p&gt;
&lt;h3 id="3.1 负载均衡配置"&gt;3.1 负载均衡配置&lt;/h3&gt;
&lt;p&gt;haproxy 使用 docker-compose 配置。分别编辑以下三个文件&lt;/p&gt;

&lt;p&gt;Dockerfile 加入以下内容 &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM haproxy:1.7
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
EXPOSE 3640
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docker-compose.yml 加入以下内容&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.2"
services:
  haproxy:
    container_name: haproxy
    build: .
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
    ports:
      - 3640:3640
    restart: always
    networks:
      - app_net
networks:
  app_net:
    external: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;haproxy.cfg 加入以下内容&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global
    daemon
    maxconn 30000
    log 127.0.0.1 local0 info
    log 127.0.0.1 local1 warning

defaults
    log-format %hr\ %ST\ %B\ %Ts
    log  global
    mode http
    option http-keep-alive
    timeout connect 5000ms
    timeout client 10000ms
    timeout server 50000ms
    timeout http-request 20000ms

# custom your own frontends &amp;amp;&amp;amp; backends &amp;amp;&amp;amp; listen conf
# CUSTOM

listen graphd-cluster
    bind *:3640
    mode tcp
    maxconn 300
    balance roundrobin
    server server1 192.168.1.166:3699 maxconn 300 check
    server server2 192.168.1.167:3699 maxconn 300 check
    server server3 192.168.1.168:3699 maxconn 300 check

listen stats
    bind *:1080
    stats refresh 30s
    stats uri /stats

&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3.2 启动 haproxy"&gt;3.2 启动 haproxy&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3.3 高可用配置"&gt;3.3 高可用配置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注：配置 keepalive 需预先准备好 vip（虚拟 ip），在以下配置中 192.168.1.99 便为虚拟 ip&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在 192.168.1.166、192.168.1.167、192.168.1.168 上 均做以下配置&lt;/p&gt;

&lt;p&gt;安装 keepalived&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt-get update &amp;amp;&amp;amp; apt-get upgrade &amp;amp;&amp;amp; apt-get install keepalived -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更改 keepalived 配置文件 &lt;code&gt;/etc/keepalived/keepalived.conf&lt;/code&gt;（三台机器中 做如下配置，priority 应设置不同值确定优先级）&lt;/p&gt;

&lt;p&gt;192.168.1.166 机器配置&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global_defs {
    router_id lb01 # 标识信息，一个名字而已；
}
vrrp_script chk_haproxy {
    script "killall -0 haproxy"    interval 2
}
vrrp_instance VI_1 {
    state MASTER
    interface ens160
    virtual_router_id 52
    priority 999
    # 设定 MASTER 与 BACKUP 负载均衡器之间同步检查的时间间隔，单位是秒
    advert_int 1
    # 设置验证类型和密码
    authentication {
    # 设置验证类型，主要有 PASS 和 AH 两种
        auth_type PASS
    # 设置验证密码，在同一个 vrrp_instance 下，MASTER 与 BACKUP 必须使用相同的密码才能正常通信
        auth_pass amber1
    }
    virtual_ipaddress {
        # 虚拟 IP 为 192.168.1.99/24; 绑定接口为 ens160; 别名 ens169:1，主备相同
        192.168.1.99/24 dev ens160 label ens160:1
    }
    track_script {
        chk_haproxy
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;167 机器配置&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global_defs {
    router_id lb01 # 标识信息，一个名字而已；
}
vrrp_script chk_haproxy {
    script "killall -0 haproxy"    interval 2
}
vrrp_instance VI_1 {
    state BACKUP
    interface ens160
    virtual_router_id 52
    priority 888
    # 设定 MASTER 与 BACKUP 负载均衡器之间同步检查的时间间隔，单位是秒
    advert_int 1
    # 设置验证类型和密码
    authentication {
    # 设置验证类型，主要有 PASS 和 AH 两种
        auth_type PASS
    # 设置验证密码，在同一个 vrrp_instance 下，MASTER 与 BACKUP 必须使用相同的密码才能正常通信
        auth_pass amber1
    }
    virtual_ipaddress {
        # 虚拟 IP 为 192.168.1.99/24; 绑定接口为 ens160; 别名 ens160:1，主备相同
        192.168.1.99/24 dev ens160 label ens160:1
    }
    track_script {
        chk_haproxy
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;168 机器配置&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global_defs {
    router_id lb01 # 标识信息，一个名字而已；
}
vrrp_script chk_haproxy {
    script "killall -0 haproxy"    interval 2
}
vrrp_instance VI_1 {
    state BACKUP
    interface ens160
    virtual_router_id 52
    priority 777
    # 设定 MASTER 与 BACKUP 负载均衡器之间同步检查的时间间隔，单位是秒
    advert_int 1
    # 设置验证类型和密码
    authentication {
    # 设置验证类型，主要有 PASS 和 AH 两种
        auth_type PASS
    # 设置验证密码，在同一个 vrrp_instance 下，MASTER 与 BACKUP 必须使用相同的密码才能正常通信
        auth_pass amber1
    }
    virtual_ipaddress {
        # 虚拟 IP 为 192.168.1.99/24;绑定接口为 ens160; 别名 ens160:1，主备相同
        192.168.1.99/24 dev ens160 label ens160:1
    }
    track_script {
        chk_haproxy
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;keepalived 相关命令&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 启动 keepalived
systemctl start keepalived
# 使 keepalived 开机自启
systemctl enable keeplived
# 重启 keepalived
systemctl restart keepalived
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="四、其他"&gt;四、其他&lt;/h2&gt;
&lt;p&gt;离线怎么部署？把镜像更改为私有镜像库就成了，有问题欢迎来勾搭啊。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/docker-swarm-02.png" title="" alt="image"&gt;&lt;/p&gt;

&lt;p&gt;我的小鱼你醒了 还认识早晨吗 昨夜你曾经说 愿夜幕永不开启&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如果你对本文有任何疑问，欢迎来论坛和原作者聊聊~~ 原帖地址：&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1388" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/1388&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/WeChatOffical.png" title="" alt="关注公众号"&gt;&lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 14 Oct 2020 11:23:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/40478</link>
      <guid>https://ruby-china.org/topics/40478</guid>
    </item>
    <item>
      <title>一文了解 Nebula Graph DBaaS 服务——Nebula Graph Cloud Service</title>
      <description>&lt;h2 id="Nebula Graph DBaaS"&gt;Nebula Graph DBaaS&lt;/h2&gt;
&lt;p&gt;作为一款 DBaaS（DataBase as s Service）的产品，&lt;a href="https://cloud.nebula-graph.com.cn/" rel="nofollow" target="_blank" title=""&gt;Nebula Graph Cloud Service&lt;/a&gt; 极大地降低了研发人员使用 Nebula Graph 的成本，更专注于使用 Nebula Graph 挖掘、分析数据背后的关联价值。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nebula-graph.com.cn/" rel="nofollow" target="_blank" title=""&gt;Nebula Graph Cloud Service&lt;/a&gt; Trial 版本已于近期开始公测试用，本篇文章主要帮助感兴趣的朋友快速了解我们云服务 Trial 版本的主要功能及开放范围。&lt;/p&gt;
&lt;h2 id="主要功能"&gt;主要功能&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一键创建 Nebula Graph 云服务实例

&lt;ul&gt;
&lt;li&gt;权限管理 - 可邀请其他 Nebula Graph Cloud Service 注册用户一起使用实例&lt;/li&gt;
&lt;li&gt;日志记录 - 记录查看实例有关操作记录&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/DBaaS01.png" title="" alt="DBaaS"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;提供在线 Nebula Graph Studio——图数据库可视化工具：

&lt;ul&gt;
&lt;li&gt;控制台 - 快速尝试 nebula 语句的基本功能&lt;/li&gt;
&lt;li&gt;图探索 - 通过图可视化发掘数据之间的联系&lt;/li&gt;
&lt;li&gt;导数据 - 通过可视化配置将数据导入 nebula&lt;/li&gt;
&lt;li&gt;可视化构图 - 通过可视化操作，迅速完成 点/边 构图建模（近期发布）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/DBaaS02.png" title="" alt="DBaaS"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/DBaaS03.png" title="" alt="DBaaS"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;服务监控 - 实时洞察机器运行的基本情况&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/DBaaS04.png" title="" alt="DBaaS"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;团队管理 - 简单的团队创建及成员添加，方便实例所属权的转移和交接 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/DBaaS05.png" title="" alt="DBaaS"&gt;&lt;/p&gt;
&lt;h2 id="公测范围"&gt;公测范围&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;初次了解 Nebula Graph 图数据库，想要快速无障碍体验 Nebula Graph 产品服务&lt;/li&gt;
&lt;li&gt;有图数据库使用需求的用户，诸如金融风控、实时推荐、知识图谱等应用场景&lt;/li&gt;
&lt;li&gt;企业用户：最好使用公司邮箱注册，有限的试用资源能帮助真正需要尝试的朋友&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Trial 版本限制"&gt;Trial 版本限制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;试用期间无法提供独立 IP 供业务客户端直连，一切侧重产品功能体验为主，商用版本会提供。&lt;/li&gt;
&lt;li&gt;试用期提供的实例服务均为单副本的统一资源：

&lt;ul&gt;
&lt;li&gt;1G 内存&lt;/li&gt;
&lt;li&gt;单核&lt;/li&gt;
&lt;li&gt;40G 磁盘大小&lt;/li&gt;
&lt;li&gt;其他：商用版本会通过资源配置选择。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据导入时上传的数据集单个文件大小不能超过 100M，总文件大小限制 1G。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="试用链接及官方联系方式"&gt;试用链接及官方联系方式&lt;/h2&gt;
&lt;p&gt;欢迎感兴趣的朋友前来申请试用：&lt;a href="https://cloud.nebula-graph.com.cn/" rel="nofollow" target="_blank" title=""&gt;https://cloud.nebula-graph.com.cn/&lt;/a&gt;，有更多需求和问题咨询的朋友，也欢迎联系我们🤝。&lt;/p&gt;

&lt;p&gt;邮箱联系：cloud-support@vesoft.com&lt;/p&gt;

&lt;p&gt;论坛提问：&lt;a href="https://discuss.nebula-graph.com.cn/c/users/DBaas/36" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/c/users/DBaas/36&lt;/a&gt;&lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Fri, 25 Sep 2020 11:15:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/40436</link>
      <guid>https://ruby-china.org/topics/40436</guid>
    </item>
    <item>
      <title>用图机器学习探索 A 股个股相关性变化</title>
      <description>&lt;p&gt;在本系列的前文 [1,2] 中，我们介绍了如何使用 Python 语言图分析库 NetworkX [3] + &lt;a href="https://0x7.me/jt2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; [4] 来进行&amp;lt;权力的游戏&amp;gt;中人物关系图谱分析。&lt;/p&gt;

&lt;p&gt;在本文中我们将介绍如何使用 Java 语言的图分析库 JGraphT [5] 并借助绘图库 mxgraph [6] ，可视化探索 A 股的&lt;strong&gt;行业个股的相关性随时间的变化情况&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/JGraphT01.png" title="" alt="JGraphT"&gt;&lt;/p&gt;
&lt;h2 id="数据集的处理"&gt;数据集的处理&lt;/h2&gt;
&lt;p&gt;本文主要分析方法参考了 [7,8]，有两种数据集：&lt;/p&gt;
&lt;h3 id="股票数据（点集）"&gt;股票数据（点集）&lt;/h3&gt;
&lt;p&gt;从 A 股中按股票代码顺序选取了 160 只股票（排除摘牌或者 ST 的）。每一支股票都被建模成一个点，每个点的属性有股票代码，股票名称，以及证监会对该股票对应上市公司所属板块分类等三种属性；&lt;/p&gt;

&lt;p&gt;表 1：点集示例&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;顶点 id&lt;/th&gt;
&lt;th&gt;股票代码&lt;/th&gt;
&lt;th&gt;股票名称&lt;/th&gt;
&lt;th&gt;所属板块&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SZ0001&lt;/td&gt;
&lt;td&gt;平安银行&lt;/td&gt;
&lt;td&gt;金融行业&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;600000&lt;/td&gt;
&lt;td&gt;浦发银行&lt;/td&gt;
&lt;td&gt;金融行业&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;600004&lt;/td&gt;
&lt;td&gt;白云机场&lt;/td&gt;
&lt;td&gt;交通运输&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;600006&lt;/td&gt;
&lt;td&gt;东风汽车&lt;/td&gt;
&lt;td&gt;汽车制造&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;600007&lt;/td&gt;
&lt;td&gt;中国国贸&lt;/td&gt;
&lt;td&gt;开发区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;600008&lt;/td&gt;
&lt;td&gt;首创股份&lt;/td&gt;
&lt;td&gt;环保行业&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;600009&lt;/td&gt;
&lt;td&gt;上海机场&lt;/td&gt;
&lt;td&gt;交通运输&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;600010&lt;/td&gt;
&lt;td&gt;包钢股份&lt;/td&gt;
&lt;td&gt;钢铁行业&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id="股票关系（边集）"&gt;股票关系（边集）&lt;/h3&gt;
&lt;p&gt;边只有一个属性，即权重。边的权重代表边的源点和目标点所代表的两支股票所属上市公司业务上的的相似度——相似度的具体计算方法参考 [7,8]：取一段时间（2014 年 1 月 1 日 - 2020 年 1 月 1 日）内，个股的日收益率的时间序列相关性 &lt;img src="https://oscimg.oschina.net/oscnet/up-e7a9ff4ee3615c1de8c5c5c717163c1519a.png" title="" alt=""&gt; 再定义个股之间的距离为 (也即两点之间的边权重）：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://oscimg.oschina.net/oscnet/up-d612a2cbc9b0e85a48adb6e8d7d4ba8ab78.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;通过这样的处理，距离取值范围为 [0,2]。&lt;strong&gt;这意味着距离越远的个股，两个之间的收益率相关性越低&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;表 2：边集示例&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;边的源点 ID&lt;/th&gt;
&lt;th&gt;边的目标点 ID&lt;/th&gt;
&lt;th&gt;边的权重&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0.493257968&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;0.517027513&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;0.606206233&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0.653692415&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;0.677631482&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;0.695705171&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0.71124344&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;0.73581915&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0.771556458&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;0.785046446&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0.789606527&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;0.796009627&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;0.797218349&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;0.799230001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;115&lt;/td&gt;
&lt;td&gt;0.803534952&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这样的点集和边集构成一个图网络，可以将这个网络存储在图数据库 &lt;a href="https://github.com/vesoft-inc/nebula/pull/2290" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 中。&lt;/p&gt;
&lt;h2 id="JGraphT"&gt;JGraphT&lt;/h2&gt;
&lt;p&gt;JGraphT 是一个开放源代码的 Java 类库，它不仅为我们提供了各种高效且通用的图数据结构，还为解决最常见的图问题提供了许多有用的算法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;支持有向边、无向边、权重边、非权重边等；&lt;/li&gt;
&lt;li&gt;支持简单图、多重图、伪图；&lt;/li&gt;
&lt;li&gt;提供了用于图遍历的专用迭代器（DFS，BFS）等；&lt;/li&gt;
&lt;li&gt;提供了大量常用的的图算法，如路径查找、同构检测、着色、公共祖先、游走、连通性、匹配、循环检测、分区、切割、流、中心性等算法；&lt;/li&gt;
&lt;li&gt;可以方便地导入 / 导出 GraphViz [9]。导出的 GraphViz 可被导入可视化工具 Gephi[10] 进行分析与展示；&lt;/li&gt;
&lt;li&gt;可以方便地使用其他绘图组件，如：JGraphX，mxGraph，Guava Graphs Generators 等工具绘制出图网络。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面，我们来实践一把，先在 JGraphT 中创建一个有向图：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jgrapht.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jgrapht.graph.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jgrapht.nio.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jgrapht.nio.dot.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jgrapht.traverse.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DefaultEdge&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultDirectedGraph&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;DefaultEdge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加顶点：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="no"&gt;URI&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://www.google.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="no"&gt;URI&lt;/span&gt; &lt;span class="n"&gt;wikipedia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://www.wikipedia.org"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="no"&gt;URI&lt;/span&gt; &lt;span class="n"&gt;jgrapht&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://www.jgrapht.org"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// add the vertices&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wikipedia&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jgrapht&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加边：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// add edges to create linking structure&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jgrapht&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wikipedia&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jgrapht&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wikipedia&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wikipedia&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="图数据库 Nebula Graph Database"&gt;图数据库 Nebula Graph Database&lt;/h2&gt;
&lt;p&gt;JGraphT 通常使用本地文件作为数据源，这在静态网络研究的时候没什么问题，但如果图网络经常会发生变化——例如，股票数据每日都在变化——每次生成全新的静态文件再加载分析就有些麻烦，最好整个变化过程可以持久化地写入一个数据库中，并且可以实时地直接从数据库中加载子图或者全图做分析。本文选用 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 作为存储图数据的图数据库。&lt;/p&gt;

&lt;p&gt;Nebula Graph 的 Java 客户端 &lt;strong&gt;Nebula-Java [11] 提供了两种访问 Nebula Graph 方式&lt;/strong&gt;：一种是&lt;strong&gt;通过图查询语言 nGQL [12] 与查询引擎层 [13] 交互，这通常适用于有复杂语义的子图访问类型&lt;/strong&gt;; 另一种是通过 API &lt;strong&gt;与底层的存储层（storaged）[14] 直接交互，用于获取全量的点和边&lt;/strong&gt;。除了可以访问 &lt;a href="https://0x7.me/jt2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 本身外，&lt;strong&gt;Nebula-Java 还提供了与 Neo4j [15]、JanusGraph [16]、Spark [17] 等交互的示例&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在本文中，&lt;strong&gt;我们选择直接访问存储层（storaged）来获取全部的点和边&lt;/strong&gt;。下面两个接口可以用来读取所有的点、边数据：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// space 为待扫描的图空间名称，returnCols 为需要读取的点/边及其属性列，&lt;/span&gt;
&lt;span class="c1"&gt;// returnCols 参数格式：{tag1Name: prop1, prop2, tag2Name: prop3, prop4, prop5}&lt;/span&gt;
&lt;span class="nc"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScanVertexResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;scanVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;returnCols&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScanEdgeResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;scanEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;returnCols&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一步：初始化一个客户端，和一个 ScanVertexProcessor。ScanVertexProcessor 用来对读出来的顶点数据进行解码：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;MetaClientImpl&lt;/span&gt; &lt;span class="n"&gt;metaClientImpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MetaClientImpl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metaHost&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metaPort&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;metaClientImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;StorageClient&lt;/span&gt; &lt;span class="n"&gt;storageClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StorageClientImpl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metaClientImpl&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Processor&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ScanVertexProcessor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metaClientImpl&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二步：调用 scanVertex 接口，该接口会返回一个 scanVertexResponse 对象的迭代器：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScanVertexResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;iterator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;storageClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scanVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spaceName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;returnCols&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第三步：不断读取该迭代器所指向的 scanVertexResponse 对象中的数据，直到读取完所有数据。读取出来的顶点数据先保存起来，后面会将其添加到到 JGraphT 的图结构中：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasNext&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;ScanVertexResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error occurs while scan vertex"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;Result&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spaceName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRows&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TAGNAME&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;读取边数据的方法和上面的流程类似。&lt;/p&gt;
&lt;h2 id="在 JGraphT 中进行图分析"&gt;在 JGraphT 中进行图分析&lt;/h2&gt;
&lt;p&gt;第一步：在 JGraphT 中创建一个无向加权图 graph：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MyEdge&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GraphTypeBuilder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;undirected&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;weighted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allowingMultipleEdges&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allowingSelfLoops&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;vertexSupplier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SupplierUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStringSupplier&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;edgeSupplier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SupplierUtil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createSupplier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyEdge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildGraph&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二步：将上一步从 Nebula Graph 图空间中读出来的点、边数据添加到 graph 中：&lt;br&gt;&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;VertexDomain&lt;/span&gt; &lt;span class="n"&gt;vertex&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertexDomainList&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVid&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;stockIdToName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVid&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EdgeDomain&lt;/span&gt; &lt;span class="n"&gt;edgeDomain&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;edgeDomainList&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edgeDomain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSrcid&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;edgeDomain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDstid&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="nc"&gt;MyEdge&lt;/span&gt; &lt;span class="n"&gt;newEdge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edgeDomain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSrcid&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;edgeDomain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDstid&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEdgeWeight&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newEdge&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edgeDomain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getWeight&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第三步：参考 [7,8] 中的分析法，对刚才的图 graph 使用 Prim 最小生成树算法（minimun-spanning-tree），并调用封装好的 drawGraph 接口画图：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;普里姆算法（Prim's algorithm），图论中的一种算法，可在加权连通图里搜索最小生成树。即，由此算法搜索到的边子集所构成的树中，不但包括了连通图里的所有顶点，且其所有边的权值之和亦为最小。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;SpanningTreeAlgorithm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SpanningTree&lt;/span&gt; &lt;span class="n"&gt;pMST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrimMinimumSpanningTree&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getSpanningTree&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Legend&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;drawGraph&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pMST&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEdges&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stockIdToName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第四步：drawGraph 方法封装了画图的布局等各项参数设置。这个方法&lt;strong&gt;将同一板块的股票渲染为同一颜色，将距离接近的股票排列聚集在一起&lt;/strong&gt;。&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Legend&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;drawGraph&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyEdge&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;VertexDomain&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;idVertexMap&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// Creates graph with model&lt;/span&gt;
     &lt;span class="n"&gt;mxGraph&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;mxGraph&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
     &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDefaultParent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

     &lt;span class="c1"&gt;// set style&lt;/span&gt;
     &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getModel&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;beginUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
     &lt;span class="n"&gt;mxStylesheet&lt;/span&gt; &lt;span class="n"&gt;myStylesheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStylesheet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
     &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStylesheet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setMsStylesheet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myStylesheet&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

     &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;idMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
     &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;industryColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

     &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;colorIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyEdge&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
       &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;idMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSrc&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
         &lt;span class="nc"&gt;VertexDomain&lt;/span&gt; &lt;span class="n"&gt;srcNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idVertexMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSrc&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
         &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;nodeColor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;industryColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;srcNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIndustry&lt;/span&gt;&lt;span class="o"&gt;())){&lt;/span&gt;
           &lt;span class="n"&gt;nodeColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;industryColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;srcNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIndustry&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
         &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
           &lt;span class="n"&gt;nodeColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;COLOR_LIST&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;colorIndex&lt;/span&gt;&lt;span class="o"&gt;++];&lt;/span&gt;
           &lt;span class="n"&gt;industryColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;srcNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIndustry&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nodeColor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
         &lt;span class="o"&gt;}&lt;/span&gt;
         &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insertVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;srcNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"fillColor="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nodeColor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
         &lt;span class="n"&gt;idMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSrc&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
       &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
         &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSrc&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
       &lt;span class="o"&gt;}&lt;/span&gt;

       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;idMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDst&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
         &lt;span class="nc"&gt;VertexDomain&lt;/span&gt; &lt;span class="n"&gt;dstNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idVertexMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDst&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

         &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;nodeColor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;industryColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dstNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIndustry&lt;/span&gt;&lt;span class="o"&gt;())){&lt;/span&gt;
           &lt;span class="n"&gt;nodeColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;industryColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dstNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIndustry&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
         &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
           &lt;span class="n"&gt;nodeColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;COLOR_LIST&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;colorIndex&lt;/span&gt;&lt;span class="o"&gt;++];&lt;/span&gt;
           &lt;span class="n"&gt;industryColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dstNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIndustry&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nodeColor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
         &lt;span class="o"&gt;}&lt;/span&gt;

         &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insertVertex&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dstNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"fillColor="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nodeColor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
         &lt;span class="n"&gt;idMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDst&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
       &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
         &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDst&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
       &lt;span class="o"&gt;}&lt;/span&gt;
       &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insertEdge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="o"&gt;}&lt;/span&gt;


     &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vertice "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;idMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
     &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"colorsize "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;industryColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

     &lt;span class="n"&gt;mxFastOrganicLayout&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;mxFastOrganicLayout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaxIterations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="c1"&gt;//layout.setMinDistanceLimit(10D);&lt;/span&gt;
     &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

     &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getModel&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;endUpdate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

     &lt;span class="c1"&gt;// Creates an image than can be saved using ImageIO&lt;/span&gt;
     &lt;span class="nc"&gt;BufferedImage&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;createBufferedImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WHITE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                               &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

     &lt;span class="c1"&gt;// For the sake of this example we display the image in a window&lt;/span&gt;
     &lt;span class="c1"&gt;// Save as JPEG&lt;/span&gt;
     &lt;span class="nc"&gt;File&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
     &lt;span class="nc"&gt;ImageIO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"JPEG"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

   &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第五步：生成可视化：&lt;/p&gt;

&lt;p&gt;图 1 中&lt;strong&gt;每个顶点的颜色代表证监会对该股票所属上市公司归类的板块&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;可以看到，实际业务近似度较高的股票已经聚拢成簇状（例如：高速板块、银行版本、机场航空板块），但也会有部分关联性不明显的个股被聚类在一起，具体原因需要单独进行个股研究。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/JGraphT01.png" title="" alt="JGraphT"&gt;&lt;/p&gt;

&lt;p&gt;图 1：基于 2015-01-01 至 2020-01-01 的股票数据计算出的聚集性&lt;/p&gt;

&lt;p&gt;第六步：基于不同时间窗口的一些其他动态探索&lt;/p&gt;

&lt;p&gt;上节中，结论主要基于 2015-01-01 到 2020-01-01 的个股聚集性。这一节我们还做了一些其他的尝试：&lt;strong&gt;以 2 年为一个时间滑动窗口，分析方法不变，定性探索聚集群是否随着时间变化会发生改变&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/JGraphT02.png" title="" alt="JGraphT"&gt;&lt;/p&gt;

&lt;p&gt;图 2：基于 2014-01-01 至 2016-01-01 的股票数据计算出的聚集性&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/JGraphT03.png" title="" alt="JGraphT"&gt;&lt;/p&gt;

&lt;p&gt;图 3：基于 2015-01-01 至 2017-01-01 的股票数据计算出的聚集性&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/JGraphT04.png" title="" alt="JGraphT"&gt;&lt;/p&gt;

&lt;p&gt;图 4：基于 2016-01-01 至 2018-01-01 的股票数据计算出的聚集性&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/JGraphT05.png" title="" alt="JGraphT"&gt;&lt;/p&gt;

&lt;p&gt;图 5：基于 2017-01-01 至 2019-01-01 的股票数据计算出的聚集性&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/JGraphT06.png" title="" alt="JGraphT"&gt;&lt;/p&gt;

&lt;p&gt;图 6：基于 2018-01-01 至 2020-01-01 的股票数据计算出的聚集性&lt;/p&gt;

&lt;p&gt;粗略分析看，随着时间窗口变化，有些板块（高速、银行、机场航空、房产、能源）的板块内部个股聚集性一直保持比较好——这意味着随着时间变化，这个版块内各种一直保持比较高的相关性；但有些板块（制造）的聚集性会持续变化——意味着相关性一直在发生变化。&lt;/p&gt;
&lt;h2 id="Disclaim"&gt;Disclaim&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/disclaim.png" title="" alt="Disclaim"&gt;&lt;/p&gt;

&lt;p&gt;本文不构成任何投资建议，且作者不持有本文中任一股票。&lt;/p&gt;

&lt;p&gt;受限于停牌、熔断、涨跌停、送转、并购、主营业务变更等情况，数据处理可能有错误，未做一一检查。&lt;/p&gt;

&lt;p&gt;受时间所限，本文只选用了 160 个个股样本过去 6 年的数据，只采用了最小扩张树一种办法来做聚类分类。未来可以使用更大的数据集（例如美股、衍生品、数字货币），尝试更多种图机器学习的办法。&lt;/p&gt;

&lt;p&gt;本文代码可见 [18]&lt;/p&gt;
&lt;h2 id="Reference"&gt;Reference&lt;/h2&gt;
&lt;p&gt;[1] 用 NetworkX + Gephi + Nebula Graph 分析&amp;lt;权力的游戏&amp;gt;人物关系（上篇）&lt;a href="https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] 用 NetworkX + Gephi + Nebula Graph 分析&amp;lt;权力的游戏&amp;gt;人物关系（下篇） &lt;a href="https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph-part-two/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph-part-two/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] NetworkX: a Python package for the creation, manipulation, and study of the structure, dynamics, and functions of complex networks. &lt;a href="https://networkx.github.io/" rel="nofollow" target="_blank" title=""&gt;https://networkx.github.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] Nebula Graph: A powerfully distributed, scalable, lightning-fast graph database written in C++. &lt;a href="https://nebula-graph.io/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] JGraphT: a Java library of graph theory data structures and algorithms. &lt;a href="https://jgrapht.org/" rel="nofollow" target="_blank" title=""&gt;https://jgrapht.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] mxGraph: JavaScript diagramming library that enables interactive graph and charting applications. &lt;a href="https://jgraph.github.io/mxgraph/" rel="nofollow" target="_blank" title=""&gt;https://jgraph.github.io/mxgraph/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[7] Bonanno, Giovanni &amp;amp; Lillo, Fabrizio &amp;amp; Mantegna, Rosario. (2000). High-frequency Cross-correlation in a Set of Stocks. arXiv.org, Quantitative Finance Papers. 1. 10.1080/713665554.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;[8] Mantegna, R.N. Hierarchical structure in financial markets. Eur. Phys. J. B 11, 193–197 (1999).&lt;/p&gt;

&lt;p&gt;[9] &lt;a href="https://graphviz.org/" rel="nofollow" target="_blank" title=""&gt;https://graphviz.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[10] &lt;a href="https://gephi.org/" rel="nofollow" target="_blank" title=""&gt;https://gephi.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[11] &lt;a href="https://github.com/vesoft-inc/nebula-java" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[12] Nebula Graph Query Language (nGQL). &lt;a href="https://docs.nebula-graph.io/manual-EN/1.overview/1.concepts/2.nGQL-overview/" rel="nofollow" target="_blank" title=""&gt;https://docs.nebula-graph.io/manual-EN/1.overview/1.concepts/2.nGQL-overview/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[13] Nebula Graph Query Engine. &lt;a href="https://github.com/vesoft-inc/nebula-graph" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-graph&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[14] Nebula-storage: A distributed consistent graph storage. &lt;a href="https://github.com/vesoft-inc/nebula-storage" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-storage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[15] Neo4j. &lt;a href="http://www.neo4j.com" rel="nofollow" target="_blank" title=""&gt;www.neo4j.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[16] JanusGraph. &lt;a href="http://janusgraph.org" rel="nofollow" target="_blank" title=""&gt;janusgraph.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[17] Apache Spark. &lt;a href="http://spark.apache.org" rel="nofollow" target="_blank" title=""&gt;spark.apache.org&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;[18] &lt;a href="https://github.com/Judy1992/nebula_scan" rel="nofollow" target="_blank" title=""&gt;https://github.com/Judy1992/nebula_scan&lt;/a&gt;&lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Thu, 24 Sep 2020 10:49:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/40434</link>
      <guid>https://ruby-china.org/topics/40434</guid>
    </item>
    <item>
      <title>从 Neo4j 导入 Nebula Graph 实践见 SPark 数据导入原理</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/import-data-from-neo4j-to-nebula-graph.png" title="" alt="Neo4j 数据导入实现"&gt;&lt;/p&gt;

&lt;p&gt;本文主要讲述如何使用数据导入工具 Nebula Graph Exchange 将数据从 Neo4j 导入到 &lt;a href="https://0x7.me/dineo4j2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; Database。在讲述如何实操数据导入之前，我们先来了解下 Nebula Graph 内部是如何实现这个导入功能的。&lt;/p&gt;
&lt;h2 id="Nebula Graph Exchange 的数据处理原理"&gt;Nebula Graph Exchange 的数据处理原理&lt;/h2&gt;
&lt;p&gt;我们这个导入工具名字是 Nebula Graph &lt;a href="https://github.com/vesoft-inc/nebula-java/tree/master/tools/exchange" rel="nofollow" target="_blank" title=""&gt;Exchange&lt;/a&gt;，采用 Spark 作为导入平台，来支持海量数据的导入和保障性能。Spark 本身提供了不错的抽象——DataFrame，使得可以轻松支持多种数据源。在 DataFrame 的支持下，添加新的数据源只需提供配置文件读取的代码和返回 DataFrame 的 Reader 类，即可支持新的数据源。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/dataframe-in-spark.png" title="" alt="DataFrame"&gt;&lt;/p&gt;

&lt;p&gt;DataFrame 可以视为一种分布式存表格。DataFrame 可以存储在多个节点的不同分区中，多个分区可以存储在不同的机器上，从而支持并行操作。Spark 还提供了一套简洁的 API 使用户轻松操作 DataFrame 如同操作本地数据集一般。现在大多数数据库提供直接将数据导出成 DataFrame 功能，即使某个数据库并未提供此功能也可以通过数据库 driver 手动构建 DataFrame。&lt;/p&gt;

&lt;p&gt;Nebula Graph Exchange 将数据源的数据处理成 DataFrame 之后，会遍历它的每一行，根据配置文件中 fields 的映射关系，按列名获取对应的值。在遍历 &lt;code&gt;batchSize&lt;/code&gt; 个行之后，Exchange 会将获取的数据一次性写入到 Nebula Graph 中。目前，Exchange 是通过生成 nGQL 语句再由 Nebula Client 异步写入数据，下一步会支持直接导出 Nebula Graph 底层存储的 sst 文件，以获取更好的性能。接下来介绍一下 Neo4j 数据源导入的具体实现。&lt;/p&gt;
&lt;h2 id="Neo4j 数据导入具体实现"&gt;Neo4j 数据导入具体实现&lt;/h2&gt;
&lt;p&gt;虽然 Neo4j 官方提供了可将数据直接导出为 DataFrame 的库，但使用它读取数据难以满足断点续传的需求，我们未直接使用这个库，而是使用 Neo4j 官方的 driver 实现数据读取。Exchange 通过在不同分区调取 Neo4j driver 执行不同 &lt;code&gt;skip&lt;/code&gt; 和 &lt;code&gt;limit&lt;/code&gt; 的 Cypher 语句，将数据分布在不同的分区，来获取更好的性能。这个分区数量由配置项 &lt;code&gt;partition&lt;/code&gt; 指定。&lt;/p&gt;

&lt;p&gt;Exchange 中的 Neo4jReader&amp;nbsp;类会先将用户配置中的 &lt;code&gt;exec&lt;/code&gt; Cypher 语句，&lt;code&gt;return&lt;/code&gt; 后边的语句替换成 &lt;code&gt;count(*)&lt;/code&gt; 执行获取数据总量，再根据分区数计算每个分区的起始偏移量和大小。这里如果用户配置了 &lt;code&gt;check_point_path&lt;/code&gt; 目录，会读取目录中的文件，如果处于续传的状态，Exchange 会计算出每个分区应该的偏移量和大小。然后每个分区在 Cypher 语句后边添加不同的 &lt;code&gt;skip&lt;/code&gt; 和 &lt;code&gt;limit&lt;/code&gt;，调用 driver 执行。最后将返回的数据处理成 DataFrame 就完成了 Neo4j 的数据导入。&lt;/p&gt;

&lt;p&gt;过程如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/import-neo4j-to-dataframe.png" title="" alt="数据导入过程"&gt;&lt;/p&gt;
&lt;h2 id="Neo4j 数据导入实践"&gt;Neo4j 数据导入实践&lt;/h2&gt;
&lt;p&gt;我们这里导入演示的系统环境如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cpu name: Intel(R) Xeon(R) CPU E5-2697 v3 @ 2.60GHz&lt;/li&gt;
&lt;li&gt;cpu cores: 14&lt;/li&gt;
&lt;li&gt;memory size: 251G&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;软件环境如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Neo4j：3.5.20 社区版&lt;/li&gt;
&lt;li&gt;Nebula graph：docker-compose 部署，默认配置&lt;/li&gt;
&lt;li&gt;Spark：单机版，版本为 2.4.6 pre-build for hadoop2.7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由于 Nebula Graph 是强 schema 数据库，数据导入前需先进行创建 Space，建 Tag 和 Edge 的 schema，具体的语法可以参考&lt;a href="https://docs.nebula-graph.com.cn/manual-CN/2.query-language/4.statement-syntax/1.data-definition-statements/create-space-syntax/" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这里建了名为 test 的 Space，副本数为 1。这里创建了两种 Tag 分别为 tagA 和 tagB，均含有 4 个属性的点类型，此外，还创建一种名为 edgeAB 的边类型，同样含有 4 个属性。具体的 nGQL 语句如下所示：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="err"&gt;创建图空间&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SPACE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replica_factor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="err"&gt;选择图空间&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="err"&gt;创建标签&lt;/span&gt; &lt;span class="n"&gt;tagA&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="n"&gt;tagA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idInt&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idString&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tboolean&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tdouble&lt;/span&gt; &lt;span class="nb"&gt;double&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="err"&gt;创建标签&lt;/span&gt; &lt;span class="n"&gt;tagB&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="n"&gt;tagB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idInt&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idString&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tboolean&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tdouble&lt;/span&gt; &lt;span class="nb"&gt;double&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="err"&gt;创建边类型&lt;/span&gt; &lt;span class="n"&gt;edgeAB&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EDGE&lt;/span&gt; &lt;span class="n"&gt;edgeAB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idInt&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idString&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tboolean&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tdouble&lt;/span&gt; &lt;span class="nb"&gt;double&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时向 Neo4j 导入 Mock 数据——标签为 tagA 和 tagB 的点，数量总共为 100 万，并且导入了连接 tagA 和 tagB 类型点边类型为 edgeAB 的边，共 1000 万个。另外需要注意的是，从 Neo4j 导出的数据在 Nebula Graph 中必须存在属性，且数据对应的类型要同 Nebula Graph 一致。&lt;/p&gt;

&lt;p&gt;最后为了提升向 Neo4j 导入 Mock 数据的效率和 Mock 数据在 Neo4j 中的读取效率，这里为 tagA 和 tagB 的 &lt;code&gt;idInt&lt;/code&gt; 属性建了索引。关于索引需要注意 Exchange 并不会将 Neo4j 中的索引、约束等信息导入到 Nebula Graph 中，所以需要用户在执行数据写入在 Nebula Graph 之后，自行&lt;a href="https://docs.nebula-graph.com.cn/manual-CN/2.query-language/4.statement-syntax/1.data-definition-statements/#_2" rel="nofollow" target="_blank" title=""&gt;创建索引&lt;/a&gt;和 &lt;a href="https://docs.nebula-graph.com.cn/manual-CN/2.query-language/4.statement-syntax/1.data-definition-statements/#_7" rel="nofollow" target="_blank" title=""&gt;REBUILD 索引&lt;/a&gt;（为已有数据建立索引）。&lt;/p&gt;

&lt;p&gt;接下来就可以将 Neo4j 数据导入到 Nebula Graph 中了，首先我们需要下载和编译打包项目，项目在 &lt;a href="https://github.com/vesoft-inc/nebula-java" rel="nofollow" target="_blank" title=""&gt;nebula-java&lt;/a&gt; 这个仓库下 tools/exchange 文件夹中。可执行如下命令：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vesoft-inc/nebula-java.git
&lt;span class="nb"&gt;cd &lt;/span&gt;nebula-java/tools/exchange
mvn package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就可以看到 &lt;code&gt;target/exchange-1.0.1.jar&lt;/code&gt; 这个文件。&lt;/p&gt;

&lt;p&gt;接下来编写配置文件，配置文件的格式为：HOCON（Human-Optimized Config Object Notation），可以基于 &lt;code&gt;src/main/resources/server_application.conf&lt;/code&gt; 文件的基础上进行更改。首先对 nebula 配置项下的 address、user、pswd 和 space 进行配置，测试环境均为默认配置，所以这里不需要额外的修改。然后进行 tags 配置，需要 tagA 和 tagB 的配置，这里仅展示 tagA 配置，tagB 和 tagA 配置相同。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# ======neo4j连接设置=======&lt;/span&gt;
    name: tagA
    &lt;span class="c"&gt;# 必须和 Nebula Graph 的中 tag 名字一致，需要在 Nebula Graph 中事先建好 tag&lt;/span&gt;
    server: &lt;span class="s2"&gt;"bolt://127.0.0.1:7687"&lt;/span&gt;
    &lt;span class="c"&gt;# neo4j 的地址配置&lt;/span&gt;
    user: neo4j
    &lt;span class="c"&gt;# neo4j 的用户名&lt;/span&gt;
    password: neo4j
    &lt;span class="c"&gt;# neo4j 的密码&lt;/span&gt;

    encryption: &lt;span class="nb"&gt;false&lt;/span&gt;
    &lt;span class="c"&gt;# (可选): 传输是否加密，默认值为 false&lt;/span&gt;
    database: graph.db
    &lt;span class="c"&gt;# (可选): neo4j database 名称，社区版不支持&lt;/span&gt;

    &lt;span class="c"&gt;# ======导入设置============&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;source&lt;/span&gt;: neo4j
        &lt;span class="c"&gt;# 还支持 PARQUET、ORC、JSON、CSV、HIVE、MYSQL、PULSAR、KAFKA...&lt;/span&gt;
        sink: client
        &lt;span class="c"&gt;# 写入 Nebula Graph 的方式，目前仅支持 client，未来会支持直接导出 Nebula Graph 底层数据库文件&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    nebula.fields: &lt;span class="o"&gt;[&lt;/span&gt;idInt, idString, tdouble, tboolean]
    fields       : &lt;span class="o"&gt;[&lt;/span&gt;idInt, idString, tdouble, tboolean]
    &lt;span class="c"&gt;# 映射关系 fields，上方为 nebula 的属性名，下方为 neo4j 的属性名，一一对应&lt;/span&gt;
    &lt;span class="c"&gt;# 映射关系的配置是 List 而不是 Map，是为了保持 fields 的顺序，未来直接导出 nebula 底层存储文件时需要&lt;/span&gt;

    vertex: idInt
    &lt;span class="c"&gt;# 作为 nebula vid 的 neo4j field，类型需要是整数(long or int)。&lt;/span&gt;

    partition: 10
    &lt;span class="c"&gt;# 分区数&lt;/span&gt;
    batch: 2000
    &lt;span class="c"&gt;# 一次写入 nebula 多少数据&lt;/span&gt;

    check_point_path: &lt;span class="s2"&gt;"file:///tmp/test"&lt;/span&gt;
    &lt;span class="c"&gt;# (可选): 保存导入进度信息的目录，用于断点续传&lt;/span&gt;

    &lt;span class="nb"&gt;exec&lt;/span&gt;: &lt;span class="s2"&gt;"match (n:tagA) return n.idInt as idInt, n.idString as idString, n.tdouble as tdouble, n.tboolean as tboolean order by n.idInt"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;边的设置大部分与点的设置无异，但由于边在 Nebula Graph 中有起点的 vid 和终点的 vid 标识，所以这里需要指定作为边起点 vid 的域和作为边终点 vid 的域。&lt;/p&gt;

&lt;p&gt;下面给出边的特别配置。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
  field: a.idInt
  &lt;span class="c"&gt;# policy: "hash"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;# 起点的 vid 设置&lt;/span&gt;
target: &lt;span class="o"&gt;{&lt;/span&gt;
  field: b.idInt
  &lt;span class="c"&gt;# policy: "uuid"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;# 终点的 vid 设置&lt;/span&gt;

ranking: idInt
&lt;span class="c"&gt;# (可选): 作为 rank 的 field&lt;/span&gt;

partition: 1
&lt;span class="c"&gt;# 这里分区数设置为 1，原因在后边&lt;/span&gt;

&lt;span class="nb"&gt;exec&lt;/span&gt;: &lt;span class="s2"&gt;"match (a:tagA)-[r:edgeAB]-&amp;gt;(b:tagB) return a.idInt, b.idInt, r.idInt as idInt, r.idString as idString, r.tdouble as tdouble, r.tboolean as tboolean order by id(r)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;点的 vertex 和边的 source、target 配置项下都可以设置 policy hash/uuid，它可以将类型为字符串的域作为点的 vid，通过 hash/uuid 函数将字符串映射成整数。&lt;/p&gt;

&lt;p&gt;上面的例子由于作为点的 vid 为整数，所以并不需要 policy 的设置。hash/uuid 的 区别请看&lt;a href="https://docs.nebula-graph.com.cn/manual-CN/2.query-language/2.functions-and-operators/uuid/" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://neo4j.com/docs/cypher-manual/3.5/clauses/skip/" rel="nofollow" target="_blank" title=""&gt;Cypher 标准&lt;/a&gt;中如果没有 &lt;code&gt;order by&lt;/code&gt; 约束的话就不能保证每次查询结果的排序一致，虽然看起来即便不加 &lt;code&gt;order by&lt;/code&gt; Neo4j 返回的结果顺序也是不变的，但为了防止可能造成的导入时数据丢失，还是强烈建议在 Cypher 语句中加入 &lt;code&gt;order by&lt;/code&gt;，虽然这会增加导入的时间。为了提升导入效率， &lt;code&gt;order by&lt;/code&gt;&amp;nbsp;语句最好选取有索引的属性作为排序的属性。如果没有索引，也可观察默认的排序，选择合适的排序属性以提高效率。如果默认的排序找不到规律，可以使用点/关系的 ID 作为排序属性，并且将 &lt;code&gt;partition&lt;/code&gt; 的值尽量设小，减少 Neo4j 的排序压力，本文中边 &lt;code&gt;edgeAB&lt;/code&gt; 的 &lt;code&gt;partition&lt;/code&gt;&amp;nbsp;就设置为 1。&lt;/p&gt;

&lt;p&gt;另外 Nebula Graph 在创建点和边时会将 ID 作为唯一主键，如果主键已存在则会覆盖该主键中的数据。所以假如将某个 Neo4j 属性值作为 Nebula Graph 的 ID，而这个属性值在 Neo4j 中是有重复的，就会导致“重复 ID”对应的数据有且只有一条会存入 Nebula Graph 中，其它的则会被覆盖掉。由于数据导入过程是并发地往 Nebula Graph 中写数据，最终保存的数据并不能保证是 Neo4j 中最新的数据。&lt;/p&gt;

&lt;p&gt;这里还要留意下断点续传功能，在断点和续传之间，数据库不应该改变状态，如添加数据或删除数据，且 &lt;code&gt;partition&lt;/code&gt;&amp;nbsp;数量也不能更改，否则可能会有数据丢失。&lt;/p&gt;

&lt;p&gt;最后由于 Exchange 需要在不同分区执行不同 &lt;code&gt;skip&lt;/code&gt; 和 &lt;code&gt;limit&lt;/code&gt; 的 Cypher 语句，所以用户提供的 Cypher 语句不能含有 &lt;code&gt;skip&lt;/code&gt; 和 &lt;code&gt;limit&lt;/code&gt; 语句。&lt;/p&gt;

&lt;p&gt;接下来就可以运行 Exchange 程序导数据了，执行如下命令：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$SPARK_HOME&lt;/span&gt;/bin/spark-submit  &lt;span class="nt"&gt;--class&lt;/span&gt; com.vesoft.nebula.tools.importer.Exchange &lt;span class="nt"&gt;--master&lt;/span&gt; &lt;span class="s2"&gt;"local[10]"&lt;/span&gt; target/exchange-1.0.1.jar &lt;span class="nt"&gt;-c&lt;/span&gt; /path/to/conf/neo4j_application.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述这些配置下，导入 100 万个点用时 13s，导入 1000 万条边用时 213s，总用时是 226s。&lt;/p&gt;
&lt;h2 id="附：Neo4j 3.5 Community 和 Nebula Graph 1.0.1的一些比较"&gt;附：Neo4j 3.5 Community 和 Nebula Graph 1.0.1 的一些比较&lt;/h2&gt;
&lt;p&gt;Neo4j 和 Nebula Graph 在系统架构、数据模型和访问方式上都有一些差异，下表列举了常见的异同&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/neo4j-vs-nebula-graph.png" title="" alt="neo4j 和 nebula graph 比较"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;作者有话说：Hi，我是李梦捷，图数据库 Nebula Graph 的研发工程师，如果你对此文有疑问，欢迎来我们的 Nebula Graph 论坛交流下心得~~&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;喜欢这篇文章？来来来，给我们的 &lt;a href="https://0x7.me/dineo4j2github" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt; 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]&lt;/p&gt;

&lt;p&gt;交流图数据库技术？交个朋友，Nebula Graph 官方小助手微信：&lt;a href="https://www-cdn.nebula-graph.com.cn/nebula-blog/nbot.png" rel="nofollow" target="_blank" title=""&gt;NebulaGraphbot&lt;/a&gt; 拉你进交流群~~&lt;/p&gt;
&lt;h2 id="推荐阅读"&gt;推荐阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/migrate-from-janusgraph-to-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;360 数科实践：JanusGraph 到 NebulaGraph 迁移&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 16 Sep 2020 09:53:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/40410</link>
      <guid>https://ruby-china.org/topics/40410</guid>
    </item>
    <item>
      <title>360 数科实践：JanusGraph 到 NebulaGraph 迁移</title>
      <description>&lt;p&gt;摘要：在本文中 360 数科的周鹏详细讲解了业务从 JanusGraph 迁移到 Nebula Graph 带来的性能提升，在机器资源不到之前 JanusGraph 配置三分之一的情况下，业务性能提升至少 20 倍。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/360-migrate.png" title="" alt="360 迁移"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文作者系 360 数科开发工程师：周鹏&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="迁移背景"&gt;迁移背景&lt;/h2&gt;
&lt;p&gt;我们之前图数据用的是单机版的 &lt;a href="https://github.com/bitnine-oss/agensgraph" rel="nofollow" target="_blank" title=""&gt;AgensGraph&lt;/a&gt;, 后面因为单机带来的性能限制问题，迁移到了分布式数据库 &lt;a href="https://github.com/JanusGraph/janusgraph" rel="nofollow" target="_blank" title=""&gt;JanusGraph&lt;/a&gt;，详细的迁移信息可以看我之前的一篇文章&lt;a href="https://www.jianshu.com/p/f372f0ef6c42" rel="nofollow" target="_blank" title=""&gt;《百亿级图数据 JanusGraph 迁移之旅》&lt;/a&gt;。但是随着数据量和业务调用量的增加，新的问题又出现了——单次查询的耗时很高个别业务场景已经到了 10s，数据量稍微多点，逻辑复杂点的查询耗时也在 2~3s 左右，这严重影响了整个业务流程的性能和相关业务的发展。&lt;/p&gt;

&lt;p&gt;JanusGraph 的架构决定了单次耗时高，核心的原因在于它的存储依赖外部，自身不能很好地控制外部存储，我们生产环境用的便是 &lt;a href="https://hbase.apache.org/" rel="nofollow" target="_blank" title=""&gt;HBase&lt;/a&gt; 集群，这导致所有的查询没法下推到存储层进行处理，只能把数据从 HBase 查询到 JanusGraph Server 内存再做相应的过滤。&lt;/p&gt;

&lt;p&gt;举个例子，查询一层关联关系年龄大于 50 岁的用户，如果一层关联有 1,000 人，年龄大于 50 岁的只有 2 个人。介于 JanusGraph 查询请求发送到 HBase 时做不了一层关联顶点属性的过滤，我们不得不通过并发请求去查询 HBase 获取这 1,000 人的顶点属性，再在 JanusGraph Server 的内存做过滤，最后返回给客户端满足条件的 2 个用户。&lt;/p&gt;

&lt;p&gt;这样做的问题就是磁盘 IO、网络 IO 浪费很大，而且查询返回的大多数据在而后查的查询并未用到。我们生产环境用的 HBase 为 19 台高配 SSD 服务器的，具体的网络 IO、磁盘 IO 使用情况如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/hbase-network-io.jpg" title="" alt="Hbase 网络IO"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/hbase-hardware-io.jpeg" title="" alt="Hbase 磁盘IO"&gt;&lt;/p&gt;

&lt;p&gt;我们对比相同的业务场景，但是只有 6 台相同配置的 SSD 服务器 &lt;a href="https://0x7.me/3602github" rel="nofollow" target="_blank" title=""&gt;Nebua Graph&lt;/a&gt; 的磁盘 IO 和网络 IO 情况如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-network-io.jpg" title="" alt="NebulaGraph 网络IO"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/nebula-graph-hardware-io.jpg" title="" alt="NebulaGraph 磁盘IO"&gt;&lt;/p&gt;

&lt;p&gt;Nebula Graph 性能确实优秀太多，而且是在机器资源只有之前 Hbase 集群 30% 的情况下。我们再来看下业务场景下的耗时情况，之前业务场景中查询耗时需要 2~3s 情况的在 Nebula Graph 这边 100ms 左右返回了，之前需要 10~20s 情况的业务场景现在也基本在 2s 就能返回，并且平均耗时也基本在 500ms 左右就能搞定，性能提升至少 20 倍以上 :)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/cat-detective.jpg" title="" alt="cat 耗时监控"&gt;&lt;/p&gt;

&lt;p&gt;就冲上面的这些数据，如果你还在用 JanusGraph，就应该立马把这篇文章转发给你的领导，并立个项开始迁移到 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebua Graph&lt;/a&gt; 👏&lt;/p&gt;
&lt;h2 id="历史数据迁移"&gt;历史数据迁移&lt;/h2&gt;
&lt;p&gt;数据迁移这块，因为我们的数据量比较大，20 亿左右的顶点，200 亿左右的边，好在 Nebula Graph 提供 Spark 导入工具——&lt;a href="https://github.com/vesoft-inc/nebula-java/tree/master/tools/exchange" rel="nofollow" target="_blank" title=""&gt;Spark Writer&lt;/a&gt;，整个数据导入过程还算比较流畅。这里有个可分享经验，当时使用 Spark 导入工具采用异步方式导入导致了不少 error，稍微改下导入方式换成同步写入就没问题了。另外一个经验是关于 Spark 的，如果导入的数据量比较大，对应的 partitions 需要设置大一点，我们就设置过 8w 个 patitions。如果你设置的 partitions 比较小，单个 partition 的数据量便会比较大，容易导致 Spark 任务 OOM Fail。&lt;/p&gt;
&lt;h2 id="查询调优"&gt;查询调优&lt;/h2&gt;
&lt;p&gt;我们现在生产环境 Nebula Graph 用的是 &lt;a href="https://github.com/vesoft-inc/nebula/releases/tag/v1.0.0" rel="nofollow" target="_blank" title=""&gt;1.0 的版本&lt;/a&gt;，生产环境上 ID 生产我们用的是 hash 函数，uuid 导入数据会很慢，后面官方也不会再支持 uuid。&lt;/p&gt;

&lt;p&gt;在我们的生产环境主要参数调优配置如下，主要是 nebula-storage 需要调优&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# The default reserved bytes for one batch operation
--rocksdb_batch_size=4096
# The default block cache size used in BlockBasedTable.
# The unit is MB. 我们生产服务器内存为128G
--rocksdb_block_cache=44024

############## rocksdb Options ##############
--rocksdb_disable_wal=true
# rocksdb DBOptions in json, each name and value of option is a string, given as "option_name":"option_value" separated by comma
--rocksdb_db_options={"max_subcompactions":"3","max_background_jobs":"3"}
# rocksdb ColumnFamilyOptions in json, each name and value of option is string, given as "option_name":"option_value" separated by comma
--rocksdb_column_family_options={"disable_auto_compactions":"false","write_buffer_size":"67108864","max_write_buffer_number":"4","max_bytes_for_level_base":"268435456"}
# rocksdb BlockBasedTableOptions in json, each name and value of option is string, given as "option_name":"option_value" separated by comma
--rocksdb_block_based_table_options={"block_size":"8192"}

--max_handlers_per_req=10
--heartbeat_interval_secs=10

# 新添加参数
--raft_rpc_timeout_ms=5000
--raft_heartbeat_interval_secs=10
--wal_ttl=14400
--max_batch_size=512
# 参数配置减小内存使用
--enable_partitioned_index_filter=true
--max_edge_returned_per_vertex=10000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linux 机器的调优主要就是把服务的 swap 关闭掉，开启后会因为磁盘 IO 影响查询性能。另外关于 minor compact 和 major compact 调优，我们生产环境是开启 minor compact 关闭 major compact。关闭 major compact 主要是因为这个操作很占磁盘 IO，并且很难通过线程数（&lt;code&gt;--rocksdb_db_options={"max_subcompactions":"3","max_background_jobs":"3"&lt;/code&gt;}）控制，后续 Nebula Graph 官方有计划优化这块。&lt;/p&gt;

&lt;p&gt;最后，来重点提下 &lt;code&gt;max_edge_returned_per_vertex&lt;/code&gt; 这个参数，能想到这个参数 Nebula Graph 不愧是图数据行业的老司机——我们之前的图查询一直受到超级节点的困扰，线上环境如果查询遇到这种关联几百万数据的超级节点能直接把 JanusGraph 的 HBase 集群查崩掉（我们生产环境出现过几次）。之前在查询 JanusGraph 的 Gremlin 语句上加各种 limit 限制都没能很好的解决这个问题，在 Nebula Graph 有了这个 &lt;code&gt;max_edge_returned_per_vertex&lt;/code&gt; 参数，数据在最底层存储层直接做了过滤，生产环境就不会再有这种超级节点的困扰，就这一点就应该给 NebulaGraph 一个 FIVE STAR！&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;本文首发于&lt;/strong&gt; &lt;a href="https://discuss.nebula-graph.com.cn/" rel="nofollow" target="_blank" title=""&gt;Nebula Graph 论坛&lt;/a&gt;，&lt;strong&gt;阅读本文的你有任何疑问，欢迎前往论坛和作者进行讨论，原帖传送门&lt;/strong&gt;：&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1172" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/1172&lt;/a&gt; &lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 09 Sep 2020 09:57:11 +0800</pubDate>
      <link>https://ruby-china.org/topics/40393</link>
      <guid>https://ruby-china.org/topics/40393</guid>
    </item>
    <item>
      <title>用 NetworkX + Gephi + Nebula Graph 分析&lt;权力的游戏&gt;人物关系（下篇）</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/game-of-thrones-01.png" title="" alt="权力的游戏"&gt;&lt;/p&gt;

&lt;p&gt;在&lt;a href="https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;上一篇&lt;/a&gt;[1] 中，我们通过 NetworkX 和 Gephi 展示了&amp;lt;权力的游戏&amp;gt;中的人物关系。在本篇中，我们将展示如何通过 NetworkX 访问图数据库 &lt;a href="https://0x7.me/gt2github2" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id="NetworkX"&gt;NetworkX&lt;/h3&gt;
&lt;p&gt;NetworkX [2] 是一个用 Python 语言开发的图论与复杂网络建模工具，内置了大量常用的图与复杂网络分析算法，可以方便地进行复杂网络数据分析、仿真建模等工作，功能丰富，简单易用。&lt;/p&gt;

&lt;p&gt;在 NetworkX 中，图是由顶点、边和可选的属性构成的数据结构。顶点表示数据，边是由两个顶点唯一确定的，表示两个顶点之间的关系。顶点和边也可以拥有更多的属性，以存储更多的信息。&lt;/p&gt;

&lt;p&gt;NetworkX 支持 4 种类型的图：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Graph：无向图&lt;/li&gt;
&lt;li&gt;DiGraph: 有向图&lt;/li&gt;
&lt;li&gt;MultiGraph: 多重无向图&lt;/li&gt;
&lt;li&gt;MultiDiGraph: 多重有向图&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在 NetworkX 中创建一个无向图：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;networkx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;
&lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加顶点：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_nodes_from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Tom&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加边：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edges_from&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1996&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上一篇文章（一）中，我们已经演示了 NetworkX 的 Girvan-Newman 社区发现算法。&lt;/p&gt;
&lt;h3 id="图数据库 Nebula Graph"&gt;图数据库 Nebula Graph&lt;/h3&gt;
&lt;p&gt;NetworkX 通常使用本地文件作为数据源，这在静态网络研究的时候没什么问题，但如果图网络经常会发生变化——例如某些中心节点已经不存在 (Fig.1) 或者引入了重要的网络拓扑变化 (Fig.2)——每次生成全新的静态文件再加载分析就有些麻烦，最好整个变化过程可以持久化在一个数据库中，并且可以实时地直接从数据库中加载子图或者全图做分析。本文选用 Nebula Graph [3] 作为存储图数据的图数据库。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/tv-game-thrones.png" title="" alt="权力的游戏"&gt;
Fig. 1&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/tv-game-thrones-02.png" title="" alt="权力的游戏"&gt;
Fig. 2&lt;/p&gt;

&lt;p&gt;Nebula Graph 提供了两种方式来获取图结构：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;编写一个查询语句，拉取一个子图；&lt;/li&gt;
&lt;li&gt;全量扫描底层存储，获取一个完整的全图。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第一种方式适合在一个大规模的图网络中通过精细的过滤和剪枝条件来获取符合需求的若干个点和边。第二种方式更适合于全图的分析，这通常是在项目前期对全图进行一些启发式探索，当有进一步认知后再用第一种方式做精细的剪枝分析。&lt;/p&gt;

&lt;p&gt;分析完 Nebula Graph 两种获取图结构方式后，下面来查看 Nebula Graph 的 Python 客户端代码，nebula-python/nebula/ngStorage/StorageClient.py 与 nebula-python/nebula/ngMeta/MetaClient.py 就是和底层存储交互的 API, 里面有扫描点、扫描边、读取一堆属性等等一系列丰富的接口。&lt;/p&gt;

&lt;p&gt;下面两个接口可以用来读取所有的点、边数据：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_vertex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1) 初始化一个客户端，和一个 scan_edge_processor。scan_edge_processor 用来对读出来的边数据进行解码：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;meta_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MetaClient&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;192.168.8.16&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;45500&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;span class="n"&gt;meta_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;storage_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StorageClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;scan_edge_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ScanEdgeProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meta_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2) 初始化 scan_edge 接口的各项参数：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;space_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nba&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;# 要读取的图空间名称
&lt;/span&gt;&lt;span class="n"&gt;return_cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;# 要返回的边（或点）及其属性列
&lt;/span&gt;&lt;span class="n"&gt;return_cols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;serve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start_year&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;end_year&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;return_cols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;follow&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;degree&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;allCols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt; &lt;span class="c1"&gt;# 是否返回所有属性列，当该值为 False 时，仅返回在 returnCols 里指定的属性列，当为 True 时，返回所有属性列
&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="c1"&gt;# 最多返回的数据条数
&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
&lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxsize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3) 调用 scan_part_edge 接口，该接口会返回一个 scan_edge_response 对象的迭代器：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scan_edge_response_iterator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;space_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4) 不断读取该迭代器所指向的 scan_edge_response 对象中的数据，直到读取完所有数据：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;scan_edge_response_iterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_next&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;scan_edge_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scan_edge_response_iterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scan_edge_response&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error occurs while scaning edge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="nf"&gt;process_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scan_edge_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，process_edge 是自定义的一个处理读出来边数据的函数，该函数可以先使用 scan_edge_processor 对 scan_edge_response 中的数据进行解码，解码后的数据可以直接打印出来，也可以做一些简单处理，另作他用，比如：将这些数据读入计算框架 NetworkX 里。&lt;/p&gt;

&lt;p&gt;5) 处理数据。在这里我们将读出来的所有边都添加到 NetworkX 中的图 G 里：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scan_edge_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scan_edge_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scan_edge_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Get the corresponding rows by edge_name
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;edge_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_rows&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;edge_rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;srcId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;dstId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%d -&amp;gt; %d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;srcId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dstId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prop&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;prop_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;prop_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prop_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prop_value&lt;/span&gt;
            &lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edges_from&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="n"&gt;srcId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dstId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="c1"&gt;# 添加边到 NetworkX 中的图G
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;读取顶点数据的方法和上面的流程类似。&lt;/p&gt;

&lt;p&gt;此外，对于分布式的一些图计算框架 [4] 来说，Nebula Graph 还提供了根据分片 (partition) 并发地批量读取存储的功能，这会在之后的文章中演示。&lt;/p&gt;
&lt;h3 id="在 NetworkX 中进行图分析"&gt;在 NetworkX 中进行图分析&lt;/h3&gt;
&lt;p&gt;当我们把所有点和边数据都按照上述流程读入 NetworkX 后，我们还可以做一些基本的图分析和图计算：&lt;/p&gt;

&lt;p&gt;1) 绘制图：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;with_labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font_weight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bold&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;savefig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./test.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;绘制出来的图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/game-of-thrones-networkx.png" title="" alt="NetworkX 绘制的图"&gt;&lt;/p&gt;

&lt;p&gt;2) 打印出图中的所有点和边：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nodes: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;edges: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出的结果：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;119&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;139&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;149&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;209&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;219&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;208&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;228&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;147&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;217&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;227&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;146&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;206&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;216&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;131&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;211&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;221&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;112&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;132&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;142&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;222&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;135&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;225&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;219&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;222&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;119&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;119&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;119&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;216&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;221&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;229&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;139&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;139&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;139&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;149&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;149&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;219&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;209&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;219&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;219&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;112&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;219&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;147&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;206&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;222&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;131&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;221&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;225&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;227&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;208&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;208&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;208&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;208&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;228&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;146&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;228&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;217&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;142&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;222&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;227&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;147&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;147&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;147&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;217&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;217&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;217&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;217&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;216&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;216&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;146&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;146&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;146&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;146&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;222&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;146&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;206&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;216&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;216&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;112&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;226&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;131&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;131&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;225&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;211&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;221&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;221&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;142&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;135&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;112&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;132&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;225&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;222&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;135&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;213&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3) 常见的，可以计算两个点之间的最短路径：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;p1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shortest_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;211&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;顶点 114 到顶点 211 的最短路径: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出的结果：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;顶点 114 到顶点 211 的最短路径:  &lt;span class="o"&gt;[&lt;/span&gt;114, 127, 208, 124, 211]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4) 也计算图中每个点的 PageRank 值，来看各自的影响力：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pagerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出的结果：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;109: 0.011507076520104863, 119: 0.007835838669313514, 129: 0.015304593799331218, 139: 0.007772926737873626, 149: 0.0073896601012629825, 209: 0.0065558926178649985, 219: 0.014100908598251508, 229: 0.011454115940170253, 108: 0.01645334474680034, 118: 0.01010598371500564, 128: 0.01594717876199238, 138: 0.01671097227127263, 148: 0.015898676579503977, 208: 0.009437234075904938, 218: 0.0153795416919104, 228: 0.005900393773635255, 107: 0.009745182763645681, 117: 0.008716335675518244, 127: 0.021565565312365507, 137: 0.011642680498867146, 147: 0.009721031073465738, 207: 0.01040504770909835, 217: 0.012054472529765329, 227: 0.005615576255373405, 106: 0.007371191843767635, 116: 0.020955704443679106, 126: 0.007589432032220849, 136: 0.015987209357117116, 146: 0.013922108926721374, 206: 0.008554794629575304, 216: 0.011219193251536395, 226: 0.013613173390725904, 101: 0.016680863106330837, 111: 0.010121524312495604, 121: 0.017545503989576015, 131: 0.008531567756846938, 141: 0.014598319866130227, 201: 0.0058643663430632525, 211: 0.003936285336338021, 221: 0.009587911774927793, 100: 0.02243017302167168, 110: 0.007928429795381916, 120: 0.011875669801396205, 130: 0.0073896601012629825, 140: 0.01205992633948699, 150: 0.010045605782606326, 200: 0.015289870550944322, 210: 0.017716629501785937, 220: 0.008666577509181518, 102: 0.014865431161046641, 112: 0.007931095811770324, 122: 0.008087439927630492, 132: 0.004659566123187912, 142: 0.006487446038191551, 202: 0.013579313206377282, 212: 0.01190888044566142, 222: 0.011376739416933006, 103: 0.013438110749144392, 113: 0.02458154500563397, 123: 0.01104978432213578, 133: 0.00743370900670294, 143: 0.008011123394996112, 203: 0.006883198710237787, 213: 0.020392557117890422, 223: 0.012345866520333572, 104: 0.024902235588979776, 114: 0.019369722463816744, 124: 0.017165705442951484, 134: 0.008284361176173354, 144: 0.019363506469972095, 204: 0.03507634139024834, 214: 0.015500649025348538, 224: 0.008320315540621754, 105: 0.01439975542831122, 115: 0.007592722237637133, 125: 0.010808523955754608, 135: 0.006883198710237788, 145: 0.014654713389044883, 205: 0.014660118545887803, 215: 0.01337467974572934, 225: 0.009909720748343093&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此外，也可以和上一篇中一样，接入 Gephi [5] 来得到更好的图可视化效果。&lt;/p&gt;

&lt;p&gt;本文的代码可以参见 [6].&lt;/p&gt;
&lt;h3 id="Reference"&gt;Reference&lt;/h3&gt;
&lt;p&gt;[1] &lt;a href="https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/" rel="nofollow" target="_blank" title=""&gt;https://nebula-graph.com.cn/posts/game-of-thrones-relationship-networkx-gephi-nebula-graph/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://networkx.github.io/" rel="nofollow" target="_blank" title=""&gt;https://networkx.github.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://0x7.me/gt2github2" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://spark.apache.org/graphx/" rel="nofollow" target="_blank" title=""&gt;https://spark.apache.org/graphx/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://gephi.org/" rel="nofollow" target="_blank" title=""&gt;https://gephi.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] &lt;a href="https://github.com/vesoft-inc/nebula-python/pull/31" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula-python/pull/31&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;喜欢这篇文章？来来来，给我们的 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt; 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]&lt;/p&gt;

&lt;p&gt;交流图数据库技术？交个朋友，Nebula Graph 官方小助手微信：&lt;a href="https://www-cdn.nebula-graph.com.cn/nebula-blog/nbot.png" rel="nofollow" target="_blank" title=""&gt;NebulaGraphbot&lt;/a&gt; 拉你进交流群~~&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;作者有话说：Hi，我是王杰，是图数据 Nebula Graph 研发工程师，希望本次的经验分享能给大家带来帮助，如有不当之处也希望能帮忙纠正，谢谢~&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 02 Sep 2020 16:53:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/40357</link>
      <guid>https://ruby-china.org/topics/40357</guid>
    </item>
    <item>
      <title>图数据库对比：Neo4j vs Nebula Graph vs HugeGraph</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/performance-comparison.png" title="" alt="性能测试对比"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文系腾讯云安全团队李航宇、邓昶博撰写&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;图数据库在挖掘黑灰团伙以及建立安全知识图谱等安全领域有着天然的优势。为了能更好的服务业务，选择一款高效并且贴合业务发展的图数据库就变得尤为关键。本文挑选了几款业界较为流行的开源图数据库与 &lt;a href="https://0x7.me/tsecurity2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 进行了多角度的对比。&lt;/p&gt;
&lt;h2 id="图数据库介绍"&gt;图数据库介绍&lt;/h2&gt;&lt;h3 id="Neo4j"&gt;Neo4j&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://neo4j.com/" rel="nofollow" target="_blank" title=""&gt;Neo4j&lt;/a&gt; 是目前业界广泛使用的图数据库，包含社区版本和商用版本，本文中使用社区版本。&lt;/p&gt;
&lt;h3 id="HugeGraph"&gt;HugeGraph&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/hugegraph/hugegraph" rel="nofollow" target="_blank" title=""&gt;HugeGraph&lt;/a&gt; 是百度基于 &lt;a href="https://janusgraph.org/" rel="nofollow" target="_blank" title=""&gt;JanusGraph&lt;/a&gt; 改进而来的分布式图数据库，主要应用场景是解决百度安全事业部所面对的反欺诈、威胁情报、黑产打击等业务的图数据存储和图建模分析需求。具有良好的读写性能。&lt;/p&gt;
&lt;h3 id="Nebula Graph"&gt;Nebula Graph&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://0x7.me/tsecurity2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 是一款开源的分布式图数据库，采用 shared-nothing 分布式架构，擅长处理千亿节点万亿条边的超大规模数据集，从而更好地服务企业级应用。&lt;/p&gt;
&lt;h2 id="测试硬件环境"&gt;测试硬件环境&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/hardware-environments.png" title="" alt="硬件测试环境"&gt;&lt;/p&gt;
&lt;h2 id="性能对比"&gt;性能对比&lt;/h2&gt;
&lt;p&gt;我们使用不同量级的图从入库时间，一度好友查询，二度好友查询，共同好友查询几个方面进行了对比，结果如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/test-results.jpeg" title="" alt="测试结果"&gt;&lt;/p&gt;

&lt;p&gt;可以看到在导入性能上，数据量小的时候 Nebula Graph 的导入效率稍慢于 Neo4j，但在大数据量的时候 Nebula Graph 的导入明显优于其他两款图数据库；在 3 种查询场景下，Nebula Graph 的效率都明显高于 Neo4j，与 HugeGraph 相比也有一定的优势。&lt;/p&gt;
&lt;h2 id="查询语言对比"&gt;查询语言对比&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/query-language-comparsion.jpeg" title="" alt="查询语言对比"&gt;&lt;/p&gt;

&lt;p&gt;从查询语句的角度出发，Gremlin 比较复杂，nGQL 和 Cypher 比较简练，从可读性角度出发，nGQL 比较类 SQL 化，比较符合大家的使用习惯。&lt;/p&gt;
&lt;h2 id="可视化对比"&gt;可视化对比&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/visualization-comparsion.png" title="" alt="查询语言对比"&gt;&lt;/p&gt;

&lt;p&gt;在可视化方面，所有的平台都还只处于可用状态，Nebula Graph 的选择性扩展在团伙挖掘中是一个加分项，但是在二度结果展示流畅度，展示结果自定义展示方面还有优化空间。&lt;/p&gt;

&lt;p&gt;在比较了多款业内主要使用的开源数据库后，我们从性能，学习成本和与业务的贴合程度多个角度考虑，最终选择了性能出众，上手简单，能大幅提高业务效率的 Nebula Graph 图数据库。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;本文首发于&lt;/strong&gt; &lt;a href="https://discuss.nebula-graph.com.cn/" rel="nofollow" target="_blank" title=""&gt;Nebula Graph 论坛&lt;/a&gt;，&lt;strong&gt;阅读本文的你有任何疑问，欢迎前往论坛和作者进行讨论，原帖传送门&lt;/strong&gt;：&lt;a href="https://discuss.nebula-graph.com.cn/t/topic/1013" rel="nofollow" target="_blank" title=""&gt;https://discuss.nebula-graph.com.cn/t/topic/1013&lt;/a&gt; &lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Thu, 27 Aug 2020 16:14:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/40335</link>
      <guid>https://ruby-china.org/topics/40335</guid>
    </item>
    <item>
      <title>用 NetworkX + Gephi + Nebula Graph 分析&lt;权力的游戏&gt;人物关系（上篇）</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/game-of-thrones-01.png" title="" alt="权力的游戏"&gt;&lt;/p&gt;

&lt;p&gt;我们都知道《权利的游戏》在全世界都很多忠实的粉丝，除去你永远不知道剧情下一秒谁会挂这种意外“惊喜”，当中复杂交错的人物关系也是它火爆的原因之一，而本文介绍如何通过 &lt;a href="https://networkx.github.io/" rel="nofollow" target="_blank" title=""&gt;NetworkX&lt;/a&gt; 访问开源的分布式图数据库 &lt;a href="https://0x7.me/game12github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt;，并借助可视化工具—— &lt;a href="https://gephi.org/" rel="nofollow" target="_blank" title=""&gt;Gephi&lt;/a&gt; 来可视化分析《权力的游戏》中的复杂的人物图谱关系。&lt;/p&gt;
&lt;h2 id="数据集"&gt;数据集&lt;/h2&gt;
&lt;p&gt;本文的数据集来源：冰与火之歌第一卷 (至第五卷)[1]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;人物集 (点集）：书中每个角色建模为一个点，点只有一个属性：姓名&lt;/li&gt;
&lt;li&gt;关系集（边集）：如果两个角色在书中发生过直接或间接的交互，则有一条边；边只有一个属性：权重，权重的大小代表交互的强弱。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样的点集和边集构成一个图网络，这个网络存储在图数据库 &lt;a href="https://0x7.me/game12github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; [2] 中。&lt;/p&gt;
&lt;h2 id="社区划分——Girvan-Newman 算法"&gt;社区划分——Girvan-Newman 算法&lt;/h2&gt;
&lt;p&gt;我们使用 NetworkX [3] 内置的社区发现算法 Girvan-Newman 来为我们的图网络划分社区。&lt;/p&gt;

&lt;p&gt;以下为「社区发现算法 Girvan-Newman」解释：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;网络图中，连接较为紧密的部分可以被看成一个社区。每个社区内部节点之间有较为紧密的连接，而在两个社区间连接则较为稀疏。社区发现就是找到给定网络图所包含的一个个社区的过程。&lt;/p&gt;

&lt;p&gt;Girvan-Newman 算法即是一种基于&lt;a href="https://zh.wikipedia.org/wiki/%E4%BB%8B%E6%95%B0%E4%B8%AD%E5%BF%83%E6%80%A7" rel="nofollow" target="_blank" title=""&gt;介数&lt;/a&gt;的社区发现算法，其基本思想是根据边介数中心性（edge betweenness）从大到小的顺序不断地将边从网络中移除直到整个网络分解为各个社区。因此，Girvan-Newman 算法实际上是一种分裂方法。&lt;/p&gt;

&lt;p&gt;Girvan-Newman 算法的基本流程如下：
（1）计算网络中所有边的边介数；
（2）找到边介数最高的边并将它从网络中移除；
（3）重复步骤 2，直到每个节点成为一个独立的社区为止，即网络中没有边存在。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;概念解释完毕，下面来实操下。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;使用 Girvan-Newman 算法划分社区。NetworkX 示例代码如下&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;comp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;networkx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;community&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;girvan_newman&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="n"&gt;limited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;takewhile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;communities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limited&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;为图中每个点添加一个 community 属性，该属性值记录该点所在的社区编号&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;community_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;community_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;community&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;communities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;community&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;community_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;community_num&lt;/span&gt;
        &lt;span class="n"&gt;community_num&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_node_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;community_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;community&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="节点样式——Betweenness Centrality 算法"&gt;节点样式——Betweenness Centrality 算法&lt;/h2&gt;
&lt;p&gt;下面我们来调整下节点大小及节点上标注的角色姓名大小，我们使用 NetworkX 的 Betweenness Centrality 算法来决定节点大小及节点上标注的角色姓名的大小。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;图中各个节点的重要性可以通过节点的中心性（Centrality）来衡量。在不同的网络中往往采用了不同的中心性定义来描述网络中节点的重要性。Betweenness Centrality 根据有多少最短路径经过该节点，来判断一个节点的重要性。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;计算每个节点的介数中心性的值&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;betweenness_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;betweenness_centrality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Run betweenness centrality
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;为图中每个点再添加一个 betweenness 属性&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_node_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;betweenness_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;betweenness&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="边的粗细"&gt;边的粗细&lt;/h2&gt;
&lt;p&gt;边的粗细直接由边的权重属性来决定。&lt;/p&gt;

&lt;p&gt;通过上面的处理，现在，我们的节点拥有 name、community、betweenness 三个属性，边只有一个权重 weight 属性。&lt;/p&gt;

&lt;p&gt;下面显示一下：&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;
&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;color_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;red&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;blue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yellow&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;purple&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;black&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pink&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;community&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;communities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spring_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nodelist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;community&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;color_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;savefig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./game.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;emmm，有点丑…&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/networkx.png" title="" alt="NetworkX 可视化"&gt;&lt;/p&gt;

&lt;p&gt;虽然 NetworkX 本身有不少可视化功能，但 Gephi [4] 的交互和可视化效果更好。&lt;/p&gt;
&lt;h2 id="接入可视化工具 Gephi"&gt;接入可视化工具 Gephi&lt;/h2&gt;
&lt;p&gt;现在将上面的 NetworkX 数据导出为 game.gephi 文件，并导入 Gephi。&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_gexf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;game.gexf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/gephi-01.jpeg" title="" alt="Gephi 界面"&gt;&lt;/p&gt;
&lt;h2 id="Gephi 可视化效果展示"&gt;Gephi 可视化效果展示&lt;/h2&gt;
&lt;p&gt;在 Gephi 中打开刚才导出的 &lt;code&gt;game.gephi&lt;/code&gt; 文件，然后微调 Gephi 中的各项参数，就以得到一张满意的可视化：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;将布局设置为 Force Atlas, 斥力强度改为为 500.0，勾选上 &lt;code&gt;由尺寸调整&lt;/code&gt;&amp;nbsp;选项可以尽量避免节点重叠：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Force Atlas 为力引导布局，力引导布局方法能够产生相当优美的网络布局，并充分展现网络的整体结构及其自同构特征。力引导布局即模仿物理世界的引力和斥力，自动布局直到力平衡。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/gephi-02.png" title="" alt="Gephi 界面"&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;给划分好的各个社区网络画上不同的颜色：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在外观 - 节点 - 颜色-Partition 中选择 community（这里的 community 就是我们刚才为每个点添加的社区编号属性）&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/gephi-03.png" title="" alt="Gephi 界面"&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;决定节点及节点上标注的角色姓名的大小：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在外观 - 节点 - 大小-Ranking 中选择 betweenness（这里的 betweenness 就是我们刚才为每个点添加的 betweenness 属性)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/gephi-04.png" title="" alt="Gephi 界面"&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;边的粗细由边的权重属性来决定：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在外观 - 边-大小-Ranking 中选择边的权重&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/gephi-05.png" title="" alt="Gephi 界面"&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;导出图片再加个头像效果&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/game-of-thrones-02.png" title="" alt="权力的游戏"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/game-of-thrones-03.png" title="" alt="权力的游戏"&gt;&lt;/p&gt;

&lt;p&gt;大功告成，一张权力游戏的关系谱图上线 :) 每个节点可以看到对应的人物信息。&lt;/p&gt;
&lt;h2 id="下一篇"&gt;下一篇&lt;/h2&gt;
&lt;p&gt;本篇主要介绍如何使用 NetworkX，并通过 Gephi 做可视化展示。下一篇将介绍如何通过 NetworkX 访问图数据库 &lt;a href="https://0x7.me/game12github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 中的数据。&lt;/p&gt;

&lt;p&gt;本文的代码可以访问 [5]。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;致谢：本文受工作 [6] 的启发&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="Reference"&gt;Reference&lt;/h2&gt;
&lt;p&gt;[1] &lt;a href="https://www.kaggle.com/mmmarchetti/game-of-thrones-dataset" rel="nofollow" target="_blank" title=""&gt;https://www.kaggle.com/mmmarchetti/game-of-thrones-dataset&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://0x7.me/game12github" rel="nofollow" target="_blank" title=""&gt;https://github.com/vesoft-inc/nebula&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://networkx.github.io/" rel="nofollow" target="_blank" title=""&gt;https://networkx.github.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://gephi.org/" rel="nofollow" target="_blank" title=""&gt;https://gephi.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://github.com/jievince/nx2gephi" rel="nofollow" target="_blank" title=""&gt;https://github.com/jievince/nx2gephi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] &lt;a href="https://www.lyonwj.com/2016/06/26/graph-of-thrones-neo4j-social-network-analysis/" rel="nofollow" target="_blank" title=""&gt;https://www.lyonwj.com/2016/06/26/graph-of-thrones-neo4j-social-network-analysis/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;作者有话说：Hi，我是王杰，是图数据 Nebula Graph 研发工程师，希望本次的经验分享能给大家带来帮助，如有不当之处也希望能帮忙纠正，谢谢~&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 19 Aug 2020 15:02:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/40305</link>
      <guid>https://ruby-china.org/topics/40305</guid>
    </item>
    <item>
      <title>新手阅读 Nebula Graph 源码的姿势</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;摘要：在本文中，我们将通过数据流快速学习 Nebula Graph，以用户在客户端输入一条 nGQL 语句 &lt;code&gt;SHOW SPACES&lt;/code&gt; 为例，使用 GDB 追踪语句输入时 Nebula Graph 是怎么调用和运行的。&lt;/p&gt;

&lt;p&gt;首发于 Nebula Graph 博客：&lt;a href="https://nebula-graph.com.cn/posts/how-to-read-nebula-graph-source-code/" rel="nofollow" target="_blank"&gt;https://nebula-graph.com.cn/posts/how-to-read-nebula-graph-source-code/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/source-code.png" title="" alt="阅读源码"&gt;&lt;/p&gt;
&lt;h2 id="导读"&gt;导读&lt;/h2&gt;
&lt;p&gt;对于一些刚开始接触开源的分布式图数据库 &lt;a href="https://0x7.me/sourcecode2github" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 开源库的小伙伴来说，刚开始可能和我一样，想要提高自己，看看大神们的代码然后试着能够做点什么，或许能够修复一个看起来并不是那么困难的 Bug。但是面对如此多的代码，我裂开了，不知道如何下手。最后硬着头皮，再看了一遍又一遍代码，跑了一个又一个用例之后终于有点眉目了。&lt;/p&gt;

&lt;p&gt;下面就分享下个人学习 Nebula Graph 开源代码的过程，也希望刚接触 Nebula Graph 的小伙伴能够少走弯路，快速入门。另外 Nebula Graph 本身也用到了一些开源库，详情可以见附录。&lt;/p&gt;

&lt;p&gt;在本文中，我们将通过数据流快速学习 Nebula Graph，以用户在客户端输入一条 nGQL 语句 &lt;code&gt;SHOW SPACES&lt;/code&gt; 为例，使用 GDB 追踪语句输入时 Nebula Graph 是怎么调用和运行的。&lt;/p&gt;
&lt;h2 id="整体架构"&gt;整体架构&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/architecture.png" title="" alt="整体架构"&gt;&lt;/p&gt;

&lt;p&gt;一个完整的 Nebula Graph 包含三个服务，即 Query Service，Storage Service 和 Meta Service。每个服务都有其各自的可执行二进制文件。&lt;/p&gt;

&lt;p&gt;Query Service 主要负责&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;客户端连接的管理&lt;/li&gt;
&lt;li&gt;解析来自客户端的 nGQL 语句为抽象语法树 AST，并将抽象树 AST 解析成一系列执行动作。&lt;/li&gt;
&lt;li&gt;对执行动作进行优化&lt;/li&gt;
&lt;li&gt;执行优化后的执行计划 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Storage Service 主要负责 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;数据的分布式存储&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meta Service 主要负责&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;图 schema 的增删查改&lt;/li&gt;
&lt;li&gt;集群的管理&lt;/li&gt;
&lt;li&gt;用户鉴权&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这次，我们主要对 Query Service 进行分析&lt;/p&gt;
&lt;h2 id="目录结构"&gt;目录结构&lt;/h2&gt;
&lt;p&gt;刚开始，可以拿到一个 source 包，解压，可以先看看代码的层级关系，不同的包主要功能是干什么的 下面只列出 src 目录：&lt;/p&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;|--src
    |--client // 客户端代码
    |--common // 提供一些常用的基础组件
    |--console
    |--daemons
    |--dataman
    |--graph // 包含了Query Service的大部分代码                         
    |--interface // 主要是一些 meta、storage 和 graph 的通讯接口定义     
    |--jni
    |--kvstore
    |--meta // 元数据管理相关 
    |--parser // 主要负责词法和语法分析       
    |--storage // 存储层相关
    |--tools
    |--webservice
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="代码跟踪"&gt;代码跟踪&lt;/h2&gt;
&lt;p&gt;通过 scripts 目录下的脚本启动 metad 和 storaged 这两个服务：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/console.png" title="" alt="阅读源码"&gt;&lt;/p&gt;

&lt;p&gt;启动后通过 &lt;code&gt;nebula.service status all&lt;/code&gt; 查看当前的服务状态&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/service-status.png" title="" alt="阅读源码"&gt;&lt;/p&gt;

&lt;p&gt;然后 gdb 运行 bin 目录下的 &lt;code&gt;nebula-graphd&lt;/code&gt; 二进制程序  &lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;flagfile&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mingquan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ji&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nebula&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nebula&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;graphd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;   &lt;span class="c1"&gt;//设置函数入参&lt;/span&gt;
&lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;follow&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;   &lt;span class="c1"&gt;// 由于是守护进程，所以在 fork 子进程后 gdb 继续跟踪子进程&lt;/span&gt;
&lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;         &lt;span class="c1"&gt;// 在 mian 入口打断点&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 gdb 中输入 &lt;code&gt;run&lt;/code&gt; 开始运行 &lt;code&gt;nebula-graphd&lt;/code&gt; 程序，然后通过 &lt;code&gt;next&lt;/code&gt; 可以一步一步运行，直到遇到 &lt;code&gt;gServer-&amp;gt;serve();  // Blocking wait until shut down via gServer-&amp;gt;stop()&lt;/code&gt;，此时 &lt;code&gt;nebula-graphd&lt;/code&gt; 的所有线程阻塞，等待客户端连接，这时需要找到客户端发起请求后由哪个函数处理。&lt;/p&gt;

&lt;p&gt;由于 Nebula Graph 使用 FBThrift 来定义生成不同服务的通讯代码，在 &lt;code&gt;src/interface/graph.thrift&lt;/code&gt; 文件中可以看到 GraphService 接口的定义如下：&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="n"&gt;GraphService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AuthResponse&lt;/span&gt; &lt;span class="n"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;oneway&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;signout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i64&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ExecutionResponse&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i64&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;gServer-&amp;gt;serve()&lt;/code&gt; 之前有&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;interface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_shared&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ioThreadPool&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;gServer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setInterface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;gServer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FLAGS_port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以知道是由 &lt;code&gt;GraphService&lt;/code&gt; 对象来处理客户端的连接和请求，因此可以在 &lt;code&gt;GraphService.cpp:&lt;/code&gt;&lt;code&gt;future_execute&lt;/code&gt; 处打断点，以便跟踪后续处理流程。&lt;/p&gt;

&lt;p&gt;此时重新打开一个终端进入 nebula 安装目录，通过 &lt;code&gt;./nebule -u=root -p=nebula&lt;/code&gt; 来连接 nebula 服务，再在客户端输入 &lt;code&gt;SHOW SPACES&lt;/code&gt;&amp;nbsp;，此时客户端没有反应，是因为服务端还在阻塞调试中，回到服务端输入 continue，如下所示：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/show-spaces.png" title="" alt="阅读源码"&gt;&lt;/p&gt;

&lt;p&gt;经过 &lt;code&gt;session&lt;/code&gt; 验证后，进入 &lt;code&gt;executionEngine-&amp;gt;execute()&lt;/code&gt; 中，&lt;code&gt;step&lt;/code&gt; 进入函数内部&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExecutionPlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ectx&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续 &lt;code&gt;step&lt;/code&gt; 进入&lt;code&gt;ExecutionPlan&lt;/code&gt; 的 &lt;code&gt;execute&lt;/code&gt; 函数内部，然后执行到&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GQLParser&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rctx&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;parse&lt;/code&gt; 这块主要使用 &lt;code&gt;flex &amp;amp; bison&lt;/code&gt;，用于词法分析和语法解析构造对象到抽象语法树，其词法文件是 &lt;strong&gt;src/parser/scanner.lex&lt;/strong&gt;，语法文件是 &lt;strong&gt;src/parser/parser.yy&lt;/strong&gt;，其词法分析类似于正则表达式，语法分析举例如下：&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;go_sentence&lt;/span&gt;
    &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KW_GO&lt;/span&gt; &lt;span class="n"&gt;step_clause&lt;/span&gt; &lt;span class="n"&gt;from_clause&lt;/span&gt; &lt;span class="n"&gt;over_clause&lt;/span&gt; &lt;span class="n"&gt;where_clause&lt;/span&gt; &lt;span class="n"&gt;yield_clause&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;GoSentence&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setStepClause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setFromClause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setOverClause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setWhereClause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;YieldColumns&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isOverAll&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EdgeDstIdExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;YieldColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;YieldClause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setYieldClause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其在匹配到对应到 go 语句时，就构造对应的节点，然后由 bison 处理，最后生成一个抽象的语法树。&lt;/p&gt;

&lt;p&gt;词法语法分析后开始执行模块，继续 &lt;code&gt;gdb&lt;/code&gt;，进入 &lt;code&gt;excute&lt;/code&gt; 函数，一直 &lt;code&gt;step&lt;/code&gt; 直到进入&lt;code&gt;ShowExecutor::execute&lt;/code&gt; 函数。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/ShowExecutor-execute.png" title="" alt="阅读源码"&gt;&lt;/p&gt;

&lt;p&gt;继续 &lt;code&gt;next&lt;/code&gt; 直到 &lt;code&gt;showSpaces()&lt;/code&gt;，&lt;code&gt;step&lt;/code&gt; 进入此函数&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ectx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getMetaClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;listSpaces&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ectx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rctx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;'''&lt;/span&gt;
&lt;span class="err"&gt;'''&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;via&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;thenValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;thenError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 Query Service 通过 metaClient 和 Meta Service 通信拿到 &lt;code&gt;spaces&lt;/code&gt; 数据，之后通过回调函数 &lt;code&gt;cb&lt;/code&gt; 回传拿到的数据，至此 nGQL 语句 &lt;code&gt;SHOW SPACES;&lt;/code&gt; 已经执行完毕，而其他复杂的语句也可以以此类推。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果是正在运行的服务，可以先查出该服务的进程 ID，然后通过 gdb attach PID 来调试该进程；&lt;/li&gt;
&lt;li&gt;如果不想启动服务端和客户端进行调试，在 src 目录下的每个文件夹下都有一个 test 目录，里面都是对对应模块或者功能进行的单元测试，可以直接编译对应的单元模块，然后跟踪运行。方法如下:

&lt;ol&gt;
&lt;li&gt;通过对应目录下的 CMakeLists.txt 文件找到对应的模块名&lt;/li&gt;
&lt;li&gt;在 build 目录下 make 模块名，在 build/bin/test 目录下生成对应的二进制程序&lt;/li&gt;
&lt;li&gt;gdb 跟踪调试该程序&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="附录"&gt;附录&lt;/h2&gt;
&lt;p&gt;阅读 Nebula Graph 源码需要了解的一些库：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://pandolia.net/tinyc/ch1_overview.html" rel="nofollow" target="_blank" title=""&gt;flex &amp;amp; bison&lt;/a&gt;：词法分析和语法分析工具，将客户端输入的 nGQL 语句解析为抽象语法树&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/facebook/fbthrift#thrift-files" rel="nofollow" target="_blank" title=""&gt;FBThrift&lt;/a&gt;：Facebook 开源的 RPC 框架，定义并生成了 Meta 层、Storage 层和 Graph 层的通讯过程代码&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/facebook/folly" rel="nofollow" target="_blank" title=""&gt;folly&lt;/a&gt;：Facebook 开源的 C++14 组件库，提供了类似 Boost 和 std 库的功能，在性能上更加优化&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/google/googletest" rel="nofollow" target="_blank" title=""&gt;Gtest&lt;/a&gt;：Google 开源的 C++ 单元测试框架&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其中数据库资料可以参考：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.infoq.cn/article/0rSVq2VIfUE0YLedLe5o" rel="nofollow" target="_blank" title=""&gt;数据库基本介绍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.aliyun.com/document_detail/144293.html?spm=a2c4g.11186623.6.641.285e5892rCL4iP" rel="nofollow" target="_blank" title=""&gt;SQL 调优&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nebula-graph.com.cn/posts/nebula-graph-architecture-overview/" rel="nofollow" target="_blank" title=""&gt;Nebula 架构剖析系列（零）图数据库的整体架构设计&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;喜欢这篇文章？来来来，给我们的 &lt;a href="https://0x7.me/sourcecode2github" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt; 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]&lt;/p&gt;

&lt;p&gt;交流图数据库技术？交个朋友，Nebula Graph 官方小助手微信：&lt;a href="https://www-cdn.nebula-graph.com.cn/nebula-blog/nbot.png" rel="nofollow" target="_blank" title=""&gt;NebulaGraphbot&lt;/a&gt; 拉你进交流群~~&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;作者有话说：Hi，我是明泉，是图数据 Nebula Graph 研发工程师，主要工作和数据库查询引擎相关，希望本次的经验分享能给大家带来帮助，如有不当之处也希望能帮忙纠正，谢谢~&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Wed, 05 Aug 2020 10:44:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/40233</link>
      <guid>https://ruby-china.org/topics/40233</guid>
    </item>
    <item>
      <title>关系型数据库查询语言 SQL 和图数据库查询语言 nGQL 对比</title>
      <description>&lt;p&gt;摘要：这篇文章将介绍图数据库 Nebula Graph 的查询语言 nGQL 和 SQL 的区别。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文首发于 Nebula Graph 官方博客：&lt;a href="https://nebula-graph.com.cn/posts/sql-vs-ngql-comparison/" rel="nofollow" target="_blank"&gt;https://nebula-graph.com.cn/posts/sql-vs-ngql-comparison/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/sql-vs-ngql.png" title="" alt="sql-vs-ngql"&gt;&lt;/p&gt;

&lt;p&gt;虽然本文主要介绍 nGQL 和 SQL 的区别，但是我们不会深入探讨这两种语言，而是将这两种语言做对比，以帮助你从 SQL 过渡到 nGQL。&lt;/p&gt;

&lt;p&gt;SQL (Structured Query Language) 是具有数据操纵和数据定义等多种功能的数据库语言，这种语言是一种&lt;a href="https://zh.wikipedia.org/wiki/%E7%89%B9%E5%AE%9A%E7%9B%AE%E7%9A%84%E7%A8%8B%E5%BC%8F%E8%AF%AD%E8%A8%80" rel="nofollow" target="_blank" title=""&gt;特定目的编程语言&lt;/a&gt;，用于管理&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%B3%E7%B3%BB%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F" rel="nofollow" target="_blank" title=""&gt;关系数据库管理系统&lt;/a&gt;（RDBMS），或在&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%B3%E7%B3%BB%E6%B5%81%E6%95%B0%E6%8D%AE%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F" rel="nofollow" target="_blank" title=""&gt;关系流数据管理系统&lt;/a&gt;（RDSMS）中进行流处理。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.nebula-graph.com.cn/manual-CN/1.overview/1.concepts/2.nGQL-overview/" rel="nofollow" target="_blank" title=""&gt;nGQL&lt;/a&gt; 是一种类 SQL 的声明型的文本查询语言，相比于 SQL，nGQL 为可扩展、支持图遍历、模式匹配、分布式事务（开发中）的图数据库查询语言。&lt;/p&gt;
&lt;h2 id="概念对比"&gt;概念对比&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://oscimg.oschina.net/oscnet/up-7ef881414b83887e96108aa2bcc3f1b1b98.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="语法对比"&gt;语法对比&lt;/h2&gt;&lt;h3 id="数据定义语言 (DDL)"&gt;数据定义语言 (DDL)&lt;/h3&gt;
&lt;p&gt;数据定义语言（DDL）用于创建或修改数据库的结构，也就是 schema。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://oscimg.oschina.net/oscnet/up-04ade22494de57cf85be5ac0e5eccf3f8d7.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h4 id="索引"&gt;索引&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://oscimg.oschina.net/oscnet/up-274320b9af10bc14691dfbfc5722a716677.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="数据操作语言（DML）"&gt;数据操作语言（DML）&lt;/h3&gt;
&lt;p&gt;数据操作语言（DML）用于操作数据库中的数据。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://oscimg.oschina.net/oscnet/up-ee4d04156645a5cc1822216cf7bcd1685a3.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="数据查询语言（DQL）"&gt;数据查询语言（DQL）&lt;/h3&gt;
&lt;p&gt;数据查询语言（DQL）语句用于执行数据查询。本节说明如何使用 SQL 语句和 nGQL 语句查询数据。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="n"&gt;select_expr&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="n"&gt;select_expr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;table_references&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;where_condition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;col_name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;HAVING&lt;/span&gt;  &lt;span class="n"&gt;where_condition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;col_name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;ASC&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GO&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;STEPS&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;node_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;edge_type_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;REVERSELY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;BIDIRECT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;where_condition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;return_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;ASC&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;offset_value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number_rows&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;col_name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;col_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;node_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;vid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;vid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;
   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;edge_type_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="n"&gt;edge_type&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="n"&gt;edge_type&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;return_list&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;col_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;col_alias&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;col_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;col_alias&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="数据控制语言（DCL）"&gt;数据控制语言（DCL）&lt;/h3&gt;
&lt;p&gt;数据控制语言（DCL）包含诸如 &lt;code&gt;GRANT&lt;/code&gt; 和 &lt;code&gt;REVOKE&lt;/code&gt; 之类的命令，这些命令主要用来处理数据库系统的权限、其他控件。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://oscimg.oschina.net/oscnet/up-0cb596567e9e4191dea2a6454fbedcb5433.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="数据模型"&gt;数据模型&lt;/h2&gt;
&lt;p&gt;查询语句基于以下数据模型：&lt;/p&gt;
&lt;h3 id="RDBMS 关系结构图"&gt;RDBMS 关系结构图&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/RDBMS.png" title="" alt="RDBMS"&gt;&lt;/p&gt;
&lt;h3 id="Nebula Graph 最小模型图"&gt;Nebula Graph 最小模型图&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/Studio.png" title="" alt="Studio"&gt;&lt;/p&gt;

&lt;p&gt;本文将使用 NBA 数据集。该数据集包含两种类型的点，也就是两个标签，即 &lt;code&gt;player&lt;/code&gt; 和 &lt;code&gt;team&lt;/code&gt; ；两种类型的边，分别是 &lt;code&gt;serve&lt;/code&gt; 和 &lt;code&gt;follow&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在关系型数据管理系统中（RDBMS）中，我们用表来表示点以及与点相关的边（连接表）。因此，我们创建了以下表格：&lt;code&gt;player&lt;/code&gt;、&lt;code&gt;team&lt;/code&gt;、&lt;code&gt;serve&lt;/code&gt; 和 &lt;code&gt;follow&lt;/code&gt;。在 &lt;strong&gt;Nebula Graph&lt;/strong&gt; 中，基本数据单位是顶点和边。两者都可以拥有属性，相当于 RDBMS 中的属性。&lt;/p&gt;

&lt;p&gt;在 &lt;strong&gt;Nebula Graph&lt;/strong&gt; 中，点之间的关系由边表示。每条边都有一种类型，在 NBA 数据集中，我们使用边类型 &lt;code&gt;serve&lt;/code&gt; 和 &lt;code&gt;follow&lt;/code&gt; 来区分两种类型的边。&lt;/p&gt;
&lt;h2 id="示例数据"&gt;示例数据&lt;/h2&gt;&lt;h3 id="在 RDBMS 插入数据"&gt;在 RDBMS 插入数据&lt;/h3&gt;
&lt;p&gt;首先，让我们看看如何在 RDBMS 中插入数据。我们先创建一些表，然后为这些表插入数据。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;team_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_year&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_year&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;follow&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_id1&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_id2&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;degree&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后插入数据。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Tim Duncan'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Tony Parker'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'LaMarcus Aldridge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rudy Gay'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Marco Belinelli'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Danny Green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Kyle Anderson'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Aron Baynes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Boris Diaw'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Tiago Splitter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Cory Joseph'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Warriors'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Nuggets'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Rockets'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Trail'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Spurs'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;205&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Thunders'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;206&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Jazz'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Clippers'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;208&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Kings'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2016&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2005&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2011&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2009&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2018&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2006&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2007&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2016&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2011&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2007&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2009&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;follow&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="在 Nebula Graph 插入数据"&gt;在 Nebula Graph 插入数据&lt;/h3&gt;
&lt;p&gt;在 &lt;strong&gt;Nebula Graph&lt;/strong&gt; 中插入数据与上述类似。首先，我们需要定义好数据结构，也就是创建好 schema。然后可以选择手动或使用 &lt;a href="https://github.com/vesoft-inc/nebula-web-docker" rel="nofollow" target="_blank" title=""&gt;Nebula Graph Studio&lt;/a&gt; （Nebula Graph 的可视化工具）导入数据。这里我们手动添加数据。&lt;/p&gt;

&lt;p&gt;在下方的 &lt;code&gt;INSERT&lt;/code&gt; 插入语句中，我们向图空间 NBA 插入了球员数据（这和在 MySQL 中插入数据类似）。&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;VERTEX&lt;/span&gt; &lt;span class="nf"&gt;player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;VALUES&lt;/span&gt;
&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Tim&lt;/span&gt; &lt;span class="n"&gt;Duncan&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Tony&lt;/span&gt; &lt;span class="n"&gt;Parker&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;LaMarcus&lt;/span&gt; &lt;span class="n"&gt;Aldridge&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Rudy&lt;/span&gt; &lt;span class="n"&gt;Gay&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Marco&lt;/span&gt; &lt;span class="n"&gt;Belinelli&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Danny&lt;/span&gt; &lt;span class="n"&gt;Green&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Kyle&lt;/span&gt; &lt;span class="n"&gt;Anderson&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Aron&lt;/span&gt; &lt;span class="n"&gt;Baynes&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Boris&lt;/span&gt; &lt;span class="n"&gt;Diaw&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Tiago&lt;/span&gt; &lt;span class="n"&gt;Splitter&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Cory&lt;/span&gt; &lt;span class="n"&gt;Joseph&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;考虑到篇幅限制，此处我们将跳过插入球队和边的重复步骤。你可以点击&lt;a href="https://oss-cdn.nebula-graph.io/doc/example_data.zip" rel="nofollow" target="_blank" title=""&gt;此处&lt;/a&gt;下载示例数据亲自尝试。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://nebula-website-cn.oss-cn-hangzhou.aliyuncs.com/nebula-blog/Nebula-Graph-Studio.png" title="" alt="Nebula-Graph-Studio"&gt;&lt;/p&gt;
&lt;h2 id="增删改查（CRUD）"&gt;增删改查（CRUD）&lt;/h2&gt;
&lt;p&gt;本节介绍如何使用 SQL 和 nGQL 语句创建（C）、读取（R）、更新（U）和删除（D）数据。&lt;/p&gt;
&lt;h3 id="插入数据"&gt;插入数据&lt;/h3&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Tim Duncan'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;nebula&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;VERTEX&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Tim Duncan'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="查询数据"&gt;查询数据&lt;/h3&gt;
&lt;p&gt;查找 ID 为 100 的球员并返回其 &lt;code&gt;name&lt;/code&gt; 属性：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;nebula&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="n"&gt;PROP&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="更新数据"&gt;更新数据&lt;/h3&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tim'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;nebula&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;VERTEX&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"Tim"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="删除数据"&gt;删除数据&lt;/h3&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tim'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;nebula&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;VERTEX&lt;/span&gt; &lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;nebula&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;EDGE&lt;/span&gt; &lt;span class="n"&gt;follow&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="建立索引"&gt;建立索引&lt;/h2&gt;
&lt;p&gt;返回年龄超过 36 岁的球员。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 nGQL 查询有些不同，因为您必须在过滤属性之前创建索引。更多信息请参见 &lt;a href="https://www.yuque.com/nebulagraph/2.query-language/4.statement-syntax/1.data-definition-statements/index.md" rel="nofollow" target="_blank" title=""&gt;索引文档&lt;/a&gt;。&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="n"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;player_age&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="nf"&gt;player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;REBUILD&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="n"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;player_age&lt;/span&gt; &lt;span class="n"&gt;OFFLINE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;LOOKUP&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="示例查询"&gt;示例查询&lt;/h2&gt;
&lt;p&gt;本节提供一些示例查询供您参考。&lt;/p&gt;
&lt;h3 id="示例 1"&gt;示例 1&lt;/h3&gt;
&lt;p&gt;在表 &lt;code&gt;player&lt;/code&gt; 中查询 ID 为 100 的球员并返回其 &lt;code&gt;name&lt;/code&gt; 属性。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来使用 &lt;strong&gt;Nebula Graph&lt;/strong&gt; 查找 ID 为 100 的球员并返回其 &lt;code&gt;name&lt;/code&gt; 属性。&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;FETCH&lt;/span&gt; &lt;span class="n"&gt;PROP&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Nebula Graph&lt;/strong&gt; 使用 &lt;code&gt;FETCH&lt;/code&gt; 关键字获取特定点或边的属性。本例中，属性即为点 100 的名称。nGQL 中的 &lt;code&gt;YIELD&lt;/code&gt; 关键字相当于 SQL 中的 &lt;code&gt;SELECT&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="示例 2"&gt;示例 2&lt;/h3&gt;
&lt;p&gt;查找球员 Tim Duncan 并返回他效力的所有球队。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;team_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tim Duncan'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用如下 nGQL 语句完成相同操作：&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="n"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;player_name&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="nf"&gt;player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;REBUILD&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="n"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;player_name&lt;/span&gt; &lt;span class="n"&gt;OFFLINE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;LOOKUP&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Tim&lt;/span&gt; &lt;span class="n"&gt;Duncan&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;GO&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VertexID&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里需要注意一下，在 nGQL 中的等于操作采用的是 C 语言风格的 &lt;code&gt;==&lt;/code&gt;，而不是 SQL 风格的 &lt;code&gt;=&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="示例 3"&gt;示例 3&lt;/h3&gt;
&lt;p&gt;以下查询略复杂，现在我们来查询球员 Tim Duncan 的队友。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;team_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;team_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Tim Duncan'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;nGQL 则使用管道将前一个子句的结果作为下一个子句的输入。&lt;/p&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;GO&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_dst&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;Team&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;GO&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Team&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="n"&gt;REVERSELY&lt;/span&gt; &lt;span class="n"&gt;YIELD&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您可能已经注意到了，我们仅在 SQL 中使用了 &lt;code&gt;JOIN&lt;/code&gt;。这是因为 &lt;strong&gt;Nebula Graph&lt;/strong&gt; 只是使用类似 Shell 的管道对子查询进行嵌套，这样更符合我们的阅读习惯也更简洁。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;
&lt;p&gt;我们建议您亲自尝试上述查询语句，这将帮您更好地理解 SQL 和 nGQL，并节省您上手 nGQL 的学习时间。以下是一些参考资料：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vesoft-inc/nebula-web-docker/blob/master/docs/nebula-graph-studio-user-guide-en.md" rel="nofollow" target="_blank" title=""&gt;Nebula Graph Studio 用户指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula Graph GitHub 仓库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.nebula-graph.io/manual-EN/1.overview/2.quick-start/1.get-started/" rel="nofollow" target="_blank" title=""&gt;Nebula Graph 快速入门文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;作者有话说：Hi，Hi，大家好，我是 Amber，Nebula Graph 的文档工程师，希望上述内容可以给大家带来些许启发。限于水平，如有不当之处还请斧正，在此感谢^^&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;喜欢这篇文章？来来来，给我们的 &lt;a href="https://0x7.me/sql2github" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt; 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]&lt;/p&gt;

&lt;p&gt;交流图数据库技术？交个朋友，Nebula Graph 官方小助手微信：&lt;a href="https://www-cdn.nebula-graph.com.cn/nebula-blog/nbot.png" rel="nofollow" target="_blank" title=""&gt;NebulaGraphbot&lt;/a&gt; 拉你进交流群~~&lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Thu, 23 Jul 2020 10:54:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/40188</link>
      <guid>https://ruby-china.org/topics/40188</guid>
    </item>
    <item>
      <title>浅析图数据库 Nebula Graph 数据导入工具——Spark Writer</title>
      <description>&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/spark-writer.png" title="" alt="Spark Writer"&gt;&lt;/p&gt;
&lt;h2 id="从 Hadoop 说起"&gt;从 Hadoop 说起&lt;/h2&gt;
&lt;p&gt;近年来随着大数据的兴起，分布式计算引擎层出不穷。&lt;a href="https://hadoop.apache.org/" rel="nofollow" target="_blank" title=""&gt;Hadoop&lt;/a&gt; 是 Apache 开源组织的一个分布式计算开源框架，在很多大型网站上都已经得到了应用。Hadoop 的设计核心思想来源于 Google MapReduce 论文，灵感来自于函数式语言中的 map 和 reduce 方法。在函数式语言中，map 表示针对列表中每个元素应用一个方法，reduce 表示针对列表中的元素做迭代计算。通过 MapReduce 算法，可以将数据根据某些特征进行分类规约，处理并得到最终的结果。&lt;/p&gt;
&lt;h2 id="再谈 Apache Spark"&gt;再谈 Apache Spark&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://spark.apache.org/" rel="nofollow" target="_blank" title=""&gt;Apache Spark&lt;/a&gt; 是一个围绕速度、易用性构建的通用内存并行计算框架。在 2009 年由加州大学伯克利分校 AMP 实验室开发，并于 2010 年成为 Apache 基金会的开源项目。Spark 借鉴了 Hadoop 的设计思想，继承了其分布式并行计算的优点，提供了丰富的算子。&lt;/p&gt;

&lt;p&gt;Spark 提供了一个全面、统一的框架用于管理各种有着不同类型数据源的大数据处理需求，支持批量数据处理与流式数据处理。Spark 支持内存计算，性能相比起 Hadoop 有着巨大提升。Spark 支持 Java，Scala 和 Python 三种语言进行编程，支持以操作本地集合的方式操作分布式数据集，并且支持交互查询。除了经典的 MapReduce 操作之外，Spark 还支持 SQL 查询、流式处理、机器学习和图计算。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/spark-stack.png" title="" alt="Spark Stack"&gt;&lt;/p&gt;

&lt;p&gt;弹性分布式数据集（RDD，Resilient Distributed Dataset）是 Spark 最基本的抽象，代表不可变的分区数据集。RDD 具有可容错和位置感知调度的特点。操作 RDD 就如同操作本地数据集合，而不必关心任务调度与容错等问题。RDD 允许用户在执行多个查询时，显示地将工作集合缓存在内存中，后续查询能够重用该数据集。RDD 通过一系列的转换就就形成了 DAG，根据 RDD 之间的依赖关系的不同将 DAG 划分成不同的 Stage。&lt;/p&gt;

&lt;p&gt;与 RDD 相似，DataFrame 也是一个不可变分布式数据集合。区别于 RDD，DataFrame 中的数据被组织到有名字的列中，就如同关系型数据库中的表。设计 DataFrame 的目的就是要让对大型数据集的处理变得更简单，允许开发者为分布式数据集指定一个模式，便于进行更高层次的抽象。&lt;/p&gt;

&lt;p&gt;DataSet 是一个支持强类型的特定领域对象，这种对象可以函数式或者关系操作并行地转换。DataSet 就是一些有明确类型定义的 JVM 对象的集合，可以通过 Scala 中定义的 Case Class 或者 Java 中的 Class 来指定。DataFrame 是 Row 类型的 Dataset，即 Dataset[Row]。DataSet 的 API 是强类型的；而且可以利用这些模式进行优化。&lt;/p&gt;

&lt;p&gt;DataFrame 与 DataSet 只在执行行动操作时触发计算。本质上，数据集表示一个逻辑计划，该计划描述了产生数据所需的计算。当执行行动操作时，Spark 的查询优化程序优化逻辑计划，并生成一个高效的并行和分布式物理计划。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://www-cdn.nebula-graph.com.cn/nebula-blog/spark-apis.png" title="" alt="Spark API"&gt;&lt;/p&gt;
&lt;h2 id="基于 Spark 的数据导入工具"&gt;基于 Spark 的数据导入工具&lt;/h2&gt;
&lt;p&gt;Spark Writer 是 &lt;a href="https://github.com/vesoft-inc/nebula" rel="nofollow" target="_blank" title=""&gt;Nebula Graph&lt;/a&gt; 基于 Spark 的分布式数据导入工具，基于 DataFrame 实现，能够将多种数据源中的数据转化为图的点和边批量导入到图数据库中。&lt;/p&gt;

&lt;p&gt;目前支持的数据源有：Hive 和 HDFS。&lt;/p&gt;

&lt;p&gt;Spark Writer 支持同时导入多个标签与边类型，不同标签与边类型可以配置不同的数据源。&lt;/p&gt;

&lt;p&gt;Spark Writer 通过配置文件，从数据中生成一条插入语句，发送给查询服务，执行插入操作。Spark Writer 中插入操作使用异步执行，通过 Spark 中累加器统计成功与失败数量。&lt;/p&gt;
&lt;h2 id="获取 Spark Writer"&gt;获取 Spark Writer&lt;/h2&gt;&lt;h3 id="编译源码"&gt;编译源码&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vesoft-inc/nebula.git  
&lt;span class="nb"&gt;cd &lt;/span&gt;nebula/src/tools/spark-sstfile-generator  
mvn compile package
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="标签数据文件格式"&gt;标签数据文件格式&lt;/h4&gt;
&lt;p&gt;标签数据文件由一行一行的数据组成，文件中每一行表示一个点和它的属性。一般来说，第一列为点的 ID ——此列的名称将在后文的映射文件中指定，其他列为点的属性。例如 Play 标签数据文件格式：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:100,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"Tim Duncan"&lt;/span&gt;,&lt;span class="s2"&gt;"age"&lt;/span&gt;:42&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:101,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"Tony Parker"&lt;/span&gt;,&lt;span class="s2"&gt;"age"&lt;/span&gt;:36&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:102,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"LaMarcus Aldridge"&lt;/span&gt;,&lt;span class="s2"&gt;"age"&lt;/span&gt;:33&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="边类型数据文件格式"&gt;边类型数据文件格式&lt;/h4&gt;
&lt;p&gt;边类型数据文件由一行一行的数据组成，文件中每一行表示一条边和它的属性。一般来说，第一列为起点 ID，第二列为终点 ID，起点 ID 列及终点 ID 列会在映射文件中指定。其他列为边属性。下面以 JSON 格式为例进行说明。&lt;/p&gt;

&lt;p&gt;以边类型 follow 数据为例：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;:100,&lt;span class="s2"&gt;"target"&lt;/span&gt;:101,&lt;span class="s2"&gt;"likeness"&lt;/span&gt;:95&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;:101,&lt;span class="s2"&gt;"target"&lt;/span&gt;:100,&lt;span class="s2"&gt;"likeness"&lt;/span&gt;:95&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;:101,&lt;span class="s2"&gt;"target"&lt;/span&gt;:102,&lt;span class="s2"&gt;"likeness"&lt;/span&gt;:90&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;:100,&lt;span class="s2"&gt;"target"&lt;/span&gt;:101,&lt;span class="s2"&gt;"likeness"&lt;/span&gt;:95,&lt;span class="s2"&gt;"ranking"&lt;/span&gt;:2&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;:101,&lt;span class="s2"&gt;"target"&lt;/span&gt;:100,&lt;span class="s2"&gt;"likeness"&lt;/span&gt;:95,&lt;span class="s2"&gt;"ranking"&lt;/span&gt;:1&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"source"&lt;/span&gt;:101,&lt;span class="s2"&gt;"target"&lt;/span&gt;:102,&lt;span class="s2"&gt;"likeness"&lt;/span&gt;:90,&lt;span class="s2"&gt;"ranking"&lt;/span&gt;:3&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="配置文件格式"&gt;配置文件格式&lt;/h4&gt;
&lt;p&gt;Spark Writer 使用 HOCON 配置文件格式。HOCON（Human-Optimized Config Object Notation）是一个易于使用的配置文件格式，具有面向对象风格。配置文件由 Spark 配置段，Nebula 配置段，以及标签配置段和边配置段四部分组成。&lt;/p&gt;

&lt;p&gt;Spark 信息配置了 Spark 运行的相关参数，Nebula 相关信息配置了连接 Nebula 的用户名和密码等信息。tags 映射和 edges 映射分别对应多个 tag/edge 的输入源映射，描述每个 tag/edge 的数据源等基本信息，不同 tag/edge 可以来自不同数据源。&lt;/p&gt;

&lt;p&gt;Nebula 配置段主要用于描述 nebula 查询服务地址、用户名和密码、图空间信息等信息。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nebula:&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;查询引擎&amp;nbsp;IP&amp;nbsp;列表&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;addresses:&amp;nbsp;[&lt;span class="s2"&gt;"127.0.0.1:3699"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;连接&amp;nbsp;Nebula&amp;nbsp;Graph&amp;nbsp;服务的用户名和密码&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;user:&amp;nbsp;user&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;pswd:&amp;nbsp;password&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;Nebula&amp;nbsp;Graph&amp;nbsp;图空间名称&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;space:&amp;nbsp;test&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;thrift&amp;nbsp;超时时长及重试次数，默认值分别为&amp;nbsp;3000&amp;nbsp;和&amp;nbsp;3&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;connection&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout:&amp;nbsp;3000&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retry:&amp;nbsp;3&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;nGQL&amp;nbsp;查询重试次数，默认值为&amp;nbsp;3&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;execution&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retry:&amp;nbsp;3&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;

&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="Nebula 配置段"&gt;Nebula 配置段&lt;/h4&gt;
&lt;p&gt;标签配置段用于描述导入标签信息，数组中每个元素为一个标签信息。标签导入主要分为两种：基于文件导入与基于 Hive 导入。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;基于文件导入配置需指定文件类型&lt;/li&gt;
&lt;li&gt;基于 Hive 导入配置需指定执行的查询语言。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#&amp;nbsp;处理标签&amp;nbsp;&amp;nbsp;&lt;/span&gt;
tags:&amp;nbsp;[&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;从&amp;nbsp;HDFS&amp;nbsp;文件加载数据，&amp;nbsp;此处数据类型为&amp;nbsp;Parquet&amp;nbsp;tag&amp;nbsp;名称为&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;&amp;nbsp;HDFS&amp;nbsp;Parquet&amp;nbsp;文件的中的&amp;nbsp;field_0、field_1将写入&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;节点列为&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY_FIELD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;parquet&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HDFS_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fields:&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;field_0:&amp;nbsp;nebula_field_0,&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;field_1:&amp;nbsp;nebula_field_1&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vertex:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY_FIELD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch&amp;nbsp;:&amp;nbsp;16&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;与上述类似&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;从&amp;nbsp;Hive&amp;nbsp;加载将执行命令&amp;nbsp;&lt;span class="nv"&gt;$&amp;nbsp;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;EXEC&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;作为数据集&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;hive&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exec:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXEC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fields:&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hive_field_0:&amp;nbsp;nebula_field_0,&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hive_field_1:&amp;nbsp;nebula_field_1&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;vertex:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KEY_FIELD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&lt;span class="o"&gt;]&lt;/span&gt;&amp;nbsp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name 字段用于表示标签名称&lt;/li&gt;
&lt;li&gt;fields 字段用于配置 HDFS 或 Hive 字段与 Nebula 字段的映射关系&lt;/li&gt;
&lt;li&gt;batch 参数意为一次批量导入数据的记录数，需要根据实际情况进行配置。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;边类型配置段用于描述导入标签信息，数组中每个元素为一个边类型信息。边类型导入主要分为两种：基于文件导入与基于 Hive 导入。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;基于文件导入配置需指定文件类型&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基于 Hive 导入配置需指定执行的查询语言&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#&amp;nbsp;处理边&amp;nbsp;&amp;nbsp;&lt;/span&gt;
edges:&amp;nbsp;[&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;从&amp;nbsp;HDFS&amp;nbsp;加载数据，数据类型为&amp;nbsp;JSON&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;边名称为&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EDGE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;HDFS&amp;nbsp;JSON&amp;nbsp;文件中的&amp;nbsp;field_0、field_1&amp;nbsp;将被写入&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EDGE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;起始字段为&amp;nbsp;source_field，终止字段为&amp;nbsp;target_field&amp;nbsp;，边权重字段为&amp;nbsp;ranking_field。&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EDGE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;json&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HDFS_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fields:&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;field_0:&amp;nbsp;nebula_field_0,&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;field_1:&amp;nbsp;nebula_field_1&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;source:&amp;nbsp;&amp;nbsp;source_field&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;target:&amp;nbsp;&amp;nbsp;target_field&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ranking:&amp;nbsp;ranking_field&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;从&amp;nbsp;Hive&amp;nbsp;加载将执行命令&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXEC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;作为数据集&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;#&amp;nbsp;边权重为可选&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EDGE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;hive&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exec:&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXEC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fields:&amp;nbsp;&lt;span class="o"&gt;{&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hive_field_0:&amp;nbsp;nebula_field_0,&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hive_field_1:&amp;nbsp;nebula_field_1&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;source:&amp;nbsp;&amp;nbsp;source_id_field&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;target:&amp;nbsp;&amp;nbsp;target_id_field&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&lt;span class="o"&gt;}&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&lt;span class="o"&gt;]&lt;/span&gt;&amp;nbsp;&amp;nbsp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;name 字段用于表示边类型名称&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;fields 字段用于配置 HDFS 或 Hive 字段与 Nebula 字段的映射关系&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source 字段用于表示边的起点&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;target 字段用于表示边的终点&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ranking 字段用于表示边的权重&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;batch 参数意为一次批量导入数据的记录数，需要根据实际情况进行配置。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="导入数据命令"&gt;导入数据命令&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/spark-submit&amp;nbsp;&lt;span class="se"&gt;\&amp;nbsp;&lt;/span&gt;&amp;nbsp;
&amp;nbsp;--class&amp;nbsp;com.vesoft.nebula.tools.generator.v2.SparkClientGenerator&amp;nbsp;&lt;span class="se"&gt;\&amp;nbsp;&lt;/span&gt;&amp;nbsp;
&amp;nbsp;--master&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MASTER&lt;/span&gt;&lt;span class="p"&gt;-URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;&lt;span class="se"&gt;\&amp;nbsp;&lt;/span&gt;&amp;nbsp;
&amp;nbsp;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SPARK_WRITER_JAR_PACKAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&amp;nbsp;-c&amp;nbsp;conf/test.conf&amp;nbsp;-h&amp;nbsp;-d&amp;nbsp;&amp;nbsp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;-c：config 用于指定配置文件路径&lt;/li&gt;
&lt;li&gt;-h：hive 用于指定是否支持 Hive&lt;/li&gt;
&lt;li&gt;-d：dry 用于测试配置文件是否正确，并不处理数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;作者有话说：Hi，大家好，我是 darion，Nebula Graph 的软件工程师，对分布式系统方面有些小心得，希望上述文章可以给大家带来些许启发。限于水平，如有不当之处还请斧正，在此感谢^^&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;喜欢这篇文章？来来来，给我们的 &lt;a href="https://0x7.me/spark2github" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt; 点个 star 表鼓励啦~~ 🙇‍♂️🙇‍♀️ [手动跪谢]&lt;/p&gt;

&lt;p&gt;交流图数据库技术？交个朋友，Nebula Graph 官方小助手微信：&lt;a href="https://www-cdn.nebula-graph.com.cn/nebula-blog/nbot.png" rel="nofollow" target="_blank" title=""&gt;NebulaGraphbot&lt;/a&gt; 拉你进交流群~~&lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Fri, 17 Jul 2020 10:10:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/40161</link>
      <guid>https://ruby-china.org/topics/40161</guid>
    </item>
    <item>
      <title>暗黑模式的 Ruby China 好酷炫</title>
      <description>&lt;p&gt;一周上一次 Ruby China 的选手路过，打开发现是个黑色界面，感觉好酷炫，找了下没有找到对应的官方帖子可以进去戳个小爱心的，😂  开了个新帖&lt;/p&gt;

&lt;p&gt;暗黑模式 💯   😍  &lt;/p&gt;</description>
      <author>NebulaGraph</author>
      <pubDate>Thu, 16 Jul 2020 18:00:23 +0800</pubDate>
      <link>https://ruby-china.org/topics/40158</link>
      <guid>https://ruby-china.org/topics/40158</guid>
    </item>
  </channel>
</rss>
