写点什么

DSN 主流项目调研 3——Orbit 数据库的故事

用户头像
AIbot
关注
发布于: 2020 年 08 月 13 日
DSN 主流项目调研 3——Orbit数据库的故事

序言

简介

通过 OrbitDB,我们有能力写出去中心化的,分布式的数据层存储系统;1.用户拥有自己的数据、2.第三方应用要求权限在本地运行而不是存储我们的数据到他们的服务器上、3.对等节点之间项目连接不需要中间层、4.应用满足 GDPR(Genral Data Protection Regulation)和 HIPAA(Health Insurance Portability and Accountability Act)的要求从不泄露敏感信息、4.每一层应用都能被交换和配置,享有高度可开发配置的体验:数据层、中间件、UI 都能够换出或配置、5.难以服务的产业可以服务了,金融、健康、基础设计还有其他涉及数据隐私保护的场景可以被设计。


历史回顾

分布式系统从俩都不是什么新鲜事物 ,有些项目给世界带来的彻底的扰乱是所有风投机构都无法想象的,但是结果往往是悲剧而深刻的。


互联网的开局是在 1960 年,网络作为一个分布式的系统来计算核爆下生存的能力而被开发。独立的计算机被连接到一起通过地下的电线和一道名叫“包切换”的技术。“包切换”被用于实现数据的传输而不是以期被接收端重新集合大块的传输。


在 1980s 学术界潜心于分布式共识算法例如:Paxos,两阶段提交的过程。一下介绍基本原理。


  • Proposer(提案者):提出一个提案,等待大家批准(chosen)为结案(value)。系统中提案都拥有一个自增的唯一提案号。(往往系统中的客户端担任这个角色)

  • Acceptor(接收者):负责对提案进行投票,接受(accept)提案(往往由服务端担任这个角色)

  • Learner(学习者):负责获取批准的结果,并且可以帮忙传播,不参与投票过程;(可能是客户端也可能是服务端)

时间来到 1991 年第一个万维网出现了,一个现在大多数人熟悉的、内容流行的现代网络诞生了;这个网络允许人们使用发布网页链接 url 的方式来展示网站。


至此 Web 时代一直持续到现在,在主流的 C/S 架构中另一种 P2P 模式也早已走进了人们的视野,1999-2004 年间火遍美洲的 Napster 可惜没能来到中国不然绝对让 10 快连 300 首歌的路边小店死得不能再死。


目前公司使用分布式的方式组织他们资产,利用中心化的平台来管理他们。但是 Bitcoin 的出现让这一切出现了变化,她不仅展示了区块链这个新的技术领域,而且总的来说为分布式系统带来了新的思考。比特币在底层使用工作量证明来实现共识,给这个行业带来了全新的焦点和方法来看待挑剔的金融业务。


OrbitDB 的定义

如果说 IPFS 是分布式的“硬科技”,所有的数据被地址编码并可以通过集群中的节点进行检索;那么 OrbitDB 就是数据库引擎。OrbitDB 创造和管理可篡改的数据库并提供额外的更简单交互界面,典型地以简单的获取 get 设置 set 功能为核心,管理大量复杂性来存储数据库到分布式存储方式 IPFS 中。


采用无冲突复制数据类型 CRDTS(Conflict Free Replicated Data Types),无论是基于操作 operation-based 还是基于状态 state-based,数学上证明可相互转化。CRDT 的强项在于合并功能,对于给定的数据类型,定义一个函数——函数接收它所有的数据的多个版本;并使用该函数将这些版本合并为的那个版本,版本的合并顺序无关紧要,最终结果还可用于 CRDT。CRDT 不保证强一致性,只保证最终一致性;好处是低延迟的读写,较高的可用性,也可以在某些场合具备较高的吞吐量。


Orbit 使用 JS 写成主要原因就是 JS 用的人多而且 IPFS 有现成的使用 js 写的源码,还乐而不为?除此以外团队也在积极进行其他语言的拓展。OrbitDB 的代码遵循 MIT 开源协议,我们可以自由的使用 OrbitDB 的代码。团队致力于让开发者能够构建将成为主流的分布式应用程序,并使软件的用户对其数据拥有真正的主权。


Orbit 的常用场景

OrbitDB 擅长灵活的构建复杂的分布式应用;他并不是一个区块链,最终一致性的运行原则可能会导致有些操作在你不知道的时间和地点发生,假设你想要最终同步所有节点的话。每次你和数据库的交互你都实时的和数据库的快照交互。这和区块链的强一致性很不同,区块链的数据上链前需要对数据进行验证。


目前已经有一下软件程序使用 OrbitDB 进行数据存储服务了。


