Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

购物车redis数据类型使用String是好的设计吗? #55

Open
hzy38324 opened this issue Aug 8, 2022 · 4 comments
Open

购物车redis数据类型使用String是好的设计吗? #55

hzy38324 opened this issue Aug 8, 2022 · 4 comments

Comments

@hzy38324
Copy link

hzy38324 commented Aug 8, 2022

举个简单的场景,加购一件sku,这时候需要做这几步操作:
1、先根据用户ID,把这个用户购物车全部数据get出来
2、然后在本地内存里,遍历,看是否有对应的skuId
3、有,则把数量+1,然后把全部数据set回去
4、无,则add一个元素,然后把全部数据set回去
时间复杂度O(n) 数据传输量大, 想想如果一个用户购物车加满了,比如200个sku,数据量会很大

另外还要考虑数据版本、看是否要加锁等问题

相反,使用 hash,除了获取购物车详情需要使用 hgetall,在Redis服务端进行操作,时间复杂度O(n)
其他的加购、编辑数量、规格等场景,时间复杂度都是O(1),数据传输量也小
另外相比String,hash数据是结构化的

@chunpat
Copy link

chunpat commented Aug 9, 2022

字段的类型,大家根据自己的需要进行修改。

接下来该说怎么选择Redis的存储结构了,Redis常用的 Hash Table、集合、有序集合、链表、字符串 五种,我们一个个来分析。

首先购车一定有一个key来标记这个购物车属于哪个用户的,为了简化,我们的key假设是:uid:cart_type

我们先来看如果用 Hash Table;我们添加时,需要用到如下命令:HSET uid:cart_type sku ShoppingData;看起来没问题,我们可以根据sku快速定位某个商品然后进行相关的修改等,但是注意,ShoppingData是一个json串,如果用户购物车中有非常多的商品,我们用 HGETALL uid:cart_type 获取到的时间复杂度是O(n),然后代码中还需要一一反序列化,又是O(n)的复杂度。

如果用集合,也会遇到类似的问题,每个购物车看做一个集合,集合中的每个元素是 ShoppingData ,取到代码中依然需要逐一反序列化(反序列化是成本),关于有序集合与链表就不在分析,大家可以按照上面的思路去尝试下问题所在。

看起来我们没得选,只有使用String,那我们来看一下String的契合度是什么样子。首先SET uid:cart_type ShoppingDataArr;我们把购物车所有的数据序列化成一个字符串存储,每次取出来的时间复杂度是O(1),序列化、反序列化都只需要一次。看来是非常不错的选择。但是在使用中大家还是有几点需要注意。

  1. 单个Value不能太大,要不然就会出现大key问题,所以一般购物车有上限限制,比如item不能超过多少个;
  2. 对redis的操作性能提升上来了,但是代码的就是修改单个item时的不便,必须每次读取全部然后找到对应的item进行修改;这里我们可以把从redis中的数据读取出来后,在内存中构建一个HashTable,来减少每次遍历的复杂度;

网上也看到很多Redis数据结构组合使用来保存购物车数据的,但是无疑增加了网络开销,相比起来还是String最经济划算。

已经给了分析了呀,hash对于多层级复杂数据格式的购物车设计没啥性能优势而且还添加了操作难度,只有string符合设计要求

@hzy38324
Copy link
Author

hzy38324 commented Aug 9, 2022

string 类型,加购场景下时间复杂度是O(n),而且需要把用户全部购物车数据get出来,这样也符合设计要求?

@chunpat
Copy link

chunpat commented Aug 9, 2022

string 类型,加购场景下时间复杂度是O(n),而且需要把用户全部购物车数据get出来,这样也符合设计要求?

存储方式

string存储:SET uid:cart_type ShoppingDataArr
hash存储:HSET uid:cart_type sku ShoppingData

Redis时间复杂度

存储\操作 进入购物车界面(get) 单一商品加购 批量商品加购
string GET O(1) SET O(1) SET O(1)
hash HGETALL O(n) HSET O(1) HSET O(n)

hash的HGETALL为O(n)大概内部也是循环HGET

@hzy38324 你看看我这样想的对不?这里针对的是redis的复杂度

@hzy38324
Copy link
Author

hzy38324 commented Aug 9, 2022

string 类型,加购场景下时间复杂度是O(n),而且需要把用户全部购物车数据get出来,这样也符合设计要求?

存储方式

string存储:SET uid:cart_type ShoppingDataArr hash存储:HSET uid:cart_type sku ShoppingData

Redis时间复杂度

存储\操作 进入购物车界面(get) 单一商品加购 批量商品加购
string GET O(1) SET O(1) SET O(1)
hash HGETALL O(n) HSET O(1) HSET O(n)
hash的HGETALL为O(n)大概内部也是循环HGET

@hzy38324 你看看我这样想的对不?这里针对的是redis的复杂度

嗯,针对redis的复杂度,这样是没问题的,但是也要考虑客户端的时间复杂度吧,redis和客户端都是操作内存

还是以加购举例:
string类型,先从redis,get这个用户全部购物车,然后需要遍历一遍,看是否已经有这个sku,有,则把数量加1,然后再把全量购物车数据set回去
hash类型,只需要hget,看看购物车是否已经有这个sku,有,则把数量加1,然后hset单个商品,没有,也是hset

整体分析下来:
string 单一商品/批量加购,都是O(n) 这个n是当前用户在购物车有多少唯一标识的商品,用户加购商品越多,性能就越差
hash
单一商品加购 O(1)
批量商品加购 O(n) 这个n取决于批量操作多少个商品,和用户当前购物车数量无关

至于购物车详情的场景,确实String优于Hash

从大多数电商场景看,加购是多于购物车详情的,用户可能会加购n件后,才打开一次购物车。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants