Redis-3. 字典


Redis对字典的使用相当广泛,例如Redis的数据库就是使用字典来作为底层实现的,对数据库的CRUD都是构建在字典的操作之上的。

除此之外,字典也是哈希键的底层实现之一。
当一个哈希键包含的键值对较多,又或者键值对中的元素都是比较长的元素时,Redis就会使用字典作为哈希键的底层实现。

Redis还有很多功能也使用到了字典,字典是Redis里面应用的特别广泛的一个数据结构

字典的实现

Redis中,字典使用哈希表作为底层实现。一个哈希表可以有多个哈希表节点,每个哈希表节点保存字典中的一个键值对

哈希表

dict.h中的dictht结构定义

typedef struct dictht {
	// 哈希表数组
	dictEntry **table;
	// 哈希表大小
	unsigned long size;
	// 哈希表大小掩码,用于计算索引值
	// 总是等于size-1
	unsigned long sizemask;
	// 该哈希表已有节点的数量
	unsigned long used;
} dictht;

每一个dictEntry结构保存着一个键值对。

哈希表节点

哈希表节点使用dictEntry结构,每个结构保存一个键值对

typedef struct dictEntry {
	// key
	void *key;
	// value
	union {
		void *val;
		uint64_t u64;
		int64_t s64;
	} v;
	// 指向下个哈希表节点,形成链表
	struct dictEntry *next;
} dictEntry;

v保存键值对的值,这个值可以是一个指针,或者是一个uint64_t整数,或者是一个int64_t整数
dictEntry使用链地址法(有一个指向下个哈希表节点的指针)解决哈希冲突。

字典

字典由dict.h中的dict结构表示

typedef struct dict {
	// 类型特定函数
	dictType *type;
	// 私有数据
	void *privdata;
	// 哈希表
	dictht ht[2];
	// rehash索引
	// rehash不存在时,值为-1
	int rehashidx;
} dict;

typeprivdata属性是针对不同类型的键值对,为了创建多态字典而设置的:

  • type是指向dictType的指针,每个dictType保存了一簇用于操作特定类型键值对的函数。Redis会为用途不同的字典设置不同的类型特定函数
  • privdata保存了需要传给那些类型特定函数的可选参数
typedef struct dictType {
	// 计算哈希值的函数
	unsigned int (*hashFunction)(const void *key);
	// 复制键的函数
	void *(*keyDup)(void *privdata, const void *key);
	// 复制值的函数
	void *(*valDup)(void *privdata, const void *obj);
	// 对比键的函数
	int (*keyCompare)(void *privdata, const void *key1, const void *key2);
	// 销毁键的函数
	void (*keyDestructor)(void *privdata, void *key);
	// 销毁值的函数
	void (*valDestructor)(void *privdata, void *obj);
	
} dictType;

ht包含两个项,每个项中都是一个dictht哈希表,一般情况下只使用ht[0]ht[1]只在对ht[0]进行rehash时使用。
rehashidx记录了rehash目前的进度,如果目前没有在进行rehash,则值为-1。

哈希算法

往字典里添加新的键值对时,需要先根据键值对的键计算出哈希值和索引值,然后根据索引值将包含新键值对的哈希表节点放到哈希表数组的指定索引上

// 使用字典设置的哈希函数计算键key的哈希值
hash = dict->type->hashFunction(key);
// 使用哈希表的sizemask属性和哈希值,计算出索引值
// 根据情况不同,ht[x]可以是ht[1]或者ht[0]
index = hash & dict ->ht[x].sizemask;

当字典被用作数据库或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值

MurmurHash算法的最新版本为MurmurHash3

解决键冲突

Redis使用链地址法解决键冲突

由于dictEntry节点组成的链表没有指向链表表尾的指针,因此为了速度考虑,程序总是将新节点添加到链表表头位置。

rehash

为了让哈希表的负载因子(load factor)维持在一个合理的范围内,当哈希表保存的键值对数量太多或太少时,程序需要对哈希表的大小进行相应的扩展或者收缩

这项工作可以通过执行rehash操作完成。Redis中对字典的哈希表执行rehash的操作如下:

  1. 为字典的ht[1]哈希表分配空间,其大小取决于将要执行的操作以及ht[0].used的值。如果是扩展,则ht[1]的大小为第一个大于等于ht[0].used*2的2^n(2的n次方幂);如果是收缩,则大小为第一个大于等于
  2. ht[0] 中的键值对rehash到ht[1]中。这里的rehash是指重新计算键的哈希值和索引值
  3. 迁移完成释放ht[0],并将ht[1]设置成ht[0],然后在ht[1]创建新的空白哈希表,为下一次rehash做准备

哈希表的扩展与收缩

当满足任意一个以下条件时,程序自动对哈希表执行扩展操作:

  • 服务器目前没有执行BGSAVE命令或BGERWRITEAOF命令,且哈希表负载因子大于等于1
  • 服务器目前正在执行BGSAVE命令或BGERWRITEAOF命令,且哈希表负载因子大于等于5

负载因子计算公式:

// 负载因子 = 哈希表已保存节点数量 / 哈希表大小
load_factor = ht[0].used / ht[0]/size;

执行BGSAVE命令和BGREWRITEAOF命令的过程中Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写实复制技术来优化子进程的使用效率。因此在子进程存在期间,服务器会提高执行扩展操作的负载因子,从而避免子进程存在期间进行哈希表扩展操作,可以避免不必要的内存写入操作,节约内存。

