游戲服務器ID生成器組件


    游戲服務器程序中,經常需要生成全局的唯一ID號,這個功能很常用,本文將介紹一種通用ID生成組件。游戲服務器程序中使用此組件的場景有:

  •  創建角色時,為其分配唯一ID
  •   創建物品時,每個物品需要唯一ID
  •   創建寶寶、靈獸時需要唯一ID

原理介紹

         ID生成器的原理就是使用全局整型變量,每次分配之后該變量遞增1。由於服務器重啟后全局變量失效,故全局變量需要持久化保存,相應的,服務器啟動時從持久化中載入全局變量。ID生成器的工作流程為:

  •   建議采用數據庫作為持久化存儲,本文以mysql為例
  •   啟動時從數據庫載入全局變量,作為分配的起始值
  •   每次分配id前,先遞增全局變量
  •   每次遞增后,更新數據庫中的全局變量值
  •   由於相同的功能模塊可能在不同的GameServer上運行,故唯一ID號使用64位整型,其中16位用來表示GameServerID
  •   由於不同的功能即使ID號相同互不影響,如角色ID和物品ID實際上是獨立的互不沖突的,所以全局變量可以分類型,不同的類型使用不同全局變量。

示例代碼

 

sql = """
create table id_generator
(
  AUTO_INC_ID bigint not null,
  TYPE int not null,
  SERVER_ID int not null,
  RUNING_FLAG int not null,
  primary key(TYPE, SERVER_ID)
)
"""
class idgen_t:
    def __init__(self, db_host_, type_id_ = 0, server_id_ = 0):
        self.type_id = type_id_
        self.server_id = server_id_
        self.auto_inc_id = 0
        self.db_host = db_host_
        self.db      = None
        self.saving_flag = False
        self.runing_flag = 0
    def init(self):
        self.db = ffext.ffdb_create(self.db_host)
        ret = self.db.sync_query("SELECT `AUTO_INC_ID`, `RUNING_FLAG` FROM `id_generator` WHERE `TYPE` = '%d' AND `SERVER_ID` = '%d'" % (self.type_id, self.server_id))
        #print(ret.flag, ret.result, ret.column)
        if len(ret.result) == 0:
            #數據庫中還沒有這一行,插入
            self.db.sync_query("INSERT INTO `id_generator` SET `AUTO_INC_ID` = '0',`TYPE` = '%d', `SERVER_ID` = '%d', `RUNING_FLAG` = '1' " % (self.type_id, self.server_id))
            return True
        else:
            self.auto_inc_id = int(ret.result[0][0])
            self.runing_flag = int(ret.result[0][1])
            if self.runing_flag != 0:
                self.auto_inc_id += 10000
                ffext.ERROR('last idgen shut down not ok, inc 10000')
            self.db.sync_query("UPDATE `id_generator` SET `RUNING_FLAG` = '1' WHERE `TYPE` = '%d' AND `SERVER_ID` = '%d'" % (self.type_id, self.server_id))
        #if self.auto_inc_id < 65535:
        #    self.auto_inc_id = 65535
        return True
    def cleanup(self):
        db = ffext.ffdb_create(self.db_host)
        now_val = self.auto_inc_id
        db.sync_query("UPDATE `id_generator` SET `AUTO_INC_ID` = '%d', `RUNING_FLAG` = '0' WHERE `TYPE` = '%d' AND `SERVER_ID` = '%d'" % (now_val, self.type_id, self.server_id))
        return True
    def gen_id(self):
        self.auto_inc_id += 1
        self.update_id()
        low16 = self.auto_inc_id & 0xFFFF
        high  = (self.auto_inc_id >> 16) << 32
        return high | (self.server_id << 16)| low16
    def dump_id(self, id_):
        low16 = id_ & 0xFFFF
        high  = id_ >> 32
        return high << 16 | low16
    def update_id(self):
        if True == self.saving_flag:
            return
        self.saving_flag = True
        now_val = self.auto_inc_id
        def cb(ret):
            #print(ret.flag, ret.result, ret.column)
            self.saving_flag = False
            if now_val < self.auto_inc_id:
                self.update_id()
        self.db.query("UPDATE `id_generator` SET `AUTO_INC_ID` = '%d' WHERE `TYPE` = '%d' AND `SERVER_ID` = '%d' AND `AUTO_INC_ID` < '%d'" % (now_val, self.type_id, self.server_id, now_val), cb)
        return

 

 

總結

  •   如果不同區組的GameServer使用不同的ID,那么即使合區,那么老的ID仍然可以工作
  •   全局變量遞增后,立即更新db,保證盡量保證db和內存的一致性,但是保證同一時間只有一個db 操作執行,db update回調后檢查若又被修改后,再次執行db update。比如一次分配了100個id,避免出現“驚群”的db update。
  •   由於遞增id后立即執行了db update,幾乎可以保證db和內存的一致。如果出現運行期宕機,可能會出現db和內存的不一致。可以在此基礎上做一個加強版,就是數據庫中的每一行都加一個字段running,每次db update都設置為0,服務器正常關閉的時候設置為1。啟動服務器載入全局變量時,若該值為0,則在此基礎上增加10000。這樣可以保證所有的ID都不會重復。

更多精彩文章 http://h2cloud.org


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM