[redis设计与实现][8]数据库

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis数据库定义: typedef struct redisDb { dict *dict; /* The keyspace for this DB */ dict *expires; /* Timeout of k

Redis数据库定义:

typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id;
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;

dict

dict数组保存所有的数据库,Redis初始化的时候,默认会创建16个数据库

#define REDIS_DEFAULT_DBNUM 16

默认情况下,Redis客户端的目标数据库是0 号数据库,可以通过select命令切换。 注意,由于Redis缺少获取当前操作的数据库命令,使用select切换需要特别注意

读写数据库中的键值对的时候,Redis除了对键空间执行指定操作外,还有一些额外的操作:

  • 读取键之后(读和写操作都会先读取),记录键空间命中或不命中次数
  • 读取键之后,更新键的LRU
  • 读取时发现已经过期,会先删除过期键
  • 如果有客户端使用watch命令监视了key,会在修改后标记为dirty
  • 修改之后,会对dirty键计数器加1,用于持久化和复制
  • 如果开启了数据库通知,修改之后会发送相应通知
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {
robj *o = lookupKeyRead(c->db, key);
if (!o) addReply(c,reply);
return o;
}
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
//查询是否已经过期
expireIfNeeded(db,key);
val = lookupKey(db,key);
if (val == NULL)
server.stat_keyspace_misses++;
else
server.stat_keyspace_hits++;
return val;
}
robj *lookupKey(redisDb *db, robj *key) {
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de);
/* Update the access time for the ageing algorithm.
* Don’t do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (server.rdb_child_pid == –1 && server.aof_child_pid == –1)
//设置lru时间
val->lru = server.lruclock;
return val;
} else {
return NULL;
}
}

expires

通过exprire或者pexpire命令,可以设置键的TTL,如果键的TTL为0,会被自动删除。

expires字典保存了数据库中所有键的过期时间。

  • 过期字典的键是指向某个数据中的键对象
  • 过期字段的值是long long类型的整数,保存这个键的过期时间
    void expireCommand(redisClient *c) {
    expireGenericCommand(c,mstime(),UNIT_SECONDS);
    }
    void expireGenericCommand(redisClient *c, long long basetime, int unit) {
    robj *key = c->argv[1], *param = c->argv[2];
    long long when; /* unix time in milliseconds when the key will expire. */
    if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
    return;
    if (unit == UNIT_SECONDS) when *= 1000;
    when += basetime;
    /* No key, return zero. */
    if (lookupKeyRead(c->db,key) == NULL) {
    addReply(c,shared.czero);
    return;
    }
    /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
    * should never be executed as a DEL when load the AOF or in the context
    * of a slave instance.
    *
    * Instead we take the other branch of the IF statement setting an expire
    * (possibly in the past) and wait for an explicit DEL from the master. */
    if (when <= mstime() && !server.loading && !server.masterhost) {
    robj *aux;
    redisAssertWithInfo(c,key,dbDelete(c->db,key));
    server.dirty++;
    /* Replicate/AOF this as an explicit DEL. */
    aux = createStringObject(DEL,3);
    rewriteClientCommandVector(c,2,aux,key);
    decrRefCount(aux);
    signalModifiedKey(c->db,key);
    notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,del,key,c->db->id);
    addReply(c, shared.cone);
    return;
    } else {
    //放到expires字典中
    setExpire(c->db,key,when);
    addReply(c,shared.cone);
    signalModifiedKey(c->db,key);
    notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,expire,key,c->db->id);
    server.dirty++;
    return;
    }
    }

