【基础篇】一个键值数据库包含什么?

背景

今天,在构造这个简单的键值数据库时,我们只需要关注整体架构和核心模块。这就相当于医学上在正式解剖人体之前,会先解剖一只小白鼠。我们通过剖析这个最简单的键值数据库,来迅速抓住学习和调优 Redis 的关键。

我们把这个简单的键值数据库称为 SimpleKV。需要注意的是,GitHub 上也有一个名为 SimpleKV 的项目,这跟我说的 SimpleKV 不是一回事,我说的只是一个具有关键组件的键值数据库架构。

怎么构建一个 SimpleKV

开始构造 SimpleKV 时,首先就要考虑里面可以存什么样的数据,对数据可以做什么样的操作,也就是数据模型和操作接口。

可以存哪些数据?

对于键值数据库而言,基本的数据模型是 key-value 模型。SimpleKV 也不例外。在 SimpleKV 中,key 是 String 类型,而 value 是基本数据类型,例如 String、整型等。

但是,SimpleKV 毕竟是一个简单的键值数据库,对于实际生产环境中的键值数据库来说,value 类型还可以是复杂类型。

不同键值数据库支持的 key 类型一般差异不大,而 value 类型则有较大差别。我们在对键值数据库进行选型时,一个重要的考虑因素是它支持的 value 类型。例如,Memcached 支持的 value 类型仅为 String 类型,而 Redis 支持的 value 类型包括了 String、哈希表、列表、集合等。Redis 能够在实际业务场景中得到广泛的应用,就是得益于支持多样化类型的 value。

从使用的角度来说,不同 value 类型的实现,不仅可以支撑不同业务的数据需求,而且也隐含着不同数据结构在性能、空间效率等方面的差异,从而导致不同的 value 操作之间存在着差异。

可以对数据做什么操作?

知道了数据模型,接下来,我们就要看它对数据的基本操作了。SimpleKV 是一个简单的键值数据库,因此,基本操作无外乎增删改查,即 PUT、GET 和 DELETE。

在实际的业务场景中,我们经常会碰到这种情况:查询一个用户在一段时间内的访问记录。这种操作在键值数据库中属于 SCAN 操作,即根据一段 key 的范围返回相应的 value 值。因此,PUT/GET/DELETE/SCAN 是一个键值数据库的基本操作集合。

此外,实际业务场景通常还有更加丰富的需求,例如,在黑白名单应用中,需要判断某个用户是否存在。如果将该用户的 ID 作为 key,那么,可以增加 EXISTS 操作接口,用于判断某个 key 是否存在。对于一个具体的键值数据库而言,你可以通过查看操作文档,了解其详细的操作接口。

当然,当一个键值数据库的 value 类型多样化时,就需要包含相应的操作接口。例如,Redis 的 value 有列表类型,因此它的接口就要包括对列表 value 的操作。后面我也会具体介绍,不同操作对 Redis 访问效率的影响。

说到这儿呢,数据模型和操作接口我们就构造完成了,这是我们的基础工作。接下来呢,我们就要更进一步,考虑一个非常重要的设计问题:键值对保存在内存还是外存?

保存在内存的好处是读写很快,毕竟内存的访问速度一般都在百 ns 级别。但是,潜在的风险是一旦掉电,所有的数据都会丢失。保存在外存,虽然可以避免数据丢失,但是受限于磁盘的慢速读写(通常在几 ms 级别),键值数据库的整体性能会被拉低。

因此,如何进行设计选择,我们通常需要考虑键值数据库的主要应用场景。 比如,缓存场景下的数据需要能快速访问但允许丢失,那么,用于此场景的键值数据库通常采用内存保存键值数据。Memcached 和 Redis 都是属于内存键值数据库。对于 Redis 而言,缓存是非常重要的一个应用场景。

为了和 Redis 保持一致,我们的 SimpleKV 就采用内存保存键值数据。接下来,我们来了解下 SimpleKV 的基本组件。大体来说,一个键值数据库包括了访问框架、索引模块、操作模块和存储模块四部分(见下图)。接下来,我们就从这四个部分入手,继续构建我们的 SimpleKV。
在这里插入图片描述
访问模式通常有两种:一种是通过函数库调用的方式供外部应用使用,比如,上图中的 libsimplekv.so,就是以动态链接库的形式链接到我们自己的程序中,提供键值存储功能;另一种是通过网络框架以 Socket 通信的形式对外提供键值对操作,这种形式可以提供广泛的键值存储服务。在上图中,我们可以看到,网络框架中包括 Socket Server 和协议解析。

