并不总是需要将用户数据发送到服务器:您可以选择在浏览器中存储一些信息。它非常适合特定于设备的设置,例如接口配置(例如亮/暗模式)或不应通过网络传输的私有数据(例如加密密钥)。

考虑用于计时网络和页面事件的性能 API 。您可以在所有数据可用的时候上传所有数据,但具有讽刺意味的是,这是一个相当大的信息量,会影响页面性能。更好的选择是将其存储在本地,使用Web Worker处理统计信息,并在浏览器不那么忙时上传结果。

有两个跨浏览器客户端存储 API 可用:

  1. 网络存储

    同步名称-值对存储永久保存 ( localStore) 或当前会话 ( sessionStore)。浏览器允许每个域最多 5MB 的 Web 存储。

  2. 索引数据库

    一种异步的类似 NoSQL 的名称-值存储,可以保存数据、文件和 blob。每个域至少应该有 1GB 可用空间,并且最多可以达到剩余磁盘空间的 60%。

另一种存储选项WebSQL在 Chrome 和 Safari 的某些版本中可用。但是,它有 5MB 的限制,不一致,并在 2010 年被弃用。

在本教程中,我们将存储页面和所有资产的时间数据。Web 存储空间可能很慢而且太有限,所以 IndexedDB 是最好的选择。

如果您想在自己的网站上试用 IndexedDB,所有示例代码都可以在 Github上找到。

什么是索引数据库?

IndexedDB 于 2011 年首次实现,并于 2015 年 1 月成为 W3C 标准。它具有良好的浏览器支持,尽管它的回调和基于事件的 API 在我们拥有 ES2015+ 时显得笨拙。本文演示了如何编写基于 Promise 的包装器,以便您可以使用链接和asyncawait

请注意以下 IndexedDB 术语:

  • 数据库——顶级存储。一个域可以创建任意数量的 IndexedDB 数据库,但看到多个数据库是不常见的。只有同一域内的页面才能访问数据库。

  • 对象存储——相关数据项的名称/值存储。它类似于 MongoDB 中的集合或关系数据库中的表。

  • key — 用于引用对象存储中每条记录(值)的唯一名称。它可以使用自动增量数字生成,也可以设置为记录中的任何唯一值。

  • 索引——在对象存储中组织数据的另一种方式。搜索查询只能检查键或索引。

  • schema — 对象存储、键和索引的定义。

  • version — 分配给模式的版本号(整数)。IndexedDB 提供自动版本控制,因此您可以将数据库更新到最新模式。

  • 操作——数据库活动,例如创建、读取、更新或删除记录。

  • 事务——一组一个或多个操作。一个事务保证它的所有操作要么成功要么失败。它不能让一些人失败,也不能让其他人失败。

  • cursor — 一种迭代记录的方法,而不必一次将所有记录加载到内存中。

开发和调试数据库

在本教程中,您将创建一个名为performance. 它包含两个对象存储:

1. navigation

这存储页面导航时间信息(重定向、DNS 查找、页面加载、文件大小、加载事件等)。将添加一个日期以用作

2. resource

这会存储资源计时信息(其他资源的计时,例如图像、样式表、脚本、Ajax 调用等)。将添加一个日期,但可以同时加载两个或更多资产,因此将使用自动递增的 ID 作为关键。_ 将为日期和名称(资源的 URL)创建索引。

所有基于 Chrome 的浏览器都有一个应用程序选项卡,您可以在其中检查存储空间、人为限制容量以及擦除所有数据。Storage 树中的IndexedDB条目允许您查看、更新和删除对象存储、索引和单个记录。Firefox 的面板名为Storage

您还可以在隐身模式下运行您的应用程序,以便在关闭浏览器窗口后删除所有数据。

连接到 IndexedDB 数据库

indexeddb.js在检查 IndexedDB 支持时创建的包装类使用:

if ('indexedDB' in window) // ...
然后它通过以下方式打开一个数据库连接indexedDB.open():
  1. 数据库名称,和
  2. 可选版本整数。