过期键删除策略

  • 惰性删除:每次执行命令前,都会调用expireIfNeeded函数检查是否过期,如果已经过期,改函数会删除过期键
  • 定时删除:定时执行activeExpireCycleTryExpire函数

    expireIfNeeded

    int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);
    mstime_t now;
    if (when < 0) return 0; /* No expire for this key */
    /* Don’t expire anything while loading. It will be done later. */
    if (server.loading) return 0;
    /* If we are in the context of a Lua script, we claim that time is
    * blocked to when the Lua script started. This way a key can expire
    * only the first time it is accessed and not in the middle of the
    * script execution, making propagation to slaves / AOF consistent.
    * See issue #1525 on Github for more information. */
    now = server.lua_caller ? server.lua_time_start : mstime();
    /* If we are running in the context of a slave, return ASAP:
    * the slave key expiration is controlled by the master that will
    * send us synthesized DEL operations for expired keys.
    *
    * Still we try to return the right information to the caller,
    * that is, 0 if we think the key should be still valid, 1 if
    * we think the key is expired at this time. */
    if (server.masterhost != NULL) return now > when;
    /* Return when this key has not expired */
    if (now <= when) return 0;
    /* Delete the key */
    server.stat_expiredkeys++;
    propagateExpire(db,key);
    notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
    expired,key,db->id);
    return dbDelete(db,key);
    }

    activeExpireCycleTryExpire

    while (num–) {
    dictEntry *de;
    long long ttl;
    if ((de = dictGetRandomKey(db->expires)) == NULL) break;
    ttl = dictGetSignedIntegerVal(de)-now;
    if (activeExpireCycleTryExpire(db,de,now)) expired++;
    if (ttl < 0) ttl = 0;
    ttl_sum += ttl;
    ttl_samples++;
    }

    AOF、RDB和复制功能对过期键的处理

  • 生成RDB文件时,已过期的键不会被保存到新的RDB文件中
  • 载入RDB文件:
    • 主服务器载入时,会忽略过期键
    • 从服务器载入时,都会被载入(但是很快会因为同步被覆盖)
  • AOF写入,已过期未删除的键没有影响,被删除后,会追加一条del命令
  • AOF重写,会对键进行检查,过期键不会保存到重写后的AOF文件
  • 复制:
    • 主服务器删除一个过期键后,会显式向所有从服务器发送DEL命令
    • 从服务器执行读命令,及时过期也不会删除,只有接受到主服务器DEL命令才会删除
转载自:https://coolex.info/blog/459.html
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3月前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
3月前
|
Oracle NoSQL 关系型数据库
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
619 2
|
4月前
|
SQL 存储 NoSQL
Redis6入门到实战------ 一、NoSQL数据库简介
这篇文章是关于NoSQL数据库的简介,讨论了技术发展、NoSQL数据库的概念、适用场景、不适用场景,以及常见的非关系型数据库。文章还提到了Web1.0到Web2.0时代的技术演进,以及解决CPU、内存和IO压力的方法,并对比了行式存储和列式存储数据库的特点。
Redis6入门到实战------ 一、NoSQL数据库简介
|
4月前
|
存储 缓存 NoSQL
Redis内存管理揭秘:掌握淘汰策略,让你的数据库在高并发下也能游刃有余,守护业务稳定运行!
【8月更文挑战第22天】Redis的内存淘汰策略管理内存使用,防止溢出。主要包括:noeviction(拒绝新写入)、LRU/LFU(淘汰最少使用/最不常用数据)、RANDOM(随机淘汰)及TTL(淘汰接近过期数据)。策略选择需依据应用场景、数据特性和性能需求。可通过Redis命令行工具或配置文件进行设置。
102 2
|
4月前
|
JSON NoSQL Redis
Redis 作为向量数据库快速入门指南
Redis 作为向量数据库快速入门指南
211 1
|
4月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
|
4月前
|
存储 缓存 NoSQL
基于SpringBoot+Redis解决缓存与数据库一致性、缓存穿透、缓存雪崩、缓存击穿问题
这篇文章讨论了在使用SpringBoot和Redis时如何解决缓存与数据库一致性问题、缓存穿透、缓存雪崩和缓存击穿问题,并提供了相应的解决策略和示例代码。
85 0
|
缓存 NoSQL 安全
Redis 7.0 Multi Part AOF的设计和实现
本文将详解Redis中现有AOF机制的一些不足以及Redis 7.0中引入的Multi Part AOF的设计和实现细节
2034 0
|
缓存 NoSQL 安全
Redis 7.0 Multi Part AOF的设计和实现
Redis 作为一种非常流行的内存数据库,通过将数据保存在内存中,Redis 得以拥有极高的读写性能。但是一旦进程退出,Redis 的数据就会全部丢失。 为了解决这个问题,Redis 提供了 RDB 和 AOF 两种持久化方案,将内存中的数据保存到磁盘中,避免数据丢失。本文将重点讨论AOF持久化方案,以及其存在的一些问题,并探讨在Redis 7.0 (已发布RC1) 中Multi Part AOF(下文简称为MP-AOF,本特性由阿里云数据库Tair团队贡献)设计和实现细节。
Redis 7.0 Multi Part AOF的设计和实现
|
缓存 NoSQL 安全
Redis 7.0 Multi Part AOF的设计和实现
Redis 7.0 Multi Part AOF的设计和实现
324 0
Redis 7.0 Multi Part AOF的设计和实现