redis的string字符串是动态字符串,是可以修改的字符串,内部结构的实现类似于java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配
Posted 阿啄debugIT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis的string字符串是动态字符串,是可以修改的字符串,内部结构的实现类似于java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配相关的知识,希望对你有一定的参考价值。
前言
字符串string是Redis最简单的数据结构,它的内部表示就是一个字符数组。
Redis的字符串是动态字符串,是可以修改的字符串,内部结构的实现类似于java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
redis的string内部实现
Redis内部构建了一个简单的动态字符串来存储,数据结构为:
struct SDS {
// 数组容量
T capacity;
// 数组长度
T len;
// 特殊标识位
byte flags;
// 数组内容
byte[] buf;
}
在数据结构中,长度的类型使用的是泛型,而没有直接用int,这是因为当字符串比较短时,可以使用byte和short来表示。
其中,Buf存储的是真正的字符串内容,capacity表示所分配的数组长度,len表示字符串的长度,当数组没有冗余空间,追加内容时会分配新的数组,将旧数组中的内容拷贝出来,再append新的内容。
redis规定字符串的最大长度为512MB,创建字符串时len和capacity一样长,不分配新的冗余空间。
Redis字符串存储结构示意图:
最后一个'\\0'为空字符串,不计算在len长度中,这样做是为了遵循C语言字符串的实现规则。
当Redis字符串长度变大时:
- 判断capacity长度,是否足够存储新增的字符,如果满足,则无需扩容,否则进行扩容;
- 如果len<=1MB,则加倍进行扩容,即capacity*2,然后继续进行判断;
- 如果len>1MB时,则1次只会增加1MB的空闲空间,然后继续进行判断;
- 字符串长度最大扩容到512MB
当Redis字符串进行缩减时,不会立即回收减少的部分,而是会分配给下一个需要内存的程序。
redis的string扩容机制
redis的字符串有两种存储方式,在长度特别短的时候,用emb形式存储,当长度超过44时,使用raw形式存储。这两种存储方式结构如下:
redis的对象头结构
struct RedisObject {
int4 type; // 4bits
int4 encoding; // 4bits
int24 lru; // 24bits
int32 refcount; // 64bits
void *ptr; // 64bits
} robj;
整个RedisObject对象头需占16个字节。
RedisObject = type(4bits)+encoding(4bits)+LRU(24bits)+refcount(32bits)+ptr(64bits)=128bits
SDS的结构
SDS的结构使用的是范型,当字符串比较短时,len和capacity可以使用byte和short表示,
所以最小的对象头大小为content的长度+3,结构如下:
struct SDS {
// 1byte
int8 capacity;
// 1byte
int8 len;
// 1byte
int8 flags;
// 内联数组,长度为 capacity
byte[] buf;
}
SDS = capacity(8bits)+len(8bits)+flags(8bits)+content;
redis为容纳一个完整的 embstr 对象,内存分配器最少会分配 32 字节的空间,如果字符串再稍微长一点,那就是 64 字节的空间。
如果总体超出了 64 字节,Redis 认为它是一个大字符串,不再使用 emdstr 形式存储,而该用 raw 形式。
RedisObject = type(4bits)+encoding(4bits)+LRU(24bits)+refcount(32bits)+ptr(64bits)=16字节;
SDS = capacity(8bits)+len(8bits)+flags(8bits)+Buf=3字节+Buf;
Buf剩余最多的长度只有45(64-19),而Buf中的字符串又是已 \\0结尾,所有 embstr最大的字符串长度为44。
扩容策略
字符串的长度在小于1M时,扩容采用加倍策略,当长度大于1M时,为避免加倍的冗余空间过大,而导致浪费,每次分配只会多1M的冗余空间。
redis的string字符串命令
命令 | 描述 |
SET key value | 设定指定key的值 |
GET key | 获取指定key的值 |
SETRANGE key offset value | 从偏移量offset开始重写value的值。如当前key为hello,SETRANGE key 2 x后的value值为hexlo |
GETRANGE key start end | 返回key中字符串值的下标从start到end的子字符串 |
GETSET key value | 将给定key的值设置为value,并返回key的旧值 |
SETBIT key offset value | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。value只能是0或者1 |
GETBIT ket offset | 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
MSET key1 value1 [key2 value2 ...] | 同时设置多个key-value对 |
MGET key1 [key2...] | 获取所有给定key的值 |
SETEX key seconds value | 设置key的值,并设置key的有效时间为seconds秒 |
SETNX key value | 只有在key不存在时,才设置key的值 |
STRLEN key | 返回key存储的字符串值得长度 |
INCR key | 将key中存储的数字加1,默认从0开始 |
INCRBY key increment | 将key存储的值增加上指定的增量increment |
INCRBYFLOAT key increment | 将key存储的值增加上指定的浮点增量increment |
DECR key | 将key中存储的数字减1,默认从0开始 |
DECRBY key increment | 将key存储的值减少指定的增量increment |
APPEND key value | 如果key存在并且是一个字符串,则将value的值追加到key原来值得后面。如果key不存在,则相当于set操作 |
应用场景
计数器
incr和decr的命令,是将key中存储的数字的值,加1和减1,这2个操作具有原子性,因此可以用来计算。
如:评论数,点赞数,分享数等。
分布式锁
setnx的作用,是当key不存在时,设置值并返回1,当key已经存在,不设置值并返回0,而这个操作也是原子性的,所以可以用来做分布式锁。
返回1表示获得到了锁,返回0表示没有获得到锁;然后使用del删除key来释放锁。
可以同时给key设置一个过期时间,这样当del删除失败时,也可以保证锁能够释放。
缓存
set操作可以存储值,可以将数据对象,进行序列化操作后,存储起来作为缓存使用。
用户签到记录
可以使用位图(byte数组)来记录用户的签到记录,每天的签到记录只占据一个位,365天就是365个位,46个字节就可以完全容纳下。
以上是关于redis的string字符串是动态字符串,是可以修改的字符串,内部结构的实现类似于java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配的主要内容,如果未能解决你的问题,请参考以下文章