bigchaindb源碼分析(三)——後端儲存

來源:互聯網
上載者:User

bigchaindb源碼分析(一)分析了bigchaindb如何解析命令列參數與設定檔,並據此啟動了日誌publisher與subscriber。對於bigchaindb start命令,將調用_run_init來初始化後端儲存,隨之利用pipeline來啟動block\vote等進程(bigchaindb源碼分析(二))。本節介紹bigchaindb的後端儲存。

_run_init由run_start所調用,代碼位於commands/bigchaindb.py

def _run_init():    # Try to access the keypair, throws an exception if it does not exist    b = bigchaindb.Bigchain()    schema.init_database(connection=b.connection)    b.create_genesis_block()    logger.info('Genesis block created.')

該函數首先建立一個Bigchain執行個體,Bigchain類位於core.py,其__init__函數將從bigchaindb.config中載入設定檔的配置,包括本節點的公私密金鑰、聯盟中其他節點的公開金鑰、一致性演算法外掛程式、以及定位串連資料庫的類等等。之後根據串連資料庫的類來初始化資料庫。最後建立創世區塊。 建立bigchain執行個體

Bigchain類__init__函數串連資料庫代碼如下所示。當類執行個體化的connect參數為None時,將調用backend.connect。

self.connection = connection if connection else backend.connect(**bigchaindb.config['database'])

其形參前的**相當於將形參中字典的key value一一對應到函數參數列表中。舉例來說:

>>> def test(a, b, c):...     print(a, b, c)...     >>> test(1,2,3)1 2 3>>> x=(1,2,3)>>> test(*x)1 2 3>>> y={'a':1,'b':2,'c':3}>>> test(**y)1 2 3

backend.connect參數列表如下。故而在調用時,將設定檔中database下的值一一對應到參數列表中。

# 參數列表def connect(backend=None, host=None, port=None, name=None, max_tries=None,            connection_timeout=None, replicaset=None, ssl=None, login=None, password=None,            ca_cert=None, certfile=None, keyfile=None, keyfile_passphrase=None,            crlfile=None):# 設定檔中的database項"database": {    "password": null,    "connection_timeout": 5000,    "ssl": false,    "replicaset": "bigchain-rs",    "login": null,    "max_tries": 3,    "name": "bigchain",    "port": 27017,    "host": "localhost",    "backend": "mongodb"},

bigchaindb後端儲存的代碼位於backend目錄下,目前包含了mongodb\rethinkdb兩種後端儲存資料庫。backend/connect.py中也指定了目前支援的兩種後端儲存。

BACKENDS = {    'mongodb': 'bigchaindb.backend.mongodb.connection.MongoDBConnection',    'rethinkdb': 'bigchaindb.backend.rethinkdb.connection.RethinkDBConnection'}

backend.connect尋找後端儲存類的代碼也就可以分為以下幾步了:首先是擷取設定檔中backend的值,之後從變數BACKENDS中找到對應的類,再通過反射機制根據類的字串路徑載入類

# backend為mongodbbackend = backend or bigchaindb.config['database']['backend']module_name, _, class_name = BACKENDS[backend].rpartition('.')Class = getattr(import_module(module_name), class_name)return Class(host=host, port=port, dbname=dbname,     max_tries=max_tries, connection_timeout=connection_timeout,     replicaset=replicaset, ssl=ssl, login=login, password=password,     ca_cert=ca_cert, certfile=certfile, keyfile=keyfile,     keyfile_passphrase=keyfile_passphrase, crlfile=crlfile)

rpartition將返回一個三元組,分別為最後一個分隔字元左邊的字串、分隔字元本身、以及最後一個分隔字元右邊的字串。因此,class_name將對應於MongoDBConnection。而後利用getattr函數載入該類,變數Class也就對應於MongoDBConnection類的一個執行個體了,最後則調用Class類。

>>> x='bigchaindb.backend.mongodb.connection.MongoDBConnection'>>> print(x.rpartition('.'))('bigchaindb.backend.mongodb.connection', '.', 'MongoDBConnection')

