IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 使用 IndexedDB 进行大数据存储 -> 正文阅读

[大数据]使用 IndexedDB 进行大数据存储

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

考虑用于计时网络和页面事件的性能 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 的包装器,以便您可以使用链接和async/?await

请注意以下 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 database
  constructor(dbName, dbVersion, dbUpgrade) {

    return new Promise((resolve, reject) => {

      // connection object
      this.db = null;

      // no support
      if (!('indexedDB' in window)) reject('not supported');

      // open database
      const dbOpen = indexedDB.open(dbName, dbVersion);

      if (dbUpgrade) {

        // database upgrade event
        dbOpen.onupgradeneeded = e => {
          dbUpgrade(dbOpen.result, e.oldVersion, e.newVersion);
        };

      }

      // success event handler
      dbOpen.onsuccess = () => {
        this.db = dbOpen.result;
        resolve( this );
      };

      // failure event handler
      dbOpen.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 connection
  const perfDB = await new IndexedDB(
    'performance',
    1,
    (db, oldVersion, newVersion) => {

      console.log(`upgrading database from ${ oldVersion } to ${ newVersion }`);

      switch (oldVersion) {

        case 0: {

          const
            navigation = 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 模式更新方法包括:

加载页面的每个人都将使用相同的版本——除非他们的应用程序在两个或更多选项卡中运行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 item
  update(storeName, value, overwrite = false) {

    return new Promise((resolve, reject) => {

      // new transaction
      const
        transaction = this.db.transaction(storeName, 'readwrite'),
        store = transaction.objectStore(storeName);

      // ensure values are in array
      value = Array.isArray(value) ? value : [ value ];

      // write all values
      value.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 information
  const
    date = 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 transaction
  transaction = db.transaction('resource', 'readonly'),

  // get resource object store
  resource = transaction.objectStore('resource'),

  // fetch record 1
  request = resource.get(1);

// request complete
request.onsuccess = () => {
  console.log('result:', request.result);
};

// request failed
request.onerror = () => {
  console.log('failed:', request.error);
};

类似的方法包括:

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

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

键范围选项:

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 cursor
  fetch(storeName, indexName, lowerBound = null, upperBound = null, callback) {

    const
      request = this.index(storeName, indexName)
        .openCursor( this.bound(lowerBound, upperBound) );

    // pass cursor to callback function
    request.onsuccess = () => {
      if (callback) callback(request.result);
    };

    request.onerror = () => {
      return(request.error); // failure
    };

  }


  // start a new read transaction on object store or index
  index(storeName, indexName) {

    const
      transaction = this.db.transaction(storeName),
      store = transaction.objectStore(storeName);

    return indexName ? store.index(indexName) : store;

  }


  // get bounding object
  bound(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 2021
  perfDB.fetch(
    'navigation',
    null, // not an index
    new Date(2021,5,1,10,40,0,0), // lower
    new Date(2021,6,1,10,40,0,0), // upper
    cursor => { // callback function

      if (cursor) {
        console.log(cursor.value.domContentLoadedEventEnd);
        cursor.continue();
      }

    }
  );

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

// calculate average download time using index
  let
    filename = 'http://mysite.com/main.css',
    count = 0,
    total = 0;

  perfDB.fetch(
    'resource', // object store
    'nameIdx',  // index
    filename,   // matching file
    filename,
    cursor => { // callback

    if (cursor) {

      count++;
      total += cursor.value.duration;
      cursor.continue();

    }
    else {

      // all records processed
      if (count) {

        const avgDuration = total / count;

        console.log(`average duration for ${ filename }: ${ avgDuration } ms`);

        // report to OpenReplay
        if (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;

  const
    estimate = await navigator.storage.estimate(),

    // calculate remaining storage in MB
    available = Math.floor((estimate.quota - estimate.usage) / 1024 / 1024);

    console.log(`${ available } MB remaining`);

})();

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

结论

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

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

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-06-16 21:45:45  更:2022-06-16 21:47:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:47:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码