项目名称概述 Colony 分布式组织 uPort/ConsenSys 3Box 使用 OrbitDB 构建的分布式数据库 Origin Protocol 利用 OrbitDB 构建的消息协议 Origin MessagingChluNetworkChluCluster LabsipfsCloud File 存储、分享 TallyLab 数据或的记录软件 Computes(Magic Leap)计算分布式命名服务 DASH BlockstackDash 进化图文档分布式加密用于替代 G-suit 和 Office365Protocol Lab 讨论平台


警告

OrbitDB 和底层 IPFS 层当前是 alpha 软件。已经发现了很多严重的错误、API 更改、安全漏洞披露等。这是透明地传达的,但是这些警告丝毫没有阻止大型组织在生产中使用这些工具。如果听起来不好,请放心,还有更糟心的消息。不仅这些工具处于 alpha 状态,而且整个分布式行业都处于 alpha 状态。不仅存在技术上尚未解决的问题,还存在法律,政治,经济和社会方面的问题。


但是时代的浪潮启示我们,个人计算机革命崩溃了,随之而来的是信息时代。移动革命的浪潮使我们进入了社交时代。现在,我们正处于新的分布式革命中。因此,请做好准备。希望我们能发现这一挑战性和刺激性,但是不要因感到恐惧和惊讶。


话虽这么说但,欢迎来到未来,很高兴我们同行。


第一章 OrbitDB 使用介绍

一场点对点、去中心化、分布式的部分和交互式、命令行和同构的 JS 的冒险


介绍

无论是在浏览器还是在 nodejs 中都能顺利的使用 OrbitDB,本章节将为您手把手教学如何建立一个库,使用 JS 类定性是并构建一个 UI 来完成整个应用的开发


前提
  • 计算机一台

  • 现代浏览器一件(IE 不算)

  • Node.js 环境(可选)

学到什么

在接下来的章节中,你会学习到如下知识。


  1. 放置基础:包含 IPFS 和 OrbitDB 安装的细节和基础数据库创建的流程

  2. 数组管理: 介绍 OrbitDB 数据存储和教您如何基础的创建,更新,删除数据

  3. 结构化数据:建议一些方法来构建多 OrbitDB,以期实现更健壮的方案

  4. 点对点 IPFS 部分:点对点网络和消息传输的大讨论,开始于 IPFS 层

  5. 点对点 OrbitDB 部分:通过将 OrbitDB 加入混合的交叉数据库发现,链接,复制?

  6. 身份认证和准许:通过加密和分布式身份是系统变得坚固

选择音乐软件的原因

OrbitDB 已经在全球得到广泛的应用(我不信), 并且作者相信音乐类 app 是独特的跨文化的交流方式——全人类都可以分享、享受、欣赏音乐;通过构建一个依据 OrbitDB 的音乐 app 可以帮助全球的音乐家找到适合练习的乐谱。这并非幼稚的夸大其词,而是确有其事, 你在这里做的努力都会被立刻运用到实际生产生活中,作为坚固的 MVP;通过实际的音乐家,我们希望您能够通知我们如果您身边有音乐家使用它。


常规设定
  • 教程包含:

  • 按照顺序阅读教程就可以顺利的构建应用

  • UI 层是被建议(暗示的),而不是直接建构;教程之构建一个 JS 类,为 UI 层提供所有的功能函数

  • 自己动手敲代码,不要复制粘贴

  • 支持异构的操作系统

  • 刚刚发生了什么会告诉你在底层的技术层面发生了什么

  • 需要读者动手的地方被使用斜体字不同圈出来了,实例代码使用 JS 编写

第一节 构建基础

OrbitDB 的基础包括安装 OrbitDB 和 IPFS,设置同构项目,创建数据库并了解如何选择数据存储


内容简介:


  • 安装依赖项:IPFS 和 OrbitDB

  • 安装 IPFS 和 OrbitDB

  • 创建数据库

  • 选择数据库

  • 关键要点

安装 IPFS 和 OrbitDB 的依赖项

依赖项

首先安装 nodeJs 和 nmp,国内的话建议使用 cnpm(淘宝的 npm 镜像);


npm init --yesnpm install orbit-db ipfs
复制代码


上述两行命令会创建一个 package.json 和一个 package-lock.json 文件,一个 node_modules 文件夹。


在浏览器中连 nodejs 和 npm 都不用装,因为内置了,所以直接 CDN 方式就可以了但是国内不行,CND 被墙了。


<script src="https://unpkg.com/ipfs/dist/index.min.js"></script><script src="https://www.unpkg.com/orbit-db/src/OrbitDB.js"></script>   
复制代码


