课程安排
- 物流信息的需求分析
- 技术实现分析
- 基于MongoDB的功能实现
- 多级缓存的解决方案
- Redis缓存存在的问题分析并解决
1、背景说明
让收发件人清晰的了解到包裹的“实时”状态,就是“快递到哪了”
在电商大促期间,快件数量非常庞大,也就意味着查询人的量也是很大的
使用缓存技术解决并形成通用的高并发查询的解决方案
2、需求分析
用户寄件后,是需要查看运单的运输详情,也就是需要查看整个转运节点,类似这样:

产品的需求描述如下(在快递员端的产品文档中):

可以看出,物流信息中有状态、时间、具体信息、快递员姓名、快递员联系方式等信息。

在快递员取件时使用mq发消息进行保存
3、实现分析
3.1、MySQL实现
如果采用MySQL的存储,一般是这样存储的,首先设计表结构:
CREATE TABLE `sl_transport_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`transport_order_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '运单号',
`status` varchar(10) DEFAULT NULL COMMENT '状态,例如:运输中',
`info` varchar(500) DEFAULT NULL COMMENT '详细信息,例如:您的快件已到达【北京通州分拣中心】',
`created` datetime DEFAULT NULL COMMENT '创建时间',
`updated` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
插入测试数据:
INSERT INTO `sl_transport_info`(`id`, `transport_order_id`, `status`, `info`, `created`, `updated`) VALUES (1, 'SL920733749248', '已取件', '神领快递员已取件, 取件人【快递员,电话 18810966207}】', '2022-09-25 10:48:30', '2022-09-25 10:48:33');
INSERT INTO `sl_transport_info`(`id`, `transport_order_id`, `status`, `info`, `created`, `updated`) VALUES (2, 'SL920733749262', '已取件', '神领快递员已取件, 取件人【快递员,电话 18810966207}】', '2022-09-25 10:51:11', '2022-09-25 10:51:14');
INSERT INTO `sl_transport_info`(`id`, `transport_order_id`, `status`, `info`, `created`, `updated`) VALUES (3, 'SL920733749248', '运输中', '您的快件已到达【昌平区转运中心】', '2022-09-25 11:14:33', '2022-09-25 11:14:36');
INSERT INTO `sl_transport_info`(`id`, `transport_order_id`, `status`, `info`, `created`, `updated`) VALUES (4, 'SL920733749248', '运输中', '您的快件已到达【北京市转运中心】', '2022-09-25 11:14:54', '2022-09-25 11:14:57');
INSERT INTO `sl_transport_info`(`id`, `transport_order_id`, `status`, `info`, `created`, `updated`) VALUES (5, 'SL920733749262', '运输中', '您的快件已到达【昌平区转运中心】', '2022-09-25 11:15:17', '2022-09-25 11:15:19');
INSERT INTO `sl_transport_info`(`id`, `transport_order_id`, `status`, `info`, `created`, `updated`) VALUES (6, 'SL920733749262', '运输中', '您的快件已到达【江苏省南京市玄武区长江路】', '2022-09-25 11:15:44', '2022-09-25 11:15:47');
INSERT INTO `sl_transport_info`(`id`, `transport_order_id`, `status`, `info`, `created`, `updated`) VALUES (7, 'SL920733749248', '已签收', '您的快递已签收,如有疑问请联系快递员【快递员},电话18810966207】,感谢您使用神领快递,期待再次为您服务', '2022-09-25 11:16:16', '2022-09-25 11:16:19');

查询运单号【SL920733749248】的物流信息:
SELECT
*
FROM
sl_transport_info
WHERE
transport_order_id = 'SL920733749248'
ORDER BY
created ASC
结果:

3.2、优化(MongoDB实现)
分析
物流信息功能的特点:
- 数据量大
- 查询频率高(签收后查询频率低)
显然,在存储大数据方面非关系型数据库更合适一些。并且物流信息相对用户信息,物品信息不是那么重要,不需要mysql的ACID功能,故使用MongoDB数据库。
MySQL存储在一张表中,每条物流信息就是一条行数据,数据条数将是运单数量的数倍,查询时需要通过运单id作为条件,按照时间正序排序得到所有的结果。
其次MongoDB数据库可以使用嵌套文档存储数据,一条物流信息只需存储一条数据即可。查询时指需要根据运单id进行一次查询。相较于mysql的多条数据存储物流信息,有较大优势
基于MongoDB的实现,可以充分利用MongoDB数据结构的特点,可以这样存储:
{
"_id": ObjectId("62c6c679a1222549d64ba01e"),
"transportOrderId": "SL1000000000585",
"infoList": [
{
"created": NumberLong("1657192271195"),
"info": "神领快递员已取件, 取件人【快递员,电话 18810966207}】",
"status": "已取件"
},
{
"created": NumberLong("1657192328518"),
"info": "神领快递员已取件, 取件人【快递员,电话 18810966207}】",
"status": "已取件"
}
],
"created": NumberLong("1657194104987"),
"updated": NumberLong("1657194105064"),
"_class": "com.sl.transport.info.entity.TransportInfoEntity"
}
如果有新的信息加入的话,只需要向【infoList】中插入元素即可,查询的话按照【transportOrderId】条件查询。
db.sl_transport_info.find({"transportOrderId":"SL1000000000585"})

