转载:http://www.ibm.com/developerworks/cn/opensource/os-riak1/index.html

简介

典型的现代关系数据库在某些类型的应用程序中表现平平,难以满足如今的互联网应用程序的性能和可扩展性要求。因此,需要采用不同的方法。在过去几年中,一种新的数据存储类型变得非常流行,通常称为 NoSQL,因为它可以直接解决关系数据库的一些缺陷。Riak 就是这类数据存储类型中的一种。

Riak 并不是惟一的一种 NoSQL 数据存储。另外两种较流行的数据存储是 MongoDB 和 Cassandra。尽管在许多方面十分相似,但是它们之间也存在明显的不同。例如,Riak 是一种分布式系统,而 MongoDB 是一种单独的系统数据库,也就是说,Riak 没有主节点的概念,因此在处理故障方面有更好的弹性。尽管 Cassandra 同样是基于 Amazon 的 Dynamo 描述,但是它在组织数据方面摒弃了向量时钟和相容散列等特性。Riak 的数据模型更加灵活。在 Riak 中,在第一次访问 bucket 时会动态创建这些 bucket;Cassandra 的数据模型是在 XML 文件中定义的,因此在修改它们过后需要重启整个群集。

Riak 的另一个优势是它是用 Erlang 编写的。而 MongoDB 和 Cassandra 是用通用语言(分别为 C++和 Java)编写,因此 Erlang 从一开始就支持分布式、容错应用程序,所以更加适用于开发 NoSQL 数据存储等应用程序,这些应用程序与使用 Erlang 编写的应用程序有一些共同的特征。

Map/Reduce 作业只能使用 Erlang 或 JavaScript 编写。对于本文呢,我们选择使用 JavaScript 编写 mapreduce 函数,但是也可以用 Erlang 编写它们。虽然 Erlang 代码的执行速度可能稍快一些,然而我们选择 JavaScript 代码的理由是它的受众更广。参阅 参考资料 中的链接,详细了解 Erlang。

回页首

开始

如果您希望尝试本文中的一些示例,则需要在您的系统中安装 Riak(参阅 参考资料)和 Erlang。

您还需要构建一个包含三个节点的群集并在您的本地机器上运行它。Riak 中保存的所有数据都被复制到群集的大量节点中。数据所在的 bucket 的一个属性 (n_val) 决定了将要复制的节点的数量。该属性的默认值为 3,因此,要完成本示例,我们需要创建一个至少包含三个节点的群集(之后您可以创建任意数量的节点)。

下载了源代码后,您需要进行构建。基本步骤如下:

  1. 解压缩源代码:$ tar xzvf riak-1.0.1.tar.gz
  2. 修改目录:$ cd riak-1.0.1
  3. 构建:$ make all rel

这将构建 Riak (./rel/riak)。要在本地运行多个节点,则需要生成 ./rel/riak 的副本,对每个额外的节点使用一个副本。将 ./rel/riak 复制到 ./rel/riak2、./rel/riak3 等地方,然后对每个副本执行下面的修改:

  • 在 riakN/etc/app.config 中,修改下面的值:http{} 部分中指定的端口,handoff_port 和 pb_port,将它们修改为惟一值
  • 打开 riakN/etc/vm.args 并修改名称,同样是修改为惟一值,例如 -name riak2@127.0.0.1

现在依次启动每个节点,如 清单 1 所示。

清单 1. 清单 1. 启动每个节点

                $ cd rel
$ ./riak/bin/riak start
$ ./riak2/bin/riak start
$ ./riak3/bin/riak start 

最后,将节点连接起来形成群集,如 清单 2 所示。

清单 2. 清单 2. 形成群集

                $ ./riak2/bin/riak-admin join riak@127.0.0.1
$ ./riak3/bin/riak-admin join riak@127.0.0.1

您现在应该创建了一个在本地运行的 3 节点群集。要进行测试,运行如下命令: $ ./riak/bin/riak-admin status | grep ring_members

您应当看到,每个节点都是刚刚创建的群集的一部分,例如 ring_members : ['riak2@127.0.0.1','riak3@127.0.0.1','riak@127.0.0.1']

回页首

Riak API

