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