问个 Redis 的问题

2021-02-26 15:51:51 +08:00
 myd

需求:

记录商品的购买用户和数量,实现:

方案一

使用哈希:

hset product_{product}  {user}  {qty}

优点:清空限制很方便,del product_{product}
缺点:hash 貌似不能针对指定 field(用户)设置缓存过期时间, 相关讨论: https://github.com/redis/redis/issues/1042 。只能在 qty 加一个过期时间 /购买,维护起来麻烦,也不太优雅。

方案二

使用字符串:

set product_{product}_{user}  {qty}

优点:可以针对产品 /用户设置过期时间,实现购买限制简单。
缺点:清空指定产品的购买限制复杂度是 O(N),类似于 keys product_{product}_* |xargs redis-cli DEL

有没有比较优雅的实现方式?

3928 次点击
所在节点    Redis
31 条回复
junan0708
2021-02-26 15:55:55 +08:00
product_{product}_{user}_{Ymd}
also24
2021-02-26 15:58:10 +08:00
『 24 小时内』 指的是滚动的 24 小时,还是固定的 24 小时( 1 天)。

前者:昨天 8 点 1 次,18 点 1 次;今天 0 点不能购买。
后者:昨天 8 点 1 次,18 点 1 次;今天 0 点可以购买。
kiracyan
2021-02-26 16:02:04 +08:00
@also24 应该是 前者 后者直接给 hash 设过期时间就可以了
myd
2021-02-26 16:10:09 +08:00
@also24 是的,后者,从购买时间开始计算的 24 小时。


@junan0708 限制时间不一定是 24 小时。
myd
2021-02-26 16:10:22 +08:00
打错,前者。
beryl
2021-02-26 16:11:01 +08:00
如果是固定 24 小时(绝对时间):
set product_{product}_{user} count expire_time(固定时间)

这个有个问题是,会在固定时间清除大量的 key, redis 会有压力。


如果是滚动 24 小时,从第一次购买算为原点:
set product_{product}_{user} count expire_time(当前时间+24 小时)

这种压力就分散多了
also24
2021-02-26 16:18:06 +08:00
@beryl #6
如果按照:
set product_{product}_{user} count expire_time(当前时间+24 小时)

那么距离第一次购买 24 小时后,仍然无法购买了。


可以改成

set product_{product}_{user}_{timestamp} timestamp expire_time(当前时间+24 小时)

取数量的时候 keys product_{product}_{user}_* 就好
junan0708
2021-02-26 16:33:55 +08:00
hset product_{product}_{user} count 值:购买次数
hset product_{product}_{user} expire 值:第一次购买时间 + 86400

expire < now 可以购买
expire >= now && count < 2 可以购买
beryl
2021-02-26 16:43:21 +08:00
@also24 #7

如果只是第一次购买,24 小时后,被自动清掉了,key 不存在可以认为 0 次,可以购买的。

但确实有问题,因为『第二次购买后,24 小时时间被重置了』(-

set product_{product}_{user}_{timestamp} timestamp expire_time(当前时间+24 小时)
这个同样会有『第二次购买后的过期时间是根据第二次的当前时间+24 小时』这样第一次购买 24 小时后,购买次数并没有被重置


可以在你的这个思路上,第二个 key 拿到第一个的时间戳
also24
2021-02-26 16:46:16 +08:00
@beryl #9
我说的不能购买,就是指第二次购买后覆盖了过期时间。


我的方法里,key 是包含了 timestamp 的,第二次购买的时候设置的是另一个 key,不存在覆盖问题。
beryl
2021-02-26 16:50:14 +08:00
@also24
嗯,那我的方案的问题理解一致

『 key 是包含了 timestamp 的,第二次购买的时候设置的是另一个 key,不存在覆盖问题』
如果第一次购买的时候是:2021-02-26 18:00,过期时间 2021-02-27 18:00
第二次购买是:2021-02-26 20:00 , 过期时间 2021-02-27 20:00

如果在 2021-02-27 18:00-20:00 理论上可以购买一次,但是其实只有一次机会了
myd
2021-02-26 16:53:22 +08:00
@also24 可以把设置 key 和设置过期时间分开。如果 key 存在就不设置过期时间。主要是使用字符串,需要遍历用户,变成 O(N)了。
also24
2021-02-26 16:53:33 +08:00
@beryl #11
2021-02-27 18:00-20:00 的时候,第一次购买的 key 已经过期了。

此时 keys product_{product}_{user}_* 只能查出 1 条购买记录,没啥问题啊。
thet
2021-02-26 16:56:07 +08:00
hset product_{product} {user} "qty {qty} expire {expire}"

值序列化一下
also24
2021-02-26 16:59:21 +08:00
这个方法的缺点还是 keys product_{product}_{user}_* 的效率比较低,性能差。

有一个优化方案是做一次剪枝,大部分用户不存在 24 小时内购买过商品,那么我们设置一个 『用户是否在 24 小时内购买过商品』的 key 就好了。

也就是每次用户产生购买行为,都 set product_{product}_{user} timestamp expire_time (不论原 key 是否存在,都续期 24 小时)

这样对于大部分用户,只需要查询 product_{product}_{user} 不存在,就可以认为不存在限制了。

而对于 product_{product}_{user} 存在的用户,再进行 keys product_{product}_{user}_* 查询,确认具体是否超过了限制。
dreamstart
2021-02-26 17:06:47 +08:00
我觉得可以按照 key 值顺序来删除吧 毕竟购买记录是按顺序写进去的(就是个队列),每次只看队列头的时间是否满足了 24 小时就可以的
beryl
2021-02-26 17:11:07 +08:00
@also24 #13

2021-02-27 18:00-20:00 我理解需求这个时候理论应该可以买两次,但是只可以买一次。
beryl
2021-02-26 17:11:28 +08:00
题外话 qty 是什么的缩写
thet
2021-02-26 17:13:24 +08:00
@beryl quantity,数量把
k9982874
2021-02-26 17:18:13 +08:00
我在项目里使用的方案二,起初也是使用方案一,后来发现很难维护内容时效性就切到了方案二。
方案一的问题在于,如果 qty 增加时间戳,在删除时必须把 qty 的内容一起取回来,当 qty 本身是个很大的对象时,成本就很高。
如果像楼上说的增加一个 expired set,在查找 qty 时,还需要再请求一次 redis 取出来 expire 值,然后校验数据有效性,一次请求硬变成两次。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/756528

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX