首页>软件资讯>常见问题

常见问题

Redis避坑指南

发布时间:2025-11-10 08:50:46人气:4

Redis对象存储选择:字符串 vs 哈希

对象存储选择.png

场景模拟假设我们要存储用户信息:{

  "id": 1001,

  "name": "张三",

  "age": 28,

  "vip": true

}方案1:字符串存储SET user:1001 '{"id":1001,"name":"张三","age":28,"vip":true}'方案2:哈希存储HSET user:1001 id 1001 name 张三 age 28 vip 1用一个电商用户系统的场景,对比两种存储方案的差异代码案例对比用户对象定义public class User {

    private int id;

    private String name;

    private int age;

    private boolean vip;

    // 省略构造函数和getter/setter

    // JSON序列化方法

    public String toJson() {

        return new Gson().toJson(this);

    }

    

    // JSON反序列化方法

    public static User fromJson(String json) {

        return new Gson().fromJson(json, User.class);

    }

}方案1:字符串存储(JSON序列化)public class StringStorageDemo {

    private static final Jedis jedis = new Jedis("localhost");

    // 存储用户

    public void saveUser(User user) {

        jedis.set("user:" + user.getId(), user.toJson());

    }

    // 获取用户(需要反序列化)

    public User getUser(int id) {

        String json = jedis.get("user:" + id);

        return User.fromJson(json);

    }

    // 更新年龄(需要完整读写)

    public void updateAge(int id, int newAge) throws Exception {

        // 非原子操作!

        String key = "user:" + id;

        User user = User.fromJson(jedis.get(key));

        user.setAge(newAge);

        jedis.set(key, user.toJson());

    }

}方案2:哈希存储(字段级存储)public class HashStorageDemo {

    private static final Jedis jedis = new Jedis("localhost");

    // 将User对象转换为Map

    private Map<String, String> toMap(User user) {

        Map<String, String> map = new HashMap<>();

        map.put("id", String.valueOf(user.getId()));

        map.put("name", user.getName());

        map.put("age", String.valueOf(user.getAge()));

        map.put("vip", user.isVip() ? "1" : "0");

        return map;

    }

    // 存储用户(批量操作)

    public void saveUser(User user) {

        jedis.hset("user:" + user.getId(), toMap(user));

    }

    // 获取用户(自动转换)

    public User getUser(int id) {

        Map<String, String> map = jedis.hgetAll("user:" + id);

        return new User(

            Integer.parseInt(map.get("id")),

            map.get("name"),

            Integer.parseInt(map.get("age")),

            map.get("vip").equals("1")

        );

    }

    // 更新年龄(直接操作字段)

    public void updateAge(int id, int newAge) {

        jedis.hset("user:" + id, "age", String.valueOf(newAge));

    }

}性能测试建议使用redis-benchmark测试对比:# 测试10万次写操作

redis-benchmark -n 100000 -t set,hset

压测数据参考.png

压测数据参考(10000次操作)

什么时候用字符串?

• 需要设置过期时间的简单值

• 计数器等单值场景

• 需要存储序列化二进制数据

为什么推荐哈希存储?

1. 内存优化(内存警察)

Redis的哈希表采用特殊内存结构:


• 使用ziplist压缩列表(字段数<512且值<64字节时)

• 自动转换为hashtable当数据量增大 内存对比(使用redis-rdb-tools分析):

• 字符串存储:约120字节

• 哈希存储:约65字节(节省45%+)

2. 操作效率(速度狂魔)

操作效率.png

3. 并发安全(原子卫士)# 非原子操作示例(字符串方案)

GET user:1001 → 修改age → SET user:1001

# 原子操作示例(哈希方案)

HSET user:1001 age 294. 扩展灵活(未来先知)当需要新增字段时:# 哈希方案直接追加

HSET user:1001 city 北京

# 字符串方案需要完整替换

GET → 修改 → SET通过这个对比,可以明显看出哈希存储在对象存储场景下的综合优势。就像整理行李箱,哈希存储是「分格收纳」,而字符串存储是「胡乱塞满」,哪个更高效一目了然!

关键差异图解

关键差异图解.png

记忆增强流程图

记忆增强流程图.png

Java开发最佳实践使用Hash的三大场景• 需要频繁修改部分字段(如用户资料)• 对象字段超过3个(内存优势显现)• 需要原子性字段操作使用String的例外情况// 适合存储整个对象的情况

void saveOrderSnapshot(Order order) {

    // 订单快照需要完整存储

    jedis.set("order:"+order.getId(), order.toJson());

}性能优化技巧// 批量操作示例(比逐条HSET快10倍+)

public void batchUpdate(Map<Integer, User> users) {

    Pipeline pipeline = jedis.pipelined();

    users.forEach((id, user) -> {

        pipeline.hset("user:"+id, toMap(user));

    });

    pipeline.sync();

}总结选择策略就像整理衣柜:• 字符串存储: 把衣服胡乱堆进箱子(适合短期存储/不常修改)• 哈希存储: 使用分格收纳盒整理(适合长期使用/高频修改)


上一条:Redis 过期键删除策略全景实战

下一条:没有了!