目前有三种方式可以访问 Riak:HTTP API(RESTful 界面)、Protocol Buffers 和一个原生 Erlang 界面。提供多个界面使您能够选择如何集成应用程序。如果您使用 Erlang 编写应用程序,那么应当使用原生的 Erlang 界面,这样就可以将二者紧密地集成在一起。其他一些因素也会影响界面的选择,比如性能。例如,使用 Protocol Buffers 界面的客户端的性能要比使用 HTTP API 的客户端性能更高一些;从性能方面讲,数据通信量变小,解析所有这些 HTTP 标头的开销相对更高。然而,使用 HTTP API 的优点是,如今的大部分开发人员(特别是 Web 开发人员)非常熟悉 RESTful 界面,再加上大多数编程语言都有内置的原语,支持通过 HTTP 请求资源,例如,打开一个 URL,因此不需要额外的软件。在本文中,我们将重点介绍 HTTP API。

所有示例都将使用 curl 通过 HTTP 界面与 Riak 交互。这样做是为了更好地理解底层的 API。许多语言都提供了大量客户端库,在开发使用 Riak 作为数据存储的应用程序时,应当考虑使用这些客户端库。客户端库提供了与 Riak 连接的 API,可以轻松地与应用程序集成;您不必亲自编写代码来处理在使用 curl 时出现的响应。

API 支持常见的 HTTP 方法:GETPUTPOSTDELETE,它们将分别用于检索、更新、创建和删除对象。我们稍后将依次介绍每一种方法。

存储对象

您可以将 Riak 看成是创建键(字符串)与值(对象)的分布式映射。Riak 将值保存在 bucket 中。在保存对象之前,不需要显式地创建 bucket;如果将对象保存到一个不存在的 bucket 中,则会自动创建该 bucket。

Bucket 在 Riak 中是一个虚拟概念,主要是为了对相关对象分组而存在。bucket 还具有其他一些属性,这些属性的值定义了 Riak 对存储在其中的对象的处理。下面是 bucket 属性的一些示例:

  • n_val:对象在群集内进行复制的次数
  • allow_mult:是否允许并发更新

您可以通过对 bucket 发出 GET 请求查看 bucket 的属性(及其当前值)。

要存储对象,我们将对 清单 3 所示的其中一个 URL 执行 HTTP POST

清单 3. 清单 3. 存储对象

                POST -> /riak/<bucket> (1)
POST -> /riak/<bucket>/<key> (2)

键可以由 Riak (1)自动分配,或由用户 (2) 定义。

当使用用户定义的键存储对象时,也可以向 (2) 执行一个 HTTP PUT 操作来创建对象。

Riak 的最新版本还支持以下 URL 格式:/buckets/<bucket>/keys/<key>,但是在本文中,我们将使用更旧的格式来维持与早期 Riak 版本的向后兼容性。

如果没有指定键,Riak 会自动为对象分配一个键。例如,我们将在 bucket “foo” 中存储一个明文对象,并且不会显式指定键(参见 清单 4)。

清单 4. 清单 4. 在不显式指定键的情况下存储一个明文对象

                $ curl -i -H "Content-Type: plain/text" -d "Some text" \
http://localhost:8098/riak/foo/HTTP/1.1 201 Created
Vary: Accept-Encoding
Location: /riak/foo/3vbskqUuCdtLZjX5hx2JHKD2FTK
Content-Type: plain/text
Content-Length: ...

通过检查 Location 标头,您可以看到 Riak 分配给对象的键。这样做不容易记忆,因此另一种选择是让用户提供键。让我们创建一个艺术家 bucket,并添加一个叫做 Bruce 的艺术家(参见 清单 5)。

清单 5. 清单 5. 创建一个艺术家 bucket 并添加一个艺术家

$ curl -i -d '{"name":"Bruce"}' -H "Content-Type: application/json" \
http://localhost:8098/riak/artists/BruceHTTP/1.1 204 No Content
Vary: Accept-Encoding
Content-Type: application/json
Content-Length: ...

如果使用我们指定的键成功存储了对象,我们将从服务器得到一个 204 No Content 响应。

在本例中,我们将对象的值保存为 JSON,但是它既可以是明文格式,也可以是其他格式。在存储对象时,需要注意正确设置 Content-Type 标头。例如,如果希望存储一个 JPEG 图像,那么您必须将内容类型设置为 image/jpeg。

检索对象

要检索已存储的对象,使用您希望检索的对象的键对 bucket 运行 GET 方法。如果对象存在,则会在响应的正文中返回对象,否则服务器会返回 404 Object Not Found 响应(参见 清单 6)。

清单 6. 清单 6. 在 bucket 上执行一个 GET 方法

                $ curl http://localhost:8098/riak/artists/BruceHTTP/1.1 200 OK
