Dapp开发

EOS内存数据库chainbase深入解析

2018-08-22  本文已影响229人  vergil6

简介

eos内存数据库的用来保存区块链执行之后的所有状态,如果把区块链看成是操作日志,那么eos内存数据库就是用来记录操作之后的系统状态。

一个transaction中的action可能会在最后执行失败但action已经对数据库做了操作,区块链本身也可能存在分叉,这就要求数据库有一种任意回滚的能力,eos内存数据库提供了这种能力,通过start_undo_session开启一个undo session(回滚session),在这个undo session内的所有操作都可以通过undo撤销,也可以通过commit弹出这个undo session使状态变成不可撤销。

eos中的database

在分析eos database时候手上的eos代码版本为Version 1.1.3,eos database的实现源码在 eos/libraries/chainbase目录下的chainbase.hpp,chainbase.cpp。database的主要是实现是通过boost::multi_index_containerboost::interprocess::managed_mapped_file,multi_index_container 用来做容器实现增删改查,managed_mapped_file实现内存映射文件并为multi_index_container 提供内存分配器allocator。

template<typename MultiIndexType>
class generic_index
{
   public:
      typedef bip::managed_mapped_file::segment_manager             segment_manager_type;
      typedef MultiIndexType                                        index_type;
      typedef typename index_type::value_type                       value_type;
      typedef bip::allocator< generic_index, segment_manager_type > allocator_type;
      typedef undo_state< value_type >                              undo_state_type;

      // 增
      template<typename Constructor>
      const value_type& emplace( Constructor&& c );
      // 改
      template<typename Modifier>
      void modify( const value_type& obj, Modifier&& m );
      // 删
      void remove( const value_type& obj );
      template<typename CompatibleKey>
      // 查
      const value_type* find( CompatibleKey&& key );
      // 创建一个undo session为后面进行undo撤销或者commit永久保存
      session start_undo_session( bool enabled );
      // 撤销最新一个undo_session
      void undo();
      // 合并最新的2个undo_session,这个功能主要用于push_transactions
      void squash();
      // 放弃revision之前所有的undo_session,这样这些更改变得不可撤销
      void commit( int64_t revision );
   private:
      // 监听修改操作 放入undo_state对象,当需要undo撤销的时候还原被修改的对象
      void on_modify( const value_type& v );

      // 监听删除操作 放入undo_state对象,当需要undo撤销的时候添加被删除的对象
      void on_remove( const value_type& v );

      // 监听创建操作,放入undo_state对象,当需要undo撤销的时候删除呗创建的对象
      void on_create( const value_type& v );

      // _stack串联起undo_state对象,undo撤销session的就靠这些对象还原
      boost::interprocess::deque< undo_state_type, allocator<undo_state_type> > _stack;

      // 版本号,等于block_num, 一个block_num对应一个undo_session, 最终由commit提交变成不可更改
      int64_t                         _revision = 0;
      // _indices下一个主键id
      typename value_type::id_type    _next_id = 0;
      // _indices boost::multi_index_container对象
      index_type                      _indices;
      uint32_t                        _size_of_value_type = 0;
      uint32_t                        _size_of_this = 0;
};
   template< typename value_type >
   class undo_state
   {
      public:
         typedef typename value_type::id_type                      id_type;
         typedef allocator< std::pair<const id_type, value_type> > id_value_allocator_type;
         typedef allocator< id_type >                              id_allocator_type;

         template<typename T>
         undo_state( allocator<T> al )
         :old_values( id_value_allocator_type( al.get_segment_manager() ) ),
          removed_values( id_value_allocator_type( al.get_segment_manager() ) ),
          new_ids( id_allocator_type( al.get_segment_manager() ) ){}

         typedef boost::interprocess::map< id_type, value_type, std::less<id_type>, id_value_allocator_type >  id_value_type_map;
         typedef boost::interprocess::set< id_type, std::less<id_type>, id_allocator_type >                    id_type_set;

         id_value_type_map            old_values;       // 保存了修改的对象字典,在undo撤销的时候还原这些对象 on_modify()
         id_value_type_map            removed_values;   // 保存了删除的对象字典,在undo撤销的时候添加这些对象 on_remove()
         id_type_set                  new_ids;          // 保存新建的对象id集合,在undo撤销的时候删除这些对象 on_create()
         id_type                      old_next_id = 0;
         int64_t                      revision = 0;     // undo_state序号,等于不可修改的block num   
   };

测试代码

#include "chainbase.hpp"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <iostream>
#include <string>

using namespace chainbase;
using namespace boost::multi_index;

struct book : public chainbase::object<0, book> {
   book() = delete;
   public: 
   template<typename Constructor, typename Allocator> 
   book(Constructor&& c, chainbase::allocator<Allocator> a):name(a),author(a) { c(*this); }