哈希表的负载因子小于0.1时,程序自动对哈希表执行收缩操作

渐进式rehash

扩展或收缩哈希表需要将ht[0] 中的键值对rehash到ht[1]中,这个rehash操作不是一次性、集中式完成的,而是分多次、渐进式地完成的

原因是避免对大型哈希表进行rehash时产生的庞大计算量导致服务器在一段时间内停止服务

渐进式rehash的步骤:

  1. ht[1]分配空间
  2. 在字典中维持一个索引计数器rehashidx并且设置为0
  3. rehash期间,每次对字典执行增删改查操作时,程序除了执行指定操作以外,还会将ht[0]哈希表在rehashidx上的所有键值对rehash到ht[1],rehash工作完成后,rehashidx属性+1
  4. 随着字典操作不断进行,最终在某个时间点上完成全部到rehash,此时将rehashidx设为1,表示rehash完成

渐进式rehash期间,字典会同时使用两个哈希表,删改查在两个哈希表上执行(优先ht[0]),而添加操作只在ht[1]上执行

字典API

字典API

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 使用FastAPI和Redis来获取session,可以按照以下步骤进行操作: 1. 首先,安装所需的库。使用pip命令安装FastAPI和fastapi_session库,以及aioredis库。 2. 创建一个FastAPI应用程序,并导入所需的库和模块。 ```python from fastapi import FastAPI from fastapi_session import SessionMiddleware import aioredis ``` 3. 初始化FastAPI应用程序和Redis连接。 ```python app = FastAPI() redis = await aioredis.create_connection("redis://localhost:6379") ``` 4. 添加SessionMiddleware中间件到FastAPI应用程序中。 ```python app.add_middleware(SessionMiddleware, secret_key="your-secret-key", redis_instance=redis) ``` 在上述代码中,需要将"your-secret-key"替换为一个随机生成的密钥,用于对会话数据进行加密。 5. 创建一个路由,用于访问和操作session数据。 ```python @app.get("/session") async def get_session(session=Depends(get_session_manager)): return session["data"] ``` 上述代码中的"get_session_manager"是一个自定义的依赖项函数,用于获取session管理器。 6. 启动FastAPI应用程序。 ```python if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) ``` 现在,您可以使用FastAPI和Redis来获取session。当访问/session路由时,将返回session中存储的数据。您可以使用其他路由来设置、更新或删除session数据。请根据您的具体需求进行相应的操作。 ### 回答2: fastapi-redis-session 是一个用于获取 session 的 FastAPI 插件。它基于 Redis 数据库实现了会话管理,并提供了一种简单的方式来存储和获取会话数据。 为了使用 fastapi-redis-session,首先需要安装并导入 fastapi 和 redis 相关的库。然后,创建一个 FastAPI 应用程序,并使用 fastapi-redis-session 插件初始化会话管理。 通过 fastapi-redis-session,可以使用 `request.session` 来访问会话对象。会话对象有一个 `get` 方法,用于从会话中获取数据。例如,可以通过 `request.session.get('username')` 获取会话中的用户名。 如果需要设置会话数据,可以使用会话对象的 `__setitem__` 方法,用于将数据写入会话。例如,可以通过 `request.session['username'] = 'John'` 将用户名存储到会话中。 此外,还可以使用会话对象的 `delete` 方法,用于从会话中删除数据。例如,可以通过 `request.session.delete('username')` 删除会话中的用户名。 要注意的是,fastapi-redis-session 还提供了其他一些功能,如设置会话过期时间、使用不同的 Redis 配置和使用自定义的会话 ID。 总体而言,fastapi-redis-session 为 FastAPI 应用程序提供了一种简单而强大的方式来管理会话。通过使用该插件,可以轻松地获取、存储和操作会话数据,从而提高应用程序的用户体验和功能扩展性。 ### 回答3: FastAPI-Redis-Session 是一个通过 Redis 存储会话数据的 FastAPI 插件。要获取 session,首先需要安装和配置 FastAPI-Redis-Session 插件。 步骤如下: 1. 首先确保已安装 FastAPI 和 FastAPI-Redis-Session 插件。可以使用 pip 进行安装: ``` pip install fastapi fastapi-redis-session ``` 2. 在 FastAPI 应用程序中导入 FastAPI-Redis-Session: ```python from fastapi_redis_session import SessionMiddleware from fastapi import FastAPI ``` 3. 创建 FastAPI 应用程序并添加中间件: ```python app = FastAPI() app.add_middleware(SessionMiddleware, secret_key="your-secret-key", expire_seconds=3600, redis_url="redis://localhost:6379") ``` 4. 在路由函数中,可以通过访问 `request.session` 来获取 session 对象,然后对其进行操作: ```python from fastapi import Request @app.get("/example") def example(request: Request): session = request.session # 获取 session 数据 data = session.get("key") # 设置 session 数据 session["key"] = "value" ``` 在上述示例中,我们首先从请求对象 `request` 中获取 `session` 对象,然后可以使用 `get()` 方法来获取 session 中的数据,或者使用字典的方式设置或修改 session 数据。 需要注意的是,在添加中间件时,我们需要提供一个密钥(`secret_key`)用于对 session 数据进行加密,过期时间(`expire_seconds`)用于定义 session 的过期时间,以及 Redis 的 URL 地址(`redis_url`)。 以上就是使用 FastAPI-Redis-Session 插件来获取 session 的简要步骤和示例。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值