我們再來看bigchaindb.backend.mongodb.connection模組的MongoDBConnection類。該類繼承backend.connection.Connection類。實際上,所有後端儲存的串連類都應該繼承Connection類,然後再實現自己所獨立的操作資料庫的代碼。MongoDBConnection類的__init__函數將調用父類的初始化函數,之後將一些mongodb所專屬的配置儲存為類的成員,如replicaset。而父類的__init__則將一些後端儲存所共有的配置儲存為類的成員,如host\port\dbname等等。

class MongoDBConnection(Connection):    def __init__(self, replicaset=None, ssl=None, login=None, password=None,                 ca_cert=None, certfile=None, keyfile=None,                 keyfile_passphrase=None, crlfile=None, **kwargs):        super().__init__(**kwargs)        self.replicaset = replicaset or bigchaindb.config['database']['replicaset']
初始化資料庫

_run_init函數在建立Bigchain執行個體後,將對資料庫進行初始化。根據之前的分析,形參中的connection實際上是MongoDBConnection類的執行個體了。

schema.init_database(connection=b.connection)# backend/schema.pydef init_database(connection=None, dbname=None):    connection = connection or connect()    dbname = dbname or bigchaindb.config['database']['name']    create_database(connection, dbname)    create_tables(connection, dbname)    create_indexes(connection, dbname)

從函數名稱上可以看出,init_database的步驟為依次建立資料庫、表以及索引。然而,在backend/schema.py中這三個函數實際上居然都沒有函數體,不過有個裝飾器singledispatch

@singledispatchdef create_database(connection, dbname):    raise NotImplementedError@singledispatchdef create_tables(connection, dbname):    raise NotImplementedError@singledispatchdef create_indexes(connection, dbname):    raise NotImplementedError

裝飾器singledispatch的文檔在https://pypi.python.org/pypi/singledispatch。閱讀這些例子,可以看出相當於實現了函數多態,即可以通過不同的形參來調用同一個函數名的不同函數。文檔中的執行個體如下: 定義:通過裝飾器singledispatch來定義需要多態的函數,通過@fun.register(int)或者fun.register來進行註冊

>>> from singledispatch import singledispatch>>> @singledispatch... def fun(arg, verbose=False):...     if verbose:...         print("Let me just say,", end=" ")...     print(arg)>>> @fun.register(int)... def _(arg, verbose=False):...     if verbose:...         print("Strength in numbers, eh?", end=" ")...     print(arg)>>> def nothing(arg, verbose=False):...     print("Nothing.")...>>> fun.register(type(None), nothing)
調用
>>> fun("Hello, world.")Hello, world.>>> fun("test.", verbose=True)Let me just say, test.>>> fun(42, verbose=True)Strength in numbers, eh? 42>>> fun(None)Nothing.

由此,我們來尋找後端儲存中對create_database進行了註冊的函數。mongodb後端儲存backend/mongodb/schema中有以下代碼:

from bigchaindb.backend.utils import module_dispatch_registrarregister_schema = module_dispatch_registrar(backend.schema)@register_schema(MongoDBConnection)def create_database(conn, dbname):    if dbname in conn.conn.database_names():        raise exceptions.DatabaseAlreadyExists('Database `{}` already exists'                                               .format(dbname))    logger.info('Create database `%s`.', dbname)    # TODO: read and write concerns can be declared here    conn.conn.get_database(dbname)

module_dispatch_registrar的實現代碼為:

def module_dispatch_registrar(module):    def dispatch_wrapper(obj_type):        def wrapper(func):            func_name = func.__name__            try:                dispatch_registrar = getattr(module, func_name)                return dispatch_registrar.register(obj_type)(func)            except AttributeError as ex:                raise ModuleDispatchRegistrationError(                    ('`{module}` does not contain a single-dispatchable '                     'function named `{func}`. The module being registered '                     'was not implemented correctly!').format(                         func=func_name, module=module.__name__)) from ex        return wrapper    return dispatch_wrapper

看著好複雜的樣子。。按照之前裝飾器的思路,我們先來進行替換吧,首先展開module_dispatch_registrar,代碼相當於

@module_dispatch_registrar(backend.schema)(MongoDBConnection)def create_database(conn, dbname):    ...

也即

@dispatch_wrapper(MongoDBConnection)def create_database(conn, dbname):    ...# 其中變數module為backend.schemadef dispatch_wrapper(obj_type):    def wrapper(func):        ...    return wrapper

再把dispatch_wrapper展開

