目录
核心业务流程
服务端抢单原型
机构端抢单原型
小结
系统整体设计
整体分析
抢单查询(对应到抢券查询)
抢单(技术方案同领取优惠券)
抢单结果异步处理(技术方案同抢券结果异步处理)
抢单设置
订单分流
表设计
编辑 订单分流代码
抢单查询
抢单池同步
同步方案
索引结构设计
抢单池库存结构
小结
抢单查询
核心业务流程
首先明确本模块在核心业务流程的位置,下图是项目的核心业务流程:
用户下单后服务人员通过app进行抢单,机构通过pc进行抢单,抢单成功后服务人员开始现场服务。
服务人员在平台注册后需要进行实名认证、服务技能设置、服务范围设置、开启接单,这四项准备工作完成后方可在平台接单。
服务技能设置
服务技能即服务人员服务范围,比如:服务人员A提供日常保洁、日常维修服务。设置服务技能后服务人员可以抢与自己的技能匹配的订单,比如:服务人员A提供日常保洁、日常维修服务,所以它只能抢日常保洁、日常维修服务的订单。
服务范围设置
设置服务范围后能抢服务范围内的订单,下图中的服务城市是北京,接单范围是朱辛庄地铁站,在抢单查询时以接单范围为中心点查询方圆几公里的用户创建的订单,具体方圆公里数在查询条件中选择。
接单设置
开启接单后方可抢单。
开始抢单
进入抢单页面,选择服务距离和服务类型查询订单,点击“立即抢单”进行抢单。
服务距离:距离服务人员接单范围的距离。
服务类型:
抢单成功查询订单
服务人员抢单成功在我的订单中查询
机构端抢单与服务端抢单的区别是:
1、机构使用pc端,服务端使用app
2、机构端抢单成功后订单状态为待分配,表示待将订单分配给机构下的服务人员;服务端抢单成功后订单状态为待服务,因为服务人员抢到的订单服务人员就是自己。
3、抢单数量限制不同
抢单数量限制是当前拥有的进行中的服务单的最大数量。服务人员默认是10,机构默认是100。
抢单数量限制的目的是规避恶意抢单。
因为机构下的服务人员较多所以机构的签单数量限制大于服务人员。
下边说明机构端抢单的界面原型:
机构端也需要进行实名认证、服务技能与服务范围设置、开启接单后方可在平台接单。
服务单初始状态:待分配或待服务
机构抢单成功:待分配。
服务人员抢单成功:待服务。
开始服务: 待服务---》服务中
服务完成:
服务中---》服务完成
用户取消订单:
待分配---》已取消
待服务---》已取消
运营人员取消订单:
服务中---》已取消
服务完成---》已取消
小结
通过需求分析,抢单模块分如下子模块:
-
抢单设置子模块
设置服务技能、服务范围、开启抢单。
-
抢单查询子模块
查询抢单池中的订单
-
抢单子模块
点击“立即抢单”开始抢单。
抢单成功后生成服务单,服务单的状态包括:待分配、待服务、服务中、服务完成、已取消。
根据抢单需求,抢单的流程和抢券的流程很相似,核心内容也包括三部分:
抢单查询(对应到抢券查询)
在抢单界面查询抢单池中的订单,订单属于抢购的资源,在抢券中优惠券也属于争抢的资源,此处的查询存在高并发需要使用缓存技术进行优化。
查询抢单池中的订单可以根据下单用户的地理位置进行搜索订单,此功能类似“搜索附近”的业务,比如:搜索附近的酒店、搜索附近的房源信息等。(因为抢单本就是高并发,所以不能让其访问数据库,但是设计多条件查询,并且涉及地理位置搜索,所以es是最佳选择,虽然redis也有地理坐标查询,但是他不能多条件进行搜索,搜索出来还要手工进行筛选实现起来比较负责,所以我们选择了es)
结合上边的要求,本项目使用Elasticsearch查询抢单资源,使用ES的基于地理坐标搜索功能完成“搜索附近”功能的开发,Elasticsearch通过全文检索技术提高查询性能。 全文检索:提供模糊搜索等自动度很高的查询方式,并进行相关性排名,高亮等功能
如果要使用Elasticsearch查询抢单池信息就需要将待派单的订单同步到Elasticsearch,这里我们使用Canal+MQ的方式实现数据同步,如下图:
订单支付成功后进行订单分流(当前时刻举例服务时刻小于两个小时,把订单放入派单池(派单表),如果大于两小时放入抢单池(抢单表),因为使用了canal,记录了binlog日志,一旦 canal检测到表出现了变化,就会发送消息到mq,然后消费者消费到消息,把数据添加到es中,用于让用户搜索到,并加入到redis库存中,用于redis的抢单。抢单也是基于redis的lua脚本进行抢单,把抢单的信息记录进同步队列,然后用xxl-job启用多线程进行同步消息到数据库,然后把redis中的库存信息和es中该订单删除)
下单支付成功通过订单分流程序将订单信息写入抢单池,满足系统自动派单需求的订单同时写入派单池,派单部分在后边章节讲解。
通过Canal将抢单池的信息同步到Elasticsearch。
抢单查询从Elasticsearch中查询订单信息,使用ES的基于地理坐标搜索功能完成搜索附近的订单。
抢单(技术方案同领取优惠券)
我们在分析抢单技术方案时结合 抢券 的技术方案进行分析,多个人争抢同一个订单,这里仍然存在超卖问题,我们使用同抢券一样的技术方案去解决超卖问题。
下图中加入了抢单的交互流程:
订单支付成功后进行订单分流(当前时刻举例服务时刻小于两个小时,把订单放入派单池(派单表),如果大于两小时放入抢单池(抢单表),因为使用了canal,记录了binlog日志,一旦 canal检测到表出现了变化,就会发送消息到mq,然后消费者消费到消息,把数据添加到es中,用于让用户搜索到,并加入到redis库存中,用于redis的抢单。抢单也是基于redis的lua脚本进行抢单,把抢单的信息记录进同步队列,然后用xxl-job启用多线程进行同步消息到数据库,然后把redis中的库存信息和es中该订单删除,和同步队列中的信息删除)(这样也是为了减少tps的事务线,通过异步方案进行流量削峰)
说明:
参考抢券的方案,使用Redis+Lua的技术解决超卖问题,这里将抢单库存同步到Redis,每个订单的库存就是1。
抢单执行Lua脚本完成抢单,具体包括:扣减库存、抢单成功写入同步队列。
抢单同步队列的作用是通过异步任务将抢单结果信息同步到数据库。
抢单结果异步处理(技术方案同抢券结果异步处理)
(这样也是为了减少tps的事务线,通过异步方案进行流量削峰)
秒杀抢购业务的并发高,为了避免直接操作数据库这里使用异步任务的方式将抢单结果同步到数据库。
抢单成功创建服务单,异步任务的主要职责是根据抢单结果创建服务单,并且更新订单的状态。
抢单结果同步异步任务的具体的内容如下:
创建服务单:
服务单记录了服务人员进行家政服务的信息,关键字段有:订单ID、订单金额、服务人员ID、服务单状态、服务时间、服务照片等。
服务单初始状态:待分配或待服务
机构抢单成功:待分配。
服务人员抢单成功:待服务。
服务单的详细状态如下图:
更新订单的状态:
用户下单并支付完成后订单的状态为“派单中”,服务人员抢单成功订单状态为“待服务”,机构抢单成功订单状态为“待分配”。
订单状态图如下,详细说明请参见第四章内容。
抢单结果同步成功删除抢单池等相关信息:
抢单结果同步成功后删除抢单池中该订单的信息,这样服务人员在抢单界面无法查询到该订单。
删除数据库中抢单池的记录,将Elasticsearch中对应的抢单记录删除。
删除Redis中该订单的库存信息。
删除Redis中抢单同步队列中的记录。
根据需求,机构和服务人员都需要作抢单前的准备工作方可抢单。
准备工作包括:实名认证、服务技能设置、服务范围设置、开启接单。关于实名认证的业务流程请参考第二章,下边通过阅读代码理解服务技能设置、服务范围设置、开启接单的设置。
下边以服务端为例说明。
服务技能设置
服务人员新注册账号后进入下边的界面:
通过此界面进行服务技能设置、服务范围设置、接单设置。
点击上图中的“去设置”进入服务技能设置界面:
点击编辑开始设置服务技能:
选择服务技能,点击保存。
服务范围设置
进入服务范围设置界面:
服务技能表:存储服务提供者的技能信息。
服务提供者设置表:存储服务提供者的服务范围等信息。
服务人员与机构表:存储服务人员与机构的注册信息。
当服务技能、服务范围、接单设置全部设置完成会将settings_status 字段更新为1
表设计
根据数据流分析需要设计抢单池表(表里的字段是基于业务,业务需要什么字段,表里面就填什么字段)
经纬度的设置
这里前段穿来得及经纬度是个字符串,我们把他分开即可
首先当用户支付成功后进行订单分流
这里就是组装数据,然后插入到抢单池中,如果服务预约时间小于指定时间,则插入派单池让系统自动派单。
抢单池同步
同步方案
根据系统设计方案,抢单池的信息需要同步到Elasticsearch,使用Canal加RabbitMQ完成同步,如下:
索引结构设计
在ES中创建索引结构orders_seize,抢单池的信息将同步到orders_seize中。
启动ES和kibana
通过下边的命令创建orders_seize索引结构:
查看微服务es的文档
创建成功进行查询 索引列表,命令如下,找到orders_seize。
查询orders_seize的索引结构,命令如下,对照是否和上边创建的orders_seize的索引一致。
抢单池库存结构
抢单池库存信息存储在Redis,在抢单时通过Redis扣减库存。
设计如下:
缓存结构:Hash
RedisKey:ORDERS:RESOURCE:STOCK:{citycode%10}
HashKey:订单id
HashValue: 库存,为1
过期时间:永不过期
缓存一致性方案:通过Canal进行同步
{} 是为了设置路由到哪个节点,只要{}里面内容一致就一定会路由到同一个节点
concurrency是只有一个消费者前来消费消息
首先配置Canal
在Canal中配置order_seize为同步表。
这里配置了之间听某个队列中的某张表
上边的配置中为什么只配置了jzo2o-orders-0下的order_seize为同步表?jzo2o-orders-1和jzo2o-orders-2需要配置吗?
order_seize为广播表,jzo2o-orders-0、jzo2o-orders-1、jzo2o-orders-2三个数据库中的order_seize表数据是一致的,所以只配置其中一个数据库的order_seize为同步表即可。
mq中收到数据:
通过 "type": "INSERT" 及data可以获取到新增的数据
测试
登录kibana,执行:GET /orders_seize/_search,查询抢单数据是否同步到ES。
小结
抢单池是怎么设计的?
-
在数据库中创建抢单池表,存储待抢单的订单信息。
-
用户下单并支付成功将订单信息写入订单表和抢单池表。
-
通过Canal+MQ将抢单池的信息同步到Elasticsearch和Redis中。
同步到Elasticsearch是为了通过ES的地理坐标搜索功能查询订单信息。
同步到Redis是为了将抢单池库存信息同步到Redis,服务人员抢单时请求Redis完成。
-
服务人员抢单完成扣减Redis中抢单池的库存,并记录抢单结果。
-
最后通过异步任务将抢单结果同步到数据库。
定义controller
首先在Kibana中测试查询抢单语句
下边表示根据服务人员的服务范围、服务技能搜索抢单池:
服务人员可以选择服务类型和距离进行筛选
根据上边编写语句通过Java代码查询ES:
编写service方法如下:
首先进行校验
抢单流程
参考抢券的方案,使用Redis+Lua的技术解决超卖问题,这里将抢单库存同步到Redis,每个订单的库存就是1。
抢单执行Lua脚本完成抢单,具体包括:扣减库存、抢单成功写入同步队列。
抢单同步队列的作用是通过异步任务将抢单结果信息同步到数据库。
Redis数据结构设计
抢单同步队列
缓存结构:Hash
RedisKey:QUEUE:ORDERS:SEIZE:SYNC:{citycode%10}
HashKey:订单id
HashValue:多值拼接中间用逗号分隔,分别为:被派单服务人员id/机构id、服务人员类型(2,服务人员,3:机构端),是否是机器抢单(1:机器抢单,0:人工抢单)
过期时间:永不过期
抢单库存结构
缓存结构:Hash
RedisKey:ORDERS:RESOURCE:STOCK:{citycode%10}
HashKey:订单id
HashValue: 库存,为1
过期时间:永不过期
缓存一致性方案:通过Canal进行同步
这个开始时间的队列,在lua脚本中没有使用
向redis中存抢单用户的服务开始时间和当天的已接单数,但是好像文中抢单时,没有往redis中里面写已经抢了多少单,没有更新,而且第二天的时候也不会把这些删除。只是抢单的时候从redis中取了一下,存在bug
异步处理方案
抢单成功根据抢单结果进行异步处理:
交互流程如下:
创建服务单:
服务单记录了服务人员进行家政服务的信息,关键字段有:订单ID、订单金额、服务人员ID、服务单状态、服务时间、服务照片等。
服务单初始状态:待分配或待服务
机构抢单成功:待分配。
服务人员抢单成功:待服务。
更新订单的状态:
用户下单并支付完成后订单的状态为“派单中”,服务人员抢单成功订单状态为“待服务”,机构抢单成功订单状态为“待分配”。
抢单结果同步成功删除抢单池等相关信息:
抢单结果同步成功后删除抢单池中该订单的信息,这样服务人员在抢单界面无法查询到该订单。
删除数据库中抢单池的记录,通过Canal将Elasticsearch中对应的抢单记录删除。
删除Redis中该订单的库存信息。
删除Redis中抢单同步队列中的记录。
表设计
服务单表设计
分库分表策略:
分库策略:按服务人员id分库,表达式:jzo2o-orders-${serve_provider_id % 3}
分表策略:orders_serve_${(int)Math.floor(id % 10000000000 / 15000000)}
抢单成功同步xxl-job
ordersSeizeService.seizeOrdersSuccess()方法如下:
当抢单数据同步到数据库后,会在抢单池中删除该订单,canal然后发送到mq,mq监听到删除命令
根据类别判断是删除还是添加