4、功能实现
4.1、Service实现
在TransportInfoService中定义了2个方法,一个是新增或更新数据,另一个是根据运单号查询物流信息。
4.2.1、saveOrUpdate
逻辑
根据运单id和infoDetail查询数据库(infoDetail是infoList中的一条信息)
1根据运单id查询TransportInfoEntity
2TransportInfoEntity为空,新建对象,并设置属性,设置更新时间,保存数据库
3非空,将infoDetail加入infoList中。设置更新时间,保存数据库
代码
mongoTemplate.findOne(Query.query(Criteria.where("transportOrderId").is(transportOrderId)),TransportInfoEntity.class);
查询,此处传入TransportInfoEntity.class,即可识别该类对应表,因为在类中有@Document注释

@Override
public TransportInfoEntity saveOrUpdate(String transportOrderId, TransportInfoDetail infoDetail) {
TransportInfoEntity transportInfoEntity = mongoTemplate.findOne(Query.query(Criteria.where("transportOrderId").is(transportOrderId)),TransportInfoEntity.class);
if(ObjectUtil.isEmpty(transportInfoEntity))
{
TransportInfoEntity entity = new TransportInfoEntity();
entity.setTransportOrderId(transportOrderId);
entity.setInfoList(ListUtil.toList(infoDetail));
entity.setCreated(System.currentTimeMillis());
entity.setUpdated(System.currentTimeMillis());
return mongoTemplate.save(entity);
}
else{
transportInfoEntity.getInfoList().add(infoDetail);
transportInfoEntity.setUpdated(System.currentTimeMillis());
return mongoTemplate.save(transportInfoEntity);
}
}
注意: MongoDB也有自动填写id的功能,不设置也可以
entity.setId(new ObjectId());
4.2.2、查询
根据运单号查询物流信息。
@Override
public TransportInfoEntity queryByTransportOrderId(String transportOrderId) {
//根据运单id查询
Query query = Query.query(Criteria.where("transportOrderId").is(transportOrderId)); //构造查询条件
TransportInfoEntity transportInfoEntity = this.mongoTemplate.findOne(query, TransportInfoEntity.class);
if (ObjectUtil.isNotEmpty(transportInfoEntity)) {
return transportInfoEntity;
}
throw new SLException(ExceptionEnum.NOT_FOUND);
}
4.2、记录物流信息
4.2.1、分析
如何时记录物流信息?
一种是微服务直接调用,另一种是通过消息的方式调用,也就是同步和异步的方式。
选择通过消息的方式,主要原因有两个:
- 物流信息数据的更新的实时性并不高,例如,运单到达某个转运中心,晚几分种记录信息也是可以的。
- 更新数据时,并发量比较大,例如,一辆车装了几千或几万个包裹,到达某个转运中心后,司机入库时,需要一下记录几千或几万个运单的物流数据,在这一时刻并发量是比较大的,通过消息(异步)的方式,可以进行对流量削峰,从而保障系统的稳定性。
4.2.2、消息结构
消息的结构如下:
{
"info": "您的快件已到达【$organId】",
"status": "运输中",
"organId": 1012479939628238305,
"transportOrderId": "SL920733749248",
"created": 1653133234913
}
通过$organId占位符表示机构,也就是,需要通过传入的organId查询机构名称替换到info中,当然了,如果没有机构,无需替换。
练习1
难度系数:★★★★☆
描述:在work微服务中完成发送【物流信息】的消息的逻辑,这样的话,work微服务就和transport-info微服务联系起来了。
提示,一共有4处代码需要完善:
- com.sl.ms.work.mq.CourierMQListener#list

最低0.47元/天 解锁文章
575

被折叠的 条评论
为什么被折叠?



