文章

新gss模型设计

新gss模型设计

GSS 重构思路

利用 MongoDB 对现有业务、未来可支持的业务进行数据建模,建立起多个数据模型,分别适用于不同类型的业务。

CVS配置

GSS 的 CVS 配置从时间成本、安全隐患、后期维护的角度考虑,不再重新建表。

在旧的 define_hisgss 中,为每个要迁移的业务新建一个新的配置,用于双跑。双跑后期,将老 hid 对应的配置修改为新配置,具体细节见下面的双跑思路。

不同模型的在 define_hisgss.model 中的字段配置和含义见下方的 GSS 新存储模型介绍。

字段

0)配置管理字段

字段所需位置:

好像没有具体需要的位置?仅仅是为了方便管理。

  • "hid":唯一配置id,此字段没有实际作用,仅为了方便管理。

1)解析配置时必要字段

字段所需位置:

【SearchDomain.cpp 】->【CSearchDomain::Init(const Json::Value & root) 】

  • "type":GSS的存储模型类型,用来对应不同的类。此字段对任何模型都必须

2)CSearchModel 基类字段

  • "pidfield":实体字段,指定哪个字段代表实体ID。此字段必须,但在Mongo模型中无用
  • "keepday":生命周期,检索信息保留的时间,过期将被清除;统一以日为单位,0表示永久保留。此字段不配置的情况下默认为0,在 mongo 模型中,此字段不起作用,仅为了方便管理和查询,doc 的 TTL 由 mongo 数据库通过索引实现。
  • "rollingcycle":无实际意义。滚动周期,模型实际存储滚动的周期,可以为:日、周、月、季、年、永久(默认)。此字段必须,但在Mongo模型中无用
  • "tablename":表名,从 mysql 遗留的字段。此字段必须,但在Mongo模型中无用
  • "clusterName":所属集群名。当前无实际意义。此字段非必要。且没有业务使用此字段。此字段可以缺失,且在Mongo模型中无用

3)数据库连接信息

  • "mongoCluster":使用的MongoDB集群。【这个字段应该不需要】
  • "mongoDatabase":使用的MongoDB数据库。【这个字段可能也不需要?】
  • "mongoCollection":使用的MongoDB集合。【这个字段可以考虑用上面的tablename来代替】

上面的三个字段需要后续和暴常军讨论,再决定是写在配置里还是直接硬编码就可以了。

OSS中Mongo的连接配置就是直接硬编码的。

4)mongoModel 类型通用的字段

  • "keyfields":doc 中作为检索的字段。【需要注意的是,这里的顺序应该与索引顺序一致】
    • 查改删的时候:直接按照此处配置的字段解析接口中的
    • 的时候:先从从entity(用户信息)中查找,然后从info(索引字段)中查找。
  • "valuefields_public":doc 或 array 中作为信息数据存在的字段。不同实体相同的信息。
    • 新增的时候:先从info(索引字段)中查找,然后desc(比赛信息)查找。
  • "valuefields_entity":doc 或 array 中作为信息数据存在的字段。不同实体独立的字段。
    • 新增的时候:先从entity(用户信息)查找,然后从info(索引字段)中查找。
  • "maxlength":同一个索引中保留的最多信息条数(array中保留的数据条数)
  • "whitelist":白名单列表(需要的时候设置,不需要的时候不需要该字段)
  • "whiterange":白名单范围(需要的时候设置,不需要的时候不需要该字段)

5)不同模型中的独有字段

  • mongoArrayModel:
    • 【无】
  • mongoSingleModel:
    • 【无】
  • 【占位】

配置实例

以一个用于迁移的新 mongoArrayModel 类型的业务为例:(斗地主近20次夺冠时间,ddzbtime)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "hid": 72602,
  "type": "mongoarraymodel",
  "pidfield": "",
  "rollingcycle": "",
  "tablename": "",
  "keepday": 0,
  "maxlength": 20,
  "keyfields": [
    "pid",
    "mpid"
  ],
  "valuefields": [
    "mpid",
    "at"
  ],
  "whitelist": [
    {
      "key": "rank",
      "values": [
        1
      ]
    }
  ],
  "mongoCluster": "mongo_hisgss",
  "mongoDatabase": "GSS_test",
  "mongoCollection": "testDDZBTIME"
}

