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是浮点数可能不精确

总结下思路:

  1. 选择依据优先级

    • 首先考虑数据访问模式(整体读写 vs 部分读写)

    • 其次考虑原子操作需求

    • 最后考虑内存效率(Hash对少量字段更高效)

你可能感兴趣的:(java,redis)