一种快速的键值数据库的系统及建立方法
技术领域
本发明设计一种快速的键值数据库的系统及建立方法。
背景技术
具备高可靠性及可扩展性的海量数据存储技术对互联网公司来说是一个巨大的挑战。传统的关系型数据库利用二维表数据模型来存储格式化的数据结构。由于,该数据库中的每个元组由相同的字段组成,因此,数据库需要为每个元组分配所有的字段。这一特性带来了关系型数据库的性能瓶颈,尤其是当系统需要处理大量数据的高频访问时(如频繁地读写日志型数据),使用关系型数据库存在效率低下且难以扩展的问题。
键值数据库是非关系型数据库的一种常见的方式(比如MongoDB和Google的LevelDB等)。其目的是存储海量的半结构化和非结构化数据,以应对不断扩大的数据量和用户规模。键值数据库中,数据之间没有关系的概念,其数据按照键值对的形式进行组织、索引和存储,其值可以是任意不定长的数据。因此,这类数据存储系统非常适合用于海量的非关系型数据的存储和查询,能够有效地减少读写磁盘的次数,提供比关系型数据库更好的可扩展性和读写性能。
在分布式的服务框架下,多个服务进程同时访问一个独立的数据库服务器会造成性能瓶颈。另一方面,现有的键值数据库将索引文件和数据文件分离,且采用定长方式存储索引数据以保证其性能。然而,在单个服务进程的数据量比较小,数据值比较小的情况下,多文件数据库会带来额外的开销(比如进行数据备份时要全部备份,索引文件可能比数据文件更大等等)。而现有的分布式框架通常采用一致性哈希算法,在该框架中任务的分配取决于任务的内容,即相同内容的任务总是会被分配到同一个服务进程上。因此,可以考虑由每个服务进程单独保存自己的少量任务数据,由一致性的分布式算法保证数据一致性,而不再需要一个存放所有任务数据的数据库服务器。在这种情况下,采用不定长的索引存储方式的单文件的数据库更能满足需求。
本发明在此基础上提出了新的数据存储结构和存储策略以实现一种快速的键值数据库(Kiwilite)。Kiwilite中一个文件就是一个采用不定长的索引存储方式存储的单文件数据库,以保证较高的读写效率。该数据库包含了索引和数据信息,按照键值对的形式进行组织、索引和存储。实现了多进程/线程对同一个数据库文件的共享,通过读写锁对访问同一个文件进行同步,以达到线程安全,读、写进程的同步与互斥。同时,保证了数据的一致性和完整性,并在出现异常时,能正确的恢复数据和日志。
发明内容
本发明提供一种快速的键值数据库的系统及建立方法。提出了一种新的数据存储结构,在单文件情况下实现快速读写文件型键值对(Key-Value)数据库。本发明的技术方案包括数据的增删改查操作、数据一致性的检查以及过期数据的处理三大功能。
本发明解决其技术问题所采用的技术方案如下:
一种快速的键值数据库的系统,包括索引模块、存储模块、读取模块、校验模块、索引重建模块、数据回滚模块、数据精简模块和日志模块。
首先,重新定义数据库文件的存储结构,该数据库文件实现了将索引文件和数据文件的合并。每个数据库文件包含连续的三部分数据:头部分(Header)、数据部分(Data)和索引部分(Index);其中数据部分和索引部分的长度均不固定,均由若干个键值数据对逐个往后排列构成。数据库文件的结构为{Header,Datai,Indexj|i=1,2..,n,j=1,2…,m},具体大小设置及功能定义如下:
Datai:用于保存数据,其结构为一个大小不固定的键值对(D_Ki,D_Vi),D_Ki为键数据,其结构包含D_K_Leni和D_K_Keyi,其中D_K_Keyi用于存储数据的唯一编号(如名称,编号等),D_K_Leni用于存储D_K_Keyi所记录的编号的大小。D_Vi为值数据,其结构包含D_V_Leni和D_V_Valuei,其中D_V_Valuei用于存储数据的具体内容,D_V_Leni用于存储D_V_Keyi所记录的数据的大小。D_K_Leni和D_V_Leni都是经过压缩的整数,其所占用的存储空间的大小是不固定的(其范围在1到10个字节之间)。
Indexj:用于保存索引数据,即索引表,其结构与一致Datai,为一个大小不固定的键值对(I_Kj,I_Vj),其中,I_Kj包含I_K_Lenj和I_K_Conj两部分。其中I_K_Conj=(Prefixj,Timestampj,Prevj)用于记录第j个索引表的上下文信息,Prefixj为一个标记性的字符串作为索引的标记,占7个字节的存储空间;Timestampj为时间戳,用于表示数据记录的时间点,占8个字节的存储空间;Prevj用于记录上个索引表所在的位置(即Indexj-1在文件中的位置),占8个字节的存储空间。I_K_Lenj占1字节的存储空间,用于记录I_K_Conj的长度。I_Vj包含I_V_Lenj,I_V_Infoj两部分,I_V_Lenj用于记录I_V_Infoj的长度。I_V_Infoj为索引表的内容,记录了所有的索引信息,其结构也为若干个键值对,即{(I_keyq,I_valueq)|q=1,2…,p}。其中I_keyq=(I_key_Lenq,I_key_kq),其中I_key_kq用于记录数据的唯一编号,即与数据库文件中Datai部分的D_K_Keyi一致。I_key_Lenq用于记录I_key_kq的大小。I_valueq=(I_value_Lenq,I_value_offsetq),其中I_value_offsetq用于记录数据的内容在文件中的偏移量,即Datai部分的D_Vi在数据库文件中的偏移量。I_value_Lenq用于记录数据的大小即Datai部分的D_V_Leni。
Header:用以保存索引表的当前位置即最后一个Indexj所在的位置,默认大小为8个字节(即默认最多保存40G数据)。当索引表数据更新时,Header数据会发生相应变化作为索引校验的依据。
所述的索引模块,其作用是在内存中建立键数据到文件中的值数据保存位置的映射关系。存储模块,其作用是通过追加的方式对数据进行增加、修改和删除操作,以提高数据操作的效率。读取模块,其作用是根据键数据在索引模块中进行检索,若没有查到记录,则返回错误信息;若查到记录,则根据记录的值数据的位置到对应文件中读取数据并返回。校验模块,其作用是为了实现多进程/线程对同一个数据库文件的共享,在某一进程/线程获得文件控制权后必须对文件格式和索引数据进行校验。若最新的校验信息和当前内存里的索引数据相同,则不需要刷新索引数据;否则,则从数据库文件中读取最新的索引数据更新到内存中;在整个过程中若没有出现文件数据解析错误,则视为文件格式校验通过。索引重建模块,其作用是在文件损坏或者数据回滚和精简操作时,需要重建索引数据。重建索引时直接从头开始扫描整个数据库文件,在索引中记录键数据和值数据的保存位置。数据回滚模块,其作用是将数据库回滚至指定的时间点,恢复该时间点的所有数据和状态,并抛弃指定时间点之后的所有修改。由于数据是按照时间顺序存储的,只需要抛弃时间点之后的数据即可,之前的数据无需更改。数据精简模块,其作用是根据输入的时间点,把指定时间点之前的被删除或修改的历史数据删除,并把剩余数据重新排列消除碎片。日志模块,其作用是从文件中提取数据库的修改记录并返回。
一种快速的键值数据库的建立方法,具体实现包括如下步骤:
步骤(1).在数据库文件中进行数据的读取和删除时,获取数据的唯一编号;同时在进行数据的写入、修改时,获取数据的唯一编号和数据内容;
步骤(2).根据文件头部记录的索引表的位置,读取数据库文件中的索引表,并对索引数据的正确性进行校验;若索引数据已损坏,则采取步骤(3)重建索引表;在索引表重建后或索引数据校验无损时,执行步骤(4)进行数据读取,或执行步骤(5)进行数据的增加、删除或修改;
步骤(3).若索引数据已损坏,则重建索引表。
步骤(4).在索引表校验或重建后,如需要读取数据,则根据用户输入的键数据,在索引表中查询对应的值数据在数据库文件中的存储位置信息,再根据该存储位置在数据库文件中读取对应的值数据;
步骤(5)在索引表校验或重建后,如需增加、删除或修改数据,则根据用户输入的键值对数据,以追加方式将该键值对数据写入数据库文件;具体执行步骤如下:
步骤(5.1)删除步骤(2)获取的原有的索引信息;
步骤(5.2)在当前数据文件结尾以追加方式记录键值对数据。新增和修改键数据对应的值数据时,记录其最新的键值对数据。对于删除键数据的情况,记录一个带空值的键值对数据;
步骤(5.3)在当前数据文件结尾以追加方式记录新操作的上下文信息;
步骤(5.4)将键数据在数据库文件中的位置信息添加到索引表,并将新的索引表同样以追加方式写入数据库文件;
步骤(6)如需获得数据库文件的修改日志,首先根据数据库文件头的索引表位置,读取最新的时间戳和上一个数据记录的位置,并依时间逆序获得所有的数据操作记录即日志;
步骤(7)如需将数据库进行回滚操作,则首先由用户指定需要回滚到的时间,然后由系统遍历步骤(6)得到的数据库日志,得到该时间对应的数据位置,删除该位置之后的所有数据,并依照步骤(3)中的方法,为留存数据上重建索引表;
步骤(8)如需要对部分旧数据进行精简,则首先需要用户指定的进行精简的时间点。然后系统遍历步骤(6)得到的数据库日志,得到指定时间对应的数据位置并对之前的数据进行精简。依照步骤(3)中的方法,重建该时间点之前所有数据的索引表。数据精简的具体步骤如下:
步骤(8.1)依照步骤3重建该时间点之前的所有数据的索引表;
步骤(8.2)保留索引表中的数据,删除所有修改或删除的数据。将该时间点之后的数据整体前移,填补精简后的空白;
步骤(8.3)更正索引表中的数据的位置信息。
本发明有益效果如下:
与传统方法相比,本发明所提供的方法提出了一种新的不定长方式存储索引数据的方式,并在单文件情况下实现快速读写文件型键值对(Key-Value)数据库,以弥补单个服务进程的数据量比较小,数据值比较小的情况下,现有的键值数据库的不足。
为了分析本发明所提出的数据库建立方式的优劣,本发明与现有的两大开源键值数据库gkvlite(https://github.com/steveyen/gkvlite)和goleveldb(https://github.com/syndtr/goleveldb)在开源的键值数据库测试平台(https://github.com/SchumacherFM/gokvbench)上进行了性能对比。对比结果如表1所示。结果显示,在读取和写入数据方面,kiwilite都具有耗时短、内存消耗小的优势。
表1.与现有键值数据库的性能对比
附图说明
图1数据部分的存储结构;
图2索引部分的数据结构。
具体实施方式
下面结合附图和实施步骤对本发明作进一步说明。
首先给出存储数据结构设计的相关概念定义和符号说明:
本发明在数据库存储结构上实现了将索引文件和数据文件的合并。每个数据库文件包含连续的三部分数据:头部分(Header)、数据部分(Data)、索引部分(Index)内容,其中数据部分和索引部分的长度均不固定,均由若干个键值数据对逐个往后排列构成。数据库文件的结构为{Header,Datai,Indexj|i=1,2..,n,j=1,2…,m},具体大小设置及功能定义如下:
Datai:用于保存数据,其结构为一个大小不固定的键值对(D_Ki,D_Vi),如图1所示。D_Ki为键,其结构包含D_K_Leni和D_K_Keyi,其中D_K_Keyi用于存储数据的唯一编号(如名称,编号等),D_K_Leni用于存储D_K_Keyi所记录的编号的大小。D_Vi为值,其结构包含D_V_Leni和D_V_Valuei,其中D_V_Valuei用于存储数据的具体内容,D_V_Leni用于存储D_V_Keyi所记录的数据的大小。D_K_Leni和D_V_Leni都是经过压缩的整数,其所占用的存储空间的大小是不固定的(其范围在1到10个字节之间)。
Indexj:用于保存索引数据,即索引表,其结构与一致Datai,为一个大小不固定的键值对(I_Kj,I_Vj),如图2所示。其中,I_Kj包含I_K_Lenj和I_K_Conj两部分。其中I_K_Conj=(Prefixj,Timestampj,Prevj)用于记录第j个索引表的上下文信息,Prefixj为一个标记性的字符串作为索引的标记,占7个字节的存储空间;Timestampj为时间戳,用于表示数据记录的时间点,占8个字节的存储空间;Prevj用于记录上个索引表所在的位置(即Indexj-1在文件中的位置),占8个字节的存储空间。I_K_Lenj占1字节的存储空间,用于记录I_K_Conj的长度。I_Vj包含I_V_Lenj,I_V_Infoj两部分,I_V_Lenj用于记录I_V_Infoj的长度。I_V_Infoj为索引表的内容,记录了所有的索引信息,其结构也为若干个键值对,即{(I_keyq,I_valueq)|q=1,2…,p}。其中I_keyq=(I_key_Lenq,I_key_kq),其中I_key_kq用于记录数据的唯一编号,即与数据库文件中Datai部分的D_K_Keyi一致。I_key_Lenq用于记录I_key_kq的大小。I_valueq=(I_value_Lenq,I_value_offsetq),其中I_value_offsetq用于记录数据的内容在文件中的偏移量,即Datai部分的D_Vi在数据库文件中的偏移量。I_value_Lenq用于记录数据的大小即Datai部分的D_V_Leni。
Header:用以保存索引表的当前位置即最后一个Indexj所在的位置,默认大小为8个字节(即默认最多保存40G数据)。当索引表数据更新时,Header数据会发生相应变化作为索引校验的依据。
其次,本发明的技术方案包括依次数据的增删改操作、数据一致性的检查以及过期数据的处理三大功能,具体步骤如下:
步骤(1)在数据库文件中进行数据的读取和删除时,获取数据的唯一编号;同时在进行数据的写入、修改时,获取数据的唯一编号和数据内容;
步骤(2)根据文件头部Header所记录的最新的索引表的位置,读取数据库文件中的索引表,并利用校验模块对索引表的正确性进行校验,以判断索引是否有损坏;若该索引表已损坏,则采取步骤(3)重建该索引表;在索引表重建后或索引表校验无损时,执行步骤(4)进行数据读取,或执行步骤(5)进行数据的增加、删除或修改;
步骤(3)若索引表已损坏,则从数据库文件的起始位置开始,顺序读取数据部分(Datai)记录的所有键值对数据,以该键(D_Ki)数据及其对应的值(D_Vi)数据在数据库文件中的存储位置重建索引表;
步骤(4)在索引表校验或重建后,如需读取数据,则根据用户输入的需要读取的数据的编号信息,在索引表中查询相匹配的键数据(即I_key_kq),获得该键对应的值数据在数据库文件中的存储位置信息(即I_value_offsetq)。再根据I_value_offsetq读取其后的I_value_Lenq个字节数,即对应的值数据即D_V_Valuei;
步骤(5)在索引表校验或重建后,如需增加、修改或删除数据,则根据用户输入的键值对数据,以追加方式将该数据写入数据库文件;具体执行步骤如下:
步骤(5.1)删除步骤(2)获取的原有的索引信息,即将当前索引的I_Vj中的I_V_Lenj置为0,I_V_Infoj为空。
步骤(5.2)将用户输入的键值对数据按照Datai的数据结构进行整理并将其追加到数据库文件的结尾。当新增数据时,D_Ki保存新增数据的编号,D_Vi保存新增的数据内容;当修改数据时,D_Ki为待修改的数据的编号,D_Vi保存修改后的数据内容;当删除数据时,D_Ki为需删除数据的编号,D_Vi保存成空值;
步骤(5.3)在当前数据库文件结尾以追加方式记录该索引数据的上下文信息I_Kj,其中Timestampj记录当前操作的时间点,Prevj记录上个索引所在的位置即步骤(2)所读取的文件头部记录的索引表位置。
步骤(5.4)在原有索引表(即步骤(2)所读取索引表)的I_Vj中更新新数据在数据库文件中的位置信息。当新增数据时,根据新增数据的键和值的位置信息在I_V_infoj中增加相应的(I_keyq,I_valueq),并修改I_V_Lenj。当修改数据时,根据修改后数据的值所保存的位置,修改I_V_infoj中相应的键值对应的I_keyq和I_valueq;当删除数据时,删除I_V_infoj中相应的索引信息(I_keyq,I_valueq)并修改I_V_Leni;然后将新的索引表同样以追加方式写入数据库文件;
步骤(6)如需获得数据库文件的修改日志,首先根据数据库Header中记录的最新的索引表的位置,读取最新的时间戳和上一个数据记录的位置即Timestampj和Prevj。然后根据Prevj的信息,依次获取上一条数据记录(即Indexj-1在文件中的位置),再根据I_K_Conj-1中所记录的Prevj-1再获得之前一条数据记录,依次类推可以依时间逆序获得所有数据的操作记录即数据库文件的修改日志{(Timestampj,Prevj-1)|j=1,2…,m};
步骤(7)如需将数据库进行回滚操作,则首先由用户指定需要回滚到的时间t。然后系统遍历步骤(6)得到的数据库文件修改日志,得到指定的回滚时间对应的数据位置,即若t在Timestampj与Timestampj+1之间,则需要回滚到第j条记录。系统删除回滚位置之后的所有内容,再依照步骤(3)中的方法,对留存的前j条数据重建索引表;
步骤(8)如需要对部分旧数据进行精简,则首先需要用户指定的进行精简的时间点t。系统遍历步骤(6)得到的数据库文件修改日志,得到指定时间t对应的数据位置,即若t在Timestampj与Timestampj+1之间,则需要对第j条记录之前的数据进行精简。具体步骤如下:
步骤(8.1)依照步骤(3),从文件起始位置开始顺序读取数据部分前j条键值对数据,以重建时间点t之前的数据的索引表。
步骤(8.2)根据新建的索引表重新遍历数据库,保留索引表中的数据,删除所有被修改或删除的数据、以及修改记录;并将时间t之后的数据整体前移,填补精简后的空白。
步骤(8.3)并从文件起始位置开始,顺序读取数据部分记录的所有键值对数据,根据该键数据及其对应的值数据在数据库文件中的新的存储位置修改索引表中的位置信息。