Redis 学习笔记


Redis 是非常优秀的非关系型数据库,它的优点如下:

1,丰富的数据结构,支持更复杂的应用场景,Memcached 只支持简单 KV
2,有持久化机制(将数据保存在磁盘中)
3,以前是单线程(加 IO 多路复用),现在也支持多线程,Memcached 是多线程加锁机制
4,支持惰性删除与定时删除
5,可以用来做分布式锁(分布式情况下保证并发安全,使用 setnx 上锁、del 解锁、expire 设置过期时间)

在开始之前,先来看看 redis 的主要问题是什么
在这里插入图片描述

事务

MySQL 的事务是非常标准的事务,满足 ACID 特性,redis 的事务更 MySQL 的不太一样,由于它不是非常好用,因此我们使用 lua 脚本来代替它

基础使用与实现

redis 因为是单线程执行,所以它的事务会将一些操作加入队列,然后串行的执行事务,每个事务执行过程中不会被其他操作打断

事务的基本操作有四大命令,multi、discard、exec 与 watch:

multi 命令用来将一些命令加入队列,redis 在入队时会有检查,如果这些中有一个出错,那么所有的命令都不会执行成功。但是入队时的检查非常有限,就和编译时检查一样,它只能检查到某些命令不存在之类的错误,并且拒绝执行所有的命令

在 redis 非常古老的版本中,入队时发现错误事务也会执行,现在改成不执行所有语句了

discard 用来取消这个队列

exec 命令用来让队列中的命令执行

我们知道 redis 的实现大部分功能并不复杂,因此功能实现的重点都是关于数据的储存操作,比如这三个命令在 redis 底层的实现,事实上非常简单

multi 命令会将客户端数据结构中的某个标识打开,即 client.flags = REDIS_MULTI。在该标识打开后,redis 只会执行四大命令,其他的所有命令都会被存放在一个队列中。discard 就是取消这个标识,exec 则是遍历执行队列中的内容,并且执行一些清除标记、清除队列这样的收尾工作

反正 redis 遇到大部分需求,都是用数据结构解决的,就像我们不管遇到什么需求,都是用数据库解决的一样

关于 ACID 特性

简单分析一下 redis 的 ACID 特性

redis 的事务满足隔离性,事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,得益于 redis 是单线程的原因,满足隔离性并不困难

不满足原子性指,执行事务过程中,如果有错误的命令 radis 也会继续执行之后的命令,并且不支持回滚。这很大一部分原因是 redis 不支持事务。如果此时队列中有命令出错,其他正确的命令也会执行成功,因此 redis 不满足原子性

关于持久性,redis 是内存数据库,就算有 RDB、AOF 这些落库的机制,也不能实时的保证数据的持久性

关于一致性,redis 有很多检错机制,在事务执行之前会进行语法判断,在事务执行时则会排除错误的语句,无论如何数据都会是正确的。在 redis 挂掉之后,redis 重新启动时要么时空白的,要么根据日志文件恢复数据库,因此挂掉之后也可以保证一致性

监视命令以及实现

如果在某个事务执行之前,另外一个客户端将某个事务需要修改的键删除了,这时出现了意料之外的多线程错误,我们如何解决这种错误呢?

redis 只能使用乐观锁的方式来保证多线程情况下事务的执行不会出现修改丢失的情况,不可以直接使用悲观锁

watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

每个 redis 的数据库都有一个 watched_keys 字典,该字典就是用来实现 watch 功能的。字典的键是被监视的数据库键,而字典的值是要一串记录了所有监视该键的客户端列表

typedef struct redisdb {
	dict *watched_keys;
}

在数据库被修改的时候,redis 会额外在该字典中判断一次,是否有键被监视了,如果被监视了,将对应列表中的客户端中的某个标记置为 true。而在客户端想要执行事务时,会先判断该标记

为什么不支持回滚

redis 没有原子性的原因,就是因为没有一种合理的手段进行回滚,MySQL 是使用 redo 日志来实现回滚操作的

上面也说过了,redis 没有事务回滚功能,因此不保证数据库原子性,为什么这么设计呢,redis 官方对此是这么解释的:

redis 一般来说,发现错误会有两种情况:

1,运行时异常,在事务编译中执行错误命令,redis 不会开始这个事务,Redis 命令只会因为错误的语法而失败,或是命令中用在了错误类型的键上面。关于这个问题如何解决:

this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production

就是说这个错误在开发过程中很可能检测到,而不是在生产环境中

2,事务执行一半,Redis 宕机,这个可以通过 AOF 日志文件解决,不需要回滚

还有一方面原因是:

An argument against Redis point of view is that bugs happen, however it should be noted that in general the roll back does not save you from programming errors. For instance if a query increments a key by 2 instead of 1, or increments the wrong key, there is no way for a rollback mechanism to help. 

这段话大致是说回滚无法解决编程错误,不过说实话,缓存里的数据可以失效删除,也没有数据回滚的必要

同时还有一部分理由,不支持回滚是因为回滚这种复杂的功能和 redis 追求简单高效的设计主旨不相符

秒杀

在这个案例中需要特别关注两点,商品库存的减少和用户的增加

需要考虑的其他问题如下:

使用连接池进行redis连接
用户的ID以及商品的ID是否合法
判断用户是否重复秒杀

如果以上条件都通过的话,进行秒杀的多线程同步操作,使用乐观锁对商品进行监视,进行事务操作时需要额外进行两步判断:

秒杀是否开始,当 redis 中商品为 null 时没有开始
秒杀是否结束,当 redis 中商品为0时秒杀结束

判断通过后执行商品库存的减少和用户链表增加的操作

在商品数量较多的情况下可能会出现库存遗留的情况,这个问题是因为乐观锁的特性导致的,使用 lua(洛)脚本解决这个问题

单线程

redis 的主要功能是通过 IO 多路复用的单线程完成的,因为 Redis 的瓶颈不是 CPU 的运行速度,而往往是网络带宽和机器的内存大小。并且,单线程切换开销小,容易实现,没有并发问题。既然单线程容易实现,那就顺理成章地采用单线程的方案

文件事件处理器

Radis 使用 Reactor(反应器)模式开发了文件事件处理器,用来处理文件事件

文件事件处理器使用 I/O 多路复用来同时监听多个套接字(客户端连接)

当被监听的套接字准备好执行连接操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件

客户端发起操作数据库请求,redis 按照顺序处理请求,接收请求参数,接收完成后解析请求协议,得到请求命令,然后执行请求命令,即操作 redis 数据库,之后将结果返回给客户端,这就是 redis 单线程处理数据的过程
在这里插入图片描述

为什么 Redis 是单线程的

1,使用 IO 多路复用来提高 IO 效率
2,主要限制是内存大小
3,多线程会增加 CPU 负担,比如上下文切换的损耗,还会出现线程安全问题,比如死锁

同时,redis 在6之后支持了多线程的功能,不过默认是关闭的,需要手动开启。引入多线程的目的是为了处理 IO,即读取命令和将文件拷贝到套接字的时候会用到多线程
在这里插入图片描述
在6之前,一些消耗时间的 IO 操作也不是主线程来完成的,比如持久化

文件事件和时间事件

redis 的 main 函数不停的轮询,等待事件的到达并且执行事件,Redis 执行的事件分两种:

1,文件事件:指对网络请求数据读取以及解析,数据的回复
2,时间事件:又分为让一段程序在指定的时间之后执行一次的定时事件,以及每隔一段时间执行一次的周期事件

如何区分定时与周期:如果方法的返回值为 ae.h/AE_NOMORE(-1),则这是定时事件,如果返回值为一个正整数 x,则代表每隔 x 毫秒执行一次的周期事件

#define AE_NOMORE -1

时间事件的执行如下:

服务器将所有时间都放在一个无序列表中,每当时间事件执行器运行时,它遍历整个链表查找所有已到达的时间事件,并调用相应的时间处理器

无序指的是不按照执行先后顺序进行排序,这就是为什么需要遍历这个链表,redis敢这么实现就是因为这个链表很短,redis 会按照 id 排序,由于新创建的时间事件是插入到链表的表头,所以链表中时间事件是按照 id 属性降序排序的

每一次轮询事实上是执行一次 aeProcessEvents 函数,该函数就是轮询的最小单位。该函数的过程如下:

先拿到从现在开始到最近需要执行的任务的开始时间,时长定位 T,这段时间就是属于文件事件的处理时间,时间事件会一直阻塞。而等到需要执行时,一般会有部分文件事件没有执行完,这时会让这些文件事件执行完之后再进行时间事件

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    int processed = 0, numevents;

    /* Nothing to do? return ASAP */
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;

    /* 获取离当前时间最近的时间事件,计算还有多少毫秒到达 */
    /* 如果毫秒为负数,说明已经到达,将其设置为0,并且不等待文件事件产生 */
    if (eventLoop->maxfd != -1 ||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
        ......
    }
    /* 等待文件事件产生,最大的阻塞时间为最近的时间事件到达时间 */
    numevents = aeApiPoll(eventLoop, tvp);

	/* 执行所有的文件事件以及时间事件 */
    if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
        eventLoop->aftersleep(eventLoop);
    for (j = 0; j < numevents; j++) {
    	......
    }
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);
}

由于执行完文件事件才处理时间事件的这个特性,时间事件的实际处理时间会比设定时间稍微晚一点

在多线程之中,时间事件会将非常耗时的持久化操作放到子线程或子进程中执行,例如主从复制、rehash 等比较重要的时间事件还是会在主线程中处理的

内存管理

内存管理是指数据库如何删除管理已经分配的内存空间

为什么要设置过期策略

1,数据储存在内存中,不及时清理就会 OOM
2,内存过期这个特性也有很多应用场景,比如验证码

如何储存数据的过期时间

有一个过期字典,过期字典就是 redisDb 结构中的 expires

该字典是一个键值对,键指向数据存放的地址,因此不会造成内存浪费,值指向一个 longlong 型的数,表示距离过期还有多长时间,具体来说,它是一个毫秒精度的 UNIX 时间戳

正常的 UNIX 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不过 redis 用的是毫秒数,比如2022-01-02 22:45:02过期则value为1641134702000

当然,引入这个过期时间肯定对 redis 的其他功能有影响,比如在持久化与磁盘输出到内存时对过期数据的判断,在集群复制时如何处理的等等

过期删除策略

既然有了判断数据过期时间的方法,那过期删除策略又是怎么样的呢,这个实现与时间相关,即到达一定时间后会发生什么事情,一般来说有以下三个思路:

1,定时删除:对于每个数据都生成一个定时器,在时间到了的时候执行删除语句
2,惰性删除:只有使用到这个数据时,才判断是否过期
3,定期删除:定时定量判断一些数据是否过期,这个定量是使用随机函数选择出一些时间进行判断

在 redis 中,使用的是单线程处理,并且自己实现了时间事件的处理方式——无序列表,遍历起来特别消耗时间,因此定时删除肯定是不行的了,在 redis 中删除策略是由两种组成的:

1,惰性删除
2,定期删除:redis 在执行这个操作时会消耗 cpu,导致请求卡顿或超时,因此执行定期删除的函数需要考虑到执行多长时间

内存淘汰机制

不过只使用过期删除清理内存肯定是跟不上内存消耗速度的,很快 redis 就会被充满。应该设置更多操作来保证内存不会OOM,这就引出了内存淘汰机制

内存淘汰(过期删除远远不能满足大量的数据删除):
Radis提供了8种内存淘汰机制
(使用最多)
1,allkeys-lru:最近最久未使用使用

(设置了过期时间)
2,volatile-ttl:从已设置过期时间的数据集中选出将要过期的淘汰
3,volatile-random:从已设置过期时间的数据集中随机淘汰部分
4,volatile-lru:从已设置过期时间的数据集中选出最近最少使用的淘汰
5,volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据淘汰

(奇奇怪怪)
6,allkeys-random:随机淘汰任意
7,no-eviction:禁止驱逐数据,超过内存会抛出异常
8,allkeys-lfu:选择最不经常使用的数据淘汰

