zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

使用Redis时我们主要关注啥

Redis 使用 我们 主要 关注
2023-06-13 09:11:16 时间

导读:本文介绍,在使用 Redis 过程中我们需要关注的两个主要方面:QPS、内存

在实际使用Redis过程中我们需要关注两点:

  1. QPS,即Redis每秒处理请求数。Redis作为单线程架构服务,如果发生阻塞将是致命的。
  2. 内存,Redis作为内存数据库,考虑到内存价格昂贵,如何充分合理的使用内存,是Redis使用者必须考虑的问题。本节,将就这两个问题做重点分析。

阻塞

我们知道,Redis是典型单线程架构。这种架构下,所有的读写命令操作都是由主线程完成。主线程的处理能力将决定着Redis整体的性能。那么,哪些因素会导致Redis阻塞呢?

Redis自身因素

Redis本身的架构设计会是导致阻塞的潜在原因。

1.Redis提供的操作命令很多是O(N)时间复杂度的,如果使用不当会导致单条命令执行时间过长而阻塞后续请求。

2. 持久化,持久化涉及写磁盘操作,如果频繁写AOF文件,或者频繁生成RDB到fork子进程也会导致主进程阻塞。

3. Redis是单线程的,所以只能使用一个CPU,如果CPU使用率过高,必然导致主进程阻塞。在小对象存取时候,内存速度和带宽看上去不是很重要,但是对大对象(> 10KB),它们就变得重要起来。图-1给出了2020年对于硬件执行速度。从图可以看出,当前从内存中读取连续1,000,000B的数据需要0.003ms,主存的一次访问耗时100ns,对于value比较多的复杂数据结构整体读取可能会耗时数百毫秒甚至更高。比如HGETALL一个上百万feildhash会导致Redis主进程阻塞数秒。

  1. 图-1 Latency Numbers Every Programmer Should Know

对于持久化,有如下三点会导致阻塞:

1. 生成RDB和AOF重写,需要fork子进程。Redis主进程调用fork系统调用产生子进程,由子进程负责完成持久化和重新工作,如果fork本身过长,将会导致主进程阻塞。

2. AOF写磁盘,aof_buf数据同步到磁盘磁盘上是由后台线程来完成的,由于涉及磁盘操作,当磁盘压力过大,后台线程在执行fsync时,可能需要等待,直到写入完成。当主线程发现距离上次fsync成功时间超过2s将会阻塞起来,直到fsync完成。

3. 系统开启HugePage写操作导致阻塞,重写期间为了减小内存开销,会利用Linux系统支持的COW机制,只有在内存页有写入操作时才会复制该页,如果开启了HugePage每次复制的内存页将会从4kB变成2MB,放大了512倍,将会拖慢写入速度。

环境因素

1. CPU竞争,这部分需要从两个点来看,第一,Redis是CPU密集型应用,当和其他服务(尤其是多核CPU密集型应用)混布时会发生资源抢占情况,导致Redis吞吐下降;第二,如果是对Redis做了核绑定,正常情况下这种优化能够确保Redis独占一个CPU核,但当Redis进程fork子进程进行RDB生成或者AOF重写时,会和父进程共享该固定CPU核,导致父进程吞吐下降。

2. 内存交换,Redis高性能的一个决定性前提就是数据都在内存中,如果数据swap到磁盘中,将导致读写速度急剧下降。

3. 网络问题,网络问题是Redis阻塞原因的怀疑重点,主要有:连接拒绝、网络延迟、网卡软中断

内存

Redis最大的特性就是数据都在内存中,那么如何合理的规划这些数据就至关重要了。

Redis 内存消耗

Redis内存消耗主要包括:

1. 自身内存,极少,通常在3MB左右;

2. 对象内存,最大的一部分,存储着用户数据,包括keyvalue,可以简单理解为sizeof(key) + sizeof(value)

3. 缓冲内存,包括,客户端缓冲区(所有接入到Redis服务器TCP连接的输入输出缓冲,输入缓冲区最大1G,输出可以通过client-output-buffer-limit控制。主要包括:普通客户端、从客户端、订阅客户端)、复制积压缓冲区(Redis 2.8版本之后提供了一个固定大小的、用户复制功能的缓冲区,根据repl-backlog-size控制,默认1MB)、AOF缓冲区(此缓冲区用于AOF重写期间写入命令);

4. 内存碎片

5. 子进程内存消耗,这部分主要是Redis在进行RDB生成和AOF重写期间fork子进程消耗内存。其中,1~3之和是used_memory,4是used_memory_rss-used_memory

Redis 内存回收

有两个场景会触发内存回收:

1. 过期数据删除

2. 惰性删除,当读取到带过期时间的数据,并且数据已经过期,这时会触发删除操作,并向客户端返回-2

3. 定时删除,Redis内部维护了一个定时任务,默认每秒运行10次。通过自适应算法来删除过期数据。

4. 内存使用量超过maxmemory触发数据淘汰,支持如下删除策略:

  1. noeviction,默认策略,不删除任何数据;
  2. volation-lru,对设置了过期时间的数据,通过模拟LRU算法进行删除,直到获取足够空间(线上使用策略);
  3. allkeys-lru,对所有数据,通过模拟LRU算法进行删除,直到获取足够空间;
  4. allkeys-random,对全体数据进行随机删除,直到获取足够空间;
  5. valatile-random,对设置了过期时间的数据进行随机删除,直到获取足够空间;
  6. valatile-ttl,根据数据TTL属性,删除最近要过期的数据,如果没有则相当于noeviction

redisObject对象

Redis所有对象在内部都定义为redisObject结构体,如代码-1。通过代码-1可知,一个redisObject结构体需要占16B。Redis存储的所有数据类型(如stringhashlistsetzset等)都通过redisObject来封装。结合前面数据结构小结,我们可知同一种数据结构至少有两种编码方式,不同的编码需要使用的存储空间是不同,如何合理地使用数据结构和编码将影响到存储空间的使用效率。

// redis-5.0.0 src/server.h 602
typedef struct redisObject {
unsigned type:4; // 对象类型 4bits
unsigned encoding:4; // 编码类型 4bits
unsigned lru:LRU_BITS; // LRU计时器 24bits
int refcount; // 引用计数 32bits
void *ptr; // 数据指针 64bits
} robj;

代码-1 redisObject 结构体

reference

Redis官网 Redis开发与运维 How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances Latency Numbers Every Programmer Should Know