广告频控业务exhash方案-凯发k8国际娱乐官网入口
exhash类型是一种支持field过期的新型数据类型,它在原先的hash类型基础上进行了扩展:在支持hash类型的通用功能以外,exhash类型还支持为field设置过期时间和版本,增强了数据结构的灵活性,从而简化了很多复杂场景下的业务开发工作。
本文以两种常见的场景(频控场景&购物车场景)为例,通过使用geminidb redis接口中的exhash类命令来实现复杂的业务,简化开发难度。
exhash命令介绍
exhash命令详细介绍请参考。
应用场景
- 频控场景
频控指的是对用户在一定时间内(例如一天、一周、一个月)进行某种操作的次数进行限制,可以控制特定广告或信息在一定时间内在特定平台上的展示次数,以避免过度曝光和广告疲劳,同时优化广告效果和用户体验;对于广告来说,也可以提高广告的效果和转化率。此外,频控还可以避免恶意行为,如刷流量、刷评论、刷点赞等。
频控的3个要素包含用户id、广告id、触发次数;以用户id为key,广告id为field,指定时间内的触发次数为value,恰好构成频控的三要素。先配置好各个广告的指定频控策略,如下图所示即可根据如下的方式来实现频控:
图1 频控hash方案
- 最左边通过hash类型来实现,通过expire命令设置user_1的过期时间为一天,每推送一次通过hincrby来增加指定广告的推送次数,每次推送指定广告前在一天内的推送次数则可以通过hget获取进行判断,一天后该用户的数据自动过期无需手动清理,这样便可以简单地实现频控。但这个方案的缺点在于对于每个用户(即每个key)只能设置一个过期时间,无法做到例如8小时3次这样指定时间段内的灵活的频控策略。
- 为了做到对每个广告都配置指定时间段内的灵活频控,如中间图所示可以通过将时间戳拼接在value里的方式用hash类型来实现,但这种方案无疑是增加了业务侧开发的工作量。
- 如最右图所示,支持给field设置过期时间的exhash类型可以很完美地解决hash类型面对频控场景的缺点。由于field支持过期时间设置,那么该场景下,平台可以给每个广告都配置不同时间段内的频次要求,假设此时给ad_2配置的频控策略为8小时内2次,那么如图所示在下一次再准备给user_1推送ad_2广告前,先通过exhget user_1 ad_2命令获取到了该值已经是2时,便可以判断出此时根据平台频控策略,不应该再给user_1推送ad_2广告了。而当8小时一过,user_1的ad_2这个field过期后,exhget无法再获取到这个field的信息,则可以继续给user_1推送ad_2广告了。
- 购物车场景
双十一期间,相信很多同学购物车里都填满了各种想要清空的宝贝,这里就以购物车场景为例介绍该场景的几种不同redis类型的实现,并比较这几种实现方案的优缺点。
- 基于string实现购物车功能
如图图2所示,基于string可以轻松地实现各个用户的购物车功能,该方案需要将用户id与商品id进行拼接作为key,例如user_1#earphones_1,key对应的value为购物车中用户准备购买的数量,其中可能有部分商品为限时特购,所以有过期时间,为key对应的过期时间。
图2 string方案
- 涉及命令如下:
incrby user_n#product_n [number] # 增加商品数量 set user_n#product_n [number] # 设置商品数量 expire user_n#product_n time_n # 设置指定用户购物车中指定物品的过期时间 get user_n#product_n # 获取商品数量 scan 0 match user_n* # 查找所有user_n下的所有商品 del user_n#product_n # 删除指定用户购物车中的指定商品
- 该方案会存在如下问题:
- 额外拼接增加编、解码开发工作量。
- 某个用户获取自己的购物车清单时还需要通过scan命令前缀匹配扫描所有key,并通过get命令去获取对应的值。
- 想要直接获取清单长度时,仍然需要遍历整个前缀key的数目,方法复杂。
- 存在大量重复的用户名前缀,浪费存储空间。
- 涉及命令如下:
- 基于hash实现购物车功能
可以根据如图3所示的hash类型来实现购物车的管理,用户id作为key,商品id作为field,value为购物车中对应商品的数量。其中对于部分限时特购的商品,其过期时间通过拼接的方式放到field对应的value里。
图3 hash方案
- 涉及命令如下:
hset user_n product_n [number#time_n] # 设置指定用户购物车中指定商品的数量和过期时间 hincrby user_n product_n [number] # 增加指定用户购物车中的指定商品数量 hgetuser_n product_n # 获取指定用户购物车中指定商品的信息 hgetall user_n # 获取指定用户的所有商品信息 hlen user_n # 获取指定用户购物车中的总商品数量 hdel user_n product_n # 删除指定用户购物车中的指定商品
- 该方案相对于string类型的方案有了不少优化:
- 获取某个用户购物车中的所有商品清单仅需要一个hgetall命令即可。
- 获取某个用户的清单长度时直接hlen获取即可。
- 不存在大量重复的用户名前缀问题。
然而该方案仍存在一个明显的缺点,即对于部分限时特购的商品处理起来复杂:对于user_1的keyboard_1商品,如果要再加一个数量,不能直接使用hincrby,而是需要先hget获取keyboard_1商品的值并解码,再加上指定的数量再编码后hset对应的值。
- 涉及命令如下:
- 基于exhash实现购物车功能
根据如图4所示的exhash类型来实现购物车的管理,同hash类型一样,用户id作为key,商品id作为field,value为购物车中对应商品的数量。其中对于部分限时特购的商品,由于exhash类型可以为field设置过期时间,其过期时间可通过hset命令直接设置。
图4 exhash方案
- 涉及命令如下:
exhset user_n product_n ex time_n # 设置指定用户购物车中指定商品的数量和过期时间 exhincrby user_n product_n [number] keepttl # 增加指定用户购物车中的指定商品数量,保留原先过期时间exhget user_n product_n # 获取指定用户购物车中指定商品的信息 exhgetall user_n # 获取指定用户的所有商品信息 exhlen user_n # 获取指定用户购物车中的总商品数量 exhdel user_n product_n # 删除指定用户购物车中的指定商品 del user_n # 清空指定用户的购物车
- 该方案相对于hash类型的优化主要体现在可以直接为各field设置过期时间,使业务侧使用起来简单又高效。可以看到exhash类型相关的命令和hash类型是类似的,使用起来学习成本很低,业务侧改造成本相对也比较低。
- 涉及命令如下:
- 基于string实现购物车功能
广告频控业务代码示例
import redis import datetime def get_cur_time(): return "[" datetime.datetime.utcnow().strftime('%y-%m-%d %h:%m:%s.%f')[:-3] "]" def get_redis(): """ 该方法用于连接redis实例。 * host:redis实例连接地址。 * port:redis实例的端口号,默认为6379。 * password:redis实例的密码。 """ return redis.redis(host='***', port=6379, password='***') '''全局的频控策略,广告1策略为3秒内最多2次,广告2策略为5秒内最多5次''' frequency_stratege = {"ad_1" : [2, 3], "ad_2" : [5, 5]} def push_ad_to_user(userid: str, adid: str): ''' 该方法用于推送指定广告给指定用户。 * userid:用户id * adid:广告id ''' # 没有设置对该广告的频控策略,则直接投放 if adid not in frequency_stratege: print("no need control frequency, push ", adid, "to", userid) return true # 根据用户id和广告id获取该广告在某个用户上的投放次数 # 命令用法:exhget key field cnt = get_redis().execute_command("exhget " userid " " adid) # 该用户没有这个广告的投放记录,则直接投放 if cnt == none: # 命令用法:exhincrby key field num [ex time] # 使用说明:exhincrby 用户id 广告id 投放次数(1) ex 该广告的过期时间 cmd = "exhincrby " userid " " adid " 1 ex " str(frequency_stratege[adid][1]) cur_cnt = get_redis().execute_command(cmd) print(get_cur_time(),"push", adid, "to", userid, "first time during", str(frequency_stratege[adid][1]), "seconds") return true # redis-py返回的结果是bytes类型,转换成str后转int类型 cnt = int(cnt.decode("utf-8")) if cnt < frequency_stratege[adid][0]: # 命令用法:exhincrby key field num keepttl 保持field原先的过期时间 cmd = "exhincrby " userid " " adid " 1 keepttl" cur_cnt = get_redis().execute_command(cmd) print(get_cur_time(), "push", adid, "to", userid, "current cnt:", cur_cnt) return true print(get_cur_time(), "control frequency, can't push", adid, "to", userid, ", max cnt:", frequency_stratege[adid][0]) return false if __name__ == "__main__": for i in range(3): push_ad_to_user("usr_1", "ad_1") for i in range(6): push_ad_to_user("usr_1", "ad_2") for i in range(3): push_ad_to_user("usr_1", "ad_1") for i in range(12): push_ad_to_user("usr_1", "ad_2")
脚本运行输出:
其中由于python脚本本身运行较慢,广告2的过期时间设置得只有5s,所以当第一次投放广告2的时间2023-12-15 07:09:51.349的5s之后,也就是2023-12-15 07:09:56.530时间点再次给这个用户推送广告2时就可以推送成功了。
[2023-12-15 07:09:50.086] push ad_1 to usr_1 first time during 3 seconds [2023-12-15 07:09:50.503] push ad_1 to usr_1 current cnt: 2 [2023-12-15 07:09:50.794] control frequency, can't push ad_1 to usr_1 , max cnt: 2 [2023-12-15 07:09:51.349] push ad_2 to usr_1 first time during 5 seconds [2023-12-15 07:09:51.745] push ad_2 to usr_1 current cnt: 2 [2023-12-15 07:09:52.128] push ad_2 to usr_1 current cnt: 3 [2023-12-15 07:09:52.889] push ad_2 to usr_1 current cnt: 4 [2023-12-15 07:09:53.417] push ad_2 to usr_1 current cnt: 5 [2023-12-15 07:09:53.632] control frequency, can't push ad_2 to usr_1 , max cnt: 5 [2023-12-15 07:09:54.120] push ad_1 to usr_1 first time during 3 seconds [2023-12-15 07:09:54.769] push ad_1 to usr_1 current cnt: 2 [2023-12-15 07:09:54.915] control frequency, can't push ad_1 to usr_1 , max cnt: 2 [2023-12-15 07:09:55.211] control frequency, can't push ad_2 to usr_1 , max cnt: 5 [2023-12-15 07:09:55.402] control frequency, can't push ad_2 to usr_1 , max cnt: 5 [2023-12-15 07:09:55.601] control frequency, can't push ad_2 to usr_1 , max cnt: 5 [2023-12-15 07:09:55.888] control frequency, can't push ad_2 to usr_1 , max cnt: 5 [2023-12-15 07:09:56.087] control frequency, can't push ad_2 to usr_1 , max cnt: 5 [2023-12-15 07:09:56.530] push ad_2 to usr_1 first time during 5 seconds [2023-12-15 07:09:57.133] push ad_2 to usr_1 current cnt: 2 [2023-12-15 07:09:57.648] push ad_2 to usr_1 current cnt: 3 [2023-12-15 07:09:58.107] push ad_2 to usr_1 current cnt: 4 [2023-12-15 07:09:58.623] push ad_2 to usr_1 current cnt: 5 [2023-12-15 07:09:58.865] control frequency, can't push ad_2 to usr_1 , max cnt: 5 [2023-12-15 07:09:59.096] control frequency, can't push ad_2 to usr_1 , max cnt: 5
本文介绍了geminidb redis接口的exhash类型的特性、使用方法及应用场景。为客户提供了一种语法与原生redis hash类型类似、和hash类型的使用相互隔离、支持给field单独设置过期时间和版本的exhash类型作为各种复杂场景的凯发k8国际娱乐官网入口的解决方案。未来,geminidb redis接口将持续致力于开发更多好用的企业级特性,帮助客户轻松运维,高效开发。
意见反馈
文档内容是否对您有帮助?
如您有其它疑问,您也可以通过华为云社区问答频道来与我们联系探讨