内存碎片

内存碎片是指被 redis 申请的内存空间中,没有使用的一块区域。内存碎片不会对 redis 的使用产生什么性能问题,就是会浪费系统资源,导致 Redis 服务器内存不够用。通常以下两种方式都会产生内存碎片:

1,redis 向操作系统申请内存时,申请的内存空间事实上比实际使用的内存空间要大,这些多余的内存空间本来是为了应付某些特殊情况,不过也因此产生了内存碎片

2,频繁修改 Redis 中的数据会产生内存碎片,当 Redis 中的某个数据删除时,Redis 通常不会轻易释放内存给操作系统。比如 String 的变长字符串,在字符变长之后不会因为把存放的字符串修改短了而将这个变长字符串也变短

有一个公式可以计算内存碎片

mem_fragmentation_ratio (内存碎片率)= used_memory_rss (操作系统实际分配给 Redis 的物理内存空间大小)/ used_memory(Redis 内存分配器为了存储数据实际申请使用的内存空间大小)

注意,used_memory是 Redis 内存分配器申请使用的内存空间大小,而不是实际使用的内存空间大小。因此这个值可能过大也可能过小

说了这么多,如何去管理内存碎片呢

可以使用 info memory 命令查看 Redis 内存相关的信息,其中 mem_fragmentation_ratio 就是内存碎片率。同时,管理内存碎片只要一些简单的配置就可以了。自动碎片清理,只要设置了如下的配置,内存就会自动清理了。

config set activedefrag yes

如果想把 Redis 的配置,写到配置文件中去

config rewrite

如果你对自动清理的效果不满意,可以使用如下命令,直接试下手动碎片清理:

memory purge

bigkey

bigkey 指的是大小或者元素超过一定数量的 value 所对应的 key,它会占用很多内存空间,但是它的危害不止于此,bigkey 对性能也会有比较大的影响

比如由于单线程的特性,bigkey 在磁盘 IO 的时候会比较慢,同时,对它的操作速度也比较慢,会导致请求阻塞

我们在程序开发时应该注意这个问题。不要使用时间复杂度为 n 的命令,读取或者删除 bigkey 的时候,可以一部分一部分的读或者一部分一部分的删,比如一个大 map,我们先读部分 key,直到把 map 读完

持久化

持久化指按一定方法或者频率将数据写到内存中,这么做的目的是为了解决 redis 故障后恢复数据

RDB

也就是全量快照,创建某一时刻的数据库副本复制到磁盘中以此来完成持久化操作,在 redis 重启或者启动的时候会读取快照内容,这个 RDB 也可以用来做集群操作

rdb 之后会生成一个快照文件,该文件是经过压缩的二进制文件(Redis 默认采用 LZF 算法对 RDB 文件进行压缩)。通常有两种命令可以让数据库生成快照文件。一个是 save 命令,主线程执行,创建快照时会阻塞主线程。另一个是 bgsave 子线程执行,不会阻塞主线程。那数据库是怎么储存并且读取这个文件的呢

在执行 RDB 时操作采用写时复制技术,即快照复制时保存在一个临时文件中,复制完毕会替换原来的 rdb 文件,这么做的好处是防止 Redis 在保存快照时挂掉快照文件被毁坏

自动间隔保存

Redis 的自动间隔快照功能是判断在 XXX 时间间隔内发生了 XX 次数据变化就进行快照操作,一般使用的是 bgsave(自动快照),也可以手动快照,不过不推荐使用手动

可以向数据库提供配置:save 900 10

该命令的意思是在900秒内对数据库做了10次修改就进行落库操作。这两个属性就是自动快照的主要属性,redis 使用 saveparams 数组来保存它们

struct redisServer {
    // ...
    struct saveparam *saveparams;//记录了save保存条件的数组
    // ...
};

struct saveparam {
    time_t seconds;//秒数
    int changes;//修改数
};

数据库应该记录下上一次持久化的时间以及上一次持久化之后做了多少次修改操作才可以遍历数组,判断是不是应该进行自动持久化,因此redis 中的 redisService 存放了这两个数据,并且100毫秒判断一次是不是应该持久化

