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)
        {
            // 这个转换过程很有意思,我们深入看看
            hashTypeConv
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值