Redis对象通用对象
由于所有的redis对象都包含此结构以便redis管理对象的内存、创建、销毁等等,所以这里需要先介绍此对象结构。
// 所有redis结构都有这个头:server.h
#define LRU_BITS 24
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
此对象可以表示所有的redis对象,它包含:
type:对象类型
encoding:数据编码类型
lru:数据淘汰记录的24bits时间戳
refcount:引用计数数量(低版本的redis中使用对象共享策略,但是由于后来redis异步线程删除过期等数据带来的诸多问题,作者放弃了这一做法)
字符串string
Redis中实现字符串并没有使用C语言的方式实现,而是自己定义的简单动态字符串(simple dynamic string,SDS),它的实现具有动态、数据可修改、预分配空间的特点。
为什么不用C字符串?
- 使用C库获取字符长度需要遍历一遍,时间复杂度O(n),SDS使用len即可。
- Redis作为缓存组件,如果使用普通char[]字符串数组,使用栈资源有限所以必须使用char*,这样每次涉及到字符串追加都需要额外申请内存,SDS使用预分配方式减少内存频繁分配。
常用操作
set key value [EX seconds] [PX milliseconds] [NX|XX]
get key
mset key value [key value ...]
mget key [key ...]
setnx key value:如果不存在则set,否则不做操作
setex key seconds value:设置并且过期时间为seconds秒
xpire key seconds:设置过期时间为seconds秒
incr key:自增数字值类型的key
incrby key increment:自增数值increment的数字值类型的key
append key value:字符串追加操作
getrange key start end:获取一段字符串从start-end
setrange key offset value:设置一段字符串从offset开始覆盖value大小
数据结构
// 这里SDS有5种header类型,主要还是为了不同长度的字符串使用不同的头以便节省内存:sds.h
// len和alloc字段从8bit到64bit
struct __attribute__((__packed__)) sdshdr5 { // 暂未使用
struct __attribute__((__packed__)) sdshdr8 {
struct __attribute__((__packed__)) sdshdr16 {
struct __attribute__((__packed__)) sdshdr32 {
struct __attribute__((__packed__)) sdshdr64 {
uint64_t len; /* buf数组已使用的长度 */
uint64_t alloc; /* 分配的buf数组长度,不包括偷和空字符结尾 */
unsigned char flags; /* 低3位表示类型,另外5位没有使用 */
char buf[];
};
// 标志位定义
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
存储方式
# 文件:object.c
/* Create a string object with EMBSTR encoding if it is smaller than
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
* used.
*
* The current limit of 44 is chosen so that the biggest string object
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
// 如果len小于44则使用embstr方式编码,否则使用raw方式编码
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
当字符串的长度小于44字节使用embstr
方式存储,否则使用raw
方式存储,可以使用debug
查看存储数据类型
127.0.0.1:6379> debug object g1
Value at:0x7f4cd72a43c8 refcount:1 encoding:embstr serializedlength:4 lru:15045668 lru_seconds_idle:2
为什么是44字节
jemalloc/tcmalloc
等内存分配器都可以2、4、8、16、32、64的单位分配内存大小,如果大小超过64字节则将被认为是大内存。
RedisObject:16字节
SDS:3字节
64字节 - 19字节 = 45字节(其中\0为redis字符串的结尾)
embstr
字符串长度和cpu cache line
保持一致可以更快加载、重用CPU缓存
扩容策略
字符串长度小于1M使用加倍扩容方式,如果大小超过1M则扩容时以1M大小扩容。但是如果字符串总长度不能超过512M。Redis Data Types A String value can be at max 512 Megabytes in length.
// 扩容的源码文件:sds.c
#define SDS_MAX_PREALLOC (1024*1024)
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;