博客

Redis源码阅读之set命令

June 19, 2024
Redis, 数据库
Redis

redis版本:7.0.2

函数setGenericCommand,对应文件是t_string.c,源码如下:

/* The setGenericCommand() function implements the SET operation with different
 * options and variants. This function is called in order to implement the
 * following commands: SET, SETEX, PSETEX, SETNX, GETSET.
 * setGenericCommand()函数实现了SET操作,它有多个不同的选项和变体。调用这个函数可以实现下面几个命令:SET, SETEX, PSETEX, SETNX, GETSET.
 *
 * 'flags' changes the behavior of the command (NX, XX or GET, see below).
 * 'flags'影响命令(NX,XX或GET,详情见下面)的操作。
 *
 * 'expire' represents an expire to set in form of a Redis object as passed
 * by the user. It is interpreted according to the specified 'unit'.
 * 'expire'代表了一个由用户传递的过期时间,它是Redis对象形式。会根据不同的单位对其进行解析。
 *
 * 'ok_reply' and 'abort_reply' is what the function will reply to the client
 * if the operation is performed, or when it is not because of NX or
 * XX flags.
 * 'ok_reply' 和 'abort_reply' 是函数在操作成功或者因为NX或XX标志而没有执行操作时,会回复给客户端的内容。
 *
 * If ok_reply is NULL "+OK" is used.
 * If abort_reply is NULL, "$-1" is used.
 * 如果ok_reply为NULL,会使用"+OK"。
 * 如果abort_reply为NULL,会使用"$-1"。
 * 
 *  */

#define OBJ_NO_FLAGS 0
#define OBJ_SET_NX (1 << 0)  /* Set if key not exists. */
#define OBJ_SET_XX (1 << 1)  /* Set if key exists. */
#define OBJ_EX (1 << 2)      /* Set if time in seconds is given */
#define OBJ_PX (1 << 3)      /* Set if time in ms in given */
#define OBJ_KEEPTTL (1 << 4) /* Set and keep the ttl */
#define OBJ_SET_GET (1 << 5) /* Set if want to get key before set */
#define OBJ_EXAT (1 << 6)    /* Set if timestamp in second is given */
#define OBJ_PXAT (1 << 7)    /* Set if timestamp in ms is given */
#define OBJ_PERSIST (1 << 8) /* Set if we need to remove the ttl */

/* Forward declaration */
static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int unit, long long *milliseconds);

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 */
    int found = 0;
    int setkey_flags = 0;

    if (expire && getExpireMillisecondsOrReply(c, expire, flags, unit, &milliseconds) != C_OK)
    {
        return;
    }

    if (flags & OBJ_SET_GET)
    {
        if (getGenericCommand(c) == C_ERR)
            return;
    }

    found = (lookupKeyWrite(c->db, key) != NULL);

    if ((flags & OBJ_SET_NX && found) ||
        (flags & OBJ_SET_XX && !found))
    {
        if (!(flags & OBJ_SET_GET))
        {
            addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
        }
        return;
    }

    setkey_flags |= (flags & OBJ_KEEPTTL) ? SETKEY_KEEPTTL : 0;
    setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST;

    setKey(c, c->db, key, val, setkey_flags);
    server.dirty++;
    notifyKeyspaceEvent(NOTIFY_STRING, "set", key, c->db->id);

    if (expire)
    {
        setExpire(c, c->db, key, milliseconds);
        /* Propagate as SET Key Value PXAT millisecond-timestamp if there is
         * EX/PX/EXAT/PXAT flag. */
        robj *milliseconds_obj = createStringObjectFromLongLong(milliseconds);
        rewriteClientCommandVector(c, 5, shared.set, key, val, shared.pxat, milliseconds_obj);
        decrRefCount(milliseconds_obj);
        notifyKeyspaceEvent(NOTIFY_GENERIC, "expire", key, c->db->id);
    }

    if (!(flags & OBJ_SET_GET))
    {
        addReply(c, ok_reply ? ok_reply : shared.ok);
    }

    /* Propagate without the GET argument (Isn't needed if we had expire since in that case we completely re-written the command argv) */
    if ((flags & OBJ_SET_GET) && !expire)
    {
        int argc = 0;
        int j;
        robj **argv = zmalloc((c->argc - 1) * sizeof(robj *));
        for (j = 0; j < c->argc; j++)
        {
            char *a = c->argv[j]->ptr;
            /* Skip GET which may be repeated multiple times. */
            if (j >= 3 &&
                (a[0] == 'g' || a[0] == 'G') &&
                (a[1] == 'e' || a[1] == 'E') &&
                (a[2] == 't' || a[2] == 'T') && a[3] == '\0')
                continue;
            argv[argc++] = c->argv[j];
            incrRefCount(c->argv[j]);
        }
        replaceClientCommandVector(c, argc, argv);
    }
}

