redis3.0深入详解(1)

redis3.0中,如果字符串长度小于39,则会使用ebeded string,将robj和字符串分配在一块连续内存中。由于局部性原理,在读取时,robj和字符串内容都会读到cache中,从而只要一次内存读取即可。

// <MM>// 分配一块内存,容纳robj, sds header, 字符串和’\0’// </MM>robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);struct sdshdr *sh = (void*)(o+1);o->type = REDIS_STRING;o->encoding = REDIS_ENCODING_EMBSTR;o->ptr = sh+1;o->refcount = 1;o->lru = LRU_CLOCK();sh->len = len;sh->free = 0;if (ptr) {// <MM>// 拷贝字符串内容// </MM>memcpy(sh->buf,ptr,len);sh->buf[len] = ‘\0’;} else {memset(sh->buf,0,len+1);}return o;

长度限制在39的原因是,redis使用jemalloc,会以64字节为一个内存块进行分配。robj(16字节),sds的头部(16字节)和字符串结尾的’\0’会占用25字节。

redis中所有的key都是字符串类型,所以这个优化会大幅提升redis的cache命中率。在实际使用时,可以尽量将key的大小限制在39字节内,充分利用cache,提升性能。

2. AOF Rewrite

在完成rewrite的最后一个步骤中,redis主进程需要将rewrite期间的增量aof diff追加到aof文件中,这是一个比较重的磁盘io操作,会阻塞事件循环,增加延迟,造成服务抖动。

1)2.8

rewrite的流程:

– 主进程fork子进程,由子进程进行rewrite,主进程继续服务请求。同时主进程会初始化一个aof rewrite buffer,用于收集rewrite期间的增量aof diff。

– 子进程完成rewrite后,主进程会wait子进程并对其进行收割。此时,启动rewrite时的数据集已经生成一份aof文件,接下来主进程需要把aof rewrite buffer追加到该aof文件的最后。

由于rewrite过程比较漫长,累积的aof rewrite buffer会比较大,主进程进行追加写操作,会产生磁盘操作,阻塞事件循环,此时的redis是不能服务的,会影响业务。

2)3.0

父子进程间建立pipe进行通信。在子进程进行rewrite期间,父进程会不断的通过pipe向子进程发送aof diff,子进程会不停的收集到aof rewrite buffer中。当子进程完成rewrite后。会通知父进程停止发送aof diff。然后子进程将收集到的aof rewrite buffer追加到重写后的aof文件的最后。

父进程完成对子进程收割后,会把剩余的rewrite buffer追加到aof文件(这个rewrite buffer相对要小一些)。

改进点:

– 大部分的磁盘操作由子进程完成,父进程只需进行小数据量的磁盘操作

– aof rewrite buffer的输出会被打散到每个命令的处理过程中,降低延迟,不会造成大的抖动

3. LRU近似算法改进

如果配置了maxmemory,在每个命令处理过程中,如果占用内存超过maxmemory,redis会根据LRU算法踢出一些key,以释放内存。redis采用的LRU算法是近似的,并没有维护一个LRU链,以精确的表示先后顺序。

在robj中,由属性lru表示该对象最后被访问的时间。同时,全局变量redisServer.lruclock表示当前的lru时钟,在serverCron(每毫秒执行一次)中会不停更新lruclock。当对象被创建或者被访问时,用lruclock更新该对象的lru属性。

#define REDIS_LRU_BITS 24typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */int refcount;void *ptr;} robj;lru占用24个bit,最大值是2^24 – 1,单位是秒。那么,lru有效范围是0.5年(2^24 / 365 / 86400),当一个key半年没有被访问,其lru会重新归0,而错过踢出。

2)2.8

lruclock的计算方法:

server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) &REDIS_LRU_CLOCK_MAX;REDIS_LRU_CLOCK_RESOLUTION表示lru的精度,设置的是秒。

在freeMemoryIfNeed函数中会进行lru踢出的逻辑。

for (k = 0; k < server.maxmemory_samples; k++) {sds thiskey;long thisval;robj *o;// <MM>// 随机选择一个kv对// </MM>de = dictGetRandomKey(dict);thiskey = dictGetKey(de);/* When policy is volatile-lru we need an additional lookup* to locate the real key, as dict is set to db->expires. */if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)de = dictFind(db->dict, thiskey);o = dictGetVal(de);// <MM>// 获取其lru值// </MM>thisval = estimateObjectIdleTime(o);// <MM>// 选择最久没有访问的key// </MM>/* Higher idle time is better candidate for deletion */if (bestkey == NULL || thisval > bestval) {bestkey = thiskey;bestval = thisval;}}踢出逻辑比较简单,随机选择maxmemory_samples个对象,选择其中lru值最小的作为要踢出的key。maxmemory_samples可以配置,默认是3。

3)3.0

lruclock计算方法:

(mstime()/REDIS_LRU_CLOCK_RESOLUTION) & REDIS_LRU_CLOCK_MAX;REDIS_LRU_CLOCK_RESOLUTION为1000,即精度是毫秒。

为了提升LRU近似算法的准确性,redisDb中增加一个属性eviction_pool,表示一个要踢出的key的候选池。

/* Redis database representation. There are multiple databases identified * by integers from 0 (the default database) up to the max configured * database. The database number is the ‘id’ field in the structure. */typedef struct redisDb {dict *dict;/* The keyspace for this DB */dict *expires;/* Timeout of keys with a timeout set */dict *blocking_keys;/* Keys with clients waiting for data (BLPOP) */dict *ready_keys;/* Blocked keys that received a PUSH */dict *watched_keys;/* WATCHED keys for MULTI/EXEC CAS */struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */int id;/* Database ID */long long avg_ttl;/* Average TTL, just for stats */} redisDb;eviction_pool结构如下,包含一个key和其对应的lru时间。

妩媚动人,让我感受到了大自然的神奇。

redis3.0深入详解(1)

相关文章:

你感兴趣的文章:

标签云: