100 lines
9.5 KiB
Markdown
100 lines
9.5 KiB
Markdown
|
# 期中测试获奖用户名单及参考答案:通达系统架构设计
|
|||
|
|
|||
|
你好,我是李智慧。今天我们来公布一下期中测试的获奖用户名单和对应的答案。
|
|||
|
|
|||
|
我们期中测试的要求是写一个同城快送业务的系统架构设计文档,这个测试主要考察的目标包括:使用UML进行系统建模的能力,用文档表达设计思路的能力,完整思考一个系统整体架构的能力,以及识别设计落地关键技术问题及对策的能力。
|
|||
|
|
|||
|
在这里,感谢各位同学的积极参与,我收到了多位同学提交的答案,所有提交的答案都满足设计文档的格式要求,在设计的完整性上也基本达到了测试题目的要求,相信提交答案的同学也一定对如何从全局思考一个系统的架构设计,如何将设计思路表达出来有了感性而深刻的认识。
|
|||
|
|
|||
|
正如昵称为“外星人”(👽 )的同学在文档中所说:“一开始,其实认为自己在理论+思想上,已经基本算是及格的。但是,等到真的给自己一个业务,要去做设计的时候发现,并不是自己预想的那么简单。因为,这时候,你需要思考的并不是某一个单一的业务场景:秒杀业务,地理位置解决方案,全球化用户加速访问之类的。这时候,你面临的是一个更加真实的挑战,现在让你来做设计,你会怎么设计。”
|
|||
|
|
|||
|
我相信,这种对全局现实性问题的思考,会对你的思考问题方式和事物认知能力产生长远的影响,你会注意到以前不曾注意的东西,也会思考以前忽略了的一些问题。
|
|||
|
|
|||
|
好,话不多说,在此,我们挑选出了3位获赞数最多且最符合作业要求的 3 位同学,获奖名单如下:
|
|||
|
|
|||
|
![图片](https://static001.geekbang.org/resource/image/ae/58/ae0b56ee1c55f3ee48d0020bdd03cc58.jpg?wh=1875x625)
|
|||
|
|
|||
|
恭喜这 3 位同学!这里我也给出一个我写的答案,供你参考。
|
|||
|
|
|||
|
## 参考答案:通达系统架构设计
|
|||
|
|
|||
|
通达公司上级母公司是行业顶尖的物流配送企业,依托母公司的行业资源,我们将在全国多个主要城市开展一对一同城快送业务。本系统架构设计目标旨在快速利用公司现有资源,结合公司初期运营目标,开发一个能支持公司当下运营,也能支持公司未来发展的互联网应用系统。
|
|||
|
|
|||
|
### 需求分析
|
|||
|
|
|||
|
通达系统的核心使用者为需要在同城快速递送物品的用户和提供快送服务的快递员,系统用例图如下:
|
|||
|
|
|||
|
![图片](https://static001.geekbang.org/resource/image/fb/bf/fb59cc2d5401f0b0bab4fe060497f6bf.jpg?wh=1920x1114)
|
|||
|
|
|||
|
用户可以通过用户App创建快送订单,输入取件地址和收件地址,以及取件人、收件人联系方式后,系统预估订单金额,用户确认后,跳转到第三方支付页面等待用户支付。
|
|||
|
|
|||
|
快递员App实时上报自己当前位置。用户支付后,系统将订单发送给取件地点附近的所有快递员,快递员自主决定是否要抢单。系统会将用户订单信息推送给抢单成功的快递员APP,快递员根据收件地点上门取件并完成配送。
|
|||
|
|
|||
|
订单管理是系统的核心功能,因此系统需要设计开发专门的订单管理模块,具体的订单状态模型图如下:
|
|||
|
|
|||
|
![图片](https://static001.geekbang.org/resource/image/6d/64/6d55fab63ddd3c8f2f9b11c975b0eb64.jpg?wh=1920x836)
|
|||
|
|
|||
|
用户点击确认创建订单后,订单状态为“待支付”,用户完成支付后,订单状态为“已支付”。如果用户支付失败或者超时未支付,订单状态为“超时未支付”,超时未支付状态为订单最终状态之一,此状态订单不可以再修改操作。
|
|||
|
|
|||
|
“已支付”状态订单发送给附近快递员,如果有快递员抢单成功,订单状态为“待取件”。如果快递员上门联系不到发件人,快递员在APP拍照输入取件失败证据,订单状态改为“取件失败”。如果快递员上门取件成功,快递员在APP点击确认取件,订单状态改为“配送中”。
|
|||
|
|
|||
|
送达目的地后,如果联系不到收件人,快递员在APP拍照输入送件失败证据,订单状态改为“配送失败”,如果配送成功,订单状态改为“已配送”。
|
|||
|
|
|||
|
**非功能需求:**
|
|||
|
|
|||
|
系统设计需要在上线后支持日订单量50万订单,一年后支持日订单量300万订单。
|
|||
|
|
|||
|
### 系统架构
|
|||
|
|
|||
|
系统采用目前主流的微服务架构体系,整体架构图如下:
|
|||
|
|
|||
|
![图片](https://static001.geekbang.org/resource/image/21/c1/21a781c30cfc43aac920aaf93a102dc1.jpg?wh=1920x2070)
|
|||
|
|
|||
|
用户和快递员的请求通过负载均衡服务器进入网关服务器集群,网关服务器集群调用具体微服务完成请求处理。
|
|||
|
|
|||
|
用户微服务负责用户注册,个人资料修改,账户充值等业务逻辑,同时,用户所有操作都通过用户微服务集中处理,由用户微服务调用其他服务完成业务逻辑,包括下单、支付等。
|
|||
|
|
|||
|
快递员微服务负责为快递员提供服务,包括快递员注册、认证等,也包括快递员抢单、确认收件成功及失败、确认配送成功及失败等。
|
|||
|
|
|||
|
订单微服务负责订单生命周期管理,订单创建以及所有订单状态变更全部调用订单微服务来完成。所以用户微服务和快递员微服务需要依赖订单微服务。
|
|||
|
|
|||
|
配单微服务负责将用户订单推送给附近的所有快递员,即为订单匹配快递员。一方面,配单微服务需要记录所有快递员的当前位置,快递员位置信息通过快递员APP每3秒定时上传到网关服务器,网关服务器直接调用配单微服务,配单微服务将当前快递员位置信息更新到Redis中。
|
|||
|
|
|||
|
另一方面,配单微服务需要得到用户最新成功支付的订单信息。用户微服务在用户成功支付后,将订单信息发送给消息队列服务器,配单服务器作为消息消费者获取订单信息。配单服务器匹配到订单取件位置5公里范围内所有快递员,通过消息推送服务器将订单信息推送给快递员,等待快递员抢单。
|
|||
|
|
|||
|
系统使用关系数据库记录用户、快递员信息,以及订单信息,关系数据库采用主从复制同步的方式进行双机部署,订单写操作通过主数据库完成,订单读操作通过从数据库完成。
|
|||
|
|
|||
|
下单抢单逻辑是系统的核心逻辑,具体处理活动图如下:
|
|||
|
|
|||
|
![图片](https://static001.geekbang.org/resource/image/b9/ef/b91236af88658676383988c55f6f18ef.jpg?wh=1920x1075)
|
|||
|
|
|||
|
订单处理涉及到的角色泳道包括用户、用户微服务、订单微服务、配单微服务、快递员微服务、快递员。
|
|||
|
|
|||
|
用户调用用户微服务请求创建订单,用户微服务调用订单微服务完成创建订单。订单微服务返回订单创建成功后,用户微服务发起支付,用户APP端跳转到第三方支付,用户微服务等待第三方支付回调。
|
|||
|
|
|||
|
收到第三方支付回调结果后,如果支付成功,调用订单微服务修改订单状态为“已支付”。同时将订单信息通过消息队列异步发送给配单微服务。配单微服务实时接受快递员上报的位置信息,根据订单信息,匹配附近的快递员。
|
|||
|
|
|||
|
匹配到符合条件的快递员后,发送订单信息给快递员。快递员收到推送的订单信息后,决定是否要抢单。快递员点下抢单按钮后,请求发送给快递员微服务,快递员微服务可能会同时收到多个快递员的抢单请求,所以抢单请求需要加锁顺序操作,保证第一个到达系统的抢单请求能成功抢单。
|
|||
|
|
|||
|
由于快递员微服务是集群部署,所以需要使用外部锁来保证请求的顺序性,系统使用Redis完成锁操作。抢单成功的请求调用订单微服务,更新订单状态为“已配单”,并发送收件地址给快递员,快递员根据地址上门取件。
|
|||
|
|
|||
|
### 主要技术选型
|
|||
|
|
|||
|
1. 开发语言采用Java开发实现,APP端支持安卓和IOS两种系统。
|
|||
|
2. 负载均衡服务器采用Nginx部署。
|
|||
|
3. 网关采用SpringBoot开发,系统上线时,订单量较小,网关服务器采用双机部署,未来根据系统负载压力和监控指标进行扩容。
|
|||
|
4. 微服务框架采用Dubbo,每个微服务至少部署3个实例,并根据监控指标动态扩容。
|
|||
|
5. 消息队列采用ActiveMQ部署。
|
|||
|
6. 数据库采用MySQL并配置主从复制,实现读写分离。
|
|||
|
7. 缓存使用Redis,Redis记录快递员实时位置信息并实现抢单加锁操作。
|
|||
|
|
|||
|
### 关键技术落地实现
|
|||
|
|
|||
|
抢单的锁实现通过调用Redis的CAS命令来实现,具体过程为:
|
|||
|
|
|||
|
1. 订单支付完成后,配单微服务收到订单消息,立即在Redis中创建一个<订单ID,-1>的键值对;
|
|||
|
2. 快递员抢单的时候,调用Redis的CAS命令:CAS <订单ID> <-1> <快递员ID>,该命令比较订单ID的value是否为-1,如果是-1,就将value设置为当前快递员ID;
|
|||
|
3. 只有第一个快递员调用的时候value为-1,才能设置成功,也就是能抢单成功,并且记录下来抢单成功的快递员ID。其他抢单请求都返回CAS失败。
|
|||
|
|
|||
|
由于快递员位置在不断变更,并且高并发地发送给系统。而Redis中GeoHash命令通过跳表来存储位置,这种不断更新位置的高并发请求将会对Redis造成巨大的计算压力。因此系统并不采用Redis的GeoHash命令来进行距离计算,而是采用专栏[第9讲](https://time.geekbang.org/column/article/492306)交友软件同样的设计方案,即采用Hash表加GeoHash编码的方式来实现,Hash表存储在Redis中。
|
|||
|
|