struct redisServer {
    // ...
    long long dirty;//修改计数器
    time_t lastsave;//上一次执行保存的时间
    // ...
};

RDB 文件结构

将文件放在磁盘或者在网络上传输就需要考虑读取的问题,计算机只储存01二进制文件,因此需要做的操作不过是那么几种,头标、尾标、计数、校验。rdb 文件也不例外
在这里插入图片描述
文件开头魔数五字节标记这是一个 redis 文件,db_version 四字节是指 redis 的版本

databases 就是储存数据库的地方,EOF 是一个特殊标记,1字节,标志 RDB 文件正文结束,check_sum 是一个8字节长的无符号整数,保留一个校验和。这样除了计数全都有了,计数一般出现在不定长的数据中,而数据库储存的数据一般都是不定长的,因此 databases 部分一定会出现计数
在这里插入图片描述
selectdb 表示接下来的数据属于哪个数据库的,pairs 文件中存放着各种数据

在 pairs 中,可以存放带过期时间的数据以及不带过期时间的数据,用一个特殊标识 EXPIRETIME_MS 来标记该数据是否带过期,ms 则表示过期时间。TYPE 表示数据类型,比如字符串键、哈希键等,KV 表示键值对,当然还包含一些编码方式等等
在这里插入图片描述
再读取 RDB 文件的时候不会全部读取到内存中,在读取带有过期时间的数据时,会先判断该数据是否已经过期,如果已经过期则直接删除该数据

AOF

开启 AOF 后会将更改 Redis 中的数据的命令写入硬盘中的 AOF 文件,也就是增量保存,一般推荐每秒写入磁盘一次。解决了数据持久化实时性的问题

载入与读取的实现

AOF 即 append(追加)only file,当服务器执行完一个写命令时,会以协议的格式将被执行的命令追加到 redisService 结构体的 aof_buf 末尾

struct redisService{
	sds aof_buf;
}

这里的 sds,就是 AOP 缓冲区了(如果每次写命令都追加写硬盘的操作,那么 Redis 的响应速度还要取决于硬盘的 IO 效率,显然不现实,所以 Redis 将写命令先写到 AOF 缓冲区),记录这些东西,可以阅读但是不清晰

*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
*3\r\n$3\r\nSET\r\n$3\r\nmsg\r\n$5\r\nhello\r\n
*5\r\n$4\r\nSADD\r\n$6\r\nfruits\r\n$5\r\napple\r\n$6\r\nbanana\r\n$6\r\ncherry\r\n

可以使用 appendfsync 来配置 aof 写入时机,可以配置为按秒写入磁盘,也可以配置每次有数据修改发生时都会写入AOF文件。默认为按秒写入磁盘

以上是 AOF 写入的实现,redis 的载入是通过一个虚拟的伪客户端来执行的。载入文件时,会生成一个不带网络连接的伪客户端,然后将 AOF 文件中的命令一条条的发生给服务器执行

AOF 重写

AOF 由于记录了所有的命令操作,它会比 RDB 文件大不少,甚至有可能装满磁盘,文件的体积越大,读取文件时消耗的时间就越多,重写就是为了解决体积过大的问题

AOF 重写是通过读取数据库中的键值对来完成的,读取完毕根据现在数据库内容生成写入的键值对,即生成新的 AOF 文件(此时其实和 RDB 差不多了,都是全量储存)。同时,这个文件的生成需要大量的写入,调用这个方法的线程会阻塞很长一段时间,因此 redis 会生成子进程来进行重写操作

重写在执行的时候会遇到一个问题,此时用户会修改数据库内容,如果不对重写的文件进行更新,会造成数据不一致。因此 redis 在重写时,会额外维持一个重写缓冲区,在修改数据库之后不仅会将记录添加到缓冲区中,还会添加到重写缓冲区中。在子进程完成自己的工作以后会发送消息给父进程

父进程会将重写缓冲区中的内容附加到 AOF 文件末尾,并对该 AOF 文件改名,原子的覆盖以前的 AOF 文件

