redis 中的 String 数据结构

简介

String类型在 redis 中最常见的数据类型,是二进制安全的,可以存放字符串、数值、json、图像数据,value最大数据量是 512M

二进制安全:Redis 的 string 类型是二进制安全的。所谓二进制安全,是指无论输入的数据是什么字节序列,Redis 都能准确无误地存储和返回,不会因为数据中可能包含特殊字符、编码格式等问题而对数据进行错误处理。 主要原因是SDS 的存储不像 C 字符串那样以 ' \0 ' 作为字符串结束的标识。

常用命令

set < key>< value>:添加键值对
nx:当数据库中key不存在时,可以将key-value添加到数据库
xx: 当数据库key存在时,可以将key-value添加到数据库,与nx参数互斥
ex: 设置key-value添加到数据库,并设置key的超时时间(以秒钟为单位)
px:设置key-value添加到数据库,并设置key的超时时间(以豪秒钟为单位),与ex互斥
get < key>查询对应键值
append < key>< value>:将给定的值追加到key的末尾
strlen < key>:获取值的长度
setnx < key>< value>:只有在key不存在时,设置key-value加入到数据库
setex < key> < timeout>< value>:添加键值对,同时设置过期时间(以秒为单位)
incr < key>:将key中存储的数字加1处理,只能对数字值操作。如果是空,值为1
decr < key>:将key中存储的数字减1处理,只能对数字值操作。如果是空,值为1
incrby < key>< increment>:将key中存储的数字值增加指定步长的数值,如果是空,值为步长。
(具有原子性)
decrby < key>< decrement>: 将key中存储的数字值减少指定步长的数值,如果是空,值为步长。
(具有原子性)
mset < key1>< value1>[< key2>< value2>...]:同时设置1个或多个key-value值
mget < key1>[< key2>...]:同时获取1个或多个value
msetnx < key1>< value1>[< key2>< value2>...]:当所有给定的key都不存在时,同时设置1个或
多个key-value值(具有原子性)
getrange/substr < key>< start>< end> 将给定key,获取从start(包含)到end(包含)的值
setrange < key>< offset>< value>:从偏移量offset开始,用value去覆盖key中存储的字符串值
getset< key>< value>: 对给定的key设置新值,同时返回旧值。如果key不存在,则添加一个
key-value值

SDS

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;      // 已使用的字节数
    uint8_t alloc;    // 分配的总字节数(包含'\0')
    char buf[];       // 柔性数组,存放实际的字符串内容
};
  • len:记录了当前字符串的长度,这样获取字符串长度的操作时间复杂度为 O(1),而 C 字符串获取长度需要遍历到 '\0',时间复杂度为 O(N)
  • alloc:表示分配给 buf 数组的总字节数(包括字符串结束符 '\0' 占用的 1 个字节),这有助于内存管理,在需要扩展字符串时,可以预先判断是否有足够空间,减少频繁的内存重新分配。
  • buf:是一个柔性数组,用于存放实际的字符串内容,并且总是以 '\0' 结尾,这样可以兼容部分 C 字符串函数。

SDS的内存分配与管理

  • 空间预分配:当 SDS 进行扩展操作时,如果扩展后长度小于 1MB,Redis 会分配两倍于所需大小的空间。例如,若原 SDS 长度为 10 字节,扩展后需 20 字节,那么实际会分配 40 字节的空间。如果扩展后长度大于等于 1MB,会额外分配 1MB 的空间。这样做可以减少频繁的内存重新分配操作,提升性能。
  • 惰性空间释放:当 SDS 缩短时,Redis 并不会立即释放多余的空间,而是将这些空间保留在 SDS 中,等待后续可能的扩展操作。例如,从一个长度为 100 的 SDS 中删除部分内容使其长度变为 50,此时不会释放剩余 50 字节的空间,而是将 alloc 调整为 100,len 调整为 50,下次有扩展需求时可直接使用这些空间。

SDS与 c 字符串的区别及优势

  • 获取长度效率:如前所述,C 字符串获取长度需遍历到 '\0',时间复杂度为 O(N),而 SDS 通过 len 字段直接获取,时间复杂度为 O(1)。在频繁获取字符串长度的场景下,SDS 性能更优。
  • 缓冲区溢出:C 字符串在进行拼接等操作时,如果没有预先分配足够空间,很容易导致缓冲区溢出。而 SDS 在进行修改操作时,会先检查空间是否足够,若不足则会进行空间扩展,从而避免缓冲区溢出问题。
  • 内存分配次数:由于 SDS 的空间预分配和惰性空间释放策略,相较于 C 字符串频繁的内存分配和释放,SDS 能显著减少内存分配次数,提升性能并降低内存碎片产生的概率。

应用场景

单值缓存 set key value

get key

对象缓存

set stu:001 value(json)

mset stu:001:name zhangsan stu:001:age 18 stu:001:gender 男

mget stu:001:name stu:001:age

分布式锁

setnx key:001 true //返回1代表加锁成功

setnx key:001 true //返回0代表加锁失败

//.....业务操作

del key:001 //执行完业务释放锁

set key:001 true ex 20 nx //防止程序意外终止导致死锁

计数器

incr article:read:1001

//统计文章阅读数量

分布式系统全局序列号

incrby orderid 100

//批量生成序列号

package com.pgs.redisstring;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.io.*;
import java.util.Base64;

public class JedisStringOperations {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String LOCK_KEY = "distributed_lock";
    private static final String COUNTER_KEY = "counter";
    private static final String SEQUENCE_KEY = "global_sequence";

    public static void main(String[] args) {
        Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);

        // 单值缓存
        singleValueCache(jedis);

        // 对象缓存
        objectCache(jedis);

        // 分布式锁
        distributedLock(jedis);

        // 计数器
        counter(jedis);

        // 分布式系统全局序列号
        globalSequence(jedis);

        jedis.close();
    }

    // 单值缓存
    public static void singleValueCache(Jedis jedis) {
        String key = "single_value_key";
        String value = "single_value";
        jedis.set(key, value);
        System.out.println("单值缓存:" + jedis.get(key));
    }

    // 对象缓存
    public static void objectCache(Jedis jedis) {
        String key = "object_key";
        User user = new User("John", 30);
        String serializedUser = serialize(user);
        jedis.set(key, serializedUser);
        User deserializedUser = (User) deserialize(jedis.get(key));
        System.out.println("对象缓存 - 姓名:" + deserializedUser.getName() + ", 年龄:" + deserializedUser.getAge());
    }

    // 分布式锁
    public static void distributedLock(Jedis jedis) {
        SetParams setParams = new SetParams();
        setParams.nx().ex(10);
        String result = jedis.set(LOCK_KEY, "locked", setParams);
        if ("OK".equals(result)) {
            try {
                System.out.println("获取到分布式锁,执行临界区代码...");
            } finally {
                jedis.del(LOCK_KEY);
                System.out.println("释放分布式锁");
            }
        } else {
            System.out.println("未获取到分布式锁");
        }
    }

    // 计数器
    public static void counter(Jedis jedis) {
        long count = jedis.incr(COUNTER_KEY);
        System.out.println("计数器值:" + count);
    }

    // 分布式系统全局序列号
    public static void globalSequence(Jedis jedis) {
        long sequence = jedis.incr(SEQUENCE_KEY);
        System.out.println("分布式系统全局序列号:" + sequence);
    }

    // 序列化对象
    public static String serialize(Object obj) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            return Base64.getEncoder().encodeToString(bos.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 反序列化对象
    public static Object deserialize(String str) {
        try {
            byte[] data = Base64.getDecoder().decode(str);
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
            return ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值