背景
在电商领域,购物车(Shopping Cart)扮演着至关重要的角色,它是连接用户浏览商品与最终完成购买行为的桥梁。
从两个视角来阐述,作为ToC的购物车,存在的意义:
从用户角度:
- 收集与整理商品:
便于管理和查看,在多个商品间进行比较,而不必立即做出购买决策。 - 便于批量操作:
提供购物车内的商品排序功能,如按价格、销量等排序,帮助用户快速做出决策。 - 计算总价与优惠:
购物车会自动计算所有选中商品的总价,让用户一目了然地了解购物总成本。
从平台角度:
- 促进消费:
当用户在购物车中看到心仪的商品时,更容易产生冲动消费的行为。 - 数据收集与分析:
通过分析用户加入购物车但最终未购买的商品,平台可以优化商品推荐算法和促销策略。
购物车数据还能帮助电商平台了解用户的购物习惯和偏好,为个性化营销提供依据。
问题
业务角度:未登录和登陆时的购物车设计。
技术角度:ToC的流量较大,如何保证购物车系统的可用性?
针对未登录的购物车,数据是暂存的,登录之后,可以将暂存的购物车数据合并到登录的用户的购物车中,同时需要保证该用户的购物车数据持久化。
那这里就有两个问题,暂存的数据该如何展示?登录之后的购物车用什么存储?
方案
我们这里以京东零售购物车为例。
首先分析一下购物车的数据结构:
- 显性
店铺名称、商品名称、SKU名称、销售价、数量、总价、标签、勾选状态、是否有货 - 隐性
店铺ID、商品ID、SKU ID、标签ID、创建时间、变更时间
未登录的购物车
未登录的购物车存在哪里?客户端还是服务端?
如果存在服务端,未登录的购物车一定要有一个唯一标识,这样做显然不太合适,所以我们需要将未登录的购物车信息保存在客户端。
客户端存储的方案就有很多了,Session、Cookie、LocalStorage。Session实际上还是会保存在服务端,不合适。LocalStorage设计太过复杂,排除。
使用 Cookie 存储,实现起来比较简单,加减购物车、合并购物车的过程中,由于服务端可以读写 Cookie,这样全部逻辑都可以在服务端实现,并且客户端和服务端请求的次数也相对少一些。
登录的购物车
登录的购物车数据信息就需要持久化到数据库中了。
数据库选型怎么选?读写性能较优的Redis?具备结构化的MySQL?
显然使用 Redis 性能要比 MySQL 高出至少一个量级,响应时间更短,可以支撑更多的并发请求,这一点 Redis 完胜。
MySQL 的数据可靠性是要好于 Redis 的,因为 Redis 是异步刷盘,如果出现服务器掉电等异常情况,Redis 是有可能会丢数据的。但考虑到购物车里的数据,对可靠性要求也没那么苛刻,丢少量数据的后果也就是,个别用户的购物车少了几件商品,问题也不大。所以,在购物车这个场景下,Redis 的数据可靠性不高这个缺点,并不是不能接受的。
MySQL 的另一个优势是,它支持丰富的查询方式和事务机制,这两个特性,对我们今天讨论的这几个购物车核心功能没什么用。但是,每一个电商系统都有它个性化的需求,如果需要以其他方式访问购物车的数据,比如说,统计一下今天加购的商品总数,这个时候,使用 MySQL 存储数据,就很容易实现,而使用 Redis 存储,查询起来就非常麻烦而且低效。
当然,我们也可以采用Redis+MySQL进行数据双写,Redis抗流量,MySQL做持久化和数据兜底,但是也要保证二者数据的一致性。
数据变更
购物车中的商品的基本信息可能会发生变更。如果是热卖的商品,价库可能变动的也会非常频繁,如果产品中心的产品价库发生变更,用户的购物车的商品怎么变?
如果实时变更的话,产品中心的产品发生变动,将变动事件广播给购物车系统,如果针对所有用户的购物车的这个产品都做变更,系统和数据库压力都会非常的大。
我们这里可以采取限流变更和延迟变更。
限流变更就是控制变更的流量,比如基于系统的性能,使用令牌桶算法和动态滑动窗口算法来做用户购物车的数据变更操作。
延迟变更就是暂不感知数据的变更情况,等用户点击【去结算】的时候,交易域会对商品的数据进行【预校验】,如果发现商品价库发生变更,发送【刷新价库事件】广播给购物车系统,再做该用户数据变更操作。这样就避免了流量集中问题。