Redis 目前已经支持 AOF 和 RDB 两种方式一起使用,如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头

redis 提供的其他功能

异步机制

这个是 redis 的一个重要优化,我们通常说,Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的,因为这些功能比较花费时间

主线程通过一个链表形式的任务队列和子线程进行交互

当 Redis 实例收到key-value删除和清空数据库的操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明删除已经完成。

这个时候删除操作还没有执行,等到后台子线程从任务队列中读取任务后,才开始实际删除键值对,并释放相应的内存空间。因此,我们把这种异步删除也称为惰性删除(lazy free)

和惰性删除类似,当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到任务队列中。后台子线程读取任务后,开始自行写入 AOF 日志,这样主线程就不用一直等待 AOF 日志写完了

发布与订阅

redis 提供了发布订阅模式,该模式指的是多个客户端在同一台主机上可以订阅多个频道。当其中一个客户端向某个频道发消息的时候,所有订阅该频道的客户端都可以收到信息,可以使用命令退订频道,其底层实现比较简单

向服务器发送 subscribe “news.it” 命令即可订阅 news.it 频道,如果在 redis 里没有该频道,则会创建该频道。PUBLISH 命令,此命令是用来发布消息。底层使用一个 dict 字典来存放所有的频道,值为 redisClient 列表表示所有的订阅者

struct redisClient{
	// 字典的键为频道,值为列表,存放客户端状态指针的列表
	dict *pubsub_channels;
	// 列表,存放客户端订阅模式的列表
	list *pubsub_patterns;
	......
}

redis 除了支持订阅频道以外还支持订阅模式,模式可以包含多个频道,比如 index.io 与 index.ht 都是频道,而 index.* 是模式。使用命令可以订阅模式,底层实现是将客户端与模式的对应关系存放进列表,每次向频道发送消息的时候都会额外遍历模式列表,向满足条件的模式发送消息

监视器

这个不是 redis 实现乐观锁处理事务并发问题的监视器,而是将 redis 的客户端当成一个整体去监视一个服务器,该功能的作用是,当某个 redis 的服务器执行任何命令时,会将命令发送给监视该服务器的每一个客户端

先服务器发送 montor 请求会让该客户端成为监视者

该命令的底层实现非常简单,首先在该客户端对应的 redisClient 中设置监视者标识,然后将该客户端指针放进服务器数据结构中的监视者列表。每次执行命令的时候,都会先遍历监视者列表,向每个客户端发送命令

redis 的这种实现在这个设计中至少有3个,即将指针放入列表,发送什么的时候遍历链表。比如发布与订阅,比如事务的乐观锁,它们的实现都差不多

慢查询日志

redis 提供慢查询日志来为我们查找需要优化的命令,其实现非常简单,但是所提供的功能却无比重要

我们可以修改 Redis 为慢查询对应提供的两个参数:slowlog-log-slower-than 和 slowlog-max-len 来更改慢查询规则,slowlog-log-slower-than 用于表示命令多久时间将该命令记录到日志中,而 slowlog-max-len 表示最多存放多少条日志,如果日志数目超过了这个数字,redis 自动将最久的一条除去。我们可以直接发送 config set 命令更改这两个参数

config set slowlog-log-slower-than 1000
config set slowlog-max-len 1200

也可以直接修改配置文件

slowlog-log-slower-than 1000
slowlog-max-len 1200

可以使用 slowlog get 命令获取慢查询日志,在 slowlog get 后面还可以加一个数字,用于指定获取慢查询日志的条数

每一条慢查询日志都有4个属性组成:

  • 唯一标识ID
  • 命令执行的时间戳
  • 命令执行时长
  • 执行的命名和参数

因此,redis 在底层对慢查询的实现是列表方式,每个列表都是包含这四个属性的结构体。redis 在命令执行的前后做 AOP 操作,在命令开始时,会记录当前的时间戳,命令执行结束时,将结束时的时间戳减去开始的时间戳,就可以得到命令执行时长。用该值判断是否超过了用户配置的 slowlog-log-slower-than,并且决定是否将其记录在数组中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值