1. 引言
在产品业务多元化的过程中,往往会遇到一些与业务发展不匹配的架构问题,这些问题可能会在业务迭代中以性能差、重复开发等形式表现出来,进而给系统的扩展和维护带来一些挑战。
本文准备以一个帐号权限的业务为例,来讨论下:当遇到架构问题时,如何识别并及时调整,以适应业务的发展。
在问题开始之前,先对帐号的权限作下说明,以便更好的理解。
在本文中,权限是指帐号在一个产品系统中能允许使用的功能列表。以视频会议产品举例大概如下图所示:
一个帐号在多个产品中可能会有多套权限,就权限的理解达成一致后,下面我们进入正文。
2. 问题
企业有一个合同计费系统叫作Boss
, 负责合同和帐号权限的开通, 它和业务系统之间的交互形式最初可能是这样:
- 一个帐号在系统中有两套组件权限:网络会议组件权限和云会议组件权限
- 开通帐号时,boss将两组权限分别下发到网络会议平台和云会议系统,由每个系统分别管理自己职责范围内的帐号权限数据。
如下图所示:
看起来也挺清晰,boss和业务之间耦合少,不用关心产品的业务迭代,只通过回调机制来同步帐号数据。
但这可能只是第一个版本,随着公司产品和业务的发展,又上线了一些系统,如:
- 电话业务
- 直播业务
- 课堂业务
并且一个帐号有可能同时开通这些业务,Boss就要向多个产品系统都同步数据,系统模型演变成下图所示:
上图中红色的气泡里也有指明,随着产品业务线的扩张,带出了不少问题,例如:
- 开通慢:开通帐号要回调的业务系统越来越多,由于Boss要等每个业务系统都回复处理成功后帐号才算开通完成,所以帐号开通速度就变得越来越慢;
- 重复做:各个产品都要自己维护一份帐号权限数据,并且业务逻辑相似,代码也类同;
- 扩展差:各个团队对帐号权限这块儿业务数据的理解层次不齐,直接表现是代码扩展性不足,导致的结果是:每当产品要增加功能权限时,对应系统的代码和DB结构就要不停的跟着调整。
尤其是第1个问题,当面向C端的个人注册上线后,这个帐号开通慢的问题就如同被拿到放大镜下,在用户侧直接表现一种很差的体验,例如:
- 用户提交注册表单成功了,但打开产品的客户端却无法登录,原因在于向业务系统的帐号同步工作是后台异步进行的,还未同步完。
这种问题在目前的系统模型下几乎没什么有效的根治办法,只能让用户等待。但等待也就意味着体验差、用户流失。
而且随着业务的多元化继续发展,帐号开通速度还会变得更慢。如果帐号开不出来,就等于是把用户挡在了门外面,产品都没有展示给用户的机会,所以问题还是相当棘手的。
那如何解决呢?
3. 方案思路
对于此问题的解决思路,我们从架构调整、存储优化、可扩展性三个方面来描述具体的优化方案。
3.1 架构调整
上面提到,各个产品对帐号权限的维护工作几乎是相同的。因此,我们可以尝试把帐号权限从各个产品中提取出来,成为公共服务,我们暂且称为统一权限
。引入此服务后,系统需要做一定的调整:
- 各个产品都改从统一权限服务查询帐号数据,自己不需要再额外存储;
- Boss开帐号时只需要等待统一权限的结果回执,帐号开通模型大大简化;
系统模型演化如下:
这样调整后,带来几个明显的好处:
- 减少重复工作:不论有多少产品,帐号权限的回调、保存、查询等操作都只需要维护一份;
- 提升开通速度:Boss只需要把数据下发到统一权限即可认为帐号开通成功,回调链路大大缩短,帐号开通慢的问题也就得到解决。
- 带动登录业务的优化:依赖帐号校验的登录也可以提取为公共服务,进一步减少了各产品中的重复业务工作量。
3.2 存储优化
看到这里,不知道你心中是否有疑问:为何要额外增加一个统一权限服务,而不直接让Boss来提供帐号查询服务呢?
- 原因1: 前面已经提到的,Boss不愿意与业务系统耦合,阻力大;
- 原因2:Boss的DB存储不是为了高并发场景服务的,它有很大的历史包袱(具体下面会说明),即使它提供了查询服务,也很有可能达不到优化的目标。
帐号权限的数据内容本身是比较复杂的,它涉及到多块信息:
- 帐号信息
- 产品状态
- 资源信息
- 开通的产品组件
- 开通的权限属性
- ……
传统的关系数据库基本就是把每块信息建一张表,使用时再多表联查出来合并数据。导致的结果就是IO多,查询慢,并且还不好扩展字段。
Boss基本就是类似上面这种拆分存储模式,甚至表比上面更多。估计也正是因为如此,Boss才把帐号下发给各个产品,以免自己成为性能瓶颈点。
这个问题放在10多年前的Boss上可能确实是个问题,但放在如今,却不一定难解。
仔细梳理会发现,上面这些信息不论如何分块,其实本质上都是帐号的一部分,如果把帐号的这些组成部分视作一个整体,其实就是一个带嵌套结构的文档对象。而这类数据的存储最适合面向文档的数据库,例如mongoDB。
它的特点是一个文档就能存下整个对象,特别适合存储结构化的对象。帐号权限用mongoDB存储后的结构示例:
{
"_id": {
"$oid": "5ef0a57570b03d622f967848"
},
"userId": 81299529,
"userStatus": 82,
"status": 62,
"contractId": 73522,
"productId": 60000,
"billingCode": "88130601",
"resource": { # 帐号资源信息
"siteId": 95729,
"siteURL": "20180518test8.quanshi.com/meetnow",
"hostPassword": "2020194396",
"guestPassword": "2020194397",
"password": ""
},
"accountId": 154772,
"siteId": 95729,
"components": [ # 产品组件列表
{ # 产品组件1
"id": 1,
……
},
{ # 产品组件2
"id": 2,
"name": "summit",
"activationUrl": "",
"property": { # 帐号在产品下的权限信息
"AllowGuestMuteOverride": "0",
"MuteOverride": "2",
"AllowHostMuteOverride": "0"
},
"status": 1
},
{ # 产品组件3
"id": 22,
……
}
],
"expiryDate": 0,
"gender": 0,
"vip": "",
"conferenceName": "88130601",
"isFee": 1,
"buyMonths": 0,
"buyParties": 0
}
这样,带来两个好处:
- 易扩展:不论是帐号信息、权限属性和还是组件都很容易扩展,DB结构不用调整;
- 查询快:帐号所有信息都在一个文档中,一次IO就能将它查出来。
3.3 扩展性优化
此处扩展性主要是指业务逻辑的可扩展性。在新设计的统一权限服务上,如果帐号权限需要扩展业务逻辑,例如:
- 支持给免费帐号特殊定义权限
- 支持按产品强制关闭某个功能权限
- 支持按会员等级来定义权限
- 支持按商业模式来定义权限
- 支持付费功能的免费试用权限
先不论这些业务的实现细节,单就工作量和影响范围来说,在新架构下实现就会比老架构下小很多。因为不论需求怎么做都只是在统一权限内部调整,各个产品系统一般都不用关心。
不仅仅是业务扩展,像一些性能调优,也只需要在统一权限服务内部考虑和实施优化方案,影响范围很容易评估和控制,这都得益于帐号权限从各个系统中提取为独立服务。
小结
本文以帐号权限为主要业务视角,介绍了一个局部架构演进的案例:通过将公共业务提取为独立服务并针对性设计,化解了帐号开通速度慢和相似业务重复开发的实际难题,同时让系统架构能与业务发展相匹配。
介绍已经完成的案例是容易的,但实际工作中要想将架构优化真正的落地却并不容易,我觉得至少需要具备以下几个条件:
- 有能发现问题的全局视角,如果只盯着局部,是很难看到整个系统结构层面的问题的;
- 有能协调资源的领导者,如果要优化的范围跨了部门,想推动下去是很难的,除非掌握资源的上级愿意帮你协调,或者自己有协调这些资源的能力和权力;
- 有能将思路真正落地的干将,提出优化方向需要的是前瞻思维,而将思路转化为能够落地的设计、代码和方案,则需要的是胆大心细 + 脚踏实地。
正因为如此,架构问题才是系统中最难解决的问题之一,有的时候缺方案、有的时候缺人、有的时候缺支持,甚至花了几个月时间最终夭折的也不乏例子。
所以有一个互相支持的研发团队和上下级很重要,最后祝愿看文章的你也能在自己的工作中获得支持,做出更大的成绩。
参考阅读:
- 从功能设置提取来看架构如何演进:https://blog.csdn.net/xiaojia1001/article/details/134214400
- 一个功能试用模块的抽取案例:https://blog.csdn.net/xiaojia1001/article/details/132959395