memcache的item key和序列化
一个完整的item长度是键长+值长+后缀长+item结构大小(32字节),item操作就是根据这个长度来计算slab的classid的。
+---------------------------------------+
| key-value | cas | suffix | item head |
+---------------------------------------+
其中suffix的格式是 flag vlen (flag即it_flag,vlen是value的长度;
key默认最大长度为250,需要修改源码才能扩展;
#define KEY_MAX_LENGTH 250
Item最大长度默认为1M,可通过启动选项-I修改;
-I Override the size of each slab page. Adjusts max item size (default: 1mb, min: 1k, max: 128m)
MC通过hash表维护item的查找,添加以及删除等操作,以查找为例,其API如下:
item *assoc_find(const char *key, const size_t nkey) {
//采用hash算法将传入的key转换,然后从hash table中查找;
uint32_t hv = hash(key, nkey, 0);
item *it;
unsigned int oldbucket;
//倘若hash表正在扩容,须判断此时hash bucket的是在old还是在new table;通过 hv & hashmask(hashpower - 1) 或者hv & hashmask(hashpower)找出对应的hash table下标,
if (expanding && (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
{
it = old_hashtable[oldbucket];
} else {
it = primary_hashtable[hv & hashmask(hashpower)];
}
item *ret = NULL;
int depth = 0;
//为应对hash冲突,每个hash bucket都有一个链表,通过遍历该链表确定要找到的key
while (it) {
if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
ret = it;
break;
}
it = it->h_next;
++depth;
}
MEMCACHED_ASSOC_FIND(key, nkey, depth);
//向客户端返回item
return ret;
}
注:hash表默认分配2^16个bucket,当item数量 > bucket*1.5时扩容,每次扩容为原来的2倍;先申请相应内存,然后启动线程同步原hash表中数据;
MC只维护了hash table和slab,因此没有内置方法来遍历其下所有item,可通过stats命令来间接实现。
来源http://www.darkcoding.net/software/memcached-list-all-keys/
1 登录MC,telnet 127.0.0.1 11211
2 查看所有slab信息
stats items
STAT items:3:number 1
STAT items:3:age 498
STAT items:22:number 1
STAT items:22:age 498
END
注:这里的3和22为slab id;
3 依次列举各个slab,将100替换为0则表示列举所有key
stats cachedump 3 100
ITEM views.decorators.cache.cache_header..cc7d9 [6 b; 1256056128 s]
END
stats cachedump 22 100
ITEM views.decorators.cache.cache_page..8427e [7736 b; 1256056128 s]
END
现在可通过get key命令获取每个key对应的item内容。
Key的选择
应该根据业务类型而定,除了常见的数据命名,还可以选择如下方式:
1 封装SQL作为key
下面这个例子就是通过Cache缓存查询数据库结果的场景,把sql+userId作为一个key,相同用户第二次查询时就可以从缓存中直接提取:
sql1 = "SELECT * FROM user WHERE user_id = ?"
key = ‘SQL:‘ . user_id . ‘:‘ . md5sum(sql1)
if (defined result = memcli:get(key)) {
return result
}
else {
handler = run_sql(sql1, user_id)
t[info] = handler:turn_into_an_array
memcli:set(key, t, 5 * 60)
return t
}
2 Get-By-Group-Key
很多客户端在处理multiget多服务器请求时采用串行方式,会导致性能严重下降,解决办法:保证Multiget中的键只出现在一台服务器。
譬如,用户名字(user:foo:name),用户年龄(user:foo:age)等数据在散列到多台服务器上时,不应按照完整的键名(user:foo:name和user:foo:age)来散列的,而应按照特殊的键(foo)来散列的,这样就保证了相关的键只出现在一台服务器上。
PHP的memcache客户端提供
Memcached::getMultiByKey — Retrieve multiple items from a specific server
Memcached::setMultiByKey — Store multiple items on a specific server
来源:http://blog.51yip.com/php/729.html
序列化
序列化 (Serialization)将对象转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
除了string和int/long,memcache还可以存储object类型,但是存储前需要将其先进行序列化,读取时再进行反序列化;
MC客户端默认自动开启了序列化和反序列化,也可以在set前使用marshal或其他工具进行序列化;
注:由于不同的Client实现的序列化方式不同,所以如果不同的语言使用同一个memcached来存取数据,可能会造成数据不一致的问题。
分别以python和java为例,
Python的客户端工具memcache.py
if isinstance(val, str):
pass
elif isinstance(val, int):
flags |= Client._FLAG_INTEGER
val = "%d" % val
# force no attempt to compress this silly string.
min_compress_len = 0
elif isinstance(val, long):
flags |= Client._FLAG_LONG
val = "%d" % val
# force no attempt to compress this silly string.
min_compress_len = 0
else:
flags |= Client._FLAG_PICKLE
file = StringIO()
pickler = self.pickler(file, protocol=self.pickleProtocol)
if self.persistent_id:
pickler.persistent_id = self.persistent_id
pickler.dump(val)
val = file.getvalue()
JAVA 的spymemcached client
byte[] b=null;
int flags=0;
if(o instanceof String) {
b=encodeString((String)o);
} else if(o instanceof Long) {
b=tu.encodeLong((Long)o);
flags |= SPECIAL_LONG;
…
} else {
b=serialize(o);
flags |= SERIALIZED;
}
来源:http://www.tech126.com/memcached-client-serialize/
有时MC客户端自带的序列化算法可能不够高效,导致MC耗用更多的内存来存储,此时可以选择第三方工具来封装客户端序列化机制,譬如google的protobuf;
如下面这个例子http://my.oschina.net/pzh0819/blog/110460
登录MC通过stat slabs命令查看slab运行情况,大多数item都存储在STAT 15,且chunk_size为2320(即2K),但是业务数据只是个Element对象,转化为xml长度仅仅只有700字节,从这可以看出memcache(客户端版本:net.spy memcached-2.4.2)客户端自带的序列化使存储的数据翻了将近3倍。通过将Element对象转为xml字符串存储到MC,缓存由以前的6G+减小到当前的2G(数据条数:300W+)。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。