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字符串长度变大时:

  1. 判断capacity长度,是否足够存储新增的字符,如果满足,则无需扩容,否则进行扩容;
  2. 如果len<=1MB,则加倍进行扩容,即capacity*2,然后继续进行判断
  3. 如果len>1MB时,则1次只会增加1MB的空闲空间,然后继续进行判断;
  4. 字符串长度最大扩容到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

128/8=16

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,采用预分配冗余空间的方式来减少内存的频繁分配的主要内容,如果未能解决你的问题,请参考以下文章

Redis-第八章节-应用场景

redis存list(int)如何转换成string

Redis底层数据结构

2 字符串String

Redis---String

Redis的数据结构