上述两行代码就可以远程拉取 CDN 的 ipfs 和 OrbitDB 代码,并在本地浏览器使用;但是国内网上不去 CND。


创建同构书立(isomorphic bookends)

创建一个mewpieceplease.js文件,然后写入如下代码;注意本文的代码都是通用的 ,因其在扩展代码的时候我们也要尽量让代码通用起来。


try {	const Ipfs = require('ipfs')	const OrbitDB = require('orbit-db')} catch(e){}
class NewPiecePlease(){ constructor(IPFS, OrbitDB){ }}
try{ module.exports = exports = new NewPoecePlease(Ipfs, OrbitDB)} catch(e){ windows.NPP = new NewPiecePlease(window.Ipfs, window.OrbitDB)}
复制代码


发生了什么: 使用关键的 JS 功能,我们为 app 创造了一个交互界面;它定义了一个新的类——NewPiecePlease, 使用一个带有双参数的构造器——constructor(IPFS,OrbitDB);在这里我们先忽略那些同构的书立,而是聚焦在 NewPiecePlease 这个类上。


实例化 IPFS 和 OrbitDB

OrbitDB 需要一个运行着的 IPFS 节点来操作,所以需要运行一个 IPFS 节点并让 OrbitDB 知道。通过如下代码,构成一个 app。


class NewPoecePlease(){	constructor(IPFS,OrbitDB){		this.OrbitDB = OrbitDB		this.node = new IPFS({			preload:{ enabled: false },			repo: "./ipfs",			EXPERIMENTAL: { pubsub: true },			config: {				Bootstrap: [],				Addresses: { Swarm: [] }			}		});				this.node.on("error", (e)=>{ throw(e) })		this.node.on("ready", this._init.bind(this))	}		async _init() {		this.orbitdb = await this.OrbitDB.createInstance(this.node)		this.onready()	}}
复制代码


上述代码的运行效果可以通过下面这行代码展现


NPP.onready = () => {	console.log(NPP.orbitdb.id)}
复制代码


上述代码的输出是 IPFS 节点的“multihash”值,形如 QmPSicLtjhsVifwJftnxncFs4EwYTBEjKUzWweh1nAA87B 。


刚刚发生了什么:从新建的 IPFS 行开始,这些代码创建了一个 IPFS 节点,并使用如下的默认设置:


  • 预加载:{ enabled: false }不允许使用所谓“preload”预加载的 IPFS 节点。这些节点的存在可以帮助负载均衡防止全球网络的 DDoS 攻击。但是这些节点可能掉线或者出现问题。目前我们的代码都只需要离线因此暂时关闭该选项。

  • 仓库:'./ipfs'指定了 repo 的路径(只在 nodejs 中有效),在浏览器中文件不能直接存放在操作系统的文件路径。

  • 实验:{ pubsub: true }允许 IPFS 使用插拔,这个一种不同节点沟通的方法,OrbitDB 必须要开启 pubsub,尽管我们未必和其它节点相连

  • 配置:{Bootstrap: [], Addresses: { Swarm: [] }}设置引导节点列表和集群节点列表。

  • node.on("error", (e) => { throw new Error(e) })实现极端基础的错误处理(如果有错误在启动 IPFS 节点的时候就出现)

  • node.on("ready", (e) => { orbitdb = new OrbitDB(node) }) 实例化 OrbitDB 在 IPFS 节点成功启动之后。

通过上述一同操作之后 IPFS 节点和 OrbitDB 都已经成功了启动了,接下来就可以愉快的编程了。


除了上述对代码的解读意外,Node.js 中还发生了一些其它的事情。当我们使用 Node.js 我们创建了两个文件夹在我们的项目结构中:'orbitdb/' 和 'ipfs/'。通过截图可以看出的信息如下,'orbitdb'文件夹中将会看到子文件夹有着和 orbitdb 一样的 id(同时也和 IPFS 的节点 id 一样);这样做是有目的的,初始化化的文件夹中包含了 OrbitDB 需要操作的元数据;在 ipfs 文件夹中包含了所有的 ipfs 数据。


在浏览器中发生的事情和 Node.js 中有些不同,在浏览器中 IPFS 节点的数据被存储在 IndexDB 中,浏览器的永久存储机制,手机端的 IndexDB 有可能被清除,所以还是 app 端靠谱点。


创建数据库

我们要创建一个音乐片段的目录来供音乐人们查看练习,你现在可以创建数据库并最终确保只用用户可以写入这个数据库。在_init 函数中添加如下内容:


