管中窥豹 EOS 数据存储 | database::add_index

eos 的 controller_impl::db 存储了交易执行之后的状态数据。在 chain_plugin::plugin_initialize 中完成在文件映射的内存上创建 generic_index 类型对象,generic_index 类型对象的成员变量 _indices 为 boost 的多索引容器对象即 multi_index_container 类型的对象,之后就可以往容器对象里面增加数据,删除数据,更改数据,和查询数据(上一篇讲的 database::find 函数)。本文以此为入口,详细分析 database::add_index 窥探eos的内存模型,eos 的版本是 mainnet-1.5.1 ,文件路径在 eos/libraries/chainbase/include/chainbase/chainbase.hpp。

database::add_index 函数分析

接下来逐行分析 database::add_index

入口在 chain_plugin 插件初始化

此时会对链控制器增加索引,即执行 controller::add_indices() 函数,实际执行 controller_impl::add_indices() 函数。

controller_impl::add_indices 函数分析

controller_impl::reversible_blocks 临时database,可能被undo,此处重点关注controller_impl::db。此处可知 controller_impl::db 存储的多索引容器对象主要分为4类:

  1. 链控制相关的主要是账户信息,交易信息等
  2. 合约相关的主要是合约的表信息,合约表具体字段信息等。
  3. 账户的权限相关的信息
  4. 资源限制相关的信息

4大类信息中的每一类信息都是一个独立 index_set 模板类型,实际执行 index_set 模版类型的静态成员函数 add_indices 增加索引。

index_set::add_indices 模板类的静态成员函数分析

此处利用了递归展开可变模板参数,模板实参类型可以是任意个,特化单参类型,可变参数模板辗转递归为单参调用。实质上执行的是 index_set::add_indices。
我们以 controller_index_set 的 account_index 为例子展开后面的分析。其类型为:

回到 database::add_index 函数的第一行,即

接下来分析 type_id 变量的值

generic_index::value_type::type_id 静态成员变量分析

查看模板类 generic_index 可得, generic_index::value_type 类型为 MultiIndexType::value_type ,以 account_index 为 MultiIndexType 类型变量的类型实参,即 account_index::value_type ,即 account_object 类型。

具体的 account_object 类型和 account_index 类型如下:

各个多索引容器管理的对象类型有一个 uint16_t 类型静态成员变量 type_id ,此 type_id 静态变量是怎么来的呢?

chainbase::object 模板类型分析

静态成员变量 type_id 的值为模板类型 object 的第一个实参的值,account_object 的 type_id 的值为 account_object_type 。

所有的多索引容器管理的对象的类型都继承自模板类型 chainbase::object ,chainbase::object 实例类型的第一个参数 TypeNumber 为递增的枚举 object_type 中的一个, 进而决定他们的 type_id 为递增的不同值,在 std::vector 类型的 _index_map 中的下标的不同。

有 MultiIndexType 类型对象在 _index_map 中的下标了,对象怎么构造和在那里构造?回到 database::add_index ,即

这一行解答了这些疑问,接下深入分析:

index_type::allocator_type 类型分析

eos利用 boost 的 managed_mapped_file 把内存中的数据写到文件上面。我们知道标准模板库里面的容器类型模板,在实例化类型时,如果不提供自己的分配器,会用默认的分配器,此默认的分配器是在堆上面分配内存,然后在此内存上面进行数据操作。boost 的 managed_mapped_file 会把一块内存映射到文件上面,此时我们操作的内存必须是 managed_mapped_file 管理的内存。在构造 generic_index 类型对象时通过 _segment->get_segment_manager() 获取分配器实例对象。

database::_index_map 成员变量分析

_index_list 成员变量本文暂时不展开分析。
因为 boost 的 managed_mapped_file::find 和 managed_mapped_file::construct 函数是以字符串索引具体的内存地址,此字符串可以理解为变量名称。利用 boost::core::demangle( typeid( typename index_type::value_type ).name() ) 获取 MultiIndexType 类型名称对应的字符串,实现不同类型不同的索引的字符串,在_segment 管理的内存中的地址不同,并通过类型名称找到具体的地址。
当在 _segment 上面构造好 generic_index 类型对象之后,返回的指针要存储起来,以方便对 _segment 管理内存上面构造的 generic_index 类型对象进行操作。 database::_index_map 成员变量主要用来存储此指针。
index 模板类型分析此处暂时不展开,可以理解为利用装饰器模式管理 generic_index 类型指针,实际功能的实现转接到 generic_index 类型实现。

总结

  1. 所有的 MultiIndexType::value_type 类型继承自 chainbase::object 类型,chainbase::object 模板类型的静态成员变量 type_id 为递增的枚举类型 object_type 中的一个。
  2. _segment 为 MultiIndexType 提供 在映射文件的内存上进行内存操作的分配器。 以 MultiIndexType 类型名称生成不同的索引字符串,进而以此字符串在 _segment 上面构造 generic_index 类型对象,并可通过此字符串索引 generic_index 类型对象。
  3. database::_index_map 成员变量以 MultiIndexType::value_type::type_id 为下标在 std::vector 中存储在 _segment 上面生成的 generic_index 类型对象的指针。后续可以通过 MultiIndexType::value_type::type_id 在 database::_index_map 中找到指针, 进而对对多索引容器类型即 MultiIndexType 类型对象进行操作。
  4. MultiIndexType 类型多索引容器对象已经构造完成,后面可以在此容器中增加删除查看数据,相当在于数据库中表已经建好,就可以在数据库表中插入删除查看一条记录。

预告

管中窥豹EOS数据存储系列致力于深入源码底层细节剖析 eos 的存储模型。

下篇管中窥豹EOS数据存储系列重点讲述 database::create ,database::remove ,database::modify 请关注后续更新。

发表评论

电子邮件地址不会被公开。 必填项已用*标注