[简述] 一个从零开始的小项目, 持续推进. go-ums 开发目标是一个开源项目, 核心由 golang 开发, 提供用户管理(user-management-subsystem) / AAA 认证/鉴权/授 / 多业务会话共享与管理等, 以支持分布式部署及云部署为主要目标
0. go-ums 是什么
go-ums 开发目标是一个开源项目, 核心由 golang 开发, 提供用户管理(user-management-subsystem) / AAA 认证/鉴权/授 / 多业务会话共享与管理等, 以支持分布式部署及云部署为主要目标
这是一个从零开始的小项目, 持续渐进
在 reading-go 夜读 相关 issue 讨论中, 有个 gin 的练手项目, 有点意思.
这是一个类似的项目, 不同的地方是, 这个项目是以文档开始的.
更多项目关联文档, 请访问 https://github.com/tsingson/go-ums , 在 readme 中列示并持续添加中
先有文档, 再写代码. 在文档简单说明一些我的设计套路
注: 这里的设计, Design, 不限于架构设计, 程序设计, 也包含个人在摄影/印刷/书籍/海报中找出问题/解决问题的一些"套路”.
先文档, 后实施, 也是曾经作 SA/SE 的习惯了吧. 当年曾戏称自己是 Simple Editor , 哈, 怀念那8年, 大胡子, yaho BB, black more, 北京香山, 上海文广,三角洲岛…….
稍后在持续更新中一一道来, 看看横向之间的有趣关联.
1. 业务场景
不谈业务需求, 与业务场景中的人儿们,再好的技术也难以落地变现.
这就是原始需求的收集/整理/清理/梳理, 是开发贯串始终的目标与仲裁准则:
- 是什么? 有什么价值?
- 是谁在用? 如何用( 业务操作流程)? 哪些是关键节点(不能省掉的业务行为), 重点与难点是什么?
- 在这些业务场景中, 边界在哪里( 哪些情况是正常, 哪些是异常, 如何判定, 什么时间什么情况下有哪些处理方式)
从一个简单而典型的业务场景开始, 来
1.1 业务场景/需求描述
这个开源项目是从 goim 在开源社区交流讨论后, 在 reading-go 讨论中偶然开始的
从业多年, 个人几乎不在网络上谈论个人在 iT 技术职业经历, 尤其是项目细节, 这里, 算是换个方式来公开谈谈吧
所以, 这里也从 goim 作为起点
很多朋友, 在阅读 goim 源码时, 都会感觉到, 就算是 im 业务完整实施, 需要与用户管理结合:
- 用户注册后, 提供一个用户唯一标识, , 对应 goim 中的 mid
- 用户认证, 让用户可以长连接到 goim , 在认证成功时
- 提供一个 token , 该 token 中包含用户可以进入的房间列表
- 提供用户发送 im 信息的入口地址( logic 部署多个情况下)
- 用户在 goim 中涉及会话的业务功能( 变更房间…..)
所以, 我们可以从上面描述到的, (不能省掉的) 基本业务功能开始
来, 来, 来 …GO!
1.1.1 场景与流程简述
( 省略)
稍后补充示意图
1.1.2 业务指标(性能要求)
(简化)
- 要求业务响应, 排除网络延迟, 业务响应要求在 100ms 以内
- 支持10K tps 高并发
- 年度业务中断时间低于5次, 业务中断时间低于30分钟
- 业务数据保留期限 1年
- 业务操作记录要求支持审计
1.2.3 部署要求
(简化)
- 服务器部署中国骨干网络IDC (电信网络为主, 广州/上海两地IDC ) 及美国骨干网络IDC ( 略)
- 以部署服务端优先支持 linux 与 docker
- 客户端支持 windows / mac / linux 的 terminal 命令行, 支持主流浏览器( 当前市场占80%的主流版本, 相当于 IE 10+)
1.2.4 开发/运维/运营要求
(省略)
1.2.5 成本要求
(省略)
1.2 feature list 需求列表
注, 以下按开发进度作了分组.
分组的意思, 就是排优先级, 排优先级即是排重要/紧急程度(按 SWOT Analysis 方式), 以决定先后处理顺序.
1.2.1 prototype 开发需求 v0.1.0
用户相关操作
- 用户注册 register 用户以 email + 密码 注册提交个人信息, 提交后验证 email 是否存在唯一冲突, 如果没有, 注册成功( 分配用户ID) 注意, 这里隐藏着一个检查用户是否存在的操作
- 用户登录与登出 login / logout 用户以 email + passwrd 或 用户名 + password 或 userID + password 可以进行登录, 登录成功, 给用户发送通行令牌 accessToken 注意, 这里隐藏着一个检查用户登录成功返回一个 access token 的操作
- 用户认证 authentication 针对具体业务, 用户访问具体业务时, 发送 access token 进行认证, 如果用户认证成功, 可以访问指定业务, 否则拒绝. 一般来说, 用户认证成功即表示创建一个合法的业务会话
- 用户通行令牌验证 verify 用户访问指定业务时, 验证 token 是否在有效期内, 以及相关授权是否有变更, 如不符合业务授权限制, 则拒绝提供服务
# 1.2.2 trial 试运行 v0.1.1
用户相关操作
- 用户激活 active 用户注册后, 向用户邮箱发送 email 验证的激活邮件, 用户从邮件中获取激活码后, 进行激活操作
- 用户登录与登出 login / logout 用户以 email + passwrd 或 用户名 + password 或 userID + password 可以进行登录, 登录成功, 给用户发送通行令牌 accessToken
- 用户锁定 suspend 禁止用户业务消费行为, 一般来说, 用户在具体业务上, 会有指定的服务周期, 超出服务周期, 用户被锁定
- 用户恢复 resume 恢复用户授权的业务行为
- 用户删除 deleted 用户删除自己或管理后台删除用户, 注意, 在商用中, 用户“删除”一般意味着用户状态修改为 deleted 并停止所有访问与所有业务, 但保留所有关联数据( 这些关联数据, 只有在一定期限后, 会手动或自动清除 purge )
2. 概要设计之模型设计
参照 1.2.1 小节, 很容易作一个简单设计
2.1 数据模型
用户对象名称 account
- 用户ID, 与 goim 配合, 就用 int64 吧, 一个全局唯一的正整数
- email , (简化), 都是字符串, email 格式也不验证, 其中 email 字符串长不得少于5个字符( >=5 ) ,
- password, (简化) 都是字符串, 密码不得少于6个字符( >= 6), 字符为 a..zA..Z0..9$%-
- access tokdn 通告令牌, 也用字符串代替吧, 字符串长度 >=32 字符
- 用户角色, 分别为 非会员, 会员
- 用户状态, 分别为注册未激活, 已激活, 禁用, 已删除
- 用户创建时间, 简单点, 用 int64 或 UTC timestamp
- 用户信息变更时间, 简单点, 用 int64 或 UTC timestamp
操作结果(状态)定义
- transaction ID 事务唯一标识
- 状态码, 整数, 操作成功返回 200 / 操作失败返回 500 / 请求接受并在处理中返回202
- 操作结果文本信息, 少于255字符, 操作成功, 文本信息, 要求标记业务操作名称, 如 register ), 操作失败, 返回失败原因( 文本信息 )
注: transaction ID 事务唯一标识, 支持异步操作, 以支持分布式或集群, 以及操作失败时, 进行操作重试( 这样可以让服务端处理上次操作失败的数据, 例如进行清理, 或继续使用, 以及在日志中进行检查/审计)
原型实现的约束与限制:
- 为了快速实现原型, 用户密码直接存储, 不加密
- 用户ID , 直接用用户创建的 utc 时间截( 纳秒 )
简化操作结果(状态定义), 这里先按 golang 实现习惯方式, 操作结果修改为 go 的 error 返回( 即 error = nil 或 != nil ) 注: 这个地方, 只是针对 go 的简化
2.2 用户操作与约束
(简化)
- 用户注册 register ——> 调用 exists 检查是否重复注册, 如果不是, 创建用户ID 并保存用户信信息, 返回用户数据( 不返回密码), 返回操作结果状态码
- 检查用户是否重复存在 exists, 返回操作结果
- 用户登录 login ——> 以 email + pwd 登录, 登录成功, 创建并返回 access token, 返回操作结果状态码
- 用户登出 logout —–> 清除 access token , 返回操作结果状态码
- 用户认证 auth ——-> 输入用户ID 或 token , 调用 verify 检查 会话中的token 是否合法, 如果 token 合法则返回成功, 返回操作结果状态码
- 用户令牌验证 verify ——–> 检查 token 是否存在, 并且是否相等, 返回操作结果状态码
2.3 业务流程设计 (仅以 register 用户注册为例)
2.3.1 register 用户注册
业务流程描述: 客户端 ———-> 服务端, 进行用户注册, 发送注册数据, 由服务器端返回注册成功(获取用户ID ) 或失败信息
—-> 输入:
- transaction ID 事务唯一标识
- email 合法邮箱地址
- password 密码
—-> 内部逻辑实现
- 检查 email 是否重复( 即被成功注册过), 判断没有重复, 则进行 2, 判断有重复, 进行下说明 3
- 生成用户 ID , 并保存到文件或数据库, 保存成功, 返回成功结果输出, 保存失败, 进行下说明 3
- 处理错误输出
- 以上处理, 每一步均以 事务 ID 与时间为主键存日志
—-> 输出
输出 (成功, 返回用户信息, 屏蔽密码字段)
- account ID 用户ID
- 用户角色
- 用户状态
- 创建时间
- 变更时间
输出 (失败, 返回事务标识, 错误状态码, 错误原因文本)
- transaction ID 事务唯一标识
- 返回操作结果状态码
- 操作结果文本描述
2.3.2 接口设计
大白话:
接口就是两个网元之间, 进行交互/通讯的网络协议/控制通信命令(信令)/以约定格式传输相关数据(数据封装)的简称
例如 web 浏览器与 web 服务器这两个网元之间, 一般采用 HTTP 协议, 信令是 GET / POST / PUT / PATCH / DELETE …., 数据封装通常是以 MIME 来指定, 比如下面用到的 “application/json; charset=utf-8”
设计实现以下接口
- RESTful 接口 ( 描述省略, 见代码)
- gRPC ( protobuf ) 接口 ( 略 )
- gRPC ( flatbuffers ) 接口( 对接 android java SDK , 详细说明略)
- websocket 接口 ( 略 )
- TCP 接口 ( 略 )
2.3.2.1 RESTful 接口设计
关于 RESTful 接口, 相关推荐规范, 请自行查询
这里先简要说明一下:
RESTful 通常是 HTTP + json 实现的接口, 其中 1. 类似 /api/v1/account 这样的 URI 就是一个接口, 其中 /api/v1 前缀只是辅助管理
在下面我们示例实现的用户注册接口, 可以定义为 /api/v1/register 或者 /xxx/yyyy 这样你喜欢的方式 2. HTTP 的 method 就是操作方式, 例如: - GET:读取(Read) - POST:新建(Create) - PUT:更新(Update) - PATCH:更新(Update),通常是部分更新 - DELETE:删除(Delete) 3. 数据编码(序列化/反序列化) 是 "application/json; charset=utf-8” 标的 JSON 格式 4. 少量操作关联数据, 可以在 HTTP header 中传递, 比如 transactionID/ token / cookie , 这些关联数据, 一般用来协助状态跟踪
2.3.2.1.1 用户注册
对照 2.3.1 , 用户注册的接口, 可以设计如下
———>input请求
请求资源
POST 请求以下网址 http://localhost:3001/api/v1/register
request header
Context-Type: "application/json; charset=utf-8"
TransactionID: "201001419845668864"
request body
{
"email": "test@email.com",
"password": "201001419845668864"
}
———>output返回
response header
Context-Type: "application/json; charset=utf-8"
TransactionID: "201001419845668864"
操作成功, 返回 http 状态码 200, 返回数据如下
response body
{
"email": "test@email.com",
"password": "201001419845668864"
}
操作失败, 返回状态码 500, 返回数据如下
**
{
"transactionID":"201001419845668864"
"code": 500,
"msg": "用户Email已经被其他用户使用, 请选择其他email重新注册"
}
其他部分, 稍后补充………
2.3.3 测试用例如下
- 检查 email 无重复, 操作成功
- 检查 email 有重复, 操作失败
- 异常, 主要关注两个, 保存失败或异常, 通讯异常中断 (详细说明, 省略)
3.概要设计之架构设计
先简化为以下架构作为验证原型开发
client ( 多个) <—-> gateWay( HA, 热备, 路由,分流) <—–> AAA (多个)<——-> UMS ( 双机热备)
其中:
- client 到 AAA 选择 RESTful
- AAA 到 UMS 之间, 选择 gRPC
稍后补充………
4. 详细设计
4.1 设计目标与原则
由于需求原因, 我们需要考虑达成以下目标:
- 低延迟, 高容量, 高并发
- 可扩展的多点部署, 云部署
所以, 先拍脑袋随意定义一下要实现目标, 如下所示. 接下来, 再评估哪些真正需要, 优先级, 以及具体实现方案
- 网元之间无状态
- 线程之间无锁
- 尽可能快的网元间接口通讯
- 尽可能减少接口之间数据序列化/反序列化的次数, 加快接口之间数据序列化/反序列化的效率
- 网元支持按需动态扩展或收缩部署, 简单点, 就是网元健康检查 + 路由 + 分流可控制
- 支持 QOS 控制, 先支持限流
4.2 设计实现 ( golang 为例)
稍后补充说明部分
4.2.1 模型的实现
用户模型与操作的 golang 实现
见 [https://github.com/tsingson/go-ums/blob/master/model/account.go]
(说明省略)
4.2.2 接口设计的实现
见 [https://github.com/tsingson/go-ums/blob/master/model/account.go]
(说明省略)
4.2.3 业务逻辑实现
见 https://github.com/tsingson/go-ums/blob/master/pkg/services/account.go
(说明省略)
4.2.4 UT ( Unit Test) 单元测试
测试用例如下
- 检查 email 无重复, 操作成功
- 检查 email 有重复, 操作失败
- 其他异常( 略)
见 /pkg/service 下测试代码
4.3 LIT ( local integration test) 网元集成测试
见 /pkg/web 下各测试代码
4.4 SIT ( system intergration test ) 系统集成测试
见 /cmd/cli 下代码
稍后补充……….
4.5 编译/部署/运维
稍后补充………….
5. 性能测试/部署测试/ trial 验证
省略 …
6. 附注/参考
6.1 接口设计相关参考
_
_
关于我
网名 tsingson (三明智, 江湖人称3爷)
原 ustarcom IPTV/OTT 事业部播控产品线技术架构湿/解决方案工程湿角色(8年), 自由职业者,
喜欢音乐(口琴,是第三/四/五届广东国际口琴嘉年华的主策划人之一), 摄影与越野,
喜欢 golang 语言 (商用项目中主要用 postgres + golang )
_