新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双跑思路
整体思路:
-
完成GSS编码。保证接口不变,在Model中新增使用MongoDB的模型,通过hid与CVS配合使用。
-
完成Store编码。在Store中搭建双跑系统,配合store.ini实现双跑逻辑(见本md文档同路径的流程图:
2025新GSS双跑设计.drawio) -
内网测试。按照如下流程测试双跑流程和正式流程。
-
向DBA申请如下资源:【MySQL和redis直接用内网的够用吗】
- MongoDB资源。用于GSS双跑和正式使用。并提前创建目标业务的表、按照业务配置设置好索引。
-
搭建GSS双跑环境。在4或8台机器上布置双跑的GSS。搭建双跑环境可参考:
HIS-双跑准备+升级服务.md -
升级老GSS服务。因为新旧gss业务都是写在同一张
define_hisgss表上,如果老GSS不升级的话就会导致CVS配置解析失败。(这里会有一定隐患:因为GSS修改的比较多,如果对老业务造成影响的话,这里会出问题,因此在这一步之前,需要对新GSS做充分测试) -
修改CVS配置。将要迁移的业务,按照新MongoDB模型,写好配置,新旧配置都放在 define_hisgss 中。
-
修改 store.ini 配置。将所有需要迁移的业务配置在store.ini中。配置示例如下:
1 2 3
[DoubleRun] DoubleRunGSS=ddzbtime:ddzbtime_m:2,coupleteam:coupleteam_m:2, ;参数解释:“老业务配置名:新业务配置名:双跑状态,... ”
-
升级所有的Store服务(包含双跑代码)。但此时还未开始双跑,因为ini中配置的双跑状态默认为1。
-
开始双跑。修改 store.ini 配置,将双跑状态统一修改为 2,开始向支线 GSS 双跑。
-
双跑一段时间(一个月?),观察双跑成功率的状态,确定新 GSS.exe 服务没有问题。
-
迁移老数据。将要迁移业务在redis上的数据迁移至MongoDB中。保证MongoDB中的数据与redis全量相同,且从新老GSS中获取的数据均一致。
-
将“要迁移业务”的流量切到双跑支路。修改 store.ini 配置,双跑状态统一修改为 3,此时,store 针对“要迁移业务”,将旧hid转为新hid,仅通过主流程的GSS传入新hid获取新的来自MongoDB的结果。
-
修改CVS配置,将老hid的配置改为新的使用MongoDB的模型配置。
-
修改 双跑状态为1,此时,主流程的GSS通过老hid的配置从MongoDB读写新的数据。
-
双跑结束。将store服务替换为没有双跑代码的版本。
-
回收双跑资源。
注意点
- CVS配置:要迁移的 GSS 业务的新旧配置都写在表 define_hisgss 中。通过hid来区分业务的新旧配置。
- 双跑的业务:全量业务双跑,包括:待迁移业务、不迁移的业务。这样可以最大程度保证双跑支路的 GSS 的可用性。