pos机密钥储存失败,在 Golang 中编写基于磁盘的键值存储

 新闻资讯  |   2023-07-01 10:45  |  投稿人:pos机之家

网上有很多关于pos机密钥储存失败,在 Golang 中编写基于磁盘的键值存储的知识,也有很多人为大家解答关于pos机密钥储存失败的问题,今天pos机之家(www.poszjia.com)为大家整理了关于这方面的知识,让我们一起来看下吧!

本文目录一览:

1、pos机密钥储存失败

pos机密钥储存失败

我一直在考虑阅读一篇计算机科学论文,并基于它实施一个项目。分布式系统、网络和数据库是让我着迷的一些东西。但是,我一直在寻求实施一个更平易近人的项目,以避免最初被淹没。我偶然通过Avinash的项目:CaskDB看到了Bitcask论文。

在快速阅读了这篇相当短的论文后,我决定编写一个相同的 Golang 实现,因为它看起来像一个令人兴奋的项目。如果您有兴趣查看完整的项目,请查看BarrelDB。

Bitcask 是基于磁盘的键值存储引擎,专为快速读写操作而设计。它主要由Riak(分布式数据库)作为存储引擎之一在生产中使用。引擎盖下的Bitc桶具有简单而巧妙的设计。它以仅追加模式写入文件。这意味着仅通过附加到文件末尾来执行写入,从而避免了执行任何随机磁盘 I/O 查找的需要。

让我们看一下Bitcask的各个组件:

记录的格式CRC:存储值的校验和,保证数据一致性时间戳:UNIX 格式的时间戳,存储为 int32。到期:如果记录定义了到期,则 UNIX 格式的时间戳将存储为 int32。密钥大小:密钥的大小(以字节为单位)值大小:值的大小(以字节为单位)钥匙价值

与键/值一起存储的附加元数据用固定宽度的标头表示。每个字段表示为 ,因此标头的总大小为 4*5 = 20 字节。下面是对此记录进行编码和解码的代码:int32

type Record struct { Header Header Key string Value []byte}// Header represents the fixed width="360px",height="auto" />

Decode takes a record object decodes the binary value the buffer.func (h *Header) decode(record []byte) error { return binary.Read(bytes.NewReader(record), binary.LittleEndian, h)}

记录在存储在磁盘上之前以二进制格式编码。

数据文件

“数据文件”(用于磁盘上的数据库文件的术语)是所有写入操作的仅追加记录。Bitcask 的一个实例可以有多个数据文件。但是,只有一个“活动”数据文件。在 BarrelDB 中,goroutine 定期在后台运行,以检查活动数据库文件的大小是否已超过阈值,然后旋转活动文件。它将此数据库文件追加到“过时”数据文件列表中。所有新的写入只发生在“活动”数据文件上,过时的文件作为“压缩”过程的一部分进行合并(稍后将在帖子中描述)。

以下是 ais 的表示方式:dataFile

type DataFile struct { sync.RWMutex writer *os.File reader *os.File id int offset int}

它包含用于写入和读取文件的不同处理程序。我们有 2 个文件处理程序而不是重用同一个的原因是,它们仅在“仅追加”模式下打开。此外,由于活动文件可以旋转,因此可以设置编写器,确保不会在该文件上发生新的写入。writernil

writer, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, fmt.Errorf("error opening file for writing db: %w", err) } // Create a reader for reading the db file. reader, err := os.Open(path) if err != nil { return nil, fmt.Errorf("error opening file for reading db: %w", err) }键目录

除了将文件存储在磁盘上之外,Bitcask 还存储其他元数据,这些元数据定义了如何检索记录。此哈希表是具有此元数据的键映射,称为。这里要注意的重要一点是,这张地图中从未存储过。这使得Bitcask可以处理比RAM可以容纳的更重要的数据集。KeyDirvalue

// KeyDir represents an in-memory hash for faster lookups of the key.// Once the key is found in the map, the additional metadata, like the offset record// and the file ID is used to extract the underlying record from the disk.// Advantage is that this approach only requires a single disk seek of the db file// since the position offset (in bytes) is already stored.type KeyDir map[string]Meta// Meta represents some additional properties for the given key.// The actual value of the key is not stored in the in-memory hashtable.type Meta struct { Timestamp int RecordSize int RecordPos int FileID int}

在这里,告诉记录在整个文件中的位置偏移量(以字节为单位)。由于记录的位置与密钥一起存储在内存中,因此检索密钥不需要超过单个磁盘查找。Bitcask 即使数据库中有许多密钥,也能实现非常低的延迟。文件系统预读缓存还有助于提高性能,并且是免费的 - 无需设计单独的缓存机制。RecordPos

压 实

正如我们之前看到的,数据文件只是一个仅追加的写入序列。对键的任何修改都只是附加到数据文件的新记录。KeyDir 使用包含记录新位置的新元数据覆盖键的条目。因此,所有读取将自动返回更新的值。

通过为密钥写入“逻辑删除”记录,以类似的方式处理删除。当用户在删除密钥后请求密钥时,BarrelDB 可以检查该值是否等于逻辑删除值并返回相应的错误。

