- 最新动态
- 功能总览
- 服务公告
- 产品介绍
-
GeminiDB Redis接口
- 产品介绍
- 计费说明
- 快速入门
-
用户指南
- 权限管理
- 购买GeminiDB Redis实例
- 实例连接及管理
-
数据迁移
- Redis数据迁移方案概览
- 使用DRS服务将GeminiDB Redis迁移到Redis(推荐)
- 阿里云数据库Redis/Tair到GeminiDB Redis的迁移
- 腾讯云Redis到GeminiDB Redis的迁移
- 使用DRS服务将自建Redis或者Redis集群迁移到GeminiDB Redis(推荐)
- 通过Redis-Shake迁移工具将自建Redis迁移到GeminiDB Redis
- 使用Redis-Shake工具将RDB文件/AOF文件 导入到GeminiDB Redis
- 使用数据导入功能将RDB文件恢复到GeminiDB Redis(推荐)
- Kvrocks到GeminiDB Redis的迁移
- Pika到GeminiDB Redis的迁移
- SSDB到GeminiDB Redis的迁移
- LevelDB到GeminiDB Redis的迁移
- RocksDB到GeminiDB Redis的迁移
- AWS ElasticCache for Redis数据库到GeminiDB Redis的迁移
- 迁移后Redis数据一致性校验
- 实例管理
- 变更实例
- 数据备份
- 数据恢复
- 诊断分析
- 账号与安全
- 参数管理
- 日志与审计
- 查看监控指标与配置告警
- GeminiDB Redis标签管理
- GeminiDB Redis用户资源配额
- 通过GeminiDB Redis实现MySQL内存加速
- 开发参考
- 最佳实践
- 性能白皮书
-
常见问题
- 高频常见问题
-
产品咨询
- GeminiDB Redis和开源Redis、其他开源Redis云服务有什么区别?
- 和开源Redis相比,GeminiDB Redis性能如何?
- GeminiDB Redis兼容Redis哪些版本,兼容哪些命令,客户端连接是否需要修改
- 自建Redis是否可以搬迁至GeminiDB Redis,需要注意什么
- 什么是GeminiDB Redis实例可用性
- GeminiDB Redis实例总容量是总内存吗,内存和容量之间是什么联系
- 购买GeminiDB Redis实例时,如何选择合适的节点规格和节点数量?
- 购买x GB的GeminiDB Redis的实例,优选主备还是集群?
- GeminiDB Redis持久化机制是怎样的,会丢数据吗
- GeminiDB Redis的内存淘汰策略是什么
- GeminiDB Redis是否支持布隆过滤器等modules
- 计费相关
-
数据库使用
- scan指定match参数,数据中确实存在匹配的key,为什么返回的是空
- 业务侧原本做了数据分片,切换到GeminiDB Redis后如何处理这部分逻辑
- GeminiDB Redis接口是否支持keys命令的模糊查询
- GeminiDB Redis是否支持多DB
- 对于scan类的操作,GeminiDB Redis接口与开源Redis 5.0的返回值顺序为什么有差异
- 针对某些不合法命令,GeminiDB Redis接口与开源Redis 5.0的报错信息为什么有差异
- 如何处理报错:CROSSSLOT Keys in request don't hash to the same slot
- GeminiDB Redis单次事务推荐包含的命令条数
- GeminiDB Redis集群版实例中,哪些命令需要使用hashtag
- 如何处理报错“ERR unknown command sentinel"
- 对于阻塞命令,GeminiDB Redis接口(主备实例)与开源Redis的返回值为什么可能有差异
- GeminiDB Redis存储扩容需要多久,对业务有影响吗?
- GeminiDB Redis多个节点同时扩容需要多长时间,对业务影响如何?
- GeminiDB Redis规格变更包含的在线变更和离线变更有什么区别,通常需要多长时间,对业务有哪些影响?
- GeminiDB Redis版本补丁升级包含的在线升级和离线升级有什么区别,通常需要多长时间,对业务有哪些影响?
- GeminiDB Redis备份文件是否可以下载到本地,是否支持线下恢复数据
- GeminiDB Redis数据备份工作机制是怎样的,对业务有哪些影响?
- 购买GeminiDB Redis 1U*2节点特惠型实例后,业务访问量比较少,但CPU占用率比较高,是什么原因?
- GeminiDB Redis监控面板上key数量下降又恢复至正常数量是什么原因?
- GeminiDB Redis节点CPU偶发冲高,可能是哪些原因
- GeminiDB Redis如何从5.0版本升级到6.2版本
- GeminiDB Redis什么时候进入只读
-
数据库连接
- 如何接入GeminiDB Redis
- 如何使用GeminiDB Redis提供的多个节点IP地址
- GeminiDB Redis提供的ELB的实现方式是怎样的
- 如何创建和连接弹性云服务器
- GeminiDB Redis实例购买成功后是否支持更换VPC
- 绑定了弹性公网IP但是连接不上数据库
- 内网如何访问GeminiDB Redis
- GeminiDB Redis自带的负载均衡地址是否能绑定公网IP?如何通过公网连接GeminiDB Redis实例?
- 设置了安全组,还需要设置负载均衡内网访问控制吗?
- 如何处理客户端连接池报错“Could not get a resource from the pool”
- 常见客户端报错及解决方法
- 备份与恢复
- 区域和可用区
-
数据迁移
- DRS上找不到GeminiDB Redis链路
- 报错ERR the worker queue is full, and the request cannot be excecuted
- 报错ERR the request queue of io thread is full, and the request cannot be excecuted
- 报错 read error, please check source redis log or network
- 报错 slaveping_thread.cc-ThreadMain-90: error: Ping master error
- 同步状态正向迁移速度太慢
- 同步状态正向迁移速度太快,报错:ERR server reply timeout, some responses may lose, but requests have been executed
- 4.0、5.0以及6.2版本的自建Redis能迁移至GeminiDB Redis吗?
- 自建Redis主备、集群实例如何迁移到GeminiDB Redis?
- 为什么阿里云Redis、腾讯云Redis等云服务不能使用DRS进行数据迁移?
- 自建主备Redis,迁移到GeminiDB Redis集群,需要考虑哪些因素?
- 迁移完成后数据量变少了,100GB的数据迁移到GeminiDB Redis只有20-30GB,数据是不是没迁移完?
- 内存加速
- 资源冻结/释放/删除/退订
- GeminiDB Influx接口
-
GeminiDB Cassandra接口
- 产品介绍
- 计费说明
- 快速入门
-
用户指南
- 权限管理
- 购买GeminiDB Cassandra实例
- 实例连接及管理
- 数据迁移
- 实例生命周期管理
- 变更实例
- 同城容灾
- 异地双活
- 数据备份
- 数据恢复
- 参数管理
- 日志与审计
- 查看监控指标与配置告警
- 企业项目
- GeminiDB Cassandra标签管理
- GeminiDB Cassandra用户资源配额
- 最佳实践
- 性能白皮书
- 常见问题
- GeminiDB (兼容DynamoDB API)实例
- HBase协议兼容版实例
- GeminiDB Mongo接口
- 技术白皮书
-
API参考
- 使用前必读
- API概览
- 如何调用API
- 快速入门
-
API v3(推荐)
- 查询API版本
- 接口版本和规格
-
实例管理
- 创建实例
- 删除实例
- 查询实例列表和详情
- 扩容实例存储容量
- 扩容实例的节点数量
- 缩容实例的节点数量
- 获取节点会话列表
- 查询实例节点会话统计信息
- 关闭实例节点会话
- 查询实例可变更规格
- 变更实例规格
- 修改实例的管理员密码
- 修改实例名称
- 变更实例安全组
- 数据库补丁升级
- 批量数据库补丁升级
- 创建冷数据存储
- 扩容冷数据存储
- 绑定/解绑弹性公网IP
- 切换实例SSL开关
- 重启实例
- 设置磁盘自动扩容策略
- 修改数据库端口
- 判断弱密码
- 修改副本集跨网段访问配置
- 删除扩容失败的节点
- 查询创建实例或扩容节点时需要的IP数量
- 查询磁盘自动扩容策略
- 变更实例存储容量
- 查询高危命令
- 修改高危命令
- 查询Redis实例的热key
- 设置Redis禁用命令
- 查询Redis禁用命令
- 删除Redis禁用命令
- 设置实例可维护时间段
- Redis主备切换
- 支持节点的开关机
- 查询GeminiDB Redis实例的大key
- 获取GeminiDB Redis的免密配置
- 支持修改GeminiDB Redis的免密配置
- 查询内存加速映射列表和详情
- 创建内存加速规则
- 解除内存加速映射
- 创建内存加速映射
- 修改内存加速规则
- 查询内存加速规则列表和详情
- 删除内存加速规则
- 开启/关闭实例数据导出
- 开启/关闭秒级监控
- 查询秒级监控配置
- 连接管理
- 备份与恢复
- 参数模板管理
- 管理数据库和账号
- 标签管理
- 日志管理
- 配额管理
- 容灾管理
- 任务管理
- 企业项目管理
- 实例负载均衡管理
- API v3(即将下线)
- 权限策略和授权项
- 附录
- SDK参考
- 场景代码示例
- 视频帮助
- 文档下载
- 通用参考
链接复制成功!
通过exHash实现广告频控业务方案概述
exHash类型是一种支持Field过期的新型数据类型,它在原先的Hash类型基础上进行了扩展:在支持Hash类型的通用功能以外,exHash类型还支持为Field设置过期时间和版本,增强了数据结构的灵活性,从而简化了很多复杂场景下的业务开发工作。
本文以两种常见的场景(频控场景&购物车场景)为例,通过使用GeminiDB Redis接口中的exHash类命令来实现复杂的业务,简化开发难度。
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对应的过期时间。
- 涉及命令如下:
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里。
- 涉及命令如下:
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命令直接设置。
- 涉及命令如下:
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 import os 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实例的密码。 """ # 认证用的用户名和密码直接写到代码中有很大的安全风险,建议在配置文件或者环境变量中存放(密码应密文存放、使用时解密),确保安全 # 本示例以用户名和密码保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量(环境变量名称请根据自身情况进行设置)EXAMPLE_USERNAME_ENV和EXAMPLE_PASSWORD_ENV password = os.getenv('EXAMPLE_PASSWORD_ENV') return redis.Redis(host='***', port=6379, password=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类型作为各种复杂场景的解决方案。未来,GeminiDB Redis接口将持续致力于开发更多好用的企业级特性,帮助客户轻松运维,高效开发。