架构师训练营大作业 -- 通达快递架构设计
业务愿景
通达是某上市公司全资投资成立的一家物流快递公司,主要进行同城快递业务,公司刚刚成立,组建 20 人技术部门,准备两个月后系统开发完成上线。
市场上已经有一些从事同城快递业务的公司,为了能够占领市场,投资公司希望通达的服务在完成包裹投送的速度上比竞争对手有优势,能更快响应用户请求,更快交付到目的地。
投资公司要求新系统两个月上线,上线后三个月日单超过 1 万;一年日单超过 50 万。
系统上下文
用户通过互联网访问系统,进行下单、支付、查询进度等操作;
快递员通过互联网访问系统,进行抢单、收单、发单等操作;
系统需要连接到第三方提供的支付网关以完成用户的支付请求;
系统需要连接到 GPS 服务提供商以跟踪快递员当前位置
需求
这里我们标识出影响架构的有重要功能、约束以及质量属性。
功能用例
用户发送快递
用户请求系统发送快递,系统记录请求后,通知快递员有新的快递请求。
只通知距离快递包裹 5 公里的快递员
快递员抢单
快递员请求处理某个快递,系统接受请求后,将快递分配给该快递员。
收取快递
快递员在获得快递包裹后,通知系统,并提供包裹详细信息。
系统根据包裹详细信息计算费用,通知用户付费。
投送快递
快递员在投送包裹后,通知系统。
支付快递
用户选择为某个订单支付。
更新快递员位置
系统定期获取/更新快递员所在位置。
约束
两个月上线
按照这个要求,一期系统的大致开发计划为:
架构设计 1 周
开发 4 周
测试 2 周
部署/试运行 1 周
团队
20 个人投入开发
大概 15 个开发,3 个测试,2 个运维
内部没有技术大牛
尽量使用团队已掌握的成熟技术。
产品经理兼任技术负责人
大厂出身,对技术方案的要求比较严格。
他在评审重大技术决策时,还会比较重视外部技术顾问(其朋友)的意见。
在团队内引入 ADD(Attribute Driven Design),基于 ADD 展开架构设计和架构评估活动。
假定
在分析系统的质量属性前,我们有以下假定:
每个快递员每天工作 10 小时,每小时最多收/发 10 单;
用户快递请求集中在 4 个小时内;
因为用户一般总是期望包裹当天送达,而投递时间大概在 3-4 小时,所以用户一般会在 9 点到 14 点前下单
用户提交快递请求前,平均花费 90 秒填写快递表单
系统峰值流量按照系统平均流量的 10 倍计算
系统负载
峰值流量
根据业务需求,前期系统平均每天 1 万订单,根据假设,订单集中在 4 小时内,所以:
平均流量 = 10000 / 4 / 3600 = 0.7 单/秒
峰值流量 = 10 * 0.7 = 7 单/秒
并发用户数
根据 Little's Law,
并发用户 = 峰值流量 (ThinkingTime + ResponseTime) = 630 + 7 * ResponseTime
由于 ResponseTime 肯定小于 1 秒,我们暂时忽略,所以峰值并发用户数为 630 个(平均并发用户数为 63 个)。
快递员数量
根据每天 10,000 个订单,每个快递员每天完成 100 单计算,需要 100 个快递员。
一年后负载增长 50 倍
峰值流量 = 350 单/秒
并发用户数 = 31500 个(平均为 3150 个)
需要快递员 = 5000
质量属性
性能
用户快递请求响应时间
系统在负荷达到峰值时能够 0.3 秒内响应用户的快递请求
系统在负荷达到峰值的 2 倍时能在 0.5 秒内响应用户的快递请求
系统快速响应用户请求是重要的业务关注点,一般情况下我们在 300 毫秒内响应用户请求。300 毫秒是综合考虑用户体验以及竞争对手等因素得出的,超过 300 毫秒,用户会有延迟感知,低于 250 毫秒则技术上很难保证。
快递通知延迟
系统在负荷达到峰值时在接收到快递请求的 60 秒内通知快递员
系统在负荷达到峰值的 2 倍时在接收到快递请求的 180 秒内通知快递员
快递抢单请求响应时间
系统在负荷达到峰值时能够 0.3 秒内响应快递员的抢单请求
系统在负荷达到峰值的 2 倍时能在 0.5 秒内响应快递员的抢单请求
伸缩性
系统在并发快递请求超过峰值后通过增加资源保持请求响应时间
系统在快递员人数超过峰值后通过增加资源保持快递员抢单响应时间
可用性
高可用级别
系统提供 99.9%的可用性
每年停机时间不超过 9 小时
限流
系统在负荷超过 2 倍峰值时采取限流措施阻止更多请求发送到后端服务
故障恢复
系统在故障恢复后能够将之前受故障影响的快递请求通知重新发送到相关快递员
领域模型
我们根据主要的系统用例对涉及的领域进行建模。
实体对象
顾客(Customer)
快递员(Courier)
订单(Order)
记录顾客提交的快递请求。
快递(Delivery)
跟踪用户请求的投递过程。投递过程有多个状态,它们之间的变迁变化如下图:
付款(Payment)
记录用户快递请求产生费用的支付。
基础设施
通知代理(CourierBroker)
负责将新的快递请求转发到满足一定条件的快递人员。
支付通道(PaymentChannel)
值对象
地址分片(Zone)
地址分片用于支持标准化地址管理,用户填写/提交的地址会转化成系内部的 zone
和详细地址,这样便于系统实现地理距离判断、根据区域分配工作等功能。
计费方案(FeeScheme)
描述快递服务的计费策略,比如:如何按照包裹的重量/距离收费。
快递请求(Request)
根据用户订单生成的请求,用于通知、抢单等场景。
项目一期架构
在系统开始建设的半年内,主要的风险在于能够及时交付并上线。
前期并发请求峰值为 7/秒,所以性能不是本阶段架构的主要关注点。
组件
Delivery
负责快递订单管理、快递过程的跟踪,具体包括:
顾客创建订单、查询订单和支付订单
快递员收单、发单、查询
Tracer
负责:
定期更新快递员位置
发送快递请求通知
响应快递员的抢单请求
Tracer 服务通过 WebSocket 和快递员 App 保持长链接,通过该链接接收最新位置,位置信息保存在 Redis 中。
Tracer 还负责主动推送通知,发送前通过 Redis 查询(GEORADIUS)到一定距离内的快递员。
Tracer 通过 Redis 实现抢单,在某个快递人员成功抢单后,Tracer 通过消息通知 Delivery 服务,修改订单/快递过程状态和相关信息。
Payment Gateway
负责处理支付请求,通过第三方支付服务提供商完成用户和平台间的资金流动。
Message Queue
场景
下单
抢单
分析
易于修改
快递过程和快递订单状态是业务核心,领域逻辑较复杂,集中在 Delivery 服务中实现;
快递员位置、快递员抢单等业务比较简单,对性能有一定要求,Tracer 服务中实现;
同第三方支付服务的对接比较独立,但是也存在着幂等、可靠性高的要求,使用独立的 Payment Gateway 实现。
性能
根据之前的分析,顾客的请求峰值流量为:7 单/秒,快递员的请求流量为:100(快递员总数)/10/60 = 0.2 个/秒。1 个 Delivery 服务实例即可满足性能要求。
Tracer 的压力在于抢单,一期中有 100 个快递人员,假定快递人员每次需要 5 秒考虑是否抢特定订单,那么流量在 20 请求/秒。另外,为了支持通知和位置更新,Tracer 实例需要和前端建立 WebSocket 连接,每个实例理论上可支持上万(65536)并发链接,所以我们只要部署 1 个 Tracer 实例就可以满足服务 100 个快递员的需求。
工作分配
整个技术部分 20 个人,预计 15 个开发,其中大概 10 个后端开发,后端人员分工如下:
Delivery 5 个开发
Tracer:2 个开发
Payment:3 个开发
项目二期架构
组件
由于二期的负载压力增长了 50 倍,为了保持系统的高性能,更好地实现可伸缩,作了以下调整(变化的组件以绿色标记)。
读写分离
将订单的处理和历史订单的查询分离,使用专门的 Query 服务实现历史订单的查询。
历史订单保存在 MongoDB。
订单和投递分离
将订单和投递分离,主要是因为引起这两类请求数量变化的原因不同。订单请求数量取决于顾客发送包裹的意愿,受市场活动的影响;而投递请求数量取决于快递员数量和投递效率,在较长时间内基本不变化。
把订单和投递分离后,我们可以根据对顾客投递意愿的预期,变化订单服务集群的规模;而投递集群的规模基本保持稳定。
评论