正如您所猜到的,如果我们不执行任何垃圾清理,我们的数据库将无限增长。需要修剪数据文件以删除过期/删除的记录并将所有过时的文件合并到单个活动文件中 - 以控制打开的文件数量。所有这些过程统称为“压缩”。

让我们来看看这些压缩例程中的每一个是如何在后台工作的:

合并

合并过程循环访问 KeyDir 中的所有键并获取其值。该值也可能来自过时的文件。更新新的键/值后,它会将它们写入新的活动文件。关闭所有旧文件处理程序,并从磁盘中删除过时的文件。KeyDir 的更新方式类似,因为新记录位于不同的位置/文件中。

提示文件

Bitcask 论文描述了一种创建最初加载到数据库中的“提示”文件以加快启动时间的方法。此文件对于在冷启动后引导 KeyDir 至关重要。这样可以避免遍历所有数据文件并按顺序读取其值。在 BarrelDB 中,编码用于将地图转储为转储。gobKeyDirgob

// generateHints encodes the contents of the in-memory hashtable// as `gob` and writes the data to a hints file.func (b *Barrel) generateHints() error { path := filepath.Join(b.opts.dir, HINTS_FILE) if err := b.keydir.Encode(path); err != nil { return err } return nil}

在启动期间,BarrelDB 会检查文件是否存在,解码此 gob 转储,然后将数据加载到其中。.hintsKeyDir

删除过期的密钥

goroutine以可配置的间隔运行,以检查密钥的值是否已过期。如果有,它将从 KeyDir 中删除该条目。在以下合并过程中,由于此条目不会出现在 KeyDir 中,因此在创建新数据文件时会自动删除该条目。

要检查密钥是否已过期,只需进行简单的检查,例如以 UNIX 纪元格式比较它们的时间戳,就足够了:time.Now().Unix() > int64(r.Header.Expiry)

瑞迪斯服务器

除了使用 BarrelDB 作为 Go 库之外,我还实现了一个与 Redis 兼容的服务器。我发现tidwall/redcon是一个易于使用的库,可以为 Go 应用程序创建一个与 Redis 兼容的服务器。我要做的就是包装 BarrelDB API 方法并定义 / 的处理程序。SETGET

我能够使用并连接到 BarrelDB 服务器:redis-cli

127.0.0.1:6379> set hello worldOK127.0.0.1:6379> get hello"world"基准

可以检查存储库中的实际基准。但是,我想指出一些结果的推论。redis-benchmark

首先,让我们使用 50 个并行客户端向服务器发送 100000 个请求。此命令为每个操作创建一个唯一键。SET

redis-benchmark -p 6379 -c 50 -t set -n 100000 -r 100000000Summary: throughput summary: 145985.41 requests per second latency summary (msec): avg min p50 p95 p99 max 0.179 0.016 0.183 0.207 0.399 1.727

因此,对于基于磁盘的 KV,每秒 140k 个请求一点也不差。但这里要注意的令人兴奋的事情是,即使您通过增加客户端来增加负载,性能也是可预测的:

redis-benchmark -p 6379 -c 200 -t set -n 100000 -r 100000000Summary: throughput summary: 140845.08 requests per second latency summary (msec): avg min p50 p95 p99 max 0.718 0.224 0.711 0.927 1.183 5.775

如果我们也增加请求数量(5 倍),吞吐量看起来几乎相同:

redis-benchmark -p 6379 -c 200 -t set -n 500000 -r 100000000Summary: throughput summary: 138350.86 requests per second latency summary (msec): avg min p50 p95 p99 max 0.748 0.056 0.711 0.879 1.135 63.135

这种魔力完全是因为Bitcask使用日志结构化哈希表(只是用于写入数据的仅附加记录)的方式。即使有大量记录,它所要做的就是写入文件的末尾,从而避免任何昂贵的 I/O 操作。

总结

总的来说,我对实施感到满意,因为我涵盖了论文中描述的所有内容。这个项目对我来说有很好的学习成果。我花了很多时间想出一个设计,用于构建不同的组件及其API方法,并在压缩过程中处理所有边缘场景。虽然,完全归功于Bitcask,因为它保持了如此优雅和简约的设计,但在基准测试中取得了一些重要的数字。这也提醒我们,简单不一定意味着不那么强大。BarrelDB

我期待通过添加对通过 Raft 连接的多个 BarrelDB 节点的支持来实现分布式 KV 存储。现在,去享受一些茶并将这个项目发布到 WWW :)

翻译原文: https://mrkaran.dev/posts/barreldb/?hmsr=toutiao.io&utm_campaign=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

以上就是关于pos机密钥储存失败,在 Golang 中编写基于磁盘的键值存储的知识,后面我们会继续为大家整理关于pos机密钥储存失败的知识,希望能够帮助到大家!

转发请带上网址:http://www.poszjia.com/news/76453.html

你可能会喜欢:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 babsan@163.com 举报,一经查实,本站将立刻删除。