记录商品的购买用户和数量,实现:
使用哈希:
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
有没有比较优雅的实现方式?
1
junan0708 2021-02-26 15:55:55 +08:00
product_{product}_{user}_{Ymd}
|
2
also24 2021-02-26 15:58:10 +08:00
『 24 小时内』 指的是滚动的 24 小时,还是固定的 24 小时( 1 天)。
前者:昨天 8 点 1 次,18 点 1 次;今天 0 点不能购买。 后者:昨天 8 点 1 次,18 点 1 次;今天 0 点可以购买。 |
5
myd OP 打错,前者。
|
6
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 小时) 这种压力就分散多了 |
7
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}_* 就好 |
8
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 可以购买 |
9
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 拿到第一个的时间戳 |
10
also24 2021-02-26 16:46:16 +08:00
|
11
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 理论上可以购买一次,但是其实只有一次机会了 |
12
myd OP @also24 可以把设置 key 和设置过期时间分开。如果 key 存在就不设置过期时间。主要是使用字符串,需要遍历用户,变成 O(N)了。
|
13
also24 2021-02-26 16:53:33 +08:00
@beryl #11
2021-02-27 18:00-20:00 的时候,第一次购买的 key 已经过期了。 此时 keys product_{product}_{user}_* 只能查出 1 条购买记录,没啥问题啊。 |
14
thet 2021-02-26 16:56:07 +08:00
hset product_{product} {user} "qty {qty} expire {expire}"
值序列化一下 |
15
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}_* 查询,确认具体是否超过了限制。 |
16
dreamstart 2021-02-26 17:06:47 +08:00
我觉得可以按照 key 值顺序来删除吧 毕竟购买记录是按顺序写进去的(就是个队列),每次只看队列头的时间是否满足了 24 小时就可以的
|
18
beryl 2021-02-26 17:11:28 +08:00
题外话 qty 是什么的缩写
|
20
k9982874 2021-02-26 17:18:13 +08:00
我在项目里使用的方案二,起初也是使用方案一,后来发现很难维护内容时效性就切到了方案二。
方案一的问题在于,如果 qty 增加时间戳,在删除时必须把 qty 的内容一起取回来,当 qty 本身是个很大的对象时,成本就很高。 如果像楼上说的增加一个 expired set,在查找 qty 时,还需要再请求一次 redis 取出来 expire 值,然后校验数据有效性,一次请求硬变成两次。 |
21
hw93 2021-02-26 17:27:40 +08:00 1
方案二再维护一个 `set product_{product} [user1, user2]`方便去`删除指定产品的全部用户的购买记录`
|
22
also24 2021-02-26 17:33:24 +08:00
|
23
adamwong 2021-02-26 17:47:48 +08:00
每个用户存两个时间戳不就完了?代码里做判断
参考 golang 的令牌桶算法 time/rate.Limiter |
24
yuankui 2021-02-26 17:58:32 +08:00
删除的动作不频繁就 2 方案了
|
25
PiersSoCool 2021-02-26 18:03:12 +08:00
这就是个限流器的问题 可参考开源方案
|
26
whileFalse 2021-02-26 18:29:39 +08:00 via iPhone 1
为产品指定版本号,product-{product}.{product.v}-{user}
清空产品时不做清空操作,而是产品版本号+1 |
27
palmers 2021-02-26 20:08:37 +08:00
第一次插入的时候设置 key 的过期时间 第二使用 incr 不会清理过期时间, 通过 key 获取值如果没有了一定是可以购买的 否则累加一
|
28
palmers 2021-02-26 20:09:59 +08:00
哦 可以购买的时候 还有判断 2
|
29
night98 2021-02-26 20:57:09 +08:00
redisson,有个分布式对象你可以参考下
|
30
rocky114 2021-02-26 21:31:45 +08:00 1
存储俩份 hset product_{product} {user} {qty},set product_{product} expire_time
|
31
lldld 2021-02-26 22:42:24 +08:00
如果“清空并重新计算指定产品的购买限制” 是实际需求, 而"删除指定产品的全部用户的购买记录"只是你想的实现方法的话, 我觉得没必要删除购买记录 key.
新增一个 key "product_reset_<product_id>", value 存重置的时间. service 这边可以定时更新(比如每 5 分钟)这个数据存在内存里面, 因为这个行为应该是运营有计划的, 所以可以要求(或者告之)他们至少要提前 10 分钟在系统设置重置. 用户购买记录, key "product_buy_<product_id>_<user_id>", value 存 购买时间,数量, ... , 购买时间,数量, 每次购买刷新过期时间为 24 小时 用户购买时, 读其购买记录, 并过滤时间早于重置时间的数量, 然后计算是否可以购买, 能购买几个 |