Redis 如何封装成一个list,然后hset
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 如何封装成一个list,然后hset相关的知识,希望对你有一定的参考价值。
参考技术A Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
Redis:hash/hset/hget 命令源码解析
Redis作为nosql数据库,kv string型数据的支持是最基础的,但是如果仅有kv的操作,也不至于有redis的成功。(memcache就是个例子)
Redis除了string, 还有hash,list,set,zset。
所以,我们就来看看hash的相关操作实现吧。
首先,我们从作用上理解hash存在的意义:Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。从另一个方面来说是,hash可以聚合很多类似的属性,这是string中难以实现的。
所以,总体来说,hash的命令与string的命令差不太多。其操作手册如下:
1> hdel 命令:删除一个或多个哈希表字段
格式:HDEL key field2 [field2]
返回值:被成功删除字段的数量,不包括被忽略的字段。2> hexists 命令:查看哈希表 key 中,指定的字段是否存在
格式:HEXISTS key field
返回值:如果哈希表含有给定字段,返回 1 。 如果哈希表不含有给定字段,或 key 不存在,返回 0 。3> hget 命令:获取存储在哈希表中指定字段的值
格式:HGET key field
返回值:返回给定字段的值。如果给定的字段或 key 不存在时,返回 nil 。4> hgetall 命令:获取在哈希表中指定 key 的所有字段和值
格式:HGETALL key
返回值:以列表形式返回哈希表的字段及字段值。 若 key 不存在,返回空列表。5> hincrby 命令:为哈希表 key 中的指定字段的整数值加上增量 increment
格式:HINCRBY key field increment
返回值:执行 HINCRBY 命令之后,哈希表中字段的值。6> hincrbyfloat 命令:为哈希表 key 中的指定字段的浮点数值加上增量 increment
格式:HINCRBYFLOAT key field increment
返回值:执行 Hincrbyfloat 命令之后,哈希表中字段的值。7> hkeys 命令:获取所有哈希表中的字段
格式:HKEYS key
返回值:包含哈希表中所有字段的列表。 当 key 不存在时,返回一个空列表。8> hlen 命令:获取哈希表中字段的数量
格式:HLEN key
返回值:哈希表中字段的数量。 当 key 不存在时,返回 0 。9> hmget 命令:获取所有给定字段的值
格式:HMGET key field1 [field2]
返回值:一个包含多个给定字段关联值的表,表值的排列顺序和指定字段的请求顺序一样。10> hmset 命令:同时将多个 field-value (域-值)对设置到哈希表 key 中
格式:HMSET key field1 value1 [field2 value2 ]
返回值:如果命令执行成功,返回 OK 。11> hset 命令:将哈希表 key 中的字段 field 的值设为 value
格式:HSET key field value
返回值:如果字段是哈希表中的一个新建字段,并且值设置成功,返回 1 。 如果哈希表中域字段已经存在且旧值已被新值覆盖,返回 0 。12> hsetnx 命令:只有在字段 field 不存在时,设置哈希表字段的值
格式:HSETNX key field value
返回值:设置成功,返回 1 。 如果给定字段已经存在且没有操作被执行,返回 0 。13> hvals 命令:获取哈希表中所有值
格式:HVALS key
返回值:一个包含哈希表中所有值的表。 当 key 不存在时,返回一个空表。14> hscan 命令:迭代哈希表中的键值对
格式:HSCAN key cursor [MATCH pattern] [COUNT count]
其中,有的是单kv操作有的是指量操作,有的是写操作有的是读操作。从实现上看,大体上很多命令是类似的:
比如: hset/hmset/hincrbyXXX 可以是一类的
比如:hget/hgetall/hexists/hkeys/hmget 可以是一类
注意:以上分法仅是为了让我们看清本质,对实际使用并无实际参考意义。
所以,我们就挑几个方法来解析下 hash 的操作实现吧。
零、hash数据结构
hash相关的命令定义如下:
{"hset",hsetCommand,4,"wmF",0,NULL,1,1,1,0,0}, {"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0}, {"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0}, {"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0}, {"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0}, {"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0}, {"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0}, {"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0}, {"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0}, {"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0}, {"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0}, {"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0}, {"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0}, {"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0}, {"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
ziplist 数据结构
typedef struct zlentry { unsigned int prevrawlensize, prevrawlen; unsigned int lensize, len; unsigned int headersize; unsigned char encoding; unsigned char *p; } zlentry; #define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl))) #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) #define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2))) #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) #define ZIPLIST_END_SIZE (sizeof(uint8_t)) #define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE) #define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) #define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
hashtable 数据结构:
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ unsigned long iterators; /* number of iterators currently running */ } dict; typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht; typedef struct dictEntry { void *key; void *val; struct dictEntry *next; } dictEntry;
一、hset 设置单个 field -> value
“增删改查”中的“增改” 就是它了。
// t_hash.c, set key field value void hsetCommand(client *c) { int update; robj *o; // 1. 查找hash的key是否存在,不存在则新建一个,然后在其上进行数据操作 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; // 2. 检查2-3个参数是否需要将简单版(ziplist)hash表转换为复杂的hash表,转换后的表通过 o->ptr 体现 hashTypeTryConversion(o,c->argv,2,3); // 3. 添加kv到 o 的hash表中 update = hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY); addReply(c, update ? shared.czero : shared.cone); // 变更命令传播 signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id); server.dirty++; } // 1. 获取db外部的key, 即整体hash数据实例 // t_hash.c robj *hashTypeLookupWriteOrCreate(client *c, robj *key) { robj *o = lookupKeyWrite(c->db,key); if (o == NULL) { // 此处创建的hashObject是以 ziplist 形式的 o = createHashObject(); dbAdd(c->db,key,o); } else { // 不是hash类型的键已存在,不可覆盖,返回错误 if (o->type != OBJ_HASH) { addReply(c,shared.wrongtypeerr); return NULL; } } return o; } // object.c, 创建hashObject, 以 ziplist 形式创建 robj *createHashObject(void) { unsigned char *zl = ziplistNew(); robj *o = createObject(OBJ_HASH, zl); o->encoding = OBJ_ENCODING_ZIPLIST; return o; } // ziplist.c static unsigned char *createList() { unsigned char *zl = ziplistNew(); zl = ziplistPush(zl, (unsigned char*)"foo", 3, ZIPLIST_TAIL); zl = ziplistPush(zl, (unsigned char*)"quux", 4, ZIPLIST_TAIL); zl = ziplistPush(zl, (unsigned char*)"hello", 5, ZIPLIST_HEAD); zl = ziplistPush(zl, (unsigned char*)"1024", 4, ZIPLIST_TAIL); return zl; } // 2. 检查参数,是否需要将 ziplist 形式的hash表转换为真正的hash表 /* Check the length of a number of objects to see if we need to convert a * ziplist to a real hash. Note that we only check string encoded objects * as their string length can be queried in constant time. */ void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { int i; if (o->encoding != OBJ_ENCODING_ZIPLIST) return; for (i = start; i <= end; i++) { // 参数大于设置的 hash_max_ziplist_value (默认: 64)时,会直接将 ziplist 转换为 ht // OBJ_ENCODING_RAW, OBJ_ENCODING_EMBSTR // 循环检查参数,只要发生了一次转换就结束检查(没必要继续了) if (sdsEncodedObject(argv[i]) && sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) { // 这个转换过程很有意思,我们深入看看 hashTypeConvert(o, OBJ_ENCODING_HT); break; } } } // t_hash.c, 转换编码方式 (如上, ziplist -> ht) void hashTypeConvert(robj *o, int enc) { if (o->encoding == OBJ_ENCODING_ZIPLIST) { // 此处我们只处理这种情况 hashTypeConvertZiplist(o, enc); } else if (o->encoding == OBJ_ENCODING_HT) { serverPanic("Not implemented"); } else { serverPanic("Unknown hash encoding"); } } // t_hash.c, 转换编码 ziplist 为目标 enc (实际只能是 OBJ_ENCODING_HT) void hashTypeConvertZiplist(robj *o, int enc) { serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); if (enc == OBJ_ENCODING_ZIPLIST) { /* Nothing to do... */ } else if (enc == OBJ_ENCODING_HT) { hashTypeIterator *hi; dict *dict; int ret; // 迭代器创建 hi = hashTypeInitIterator(o); // 一个hash的数据结构就是一个 dict, 从这个级别来说, hash 与 db 是一个级别的 dict = dictCreate(&hashDictType, NULL); // 依次迭代 o, 赋值到 hi->fptr, hi->vptr // 依次添加到 dict 中 while (hashTypeNext(hi) != C_ERR) { sds key, value; // 从 hi->fptr 中获取key // 从 hi->vptr 中获取value key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY); value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE); // 添加到 dict 中 ret = dictAdd(dict, key, value); if (ret != DICT_OK) { serverLogHexDump(LL_WARNING,"ziplist with dup elements dump", o->ptr,ziplistBlobLen(o->ptr)); serverPanic("Ziplist corruption detected"); } } // 释放迭代器 hashTypeReleaseIterator(hi); zfree(o->ptr); // 将变更反映到o对象上返回 o->encoding = OBJ_ENCODING_HT; o->ptr = dict; } else { serverPanic("Unknown hash encoding"); } } // 2.1. 迭代ziplist元素 // t_hash.c, 迭代器 /* Move to the next entry in the hash. Return C_OK when the next entry * could be found and C_ERR when the iterator reaches the end. */ int hashTypeNext(hashTypeIterator *hi) { if (hi->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *zl; unsigned char *fptr, *vptr; // 每次都是基于原始字符器进行计算偏移 // 迭代的是 fptr,vptr zl = hi->subject->ptr; fptr = hi->fptr; vptr = hi->vptr; // 第一次查找时使用index查找,后续则使用 fptr,vptr 进行迭代 if (fptr == NULL) { /* Initialize cursor */ serverAssert(vptr == NULL); fptr = ziplistIndex(zl, 0); } else { /* Advance cursor */ serverAssert(vptr != NULL); fptr = ziplistNext(zl, vptr); } if (fptr == NULL) return C_ERR; /* Grab pointer to the value (fptr points to the field) */ vptr = ziplistNext(zl, fptr); serverAssert(vptr != NULL); /* fptr, vptr now point to the first or next pair */ hi->fptr = fptr; hi->vptr = vptr; } else if (hi->encoding == OBJ_ENCODING_HT) { if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR; } else { serverPanic("Unknown hash encoding"); } return C_OK; } // ziplist.c, 查找 index 的元素 /* Returns an offset to use for iterating with ziplistNext. When the given * index is negative, the list is traversed back to front. When the list * doesn\'t contain an element at the provided index, NULL is returned. */ unsigned char *ziplistIndex(unsigned char *zl, int index) { unsigned char *p; unsigned int prevlensize, prevlen = 0; if (index < 0) { // 小于0时,反向查找 index = (-index)-1; p = ZIPLIST_ENTRY_TAIL(zl); if (p[0] != ZIP_END) { ZIP_DECODE_PREVLEN(p, prevlensize, prevlen); while (prevlen > 0 && index--) { p -= prevlen; ZIP_DECODE_PREVLEN(p, prevlensize, prevlen); } } } else { p = ZIPLIST_ENTRY_HEAD(zl); while (p[0] != ZIP_END && index--) { p += zipRawEntryLength(p); } } // 迭代完成还没找到元素 p[0]=ZIP_END // index 超出整体ziplist大小则遍历完成后 index>0 return (p[0] == ZIP_END || index > 0) ? NULL : p; } // ziplist.c, 由 fptr,vptr 进行迭代元素 /* Return pointer to next entry in ziplist. * * zl is the pointer to the ziplist * p is the pointer to the current element * * The element after \'p\' is returned, otherwise NULL if we are at the end. */ unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) { ((void) zl); /* "p" could be equal to ZIP_END, caused by ziplistDelete, * and we should return NULL. Otherwise, we should return NULL * when the *next* element is ZIP_END (there is no next entry). */ if (p[0] == ZIP_END) { return NULL; } // 当前指针偏移当前元素长度(根据ziplist协议),即到下一元素指针位置 p += zipRawEntryLength(p); if (p[0] == ZIP_END) { return NULL; } return p; } /* Return the total number of bytes used by the entry pointed to by \'p\'. */ static unsigned int zipRawEntryLength(unsigned char *p) { unsigned int prevlensize, encoding, lensize, len; ZIP_DECODE_PREVLENSIZE(p, prevlensize); ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len); return prevlensize + lensize + len; } // 2.2. t_hash.c, 获取 hashTypeIterator 的具体值,写入 vstr, vlen 中 /* Return the key or value at the current iterator position as a new * SDS string. */ sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what) { unsigned char *vstr; unsigned int vlen; long long vll; hashTypeCurrentObject(hi,what,&vstr,&vlen,&vll); if (vstr) return sdsnewlen(vstr,vlen); return sdsfromlonglong(vll); } /* Higher level function of hashTypeCurrent*() that returns the hash value * at current iterator position. * * The returned element is returned by reference in either *vstr and *vlen if * it\'s returned in string form, or stored in *vll if it\'s returned as * a number. * * If *vll is populated *vstr is set to NULL, so the caller * can always check the function return by checking the return value * type checking if vstr == NULL. */ void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) { if (hi->encoding == OBJ_ENCODING_ZIPLIST) { *vstr = NULL; hashTypeCurrentFromZiplist(hi, what, vstr, vlen, vll); } else if (hi->encoding == OBJ_ENCODING_HT) { sds ele = hashTypeCurrentFromHashTable(hi, what); *vstr = (unsigned char*) ele; *vlen = sdslen(ele); } else { serverPanic("Unknown hash encoding"); } } // t_hash.c, 从ziplist中获取某个 hashTypeIterator 的具体值,结果定稿 vstr, vlen /* Get the field or value at iterator cursor, for an iterator on a hash value * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */ void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) { int ret; serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST); // OBJ_HASH_KEY 从 fptr 中获取, 否则从 vptr 中获取 if (what & OBJ_HASH_KEY) { ret = ziplistGet(hi->fptr, vstr, vlen, vll); serverAssert(ret); } else { ret = ziplistGet(hi->vptr, vstr, vlen, vll); serverAssert(ret); } } // ziplist.c, /* Get entry pointed to by \'p\' and store in either \'*sstr\' or \'sval\' depending * on the encoding of the entry. \'*sstr\' is always set to NULL to be able * to find out whether the string pointer or the integer value was set. * Return 0 if \'p\' points to the end of the ziplist, 1 otherwise. */ unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval) { zlentry entry; if (p == NULL || p[0] == ZIP_END) return 0; if (sstr) *sstr = NULL; // 按照ziplist的编码协议, 获取头部信息 zipEntry(p, &entry); if (ZIP_IS_STR(entry.encoding)) { if (sstr) { *slen = entry.len; *sstr = p+entry.headersize; } } else { if (sval) { *sval = zipLoadInteger(p+entry.headersize,entry.encoding); } } return 1; } // ziplist.c, 解析原始字符串为 zlentry /* Return a struct with all information about an entry. */ static void zipEntry(unsigned char *p, zlentry *e) { // 按照ziplist的编码协议,依次读取 prevrawlensize, prevrawlen ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen); // 指向下一位置偏移,按照ziplist的编码协议,依次读取 encoding, lensize, len ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len); // 除去header得到 body偏移 e->headersize = e->prevrawlensize + e->lensize; e->p = p; }
具体header解析如下, 有兴趣的点开瞅瞅:
// ziplist.c /* Decode the length of the previous element, from the perspective of the entry * pointed to by \'ptr\'. */ #define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do { \\ // 解析第1个字符为 prevlensize ZIP_DECODE_PREVLENSIZE(ptr, prevlensize); \\ if ((prevlensize) == 1) { \\ (prevlen) = (ptr)[0]; \\ } else if ((prevlensize) == 5) { \\ assert(sizeof((prevlensize)) == 4); \\ // 当ptr[0]>254时,代表内容有点大,需要使用 5个字符保存上一字符长度 memcpy(&(prevlen), ((char*)(ptr)) + 1, 4); \\ memrev32ifbe(&prevlen); \\ } \\ } while(0); /* Decode the number of bytes required to store the length of the previous * element, from the perspective of the entry pointed to by \'ptr\'. */ #define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \\ if ((ptr)[0] < ZIP_BIGLEN) { \\ (prevlensize) = 1; \\ } else { \\ (prevlensize) = 5; \\ } \\ } while(0); /* Decode the length encoded in \'ptr\'. The \'encoding\' variable will hold the * entries encoding, the \'lensize\' variable will hold the number of bytes * required to encode the entries length, and the \'len\' variable will hold the * entries length. */ #define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \\ // 解析第1个字符为 编码格式 &ZIP_STR_MASK=0xc0 ZIP_ENTRY_ENCODING((ptr), (encoding)); \\ if ((encoding) < ZIP_STR_MASK) { \\ // 0 << 6 =0 // 具体解析如下代码, if ((encoding) == ZIP_STR_06B) { \\ (lensize) = 1; \\ (len) = (ptr)[0] & 0x3f; \\ } // 1 << 6 =64 else if ((encoding) == ZIP_STR_14B) { \\ (lensize) = 2; \\ (len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \\ } // 2 << 6 =128 else if (encoding == ZIP_STR_32B) { \\ (lensize) = 5; \\ (len) = ((ptr)[1] << 24) | \\ ((ptr)[2] << 16) | \\ ((ptr)[3] << 8) | \\ ((ptr)[4]); \\ } else { \\ assert(NULL); \\ } \\ } else { \\ // 超过 0xc0 的长度了,直接使用 1,2,3,4 表示len (lensize) = 1; \\ (len) = zipIntSize(encoding); \\ } \\ } while(0); /* Extract the encoding from the byte pointed by \'ptr\' and set it into * \'encoding\'. */ #define ZIP_ENTRY_ENCODING(ptr, encoding) do { \\ (encoding) = (ptr[0]); \\ if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \\ } while(0) /* Different encoding/length possibilities */ redis数据库list类型各方法封装成类