【存储】blotdb的原理及实现(2)

news2024/11/25 15:45:29

【存储】etcd的存储是如何实现的(3)-blotdb

在etcd系列中,我们对作为etcd底层kv存储的boltdb进行了比较全面的介绍。但是还有两个点没有涉及。

第一点是boltdb如何和磁盘文件交互。
持久化存储和我们一般业务应用程序的最大区别就是其强依赖磁盘文件。一方面文件数据结构和内存数据结构的差异很大,需要设计合适的文件数据结构(文件布局)来保证足够的读写效率;另一个方面,在与磁盘文件的读写交互上也需要做各种优化以提升db的整体性能。boltdb的文件布局在上一篇中已经介绍过了,本篇中会介绍mmap,boltdb用来做文件交互的技术。

第二点是boltdb如何管理free page。
在第一篇中我们讲过,boltdb采用了shadow paging(影子分页)的实现,这种类似copy

文章目录

  • mmap
  • freelist
    • 数组freelist
    • map freelist
    • 页的释放
      • 事务的记录
      • 页的释放

mmap

mmap是linux提供的一个系统调用。

mmap的作用是将进程的一块虚拟内存和文件相映射,通过对该虚拟内存的读写就可以直接对文件进行读写。相对于一般的使用read、write系统调用来读写文件的情况,使用mmap可以减少用户空间和内核空间之间的数据拷贝,提高效率。

mmap的原理是将一块虚拟内存和一块内核物理内存相映射,以此来减少用户空间和内核空间的数据拷贝。更具体的细节这里就不展开,网上有不少文章都介绍的非常详细,感兴趣可自行了解。

mmap是一种高效的操作文件的方式,但是应用在数据库也会有一些问题。这些问题产生的根本原因就是在mmap中,内存回写文件完全由内核管理而无法由存储应用层控制。可能导致的后果有:

  • 事务安全

在mmap中,内存刷盘的过程完全由操作系统控制,其无法感知上层应用的情况,会存在事务尚未提交,就将脏页刷盘的情况。这种情况可能会对事务造成影响。

博主认为在存在mvcc的db中其实是没有影响的,如果没有mvcc,则确实会产生中间状态。因为mvcc是存储应用层针对事务设计,如果没有mvcc,则事务完全依赖持久化能力。当然实际情况会更复杂,应该根据不同的实现具体情况具体分析。

解决mmap事务安全可以采用wal+copy-on-write,或者采用shadow paging(影子分页)。boltdb采用的是shadow paging。其能解决问题的根本原因是shadow paging中,每次写入都会将更新写入新的页而不是在原有页上update。在页表刷盘前,即使新的页被回写磁盘,该页也只会被当作空白页对待。

  • i/o停顿

操作系统无法感知存储应用层对数据的使用情况,其页面的置换无法保证db的热点数据在内存中,会导致i/o的性能波动。

除以上问题外,mmap还存在一些其他的问题,数据库大神andy专门写了一篇论文来论证为什么不要在数据库中使用mmap,详情见论文。但实际上,在工业界的很多知名项目中都使用了mmap,其中的考量博主目前也没有深入研究,留待下回分解。

freelist

freelist在内存中维护了所有的空闲页,以便快速的进行页的分配。boltdb支持两种形式的freelist,分别以数组和map的形式维护空闲页面,可以在创建DB对象时通过option中的FreelistType参数控制。

const (
    // FreelistArrayType indicates backend freelist type is array
    FreelistArrayType = FreelistType("array")
    // FreelistMapType indicates backend freelist type is hashmap
    FreelistMapType = FreelistType("hashmap")
)

官方解释中,数组形式的freelist实现简单,但是性能较差,尤其当db比较大时,并且容易产生文件碎片。map形式的freelist性能很快,在文件碎片问题上表现更好,但不能保证分配的页是offset最小的页。默认使用数组形式的freelist。

数组freelist

数组形式的freelist使用数组按照升序来维护所有的page id。当需要分配一块n页的连续空间时,会从前向后找到第一块大于n页的连续空间,从中截取n页分配。这种形式保证了我们一定能拿到offset最小的符合要求的page。但是遍历的方式是O(n)的复杂度,当db很大时,可能会产生严重的性能问题。同时这种方式也很容易产生文件碎片。

// 省略其他字段
type freelist struct {
    freelistType   FreelistType                // freelist type
    ids            []pgid                      // all free and available free page ids.
}

map freelist

