商城如何设计商品详情页系统 
电商商品系统主要是对商品信息进行增删查改,设计系统的存储需要考虑两个方面问题
- 高并发,商品详情页一定是商品系统中 DAU(日均访问次数)最高的页面之一,特别是商品大促时,支撑商详页的商品系统必然是第一个被流量冲击的系统
- 商品数据规模,对于商详页(商品详情页),有数量多、重量大等特点
商品系统需要保存哪些数据 

右边灰色的部分,来自于电商的其他系统,我们暂且不去管这些,左边彩色部分,都是商品系统需要存储的内容。针对较大规模的系统,解决的思路是分而治之,我们可以把商品系统需要存储的数据按照特点,分成商品基本信息、商品参数、图片视频和商品介绍几个部分来分别存储 。
商品基本信息该如何存储? 
商品的基本信息,它包括商品的主副标题、价格、描述、状态等最基本、主要的属性。这些属性通常都是固定的,不太可能会因为需求或者不同的商品而变化,而且,这部分数据也不会太大。所以,还是建议你在数据库中建一张表来保存商品的基本信息。
然后,还需要在数据库前面,加一个缓存,帮助数据抵挡绝大部分的读请求。这个缓存,你可以使用Redis,也可以用Memcached,这两种存储系统都是基于内存的 KV 存储,都能解决问题。
下面简单看一下,如何来使用前置缓存来缓存商品数据。
- 处理商品信息的读请求时,先去缓存查找,如果找到就直接返回缓存中的数据。如果在缓存中没找到,再去查数据库,把从数据库中查到的商品信息返回给页面,顺便把数据在缓存里也放一份。
- 更新商品信息的时候,在更新数据库的同时,也要把缓存中的数据给删除掉。不然就有可能出现这种情况:数据库中的数据变了,而缓存中的数据没变,商详页上看到的还是旧数据。
这种缓存更新的策略,称为 Cache Aside,是最简单实用的一种缓存更新策略,适用范围也最广泛。如果你要缓存数据,没有什么特殊的情况,首先就应该考虑使用这个策略。
除了 Cache Aside 以外,还有 Read/Write Through、Write Behind 等几种策略,分别适用于不同的情况。
设计商品基本信息表的时候,有一点需要提醒你的是, 一定要记得保留商品数据的每一个历史版本。因为商品数据是随时变化的,但是订单中关联的商品数据,必须是下单那个时刻的商品数据,这一点很重要。你可以为每一个历史版本的商品数据保存一个快照,可以创建一个历史表保存到 MySQL 中,也可以保存到一些 KV 存储中。
使用 MongoDB 保存商品参数 
我们再来分析商品参数,参数就是商品的特征。比如说,电脑的内存大小、手机的屏幕尺寸、酒的度数、口红的色号等等。和商品的基本属性一样,都是结构化的数据。但麻烦的是,不同类型的商品,它的参数是完全不一样的。
如果我们设计一个商品参数表,那这个表的字段就会太多了,并且每增加一个品类的商品,这个表就要加字段,这个方案行不通。
MongoDB 是一个面向文档存储的 NoSQL 数据库,在 MongoDB 中,表、行、列对应的概念分别是:collection、document、field。MongoDB 最大的特点就是,它的 「表结构」是不需要事先定义的,其实,在 MongoDB 中根本没有表结构。由于没有表结构,它支持你把任意数据都放在同一张表里,你甚至可以在一张表里保存商品数据、订单数据、物流信息等这些结构完全不同的数据。并且,还能支持按照数据的某个字段进行查询。
使用对象存储保存图片和视频 
图片和视频由于占用存储空间比较大,一般的存储方式都是,在数据库中只保存图片视频的 ID 或者 URL,实际的图片视频以文件的方式单独存储。现在图片和视频存储技术已经非常成熟了,首选的方式就是保存在对象存储(Object Storage)中。各大云厂商都提供对象存储服务,比如国内的七牛云、AWS 的 S3 等等,也有开源的对象存储产品,比如 MinIO,可以私有化部署。虽然每个产品的 API 都不一样,但功能大同小异。
访问图片视频的时候,真正的图片和视频文件也不需要经过商品系统的后端服务,页面直接通过对象存储提供的 URL 来访问,又省事儿又节约带宽。而且,几乎所有的对象存储云服务都自带 CDN(Content Delivery Network)加速服务,响应时间比直接请求业务的服务器更短。
小结 
商品系统的存储需要提供商品的基本信息、商品参数、图片和视频以及商品介绍等等这些数据。商品的基本信息和商品参数分别保存在 MySQL 和 MongoDB 中,用 Redis 作为前置缓存,图片和视频存放在对象存储中。
实际场景 
商品调价 
如果说,用户下单这个时刻,正好赶上商品调价,就有可能出现这样的情况:我明明在商详页看到的价格是 10 块钱,下单后,怎么变成 15 块了?你的系统是不是偷偷在坑我?
这样给用户的体验非常不好。你不要以为这是一个小概率事件,当你的系统用户足够多的时候,每时每刻都有人在下单,这几乎是个必然出现的事件。
提到多的一个方案是:用户进入下单页面进行商品价格校验,然后进行提示或则重新下单。这种在于对用户不太友好
这里还有一个方案思路:
首先,商品系统需要保存包含价格的商品基本信息的历史数据,对每一次变更记录一个自增的版本号。在下单的请求中,不仅要带上 SKUID,还要带上版本号。订单服务以请求中的商品版本对应的价格来创建订单,就可以避免 「下单时突然变价」的问题了。
但是,这样改正之后会产生一个很严重的系统漏洞:黑客有可能会利用这个机制,以最便宜的历史价格来下单。所以,我们在下单之前需要增加一个检测逻辑:请求中的版本号只能是当前版本或者上一个版本,并且使用上一个版本要有一个时间限制,比如说调价 5 秒之后,就不再接受上一个版本的请求。这样就可以避免这个调价漏洞了。
简单说:利用历史商品信息创建订单 + 历史版本控制 + 版本之间的时间控制