const dbOpen = indexedDB.open('performance', 1);
必须定义三个重要的事件处理函数:
  1. dbOpen.onerror当无法建立 IndexedDB 连接时运行。

  2. dbOpen.onupgradeneeded当所需版本 ( 1) 大于当前版本0时运行(未定义数据库时)。处理函数必须运行 IndexedDB 方法,例如createObjectStore()和createIndex()来创建存储结构。

  3. dbOpen.onsuccess在建立连接并且完成任何升级时运行。中的连接对象dbOpen.result用于所有后续的数据操作。它被分配到this.db包装类中。

包装构造函数代码:

// IndexedDB wrapper class: indexeddb.js
export class IndexedDB {// connect to IndexedDB databaseconstructor(dbName, dbVersion, dbUpgrade) {return new Promise((resolve, reject) => {// connection objectthis.db = null;// no supportif (!('indexedDB' in window)) reject('not supported');// open databaseconst dbOpen = indexedDB.open(dbName, dbVersion);if (dbUpgrade) {// database upgrade eventdbOpen.onupgradeneeded = e => {dbUpgrade(dbOpen.result, e.oldVersion, e.newVersion);};}// success event handlerdbOpen.onsuccess = () => {this.db = dbOpen.result;resolve( this );};// failure event handlerdbOpen.onerror = e => {reject(`IndexedDB error: ${ e.target.errorCode }`);};});}// more methods coming later...}
一个performance.js脚本加载这个模块并实例化一个以perfDB页面加载后命名的新 IndexedDB 对象。它传递数据库名称 ( performance)、版本 ( 1) 和升级函数。构造indexeddb.js函数使用数据库连接对象、当前数据库版本和新版本调用升级函数:
// performance.js
import { IndexedDB } from './indexeddb.js';window.addEventListener('load', async () => {// IndexedDB connectionconst perfDB = await new IndexedDB('performance',1,(db, oldVersion, newVersion) => {console.log(`upgrading database from ${ oldVersion } to ${ newVersion }`);switch (oldVersion) {case 0: {constnavigation = db.createObjectStore('navigation', { keyPath: 'date' }),resource = db.createObjectStore('resource', { keyPath: 'id', autoIncrement: true });resource.createIndex('dateIdx', 'date', { unique: false });resource.createIndex('nameIdx', 'name', { unique: false });}}});// more code coming later...});

在某些时候,有必要更改数据库模式——可能是添加新的对象存储、索引或数据更新。在这种情况下,您必须增加版本(从12)。下一页加载将再次触发升级处理程序,因此您可以在语句中添加更多块,例如在对象存储中的属性上switch创建一个名为的索引:durationIdxdurationresource

case 1: {const resource = db.transaction.objectStore('resource');resource.createIndex('durationIdx', 'duration', { unique: false });
}

省略break每个块末尾的通常。case当有人第一次访问应用程序时,该case 0块将运行,然后是case 1所有后续块。任何已经在版本上1的人都将从case 1. IndexedDB 模式更新方法包括:

  • createIndex()
  • deleteIndex()
  • createObjectStore()
  • deleteObjectStore()
  • deleteDatabase()

加载页面的每个人都将使用相同的版本——除非他们的应用程序在两个或更多选项卡中运行onversionchange为了避免冲突,可以添加数据库连接处理程序indexeddb.js,提示用户重新加载页面:

// version change handler
dbOpen.onversionchange = () => {dbOpen.close();alert('Database upgrade required - reloading...');location.reload();};

您现在可以将performance.js脚本添加到页面并运行它以检查是否创建了对象存储和索引(DevTools应用程序存储面板):

<script type="module" src="./performance.js"></script>

记录性能统计

所有 IndexedDB 操作都包装在一个事务中。使用以下过程:

  1. 创建数据库事务对象。这定义了一个或多个对象存储(单个字符串或字符串数​​组)和访问类型:"readonly"用于获取数据,或"readwrite"用于插入和更新。

  2. objectStore()在事务范围内创建对 an 的引用。

  3. 运行任意数量的add()(仅插入)或put()方法(插入和更新)。

在类中添加一个新update()方法:IndexedDBindexeddb.js

// store itemupdate(storeName, value, overwrite = false) {return new Promise((resolve, reject) => {// new transactionconsttransaction = this.db.transaction(storeName, 'readwrite'),store = transaction.objectStore(storeName);// ensure values are in arrayvalue = Array.isArray(value) ? value : [ value ];// write all valuesvalue.forEach(v => {if (overwrite) store.put(v);else store.add(v);});transaction.oncomplete = () => {resolve(true); // success};transaction.onerror = () => {reject(transaction.error); // failure};});}

这会在命名存储中添加或更新(如果overwrite参数是true)一个或多个值,并将整个事务包装在 Promise 中。当transaction.oncomplete事务在函数结束时自动提交并且所有数据库操作都完成时,事件处理程序将运行。处理程序transaction.onerror报告错误。

IndexedDB 事件从操作冒泡到事务、存储和数据库。onerror您可以在接收所有错误的数据库上创建一个处理程序。像 DOM 事件一样,传播可以用event.stopPropagation().

performance.js脚本现在可以报告页面导航指标:

// record page navigation informationconstdate = new Date(),nav = Object.assign({ date },performance.getEntriesByType('navigation')[0].toJSON());await perfDB.update('navigation', nav);

和资源指标:

const res = performance.getEntriesByType('resource').map(r => Object.assign({ date }, r.toJSON()));await perfDB.update('resource', res);

在这两种情况下,都会将一个date属性添加到克隆的计时对象中,这样就可以在特定时间段内搜索数据。

阅读成绩记录

与其他数据库相比,IndexedDB 搜索是初级的。您只能通过键或索引值获取记录。您不能使用 SQL 的等效项JOIN或函数,例如AVERAGE()SUM()。所有记录处理都必须使用 JavaScript 代码处理;后台Web Worker线程可能是一个实用的选择。

.get()您可以通过将其键传递给对象存储或索引的方法并定义处理程序来检索单个记录onsuccess

// EXAMPLE CODE
const// new readonly transactiontransaction = db.transaction('resource', 'readonly'),// get resource object storeresource = transaction.objectStore('resource'),// fetch record 1request = resource.get(1);// request complete
request.onsuccess = () => {console.log('result:', request.result);
};// request failed
request.onerror = () => {console.log('failed:', request.error);
};

类似的方法包括:

  • .count(query)— 匹配记录数
  • .getAll(query)— 匹配记录的数组
  • .getKey(query)— 匹配的键(而不是分配给该键的值)
  • .getAllKeys(query)— 匹配键的数组

查询也可以是KeyRange参数来查找范围内的记录,例如IDBKeyRange.bound(1, 10)返回键在 1 到 10 之间的所有记录:

request = resource.getAll( IDBKeyRange.bound(1, 10) );

键范围选项:

  • IDBKeyRange.lowerBound(X)— 大于或等于X
  • IDBKeyRange.upperBound(X)— 小于或等于Y
  • IDBKeyRange.bound(X,Y)——介于X两者Y之间
  • IDBKeyRange.only(X)— 单键匹配X

lower、upper 和 bound 方法有一个可选的独占标志,例如IDBKeyRange.bound(1, 10, true, false)- 大于1(但不是1自身)且小于或等于 的键10

随着数据库变得越来越大,将整个数据集读入数组变得不可能。IndexedDB 提供了可以一次遍历每条记录的游标。该.openCursor()方法传递一个 KeyRange 和可选的方向字符串("next""nextunique""prev""preunique")。

向类中添加一个新fetch()方法,以使用传递给游标的回调函数搜索具有上限和下限的对象存储或索引。还需要另外两种方法:IndexedDBindexeddb.js

  1. index(storeName, indexName)— 返回对象存储或该存储上的索引,并且
  2. bound(lowerBound, upperBound)— 返回一个适当的 KeyRange 对象。
// get items using cursorfetch(storeName, indexName, lowerBound = null, upperBound = null, callback) {constrequest = this.index(storeName, indexName).openCursor( this.bound(lowerBound, upperBound) );// pass cursor to callback functionrequest.onsuccess = () => {if (callback) callback(request.result);};request.onerror = () => {return(request.error); // failure};}// start a new read transaction on object store or indexindex(storeName, indexName) {consttransaction = this.db.transaction(storeName),store = transaction.objectStore(storeName);return indexName ? store.index(indexName) : store;}// get bounding objectbound(lowerBound, upperBound) {if (lowerBound && upperBound) return IDBKeyRange.bound(lowerBound, upperBound);else if (lowerBound) return IDBKeyRange.lowerBound(lowerBound);else if (upperBound) return IDBKeyRange.upperBound(upperBound);}

performance.js脚本现在可以检索页面导航指标,例如domContentLoadedEventEnd在 2021 年 6 月期间全部返回:

// fetch page navigation objects in June 2021perfDB.fetch('navigation',null, // not an indexnew Date(2021,5,1,10,40,0,0), // lowernew Date(2021,6,1,10,40,0,0), // uppercursor => { // callback functionif (cursor) {console.log(cursor.value.domContentLoadedEventEnd);cursor.continue();}});

同样,您可以计算特定文件的平均下载时间并将其报告回OpenReplay:

// calculate average download time using indexletfilename = 'http://mysite.com/main.css',count = 0,total = 0;perfDB.fetch('resource', // object store'nameIdx',  // indexfilename,   // matching filefilename,cursor => { // callbackif (cursor) {count++;total += cursor.value.duration;cursor.continue();}else {// all records processedif (count) {const avgDuration = total / count;console.log(`average duration for ${ filename }: ${ avgDuration } ms`);// report to OpenReplayif (asayer) asayer.event(`${ filename }`, { avgDuration });}}});

在这两种情况下,cursor对象都被传递给回调函数,它可以:

  1. 获取记录值cursor.value
  2. 前进到下一个记录cursor.continue()
  3. 向前移动N记录cursor.advance(N)
  4. 更新记录cursor.update(data),或
  5. 删除记录cursor.delete()

cursornull当所有匹配的记录都已处理完毕。

检查剩余存储空间

浏览器会为 IndexedDB 分配大量存储空间,但最终会用完。新的基于 Promise 的StorageManager API可以计算域的剩余空间:

(async () => {if (!navigator.storage) return;constestimate = await navigator.storage.estimate(),// calculate remaining storage in MBavailable = Math.floor((estimate.quota - estimate.usage) / 1024 / 1024);console.log(`${ available } MB remaining`);})();

IE 或 Safari 不支持 API。当接近限制时,您可以选择删除较旧的记录。

结论

IndexedDB 是较旧且更复杂的浏览器 API 之一,但您可以添加包装器方法以采用 Promises 和asyncawait。如果您不想自己这样做,诸如idb之类的预构建库会有所帮助。

尽管存在缺点和一些不寻常的设计决策,IndexedDB 仍然是最快和最大的基于浏览器的数据存储。

使用 IndexedDB 进行大数据存储相关推荐

  1. 澄清大数据存储——系统集成商篇

    大数据考验整合能力 大数据在带来新的商机和用户的同时,也带来了诸多挑战. 大数据存储主要考验的是技术整合能力和资源整合能力. 大数据是一项持久的工程,也是一个不断迭代的过程,不能一蹴而就. 业务集中在 ...

  2. 全球海拔最高的国际级数据中心竣工,西藏有了大数据存储中心

    全球海拔最高的国际级数据中心.西藏首个大数据存储中心,宁算科技集团拉萨一体化项目数据中心(一期)工程12月18日通过竣工验收. 项目位于海拔3670米的拉萨市国家级高新区信息产业园内,总建筑面积为2. ...

  3. 《大数据存储:MongoDB实战指南》一1.1 什么是大数据

    本节书摘来异步社区<大数据存储:MongoDB实战指南>一书中的第1章,第1.1节,作者: 郭远威 , 彭文波 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区" ...

  4. 面向智能电网的电力大数据存储与分析应用

    面向智能电网的电力大数据存储与分析应用 崔立真1, 史玉良1, 刘磊1, 赵卓峰2, 毕艳冰3 1. 山东大学计算机科学与技术学院,山东 济南 250101 2. 北方工业大学云计算研究中心,北京 1 ...

  5. 云时代的大数据存储-云HBase

    纵观数据库发展的几十年,从网状数据库.层次数据库到RDBMS数据库,在最近几年的NewSQL的兴起,加上开源的运动,再加上云的特性,可以说是日新月异.在20世纪80年代后,大部分的业务确定了使用RDB ...

  6. 管理大数据存储的十大技巧

    在1990年,每一台应用服务器都倾向拥有直连式系统(DAS).SAN的构建则是为了更大的规模和更高的效率提供共享的池存储.Hadoop已经逆转了这一趋势回归DAS.每一个Hadoop集群都拥有自身的- ...

  7. 基于HBase的大数据存储在京东的应用场景

    引言 HBase是一个高可靠性.高性能.面向列.可伸缩的分布式存储系统,适用于结构化的存储,底层依赖于Hadoop的HDFS,利用HBase技术可在廉价PCServer上搭建起大规模结构化存储集群.因 ...

  8. 主流大数据存储解决方案评析

    EMC Isilon:横向扩展 性能突出 大数据存储不是一类单独的产品,它有很多实现方式.EMC Isilon存储事业部总经理杨兰江概括说,大数据存储应该具有以下一些特性:海量数据存储能力,可轻松管理 ...

  9. 1.大数据存储选型——何时用hbase

    数据库发展 NoSQL Sharding-nothing 存储选型 要搞懂大数据存储选型,首先必须得了解数据库的发展历史,了解关系数据库的优势和缺点,才能进一步考虑如何处理这些问题. 数据库发展 简单 ...

最新文章

  1. Difference Between InnoDb and MyISAM(个人觉着是好文章,简单易懂,推荐看)
  2. 算法练习day15——190403(简介、求n!、汉诺塔、打印字符串的子序列、打印字符串的全排列、母牛生小牛、最小路径和、累加和是否达到给定值)
  3. Best Free Network Performance Test tool – Iperf
  4. Notification Swift 3 0
  5. JavaScript创建对象:深入理解编程原理
  6. 为企业量身定制IT资产管理解决方案(一)
  7. c primer plus 第七章 第十题
  8. nb物联网 bc95模块联网 bc95使用
  9. Basic 语言发展史
  10. 辞职信 求助编辑百科名片
  11. CSS3之图片列表展示特效
  12. IP种子眼中的《延禧攻略》流落何处?
  13. IIS 启动不了(发生意外错误0x8ffe2740)
  14. android+添加网络权限,已在AndroidManifest.xml增添网络权限,运行还报错
  15. win10更改时间崩溃
  16. 计算机思维使用的方法是,计算机思维
  17. 飞机躲子弹小游戏案例
  18. Spring事务和MySQL事务详解面试
  19. Dataset之GermanCreditData:GermanCreditData数据集的简介、下载、使用方法之详细攻略
  20. 如何强迫您的Apple Watch与iPhone同步

热门文章

  1. VUE 引用腾讯地图
  2. 最短路径算法(上)——迪杰斯特拉(Dijikstra)算法
  3. 华为破除魔咒:任正非的自我革命
  4. gtx1060和gtx1650 的差距 哪个好
  5. [HDU5873] [2016亚洲区域赛大连网络赛] Football Games [构造][兰道定理]
  6. 关于计算机知识的趣事,最全计算机发展史
  7. 《蟋蟀的xss淫荡教程之如何劫持OSC用户账号》
  8. win7开机登录界面的壁纸怎样更换修改
  9. 对于我们大冒险游戏的评论回复
  10. ibm服务器 产品型号对应表,产品线最全 IBM中小企业服务器选型指南