当前位置:首页 > 问答 > 正文

用Redis玩转购物车性能,聊聊那些数据结构和优化细节

今天咱们来聊聊怎么用Redis这个速度快得飞起的内存数据库,来做一个既高效又能撑住大流量的购物车系统,你想想,双十一的时候,几亿人同时往购物车里塞东西,要是用传统的关系型数据库,比如MySQL,每次加个商品都得去读写硬盘,数据库很可能就直接被压垮了,Redis把数据放在内存里,读写速度是微秒级别的,天生就是解决这种高并发场景的利器。

核心数据结构:就用Hash(来源:Redis官方文档与常见实践)

那购物车的数据用Redis的哪种结构来存最合适呢?答案最常见的就是Hash(哈希),为什么是它?因为它太像购物车本身的样子了。

我们可以给每个用户分配一个唯一的购物车Key,cart:user123,这个Key对应的就是一个Hash结构,在这个Hash里面:

  • 字段(Field):就用商品的ID,product_1001
  • 值(Value):就存这个商品的数量,3

这样一个简单的结构,就能清晰地记录下用户买了哪几样东西,每样买了多少,而且Hash结构的优点非常明显:

用Redis玩转购物车性能,聊聊那些数据结构和优化细节

  1. 高效操作:你想给用户增加一件商品 product_1001,直接用 HINCRBY cart:user123 product_1001 1 命令就行了,这个命令是原子性的,不用担心多个人同时操作会出错,而且速度极快,想查看整个购物车?一个 HGETALL cart:user123 命令就把所有商品和数量都取出来了。
  2. 节省空间:相比用String类型存储整个购物车的JSON字符串,Hash只存储发生变化的字段,在商品数量多、频繁修改少量商品时,更节省内存。

不能只存ID和数量:商品信息的优化(来源:电商系统设计经验)

但是问题来了,用户在查看购物车页面时,需要看到商品的名字、图片、当前价格等信息,你不可能只显示一个商品ID和数量给用户看,那这些额外的商品信息从哪里来?

最简单的想法是,从Redis里取出商品ID列表,然后再去后面的MySQL数据库里查询这些商品的详细信息,但这会产生大量的数据库查询,尤其是购物车商品多的时候,速度就慢下来了,又走回了老路。

用Redis玩转购物车性能,聊聊那些数据结构和优化细节

这里常见的优化方案是冗余存储关键信息(来源:性能优化常见策略),我们可以在把商品加入购物车的那一刻,不仅记录商品ID和数量,还把当时商品的关键快照信息也存进去,怎么做呢?有两种主流思路:

  • 还是用Hash,但扩展Value,我们可以把Value不再是简单的一个数字,而是一个小JSON字符串,{"skuId": "1001", "name": "某某手机", "image": "url", "price": 2999, "quantity": 1},这样一次 HGETALL 就能拿到渲染页面所需的所有数据,完全不用查数据库,缺点是如果商品信息变了(比如降价),购物车里的快照价格不会自动变,需要在业务逻辑里处理(比如下单时重新校验价格)。
  • 采用组合数据结构(来源:复杂场景数据设计),主购物车还是用Hash存 商品ID:数量,使用String类型或Hash类型建立一个商品信息缓存,Key是 product_info:1001,Value是商品的详细信息JSON,当需要展示购物车时,先用 HMGET 批量获取所有商品的ID和数量,再用 MGET 命令一次性从商品信息缓存中获取所有商品的详细信息,然后在应用程序里做拼装,这种方案解耦了购物车数据和商品数据,商品信息更新更灵活,但需要两次Redis请求(不过批量操作依然很快)。

应对特殊场景和进一步优化(来源:大规模系统实战经验)

除了基本的增删改查,购物车还有些特殊场景要考虑。

  • 过期时间:用户的购物车数据不能永久存在Redis里,太占内存,所以一定要给购物车的Key设置一个过期时间(TTL),比如30天,用 EXPIRE cart:user123 2592000 命令,这样超过30天没动的购物车,Redis会自动清理掉。
  • 全选和反选:这种状态不适合存在Hash里,因为它是针对整个购物车的操作,我们可以用一个简单的String类型Key来存,cart_select_all:user123,值就是 truefalse
  • 大数据量问题:万一有个用户是“收藏癖”,往购物车里加了上万件商品怎么办?一个HashKey太大,可能会影响Redis性能,这时候可以考虑分片(来源:Redis集群管理),比如按商品类别分,把电子产品放到 cart:user123:electronics,图书放到 cart:user123:books 不同的Key里,虽然查询时麻烦点,但保证了单个Key不会过大。

别忘了Redis是内存数据库,数据存在内存里,虽然有持久化机制,但通常我们不把它当作最终可靠的数据源,最保险的做法是,在用户进行下单结算这个关键动作时,再将购物车数据从Redis中读取出来,并持久化到MySQL等数据库中生成正式的订单,这样既保证了购物车操作的速度,又确保了订单数据的万无一失。

用Redis做购物车,核心就是利用其内存高速读写的特性,配合灵活的数据结构(主要是Hash),再通过冗余存储、缓存策略等技巧规避数据库瓶颈,最后设置好过期时间和管理好大数据量,就能轻松玩转高并发下的购物车了。