...
{ "name" : "Bruce" }

更新对象

在更新对象时,和存储对象一样,需要用到 Content-Type 标头。例如,让我们来添加 Bruce 的别名,如 清单 7 所示。

清单 7. 清单 7. 添加 Bruce 的别名

                $ curl -i -X PUT -d '{"name":"Bruce", "nickname":"The Boss"}' \
-H "Content-Type: application/json" http://localhost:8098/riak/artists/Bruce

如前所述,Riak 自动创建了 bucket。这些 bucket 拥有一些属性,其中一个属性为 allow_mult,用于确定是否允许执行并发写操作。默认情况下,该属性被设置为 false;但是,如果允许进行并发更新,则需要向每个更新发送 X-Riak-Vclock 标头。应该将该标头的值设置为与客户端最后一次读取对象时看到的值相同。

Riak 使用向量时钟 (vector clock) 判断修改对象的原因。向量时钟的工作原理超出了本文的讨论范围,但是,在允许执行并发写操作时,可能会出现冲突,这时需要使用应用程序来解决这些冲突(参阅 参考资料)。

删除对象

删除对象的操作使用了一个与前面的命令类似的模式,我们只需要对希望删除的对象所对应的 URL 执行一个 HTTP DELETE 方法: $ curl -i -X DELETE http://localhost:8098/riak/artists/Bruce

如果成功删除对象,我们会从服务器获得一个 204 No Content 响应;如果试图删除的对象不存在,那么服务器会返回一个 404 Object Not Found 响应。

回页首

链接

目前为止,我们已经了解了如何通过将对象与特定键相关联来存储对象,稍后可以使用此特定键来检索对象。如果能够将这个简单的模型进行扩展以表示对象如何(以及是否)与其他对象相关,那么这会非常有用。我们当然可以实现这一点,并且 Riak 是使用链接实现的。

那么,什么是链接?链接允许用户创建对象之间的关系。如果熟悉 UML 类图的话,您可以将链接看作是对象之间的某种关联,并用一个书签说明这种关系;在关系数据库中,该关系被表示为一个外键。

通过 “Link” 标头,以将链接 “依附” 到对象上。下面演示了链接标头看起来是什么样子。例如,关系的目标(即我们准备进行链接的对象)是尖括号中的内容。关系内容(本例中为 “performer”)是通过 riaktag 属性来表示的:Link: </riak/artists/Bruce>; riaktag="performer"

现在让我们添加一些专辑,并将它们与专辑的表演者艺术家 Bruce 关联起来(参见 清单 8)。

清单 8. 清单 8. 添加一些专辑

                $ curl -H "Content-Type: text/plain" \
-H 'Link: </riak/artists/Bruce> riaktag="performer"' \
-d "The River" http://localhost:8098/riak/albums/TheRiver$ curl -H "Content-Type: text/plain" \
-H 'Link: </riak/artists/Bruce> riaktag="performer"' \
-d "Born To Run" http://localhost:8098/riak/albums/BornToRun

现在我们已经设置了一些关系,接下来要通过 link walking 查询它们,link walking 是一个用于查询对象关系的进程。例如,要查找表演 River 专辑的艺术家,您应当这样做:$ curl -i http://localhost:8098/riak/albums/TheRiver/artists,performer,1

末尾的位是链接说明。链接查询的外观就是这个样子。第一个部分(artists)指定我们应当执行查询的 bucket。第二个部分(performer)指定了我们希望用于限制结果的标签,最后的 1 部分表示我们希望包含这个查询阶段的结果。

还可以发出过渡性查询。假设我们在专辑和艺术家之间建立了关系,如 图 1 所示。

图 1. 图 1. 专辑和艺术家之间的关系

通过执行下面的命令,可以发出 “哪些艺术家与表演 The River 专辑的艺术家合作过” 之类的查询:$ curl -i http://localhost:8098/riak/albums/TheRiver/artists,_,0/artists,collaborator,1。链接说明中的下划线的作用类似于通配符,表示我们不关心具体的关系是什么。

回页首

运行 Map/Reduce 查询

Map/Reduce 是一个由 Google 推广的框架,用于在大型数据集上同时运行分布式计算。Riak 还提供 Map/Reduce 支持,它允许对群集中的数据运行功能更强大的查询。

Map/Reduce 函数包括一个 map 阶段和一个 reduce 阶段。map 阶段应用于某些数据并生成 0 个或多个结果;这在编程中类似于通过列表中的每一项映射函数。map 阶段是并行发生的。reduce 阶段将获取 map 阶段的所有结果,并将它们组合起来。

