技巧💡
我们知道,redis的对象,使用的是redisObject 进行存储。在刚启动的时候,redis会创建一些整数的共享对象池来使用,而不是创建对象。节省内存。与Integer的缓存是一样的思想。
- 创建整数对象范围: 0 ~
OBJ_SHARED_INTEGERS
.OBJ_SHARED_INTEGERS可以配置- 共享对象的,redisObject中的refcout,初始化时,默认设置成最大值(旧版引用计数初始是0)。Interger.max
源码
服务启动时,创建共享池对象
// server.c
void createSharedObjects(void) {
int j;
/* Shared command responses */
shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));
shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
// ....
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
// 初始化对象
shared.integers[j] =
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
shared.integers[j]->encoding = OBJ_ENCODING_INT;
}
}
robj *makeObjectShared(robj *o) {
serverAssert(o->refcount == 1);
// 新版中,引用数默认为最大值
o->refcount = OBJ_SHARED_REFCOUNT;
return o;
}
使用共享池对象限制条件
整数对象共享池与maxmemory和maxmemory-policy设置冲突,原因是:
- LRU算法需要获取对象最后被访问时间, 以便淘汰最长未访问数据, 每个对象最后访问时间存储在redisObject对象的lru字段。 对象共享意味着多个引用共享同一个redisObject, 这时lru字段也会被共享, 导致无法获取每个对象的最后访问时间
- 如果没有设置maxmemory,那么服务器内存耗尽时都不会驱逐键,一般会报oom的错误,所以对象共享池能正常工作
所以:当设置了maxmemory+maxmemory-policy(如volatile-lru,allkeys-lru,volatile-lfu,allkeys-lfu),对象共享池是禁用的
//object.c
// 尝试对string对象进行编码,减少内存占用
robj *tryObjectEncoding(robj *o) {
long value;
sds s = o->ptr;
size_t len;
// 非en
if (!sdsEncodedObject(o)) return o;
// 引用数>0,修改不安全,不编码
if (o->refcount > 1) return o;
len = sdslen(s);
// 如果字符串,能转换成long类型,则尝试转换
if (len <= 20 && 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. */
// 启用了 最大内存并且启用lfu,lru时,就不能使用整数共享对象。避免被回收
if ((server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS)
{
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
if (o->encoding == OBJ_ENCODING_RAW) {
sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
} else if (o->encoding == OBJ_ENCODING_EMBSTR) {
decrRefCount(o);
return createStringObjectFromLongLongForValue(value);
}
}
}
/* 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. */
// 转换成 embstr
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
robj *emb;
if (o->encoding == OBJ_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
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
// 缩减空间大小
trimStringObjectIfNeeded(o);
/* Return the original object. */
return o;
}
为什么只有整数对象共享池?
通用返回等信息对象也是有共享💡
这个说明不太准确,其实在通用返回的错误信息对象,也是有共享的。参考共享对象创建过程
首先整数对象池复用的几率最大, 其次对象共享的一个关键操作就是判断相等性, Redis之所以只有整数对象池, 是因为整数比较算法时间复杂度为O( 1) , 只保留一万个整数为了防止对象池浪费。 如果是字符串判断相等性, 时间复杂度变为O( n) , 特别是长字符串更消耗性能( 浮点数在Redis内部使用字符串存储) 。 对于更复杂的数据结构如hash、 list等, 相等性判断需要O( n2) 。 对于单线程的Redis来说, 这样的开销显然不合理, 因此Redis只保留整数共享对象池