设计模式之美 https://time.geekbang.org/column/intro/100039001?tab=catalog
钱包系统分为虚拟钱包和三方支付
功能清单
- 充值
- 提现
- 支付
- 查询余额
- 查询交易流水
需求:只有鉴权通过的请求可以调用接口api
- 调用方进行接口请求的时候,将 URL、AppID、密码、时间戳拼接在一起,通过加密算法生成 token,并且将 token、AppID、时间戳拼接在 URL 中,一并发送到微服务端。
- 微服务端在接收到调用方的接口请求之后,从请求中拆解出 token、AppID、时间戳。
- 微服务端首先检查传递过来的时间戳跟当前时间,是否在 token 失效时间窗口内。如果已经超过失效时间,那就算接口调用鉴权失败,拒绝接口调用请求。
- 如果 token 验证没有过期失效,微服务端再从自己的存储中,取出 AppID 对应的密码,通过同样的 token 生成算法,生成另外一个 token,与调用方传递过来的 token 进行匹配。如果一致,则鉴权成功,允许接口调用;否则就拒绝接口调用。
根据需求描述拆分功能点
- 把URL、AppID、密码、时间戳拼接形成字符串
- 对字符串通过加密算法加密生成token
- 将 token、AppID、时间戳拼接到 URL 中,形成新的 URL;
- 解析 URL,得到 token、AppID、时间戳等信息;
- 从存储中取出 AppID 和对应的密码;
- 根据时间戳判断 token 是否过期失效;
- 验证两个 token 是否匹配;
1、根据订单金额定义可兑换积分比例 100元的订单可以兑换10积分,兑换比例为10% 2、评论换取积分 评论后可获得1积分 3、每日签到兑换积分 签到后可获得1积分
1、积分换算为订单金额比例 10积分可兑换1元,兑换比例为10% 2、积分兑换优惠券 100积分可兑换10元的优惠券 3、积分换购 1w积分可换购100元的商品
查询用户的总积分,以及赚取积分和消费积分的历史记录。
1、订单系统 --》 营销系统 --》积分系统(仅包括积分的增删改查,兑换规则在营销系统中)
2、订单系统 --》 积分系统(仅包括积分的增删改查,兑换规则在订单等系统中)
3、订单系统 --》 积分系统(包括积分的增删改查,积分兑换规则)
按照 系统设计的高内聚 低耦合 以及上层系统可以依赖下层系统, 下层系统不可依赖上层系统的原则 ,选择方案1或2
同层系统异步调用(如:基于消息系统解耦),上下层系统同步调用,此原则也是为了系统解耦。 同层系统可能存在相互依赖,通过消息队列异步调用解耦。 而,上下层系统,一般不存在相互依赖,只有上层系统依赖下层系统,则可以同步调用。
积分系统可以单独形成一个项目,也可以是营销系统一个模块,根据目前架构实际情况而定。
- 兑换积分
- 消费积分
- 查询总可用积分
- 查询兑换的积分明细
- 查询消费的积分明细
积分明细表
- id
- userId
- 积分 正值表示兑换 负值表示消费
- 渠道id 如 订单、评论、每日签到、优惠券、积分商城等
- 事件id 如 订单id、评论id、优惠券id等
- create_time 积分赚钱或消费时间
- expired_time 积分过期时间
开发一个小的框架,能够获取接口调用的各种统计信息,比如,响应时间的最大值(max)、最小值(min)、平均值(avg)、 百分位值(percentile)、 接口调用次数(count)、频率(tps) 等, 并且支持将统计结果以各种显示格式(比如:JSON 格式、 网页格式、自定义显示格式等)输出到各种终端(Console 命令行、HTTP 网页、Email、日志文件、自定义输出终端等),以方便查看。
开发这样一个通用的框架,应用到各种业务系统中,支持实时计算、查看数据的统计信息,如何设计和实现呢?
-
接口统计信息:包括接口响应时间的统计信息,以及接口调用次数的统计信息等。
-
统计信息的类型:max、min、avg、percentile、count、tps 等。
-
统计信息显示格式:Json、Html、自定义显示格式。
-
统计信息显示终端:Console、Email、HTTP 网页、日志、自定义显示终端。
-
统计触发方式:包括主动和被动两种。主动表示以一定的频率定时统计数据,并主动推送到显示终端,比如邮件推送。被动表示用户触发统计,比如用户在网页中选择要统计的时间区间,触发统计,并将结果显示给用户。
-
统计时间区间:框架需要支持自定义统计时间区间,比如统计最近 10 分钟的某接口的 tps、访问次数,或者统计 12 月 11 日 00 点到 12 月 12 日 00 点之间某接口响应时间的最大值、最小值、平均值等。
-
统计时间间隔:对于主动触发统计,我们还要支持指定统计时间间隔,也就是多久触发一次统计显示。比如,每间隔 10s 统计一次接口信息并显示到命令行中,每间隔 24 小时发送一封统计信息邮件。
- 易用性
框架要容易集成到业务系统、易插拔、不与业务系统耦合、 接口是否灵活, 文档的好坏也会影响到框架的易用性
-
性能
-
扩展性
-
容错性
容错性这一点也非常重要。对于性能计数器框架来说, 不能因为框架本身的异常导致接口请求出错。所以, 我们要对框架可能存在的各种异常情况都考虑全面, 对外暴露的接口抛出的所有运行时、非运行时异常 都进行捕获处理。
- 通用性
先基于简单的场景,基于此设计实现一个简单的原型。 比如,统计登录、注册两个接口的响应时间最大值和平均值、 接口调用次数, 并且将结果以json格式输出到命令行中。
- 数据采集
- 数据存储
- 聚合统计
- 显示
小步快跑、逐步迭代
- 数据采集:负责打点采集原始数据,包括记录每次接口请求的响应时间和请求时间。
- 存储:负责将采集的原始数据保存下来,以便之后做聚合统计。数据的存储方式有很多种,我们暂时只支持 Redis 这一种存储方式,并且,采集与存储两个过程同步执行。
- 聚合统计:负责将原始数据聚合为统计数据,包括响应时间的最大值、最小值、平均值、99.9 百分位值、99 百分位值,以及接口请求的次数和 tps。
- 显示:负责将统计数据以某种格式显示到终端,暂时只支持主动推送给命令行和邮件。命令行间隔 n 秒统计显示上 m 秒的数据(比如,间隔 60s 统计上 60s 的数据)。 邮件每日统计上日的数据。
- MetricsCollector 类负责提供 API,来采集接口请求的原始数据。我们可以为 MetricsCollector 抽象出一个接口,但这并不是必须的,因为暂时我们只能想到一个 MetricsCollector 的实现方式。
- MetricsStorage 接口负责原始数据存储,RedisMetricsStorage 类实现 MetricsStorage 接口。这样做是为了今后灵活地扩展新的存储方法,比如用 HBase 来存储。
- Aggregator 类负责根据原始数据计算统计数据。
- ConsoleReporter 类、EmailReporter 类分别负责以一定频率统计并发送统计数据到命令行和邮件。至于 ConsoleReporter 和 EmailReporter 是否可以抽象出可复用的抽象类,或者抽象出一个公共的接口,我们暂时还不能确定。
统计和显示所要完成的功能逻辑
- 根据给定的时间区间,从数据库中拉取数据;
- 根据原始数据,计算得到统计数据;
- 将统计数据显示到终端(命令行或邮件);
- 定时触发以上 3 个过程的执行。
在v1版本的基础上,将性能计数器划分为几个部分,但是其中存在一些问题:
- Aggregator 类代码有点多,当需要新增或修改统计方式,会不断增加代码,影响代码可读性和可维护性
- ConsoleReporter 类、EmailReporter 类 存在代码重复问题:拉取一段时间的数据,调用Aggregator 类获取统计数据,这部分逻辑需要合并,不然违反了DRY原则
- ConsoleReporter 类、EmailReporter 类 既有统计逻辑也有显示逻辑,特别是EmailReporter 类,组装成xml比较复杂。因此需要将统计逻辑和显示逻辑分离,这样违反了SRP原则
- ConsoleReporter 类、EmailReporter 类的代码中设计线程操作,并且调用Aggregator 类的静态函数,代码可测试性不高
Aggregator 类和 ConsoleReporter、EmailReporter 类主要负责统计显示的工作。 如果我们把统计显示所要完成的功能逻辑细分一下,主要包含下面 4 点:
- 根据给定的时间区间,从数据库中拉取数据;
- 根据原始数据,计算得到统计数据;
- 将统计数据显示到终端(命令行或邮件);
- 定时触发以上三个过程的执行。
具体的重构工作:
- 根据给定时间区间,从数据库中拉取数据。这部分逻辑已经被封装在 MetricsStorage 类中了,所以这部分不需要处理。
- 根据原始数据,计算得到统计数据。我们可以将这部分逻辑移动到 Aggregator 类中。这样 Aggregator 类就不仅仅是只包含统计方法的工具类了。
- 将统计数据显示到终端。我们将这部分逻辑剥离出来,设计成两个类:ConsoleViewer 类和 EmailViewer 类,分别负责将统计结果显示到命令行和邮件中。
- EmailReporter类和ConsoleReporter仍然存在代码重复问题,且都用使用到多线程,因而代码可测试性不好
- 框架易用性不高,创建EmailReporter类和ConsoleReporter过程比较复杂