例如,计算某个单词在大量文档中出现的次数。每个 map 阶段都将计算每个单词在特定文档中出现的次数。这些中间计数在计算完后将发送到 reduce 函数,然后计算总数并得出在所有文档中的次数。参见 参考资料,获得有关 Google 的 Map/Reduce 文章的链接。

回页首

示例:分布式 grep

对于本文,我们将开发一个 Map/Reduce 函数,该函数将对 Riak 中存储的一组文档执行一次分布式 grep。和 grep 一样,最终的输出是一些匹配所提供模式的行。此外,每个结果还将表示文档中出现匹配时所在位置的行号。

要执行一个 Map/Reduce 查询,我们将对 /mapred 资源执行 POST 操作。请求的内容是查询的 JSON 表示;和前面的例子一样,必须提供 Content-Type 标头,并且始终将其设置为 application/json。清单 9 显示了我们为执行分布式 grep 而做的查询。后面将依次讨论查询的每一个部分。

清单 9. 清单 9. 示例 Map/Reduce 查询

                {"inputs": [["documents","s1"],["documents","s2"]],"query": [{ "map": { "language": "javascript", "name": "GrepUtils.map", "keep": true, "arg": "[s|S]herlock" } },{ "reduce": { "language": "javascript", "name": "GrepUtils.reduce" } }]
}

每个查询都包含若干输入,例如,我们希望对之执行计算的文档,在 map 和 reduce 阶段运行的函数的名称。也可以直接在查询中包含 mapreduce 函数的源代码,只需要使用源属性替代名称即可,但是我在本例中没有这样做;然而,要使用指定的函数,则需要对 Riak 的默认配置进行一些修改。将清单 9 中的代码保存到某个目录中。对于群集中的每个节点,找到文件 etc/app.config,打开它并将属性 property js_source_dir 设置为您用于保存代码的目录。您需要重启群集中的所有节点使变更生效。

清单 10 中的代码包含将在 map 和 reduce 阶段执行的函数。map 函数将查看文档的每一行,确定是否与提供的模式(arg 参数)匹配。本例中的 reduce 函数并不会执行太多操作;它类似于一个恒等函数,仅仅用于返回输入。

清单 10. 清单 10. GrepUtils.js

                var GrepUtils = {       map: function (v, k, arg) {var i, len, lines, r = [], re = new RegExp(arg);lines = v.values[0].data.split(/\r?\n/);  for (i = 0, len = lines.length; i < len; i += 1) {var match = re.exec(lines[i]);if (match) {r.push((i+1) + “. “ + lines[i]);}}return r;}, reduce: function (v) {return [v];}
};

在运行查询之前,我们需要一些数据。我从 Project Gutenberg Web 站点下载了 Sherlock Holmes 电子图书(参见 参考资料)。第一个文本存储在键 “s1” 下的 “documents” bucket 中;第二个文本位于同一个 bucket 中,键为 “s2”。

清单 11 展示了如何将这类文档上传到 Riak。

清单 11. 清单 11. 将文档上传到 Riak

                $ curl -i -X POST http://localhost:8098/riak/documents/s1 \
-H “Content-Type: text/plain” --data-binary @s1.txt

上传文档后,我们现在可以对文档执行搜索。在本例中,我们想输出匹配常规表达式 "[s|S]herlock"(参见 清单 12)的所有行。

清单 12. 清单 12. 搜索文档

                $ curl -X POST -H "Content-Type: application/json" \
http://localhost:8098/mapred --data @-<<\EOF
{"inputs": [["documents","s1"],["documents","s2"]],"query": [{ "map": { "language":"javascript", "name":"GrepUtils.map",  "keep":true, "arg": "[s|S]herlock" } },{ "reduce": { "language": "javascript", "name": "GrepUtils.reduce" } }]
}
EOF

查询中的 arg 属性包含我们希望在文档中对其执行 grep 查询的模式;该值被作为 arg 参数传递给 map 函数。

清单 13 中显示了对样例数据运行 Map/Reduce 作业所产生的输出。

