Redis源码剖析--字符串t_string

2023-05-16

前面一直在分析Redis的底层数据结构,Redis利用这些底层结构设计了它面向用户可见的五种数据结构,字符串、哈希,链表,集合和有序集合,然后用redisObject对这五种结构进行了封装。从这篇博客开始,带你一点点分析五种数据类型常见命令对应的源码实现,慢慢地解开Redis的面纱。

字符串概述

字符串是Redis中最为常见的数据存储类型,其底层实现是简单动态字符串sds,因此,该字符串类型是二进制安全的,这就意味着它可以接受任何格式的数据。另外,Redis规定,字符串类型最多可以容纳的数据长度为512M。Redis提供了下列函数,来检测字符串键的大小。

static int checkStringLength(client *c, long long size) {
    // 超出了512M,就直接报错
    if (size > 512*1024*1024) {
        addReplyError(c,"string exceeds maximum allowed size (512MB)");
        return C_ERR;
    }
    return C_OK;
}

字符串命令

Redis为string提供了一系列的命令,用来操作和管理字符串,主要包括以下几个命令。

命令命令描述
SET key value [ex 秒数][px 毫秒数][nx/xx]设置指定key的值
GET key获取指定key的值
APPEND key value将value追加到指定key的值末尾
INCRBY key increment将指定key的值加上增量increment
DECRBY key decrement将指定key的值减去增量decrement
STRLEN key返回指定key的值长度
SETRANGE key offset value将value覆写到指定key的值上,从offset位开始
GETRANGE key start end获取指定key中字符串的子串[start,end]
MSET key value [key value …]一次设定多个key的值
MGET key1 [key2..]一次获取多个key的值

上述命令均为常用的字符串命令,其实现在t_string.c文件中,我们进而来查看一下它们的实现源码。

void setCommand(client *c); // SET命令,设定键值对
void setnxCommand(client *c); // SETNX命令,key不存在时才设置值
void setexCommand(client *c); // SETEX命令,key存在时才设置值,到期时间为秒
void psetexCommand(redisClient *c) // PSETEX命令,key存在时才设置值,到期时间为毫秒
void setrangeCommand(client *c); // SETRANGE命令,范围性的设置值
void msetCommand(client *c); // MSET命令,一次设定对个键值对
void msetnxCommand(client *c); // MSETNX命令,key不存在时才设置值
void getCommand(client *c); // GET命令,获取key对应的value
void mgetCommand(client *c); // MGET命令,获取多个key对应的value
void getrangeCommand(client *c); // GETRANGE命令,范围性的获取值
void getsetCommand(client *c); // 获取指定的键,如果存在则修改其值;反之不进行操作
void incrCommand(client *c); // 值递增1操作
void decrCommand(client *c); // 值递减1操作
void incrbyCommand(client *c); // 值增加操作
void decrbyCommand(client *c); // 值减少操作
void appendCommand(redisClient *c) // 追加key对应的值 
void strlenCommand(redisClient *c) // 获取key对应值得长度

接下来,我们以SET命令为例,来理解以下Redis处理字符串命令的过程。

例:SET命令实现流程

set命令用于设置指定的值,其具体命令格式如下:

set key value [ex 秒数] [px 毫秒数] [nx/xx]

其中,各个选项的含义如下:

  • ex 设置指定的到期时间,单位为秒
  • px 设置指定的到期时间,单位为毫秒
  • nx 只有在key不存在的时候,才设置key的值
  • xx 只有key存在时,才对key进行设置操作

例如,我们在Redis的客户端中输入:

127.0.0.1:6379> set zee 100 ex 1000 nx
OK
// 代表设定一组键值对[zee,100],其中,到期时间为1000秒,如果zee不存在则创建key并设定值

SET 命令的源码由setcommod函数实现,调用set命令需要传入一个client的指针,client类型里面包含了很多Redis对于交互命令的处理参数,我们没必要去管一些目前还用不上的参数,先来看看set命令需要用到的参数。

typedef struct client {
    redisDb *db;            // 当前数据库
    robj **argv;            // 命令参数
    // ....
} client;