不同的键值数据库服务器和客户端交互的协议并不相同,我们在对键值数据库进行二次开发、新增功能时,必须要了解和掌握键值数据库的通信协议,这样才能开发出兼容的客户端。

实际的键值数据库也基本采用上述两种方式,例如,RocksDB 以动态链接库的形式使用,而 Memcached 和 Redis 则是通过网络框架访问。

通过网络框架提供键值存储服务,一方面扩大了键值数据库的受用面,但另一方面,也给键值数据库的性能、运行模型提供了不同的设计选择,带来了一些潜在的问题。

举个例子,当客户端发送一个如下的命令后,该命令会被封装在网络包中发送给键值数据库:

PUT hello world

键值数据库网络框架接收到网络包,并按照相应的协议进行解析之后,就可以知道,客户端想写入一个键值对,并开始实际的写入流程。此时,我们会遇到一个系统设计上的问题,简单来说,就是网络连接的处理、网络请求的解析,以及数据存取的处理,是用一个线程、多个线程,还是多个进程来交互处理呢?该如何进行设计和取舍呢?我们一般把这个问题称为 I/O 模型设计。不同的 I/O 模型对键值数据库的性能和可扩展性会有不同的影响。

举个例子,如果一个线程既要处理网络连接、解析请求,又要完成数据存取,一旦某一步操作发生阻塞,整个线程就会阻塞住,这就降低了系统响应速度。如果我们采用不同线程处理不同操作,那么,某个线程被阻塞时,其他线程还能正常运行。但是,不同线程间如果需要访问共享资源,那又会产生线程竞争,也会影响系统效率,这又该怎么办呢?所以,这的确是个“两难”选择,需要我们进行精心的设计。

如何定位键值对的位置?

当 SimpleKV 解析了客户端发来的请求,知道了要进行的键值对操作,此时,SimpleKV 需要查找所要操作的键值对是否存在,这依赖于键值数据库的索引模块。索引的作用是让键值数据库根据 key 找到相应 value 的存储位置,进而执行操作。

索引的类型有很多,常见的有哈希表、B+ 树、字典树等。不同的索引结构在性能、空间消耗、并发控制等方面具有不同的特征。如果你看过其他键值数据库,就会发现,不同键值数据库采用的索引并不相同,例如,Memcached 和 Redis 采用哈希表作为 key-value 索引,而 RocksDB 则采用跳表作为内存中 key-value 的索引。

一般而言,内存键值数据库(例如 Redis)采用哈希表作为索引,很大一部分原因在于,其键值数据基本都是保存在内存中的,而内存的高性能随机访问特性可以很好地与哈希表 O(1) 的操作复杂度相匹配。

SimpleKV 的索引根据 key 找到 value 的存储位置即可。但是,和 SimpleKV 不同,对于 Redis 而言,很有意思的一点是,它的 value 支持多种类型,当我们通过索引找到一个 key 所对应的 value 后,仍然需要从 value 的复杂结构(例如集合和列表)中进一步找到我们实际需要的数据,这个操作的效率本身就依赖于它们的实现结构。

Redis 采用一些常见的高效索引结构作为某些 value 类型的底层数据结构,这一技术路线为 Redis 实现高性能访问提供了良好的支撑。

不同操作的具体逻辑是怎样的?

SimpleKV 的索引模块负责根据 key 找到相应的 value 的存储位置。对于不同的操作来说,找到存储位置之后,需要进一步执行的操作的具体逻辑会有所差异。SimpleKV 的操作模块就实现了不同操作的具体逻辑:

  • 对于 GET/SCAN 操作而言,此时根据 value 的存储位置返回 value 值即可;
  • 对于 PUT 一个新的键值对数据而言,SimpleKV 需要为该键值对分配内存空间;
  • 对于 DELETE 操作,SimpleKV 需要删除键值对,并释放相应的内存空间,这个过程由分配器完成。

对于 PUT 和 DELETE 两种操作来说,除了新写入和删除键值对,还需要分配和释放内存。这就不得不提 SimpleKV 的存储模块了。

如何实现重启后快速提供服务?

SimpleKV 采用了常用的内存分配器 glibc 的 malloc 和 free,因此,SimpleKV 并不需要特别考虑内存空间的管理问题。但是,键值数据库的键值对通常大小不一,glibc 的分配器在处理随机的大小内存块分配时,表现并不好。一旦保存的键值对数据规模过大,就可能会造成较严重的内存碎片问题。

因此,分配器是键值数据库中的一个关键因素。对于以内存存储为主的 Redis 而言,这点尤为重要。Redis 的内存分配器提供了多种选择,分配效率也不一样。