map形式的freelist采用三个map维护空闲页,或者说连续的页面组成的不同大小的连续空间。

  • freemaps的key是空间大小(页数表示),value是以对应大小连续空间的起始page id表示的集合;
  • forwardMap的key是连续空间起始page id,value为连续空间大小;
  • backwardMap的key是连续空间结束page id,value为连续空间大小;
// 省略其他字段
type freelist struct {
    freelistType   FreelistType                // freelist type
    freemaps       map[uint64]pidSet           // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
    forwardMap     map[pgid]uint64             // key is start pgid, value is its span size
    backwardMap    map[pgid]uint64             // key is end pgid, value is its span size
}

type pidSet map[pgid]struct{}

当需要分配一块n页的连续空间时,会在freemaps中查找是否存在n页的连续空间,如有,则选择一块分配,否则随机选择大于n页的连续空间分配(这样看在碎片问题上也没好到哪去)。forwardMap和backwardMap的作用则是在释放内存时进行连续空间的合并。

页的释放

上面提到了freelist有数组和map两种实现,其差别主要在空闲页的分配上。个人觉得freelist的最有意思的点在页的释放上,这也是freelist跨事务管理page的体现。

在介绍具体的实现前,我们先回顾下boltdb事务的特点。

  • boltdb支持同时进行多个读事务和最多一个写事务。
  • 读事务是快照读,快照的内容是读事务开始前最近一次已提交写事务。
  • 读事务不会对db的数据造成影响,但是会导致过期的页不能被释放。(长读事务会导致db空间暴涨)

回顾过以上内容,我们再来看具体的实现。

事务的记录

boltdb会将读写事务和只读事务分别记录。其中读写事务同时最多存在一个,只读事务同时存在多个,采用数组记录,并且boltdb会按照txnid升序维护只读事务。

// 省略其他字段
type DB struct {
    rwtx     *Tx         // 读写事务
    txs      []*Tx       // 只读事务
}

当事务结束(提交或者回滚)时,会调用事务的close方法消除事务。

// 省略多余代码
func (tx *Tx) close() {
    if tx.db == nil {
       return
    }
    if tx.writable {
       // Remove transaction ref & writer lock.
       tx.db.rwtx = nil
    } else {
       tx.db.removeTx(tx)
    }
}

页的释放

页的释放分为两部分:

  • 读写事务中有delete或者update(影子分页的特点)时将page标记为待释放;
  • 持有待释放page的只读事务结束,读写事务开始前将不被只读事务持有的待释放page释放;

在读写事务中,当发生update或者delete操作时,对应的页需要被释放。但是这些页不能在读写事务提交时立刻被释放,因为可能会被只读事务的快照持有。

所以freelist对外提供free方法,记录当前事务需要释放的页。读写事务中在对应的位置调用free方法。

待释放的page以txPending的方式组织,这种组织方式是为了在事务回滚时快速进行回滚操作。

type txPending struct {
    ids              []pgid
    alloctx          []txid // txids allocating the ids
    lastReleaseBegin txid   // beginning txid of last matching releaseRange
}

free方式的实现如下。其会记录释放的页以及对应分配该页的txnid。在boltdb中只有开启读写事务才会对txnid进行递增,所以txnid可以认为是版本的概念。当一个版本不被只读事务持有,那么该版本分配的待释放页就可以释放。在boltdb中,只读事务永远只能拿到最新版本的快照而无法获取旧版本的快照,所以页总是可以被释放。但同时长的只读事务也会导致页迟迟不能释放,从而可能会导致db空间快速增长。

// free releases a page and its overflow for a given transaction id.
// If the page is already free then a panic will occur.
func (f *freelist) free(txid txid, p *page) {
    if p.id <= 1 {
       panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id))
    }

    // Free page and all its overflow pages.
    txp := f.pending[txid]
    if txp == nil {
       txp = &txPending{}
       f.pending[txid] = txp
    }
    allocTxid, ok := f.allocs[p.id]
    if ok {
       delete(f.allocs, p.id)
    } else if (p.flags & freelistPageFlag) != 0 {
       // Freelist is always allocated by prior tx.
       allocTxid = txid - 1
    }

    for id := p.id; id <= p.id+pgid(p.overflow); id++ {
       // Verify that page is not already free.
       if _, ok := f.cache[id]; ok {
          panic(fmt.Sprintf("page %d already freed", id))
       }
       // Add to the freelist and cache.
       txp.ids = append(txp.ids, id)
       txp.alloctx = append(txp.alloctx, allocTxid)
       f.cache[id] = struct{}{}
    }
}

在新读写事务开启时,会根据txPending和当前只读事务的状态来释放待释放的page,可以认为是一种lazy的策略,但是在这个场景下效果很好。