很显然,db指向一个我们当前需要操作的数据库,argv指向待传入的命令参数。当我们执行set zee 100 ex 1000 nx命令时,argv中就包含六个RedisObject结构,其对应如下:

argv[0] -- set
argv[1] -- zee
argv[3] -- 100
argv[4] -- ex
argv[5] -- 1000
argv[6] -- nx

我们规定了到期时间为1000秒,且只有在zee键不存在的时候才设定该键的值。Redis为SET命令的操作设定了下列三个宏定义,用来标记SET的操作类型。

// 关于set命令的操作有三种宏定义
#define OBJ_SET_NO_FLAGS 0    // 没有设定参数
#define OBJ_SET_NX (1<<0)     // 只有键不存在时才设定其值
#define OBJ_SET_XX (1<<1)      // 只有键存在时才设定其值
#define OBJ_SET_EX (1<<2)       // ex属性,到期时间单位为秒
#define OBJ_SET_PX (1<<3)       // px属性,到期时间单位为毫秒

有了上述的理解之后,我们可以进入setCommand函数了。

/* set命令实现函数 */
void setCommand(client *c) {
    int j;
    robj *expire = NULL;
    int unit = UNIT_SECONDS;
    // 用于标记ex/px和nx/xx命令参数
    int flags = OBJ_SET_NO_FLAGS;
    // 从命令串的第四个参数开始,查看其是否设定了ex/px和nx/xx
    for (j = 3; j < c->argc; j++) {
        char *a = c->argv[j]->ptr;
        robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
        if ((a[0] == 'n' || a[0] == 'N') &&
            (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
            !(flags & OBJ_SET_XX)) // 标记
        {
            flags |= OBJ_SET_NX;
        } else if ((a[0] == 'x' || a[0] == 'X') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
                   !(flags & OBJ_SET_NX))
        {
            flags |= OBJ_SET_XX;
        } else if ((a[0] == 'e' || a[0] == 'E') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
                   !(flags & OBJ_SET_PX) && next)
        {
            flags |= OBJ_SET_EX;
            unit = UNIT_SECONDS;
            expire = next;
            j++;
        } else if ((a[0] == 'p' || a[0] == 'P') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
                   !(flags & OBJ_SET_EX) && next)
        {
            flags |= OBJ_SET_PX;
            unit = UNIT_MILLISECONDS;
            expire = next;
            j++;
        } else {
            // 如果不是上述参数,则需要报错,命令错误
            addReply(c,shared.syntaxerr);
            return;
        }
    }
    // 判断value是否可以编码成整数,如果能则编码;反之不做处理
    c->argv[2] = tryObjectEncoding(c->argv[2]);
    // 调用底层函数进行键值对设定
    setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}
/* 真正的set底层实现函数 */
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    long long milliseconds = 0; /* initialized to avoid any harmness warning */
    // 设定过期时间
    if (expire) {
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
            return;
        if (milliseconds <= 0) {
            addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
            return;
        }
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
    }
    // 判断key是否存在,并根据nx和xx命令来决定是否set命令是否执行
    if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
        (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
    {
        addReply(c, abort_reply ? abort_reply : shared.nullbulk);
        return;
    }
    // 将键值对关联到数据库
    setKey(c->db,key,val);
    // 设定该数据库为脏
    server.dirty++;
    // 设定过期时间
    if (expire) setExpire(c->db,key,mstime()+milliseconds);
    // 发送事件通知
    notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
    // 发送定期事件通知
    if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
        "expire",key,c->db->id);
    // 向客户端发送命令处理结果
    addReply(c, ok_reply ? ok_reply : shared.ok);
}

从SET命令中,衍生除了SETNX,SETEX,PSETEX等命令,其底层均是调用setGenericCommand来实现。