async _init {	this.orbit = await OrbitDB.create(node)	this.defaultOptions = { accessController: { Write: [this.orbitdb.identity.publickey] }}		const docStoreOptions = {		...defaultOptions,		indexBy: 'hash',	}	this.pieces =  await this.orbitdb.docstore('pieces', docStoreOptions)}NPP.onready = () => {	console.log(NPP.pieces.id)  }
复制代码


你将会看到如下的输出:/orbitdb/zdpuB3VvBJHqYCocN4utQrpBseHou88mq2DLh7bUkWviBQSE3/pieces;这个是你数据库的 id 或者说是你数据库的地址(技术上来说就是 multiaddress)。对你而言理解这个 id 非常的重要,这个 id 字符串有三段组成。


  1. 一开始的字段是/orbitdb,这是一个协议,他告诉我们这个字符串是一个 OrbitDB 的地址。

  2. 第二段或者说中间的字段,zdpuB3VvBJHqYCocN4utQrpBseHou88mq2DLh7bUkWviBQSE3,这是最有趣的地方。这个是数据库载货单的 CID,它包含了:

  3. 最后一个部分是用户自己提供的名字,在示例文件中使用的是 pieces,所以 pieces 变成了最后部分的多地址 multiaddress

注意:地址使用 Qm 开头的是 CIDv0 内容地址,zdpu 开头的是 CIDv1。OrbitDB 地址需要彻底的理解否则会导致一些意想不到的问题——滑稽或者灾难性的后果