    id_type id;
    shared_string name;
    shared_string author;
    int pulish_time;
};

std::ostream& operator<<(std::ostream& os,const book& bk)
{
  os<<bk.id<<" "<<bk.name<<" "<<bk.author<<" "<<bk.pulish_time<<" "<<std::endl;
  return os;
}

struct by_id;
struct by_pulish_time;
using book_index = chainbase::shared_multi_index_container<
  book,
  indexed_by<
     ordered_unique<tag<by_id>, member<book, book::id_type, &book::id>>,
     ordered_non_unique<tag<by_pulish_time>, BOOST_MULTI_INDEX_MEMBER(book,int,pulish_time) >
  >
>;


//CHAINBASE_SET_INDEX_TYPE(book, book_index)
namespace chainbase { 
template<> struct get_index_type<book> 
  { typedef book_index type; }; 
}

void func(chainbase::database& db)
{
  const auto& nb = db.create<book>([](book& b) {
        b.name = "《1984》";
        b.author = "乔治·奥威尔";
        b.pulish_time = 1949;
  });
  const auto& nb2 = db.create<book>([](book& b) {
        b.name = "《动物庄园》";
        b.author = "乔治·奥威尔";
        b.pulish_time = 1945;
  });
  auto session = db.start_undo_session(true);
  db.modify( nb2, [&]( book& b ) {
      b.name = "《xxxxx》";
  });
  std::cout << nb2;
  db.undo();
  std::cout << nb2 << std::endl;
}

int main()
{
    chainbase::database db("data", database::read_write, 1024*1024*100, true);
    db.add_index<book_index>();
    func(db);
    //std::cout << *db.find<book, by_id>(0);
    //std::cout << *db.find(book::id_type(0));

    const auto& book_id_index = db.get_index<book_index, by_id>();
    //std::cout << *book_id_index.find(0);
    copy(book_id_index.begin(),book_id_index.end(), std::ostream_iterator<book>(std::cout));
    std::cout << std::endl;

    const auto& book_pulish_time_index = db.get_index<book_index, by_pulish_time>();
    copy(book_pulish_time_index.begin(),book_pulish_time_index.end(), std::ostream_iterator<book>(std::cout));
    std::cout << std::endl;

    // 索引的记录数
    database::database_index_row_count_multiset dircm = db.row_count_per_index();
    int num;
    for(auto i:dircm)
    {
      std::cout<<i.second<<": "<<i.first<<std::endl;
      num = i.first;
    }

    std::cout<<"free_memory: "<<db.get_free_memory()<<std::endl;
    std::cout<<"is_read_only: "<<db.is_read_only()<<std::endl;
    std::cout<<"revision: "<<db.revision()<<std::endl;

    //db.remove(*book_id_index.find( 0 ));

    db.flush();
    return 0;
}

输出结果:

chainbase::oid<book>(1) 《xxxxx》 乔治·奥威尔 1945 
chainbase::oid<book>(1) 《动物庄园》 乔治·奥威尔 1945 

chainbase::oid<book>(0) 《1984》 乔治·奥威尔 1949 
chainbase::oid<book>(1) 《动物庄园》 乔治·奥威尔 1945 

chainbase::oid<book>(1) 《动物庄园》 乔治·奥威尔 1945 
chainbase::oid<book>(0) 《1984》 乔治·奥威尔 1949 

book: 2
free_memory: 104855792
is_read_only: 0
revision: 0

eos内存数据库在区块链中的使用

void start_block( block_timestamp_type when, uint16_t confirm_block_count, controller::block_status s ) {
      EOS_ASSERT( !pending, block_validate_exception, "pending block is not available" );
 ...
    //创建block undo session
      pending = db.start_undo_session(true);
...
}
transaction_context::transaction_context( controller& c,
                                             const signed_transaction& t,
                                             const transaction_id_type& trx_id,
                                             fc::time_point s )
...
//创建transaction undo session
   ,undo_session(c.db().start_undo_session(true))
...
void on_irreversible( const block_state_ptr& s ) {
...
      db.commit( s->block_num );
...
   }

最后

chainbase通过managed_mapped_file内存映射文件,把内存数据映射到一个文件中去(实现持久化),可用通过flush()函数显示的把内存同步中去,chainbase可用容量大小完全取决于系统中物理内存的多少,好在chainbase存储的是系统状态的核心数据,不会存放例如用户的媒体数据所以目前阶段不必担心数据库容量不够的问题。chainbase通过managed_mapped_file内存映射文件映射的文件本身可能不可以拷贝到其他系统中去用,chainbase也可能在突然宕机之后内存文件可能出现损坏,不过这些都不是最重要的问题因为都可以通过replay区块链来重新获得

上一篇下一篇

猜你喜欢

热点阅读