跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 问个 Redis 的问题
未分類
11 3 月 2021

问个 Redis 的问题

问个 Redis 的问题

資深大佬 : myd 8

需求:

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

  • 购买限制:例如每个用户 24 小时内只能购买 2 次
  • 清空并重新计算指定产品的购买限制:删除指定产品的全部用户的购买记录。

方案一

使用哈希:

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

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

大佬有話說 (31)

  • 資深大佬 : junan0708

    product_{product}_{user}_{Ymd}

  • 資深大佬 : also24

    『 24 小时内』 指的是滚动的 24 小时,还是固定的 24 小时( 1 天)。

    前者:昨天 8 点 1 次,18 点 1 次;今天 0 点不能购买。
    后者:昨天 8 点 1 次,18 点 1 次;今天 0 点可以购买。

  • 資深大佬 : kiracyan

    @also24 应该是 前者 后者直接给 hash 设过期时间就可以了

  • 主 資深大佬 : myd

    @also24 是的,后者,从购买时间开始计算的 24 小时。

    @junan0708 限制时间不一定是 24 小时。

  • 主 資深大佬 : myd

    打错,前者。

  • 資深大佬 : beryl

    如果是固定 24 小时(绝对时间):
    set product_{product}_{user} count expire_time(固定时间)

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

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

    这种压力就分散多了

  • 資深大佬 : also24

    @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

    hset product_{product}_{user} count 值:购买次数
    hset product_{product}_{user} expire 值:第一次购买时间 + 86400

    expire < now 可以购买
    expire >= now && count < 2 可以购买

  • 資深大佬 : beryl

    @also24 #7

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

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

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

    可以在你的这个思路上,第二个 key 拿到第一个的时间戳

  • 資深大佬 : also24

    @beryl #9
    我说的不能购买,就是指第二次购买后覆盖了过期时间。

    我的方法里,key 是包含了 timestamp 的,第二次购买的时候设置的是另一个 key,不存在覆盖问题。

  • 資深大佬 : beryl

    @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

    @also24 可以把设置 key 和设置过期时间分开。如果 key 存在就不设置过期时间。主要是使用字符串,需要遍历用户,变成 O(N)了。

  • 資深大佬 : also24

    @beryl #11
    2021-02-27 18:00-20:00 的时候,第一次购买的 key 已经过期了。

    此时 keys product_{product}_{user}_* 只能查出 1 条购买记录,没啥问题啊。

  • 資深大佬 : thet

    hset product_{product} {user} “qty {qty} expire {expire}”

    值序列化一下

  • 資深大佬 : also24

    这个方法的缺点还是 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

    我觉得可以按照 key 值顺序来删除吧 毕竟购买记录是按顺序写进去的(就是个队列),每次只看队列头的时间是否满足了 24 小时就可以的

  • 資深大佬 : beryl

    @also24 #13

    2021-02-27 18:00-20:00 我理解需求这个时候理论应该可以买两次,但是只可以买一次。

  • 資深大佬 : beryl

    题外话 qty 是什么的缩写

  • 資深大佬 : thet

    @beryl quantity,数量把

  • 資深大佬 : k9982874

    我在项目里使用的方案二,起初也是使用方案一,后来发现很难维护内容时效性就切到了方案二。
    方案一的问题在于,如果 qty 增加时间戳,在删除时必须把 qty 的内容一起取回来,当 qty 本身是个很大的对象时,成本就很高。
    如果像上说的增加一个 expired set,在查找 qty 时,还需要再请求一次 redis 取出来 expire 值,然后校验数据有效性,一次请求硬变成两次。

  • 資深大佬 : hw93

    方案二再维护一个 `set product_{product} [user1, user2]`方便去`删除指定产品的全部用户的购买记录`

  • 資深大佬 : also24

    @beryl #17
    当你再下第三个订单的时候,keys product_{product}_{user}_* 就变成 2 了,
    此时你再想下第四个订单,就需要等第二个订单超过 24 小时。

  • 資深大佬 : adamwong

    每个用户存两个时间戳不就完了?代码里做判断
    参考 golang 的令牌桶算法 time/rate.Limiter

  • 資深大佬 : yuankui

    删除的动作不频繁就 2 方案了

  • 資深大佬 : PiersSoCool

    这就是个限流器的问题 可参考开源方案

  • 資深大佬 : whileFalse

    为产品指定版本号,product-{product}.{product.v}-{user}
    清空产品时不做清空操作,而是产品版本号+1

  • 資深大佬 : palmers

    第一次插入的时候设置 key 的过期时间 第二使用 incr 不会清理过期时间, 通过 key 获取值如果没有了一定是可以购买的 否则累加一

  • 資深大佬 : palmers

    哦 可以购买的时候 还有判断 2

  • 資深大佬 : night98

    redisson,有个分布式对象你可以参考下

  • 資深大佬 : rocky114

    存储俩份 hset product_{product} {user} {qty},set product_{product} expire_time

  • 資深大佬 : lldld

    如果“清空并重新计算指定产品的购买限制” 是实际需求, 而”删除指定产品的全部用户的购买记录”只是你想的实现方法的话, 我觉得没必要删除购买记录 key.

    新增一个 key “product_reset_<product_id>”, value 存重置的时间. service 这边可以定时更新(比如每 5 分钟)这个数据存在内存里面, 因为这个行为应该是运营有计划的, 所以可以要求(或者告之)他们至少要提前 10 分钟在系统设置重置.

    用户购买记录, key “product_buy_<product_id>_<user_id>”, value 存 购买时间,数量, … , 购买时间,数量, 每次购买刷新过期时间为 24 小时

    用户购买时, 读其购买记录, 并过滤时间早于重置时间的数量, 然后计算是否可以购买, 能购买几个

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具