老配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "hid": 2602,
  "type": "filterlist",
  "namespace": "DDZBTIMEMPID",
  "partitionfield": "mpid",
  "pidfield": "pid",
  "rollingcycle": "permanent",
  "tablename": "ddzbesttime",
  "maxlength": 20,
  "valuefields": [
    "mpid",
    "at"
  ],
  "entityinfo": [],
  "whitelist": [
    {
      "key": "rank",
      "values": [
        1
      ]
    }
  ]
}

GSS存储模型

保留的老模型

保留的模型有两类:

  • 使用 MySQL 的业务。包括 录像奖状 两个大业务,因为 WEB 那边有的是直查 MySQL,不太好让他们做修改。

  • 内部包含处理逻辑、不是单纯数据模型的业务:例如 GSS 模型为 normallist 的 比赛回顾 的几个业务;比赛模型为 normalrecordmodel 的 本地录像 业务;包含复杂逻辑补丁的业务,例如使用 hash 的 “JJ直播家族改”、使用 filterlist 的 “JJ直播”。

    这几个模型如果要迁移的话,就要考虑怎样处理其中的逻辑判断,如果要迁移的话,有以下几个思路:

    • 在 GSS 以 mongo 为基础,为上述业务定制化创建对应的模型。相当于用 mongo 重写上面提到的那几个定制模型。
    • 将这几个业务进行拆分,把数据存储和逻辑判断拆开,将逻辑判断放到 Store 中,相当于在 Store 中为这几个业务打了一堆补丁,处理定制化的业务逻辑。
    • 再详细研究一下这几个业务,看看能否抽象出 “包含简单逻辑的数据模型”,如果可以的话就在 GSS 中创建这几个数据模型。难点:初步评估不太好弄、并且有新旧模型处理结果不一样的隐患。

MongoArrayModel

1. 模型方案

  • 主要使用 MongoDB 中的 array 结构做数据存储。

  • 提供 filter 筛选功能:针对目标字段做筛选,符合条件的数据才会写入。类似白名单。

  • doc 过期:先评估业务数据量,然后联系 DBA 确定采用以下哪种方式过期数据:

    • 业务数据量小:将 “UpdateAt” 字段设置为 TTL 索引,并设置过期时间。
    • 业务数据量大:DBA 设置定时任务,在低峰期(凌晨1~4点)按照 “UpdateAt” 字段过期数据。
  • doc 中的 array 各个元素的过期解决方案:【初步】

    • 不实现这个功能

    • 在 array 的每个元素中也增加字段 “lastModifyTime”,用于记录最后一次修改的时间。在每次【查询】的时候,筛选出整个 array 中过期的元素,全键指定元素,执行删除。

      选择在 “查询” 而非 “写入” 时删除的原因:HIS 是一个高写低读的服务,有的业务的读写比例甚至能达到1:100。而 array 中过期单个元素这个功能的又比较复杂,因此在低频率的“查询”时来做这件事较为合理,可避免给MongoDB服务器带来大量的ops。

  • doc 中 array 支持按照最大长度截断,且要求每个业务必须要设置最大长度。

  • 基本功能:提供 “增删改查” 4个基本功能,均是针对 array 中的元素进行操作的。

    注意点:4 个基本功能尽量保证都是原子操作,至少保证不会线程冲突。

  • 文档数据结构:

    • key:文档索引。
    • time:”createAt” 和 “updateAt” 两个时间戳。
    • value-array:一个数组,用来记录所有的数据。
  • 【占位】

2. 模型特点

1)优点
  • 支持多源写入。例如“赛程回顾”业务。
  • 查询速度较快(不考虑 array 中单个元素按照时间过期功能的情况下)。匹配到结果后可以直接返回一个 array,一次 search 就能满足要求。
  • 自然支持按照最大长度截断的功能。