boltdb会遍历进行中的所有只读事务(db.Txs),调用release和releaseRange方法来释放已结束版本的待释放页。

func (db *DB) freePages() {
    // Free all pending pages prior to earliest open transaction.
    sort.Sort(txsById(db.txs))
    minid := txid(0xFFFFFFFFFFFFFFFF)
    if len(db.txs) > 0 {
       minid = db.txs[0].meta.txid
    }
    if minid > 0 {
       db.freelist.release(minid - 1)
    }
    // Release unused txid extents.
    for _, t := range db.txs {
       db.freelist.releaseRange(minid, t.meta.txid-1)
       minid = t.meta.txid + 1
    }
    db.freelist.releaseRange(minid, txid(0xFFFFFFFFFFFFFFFF))
    // Any page both allocated and freed in an extent is safe to release.
}

至此,我们对boltdb的各个方面进行了比较完善的讲解。


如果觉得本文对您有帮助,可以请博主喝杯咖啡~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1267719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

想要更快的文件传输?看看这些aspera的替代方案吧

随着数据量的增大&#xff0c;文件传输已经成为许多公司和组织日常工作中必不可少的环节之一。而对于大容量、海量数据的传输&#xff0c;普通的传输方式可能甚至无法胜任。Aspera作为一种高效的文件传输协议应运而生&#xff0c;其能够在处理大容量、高速传输方面表现出色。然…

Excel导出操作

<div class"right"> <el-button size"mini" click"exportEmployee">excel导出</el-button></div>安装file-saver $ npm i file-saver $ yarn add file-saver //下包后引入 import FileSaver from "file-sav…

开放式耳机怎么选?自费千元测评,百元、千元价位选哪个

开放式耳机以其不入耳式设计&#xff0c;更容易带给用户舒适的佩戴体验&#xff0c;也不影响使用中聆听周围声响&#xff0c;还可以保证长时间的舒适佩戴&#xff0c;适配漫长的通勤、游玩旅程。当然&#xff0c;开放式耳机种类也有许多&#xff0c;究竟哪一款更适合大家呢&…

离散系统的频域分析(数字信号处理实验2-1)

创建具有15 Hz和40 Hz分量频率的信号&#xff0c;叠加两个信号形成混合信号x&#xff0c;使用fft命令绘制x的频域图&#xff0c;标注频率为横坐标&#xff0c;平均能量为纵坐标。 文章目录 一.题目二.实验目的三.实验仪器四.实验原理1.MATLAB使用函数2.离散傅里叶变换(DFT)实验…

人工智能(pytorch)搭建模型21-基于pytorch搭建卷积神经网络VoVNetV2模型,并利用简单数据进行快速训练

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(pytorch)搭建模型21-基于pytorch搭建卷积神经网络VoVNetV2模型&#xff0c;并利用简单数据进行快速训练。VoVNetV2模型是计算机视觉领域的一个重要研究成果&#xff0c;它采用了Voice of Visual Residual&…

“2024上海智博会、2024北京智博会”双展联动,3月上海,6月北京

“2024上海智博会、2024北京智博会”双展联动&#xff0c;将分别于3月和6月在上海和北京举办。这两个展会旨在充分展示智慧城市、人工智能、物联网、大数据、软件等新兴行业的最新产品和技术。 作为中国最具影响力和创新力的智能科技展会&#xff0c;上海智博会和北京智博会吸引…

C#——Delegate(委托)与Event(事件)

C#——Delegate&#xff08;委托&#xff09;与Event&#xff08;事件&#xff09; 前言一、Delegate&#xff08;委托&#xff09;1.是什么&#xff1f;2.怎么用&#xff1f;Example 1&#xff1a;无输入无返回值Example 2&#xff1a;有输入Example 3&#xff1a;有返回值Exa…

实现数据一体化的有效措施

数据一体化的趋势 数据一体化是指将企业内部和外部的各种数据资源整合、统一管理和共享利用的过程&#xff0c;是当前信息化发展的重要趋势。 统一数据平台&#xff1a;企业越来越意识到数据是重要资产&#xff0c;需要建立统一的数据平台&#xff0c;集中管理和利用各类数据资…

直击广州车展 | 远航汽车“卷”出新高度

第23届广州车展作为2023年汽车行业的年度收官之作&#xff0c;成为各大汽车厂商“秀肌肉”的绝佳舞台&#xff0c;22万平方米的开放展区内容纳了1132辆展车&#xff0c;包括全球首发车59辆、概念车20辆、新能源车469辆。 新能源汽车产业发展迅猛&#xff0c;得益于新能源车型在…

