python 操作redis之三(哈希)

本文详细介绍了Redis中的字典类型(Hash),包括基础使用方法如插入、查询、删除元素,以及字典的内部实现原理,如数组和链表结合的数据结构。还探讨了哈希冲突的解决方式,特别是渐进式rehash机制,用于在扩容和缩容时保证性能。最后,列举了字典类型的典型应用场景,如购物车和用户属性存储。
摘要由CSDN通过智能技术生成

字典使用与内部实现原理

字典类型 (Hash) 又被成为散列类型或者是哈希表类型,它是将一个键值 (key) 和一个特殊的“哈希表”关联起来,这个“哈希表”表包含两列数据:字段和值。例如我们使用字典类型来存储一篇文章的详情信息,存储结构如下图所示:

哈希表存储结构.png

同理我们也可以使用字典类型来存储用户信息,并且使用字典类型来存储此类信息,是不需要手动序列化和反序列化数据的,所以使用起来更加的方便和高效。

1.基础使用

from redis import StrictRedis
redis_cli = StrictRedis(host="xx", port=xx, password="xx", db=xx, decode_responses=True)

"""
decode_responses=True 如果不设置该参数那么返回数据为bytes类型
"""


# 插入单个元素
redis_cli.hset("d1", "name", "li")

# 插入一个或多个元素
redis_cli.hmset("d2", {"cc": "cc", "ss": 18})

# 当某键不存在时,插入数据(如果尝试插入已存在的键,不会改变原来的值)
redis_cli.hsetnx("d3", "key", "val")
redis_cli.hsetnx("d3", "key", "v")  # 不生效
d3 = redis_cli.hgetall("d3")
print(d3)

# 查询单个元素
cc = redis_cli.hget("d2", "cc")
print(cc)

# 查询一个或多个元素
mul = redis_cli.hmget("d2", "cc", "ss")
print(mul)

# 查询元素是否存在
is_exist = redis_cli.hexists("d2", "aa")
print(is_exist)

# 查询某个 key 的所有字段
all_keys = redis_cli.hkeys("d2")
print(all_keys)

# 查询某个 key 的所有值
all_val = redis_cli.hvals("d2")
print(all_val)

# 查询某个 key 的所有字段和值
d2 = redis_cli.hgetall("d2")
print(d2)

# 查询元素个数
d_len = redis_cli.hlen("d2")
print(d_len)

# 删除 key 中的一个或多个元素
# redis_cli.hdel("d1", "a")
# redis_cli.hdel("d2", "cc", "ss")
# print(redis_cli.hgetall("d2"))

# 某个整数值累加计算
redis_cli.hincrby("d2", "ss", 1)
d2 = redis_cli.hgetall("d2")
print(d2)

# 某个浮点值累加计算
redis_cli.hincrbyfloat("d2", "ss", "1.23")
d2 = redis_cli.hgetall("d2")
print(d2)

 2.数据结构

字典类型本质上是由数组和链表结构组成的,来看字典类型的源码实现:

typedef struct dictEntry { // dict.h
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next; // 下一个 entry
} dictEntry;

字典类型的数据结构,如下图所示:

Redis-HashType-02.png

通常情况下字典类型会使用数组的方式来存储相关的数据,但发生哈希冲突时才会使用链表的结构来存储数据。

 3.哈希冲突

字典类型的存储流程是先将键值进行 Hash 计算,得到存储键值对应的数组索引,再根据数组索引进行数据存储,但在小概率事件下可能会出完全不相同的键值进行 Hash 计算之后,得到相同的 Hash 值,这种情况我们称之为哈希冲突

哈希冲突一般通过链表的形式解决,相同的哈希值会对应一个链表结构,每次有哈希冲突时,就把新的元素插入到链表的尾部,请参考上面数据结构的那张图。

键值查询的流程如下:

  • 通过算法 (Hash,计算和取余等) 操作获得数组的索引值,根据索引值找到对应的元素;
  • 判断元素和查找的键值是否相等,相等则成功返回数据,否则需要查看 next 指针是否还有对应其他元素,如果没有,则返回 null,如果有的话,重复此步骤。

键值查询流程,如下图所示:

Redis-HashType-03.png

4.渐进式rehash

Redis 为了保证应用的高性能运行,提供了一个重要的机制——渐进式 rehash。 渐进式 rehash 是用来保证字典缩放效率的,也就是说在字典进行扩容或者缩容是会采取渐进式 rehash 的机制。

1)扩容

当元素数量等于数组长度时就会进行扩容操作,源码在 dict.c 文件中,核心代码如下:

int dictExpand(dict *d, unsigned long size)
{
    /* 需要的容量小于当前容量,则不需要扩容 */
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;
    dictht n; 
    unsigned long realsize = _dictNextPower(size); // 重新计算扩容后的值
    /* 计算新的扩容大小等于当前容量,不需要扩容 */
    if (realsize == d->ht[0].size) return DICT_ERR;
    /* 分配一个新的哈希表,并将所有指针初始化为NULL */
    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0;
    if (d->ht[0].table == NULL) {
        // 第一次初始化
        d->ht[0] = n;
        return DICT_OK;
    }
    d->ht[1] = n; // 把增量输入放入新 ht[1] 中
    d->rehashidx = 0; // 非默认值 -1,表示需要进行 rehash
    return DICT_OK;
}

从以上源码可以看出,如果需要扩容则会申请一个新的内存地址赋值给 ht[1],并把字典的 rehashindex 设置为 0,表示之后需要进行 rehash 操作。

2)缩容

当字典的使用容量不足总空间的 10% 时就会触发缩容,Redis 在进行缩容时也会把 rehashindex 设置为 0,表示之后需要进行 rehash 操作。

3)渐进式rehash流程

在进行渐进式 rehash 时,会同时保留两个 hash 结构,新键值对加入时会直接插入到新的 hash 结构中,并会把旧 hash 结构中的元素一点一点的移动到新的 hash 结构中,当移除完最后一个元素时,清空旧 hash 结构,主要的执行流程如下:

  • 扩容或者缩容时把字典中的字段 rehashidx 标识为 0;
  • 在执行定时任务或者执行客户端的 hset、hdel 等操作指令时,判断是否需要触发 rehash 操作(通过 rehashidx 标识判断),如果需要触发 rehash 操作,也就是调用 dictRehash 函数,dictRehash 函数会把 ht[0] 中的元素依次添加到新的 Hash 表 ht[1] 中;
  • rehash 操作完成之后,清空 Hash 表 ht[0],然后对调 ht[1] 和 ht[0] 的值,把新的数据表 ht[1] 更改为 ht[0],然后把字典中的 rehashidx 标识为 -1,表示不需要执行 rehash 操作。

5.使用场景

哈希字典的典型使用场景如下:

  • 商品购物车,购物车非常适合用哈希字典表示,使用人员唯一编号作为字典的 key,value 值可以存储商品的 id 和数量等信息;
  • 存储用户的属性信息,使用人员唯一编号作为字典的 key,value 值为属性字段和对应的值;
  • 存储文章详情页信息等。

6.小结

本文我们学习了字典类型的操作命令和在代码中的使用,也明白了字典类型实际是由数组和链表组成的,当字典进行扩容或者缩容时会进行渐进式 rehash 操作,渐进式 rehash 是用来保证 Redis 运行效率的,它的执行流程是同时保留两个哈希表,把旧表中的元素一点一点的移动到新表中,查询的时候会先查询两个哈希表,当所有元素都移动到新的哈希表之后,就会删除旧的哈希表。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值