// key不存在时,才设定值,flag为REDIS_SET_NX;如果key存在则不做处理
// 命令形式为:setnx key value
void setnxCommand(client *c) {
    c->argv[2] = tryObjectEncoding(c->argv[2]);
    setGenericCommand(c,OBJ_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
}
// key存在时才设置值,flag为REDIS_SET_NO_FLAGS,过期时间单位为秒,如果key不存在则不做处理
// 命令形式为:setex key seconds value (seconds为键过期时间,单位秒)
void setexCommand(client *c) {
    // 这里为argv[3],因为value存放在此
    c->argv[3] = tryObjectEncoding(c->argv[3]);
    setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
}
// key存在时才设置值,flag为REDIS_SET_NO_FLAGS,过期时间单位为毫秒
// PSETEX key milliseconds value(milliseconds为键过期时间,单位毫秒)
void psetexCommand(client *c) {
    c->argv[3] = tryObjectEncoding(c->argv[3]);
    setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
}

字符串小结

在字符串的处理命令中,涉及到很多数据库和事件的相关处理函数,现阶段我们可以忽略。本博客只是列举了set命令的源码处理过程,这些命令的处理大多是涉及到命令解析的过程,比较繁琐,但是很好理解。有兴趣的可以在深入到每个命令的源码中,一窥实现步骤。源码面前,了无秘密。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis源码剖析--字符串t_string 的相关文章

  • 使用Example_where_Cause出现 Column 'goods_id' in where clause is ambiguous解决办法

    改写SSM项目https www bilibili com video BV18J411k7SF from 61 search amp seid 61 7715680395343362130出现 Column 39 goods id 39
  • 在CLI中打印表格----gotable使用介绍

    目录 介绍 获取gotable 在github中获取 下载源码 git clone go mod API 创建table 从结构体中创建空table 获取版本信息 获取版本列表 打印表格 给表格添加行 给表格添加多个行 给表格添加列 介绍
  • 抽象类和普通类

    包含抽象方法的类称为抽象类 xff0c 但并不意味着抽象类中只能有抽象方法 xff0c 它和普通类一样 xff0c 同样可以拥有成员变量和普通的成员方法 注意 xff0c 抽象类和普通类的主要有三点区别 xff1a 1 抽象方法必须为pub
  • 优化器(Optimizer)(SGD、Momentum、AdaGrad、RMSProp、Adam)

    文章目录 3 1 传统梯度优化的不足 BGD SGD MBGD 3 1 1 一维梯度下降3 1 2 多维梯度下降 3 2 动量 Momentum 3 3 AdaGrad算法3 4 RMSProp算法3 5 Adam算法 优化器在机器学习 深
  • ViewBinding绑定布局

    最近这段时间在学习Kotlin xff0c 突然发现谷歌已经把kotlin android extensions插件废弃 xff0c 目前推荐使用ViewBinding来进行替代 xff0c 接下来通过本文给大家分享Android使用Vie
  • element-ui更改图标icon大小

    element ui改变icon大小 在template里面加入div lt div class 61 34 change icon 34 gt lt i class 61 34 el icon switch button 34 gt lt
  • @PathVariable注解

    转自 xff1a http www cnblogs com FFFFF p 4624140 html 使用 64 PathVariable可以快速的访问 xff0c URL中的部分内容 在 64 RequestMapping的value中使
  • Ubuntu安装Google Chrome,报NSS version的错误

    使用网上的教程安装google chrome xff0c 启动时报这个错误 xff1a 4594 4630 1021 124049 156901 FATAL nss util cc 632 NSS VersionCheck 34 3 26
  • gitlab操作 in pycharm

    1 install gitlab projects plugins 如果遇到 marketplace plugins are not loaded 查看 34 ubuntu下PyCharm 34 遇到问题博文 2 version contr
  • 挂载system.img

    将多个system压缩成单个img文件 xff0c 需要文件 xff1a generate image xff08 版本里有 xff0c img生成器 xff09 所有的system img文件以及system ini文件 generate
  • synchronized 中4种锁状态

    4种锁状态 xff1a 无锁 xff0c 偏向锁 xff0c 轻量级锁 xff0c 重量级锁 xff1a 偏向锁 xff1a 当一个线程访问加了同步锁的代码块时 xff0c 会在对象头中存储当前线程ID xff0c 后续当这个线程再次进入
  • oracle11g asm单实例重建has

    最近到客户那里处理故障 xff0c 客户说 xff0c 他们修改了一下hostname xff0c 导到has出现了问题 xff0c 当然 xff0c 他们的数据库也就无法再启动 xff0c 把处理过程记录下来 xff0c 供大家参考 xf
  • kubelet-config“ is forbidden

    FYI You can look at this config file with 39 kubectl n kube system get cm kubeadm config o yaml 39 error execution phase
  • eclipse注释字体大小设置

    Window gt Preferences gt General gt Appearance gt Colors and Fonts gt Basic gt Text Font gt Edit 另外还可以特定Eclipse区域中的字体 xf
  • 编译安装zabbix error: MySQL library not found

    本人用编译方式安装的mysql xff0c 用编译安装zabbix 报checking for mysql config configure error MySQL library not found 用 with mysql 61 usr
  • zabbix 不能启动之非一般原因(配置的大意也可导致出错,也可以导致)

    root 64 localhost service zabbix server restart Restarting zabbix server via systemctl 确定 虽服务启动命令外面显示正确 xff0c 但 zabbix s
  • CentOs6.5 通过vncserver安装oracle

    root 64 ora11g lsb release a LSB Version base 4 0 amd64 base 4 0 noarch core 4 0 amd64 core 4 0 noarch graphics 4 0 amd6
  • oracle 11gR2 安装提示INS-20802 Oracle Net Congfiguration Assistant失败

    grid用户安装oracle 43 asm单机碰到了问题 xff1a 日志 xff1a 信息 Started Plugin named Oracle Net Configuration Assistant 信息 Found associat
  • VirtualBox虚拟机与主机互通,并且虚拟机又能上网配置

    为了在VirtualBox上安装oracle 11g rac xff0c 使VirtualBox虚拟机与主机互通 xff0c 并且虚拟机又能上网配置 xff0c 找了很多网上文章但都没有都对最终设置方式进行详细的说明 xff0c 现在自己总
  • vim、cat编辑查看文本

    进入 1 使用vim命令时 xff0c 如果文件不存在 xff0c 在新建文件的同时 xff0c 下方则显示新建文件提示 2 进入编辑器 xff0c 按i键进行编辑操作 退出 按esc退出 xff0c 输入冒号 xff0c 然后再进行其他操

随机推荐

  • Linux上VNC 启动和关闭 已经常见问题

    0 重设密码 root 64 yqrh5u2 vncpasswd Password Verify root 64 yqrh5u2 1 xff0c 启动和kill vncserver root 64 yqrh5u2 vncserver 1 N
  • Linux6安装11.2.0.4提示缺少elfutils-libelf-devel-0.97及pdksh-5.2.14

    Installing 11 2 0 3 Or 11 2 0 4 32 bit x86 or 64 bit x86 64 On RHEL6 Reports That Packages 34 elfutils libelf devel 0 97
  • redhat 6.5(rhel-server-6.5-x86_x64)+oracle11gr2 rac

    VMware 9 0 43 redhat 6 5 rhel server 6 5 x86 x64 43 oracle11gr2 rac 很久没有关注ORACLE了 xff0c 官方发行版已经更新到12c了 xff0c 考虑到目前大部分用户还
  • OracleRAC管理 之 集群状态&信息查看

    OracleRAC管理 之 集群状态 amp 信息 查看 by 王磊 菜小小 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
  • fgetc,fgets,getline用法

    Linux Ubuntu 下用法 在Ubuntu下shell中 xff0c man fgets可以看到fgetc fgets等用法 xff0c man getline可以看到getline用法 span class hljs comment
  • 使用PyQt4制作一个音乐播放器(1)

    1 前言 最近用Python给老妈写了一个处理excel表格的小软件 xff0c 因为自己平时用Python一般都是用在数值计算领域 xff0c 所以一般使用命令行的方式交互即可 但是给老妈用发现用命令行交互方式使用并不是很方便 xff0c
  • ubuntu18.04/16.04 忘记开机密码,咋办?

    自己的 笔记本 装了双系统 xff0c 很久没有ubuntu系统了 xff0c 密码竟然给忘记了 于是乎 xff0c 在网上找答案 精髓 大概就是在句话 xff1a recovery nomodeset 并将之删除 xff0c 然后下移一行
  • C++ 实现阻塞队列

    文章出处 xff0c 来源自地址 xff1a C 43 43 实现阻塞队列 文章 详细代码 xff1a include lt queue gt include lt thread gt include lt mutex gt include
  • 浏览器请求nodejs搭建的web服务器上的html文件时引用不了jQuery.js文件解决办法

    构建web服务器代码 nodejs构建 span class token comment 引入核心模块http和fs span span class token keyword var span http span class token
  • AI 到底是怎么「想」的?

    本文作者 xff1a kurffzhou xff0c 腾讯 TEG 安全工程师 最近 xff0c Nature发表了一篇关于深度学习系统被欺骗的新闻文章 xff0c 该文指出了对抗样本存在的广泛性和深度学习的脆弱性 xff0c 以及几种可能
  • kafka3.1 docker-compose方式安装(二)

    ZooKeeper 是为 Kafka 提供协调服务的工具 xff0c 所以得启动一个ZooKeeper 容器 配置文件 docker compose yml span class token key atrule version span
  • 效能优化实践:C/C++单元测试万能插桩工具

    作者 xff1a mannywang xff0c 腾讯安全平台后台开发 研发效能是一个涉及面很广的话题 xff0c 它涵盖了软件交付的整个生命周期 xff0c 涉及产品 架构 开发 测试 运维 xff0c 每个环节都可能影响顺畅 高质量地持
  • Windows使用cmd刷入recovery.img

    Windows使用cmd刷入recovery img Windows键 43 R回车后进入cmd命令终端 进入fastboot 手机进入fastboot模式有2种方法 第一种进入方法是 xff0c 如果你的手机能用adb识别到 xff0c
  • Linux系统安装环境桌面

    最近工作加班还有在弄大数据 xff0c 有段时间没有记录了 xff0c 后续有空的话整理下大数据的东西分享 xff0c 今天记录下Linux安装环境桌面 之前弄了个阿里云服务器玩玩 xff0c CentOS 7的版本 xff0c CentO
  • 【Linux学习笔记】关于ubuntu开机菜单栏和任务栏不见了的有效解决方法

    一 问题描述 ubuntu开机只有桌面 xff0c 没有菜单栏和任务栏 xff0c 如下图 xff1a 二 问题解决 刚学习ubuntu xff0c 总有些像我这样不折腾就不舒服的人 xff0c 今天改了一下主题 xff0c 图标什么的 x
  • 【数据结构与算法】深入浅出递归和迭代的通用转换思想

    深入浅出递归和迭代的通用转换思想 一般来说 xff0c 能用迭代的地方就不要用递归 xff01 理论上讲 xff0c 所有的递归和迭代之间都能相互转换 xff01 刷题碰到 一天一道LeetCode 130 Surrounded Regio
  • 【unix网络编程第三版】阅读笔记(二):套接字编程简介

    unp第二章主要将了TCP和UDP的简介 xff0c 这些在 TCP IP详解 和 计算机网络 等书中有很多细致的讲解 xff0c 可以参考本人的这篇博客 计算机网络 第五版 阅读笔记之五 xff1a 运输层 xff0c 这篇博客就不再赘述
  • 带你深入理解STL之Deque容器

    在介绍STL的deque的容器之前 xff0c 我们先来总结一下vector和list的优缺点 vector在内存中是分配一段连续的内存空间进行存储 xff0c 其迭代器采用原生指针即可 xff0c 因此其支持随机访问和存储 xff0c 支
  • 带你深入理解STL之Set和Map

    在上一篇博客 带你深入理解STL之RBTree中 xff0c 讲到了STL中关于红黑树的实现 xff0c 理解起来比较复杂 xff0c 正所谓前人种树 xff0c 后人乘凉 xff0c RBTree把树都种好了 xff0c 接下来就该set
  • Redis源码剖析--字符串t_string

    前面一直在分析Redis的底层数据结构 xff0c Redis利用这些底层结构设计了它面向用户可见的五种数据结构 xff0c 字符串 哈希 xff0c 链表 xff0c 集合和有序集合 xff0c 然后用redisObject对这五种结构进