游戏服务器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都不会重复。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏乐沙弥的世界

MySQL数据类型 -- 日期时间型

版权声明:本文为博主原创文章,欢迎扩散,扩散请务必注明出处。 https://blog.csdn.net/robinson_0612/art...

952
来自专栏逸鹏说道

程序猿是如何解决SQLServer占CPU100%的

文章目录 遇到的问题 使用SQLServer Profiler监控数据库 SQL1:查找最新的30条告警事件 SQL2:获取当前的总报警记录数 有哪些SQL语句...

3258
来自专栏芋道源码1024

数据库中间件 MyCAT源码分析:【单库单表】插入

本文主要基于 MyCAT 1.6.5 正式版 1. 概述 2. 接收请求,解析 SQL 3. 获得路由结果 4. 获得 MySQL 连接,执行 SQL 5. 响...

46912
来自专栏逸鹏说道

通过MySQL自动同步刷新Redis

在服务端开发过程中,一般会使用MySQL等关系型数据库作为最终的存储引擎,Redis其实也可以作为一种键值对型的数据库,但在一些实际场景中,特别是关系型结构并不...

81710
来自专栏程序猿

从0学习MySQL系列(三)概念篇

概要 ---- 在篇文章中提过:概念:数据库管理系统(Database Management System)一些语法的汇总点。 ...

2925
来自专栏吴伟祥

百万级数据库优化方案 转

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

912
来自专栏idba

MySQL 5.7 新特性之三

本系列文章基于5.7.20 版本讲述MySQL的新特性,从安装,文件结构,SQL,优化,复制等几个方面展开介绍5.7 的新特性和功能,同时也建议大家跟踪官方bl...

742
来自专栏性能与架构

体验 Mysql 操作 JSON 文档

新版 Mysql 中加入了对 JSON Document 的支持,可以创建 JSON 类型的字段,并有一套函数支持对JSON的查询、修改等操作,下面就实际体验一...

3373
来自专栏唐郑望的专栏

Django数据处理的一些实践

提到 Django 肯定避不开 MVC 模式,即模型(Model)-视图(View)-控制器(Controller),通过将业务逻辑、数据、界面显示分离的方法组...

2401
来自专栏GreenLeaves

Oracle TM锁和TX锁

CREATE TABLE "TEST6" ( "ID" VARCHAR2(30), "NAME" VARCHAR2(30), "...

1747

扫码关注云+社区