中台和平台的说法都存在,没有必要咬文嚼字两者的区别和各自定义。本文中统一使用平台。
上一回讲到,美丽温柔爱思考的小美设计了用户群组的系统模型###程序媛小美在营销中台的成长 ,leader 王哥针对系统扩展性和数据规模提了几个问题。
爱思考的小美很快就有了解决办法,这一次的设计方案在评审会上王哥非常满意。
考虑到用户群组获取用户Id 的方式多种多样,且未来可能会增加多种方式。小美进行了如下设计
- biz 产品线,区分公司内各个产品形态,未来新的产品线使用用户群组将为其分配一个 biz
- groupType 群组类型定义为 群组获取用户的方式。包括指定用户、从文件系统获取、课程群组或课程 id 购买用户。这样未来如果新增其他用户获取方式,直接新增类型即可。此外 如果文件系统类型有变化,例如公司自建文件系统、云厂商 S3等通过 groupSubType 来进行区分。未来其他 groupType 有需要再细分的需求,都可以使用 groupSubType 进行额外扩展。
- 获取方式的详细内容,例如文件系统路径、课程群组 id、课程 id、指定用户等方式。 统一使用mysql text 类型进行存储,如果运营指定的用户 id列表长度过大就在前端限制,建议运营上传到文件系统再创建用户群组。不同的 groupType 解析 content 的方式也不同。但是新增用户获取方式无需增加数据库字段,也许连扩展字段也无需增加。
- ext 扩展字段,json 格式,应对未来的需求变化。
- readyTime 主要考虑到某个课程的购买用户是动态变化的,所以新增readyTime, 记录用户群组一次性的构建完成时间。目前用户群组定义为一次性使用,并不打算实时更新购买的用户 id,readyTime 是预留字段。
- realType, 实时类型,是指用户群组是否实时更新,例如某个课程一直在卖课,每次用户购买完成都会被更新到用户群组中。目前默认是不需要实时更新。未来可能需要更新,那么使用 realType 区分。
- version 是考虑到未来用户群组可能会实时更新或被运营手动更新,新增了版本标记。
- status用来标记用户群组的构建状态。目前只有更新中、已就绪两种状态。
- 小美和 leader 王哥沟通后了解到,运营可能有诉求 同时选择多种获取方式 。那么增加一个 parentUserGroupId关联上父级用户群组 id 就可以了。
- expireTime 用于归档用户群组,考虑到用户群组的关联关系数量巨大,存在归档数据的需求。这需要在 UserGroup 上新增过期时间,定时扫描可归档的用户群组,删除掉用户群组、用户群组关联关系。
针对 leader 王哥提出的问题,用户群组的规模非常大,可能一个用户群组包含上千万、上亿的数据,userId 和用户群组直接关联会导致数据库记录过多。小美想到之前的经验:应对大数据量存储和查询的时候,思路只有两个,切分和聚合。既然数据量过大,可以考虑将 userId聚合为一组记录是不是更好呢?小美进行了如下设计。
- 用户群组和用户id的关联数据规模非常大,小美打算使用userGroupId进行分表。先暂定分100个表。
- 每 10000 个userId 对应一条关系记录, 这样 1 亿条用户也仅仅对应 1w 条记录。其中 userId 加逗号,大致共10个字符,1w 个 userId 对应 100K个字符。考虑到Utf-8 编码,数字和逗号都是 1 个字节。大致对应存储空间是 100K。针对批量查询的场景,不会对数据库有较大的压力。并且小美打算1W的阈值要支持动态可调整,这样万一线上压力过大,她可以随时调整。
- userId 列表有去重的诉求,如果需要去重,写入 数据库之前,需要根据userId+ userGroupId查询,确定不具备关联关系。 同时小美记得王哥说过,以后用户群组需要具备查询某个用户所有的用户群组的能力,也需要提供接口判断userId 和用户群组是否具备关联的查询能力。那么最好的方案当然是 redis 了。为此 小美打算使用redis hash 存储结构。 大key是userId(及必要的前缀), 小 key 是 userMatchId。当用户群组归档后,对应的小 key也要被清理。避免 hash 结构过大。(实际上还可以加上 userId, userGroupId 反向关联。即 userId 所属的 userGroupId 被存储到一条关系记录里。)
各位兄弟姐妹们,如果觉得本文没有注水,关注、点赞、收藏转发,怎么方便怎么来,小弟在此拜谢了。你的一次点赞就足够让我开心一上午。比心
问题:既然用户和用户群组的关联关系规模如此大,使用更大的分库分表方案是否可以,例如切分为10库, 100表/库,总共1K个表 有的场景可以,有的场景不可以。
- 查询用户群组包含的 userId 列表,使用即使用更大规模的分表方案不合适。即便分表后,一个用户群组的用户 id 也只被分配到 1 个表里,一次性扫描表中 1000w条记录, mysql 压力和查询时长都不可接受。更致命的是这样做一个表行数会非常庞大,mysql 性能会急剧下降。
- 数据库查询用户所属的用户群组。可以考虑使用更大规模的分表方案,但不推荐。一个用户所属的用户群组总归数量较少,因为存在归档,一般是1000 以下。所以分表方案也是可以的。但是新建一个大数据量群组,更新的记录数较多,性能也非常堪忧。所以这个方案,需要配套优化写入逻辑。
改良后的用户群组方案相比之前思考深度增加了很多。
- 考虑到未来获取用户方式 多种多样。预留多个类型字段,提供可扩展性
- 考虑到未来需要新增多个产品线接入。在用户群组表、关系表都预留产品线字段,虽然此处不起眼,但是如若不然,未来新的产品线接入时,这个小小的字段工作量可是非常大。系统建设初期,预留产品线字段,成本小,收益大。
- 考虑到未来个性化的需求,预留了 json 的扩展字段。
- 考虑到大数据量的读写需求,预留了归档字段清理过期数据。优化存储结构支持大数据量的写入和查询。
- 考虑到未来用户群组可能需要实时更新,也预留了实时更新类型字段。
- 考虑到一个活动可选择多个用户群组的潜在需求,预留了 parentUserGroupId,把多个用户群组聚合为 1 个群组。
以上的方案设计不是小美闭门造车,闷头搞出来的,而是和 leader 王哥、组内同事无数次工位旁沟通的结果。新入职的小美,工作状态渐入佳境。即便新入职一切都不熟悉,但是方案设计时,她积极和领导同事沟通交流,不闷头搞,时刻从领导那里得到信息输入,毫无疑问:他的leader 王哥更加了解系统未来可能要支持的场景。
小美的思路渐渐清晰,她在和 leader 王哥沟通多次后,用户群组的系统模型终于确定了。小美心里长舒一口气,她相信方案评审时一定会很顺利,领导肯定不会提出一堆问题和 TODO(事实也是如此)。
后来的方案评审时,王哥对小美说:"用户群组是营销系统必需的系统能力,目前没有其他专门团队在做。我们这样设计,未来足以满足营销系统所需。"。王哥呢,是敢拼敢干、非常自信的程序员,说起话来,自然也有大多数男生的毛病,爱吹牛。
但美丽可爱的小美可是十分谦虚,她笑着对王哥说:"希望公司日渐壮大,业务越来越复杂多样。到时候某人可能会被无情的打脸。"
"那可太好了,求之不得",王哥得意的笑着说。 小美不知道的是,王哥入职早,几年来,已经有了数量可观的期权。他就等着上市买房,财务自由的那一天呢。
后来公司的业务发展确实迅速,小美因为设计过用户群组,来来被紧急抽调到新组建的社交类产品线,主导设计社交产品用户关注关系的设计。在那里小美将继续挑战大数据量高并发场景的关联关系设计。毫无疑问,用户群组的设计经验,让小美有了更多的锻炼机会和人生机遇。
当前用户群组的设计暂时告一段落,接下来的小美又遇到什么问题呢?为什么后来王哥对小美说: 你太心急了?
各位兄弟姐妹们,如果觉得本文没有注水,关注、点赞、收藏转发,怎么方便怎么来,小弟在此拜谢了。你的一次点赞就足够让我开心一上午。比心