主要逻辑为:

...

Redis源码阅读之get命令

June 18, 2024
Redis, 数据库
Redis

redis版本:7.0.2

get命令对应源码中的函数名getCommand,对应文件是t_string.c。相关源码如下:

int getGenericCommand(client *c) {

robj *o;

  

if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL)

return C_OK;

  

if (checkType(c,o,OBJ_STRING)) {

return C_ERR;

}

  

addReplyBulk(c,o);

return C_OK;

}

  

void getCommand(client *c) {

getGenericCommand(c);

}

可以看到,主要逻辑为:

...

InnoDB之隐式锁

June 16, 2024
后端开发, 数据库
Mysql

MySQL版本:8.3.0。 隔离级别:RC。

为了节约资源,尽可能避免生成不必要的锁结果,InnoDB设计了隐式锁。隐式锁是指在执行语句时并不生成锁,而在特定场景触发时才生成锁。主要用于插入场景。

...

Redis位图的使用

June 16, 2024
后端开发, Redis
Redis

位图的概念及原理 #

我们可以把位图理解成一个bit数组,每个元素的值是0或1。

下面用一张简单的图来解释一下: redis-map

我们可以用第一位来存储用户id为1的用户的在线状态,用第二位来存储用户id为2的用户的在线状态,以此类推。

...

gRPC入门系列之6-添加pprof

December 17, 2023
Grpc, 微服务
Grpc, 微服务, Consul

程序在运行过程中,总是会遇到一些性能问题,比如cpu使用率莫名奇妙的飙升、内存使用率奇高等,轻者导致接口响应速度变慢,重者导致系统崩溃,无法提供服务。

这时候,我们就需要对程序进行性能分析,找出问题所在。在golang中,我们可以使用pprof进行性能分析。今天,我们继续丰富我们的gRPC服务,为其添加pprof组件。

...

gRPC入门系列之4-consul服务注册与发现

December 10, 2023
Grpc, 微服务
Grpc, 微服务, Consul

在前面几篇关于gRPC的文章中,我们已经实现了简单的gRPC接口,但是这些接口都是本地调用,这在现实生产环境中几乎不可能。在生产环境中,除非是直直接在固定ip的物理机/虚拟机上运行,否则ip地址是不固定的,而且服务的数量也是不固定的,这时候就需要服务注册与发现了。

...

Go日志库zap和lumberjack库的使用

December 3, 2023
后端开发, Go, 微服务
Go, Zap, 日志

日志库在每个项目中都是必不可少的一部分,Go语言中有很多优秀的日志库,比如logrus、zap等,这里我们介绍zap日志库的使用。

zap库 #

zap是uber公司开源的一款go语言的日志库。它支持多种日志级别,结构化日志,而且性能很好。性能对比图片这里就贴了,感兴趣的话可以访问仓库地址查看(https://github.com/uber-go/zap)。有一些日志库使用了基于反射的序列化和字符串格式化,这写都是CPU密集型的操作,会影响日志库的性能,这些都是。而zap之所以能性能好,是因为使用了一个无反射、零分配的JSON编码器,尽可能的降低了序列化开销和分配。

...