大厂面试,如何介绍项目中的商品查询交互业务架构

321 阅读12分钟

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

我们要跟面试官讲解的是对应的一个商品详情的一个查询交互的一个措施以及手段。

为什么我们要如此复杂的去讲清楚我们的一个领域模型呢?本质上来说就是和商品详情的一个查询能力有关。

我们来看一下作为对应的一个商品详情的一个查询交互,我们一般是怎么做的?

假设我们省略了我们系统架构当中的对应的一个流量入口,我们直接从 Zuul 层到我们对应的一个商品的核心服务当中,我们的 Zuul 将我们对应的一个 API 对应的一个商品获取商品详情页请求对应的接口,发给我们的一个商品服务,之后我们对应的商品服务要做哪些信息呢?

这个时候我们就可以知道的是,我们要应对商品详情,应对类似于像秒杀等等这样的一个内容,以及高 QPS 的内容之外,我们需要简单的分解一下我们对应的一个商品详情的一个模型。

商品详情模型

我们首先来看一下,我们对应的这个商品详情的信息的模型,哪些是没有必要一次性加载出来的。

非常显然,当用户点开我们对应的一个商品详情的内容的时候,整个的一个详情的描述其实是在第二个 temp 上面,也就是我们没有必要在第一屏的时候就加载我们对应的一个详情的内容。

于是乎我们对应的一个查询商品详情的内容落到我们商品服务当中,其实就至少可以分出两个接口,一个是查询商品主体内容,一个是查询商品详情内容,也就是获取商品详情信息。

那现在我们其实就可以看到,由于获取商品详情信息在商品服务当中是处于一个异步加载的,因此它对应的这个详细信息我们一般可以放在缓存内,直接通过商品 id 就可以获取到。

然后我们看一下商品主体内容的查询是一个怎么样的维度。这个时候我们就需要来看一下,我们一般商品服务会有一个 MySQL 的数据库,那我们肯定不可能将所有的一个信息通过数据库去扛上 10 万的 QPS。

那于是我们对应了一个商品服务,就必须得要将一些数据的内容给它存到 Redis 缓存当中,并且从 Redis 缓存去取。

那这个时候我们就可以看到我们有几种对应的一个策略,我们首先是先读缓存,如果说缓存当中有对应的数据直接返回主体内容,没有的话就去读 MySQL 数据库。那这个是一个对应的一个比较通用型的一个设计。

那这个时候我们其实可以看到,我们可以跟面试官提,我们有两层的优化手段。

首先第一,关于这个 Redis,到底什么样的一个模型内容需要进 Redis,那我们来看一下,首先对应的这个详情内容不是在第一屏对应的内容上面去加载的。

然后对应的这样的一个运费模型的话,其实对应的一个运费计算,我们完全没有必要再对应的一个详情页告知用户它的运费是多少。如果说是包邮,我们是可以将它固化到对应的一个 Redis 当中。

然后我们先不看销量,我们先看对应的一个价格和对应的一个品牌。首先品牌和类目,这就要看我们的产品对交互的一个需求。

如果说在商品详情页内是需要展示品牌和对应的一个类目信息的,那我们就把它做到商品的主体内。

然后我们来看一下价格计算,价格计算往往是跟着时间差,游走时间差对应的一个内容的,也就是不同的时间看到的一个商品的价格信息是不一样的。

那我们可以有几种对应的一个优化方案,包括价格、库存、销量,其实都是需要有一些更新的措施的,什么意思呢?

我们首先来看一下库存和对应那个销量其实是会不断发生变化的,最典型的是当用户交易掉一个对应的商品的话,我们的库存会减1,而产生的销量就会加1。

因此我们首先将对应的基础信息、运费、品牌、类目当作第一次商品的主查询,这个主信息查询是近乎不会变的。

因此从 MySQL 数据库内查询出来对应的第一次的商品主信息之后,我们就都把它跌入到 Redis 内。

由于对应的这样的一个信息在商品服务内近乎是一个不会变的存在,因此它对应的一个缓存的失效时间我们可以放置的比较长,比如我们放置一个小时、两个小时,甚至于七天都没有任何关系。

库存和销量模型

然后我们来看一下我们对应的库存跟销量以及对应的一个价格模型。

我们首先来看库存和销量,这两个东西是跟着对应的一个商品的交易信息走的,对应的这样的一个内容,我们把它放在另一个 Redis 内,当然这两个 Redis 可以是同一个 Redis,我们只不过是设计了两个 key 而已。我们放的是对应的一个商品交易信息,这个交易信息其实就是跟着库存和销量这样的一个带着交易属性的一个信息,放在对应的这个 Redis 内。

这个时候我们就需要面临一个缓存更新和对应的数据一致性的问题。我们假定我们对应的 MySQL 数据库是最新的数据,任何商品主信息的一个更新都体现在 MySQL 数据库内,而包括对应的一个商品交易的一个信息的更新、库存和销量也会体现在 MySQL 内。

我们为什么要拆分这样的两个 Redis?就是因为这两个redis的一个刷新策略是完全不同的。