wrapper(create_database)# 其中變數module為backend.schema,變數obj_type為MongoDBConnectiondef wrapper(func):    ...return wrapper

所以這段代碼實際上是調用了(注意,這裡將所有變數寫成了字串,實際上均指得是對應的對象)

dispatch_registrar = getattr(backend.schema, create_database)dispatch_registrar.register(MongoDBConnection)(create_database)

因此,當init_database執行代碼create_database(connection, dbname)時,由於connection的類型為MongoDBConnection類,故而將調用的函數為backend/mongodb/schema.py中的create_database函數。create_tables以及create_indexes也是如此。 建立資料庫

如此,建立資料庫所調用的函數為backend.mongodb.schema.create_database,形參conn對應MongoDBConnection類的執行個體

@register_schema(MongoDBConnection)def create_database(conn, dbname):    if dbname in conn.conn.database_names():        raise exceptions.DatabaseAlreadyExists('Database `{}` already exists'                                               .format(dbname))    logger.info('Create database `%s`.', dbname)    # TODO: read and write concerns can be declared here    conn.conn.get_database(dbname)

conn.conn實際上是MongoDBConnection的父類Connection類的一個屬性,由於在該類__init__時,self._conn值為None,所以將調用self.connect(),而connect又將調用_connect函數self.max_tries_counter次,直到串連成功,或者拋出異常

@propertydef conn(self):    if self._conn is None:        self.connect()    return self._conndef connect(self):    attempt = 0    for i in self.max_tries_counter:        attempt += 1        try:            self._conn = self._connect()        except ConnectionError as exc:            ...        else:            break

_connect函數在去掉異常處理、函數調用時的參數列表後如下所示。函數邏輯也很清晰了,首先是初始化複本集,然後利用pymongo串連到mongodb資料庫,再用設定檔中的帳號與口令驗證,最後返回串連並驗證成功的client對象。注意最後返回的client對象將賦值給MongoDBConnection類的conn成員屬性。

def _connect(self):    try:        initialize_replica_set(self.host,...)        if self.ca_cert is None or self.certfile is None or \                self.keyfile is None or self.crlfile is None:            client = pymongo.MongoClient(self.host,...)            if self.login is not None and self.password is not None:                client[self.dbname].authenticate(self.login, self.password)        else:            logger.info('Connecting to MongoDB over TLS/SSL...')            client = pymongo.MongoClient(self.host,...)            if self.login is not None:                client[self.dbname].authenticate(self.login,                                                 mechanism='MONGODB-X509')        return client    except ...

initialize_replica_set函數也是首先串連mongodb資料庫,然後檢查資料庫是否已經配置了複本集,並與設定檔中的複本集的名字進行匹配,若匹配成功,則說明已經初始化了複本集,否則則初始化。。 建立表與建立索引

在建立資料庫成功後的下一步是建立表,這一步的代碼就比較簡單了:直接調用pymongo的create_collection函數,在資料庫下建立四個表:bigchain、backlog、votes、assets

@register_schema(MongoDBConnection)def create_tables(conn, dbname):    for table_name in ['bigchain', 'backlog', 'votes', 'assets']:        logger.info('Create `%s` table.', table_name)        # create the table        # TODO: read and write concerns can be declared here        conn.conn[dbname].create_collection(table_name)

在成功建立表之後,bigchaindb為這四個表分別建立二級索引,以方便進行查詢,如建立名為transaction_id的索引,用來支援對事務id進行查詢

@register_schema(MongoDBConnection)def create_indexes(conn, dbname):    create_bigchain_secondary_index(conn, dbname)    create_backlog_secondary_index(conn, dbname)    create_votes_secondary_index(conn, dbname)    create_assets_secondary_index(conn, dbname)def create_bigchain_secondary_index(conn, dbname):    logger.info('Create `bigchain` secondary index.')    # to order blocks by timestamp    conn.conn[dbname]['bigchain'].create_index([('block.timestamp',                                                 ASCENDING)],                                               name='block_timestamp')    # to query the bigchain for a transaction id, this field is unique    conn.conn[dbname]['bigchain'].create_index('block.transactions.id',                                               name='transaction_id')    ...

至此,資料庫初始化完成,_run_init中唯一還沒有閱讀的是如何建立創世區塊,這一部分下次再學習。。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.