Redis数据结构选择策略--String?Hash?怎么选?
背景:
先想下我们要做一个用户砸蛋抽券的活动,然后需要对于不同的数据需要设计不同的Redis数据结构,是String呢?还是Hash?怎么选择?
要知道:不同数据采用不同Redis数据结构的设计是基于数据特性和操作需求做出的合理选择~
先看设计好的:
private static final String COUPON_KEY = "egg_break:coupons"; // 存储奖券库存private static final String PROB_KEY = "egg_break:probabilities"; // 存储奖券概率private static final String DAY_KEY = "egg_break:current_day"; // 当前活动天数private static final String USER_PREFIX = "egg_break:user:"; // 用户数据前缀private static final String LOCK_PREFIX = "egg_break:lock:"; // 锁前缀private static final String DAILY_INIT_KEY = "egg_break:daily_init"; // 新增
然后分析如下:
一、Hash结构的使用场景
1. 奖券库存(COUPON_KEY)
private static final String COUPON_KEY = "egg_break:coupons"; // Hash结构
使用Hash的原因:
就像一张表格:
券类型 | 数量 |
---|---|
5 | 1000 |
7 | 500 |
10 | 200 |
-
键名设计技巧:
-
egg_break:coupons
:-
egg_break
是活动名前缀(防止和其他业务冲突) -
coupons
明确表示存储的是奖券信息
-
-
-
多字段管理:需要存储多种奖券类型及其对应库存(如:"5元券"→100, "10元券"→50)
-
原子操作:相比String存储序列化的JSON,可以单独获取/更新某个Value,即可以直接对特定奖券执行
HINCRBY
增减库存,无需读取整个数据集 -
高效查询:通过
HGET
可以快速获取特定奖券的库存量 -
相比String结构(每个信息一个key,所以需要多个key)而Hash一个key下多个field,可以减少key的数量,节省内存
-
在需要整体获取所有券信息时,String需要多次get,而Hash只需一次hgetall
操作示例:
HSET egg_break:coupons "5元券" 100 "10元券" 50
HINCRBY egg_break:coupons "5元券" -1 # 扣减库存
2. 奖券概率(PROB_KEY)
private static final String PROB_KEY = "egg_break:probabilities"; // Hash结构
使用Hash的原因:
-
关联数据:需要与奖券库存保持相同的键结构(相同的奖券名称作为field)
-
批量获取:可以通过
HGETALL
一次性获取所有奖券及其概率,用于概率计算 -
动态更新:可以单独修改某个奖券的概率而不影响其他数据
另外注意点:
- 使用字符串存储(Redis本身没有浮点类型)
-
为什么不和数量存在一起?
遵循"单一职责原则":一个Hash只负责一件事,要么管数量,要么管概率
二、String结构的使用场景
1. 当前活动天数(DAY_KEY)
private static final String DAY_KEY = "egg_break:current_day"; // String结构
使用String的原因:
-
单一值:只需要存储一个整数值表示当前天数
-
原子递增:可以使用
INCR
命令安全地增加天数 -
简单读取:只需要
GET
命令即可获取当前值
-
为什么用String而不是数字?
Redis命令set
默认就是字符串存储,足够简单
操作示例:
SET egg_break:current_day 1
INCR egg_break:current_day # 天数+1
2. 每日初始数量(DAILY_INIT_KEY)
private static final String DAILY_INIT_KEY = "egg_break:daily_init"; // String结构
使用String的原因:
-
整体性数据:通常作为整体配置读取(如JSON格式的每日初始化设置)
-
一次性设置:每日初始化时整体写入,不需要单独修改其中部分
-
可能的结构:
{ "day1": {"5元券": 100, "10元券": 50}, "day2": {"5元券": 80, "10元券": 70} }
三、数据结构选择决策矩阵
考虑因素 | Hash结构 | String结构 |
---|---|---|
数据复杂度 | 多个键值对的组合 | 单一值或序列化后的复杂对象 |
访问模式 | 需要单独访问或修改部分字段 | 总是整体读写 |
原子操作需求 | 需要字段级原子操作(如HINCRBY) | 需要键级原子操作(如INCR) |
数据关联性 | 需要保持相同field结构的关联数据 | 独立数据项 |
典型命令 | HSET/HGET/HINCRBY/HGETALL | SET/GET/INCR/DECR |
四、其他可能的数据结构选择
1. 用户数据(USER_PREFIX + userId)
当前代码中使用Hash存储用户数据也是合理的:
String userKey = USER_PREFIX + userId; // Hash结构
优势:
-
可以单独更新用户某天的记录(HSET day1 "5元券")
-
可以高效检查某天是否参与(HEXISTS day1)
2. 为什么不用Sorted Set?
虽然Sorted Set适合带权重的场景,但在本系统中:
-
奖券概率只在抽奖时一次性使用,不需要持续排序
-
库存管理需要精确的数值操作,Sorted Set的score是浮点数可能不精确
总结下思路:
-
选择依据优先级:
-
首先考虑数据访问模式(整体读写 vs 部分读写)
-
其次考虑原子操作需求
-
最后考虑内存效率(Hash对少量字段更高效)
-