2)缺点
  • 不好实现 array 中单个元素按照时间过期的功能。
  • 写入速度不如单独插入一个 doc 快。向目标 doc 的 array 插入一个新元素时,实际是调用的 update 方法。
  • “删除” 或 “更新” 有隐患。通过条件能匹配到 array 中的多个结果时,是否都删除/更新?解决方法:
    • 通过限制字段数量,增加安全保护
    • 考虑该模型不要支持 “删改” 功能
3)适用业务
  • 需要多源写入的业务。
  • 最近 N 条类型的业务。且 N 的条数比较少。

3. MySQL 配置

  • 具体细节有待设计
1)CVS配置
1)key
  • doc 的组成部分,作为索引键存在。
  • 在 MongoDB 中建表的时候将这些字段指定为索引。
  • 增删改查时,通过 key 中的字段定位目标 doc。
  • MySQL 配置示例:
  • Mongo 文档内容示例:
  • 【占位符】
2)value
  • doc 的组成部分,作为存储内容存在。
3)filter
  • 生成 doc 时的筛选条件,类似于白名单。

4. 基本功能

  • 待完善

5.业务示例

索引设置
Name & Definition Type Size Properties Status  
_id_ regular 16.0 MB unique Ready  
updateAt_1 regular 12.9 MB TTL Ready  
userid_1_gameid_1_mpid_1 regular 34.1 MB unique compound Ready  

上表的 Size 是在有效数据为 5000W 条、ModelArray 表中 doc 个数为 50W 个时的数据。

Document示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
  "_id": {
    "$oid": "6790c223b6a028b5bf30364a"
  },
  "createAt": {
    "$date": "2025-01-22T08:21:08.448Z"
  },
  "updateAt": {
    "$date": "2025-01-22T08:21:08.448Z"
  },
  "gameid": 1105,
  "mpid": 77080,
  "userid": 10000001,
  "gamedata": [
    {
      "round": 57,
      "result": "Win",
      "role": "Prince",
      "_id": "6790615ca6faf6b3a1a8f40a",
      "_ts": {
        "$date": "2025-01-22T03:09:16.490Z"
      }
    },
    {
      "round": 57,
      "result": "Win",
      "role": "Prince",
      "_id": "6790615ca6faf6b3a1a8f579",
      "_ts": {
        "$date": "2025-01-22T03:09:16.687Z"
      }
    }, ...
  ]
}

MongoSingleModel

1. 模型方案

  • 一个 doc 就是一条记录,查询的时候通过聚合语句查询多条结果,整理后返回。
  • 提供 filter 筛选功能。
  • doc 过期,也就是每条记录的过期。与 MongoArrayModel 一样:业务量大的话走 DBA 定时任务,任务量小直接设置 TTL。
  • 不提供最大长度截断的功能,因为不需要,按照时间戳 “updateAt” 过期就行。客户端想取多少自己传参。
  • 基本功能:提供 “增删改查” 4个基本功能,均是针对单个 doc 进行操作的。
  • 文档数据结构:
    • key:文档索引
    • time:”createAt” 和 “updateAt” 两个时间戳。
    • value-single:单个的键值对,用于记录所需的数据。
  • 【占位】

2. 模型特点

1)优点
  • 写入速度较快。因为每次都是单独插入一条新的文档。
  • 方便实现 “删改” 的功能。因为一条 doc 就是一条记录,匹配到结果后直接对目标 doc 执行即可。
  • 方便实现针对doc过期的功能。此模型下的每条索引,自然与OSS中的content生命周期一致。不会出现查到了索引,但是通过索引中的oid查找不到content的情况。
  • 此种模型下可以考虑将 content 直接存到 GSS 里,不用担心倾斜的情况。
2)缺点
  • 查询速度较慢。因为需要根据条件聚合。
  • 不好实现最大长度截断的功能。解决方案:不实现最大长度截断,按照 TTL 过期即可。
  • 将 MongoDB 按照结构型数据库来使用了,看起来有些笨。那为啥不直接用 MySQL?
3)适用业务
  • 插入后需要 “删改” 的业务。