刚才发生了什么:我们的代码创建了一个本地的 OrbitDB 数据库,使用了 docstore 类型,只能被创建它的用户所写入✍。


  • defaultOptions 和 docStoreOptions 定义了我们将建立的数据库的参数。

  • accessController:{ write: [orbitdb.identity.publicKey] }定义了一个 ACL(Access Control List)在这个示例中我们将写入的权限限制到只有公钥持有者能够访问。

  • indexBy:“hash” 是一个 docstore-specific 选项,他指定通过 hash 的方式来编纂数据库的索引

  • pieces = await orbitdb.docstore(’pieces', options) 是一个莫力十足的代码行,正是这行代码最终创建了数据库;当这行被成功执行后,数据库就能被自由的访问和修改了

注意:你的公钥并不是你的身份证,重复一遍,公钥不是身份证。


Nodejs 中发生了什么: 在系统目录中新建了一个数据库的目录,OrbitDB 好处是将 IPFS 的交互都封装起来交给用于一个直接调用的 API,方便用户进行数据的管理。


选择数据库

OrbitDB 通过在存储的时候划分不同的数据管理关系、模式、API 来组织功能,在上述的代码中我们选择了 docstore 数据模式,在接下来的介绍中,需要自行决定要采用的数据模式。每一种数据存储模式都有自己对应的数据存储方法 API:新增 C(create)、删除 D(delete)、检索 R(retrieve)、更新 U(update);大体上来说,我们可以认为是 get、put 操作。可供选择的数据库模式表


名称 介绍

log 日志 一种不可篡改的数据记录模式[仅添加],常用于:显示最近 N 条数据和消息队列

feed 供给 一种可篡改的日志记录模式,消息实体可以被添加或者删除,常用于:购物车、微博

keyvalue 键值对 一种简单的键值对数据库可以支持任何 JSON 序列化的数据,包括嵌套对象

docs 文章 一种可以存储利用特定 key 索引的 JSON 文档的文档数据库,常用于:构建索引目录、版本控制的文章或数据

counter 计数器 一种只能增加的整数计数器,常用于:活动计数,区分 log/feed 数据


如果你想的话你也可以设计自己的存储模式


关键要点
  • OrbitDB 是一种分布式数据库层,它的底层将数据存储在 IPFS 中

  • IPFS 和 OrbitDB 都可以在线离线工作

  • OrbitDB 的实例具有一个 ID,这个 ID 和 IPFS 的节点 ID 是一样的

  • OrbitDB 实力创建一个数据库并分配到一个独一无二的地址

  • 访问 OrbitDB 的基础权力被通过 ACL 管理,基于 IPFS 的 ID 来执行对数据库的请求

  • OrbitDB 的地址是访问控制表-数据库类型-数据库名称的哈希值

  • 由于 IPFS 和 OrbitDB 都是用 JS 写成,在浏览器和 NodeJS 中构建同构的应用是可能的的

  • OrbitDB 通过叫做 stores 的功能方法来管理方案需要的灵活性和 API 设计(数据模式)

  • OrbitDB 仅提供了少数的数据库模式,你可以自己加数据库模式

  • 每一种数据库模式都有独特的 CRUD 方法,但总的来说大家都有 get 和 put

现在我们已经部署了底层工作,你已经学习了如何和这些数据打交道;让我们到第二小节看看如何管理数据吧。


第二节 管理数据

在 Robit 数据库中管理数据包括加载数据库到内存中和 CRUD 方法的具体介绍


  • 加载数据库

  • 添加数据

  • 读取数据

  • 更新和删除数据

  • 存储媒体文件

  • 关键要点

加载数据库

首先我们要做的第一件事就是达能我们启动数据库的时候,我们的数据可以立刻获得。只要将数据加载到内存中就很容易做到这一点了。


更新NewPiecePlease类中的 handle, 在 IPFS 准备后再加一行代码


_init() {	this.orbitdb = await OrbitDB.createInstance(this.node)	this.defaultOptions = { accessController: { write:[this.orbitdb.identity.publicKey] }}		const docStoreOptions = {		...defalutOptions,		indexBy: 'hash',	}	this.pieceDb = await this.orbitdb.docstore('pieces', docStoreOptions)	await this.pieceDb.load()}
复制代码


刚刚发生了什么: 在我们实例化数据库之后,我们将数据加载到了内存中以备不时之需,现在的数据库还是空的,但是不久之后我们就会为它添加数据了。在实例化的时候将数据库加载到内存中会为我们接下来的工作减少麻烦。


  • await pieceDb.load()是一个函数,当我们需要获取数据库的最新存储快照的时候我们调用这个函数,这个加载函数通过数据的内容地址来检索数据并将数据加载到内存中

添加数据

接下俩,你的用户需要能够添加音乐片段到他们的目录中。你使用 OrbitDB 提供的键值对存储他们。


添加addNewPiece功能


async addNewPiece(hash, instrument = "Piano") {	const existingPiece = this.pieces.get(hash)	if(existingPiece){		await this.updatePieceByHash(hash, instrument)		return;	}		const cid = await pieceDb.put({		hash: hash,		instrument: instrument	})	return cid}
复制代码


下面的截图是事先已经存储好了部分乐谱片段的基础上做的,在你的应用代码下加入如下部分


const cid = NPP.addNewPiece("QmNR2n4zywCV61MeMLB6JwPueAPqheqpfiA4fLPMxouEmQ")cosnt content = await NPP.node.dag.get(cid)console.log(content.value.payload)
复制代码


运行这些代码,我们会得到如下的结果,这个结果很复杂(压倒性的令人窒息);但是我们解释完发生了什么之后这一切就有意义了。


刚刚发生了什么: 我们写出并测试了一个功能,允许用户添加音乐片段到他们的数据库中


  • piecesDb.put({})是这些代码中最重要的一行,这段代码将对象存储到数据库中,并返回一个 multihash。也就是 IPFS 存储对象返回的 cid

  • node.dag.get(hash) 是一个功能函数利用 cid 来查找内容

  • "op": "PUT"是一个输出中值得注意的地方,在 OrbitDB 的核心层中是 OPLOG,所有的数据都是操作的日志。这个操作在这里是 PUT,key 和 value 是实际的数据

注意:代码中的“dag”是邮箱无环图的引用; 是一种数据结构


读取数据

当然你的用户需要读取他们创造的数据,所以你需要给他们提供相应的功能。OrbitDB 给我们许多方式来实现这给功能,主要取决于拟选用的数据模式(store)。


我们之前使用的是 docstore 数据模式,因此我们用下面的代码实现 get 函数,docstore 也提供了功能更丰富的 query 函数,我么可以任意的选用 getPieceByInstrument 函数。


getALLPieces() {	const pieces = this.piecesDb.get('')	return pieces}getPieceByHash(hash) {	const singleOiece = this.piecesDb.get(hash)[0]	return singlePiece}getByInstrument(instrument){	return this.piecesDb.query((piece) => piece.instrument === instrument)}
复制代码


对于上述的函数,我们可以使用如下的方式调用


pieces = NPP.getAllPieces()pieces.forEach((piece) => {console.log(piece) })
piece = NPP.getPieceByHash("QmNR2n4zywCV61MeMLB6JwPueAPqheqpfiA4fLPMxouEmQ")console.log(piece)
复制代码


有时候我们希望随便找个片段给我灵感,如下的随机函数会是个很好的选择


const pieces = NPP.getPiecesByInstrument("Piano")const randomPiece = pieces[items.length * Math.random() | 0]console.log(randomPiece)
复制代码


刚刚发生了什么:我们查询本节之前创建的数据库内容,通过哈希或者随机读取他们。


  • pieces.get(hash) 是一个简单的功能,利用并行的字符串搜索数据库中的索引,它将返回匹配的记录的数据,利用空字符串返回全部的匹配记录

  • return this.piecesDb.query((piece) => piece.instrument === instrument) 查询数据库并返回,在一个和 JS 中的 Array.filter 方法类似。

注意:大体上来说,get 功能函数在数据库写入数据期间并不返回数据,这样的权衡让数据库的易用性和性能都不错,毕竟大部分情况下读取操作比写入操作频繁的多。


更新和删除数据

现在是时候给用户们打造更新和删除音乐片段的能力了,例如,如果你意识都比起在钢琴上,你更喜欢在大建琴上练习此曲,或者用户干脆就不想联系这个片段了。


再次强调一遍不同的数据模式(store)有轻微不同的方法来删除数据,在 docstore 中米可以通过 put 方法来更新记录,接下来完成updatePieceByHash/deletePieceByHash


async updatePieceByHash(hash, instrument = 'Piano') {	const piece =  await this.getPieceByHash(hash)	piece.instrument = instrument	cosnt cid = await this.piecesDb.put(piece)	return cid}
async deletePieceByHash(hash){ const cide = await this.piecesDb.del(hash) return cid}
复制代码

应用的代码中可以看到 opcode 的返回状态

const cid = await NPP.updatePieceByHash(hash_value,"大键琴")// 利用上行返回的cid做些什么吧const cid = await NPP.deletePieceByHash(hash_value)const content = await NPP.node.dag.get(cid)console.log(content.value.payload)
复制代码


删除操作返回的 cid,再读取就会发现返回的负载 value 是空的。


刚刚发生了什么: 可能你会有疑惑,为什么 IPFS 是不可篡改的但是 OrbitDB 可以删除数据;答案在 opcodes 里面。


  • this.piecesDd.put 并不新奇, 这次用来更新而不是插入

  • this.piecesDb.del 是一个简单的函数来用哈希值找到记录并删除

  • "op": "DEL"是另一种 opcode 操作代码,DEL 是 DELETE 删除的简称;这条日志实体将有效的从本地的记录中删除键和本地的 IPFS 上的内容

存储媒体文件

总是让用户存储文件到 IPFS 再将 hash 写道数据库中是一件十分麻烦的事情,为了不让事情变得更糟糕,我们将为用户提供直接将媒体数据存储到 IPFS 并写入 OrbitDB 的方法。总的模式如下:1. 将文件添加到 IPFS 中返回了 cid,2.将哈希值存储到 OrbitDB 中,3.当需要播放的时候直接使用 IPFS 的功能取文件。


添加内容到 IPFS, 在 Nodejs 中可以自己接使用如下方式


var IPFS = require('ipfs')var ipfs = IPFS(/*插入合适的ipfs实例化选项*/)ipfs.addFromFs("./文件名").then(console.log('插入文件成功'))
复制代码


刚才发生了什么:你讲一些非常大媒体文件添加到了 IFPS 中, 然后我们将返回的 cid 存储到 Orbit 数据库中就可以了。


注意:IPFS 使用了 IndexDB 所以数据量很大的时候别把 IndexDB 搞崩了


关键要点
  • 定期调用 load()函数,确保你有最新的数据库实体可以在内存中直接访问

  • 大体上讲,put 和 delete 将返回一个 Promise(await), get 会立刻返回值

  • 更新数据库相当于添加一个实体到 OPLOG 中

  • 计算 OPLOG 来给到现在数据库的状态,你可以和这个状态交互

  • OPLOGS 是自由的,特别是你写自己的数据模式(store),docstore 主要利用了 put 和 get 方法

  • 虽然利用一些方法也可以将媒体数据存储到 OrbitDB 中但是 IPFS 可以更好的承载这些数据

  • 使用浏览器存储数据的时候,注意一下 IndexDB 容量

第三节 结构化数据

本小节会让你爱上嵌套数据的,也就是我们的 OrbitDB


  • 为每一个片段添加一份实际的计数器

  • 利用你的计数器

  • 写一个高级的用户数据库

  • 处理固定数据

  • 关键要点

添加计数器

用户可能想要记录他们的联系轨迹,最起码想知道他们呢已经联系这个片段多少次了。你可以通过增加一个计数器的功能来满足他们的需求,然后在NewPiecePlease利用一个函数来这和个计数器交互。这里面存在一个嵌套问题,看看大家如何解决吧。


更新addNewPiece函数来创建一个计数器,每次有新的片段加进来的使用利用基础的访问控制确保只有你的 IPFS node‘s ID 的用户可以写入他。


async addNewPiece(hash, instrument = "Piano") {	const existingPiece = this.pieces.get(hash)	if(existingPiece) {		await this.updatePieceByHash(hash, instrument)		return;	}}
const dbName = "counter." + hash.substr(20,20)const counterDb = await this.orbitdb.counter(dbName, this.defalutOptions)
const cid = await this.pieces.put({ hash: hash, instrument: instrument, counter: counterDb.id})return cid
复制代码


在应用代码中写下如下及就可以校验上述函数


const cid = await NPP.addNewPiece("QmdzDacgJ9EQF9Z8G3L1fzFwiEu255Nm5WiCey9ntrDPSL","Piano")const content = await NPP.node.dag.get(cid)console.log(content.value.payload.value)
复制代码


刚刚发生了什么: 你改变代码添加了一个计数器数据库到应用中


  • const options = { accessController: { write: [this.orbitdb.identity.publicKey] }}这个可以在第一小节(chapter)中找到,设置访问控制表 ACL(access control list)

  • this.orbitdb.counter 创建了一个计数器模式的数据库,只给 IPFS node’s ID 写入权限

  • const dbName = "counter." + hash.substr(20,20) 预先指定 counter, 缩短数据库的名称

  • this.pieces.put 被修改用来存储新的数据库的地址,接下俩也要用它来访问数据库

  • "counter":"/orbitdb/zdpuAoM3yZEwsynUgeWPfizmWz5DEFPiQSvg5gUPu9VoGhxjS/counter.fzFwiEu255Nm5WiCey9n" 这是新建的 counter 数据库的地址

利用练习计数器

现在 添加一些功能到NewPiecePleae来利用联系计数器


async getPracticeCount(piece) {	const counter = await this.orbitdb.counter(piece.counter)	await counter.load()	return counter.value}
aysnc incrementPracticeCounter(piece) { const counter = await this.orbitdb.counter(piece.countet) const cid = await counter.inc() return cide}
复制代码


用如下的方法来检验函数是否写成功了。


const piece = NPP.getPieceByHash("QmdzDacgJ9EQF9Z8G3L1fzFwiEu255Nm5WiCey9ntrDPSL")const cid = await NPP.incrementPracticeCounter(piece)const content = await NPP.node.dag.get(cid)console.log(content.value.payload)
复制代码


刚刚发生了什么: 我们创建并使用了两个函数用来读取计数器数据库和增加计数器


  • await this.orbitdb.counter(piece.counter) 输一个心得方法用来使用 this.orbitdb.counter 通过解析一个已经存在的数据库的地址来打开一个存在的数据库而不是创建一个新的

  • counter.load() 一旦 getPracticeCount 被调用就会加载计数器数据库的实体到内存中

  • await counter.inc()增加计数器, 类似于整数变量的 counter++

  • "op": "COUNTER"是一个新的操作类型,表示你在操作计数器加法,事实上 op 是可以在编写数据库类型(store)的时候任意指定的

  • "counter": {"xxx...xxx": 3}是计数器数据库中存放的值

添加高层次数据库

光存储音乐的片段不足以满足用户的需求,用户还想有一些个性化的姓名以及简介。更新_init 功能:

async _init() {	this.orbitdb = await OrbitDB.createInstance(this.node)	this.defaultOptions = { write: [this.orbitdb.identity.publicKye] }	const docStoreOptions = {		...defaultOptions,		indexBy: 'hash',	}	this.pieceDb = await this.orbitdb.docstore('pieces', docStoreOptions)	await this.pieces.load()		this.user = await this.orbitdb.kvstore("user", this.defaultOptions)	await this.user.load()	await this.user.set('pieces', this.pieces.id)}
复制代码

当我们需要使用函数的时候就用下面的代码调用

async deleteProfileField(key) {	const cid = await this.user.del(key)	return cid}
getALLProfileFileds() { return NPP.user.all()}
getProfileField(key) { return this.user.get(key)}
async updateProfileField(key, value) { const cid = await this.user.set(key, value) return cid}
复制代码


使用如下的方法检验


await NPP.updateProfile("username","aphelionz")var profileFileds = NPP.getAllProfileFields()await NPP.deleteProfileField("username")
复制代码


是不是渐入佳境了


发生了什么:我们可以创建一个数据库用于存储任意的关于用户的数据,然后用嵌套的方式将他和 piece 链接起来。


  • this.orbitdb.kvstore("user", this.defaultOptions) 创建一个 OrbitDB 允许我们管理简单的键值对存储

  • this.user.set('pieces', this.pieces.id) 是一个简直对数据库用来创建条目的方法,类似于 user={}

  • NPP.user.all() 返回所有键值对数据库中的 key 和 value。

  • this.user.del(key) 从数据库中删除特定的键和内容

  • this.user.get(keu)从数据库中检索特定的键和值

处理固定数据

新用户需要一些入门经验,你将为用户提供这些入门经验通过给用户一些数据让他们呢能够离线的体验。


首先在 NewPiecePlease 类中创建一个 loadFixtureData 的功能。

async loadFixtureData(fixtureData) {	const fixtureKeys = Object.keys(fixtureData)	for (let i in fixtureKeys) {		let key = fixtureKeys[i]		if(!this.user.get(key)) await this.user.set(key, fixtureData[key])	}}
复制代码


async _init() {	const peerInfo = await this.node.id()	this.orbitdb = await OrbitDB.createInstance(this.node)	this.defaultOptions = { accessController: { write: [this.orbitdb.identity.publicKey] }}	const docStoreOptions = {		...defaultOptions,		indexBy: 'hash',	}	this.pieceDb = await this.orbitdb.docstore('pieces', docStoreOptions)	await this.pieces.load()		this.user = await this.orbitdb.kvstore("user", this.defaultOptions)	await this.user.load()		await this.loadFitureData( {		"username": Math.floor(Math.rand() * 1000000),		"pieces": this.pieces.id,		"nodeID":peerInfo.id	})		this.onready()}
复制代码


然后如果你想要清楚本地的数据然后重新加载数据,你将会看到如下场景


var profileFileds = NPP.geAllProfileFields()console.log(profileFileds)
复制代码


发生了什么: 我们创建了一个简单的固定数据,并在刷新 app 的时候加在他


  • for(let i in fixtureKeys)这个类型的循环确保写入的操作是序列化的完成的

  • await this.user.set(key, fixtureData[key]) 设置用户的简介中创建这些固定项目

  • await this.node.id() 有轻微的误解,因为他提供更一般的对等节点信息对象

  • peerInfo.id 包含你想要的 ID 字符串和 IPFS id 的 base58 哈希值。

关键要点
  • 未来的分布式应用的功能可能很复杂需要数据结构来镜像或者管理他们的复杂性

  • 幸运的是 OrbitDB 是一个极端自由的,产生复杂性和数据结构的链接

  • 这些结构可能包含任何 OrbitDB 的存储方案 store——你并不用给自己设限

  • 你可以用一个数据库嵌套另一个数据库,然后你可以新的数据库嵌套当前的数据库

  • 嵌套数据库是一种有利的方法,但是有一点就是不要把自己局限了,和社区分享你的好方法

  • 通过在 app 初始化的时候,包含基础的数据就可以固定的信息可以被很容易的在本地加载

通过上面个小节的学习,我们马上就可以和世界相连了。

第三章 Orbit 数据库的结构

OrbitDB 如何使用 IPFS 来存储管理和编辑数据,这一章节从技术栈的底层到顶层的 API 接口向大家介绍 OrbitDB 的工作原理,使你从原理角度理解内部如何运转。


固件层:星际文件系统

理解 IPFS 的独特运行原理可以帮助我们更好地立即 OrbitDB 的内容。TODO 的内容和你如何指定数据的地址和内容表相关:内容索引 CID(Content-Addressed)、有向无环图 DAG(Directed Acyclic Graphs)。


CID

在 IPFS 中你的文件是内容索引的,当你添加文件到 IPFS 网络中的时候,系统会根据他的内容给出存储地址将他从位置地址的囚笼中解救出来;通过这个哈希,服务器如果有的话,可以很快同时回复这个数据。


CIDv0 和 CIDv1 两个版本的 CID 具体介绍可参考 IPFS 和 FIlecoin 的简介中的描述,本质上是使用 multiformat 方法进行了一些封装(一种子描述前缀的编码方式)


和 Bittorent 一样,在 IPFS 中可以对获取的文件进行哈希验证,通过对等结点的服务来获得想要的数据。


DAG

就是有向无环图,一个有方向的,没有换路的图数据结构


数据库类型

可以选用:键值对数据库、文档数据库、计数器、日志、Feed


Replication

OrbitDB 的副本是被罩起来、自动的过程;理解如何用它是一码事,但是很多情况下我们想知道如何一步步的将一个数据库建立,并且数据是如何在节点之间转移的。接下来介绍:手把手介绍副本、啥叫分区


Replication

在副本中介绍了,如何通过 debug log 的方式在控制台去观察复制的步骤,你能够分析这些日志并通过这个过程了解到 OrbitDB 是怎么在对等节点间通过可靠的分享小规模的数据先启动再分享数据。


关于分区

分区的功能是如何在这样一个点对点系统实现的。


发布于: 2020 年 08 月 13 日阅读数: 325
用户头像

AIbot

关注

西风不起, 东风赐祚 2020.04.10 加入

默默无闻的CODER

评论

发布
暂无评论
DSN 主流项目调研 3——Orbit数据库的故事