SimpleKV 虽然依赖于内存保存数据,提供快速访问,但是,我也希望 SimpleKV 重启后能快速重新提供服务,所以,我在 SimpleKV 的存储模块中增加了持久化功能。

不过,鉴于磁盘管理要比内存管理复杂,SimpleKV 就直接采用了文件形式,将键值数据通过调用本地文件系统的操作接口保存在磁盘上。此时,SimpleKV 只需要考虑何时将内存中的键值数据保存到文件中,就可以了。

一种方式是,对于每一个键值对,SimpleKV 都对其进行落盘保存,这虽然让 SimpleKV 的数据更加可靠,但是,因为每次都要写盘,SimpleKV 的性能会受到很大影响。

另一种方式是,SimpleKV 只是周期性地把内存中的键值数据保存到文件中,这样可以避免频繁写盘操作的性能影响。但是,一个潜在的代价是 SimpleKV 的数据仍然有丢失的风险。

和 SimpleKV 一样,Redis 也提供了持久化功能。不过,为了适应不同的业务场景,Redis 为持久化提供了诸多的执行机制和优化改进,后面我会和你逐一介绍 Redis 在持久化机制中的关键设计考虑。

小结

至此,我们构造了一个简单的键值数据库 SimpleKV。可以看到,前面两步我们是从应用的角度进行设计的,也就是应用视角;后面四步其实就是 SimpleKV 完整的内部构造,可谓是麻雀虽小,五脏俱全。

SimpleKV 包含了一个键值数据库的基本组件,对这些组件有了了解之后,后面在学习 Redis 这个丰富版的 SimpleKV 时,就会轻松很多。

为了支持更加丰富的业务场景,Redis 对这些组件或者功能进行了扩展,或者说是进行了精细优化,从而满足了功能和性能等方面的要求。
在这里插入图片描述
从这张对比图中,我们可以看到,从 SimpleKV 演进到 Redis,有以下几个重要变化:

  • Redis 主要通过网络框架进行访问,而不再是动态库了,这也使得 Redis 可以作为一个基础性的网络服务进行访问,扩大了 Redis 的应用范围。
  • Redis 数据模型中的 value 类型很丰富,因此也带来了更多的操作接口,例如面向列表的 LPUSH/LPOP,面向集合的 SADD/SREM 等。
  • Redis 的持久化模块能支持两种方式:日志(AOF)和快照(RDB),这两种持久化方式具有不同的优劣势,影响到 Redis 的访问性能和可靠性。
  • SimpleKV 是个简单的单机键值数据库,但是,Redis 支持高可靠集群和高可扩展集群,因此,Redis 中包含了相应的集群功能支撑模块。

通过这节课 SimpleKV 的构建,我相信你已经对键值数据库的基本结构和重要模块有了整体认知和深刻理解,这其实也是 Redis 单机版的核心基础。

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

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

相关文章

STM32外设应用知识详解

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

RKMEDIA画面质量调节-QP调节