3. MySQL 配置

  • 待完善

4. 基本功能

  • 待完善

5.示例

索引设置
Name & Definition Type Size Properties Status  
_id_ regular 1.6 GB unique Ready  
updateAt_-1 regular 941.0 MB TTL Ready  
userid_1gameid_1_mpid_1_updateAt-1 regular 3.1 GB unique compound Ready  

上表的 Size 是在有效数据为 5000W 条、ModelSingle 表中 doc 个数为 5000W 个时的数据。

Document示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "_id": {
    "$oid": "6790aa74acfaace9b517ab59"
  },
  "userid": 10000006,
  "gameid": 1105,
  "mpid": 77019,
  "round": "66",
  "result": "Draw",
  "role": "Emperor",
  "createAt": {
    "$date": "2025-01-22T08:21:08.448Z"
  },
  "updateAt": {
    "$date": "2025-01-22T08:21:08.448Z"
  }
}

新GSS双跑思路

整体思路:

  1. 完成GSS编码。保证接口不变,在Model中新增使用MongoDB的模型,通过hid与CVS配合使用。

  2. 完成Store编码。在Store中搭建双跑系统,配合store.ini实现双跑逻辑(见本md文档同路径的流程图:2025新GSS双跑设计.drawio

  3. 内网测试。按照如下流程测试双跑流程和正式流程。

  4. 向DBA申请如下资源:【MySQL和redis直接用内网的够用吗】

    • MongoDB资源。用于GSS双跑和正式使用。并提前创建目标业务的表、按照业务配置设置好索引。
  5. 搭建GSS双跑环境。在4或8台机器上布置双跑的GSS。搭建双跑环境可参考:HIS-双跑准备+升级服务.md

  6. 升级老GSS服务。因为新旧gss业务都是写在同一张 define_hisgss 表上,如果老GSS不升级的话就会导致CVS配置解析失败。(这里会有一定隐患:因为GSS修改的比较多,如果对老业务造成影响的话,这里会出问题,因此在这一步之前,需要对新GSS做充分测试)

  7. 修改CVS配置。将要迁移的业务,按照新MongoDB模型,写好配置,新旧配置都放在 define_hisgss 中。

  8. 修改 store.ini 配置。将所有需要迁移的业务配置在store.ini中。配置示例如下:

    1
    2
    3
    
    [DoubleRun]
    DoubleRunGSS=ddzbtime:ddzbtime_m:2,coupleteam:coupleteam_m:2,
    ;参数解释:“老业务配置名:新业务配置名:双跑状态,... ”
    
  9. 升级所有的Store服务(包含双跑代码)。但此时还未开始双跑,因为ini中配置的双跑状态默认为1。

  10. 开始双跑。修改 store.ini 配置,将双跑状态统一修改为 2,开始向支线 GSS 双跑。

  11. 双跑一段时间(一个月?),观察双跑成功率的状态,确定新 GSS.exe 服务没有问题。

  12. 迁移老数据。将要迁移业务在redis上的数据迁移至MongoDB中。保证MongoDB中的数据与redis全量相同,且从新老GSS中获取的数据均一致。

  13. 将“要迁移业务”的流量切到双跑支路。修改 store.ini 配置,双跑状态统一修改为 3,此时,store 针对“要迁移业务”,将旧hid转为新hid,仅通过主流程的GSS传入新hid获取新的来自MongoDB的结果。

  14. 修改CVS配置,将老hid的配置改为新的使用MongoDB的模型配置。

  15. 修改 双跑状态为1,此时,主流程的GSS通过老hid的配置从MongoDB读写新的数据。

  16. 双跑结束。将store服务替换为没有双跑代码的版本。

  17. 回收双跑资源。

注意点

  • CVS配置:要迁移的 GSS 业务的新旧配置都写在表 define_hisgss 中。通过hid来区分业务的新旧配置。
  • 双跑的业务:全量业务双跑,包括:待迁移业务、不迁移的业务。这样可以最大程度保证双跑支路的 GSS 的可用性。
本文由作者按照 CC BY 4.0 进行授权