清单 13. 清单 13. 运行 Map/Reduce 作业的样例输出

                [["1. Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan
Doyle","9. Title: The Adventures of Sherlock Holmes","62. To Sherlock Holmes
she is always THE woman. I have seldom heard","819. as I had pictured it from
Sherlock Holmes' succinct description,","1017. \"Good-night, Mister Sherlock
Holmes.\"","1034. \"You have really got it!\" he cried, grasping Sherlock
Holmes by" …]]

回页首

流化 Map/Reduce

在关于 Map/Reduce 的最后部分中,我们将简单地了解 Riak 的 Map/Reduce 流化 (streaming) 特性。该特性对于包含 map 阶段并需要花一些时间完成这些阶段的作业非常有用,因为对结果进行流化允许您在生成每个 map 阶段的结果后立即访问它们,并且在执行 reduce 阶段之前访问它们。

我们可以对分布式 grep 查询应用这个特性。本例中的 reduce 步骤并没有多少实际操作。事实上,我们完全可以去掉 reduce 阶段,只需要将每个 map 阶段的结果直接发送到客户端即可。为了实现此目标,需要对查询进行修改,删除 reduce 步骤,将 ?chunked=true 添加到 URL 末尾,表示我们希望对结果进行流化(参见 清单 14)。

清单 14. 清单 14. 修改查询以流化结果

                $ curl -X POST -H "Content-Type: application/json" \
http://localhost:8098/mapred?chunked=true --data @-<<\EOF
{ "inputs": [["documents","s1"],["documents","s2"]],"query": [{ "map": {"language": "javascript", "name": "GrepUtils.map","keep": true, "arg": "[s|S]herlock" } }]
}
EOF

在完成 map 阶段后,会将每个 map 阶段的结果(在本例中为匹配查询字符串的行)返回给客户端。该方法可用于需要在查询的中间结果可用时就对它们进行处理的应用程序。

回页首

结束语

Riak 是基于 Amazon 的 Dynamo 文件中记载的规则的一种开源的、高度可扩展的键值存储库。Riak 非常易于部署和扩展。可以无缝地向群集添加额外的节点。link walking 之类的特性以及对 Map/Reduce 的支持允许实现更加复杂的查询。除了 HTTP API 外,Riak 还提供了一个原生 Erlang API 以及对 Protocol Buffer 的支持。在本系列的第 2 部分中,我们将探讨各种不同语言中的大量客户端库,并展示如何将 Riak 用作一种高度可扩展的缓存。

参考资料

学习

  • 参见 Basic Cluster Setup and Building a Development Environment,获得有关设置一个 3 节点群集的详细信息。
  • 阅读 Google 的 MapReduce: Simplified Data Processing on Large Clusters。
  • Erlang 编程简介,第 1 部分(Martin Brown,developerWorks,2011 年 5 月):对 Erlang 的函数性编程风格与其他编程模式进行了比较,如命令式、过程式和面向对象的编程。
  • 强烈建议阅读 Amazon 的 Dynamo 文档,以了解 Riak 的基础知识。
  • 阅读文章 How To Analyze Apache Logs,了解如何使用 Riak 处理您的服务器日志。
  • 了解 向量时钟,以及为什么它们要比您想象的更加容易理解。
  • 在 Riak wiki 上找到有关 向量时钟 的出色介绍,以及更多有关 link walking 的信息。
  • 如果您需要一些文本资源进行测试的话,Project Gutenberg 站点 是一个不错的选择。
  • developerWorks 中国网站 Web 开发专区 提供了涵盖各种基于 Web 的解决方案的文章。
  • 要收听面向软件开发人员的有趣访谈和讨论,请参阅 developerWorks podcasts。
  • IBM Rational Twitter:立即加入并关注 developerWorks tweets。
  • 观看 演示如何用 WebSphere Studio 快速开发Web Services,其中包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
  • 随时关注 developerWorks 技术活动和网络广播。
  • 访问 developerWorks Open source 专区获得丰富的 how-to 信息、工具和项目更新以及最受欢迎的文章和教程,帮助您用开放源码技术进行开发,并将它们与 IBM 产品结合使用。

获得产品和技术

  • 从 basho.com 下载 Riak。
  • 下载 Erlang 编程语言。
  • 使用专门面向开发人员的软件创新您的下一个开源开发项目;访问 IBM 产品评估试用版软件,可以通过下载或从 DVD 获得它。

Riak 简介,第 1 部分: 与语言无关的 HTTP API相关推荐

  1. Riak 简介(2)

    第 2 部分: 将 Riak 集成为 Web 应用程序的重负荷缓存服务器  http://www.ibm.com/developerworks/cn/opensource/os-riak2/ 简介 某 ...

  2. 题目内容: 你的程序要读入一行文本,其中以空格分隔为若干个单词,以‘.’结束。你要输出这行文本中每个单词的长度。这里的单词与语言无关,可以包括各种符号,比如“itJava练习之翁恺MOOC——第六周

    Java练习之翁恺MOOC 一.前言 一.题目 1.第六周 单词长度 2.第六周 GPS数据处理 二.代码实现 1.单词长度 2.GPS数据处理 总结 一.前言 零基础学习Java语言 一.题目 1. ...

  3. MSIL:微软中间语言——.Net语言无关性

    MSIL是一种通用的中间语言,类似于现在国际使用的英语,实现语言无关性.各编程语言,如C#.Javad,通过编译器翻译成MSIL. MSIL是将.NET代码转化为机器语言的一个中间过程.它是一种介于高 ...

  4. GO语言03(Restful API,API流程以及代码结构)

    目录 Restful API 1. 什么是 API 2. REST 简介 3.RESTful RUI的设计 4. RPC 简介 API 流程 1. HTTP API 服务器启动流程 2. HTTP 请 ...

  5. python语言实例-Python语言实现百度语音识别API的使用实例

    未来的一段时间,人工智能在市场上占有很重的位置,Python语言则是研究人工智能的最佳编程语言,下面,就让我们来感受一下它的魅力吧! 百度给的样例程序,不论C还是Java版,都分为method1和me ...

  6. 用python语言实现-Python语言实现百度语音识别API的使用实例

    未来的一段时间,人工智能在市场上占有很重的位置,Python语言则是研究人工智能的最佳编程语言,下面,就让我们来感受一下它的魅力吧! 百度给的样例程序,不论C还是Java版,都分为method1和me ...

  7. atitit.跨语言执行cmd cli api的原理及兼容性设计草案

    atitit.跨语言执行cmd cli api的原理及兼容性设计草案 1. 标准输入,标准输出,标准错误与重新定向1 2. 常见问题2 2.1. 执行bat文件2 2.2. 执行bat文件  /c   ...

  8. python restful api_用Python语言写一个restful API

    本文主要向大家介绍了用Python语言写一个restful API,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. """ package.modul ...

  9. Go语言使用百度翻译api

    Go语言使用百度翻译api 之前做过一个使用百度翻译api的工具,这个工具用于用户的自动翻译功能,是使用C#调用百度翻译api接口,既然在学习Go语言,那必然也是要使用Go来玩耍一番.这里我是这么安排 ...

最新文章

  1. 条件注解 @ConditionalOnBean 的正确使用姿势
  2. python3菜鸟教程-总算理解python3中文入门教程
  3. secureCRT连接问题
  4. C++ Primer 5th笔记(chap 17 标准库特殊设施)指定浮点数记数法
  5. Linux Socket TCP/IP通信
  6. 微软云Azure训练营 | 八城联动,全球盛会
  7. python随机产生100个整数二进制_PYTHON练习题 二. 使用random中的randint函数随机生成一个1~100之间的预设整数让用户键盘输入所猜的数。...
  8. Navicat连不上Ubuntu?
  9. 加州大学欧文分校 计算机专业,加州大学欧文分校计算机科学排名第36(2020年TFE美国排名)...
  10. bzoj 2962 序列操作 线段树
  11. python 读取csv带表头_python读csv文件时指定行为表头或无表头的方法
  12. 编译加速 remote cache
  13. 端口占用问题解决办法(以1099端口为例)
  14. 数据平台作业调度系统详解-理论篇
  15. 基于javacv的视频转码(升级版)
  16. 2183440-33-5,Methyltetrazine-PEG8-acid在存在活化剂(如EDC或HATU)的情况下,它可用于与含胺分子共轭
  17. 互斥锁Mutex解锁两次
  18. mysql里guest用户_MySQL降权:MySQL以Guests帐户启动设置方法
  19. beyond compare 用法
  20. HTML报错 Malformed markup: Attribute “xxx“ appears more than once in element

热门文章

  1. UVA - 10382- Watering Grass
  2. 就这样吧,从此山水不相逢
  3. centos6 trouble shooting
  4. EFM8 USB Library使用笔记
  5. 嘀嘀和快的上演十月围城 020模式被强化
  6. 【日语口语词典学习】第0005页
  7. verilog always语法_Verilog 最全经验总结(建议收藏)
  8. 和风天气API调用结果乱码
  9. AWS S3文件/文件夹删除
  10. 手机kakao聊天能自动翻译 WhatsApp翻译 实时翻译