QP是在视频采集编码过程中的量化参数,其值与画面质量成反比,即QP值越大画面质量越小,其具体调整方法如下: typedef struct rkVENC_RC_PARAM_S {RK_U32 u32ThrdI[RC_TEXTURE_THR_SIZE]; // [0, 255]RK_U32 u32ThrdP[RC_TEXTURE_TH…

如何基于 RLHF 来优化 ChatGPT 类型的大语言模型

🚴前言 对于ChatGPT来说,RLHF是其训练的核心。所谓RLHF,即Reinforcement Learning with Human Feedback,基于人类反馈的强化学习。这项技术通过结合模型自身的生成能力和人类专家的反馈,为改进文本生成质量提供了新的…

解决Android Studio中使用lombok插件错误: 找不到符号的问题

问题 主要是想节省实体类的set、get等方法,使用lombok报错如下: 解决方案 由于Android的限制,在Android中使用lombok兼容极其麻烦,如果你只是想减少set、get等代码可以直接使用kotlin的data class 示例 data class KotlinTes…

等级保护等保资料原件合集(word源资料)

第二章 系统定级与安全域 2.1 系统定级 2.1.1 不同等级的安全保护能力 2.1.2 重要信息系统 2.1.3 定级参考 2.2 安全域定义 2.2.1 安全域定义方法 2.2.2 安全域等级描述 第三章 实施方案设计 3.1 三级等保要求 3.2 基本要求的详细技术要求 3.2.1 物理安全 3.2.2 网…

Unity 从零开始的框架搭建1-1 unity中对象调用的三种方式的优缺点分析【干货】

该文章专栏是向QFrameWork作者凉鞋老师学习总结得来,吃水不忘打井人,不胜感激 Unity 框架搭建学习笔记1-1,前一个1代表凉鞋的第一季教程,后一个1代表该季第一篇我的文章 unity中对象调用的三种方式 方法调用,例如&…

Qt设计登录界面

优化登录框: 将两个按钮连接到槽函数 在构造函数中定义 connect(this->btn1,&QPushButton::clicked,this,&Logon::my_slot);connect(this->btn2,&QPushButton::clicked,this,&Logon::my_cancel); 定义登录按钮连接的槽函数 void Logon::my…

基于Java语言的充电桩平台+云快充协议+充电桩管理后台+充电桩小程序

软件架构 1、提供云快充底层桩直连协议,版本为云快充1.5,对于没有对接过充电桩系统的开发者尤为合适; 2、包含:启动充电、结束充电、充电中实时数据获取、报文解析、Netty通讯框架、包解析工具、调试器模拟器软件等;…

CMake 属性之目标属性

【写在前面】 CMake 可以通过属性来存储信息。它就像是一个变量,但它被附加到一些其他的实体上,像是一个目录或者是一个目标。例如一个全局的属性可以是一个有用的非缓存的全局变量。 在 CMake 的众多属性中,目标属性 ( Target Properties ) …

NodeJS智慧社区管理微信小程序-计算机毕业设计源码40623

摘 要 随着中国经济的飞速增长,消费者的智能化水平不断提高,许多智能手机和相关的软件正在得到更多的关注和支持。其中,智慧社区管理微信小程序更是深得社区人员的喜爱,它的出现极大地改善了社区人员的生活质量,同时&…

宠物咖啡馆在线体验:SpringBoot框架的创新应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式,是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示: 图4-1系统工作原理…

云微客AI直播矩阵,让小白轻松上手的必备直播利器

现在直播带货都已经杀疯了,在新趋势下,AI智能直播应运而生。AI智能直播相较于传统直播,直播模式对于场地的要求和人员的要求都相对较低,大大降低了我们的试错成本,同时直播矩阵系统也为企业和个人带来了低成本、高效率…

浅析基于双碳目标的光储充一体化电站状态评估技术

摘要:全国碳市场拉开了我国能源结构加速转型的大幕,催生了光伏、储能和新能源汽车等一批绿色产业的兴起,同时随着利好政策扶植和消费者的青睐,光伏、储能和新能源汽车市场均加快发展。但传统的充电桩和光伏电站都是分开建设&#…

如何在电脑上创建虚拟网卡

1.右键点击此电脑,选择——管理 2.选择设备管理器——网络适配器,在点击操作选择 添加过时硬件 3.点击 下一页 4.在这里选择网络适配器,点击下一页 5.选择微软的环回适配器 6.打开控制面板 7.点击网络和Internet 8.点击网络和共享中心 9…

一个读取CT图像序列,并进行表面重建的C++代码

这篇文章中,介绍使用VTK进行读取CT图像(一个序列),然后进行表面重建。为什么不使用DCMTK呢?因为使用DCMTK需要一张一张读取,要自己写一个代码,还要创建一个容器来放读入的CT数据,比较…

亳州自闭症寄宿制学校,关注孩子的学习和生活

在特殊教育领域,自闭症儿童的教育与成长一直是社会各界关注的焦点。近年来,随着对自闭症认识的加深,越来越多的寄宿制学校应运而生,致力于为这些特殊的孩子提供全面、个性化的教育服务。在安徽亳州,这样的学校正努力为…

Metasploit渗透测试之后渗透

简介 Metasploit拥有300多个后渗透模块,是渗透测试的最佳框架之一,覆盖了从信息收集到后渗透甚至报告的每个阶段。本章将重点介绍提权、持久化、获取凭证和横向移动等内容。 # 1、后渗透模块 在Metasploit框架升级后,用于自动化后渗透任务…

C++——类和对象(二)

1. 类的默认成员函数 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。⼀个类,我们不写的情况下编译器会默认生成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载不…

某国有资本运营中心人才选拔项目纪实

某国有资本运营中心人才选拔项目纪实 【客户行业】 政府与事业单位 【问题类型】 人才招聘选拔 【客户背景】 在三年国企改革过程中,南方某省政府为响应国家政策,提出组建专业化国有资本投资运营公司,大力开展专业化资本运营,…

移除元素(算法题分享)

移除元素 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作&#xf…