大纲
一.Redis的应用和解决方案
二.Redis在用户数据里的应用
三.Redis在列表数据里的应用
四.Redis在购物车里的应用
五.Redis在库存里的应用
六.Redis相关解决方案
四.Redis在购物车里的应用
1.购物车的读多写多场景分析
2.购物车的复杂缓存与异步落库(Sorted Set + Hash -> hPut + zadd)
3.购物车异步落库的消息丢失与不一致分析(缓存雪崩 + MQ异步出现问题)
4.购物车的阈值检查与重复加入逻辑(hGet + hLen + hFieldExists)
5.购物车加入商品多线程并发问题解决(分布式锁保证请求幂等)
6.购物车的查询、更新与选中功能(zrevrange +hGetAll + zremove+ hDel)
7.购物车的选中提交功能
8.总结
1.购物车的读多写多场景分析
(1)对用户数据和商品列表数据的处理
在新增或修改时,采用的是同步写库+写缓存或异步写库+写缓存的方案。其中用户数据缓存会进行同步更新,而商品列表数据的分页缓存则由于可能需要重建的分页缓存比较多,则会通过异步更新。并且在读取用户数据、商品列表数据时,是直接读缓存的。除非是读到了被淘汰掉的冷数据,才会重新读数据库 + 写缓存。
之所以这样设计,是因为用户数据和商品数据都是典型读多写少的数据,可能用户数据是0.01%写 + 99.99%读,而商品数据是1%写 + 99%读。由于写的情况很少,所以对应数据库的写压力也就很小。因此在用户数据写库时直接采用同步写库和写缓存,是没问题的。由于读的情况很多,所以通过先读缓存就可以用缓存抗下大量高并发的读。
(2)对购物车数据的处理
首先购物车功能包括:加入购物车、查看购物车、编辑购物车、发起结算等。然后当平台进行促销活动时,这时购物车的数据就会变成读多写多的数据,此时的购物车的数据可能会出现高并发的写。如果购物车数据的写也同步落库,那么可能就会导致数据库的压力很大。
因此对于购物车或者库存这种读多写多的数据,由于存在大量高并发的写、大量高并发的读,那么我们会把主要数据基于Redis来进行主存储,来实现高性能读写,同时通过异步把数据同步到MySQL进行持久化落库。
2.购物车的复杂缓存与异步落库(Sorted Set + Hash -> hPut + zadd)
由于购物车的数据是读多写多的数据,所以会使用缓存来存储主数据,以便能够抗住高并发的写和读,然后进行落库的时候再通过异步来落库。此外,商品系统一般也会使用缓存架构来提供商品数据的读接口。
更新购物车时,需要涉及如下操作:更新用户购物车的SKU数量缓存、更新加入到购物车的SKU扩展信息缓存、更新用户购物车的SKU加购时间排序缓存。
由于购物车包含很多种数据,所以会分成多个缓存key,这些key对应的缓存数据类型有:
Hash数据类型:购物车商品数量和信息的哈希{skuId -> count}和{skuId -> skuInfo}
ZSet数据类型:购物车加购排序的有序集合[skuId -> timestamp]
3.购物车异步落库的消息丢失与不一致分析(缓存雪崩 + MQ异步出现问题)
(1)异步落库时缓存崩了没有出现数据不一致(可以通过降级 + 缓存预热加载恢复)
此时其实就是缓存雪崩的情况。现已知用户在加购一个商品SKU到购物车时,会进行异步化落库磁盘。这时MySQL数据库有点像是备用存储,主要用在异步同步数据时备份数据。
一般来说购物车的主数据存储,是由Redis来实现的,并都优先从Redis中进行购物车的写和读,这时是不会有不一致的问题的。由于落库时通过异步化使用MySQL备用存储,那么万一Redis集群全都崩溃了,这时可能就会导致购物车的主数据都没了。
但即便Redis主数据全都没了,我们还是可以基于MySQL来进行降级,通过降级继续提供购物车的写和读。然后等缓存恢复后,再进行缓存预热加载,把数据库里的数据加载到缓存里。当然缓存集群崩了,其实就是缓存雪崩的问题了。
(2)异步落库时缓存写了但是MQ没有写成功从而出现数据不一致
此时其实就是MQ异步出现问题的情况,此时可能会对应下面两种异常情况:
情况一:刚刚写完缓存,还没来得及发送消息到MQ里,突然系统崩了。导致缓存写成功,但是异步消息没有发送出去。
情况二:刚刚写完缓存,系统正常运行,已经向MQ发出消息,但RocketMQ崩了。导致缓存写成功,但是异步消息也没发送成功。
这两种异常情况都会导致Redis里有数据,但RocketMQ里没消息。这时Redis中的数据自然也就没有异步落库到MySQL,从而造成Redis缓存和MySQL数据库之间的数据是不一致的。
这时候其实问题也不太大,即使出现了这样的情况,只要Redis里有数据就即可。因为用户后续一旦提交购物车生成订单,那么其数据就会从Redis里删除,这时MySQL就会跟Redis同步了。
(3)先出现不一致然后缓存崩了从而造成数据丢失
如果是Redis突然崩溃了,导致只有MySQL里有数据了。但是MySQL之前又因MQ崩了丢了一条数据,那么此时因为Redis崩了所以那条数据就丢失了。
这时其实问题也不大,因为这最多导致用户在购物车里找不到自己刚加入的商品。而且购物车只要没发起提交,Redis的本质还是临时性的数据存储空间。在购物车中找不到商品,那么重新加入购物车即可。
(4)为什么购物车的主数据存储要选用Redis
因为从业务上来说,购物车的数据其实是属于临时性的数据,用户仅仅是把一些商品在购物车里进行暂存。对用户来说,购物车里的商品会有三种情况:不发起购买,从购物车里直接删除这些商品、过了很长时间都没买,用户都已经把它给忘了、选择购物车里的商品发起购买。
所以对于这种比较偏临时的数据,使用Redis来当主数据的存储是没问题的。即便出现缓存雪崩或MQ异步出现问题,导致Redis和MySQL数据不一致,甚至购物车数据丢失,那么问题也不大。因为极端情况下,购物车少了一些商品,大不了让用户重新加购。