如何将 Redis 的内存优化?
liebian365 2024-11-21 17:35 20 浏览 0 评论
作者 | 阿文
责编 | Elle
双十一、双十二都是买买买的节奏,但是你知道吗?像京东、淘宝这样的电子商城是如何扛得住几亿的请求吗?
下面是一个电商网站的基本架构,他包括了一个前端入口和一个缓存集群以及数据库集群。
一般来说,web 服务器作为前端入口,在 web 服务器内会把一些静态文件通过 CDN 分发到各个节点分摊服务器的请求压力。同时也能加速网站的访问。
当用户的一些查询请求,比如要查询一个商品的信息,会通过前端页面会经过缓存层,如果这些信息在缓存层就已经存在了,则直接返回给客户端,如果没有则去数据库集群查询,并经过缓存层缓存然后返回给客户端。
这样设计的好处是可以大大减小数据库集群的压力。我们都知道类似 MySQL 这种关系型数据库最大的瓶颈就是当出现像双十一这样的高并发请求时候会给 IO 带来巨大的压力,导致 IO 出现瓶颈。哪怕你的数据库优化做的再好,也不能改变这个基本事实。那么在数据库的上面加一道缓存层就可以大大缓解后端数据库的压力,通过类似 Redis 这样的内存型数据库将热点数据存储在内存中,可以大大提高读写效率和请求时延。
那么, 本文就来和大家分享下 Redis 内存的相关优化措施。
Redis 内存的消耗
要知道如何优化 Redis 的内存,我们需要先了解 Redis 都有哪些地方需要消耗内存。
众所周知,Redis 默认会把内存存储在内存中,当然你可以通过一些持久化方案,例如 AOF 或 RDB 的方式来将内存中的数据写入到磁盘中,但是 Redis 默认读取数据还是从内存中读取,因为内存的速度要比磁盘的读写速度快很多倍, 那在 Redis 中是如何对内存的消耗进行统计的呢?我们可以这样查看与内存相关的参数:
127.0.0.1:6379> info memory
# Memory
used_memory:812712
used_memory_human:793.66K
used_memory_rss:6033408
used_memory_rss_human:5.75M
used_memory_peak:812712
used_memory_peak_human:793.66K
total_system_memory:1850044416
total_system_memory_human:1.72G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:7.42
mem_allocator:jemalloc-3.6.0
其中参数含义:
? used_memory 以字节的形式表示 Redis 分配器分配的内存总量,也就是存储在 Redis 存储中的所有数据的占用量。
? used_memory_rss 是从操作系统的角度显示 Redis 进程占用的物理内存总量。
? used_memory_peak 是内存使用的最大值,表示 used_memory 的峰值。
? total_system_memory 表示操作系统的最大内存。
? used_memory_lua 表示 Lua 引擎所消耗的内存大小。
? maxmemory Redis 可分配使用的最大内存量。
? maxmemory_policy 当 Redis使用超过最大分配内存采用什么方式来剔除旧数据,默认是 noeviction
? mem_fragmentation_ratio 表示 used_memory_rss/used_memory 比值,表示内存碎片率
? mem_allocator 表示 Redis 的内存分配器,默认是 jemalloc
在 info memory 中,你会发现有类似 used_memory_human、used_memory_rss_human 等参数带 human 的,这些都是以可读的形式显示对应参数的信息。
在这些参数中,作为运维和相关开发人员,我们应该重点关注 used_memory_rss、used_memory、mem_fragmentation_ratio 这三个参数:
? 当 mem_fragmentation_ratio >1 时,说明used_memory_rss、used_memory 多出的部分内存并没有用于数据存储,而是被内存碎片所消耗,如果这两者相差很大,说明碎片率很严重。
? 当 mem_fragmentation_ratio < 1 时,这种情况通常是操作系统把 Redis Swap 到硬盘导致,出现这种情况要格外关注,由于硬盘的速度远远慢于内存,Redis 的性能会大幅下降。
内存消耗分类
介绍完了 Redis 的内存统计参数,我们来讲讲 Redis 的内存消耗主要是发生在哪些方面。Redis 的进程消耗内存主要包括:自身内存+对象内存+缓冲内存+内存碎片。Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右, used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。
下面对这几个方面进行一一讲解:
对象内存
这块内存消耗最大,它存储着用户所有的数据 ,我们都知道 Redis 存储的数据都是以 key-value 形式的数据类型,当你每次创建键值对时,至少要创建 2 个类型的对象,即key 和 value 对象。对象内存消耗我们可以简单理解为sizeof(keys)+sizeof(values)。
缓存内存
这块主要包括客户端缓存、复制积压缓存区、AOF 缓存区。
客户端缓存
对于客户端缓存,我们可以使用 client-output-buffer-limit 查看相关配置,默认情况下,这部分配置为
client-output-buffer-limit normal 0 0 0 # 客户端
client-output-buffer-limit replica 256mb 64mb 60 # 从客户端
client-output-buffer-limit pubsub 32mb 8mb 60 # 订阅客户端
对于普通客户端来说,限制为0,也就是不限制。因为普通客户端通常采用阻塞式的消息应答模式,何谓阻塞式呢?如:发送请求,等待返回,再发送请求,再等待返回。这种模式下,通常不会导致Redis服务器输出缓冲区的堆积膨胀;
对于Pub/Sub客户端(也就是发布/订阅模式),大小限制是8M,当输出缓冲区超过8M时,会关闭连接。持续性限制是,当客户端缓冲区大小持续60秒超过2M,则关闭客户端连接;
对于slave客户端来说,大小限制是256M,持续性限制是当客户端缓冲区大小持续60秒超过64M,则关闭客户端连接。
我们可以使用如下方式查看这三种客户端的配置
127.0.0.1:6379> CONFIG GET client-output-buffer-limit
1) "client-output-buffer-limit"
2) "normal 0 0 0 slave 268435456 67108864 60 pubsub 33554432 8388608 60"
这里的 normal 0 0 0 表示普通客户端,值都为 0,表示 Redis 默认不做限制,一般普通客户端的内存消耗几乎可以忽略不计,但是如果有大佬的慢连接客户端接入就会存在问题,我们可以使用 maxclients 来限制最大客户端连接
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "10000"
slave 268435456 67108864 60 表示的是从客户端的连接,默认的限制是最大 256M 和 60s 内不超过 64M ,如需调整,可以使用如下方式设置,其中 1 表示最大限制,3 表示多少秒,2 表示多少秒内不能超过的最大限制。
config set client-output-buffer-limit 'slave 1 2 3'
复制积压缓冲区
复制积压缓冲区是 Redis 2.8 版本以后提供的一个可重用的固定大小的缓冲区,目的是用于实现部分复制功能,它主要根据参数 repl-backlog-size 来控制,默认是 1MB,对于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区,因此可以设置较大的缓冲区空间,如100MB,它可以有效避免全量复制。
AOF 缓冲区
这部分空间用于在Redis重写期间保存最近的写入命令 ,这部分的消耗主要取决于 AOF 的重写时间和写入量。,通常占用很小。
内存碎片
Redis默认的内存分配器采用jemalloc,jemalloc在64位系统中将内存空间划 分为:小、大、巨大三个范围:
? 小:8byte,192byte,256byte,...,512byte
? 大:[4KB,8KB,12KB,...,4072KB]
? 巨大:[4MB,8MB,12MB,...]
比如当保存5KB对象时jemalloc可能会采用8KB的块存储,而剩下的3KB 空间变为了内存碎片不能再分配给其他对象存储。内存碎片问题虽然是所有 内存服务的通病,但是jemalloc针对碎片化问题专门做了优化,一般不会存在过度碎片化的问题,正常的碎片率(mem_fragmentation_ratio)在1.03左右。
此外子进程也会存在一些内存消耗,这部分消耗主要是耗费在 AOF/RDB 重写时创建子进程的内存消耗,Redis执行fork操作产生的子进程内存占用量对外表现为与父进程相同, 理论上需要一倍的物理内存来完成重写操作。但Linux具有写时复制技术 (copy-on-write),父子进程会共享相同的物理内存页,当父进程处理写请求时会对需要修改的页复制出一份副本完成写操作,而子进程依然读取fork 时整个父进程的内存快照。
内存优化
我们从操作系统和 Redis 本身的优化来讲讲如何优化内存。
操作系统层面
1.vm.overcommit_memory
Linux操作系统对大部分申请内存的请求都回复 yes,以便能运行更多的程序。因为申请内存后,并不会马上使用内存,这种技术叫做 overcommit。vm.overcommit_memory用来设置内存分配策略,有三个可选值:
? 0 表示内核将检查是否有足够的可用内存,如果有足够的内存,内存申请通过,否则申请失败。
? 1 表示允许超量使用内存直到用完为止。
? 2 表示内核绝不过量使用内存,即系统整个内存地址空间不能超过 swap+50%的 RAM 值。
建议将值设置为 1,避免发生 fork 失败。
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
Redis设置合理的maxmemory,保证机器有20%~30%的闲置内存。
2.swappiness
当物理内存不足时,可以将一部分内存页进行swap操作,但是swap 空间是由硬盘提供的,对于需要高并发、高吞吐的应用来说,磁盘IO通常会成为系统瓶颈。
在Linux中,并不是要等到所有物理内存都使用完才会使用到swap,系 统参数swppiness会决定操作系统使用swap的倾向程度。
swappiness的取值范 围是0~100,swappiness的值越大,说明操作系统可能使用swap的概率越 高,swappiness值越低,表示操作系统更加倾向于使用物理内存。默认是 60,它的含义如下:
? 0 Linux kernel 3.5+ 宁愿 OOM 也不用 swap,Linux kernel 3.4以下,宁愿 swap 也不用 OOM
? 1 Linux kernel 3.5+ 宁愿 swap 也不 OOM
? 60 默认值
可以如下方式设置该参数,{bestvalue}为swappiness 的值:
echo vm.swappiness={bestvalue} >> /etc/sysctl.conf
3.THP
Linux Kernel内存中的Transparent Huge Pages(THP)机制,默认会开启 THP,虽然开启THP可以降 低fork子进程的速度,但之后copy-on-write期间复制内存页的单位从4KB变 为2MB,如果父进程有大量写命令,会加重内存拷贝量,从而造成过度内存 消耗。
对于子进程的消耗,我们可以通过如下方式来进行优化:
? 设置sysctl vm.overcommit_memory=1允许内核可以分配所有的物理 内存,防止Redis进程执行fork时因系统剩余内存不足而失败。
? 关闭 THP,防止copy-on- write期间内存过度消耗。
echo never > /sys/kernel/mm/transparent_hugepage/enabled
在设置THP配置时需要注意:有些Linux的发行版本没有将THP放 到/sys/kernel/mm/transparent_hugepage/enabled中,例如Red Hat6以上的THP配 置放到/sys/kernel/mm/redhat_transparent_hugepage/enabled中。而有些版本的 Redis源码中 检查THP时,把THP位置写死:
FILE *fp = fopen("/sys/kernel/mm/transparent_hugepage/enabled","r"); if (!fp) return 0;
所以在发行版中,虽然没有THP的日志提示,但是依然存在THP所带来 的问题:
echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled
4.OOM Killer
OOM killer会在可用内存不足时选择性地杀掉用户进程,OOM killer进程会为每个用户进程设置一个权值,这个权值越高,被 Kill 的概率就越高,反之概率越低。每个进程的权值存放在/proc/{progress_id}/oom_score中,这个值是受/proc/{progress_id}/oom_adj的控制,oom_adj在不同的Linux版本中最小值不同,设置方法:
echo {value} > /proc/${process_id}/oom_adj
对于Redis所在的服务器来说,可以将所有Redis的oom_adj设置为最低 值或者稍小的值,降低被OOM killer杀掉的概率:
for redis_pid in $(pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done
Oom_adj 只是起到辅助作用,合理的分配和使用内存才是最重要的。
4.ulimit
Linux中,可以通过ulimit查看和设置系统当前用户进程的资源数。其中ulimit-a命令包含的open files参数,是单个用户同时打开的最大文件个数:
[root@redis ~]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 6972
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 6972
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
Redis允许同时有多个客户端通过网络进行连接,可以通过配置 maxclients来限制最大客户端连接数。对Linux操作系统来说,这些网络连接 都是文件句柄。
设置方法如下:
ulimit –Sn {max-open-files}
通常来说,ulimit的值要 >= maxclients 的值。
5.TCP backlog
Redis默认的tcp-backlog值为511,可以通过修改配置tcp-backlog进行调整:
echo 511 > /proc/sys/net/core/somaxconn
Redis 自身优化
1.设置内存上限
使用maxmemory参数限制最大可用内存,当超出内存上限maxmemory时使用LRU等删除策略释放空间以及防止所用内存超过服务器物理内存。
2.配置内存回收策略
Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受maxmemory-policy参数控制,Redis支持6种策略,如下所示:
? noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此 时Redis只响应读操作。
? volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
? allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性, 直到腾出足够空间为止。
? allkeys-random:随机删除所有键,直到腾出足够空间为止。
? volatile-random:随机删除过期键,直到腾出足够空间为止。
? volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果 没有,回退到noeviction策略。
3.键值对优化
降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。在完整描述业务情况下,键值越短越好。值对象缩减比较复杂,应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。
4.共享对象池
共享对象池是指Redis内部维护[0-9999] [0-9999]的整数对象 池,用于节约内存。
但是共享对象池与maxmemory+LRU策略冲突,使用时需要注意。对于ziplist编码的值对象,即使内部数据为整数也无法使用共享对象池,因为ziplist使用压缩且内存连续的结构,对象共享判断成本过高。
5.字符串优化
字符串对象是Redis内部最常用的数据类型。所有的键都是字符串类 型,值对象数据除了整数之外都使用字符串存储。在使用过程中应当尽量优先使用整数,比字符串类型更节省空间。并且要优化字符串使用,避免预分配造成的内存浪费。使用ziplist压缩编码优化hash、list等结构,注重效率和空间的平衡,使用intset编码优化整数集合。使用ziplist编码的hash结构降低小对象链规模。
6.编码优化
Redis对外提供了多种数据类型,但是Redis内部对于不同类型的数据使用的内部编码不一样。内部编码不同将直接影响数据的内存占用和读写效率。
7.控制键的数量
当使用Redis存储大量数据时,通常会存在大量键,过多的键同样会消 耗大量内存。
声明:本文系作者投稿。
相关推荐
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
-
大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
-
这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...
- codeblocks和VS2019下的fltk使用中文
-
在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
-
FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
-
IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
-
预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...
- Ubuntu系统下COM口测试教程(ubuntu port)
-
1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
-
很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...
- 新阁上位机开发---10年工程师的Modbus总结
-
前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...
- 创建你的第一个可运行的嵌入式Linux系统-5
-
@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...
- 如何在Linux下给zigbee CC2530实现上位机
-
0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...
- Python实现串口助手 - 03串口功能实现
-
串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...
- 为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口
-
UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...
- 同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理
-
串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...
- 嵌入式linux为什么可以通过PC上的串口去执行命令?
-
1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
- codeblocks和VS2019下的fltk使用中文
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
- Ubuntu系统下COM口测试教程(ubuntu port)
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
- 新阁上位机开发---10年工程师的Modbus总结
- 创建你的第一个可运行的嵌入式Linux系统-5
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)