Redis作為一款緩衝軟體,在Redis的源碼中,處處體現著節省記憶體的思想,下面先從3個方面對Redis的記憶體節省策略做一個分享。(以下代碼為Redis 3.0.5版本)
1.redisObject 是Redis中最常見的一個資料結構,Redis儲存的所有的鍵和值都是一個redisObject, 因此必須盡肯能節省redisObject的大小。由於Redis 資料類型只有我們所常見的5種,而Redis的資料編碼類別型總共8種,所以Redis中分別採用4Bit來表示,兩者相加正好1個位元組。
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr;} robj; 2.Redis對於經常使用變數,將其設為全域變數,並採用引用計數的方法。本策略主要目的是為了避免重複的建立,提高效能。只有當足夠數量的資料值在0-9999時才會發揮節省記憶體的作用。如代碼所示,在結構體中有一個變數integers, 裡面儲存著資料0-9999的對象指標。
struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space, *colon, *nullbulk, *nullmultibulk, *queued, *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, *busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk, *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop, *lpush, *emptyscan, *minstring, *maxstring, *select[REDIS_SHARED_SELECT_CMDS], *integers[REDIS_SHARED_INTEGERS], *mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */ *bulkhdr[REDIS_SHARED_BULKHDR_LEN]; /* "$<value>\r\n" */};
3.Redis的8種編碼類別型,REDIS_ENCODING_INT, REDIS_ENCODING_EMBSTR, REDIS_ENCODING_RAW是基礎,對於其他幾種編碼類別型,除REDIS_ENCODING_INTSET外,其所對應Object Storage Service的資訊對象均是以前三種編碼類別型為基礎,產生一個個具體的對象。如Redis鏈表中,每個節點儲存的對象,其編碼類別型可以為REDIS_ENCODING_INT, REDIS_ENCODING_EMBSTR, REDIS_ENCODING_RAW 中的任意一種。
/* Try to encode a string object in order to save space */robj *tryObjectEncoding(robj *o) { long value; sds s = o->ptr; size_t len; /* Make sure this is a string object, the only type we encode * in this function. Other types use encoded memory efficient * representations but are handled by the commands implementing * the type. */ redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); /* We try some specialized encoding only for objects that are * RAW or EMBSTR encoded, in other words objects that are still * in represented by an actually array of chars. */ if (!sdsEncodedObject(o)) return o; /* It's not safe to encode shared objects: shared objects can be shared * everywhere in the "object space" of Redis and may end in places where * they are not handled. We handle them only as values in the keyspace. */ if (o->refcount > 1) return o; /* Check if we can represent this string as a long integer. * Note that we are sure that a string larger than 21 chars is not * representable as a 32 nor 64 bit integer. */ len = sdslen(s); if (len <= 21 && string2l(s,len,&value)) { /* This object is encodable as a long. Try to use a shared object. * Note that we avoid using shared integers when maxmemory is used * because every object needs to have a private LRU field for the LRU * algorithm to work well. */ if ((server.maxmemory == 0 || (server.maxmemory_policy != REDIS_MAXMEMORY_VOLATILE_LRU && server.maxmemory_policy != REDIS_MAXMEMORY_ALLKEYS_LRU)) && value >= 0 && value < REDIS_SHARED_INTEGERS) { decrRefCount(o); incrRefCount(shared.integers[value]); return shared.integers[value]; } else { if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr); o->encoding = REDIS_ENCODING_INT; o->ptr = (void*) value; return o; } } /* If the string is small and is still RAW encoded, * try the EMBSTR encoding which is more efficient. * In this representation the object and the SDS string are allocated * in the same chunk of memory to save space and cache misses. */ if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) { robj *emb; if (o->encoding == REDIS_ENCODING_EMBSTR) return o; emb = createEmbeddedStringObject(s,sdslen(s)); decrRefCount(o); return emb; } /* We can't encode the object... * * Do the last try, and at least optimize the SDS string inside * the string object to require little space, in case there * is more than 10% of free space at the end of the SDS string. * * We do that only for relatively large strings as this branch * is only entered if the length of the string is greater than * REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */ if (o->encoding == REDIS_ENCODING_RAW && sdsavail(s) > len/10) { o->ptr = sdsRemoveFreeSpace(o->ptr); } /* Return the original object. */ return o;}