首页 > 数据库 >Redis详解——性能

Redis详解——性能

时间:2023-07-02 23:00:33浏览次数:46  
标签:性能 Redis 碎片 详解 内存 CPU NUMA

一、rehash会导致操作阻塞吗?

如下图,Redis的字典结构中包含了两个哈希表: image.png

默认是往ht[0]写数据的,随着数据主键增多,Redis就会触发执行rehash操作了,主要步骤如下:

  • 给ht[1]分配更大的空间;
  • 将ht[0]的数据拷贝到ht[1];
  • 释放ht[0]的空间。

如果直接拷贝数据,肯定是会花很长时间的,进一步会导致阻塞Redis。

为了避免这个问题,Redis中使用的是渐进式的rehash。

二、如何高效存储数据

为了节省存储空间,针对不同的数据类型,优先采用更加紧凑型的编码格式。通过特定条件,可以让Redis选择紧凑型的编码: image.png

比如我们有一个很大集合的数据需要存储,如果数据有一定规律,并且键值大小都小于 hash-max-ziplist-value,并且可以划分成若干份,每份都可以保持在 hash-max-ziplist-entries以内,那么我们就可以分开来存储成多个Hash,这样就可以保证每个Hash都是用上ZIPLIST编码,极大的节省了空间。

 

但这是用时间换空间的做法:使用了压缩列表,也就意味着查询需要遍历ziplist,降低了查询的性能。在时间和空间之间,我们需要根据具体场景做出合理的抉择。

三、Redis中的性能杀手

Part III 部分,我们已经了解到,接收客户端网络执行命令的网络请求,网络开销对Redis的开销影响很小,那么在Redis中,究竟有哪些影响性能的点呢?这节,我们详细来探讨下。

 

不像线上性能分析,可以根据各种参数指标去挖掘定位性能点,为了摸底Redis的性能,那么就得从Redis的方法面面入手,我们将从以下各个维度去解析性能影响点。

3.1、内存数据操作

读操作

每当客户端请求数据的时候,都需要等待命令执行完,获取到执行的结果,所以执行命令的复杂度决定了性能的好坏,我们在实际操作中,要尽量使用复杂度低的命令,少用复杂度低的命令,控制一次读取的数据量。

以下是复杂度比较高的命令:

  • 集合统计排序

    • SINTER 交集,复杂度O(N*M),其中 N 是最小集合的基数,M 是集合的数量;
    • SUNION 并集,复杂度O(N),其中 N 是所有给定集合中元素的总数;
    • SDIFF 差集,复杂度O(N) ,其中 N 是所有给定集合中元素的总数;
    • SORT 排序,复杂度O(N+M*log(M)),其中 N 是列表或集合中要排序的元素数,M 是返回元素的数量。
  • 大数据量查询

    • HGETALL 返回存储在字典中所有的键值对,时间复杂度O(N),其中 N 是字典的大小;
    • SMEMBERS 返回集合中的所有成员key,时间复杂度O(N),其中N是设置的基数。

如果实在要执行此类操作,而数据量又比较大,建议将此类操作放到单独的从库中执行。

删除操作

如果我们删除了bigkey,那么就可能导致阻塞主线程。为什么呢?

 

因为删除操作除了会释放内存空间,还会把空闲空间插入操作系统的空闲内存块链表中,删除的key越大,那么就越耗时。

 

为了避免删除bigkey对主线程的阻塞,Redis 4.0开始新增了UNLINK命令实现惰性删除。删除bigkey,建议都使用UNLINK命令。

 

UNLINK命令是先释放掉字典和过期字典中的键值对引用,然后在符合以下任一条件的情况,决定是否需要放到lazyfree队列中进行异步删除:

  • Hash、Set底层采用哈希,并且元素个数超过64;
  • ZSET底层采用跳跃表,并且元素个数超过64;
  • List节点数量超过64。

否则,还是会直接删除。

 

可见惰性删除也不一定会起效,所以为了杜绝此类性能问题,最好避免在Redis中存储bigkey。

 

另外,如果我们执行FLUSHALL或者FLUSHDB,也会阻塞线程。为了避免此种情况,可以通过向FLUSHALL/FLUSHDB添加async异步清理选项,redis在清理整个实例或db时会以异步运行。

