文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Golang如何实现事务型内存数据库

2023-07-05 09:09

关注

这篇文章主要讲解了“Golang如何实现事务型内存数据库”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang如何实现事务型内存数据库”吧!

特性

MossDB是一个纯Golang编写、可嵌入的、键值型内存数据库,包含以下特性

命名由来

Moss有苔、苔花的含义,MossDB的名字来源于清代袁牧的一句诗:

苔花如米小,也学牡丹开

MossDB虽小,但五脏俱全,也支持了很多重要功能。另外,巧合的是《流浪地球2》中的超级计算机550W名字就是Moss。

架构

内存数据库虽然使用简单,实现起来却有很多细节,Golang目前也存在不少优秀的开源内存数据库,比如buntdb、go-memdb,在编写MossDB过程中也借鉴了一些它们的特性。

MossDB的架构如图:

Golang如何实现事务型内存数据库

自上往下分为:

快速开始

MossDB可嵌入到Go程序中,可以通过go get获取

go get github.com/qingwave/mossdb

MossDB提供了易用的API,可以方便地进行数据处理,下面的示例代码展示了如何使用MossDB:

package mainimport ("log""github.com/qingwave/mossdb")func main() {    // create db instancedb, err := mossdb.New(&mossdb.Config{})if err != nil {log.Fatal(err)}    // set, get datadb.Set("key1", []byte("val1"))    log.Printf("get key1: %s", db.Get("key1"))    // transaction    db.Tx(func(tx *mossdb.Tx) error {        val1 := tx.Get("key1")        tx.Set("key2", val1)        return nil    })}

更多示例见源码

具体实现

从下往上分别介绍下MossDB如何设计与实现,以及相关的细节。

AOF持久化

AOF源于Redis提供两种持久化技术,另外一种是RDB,AOF是指在每次写操作后,将该操作序列化后追加到文件中,重启时重放文件中的对应操作,从而达到持久化的目的。其实现简单,用在MossDB是一个不错的选择,但需要注意的是AOF缺点同样明显,如果文件较大,每次重启会花费较多时间。

Redis的AOF是一种后写式日志,先写内存直接返回给用户,再写磁盘文件持久化,可以保证其高性能,但如果中途宕机会丢失数据。MossDB中的AOF采用了WAL(预写式日志)实现,先写Log再写内存,用来保证数据不会丢失,从而可以进一步实现事务。

那么采用WAL会不会影响其性能?每次必须等到落盘后才进行其他操作,WAL的每次写入会先写到内核缓冲区,这个调用很快就返回了,内核再将数据落盘。我们也可以使用fsync调用强制内核执行直接将数据写入磁盘。在MossDB中普通写操作之会不会直接调用fsync,事务写中强制开启fsync,从而平衡数据一致性与性能。

WAL的实现引用了tiwall/wal,其中封装了对Log文件的操作,可以支持批量写入。由于WAL是二进制的,必须将数据进行编码,通过varint编码实现,将数据长度插入到数据本体之前,读取时可以读取定长的数据长度,然后按长度读取数据本体。MossDB中数据格式如下:

type Record struct {Op        uint16KeySize   uint32ValSize   uint32Timestamp uint64TTL       uint64Key       []byteVal       []byte}

对应编码后的二进制格式为:

|------------------------------------------------------------|
| Op | KeySize | ValSize | Timestamp | TTL | Key    | Val    |
|------------------------------------------------------------|
| 2  | 4       | 4       | 8         | 8   | []byte | []byte |
|------------------------------------------------------------|

使用binary.BigEndian.PutUint16进行编码,解码时通过binary.BigEndian.Uint16,从而依次取得生成完整的数据。

存储引擎

MossDB提供了存储接口,只要实现了此接口都可以作为其后端存储

type Store interface {Get(key string) ([]byte, bool)Set(key string, val []byte)Delete(key string)Prefix(key string) map[string][]byteDump() map[string][]byteLen() int}

内置提供了HashMap与RadixTree两种方式,HashMap实现简单通过简单封装map可以快速进行查询与插入,但范围搜索性能差。RadixTree即前缀树,查询插入的时间复杂度只与Key的长度相关,而且支持范围搜索,MossDB采用Adaptive Radix Tree可以避免原生的前准树空间浪费。

由于RadixTree的特性,MossDB可以方便的进行前缀搜索,目前支持ListWatch操作。

事务实现

要实现事务必须要保证其ACID特性,MossDB的事务定义如下:

type Tx struct {db      *DB // DB实例commits []*Record // 对于写操作生成 Record, 用来做持久化undos   []*Record // 对于写操作生成 Undo Record,用于回滚}

MossDB中一次事务的流程主要包含以下几个步骤:

Watch

由于工作中经常使用Kubernetes,对于其Watch接口印象深刻,通过Watch来充当其事件总线,保证其声明式对象的管理。Kubernetes的Watch底层由etcd实现,MossDB也实现了类似的功能。

Watch的定义如下:

type Watcher struct {mu       sync.RWMutex // 锁watchers map[string]*SubWatcher // watchId与具体Watcher直接的映射keys     map[string]containerx.Set[string] // Watch单个keyranges   *art.Tree // 前缀Watchqueue    workqueue.WorkQueue // 工作队列,存放所有事件stop     chan struct{} // 是否中止}

通过工作队列模式,任何写操作都会同步追加到队列中,如果存在单个key的监听者,则通过watchers map获取到对应列表,依次发送事件。对于前缀Watch,我们不可能记录此前缀的所有Key,这里借鉴了etcd,通过RadixTree保存前缀Key,当有新事件时,匹配Key所在的路径,如果有监听者,则进行事件通知。

调用Watch会返回一个Channel,用户只需要监听Channel即可

func Watch(db *mossdb.DB) {key := "watch-key"ctx, cancel := context.WithTimeout(context.Background(), 1000*time.Millisecond)defer cancel()// start watch keych := db.Watch(ctx, key)log.Printf("start watch key %s", key)go func() { // 模拟发送eventdb.Set(key, mossdb.Val("val1"))db.Set(key, mossdb.Val("val2"))db.Delete(key)time.Sleep(100 * time.Second)db.Set(key, mossdb.Val("val3"))}()for {select {case <-ctx.Done():log.Printf("context done")returncase resp, ok := <-ch:if !ok {log.Printf("watch done")return}log.Printf("receive event: %s, key: %s, new val: %s", resp.Event.Op, resp.Event.Key, resp.Event.Val)}}    // Output    // 2023/02/23 09:48:50 start watch key watch-key// 2023/02/23 09:34:19 receive event: MODIFY, key: watch-key, new val: val1// 2023/02/23 09:34:19 receive event: MODIFY, key: watch-key, new val: val2// 2023/02/23 09:34:19 receive event: DELETE, key: watch-key, new val:// 2023/02/23 09:34:19 context done}

感谢各位的阅读,以上就是“Golang如何实现事务型内存数据库”的内容了,经过本文的学习后,相信大家对Golang如何实现事务型内存数据库这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