若只如如初见-Redis字符串
String 数据结构是简单的 key-value 类型,value 不仅可以是 String,也可以是数字(当数字类型用 Long 可以表示的时候encoding 就是整型,其他都存储在 sdshdr 当做字符串)。
使用 Strings 类型,可以完全实现目前 Memcached 的功能,并且效率更高。
还可以享受 Redis 的定时持久化(可以选择 RDB 模式或者 AOF 模式),操作日志及 Replication 等功能。除了提供与 Memcached 一样的 get、set、incr、decr 等操作外,Redis 还提供了下面一些操作:
- LEN niushuai:O(1)获取字符串长度
- APPEND niushuai redis:往字符串 append 内容,而且采用智能分配内存(每次2倍)
- 设置和获取字符串的某一段内容
- 设置及获取字符串的某一位(bit)
- 批量设置一系列字符串的内容
- 原子计数器
- GETSET 命令的妙用,请于清空旧值的同时设置一个新值,配合原子计数器使用
- …
string使用场景一般是存储简单的键值类型。比如用户信息,登录信息,配置信息等。
还有一种用得比较多的是string的incr/decr操作,即自减/自增操作。调用它是原子性的,无论调用多少次,都一一计算成功。例如需要增减库存的操作。
尽管string的value可以存储很大,甚至500多MB的容量。但是在性能上来说,我们尽量存储value的值不要过1MB。
千呼万唤始出来-Redis中的字符串数据结构
SDS结构
redis中的字符串是可以修改的字符串,又叫SDS。Simple Dynamic String。在内存中底层是一个带有长度信息的数组。
Redis规定字符串的长度不得超过512MB。创建字符串时,len和capacity一样长,不多分配冗余空间。因为绝大部分场景,都不使用append操作来动态修改字符串。
struct SDS<T> {
T capacity; // 数组分配的空间 1byte
T len; // 字符串实际的长度 1byte
byte flags; // 标志 1byte
byte[] content; // 数组内容
}
不同的存储方式
长度 <= 44 byte,使用embstr形式存储。
长度 > 44 byte,使用raw方式存储。
Redis的对象头结构
struct RedisObject {
int4 type; // 4 bits
int4 encoding; // 4 bits
int24 lru; // 24 bits
int32 refcount; // 4 bytes
void *ptr; // 8 bytes,64位系统
}
embstr的存储方式,是将RedisObject对象头结构与SDS对象连续存储在一起,使用malloc方法一次分配。而raw则需要使用两次malloc分配内存,两个对象头在内存地址上一般是不连续的。
如果字符串的整体大小超过64字节,那么Redis认为这是一个大字符串,使用raw存储。
Redis的字符串扩容策略
字Redis的字符串长度在小于1MB之前,扩容采用加倍策略。
当字符串长度超过1MB之后,避免加倍后的冗余空间过大导致浪费,每次扩容申请1MB大小的冗余空间。
疑难杂症
Redis的embstr与raw存储形式为何划分界限是44个字节?
字符串整体超过64字节。redis就认为是一个大字符串。不再适合使用emdstr存储。使用raw存储。
由于Redis对象头 + SDS 结构头 + 字符串结尾符NULL 恰好是20bytes。64-20 = 40 bytes。
SDS结构体为何使用泛型?
当字符串比较短的时候,len和capacity可以使用byte与short表示。Redis为了对内存的极致优化,
不同长度的字符串,使用不同的结构体表示。
什么情况下,Redis的字符串需要使用append操作?
首先我们来看下Redis的Append命令。Redis Append 命令用于为指定的 key 追加值。
如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。
如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样。
场景模式:节拍序列(Time series)
APPEND 命令可以用来连接一系列固定长度的样例,与使用列表相比这样更加紧凑. 通常会用来记录节拍序列. 每收到一个新的节拍样例就可以这样记录:
APPEND timeseries “fixed-size sample”
在节拍序列里, 可以很容易地访问序列中的每个元素:
STRLEN 可以用来计算样例个数.
GETRANGE 允许随机访问序列中的各个元素. 如果序列中有明确的节拍信息, 在Redis 2.6中就可以使用GETRANGE配合Lua脚本来实现一个二分查找算法.
SETRANGE 可以用来覆写已有的节拍序列.
该模式的局限在于只能做追加操作. Redis目前缺少剪裁字符串的命令, 所以无法方便地把序列剪裁成指定的尺寸. 但是, 节拍序列在空间占用上效率极好.
使用定长字符串进行温度采样的例子(在实际使用时,采用二进制格式会更好).
redis> APPEND ts “0043”
(integer) 4
redis> APPEND ts “0035”
(integer) 8
redis> GETRANGE ts 0 3
“0043”
redis> GETRANGE ts 4 7
“0035”
参考链接
- https://www.redis.net.cn/order/3558.html
- 《Redis深度历险》