3.2、磁盘写

AOF日志落盘

如果我们的AOF写回策略是Always同步写,那么每次写数据的过程中,都会因写磁盘而阻塞住了。

 

如果可以容忍一秒钟的数据丢失,那么,我们可以把AOF写回策略设置为Everysec,这样就会通过异步线程去落盘了,从而避免阻塞主线程。

 

如果我们使用了Always策略,那么就需要注意了,如果刚好Redis在执行AOF重新,会导致大量的磁盘IO,最终导致操作系统fsync被阻塞,最终导致主线程也被fsync调用阻塞住了。

 

为了进一步减小重写AOF被阻塞的风险,可以设置为AOF重写是,不进行fsync:

no-appendfsync-on-rewrite yes

3.3、主从同步

当我们要进行主从同步的时候,首先,主库会生成一份完整的RDB,传给从库,从库首先执行FLUSHDB清空原来数据库,然后从库在载入RDB文件,这个过程会导致从库被阻塞。

3.4、切片集群

在切片集群场景,如果刚好有big key需要迁移到其他节点,那么就会导致主线程阻塞,因为Redis Cluster是用的同步迁移。迁移过程中会同时阻塞源节点和目标节点。

 

而如果使用Codis进行集群,则可以利用其异步迁移的特性减少big key迁移对集群性能的影响。

四、NUMA陷阱

Redis的性能跟CPU也有关?没错。接下来看看NUMA陷阱对Redis性能的影响。

4.1、NUMA

NUMA(Non-Uniform Memory Access),非统一内存访问架构,为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。

 

在NUMA下,处理器访问它自己的本地内存的速度比非本地内存(内存位于另一个处理器,或者是处理器之间共享的内存)快一些。

诞生背景

现代 CPU 的运行速度比它们使用的主内存快得多。在计算和数据处理的早期,CPU 通常比自己的内存运行得慢。

 

随着第一台超级计算机的出现,处理器和内存的性能线在 1960 年代出现了转折点。从那时起,CPU 越来越多地发现自己“数据匮乏”,不得不在等待数据从内存中到达时停止运行。1980 年代和 1990 年代的许多超级计算机设计专注于提供高速内存访问,而不是更快的处理器,使计算机能够以其他系统无法接近的速度处理大型数据集。

 

NUMA 尝试通过为每个处理器提供单独的内存来解决这个问题,避免在多个处理器尝试寻址同一内存。

NUMA架构解析

以下是NUMA架构图示:(图片来源:NUMA Deep Dive Part 2: System Architecture)

image.png

每个CPU有自己的物理核、L3缓存,以及连接的内存,每个物理核包括L1、L2缓存。不同处理器之间通过QPI总线连接。

 

每个CPU可以访问本地内存和系统中其他CPU控制的内存,由其他CPU管理的内存被视为远程内存,远程内存通过QPI访问。

 

在上图中,包含两个CPU,每个CPU包含4个核,每个CPU包含4个通道,每个通道最多3个DIMM,每个通道都填充了一个16 GB的内存,每个CPU用64GB内存,系统总共有128GB内存。

4.2、NUMA对REDIS性能有何影响?

读取远程内存导致的问题

在NUMA架构上,程序可以在不同的CPU上运行,假设一个程序现在CPU 0上面运行,并把数据保存到了CPU 0的内存中,然后继续在CPU 1上面运行,这个时候需要通过QPI访问远程内存,导致增加数据读取的时间,从而导致性能变差。

 

另外,每次切换CPU,就需要从L3缓存重新加载相关的指令和数据到L1、L2缓存中,如果L3缓存也找不到,则会从内存中加载,导致增加CPU的处理时间。

如何解决?

linux 操作系统提供了一个名为 NUMACTL 的函数,NUMACTL 提供了控制的能力:

  • NUMA 调度策略,例如,我想在哪些内核上运行这些任务
  • 内存放置策略,在哪里分配数据

为了解决这个问题,我们可以把我们的程序绑定到某一个CPU上面来调度执行,相关命令:

# 查看CPU信息
numactl --hardware 
# 指定进程在某个CPU上运行
taskset -c 1 ./redis-server
numactl --cpubind=0 --membind=0 ./redis-server