开源vs闭源,大模型的未来在哪一边?

开源和闭源&#xff0c;两种截然不同的开发模式&#xff0c;对于大模型的发展有着重要影响。开源让技术共享&#xff0c;吸引了众多人才加入&#xff0c;推动了大模的创新。而闭源则保护了商业利益和技术优势&#xff0c;为大模型的商业应用提供了更好的保障。 那么&#xff0c…

Android-P CameraSerivce

0 前言 本文重点分析Android-P的CameraService实现。 验证:Goldfish模拟器 1 定义 图1.1 CameraService ICameraServiceframeworks/av/camera/aidl/android/hardware/ICameraService.aidlBnCameraServiceout/soong/.intermediates/frameworks/av/camera/libcamera_client/an…

如何远程开关机电脑丨远程开关机电脑的小技巧

在日常生活和工作中&#xff0c;我们可能需要远程控制电脑的开关机。下面就介绍几种常用的远程开关机方法。 方法一&#xff1a; 一、自行下载域之盾软件 https://www.yuzhidun.cn/https://www.yuzhidun.cn/ 二、在一台老板电脑上部署管理端&#xff0c;在想要远程开关机的电…

03、K-means聚类实现步骤与基于K-means聚类的图像压缩(2)

03、K-means聚类实现步骤与基于K-means聚类的图像压缩&#xff08;2&#xff09; 工程下载&#xff1a;K-means聚类实现步骤与基于K-means聚类的图像压缩 其他&#xff1a; 03、K-means聚类实现步骤与基于K-means聚类的图像压缩&#xff08;1&#xff09; 03、K-means聚类实现…

day66

今日回顾内容 web框架 django 路由控制 视图层 web框架 一、什么是web框架 Web框架&#xff08;Web framework&#xff09;是一种开发框架&#xff0c;用来支持动态网站、网络应用和网络服务的开发。这大多数的web框架提供了一套开发和部署网站的方式&#xff0c;也为web行…

从0开始学习JavaScript--JavaScript中的解构赋值及使用场景

在现代JavaScript中&#xff0c;解构赋值是一种强大而灵活的语法特性&#xff0c;它允许从数组或对象中提取值并赋给变量。这种语法不仅使代码更简洁&#xff0c;而且提高了可读性。在本篇文章中&#xff0c;将深入探讨JavaScript中解构赋值的基本概念、语法规则以及丰富的使用…

element-plus 使用密码输入框的自定义图标

<el-inputv-model"ruleFormPassword.newPassword"placeholder"请输入新密码":type"showPassword ? text : password":style"{ width: 360px }"><template #suffix><span class"input_icon" click"swit…

JavaScript基础—函数、参数、返回值、作用域、变量、匿名函数、综合案例—转换时间,逻辑中断,转换为Boolean型

版本说明 当前版本号[20231129]。 版本修改说明20231126初版20231129完善部分内容 目录 文章目录 版本说明目录JavaScript 基础 - 第4天笔记函数声明和调用声明&#xff08;定义&#xff09;调用细节补充 参数形参和实参函数默认值 返回值作用域全局作用域局部作用域 变量全…

【论文阅读】基于隐蔽带宽的汽车控制网络鲁棒认证(三)

文章目录 第六章 通过认证帧定时实现VulCAN的非once同步6.1 问题陈述6.2 方法概述6.3 动机和缺点6.3.1 认证帧定时隐蔽通信6.3.2 VulCAN的梵蒂冈后端Nonce同步的应用 6.4 设计与实现6.4.1发送方6.4.2 接收方6.4.3 设计参数配置6.4.4 实现 6.5 安全注意事项6.5.1 系统模型6.5.2攻…

提升认知|为什么比尔盖茨在地上发现100美元也会捡?

哈喽呀&#xff0c;大家好&#xff0c;我是雷工&#xff01; 大概在高中时代&#xff0c;听到过这么一个段子&#xff0c;“说如果地上有100美元&#xff0c;比尔盖茨是不会去捡的&#xff0c;因为他弯腰去捡100美元浪费的时间足够其创造1000美元以上的价值。” 当时听完也觉得…

场景应用丨厦门水环境综合治理监测系统效果和作用

厦门&#xff0c;这座美丽的海滨城市&#xff0c;其水资源的状况与水环境的治理对于城市的生存与发展至关重要。近年来&#xff0c;厦门致力于打造生态宜居的城市环境&#xff0c;对城市生命线——水资源的保护与治理给予了极大的关注。其中&#xff0c;水资源综合治理监测系统…