我们试想一下,我们会有一个商家后台对应的这样的一个服务的能力,这个商家后台可以去变更我对应的一个商品名、对应的一个类目、对应的一个品牌,以及对应的一个是否包邮的一个设置。

因此当我们的商家后台向我们的 MySQL 内操作了更新这些商品的主信息之后,它需要通过异步消息这样的一个方式去将我们的对应的一个缓存异步的更新掉,也就是异步更新缓存。这个更新缓存的机制可以用 InvestData 的方式直接将这个缓存置为空,等待下一次服务进来之后再去读一次对应该数据库。

当然更好的方式,为了应对高流量,一般来讲更新完对应的一个内容之后,我们可以立刻做对应的一个缓存的一个填充、刷新了操作。

这是因为我们对应的商家后台做这样的一个内容的更新往往不是特别的频繁,因此可以在触发的时候就刷新掉对应的缓存的内容,并不会影响整个缓存的一个可用性。用户下一次对应的访问的时候,仍然可以读取对应的 Redis。

但是像对应的一个库存和销量就不一样了,假设我们有一个交易系统,交易系统在操作完对应的一个下单支付的流程之后,是需要去更新我们的一个商品的库存和销量的能力的,这个时候如果我们采取每下一单就异步的更新缓存的一个内容,试想在秒杀阶段,同一秒我们会有大量的一个库存的减和销量的加,这个缓存的利用率就会变得非常的差。

因为我们的商品服务是必定需要有对应的一个商品的主信息和商品的交易信息才能够触发查询的,这个时候我们就需要有一个取舍的选择。

一般来说我们会引入一个队列系统,这个队列的作用是什么呢?这个队列的作用是一个局促的作用,也就是每发生对应的一笔交易之后,发生交易之后就推送一个对应的一个库存或销量更新的一个消息。

库存或销量更新的这个消息过来之后,我们的队列其实做的事情非常的简单,并不是一收到对应的这个更新消息就立马去更新 Redis ,而是会有一个剧促的作用,比如我们设置缓存的更新频率是 5 秒钟或者是更长的 10 秒钟的时间。

我们把 10 秒钟之内收到的所有这条库存、销量和更新的消息全部打包到第十秒的时候,去做对应的一个 Redis 的更新。

这个时候我们就可以看到,我们在保证用户体验的情况下,让用户在 5 秒钟或者 10 秒钟的时间内,可以看到最新的销量和最新的库存,而不是每次都去同步对应的一个最新的库存,因为这的确是没有必要的。

价格模型

在介绍完对应的这个主信息和交易信息之后,我们最后再来看一下这个价格模型。

这个价格模型往往是整个电商体系内最最复杂的一套体系。

首先我们对应的价格是否要非常精确地展示,这个其实也并不是一定的,试想一下我们对应的一个价格,如果像放在对应的主信息以内,一旦到点了去做更新,有稍微的一个精确性的一个统计,以及我们每隔 10 秒钟去刷新一下对应的缓存,有对应的一个内容是完全都是两个概念,但是原理上它都是一致的。

这个就要看我们对应的一个业务的一个形态。如果说我们对这个价格的一个价格模型、对应的业务型形态要求实时性非常的高,那我们可以采取对应这样的一个数据的能力,将我们的对应的一个商品的价格、服务放在对应的 MySQL 中间之后,任何商家后台修改价格或者活动时间到了变价对应的一个触发,我们都是需要实时的同步到 Redis 内,以保证对应的一个商品服务到 Redis 内获取的对应的一个数据是可靠的。

还有一种策略我们也可以做得非常简单,试想一下,我们因为有对应的一个价格系统,我们可以提前把所有的价格设置在这个 Redis 内。

举个例子,我们有一个价格的 Redis,我们对应的一个商品服务会到这个价格的 Redis 内去拿所有的一个信息。

那什么意思呢?一般来说我们都有几种价格,第一种价格是一个平销的普通价格,这个评销的普通价格和我们商家后台配置到 MySQL 数据库当中的一模一样。

第二种会有一个活动价格,那这个活动价格相对比较复杂。我们假设双十一活动是对应的在 11 月 11 号的凌晨 0 点开始,那自然到这个时间点之后,我们对应的在页面上展示的界面这个价格信息就应该是对应的一个活动价格,而并不是普通的一个平销价格。

但是无论是这两个价格的哪一个都是提前配置好,并不是会临时性的发生变更的。因此我们可以将对应的所有的价格的一个变更的信息也同步到 Redis 内,并且每次在商品服务内将对应的价格信息查询出来,也就是我们的商品服务可以同时拿到评销价格和活动价格,然后在内存里做一次计算的判断,也就是我们判断当前时间是否进入了活动期、活动是否开始,来决定使用哪个价格返回给我们的一个前端用户。

我们的价格体系其实也是依赖于缓存,可以达到很高的一个 QPS 的流量,但是我们经过了一次内存运算之后,靠服务器的一个时钟点的一致性,来保证我们的前端的用户是可以拿到最应该展示的一个价格的内容信息。