当然,也可以通过Taskset,进行设置,更详细的相关操作说明,参考:NUMACTL, taskset and other tools for controlling NUMA accesses notes

如果我们的网络中断处理程序也做了绑核操作,建议把Redis和网络中断成本绑定到同一个CPU上。

切记:Redis是需要用到多线程能力的(RDB、AOF文件生成、惰性删除...),我们不能把Redis只绑定到某一个内核中,否则就失去了多线程的能力。

五、内存问题

5.1、内存不足,性能急剧下降

在内存不足的情况下,操作系统会启用swap机制,这个时候,Redis主线程会导致大量磁盘IO,极大的增加Redis响应时间,导致Redis性能急剧下降。

 

为此,我们除了要给Redis服务设置最大内存之外,还需要监控Redis服务器的内存使用情况,避免与其他内存需求大的应用一起运行。

如何监控SWAP情况

# 找到Redis的进程号,进入/proc目录,进行查看
cd /proc/进程号
cat smaps | egrep '^(Swap|Size)'

如果发现很多几百MB或者上GB的swap,就说明实例的内存压力很大,需要排查是否内存不足导致Redis性能变慢。

5.2、内存大页机制

在发起BGSAVE命令之后,Redis会fork出一个子进程用于执行生成RDB文件。fork的时候采用的是写时复制(Copy-on-write)技术。不会立刻复制所有的内存,只是复制了页表,保证了fork执行足够快。

 

如果此时正好开启了内存大页机制,即使客户端请求的数据只有几k,也会拷贝2MB的大页,最终导致大量的内存拷贝,从而影响Redis的访问速度。

 

为了避免这种情况,可以关闭内存大页机制:

[root@VM_0_14_centos ~]# echo never > /sys/kernel/mm/transparent_hugepage/enabled 
[root@VM_0_14_centos ~]# echo never > /sys/kernel/mm/transparent_hugepage/defrag 
[root@VM_0_14_centos ~]# cat /sys/kernel/mm/transparent_hugepage/defrag
[always] madvise never
[root@VM_0_14_centos ~]# cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
# 验证是否生效
[root@VM_0_14_centos ~]# grep Huge /proc/meminfo
AnonHugePages:     18432 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
[root@VM_0_14_centos ~]# cat /proc/sys/vm/nr_hugepages
0

5.3、碎片

Redis并没有实现自己的内存池,也没有基于标准的系统内存分配器进行扩展。因此,系统内存分配器的性能和碎片率会对Redis产生一定的性能影响。向jemalloc这类的分配策略,是以一系列固定大小来进行内存空间的划分的,如8bit、16bit、32bit...这无形之中会导致一些分配的内存用不上,导致内存碎片的出现。

 

而内存碎片的出现,会导致内存空间利用率不高,导致无法分配新的空间。

 

如,在Redis中,SDS的空间预分配策略就很容易造成内存碎片。另外,频繁的删改操作、键值对大小不一致,也是造成内存碎片的原因之一。

 

为了监控Redis是否存在内存碎片,可以执行以下命令:

127.0.0.1:6379> INFO memory
...
mem_fragmentation_ratio:2.64
...

2.64,这个值大不大呢?

这个值的计算公式:

mem_fragmentation_ratio = used_memory_rss/ used_memory
  • used_memory_rss:操作系统分配给Redis实例的内存大小,表示Redis进程占用的物理内存大小;
  • used_memory:Redis使用其分配器分配的内存大小;
  • mem_fragmentation_ratio < 1:表示Redis内存分配超出了物理内存,操作系统正在进行内存交换;
  • mem_fragmentation_ratio > 1:合理的指标值;
  • mem_fragmentation_ratio > 1.5:表示Redis消耗了实际需要物理内存的150%以上,其中50%是内存碎片率。

如果这个值超过1.5(50%)了,说明碎片化还是比较严重的,需要进行优化了。

内存碎片清理方法

在Redis 4.0之前,解决内存碎片问题的唯一方法就是重启Redis了,从Redis 4.0开始,提供了主动内存碎片整理的方式。

通过以下配置,来让Redis触发自动内存清理:

# 启用主动碎片整理
activedefrag yes
# 启动活动碎片整理的最小碎片浪费量
active-defrag-ignore-bytes 100mb
# 启动活动碎片整理的最小碎片百分比
active-defrag-threshold-lower 10
# 主动碎片整理所用CPU时间最小占比,保证能够正常展开清理
active-defrag-cycle-min 25
# 主动碎片整理所用CPU时间最大占比,超过这个值,就停止清理,避免因为清理导致阻塞Redis
active-defrag-cycle-max 75

您可以在不用重启Redis的情况下,通过以下方式启用碎片整理:

redis-cli config set activedefrag "yes"

参考: https://www.itzhai.com/articles/redis-technology-insider-cache-data-structure-concurrency-clustering-and-algorithm.html

标签:性能,Redis,碎片,详解,内存,CPU,NUMA
From: https://blog.51cto.com/u_14014612/6607608

相关文章

  • Jmeter学习之一_连接与测试Redis
    Jmeter学习之一_连接与测试Redis简介下载:https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.zip注意事项:Don'tuseGUImodeforloadtesting!,onlyforTestcreationandTestdebugging.Forloadtesting,useCLIMode(wasNONGUI):jmeter-n......
  • mysql性能优化(博学谷)
      索引可以理解为一本书的目录,也是占页数,占存储空间的。key(关键字)-value(磁盘地址或行记录)。 根据关键字去索引里面查,数据结构是B+数,查询速度快。   哈希冲突: 通过key(k1,k2)的hashcode()获取哈希值,经过算法取得在数组上得下标位置,2个key取到了同一个下标,这就是哈希......
  • 龙蜥7.9 编译安装redis
    1、下载redis安装包2、我一般放在/home下面,redis默认安装在/usr/local/bin/下面,修改目录命令为:makePREFIX=/usr/local/redisinstall#安装编译环境yum-yinstallgcc-c++autoconfautomake#开始安装cd/hometarzxvfredis-7.0.11.tar.gzcdredis-7.0.11/makemakeinsta......
  • Redis详解——内存数据库
    前言在Redis详解——存储中介绍了Redis的基础数据结构,本文我们来看看Redis是如何组织这些数据类型,来构建一个内存数据库的。一、内存数据库以下是Redis数据库的结构:Redis服务器程序所有的数据库都保存在redisService结构体中,其中有个db数组,为redisDb类型,每个元素为一个数据......
  • springboot+token+redis,模拟登录
    登录测试的controller:loginTest.javapackagecom.example.demo.controller;importcom.example.demo.po.ResponseBean;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.web.bind.annotation.*;importjavax.annotation.Resource;i......
  • Python | with关键字详解
    with使用背景对于系统资源如文件、数据库连接、socket而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(释放)该资源。比如Python程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件,如果不关闭会出现什么情况呢?极端情况下会出现Toomanyopenfiles......
  • 提升性能的利器:深入解析SectionReader
    一.简介本文将介绍Go语言中的SectionReader,包括SectionReader的基本使用方法、实现原理、使用注意事项。从而能够在合适的场景下,更好得使用SectionReader类型,提升程序的性能。二.问题引入这里我们需要实现一个基本的HTTP文件服务器功能,可以处理客户端的HTTP请求来读取指......
  • Redis数据结构——链表
    前言Redis链表为双向无环链表!Redis使用了简单动态字符串,链表、字典(散列表)、跳跃表、整数集合、压缩列表这些数据结构来操作内存。本文继续来分析链表。 链表是一种非常常见的数据结构,在Redis中使用非常广泛,列表对象的底层实现之一就是链表。其它如慢查询,发布订阅,监视器等功......
  • Redis数据结构——字典
    前言字典在Redis中的应用非常广泛,数据库与哈希对象的底层实现就是字典。一、复习散列表1.1散列表散列表(哈希表),其思想主要是基于数组支持按照下标随机访问数据时间复杂度为O(1)的特性。可以说是数组的一种扩展。假设,我们为了方便记录某高校数学专业的所有学生的信息。要求可......
  • 最强优化指令大全 | 【Linux技术专题】「系统性能调优实战」终极关注应用系统性能调优
    Linux命令相关查看指标CPU指标vmstat指令vmstat-nm该命令用于每隔n秒采集系统的性能统计信息,共采集m次。[root@svr01]$vmstat13procs-----------memory-------------swap-------io------system-------cpu-----rbswpdfreebuffcachesiso......