Redis优化经验

内存管理优化

Redis Hash是value内部为一个HashMap,如果该Map的成员数比较少,则会采用类似一维线性的紧凑格式来存储该Map, 即省去了大量指针的内存开销,这个参数控制对应在redis.conf配置文件中下面2项:

hash-max-zipmap-entries 64 hash-max-zipmap-value 512

当value这个Map内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即value内部有64个以下的成员就是使用线性紧凑存储,超过该值自动转成真正的HashMap。

hash-max-zipmap-value 含义是当 value这个Map内部的每个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。

以上2个条件任意一个条件超过设置值都会转换成真正的HashMap,也就不会再节省内存了,那么这个值是不是设置的越大越好呢,答案当然是否定的,HashMap的优势就是查找和操作的时间复杂度都是O(1)的,而放弃Hash采用一维存储则是O(n)的时间复杂度,如果

成员数量很少,则影响不大,否则会严重影响性能,所以要权衡好这个值的设置,总体上还是最根本的时间成本和空间成本上的权衡。

list-max-ziplist-value 64 list-max-ziplist-entries 512

list数据类型节点值大小小于多少字节会采用紧凑存储格式、list数据类型多少节点以下会采用去指针的紧凑存储格式。

内存预分配:

Redis内部实现没有对内存分配方面做过多的优化(对比Memcache),在一定程度上会存在内存碎片,不过大多数情况下这个不会成为Redis的性能瓶颈,不过如果在Redis内部存储的大部分数据是数值型的话,Redis内部采用了一个shared integer的 方式来省去分配内存的开销,即在系统启动时先分配一个从1~n 那么多个数值对象放在一个池子中,如果存储的数据恰好是这个数值范围内的数据,则直接从池子里取出该对象,并且通过引用计数的方式来共享,这样在系统存储 了大量数值下,也能一定程度上节省内存并且提高性能,这个参数值n的设置需要修改源代码中的一行宏定义REDIS_SHARED_INTEGERS,该值 默认是10000,可以根据自己的需要进行修改,修改后重新编译就可以了。

持久化机制:

定时快照方式(snapshot):

该持久化方式实际是在Redis内部一个定时器事件,每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件,如果满足则通 过操作系统fork调用来创建出一个子进程,这个子进程默认会与父进程共享相同的地址空间,这时就可以通过子进程来遍历整个内存来进行存储操作,而主进程 则仍然可以提供服务,当有写入时由操作系统按照内存页(page)为单位来进行copy-on-write保证父子进程之间不会互相影响。

该持久化的主要缺点是定时快照只是代表一段时间内的内存映像,所以系统重启会丢失上次快照与重启之间所有的数据。

基于语句追加方式(aof):

aof方式实际类似mysql的基于语句的binlog方式,即每条会使Redis内存数据发生改变的命令都会追加到一个log文件中,也就是说这个log文件就是Redis的持久化数据。

aof的方式的主要缺点是追加log文件可能导致体积过大,当系统重启恢复数据时如果是aof的方式则加载数据会非常慢,几十G的数据可能需要几小时才能加载完,当然这个耗时并不是因为磁盘文件读取速度慢,而是由于读取的所有命令都要在内存中执行一遍。另外由于每条命令都要写log,所以使用aof的方式,Redis的读写性能也会有所下降。

可以考虑将数据保存到不同的Redis实例中,每个实例的内存大小在2G左右,避免将鸡蛋放到一个篮子里,既可以减少缓存失效给系统带来的影响,又可以加快数据恢复的速度,不过同时也给系统设计带来了一定的复杂性。

 

Redis持久化崩溃问题:

有Redis线上运维经验的人会发现Redis在物理内存使用比较多,但还没有超过实际物理内存总容量时就会发生不稳定甚至崩溃的 问题,有人认为是基于快照方式持久化的fork系统调用造成内存占用加倍而导致的,这种观点是不准确的,因为fork 调用的copy-on-write机制是基于操作系统页这个单位的,也就是只有有写入的脏页会被复制,但是一般你的系统不会在短时间内所有的页都发生了写 入而导致复制,那么是什么原因导致Redis崩溃的呢?

答案是Redis的持久化使用了Buffer IO造 成的,所谓Buffer IO是指Redis对持久化文件的写入和读取操作都会使用物理内存的Page Cache,而大多数数据库系统会使用Direct IO来绕过这层Page Cache并自行维护一个数据的Cache,而当Redis的持久化文件过大(尤其是快照文件),并对其进行读写时,磁盘文件中的数据都会被加载到物理内 存中作为操作系统对该文件的一层Cache,而这层Cache的数据与Redis内存中管理的数据实际是重复存储的,虽然内核在物理内存紧张时会做 Page Cache的剔除工作,但内核很可能认为某块Page Cache更重要,而让你的进程开始Swap ,这时你的系统就会开始出现不稳定或者崩溃了。我们的经验是当你的Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了。

总结:

  1. 根据业务需要选择合适的数据类型,并为不同的应用场景设置相应的紧凑存储参数。
  2. 当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能以及最大的内存使用量。
  3. 如果需要使用持久化,根据是否可以容忍重启丢失部分数据在快照方式与语句追加方式之间选择其一,不要使用虚拟内存以及diskstore方式。
  4. 不要让你的Redis所在机器物理内存使用超过实际内存总量的3/5。

redis.conf中的maxmemory选项,该选项是告诉Redis当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的Redis不会因为使用了过多的物理内存而导致swap,最终严重影响性能甚至崩溃。

redis.conf文件中 vm-enabled 为 no

常用内存优化手段与参数

通过我们上面的一些实现上的分析可以看出redis实际上的内存管理成本非常高,即占用了过多的内存,作者对这点也非常清楚,所以提供了一系列的参数和手段来控制和节省内存,我们分别来讨论下。

首先最重要的一点是不要开启Redis的VM选项,即虚拟内存功能,这个本来是作为Redis存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,但是其内存管理成本也非常的高,并且我们后续会分析此种持久化策略并不成熟,所以要关闭VM功能,请检查你的redis.conf文件中 vm-enabled 为 no。

其次最好设置下redis.conf中的maxmemory选项,该选项是告诉Redis当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的Redis不会因为使用了过多的物理内存而导致swap,最终严重影响性能甚至崩溃。

另外Redis为不同数据类型分别提供了一组参数来控制内存使用,我们在前面详细分析过Redis Hash是value内部为一个HashMap,如果该Map的成员数比较少,则会采用类似一维线性的紧凑格式来存储该Map, 即省去了大量指针的内存开销,这个参数控制对应在redis.conf配置文件中下面2项:

hash-max-zipmap-entries 64
hash-max-zipmap-value 512
hash-max-zipmap-entries

含义是当value这个Map内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即value内部有64个以下的成员就是使用线性紧凑存储,超过该值自动转成真正的HashMap。

hash-max-zipmap-value 含义是当 value这个Map内部的每个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。

以上2个条件任意一个条件超过设置值都会转换成真正的HashMap,也就不会再节省内存了,那么这个值是不是设置的越大越好呢,答案当然是否定的,HashMap的优势就是查找和操作的时间复杂度都是O(1)的,而放弃Hash采用一维存储则是O(n)的时间复杂度,如果

成员数量很少,则影响不大,否则会严重影响性能,所以要权衡好这个值的设置,总体上还是最根本的时间成本和空间成本上的权衡。

同样类似的参数还有:

list-max-ziplist-entries 512

说明:list数据类型多少节点以下会采用去指针的紧凑存储格式。

list-max-ziplist-value 64 

说明:list数据类型节点值大小小于多少字节会采用紧凑存储格式。

set-max-intset-entries 512 

说明:set数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。

最后想说的是Redis内部实现没有对内存分配方面做过多的优化,在一定程度上会存在内存碎片,不过大多数情况下这个不会成为Redis的性能瓶颈,不过如果在Redis内部存储的大部分数据是数值型的话,Redis内部采用了一个shared integer的方式来省去分配内存的开销,即在系统启动时先分配一个从1~n 那么多个数值对象放在一个池子中,如果存储的数据恰好是这个数值范围内的数据,则直接从池子里取出该对象,并且通过引用计数的方式来共享,这样在系统存储了大量数值下,也能一定程度上节省内存并且提高性能,这个参数值n的设置需要修改源代码中的一行宏定义REDIS_SHARED_INTEGERS,该值默认是10000,可以根据自己的需要进行修改,修改后重新编译就可以了。

Redis的持久化机制

Redis由于支持非常丰富的内存数据结构类型,如何把这些复杂的内存组织方式持久化到磁盘上是一个难题,所以Redis的持久化方式与传统数据库的方式有比较多的差别,Redis一共支持四种持久化方式,分别是:

  • 定时快照方式(snapshot)
  • 基于语句追加文件的方式(aof)
  • 虚拟内存(vm)
  • Diskstore方式

在设计思路上,前两种是基于全部数据都在内存中,即小数据量下提供磁盘落地功能,而后两种方式则是作者在尝试存储数据超过物理内存时,即大数据量的数据存储,截止到本文,后两种持久化方式仍然是在实验阶段,并且vm方式基本已经被作者放弃,所以实际能在生产环境用的只有前两种,换句话说Redis目前还只能作为小数据量存储(全部数据能够加载在内存中),海量数据存储方面并不是Redis所擅长的领域。下面分别介绍下这几种持久化方式:

定时快照方式(snapshot):

该持久化方式实际是在Redis内部一个定时器事件,每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件,如果满足则通过操作系统fork调用来创建出一个子进程,这个子进程默认会与父进程共享相同的地址空间,这时就可以通过子进程来遍历整个内存来进行存储操作,而主进程则仍然可以提供服务,当有写入时由操作系统按照内存页(page)为单位来进行copy-on-write保证父子进程之间不会互相影响。

该持久化的主要缺点是定时快照只是代表一段时间内的内存映像,所以系统重启会丢失上次快照与重启之间所有的数据。

基于语句追加方式(aof):

aof方式实际类似mysql的基于语句的binlog方式,即每条会使Redis内存数据发生改变的命令都会追加到一个log文件中,也就是说这个log文件就是Redis的持久化数据。

aof的方式的主要缺点是追加log文件可能导致体积过大,当系统重启恢复数据时如果是aof的方式则加载数据会非常慢,几十G的数据可能需要几小时才能加载完,当然这个耗时并不是因为磁盘文件读取速度慢,而是由于读取的所有命令都要在内存中执行一遍。另外由于每条命令都要写log,所以使用aof的方式,Redis的读写性能也会有所下降。

虚拟内存方式:

虚拟内存方式是Redis来进行用户空间的数据换入换出的一个策略,此种方式在实现的效果上比较差,主要问题是代码复杂,重启慢,复制慢等等,目前已经被作者放弃。

diskstore方式:

diskstore方式是作者放弃了虚拟内存方式后选择的一种新的实现方式,也就是传统的B-tree的方式,目前仍在实验阶段,后续是否可用我们可以拭目以待。

Redis持久化磁盘IO方式及其带来的问题

有Redis线上运维经验的人会发现Redis在物理内存使用比较多,但还没有超过实际物理内存总容量时就会发生不稳定甚至崩溃的问题,有人认为是基于快照方式持久化的fork系统调用造成内存占用加倍而导致的,这种观点是不准确的,因为fork 调用的copy-on-write机制是基于操作系统页这个单位的,也就是只有有写入的脏页会被复制,但是一般你的系统不会在短时间内所有的页都发生了写入而导致复制,那么是什么原因导致Redis崩溃的呢?

答案是Redis的持久化使用了Buffer IO造成的,所谓Buffer IO是指Redis对持久化文件的写入和读取操作都会使用物理内存的Page Cache,而大多数数据库系统会使用Direct IO来绕过这层Page Cache并自行维护一个数据的Cache,而当Redis的持久化文件过大(尤其是快照文件),并对其进行读写时,磁盘文件中的数据都会被加载到物理内存中作为操作系统对该文件的一层Cache,而这层Cache的数据与Redis内存中管理的数据实际是重复存储的,虽然内核在物理内存紧张时会做Page Cache的剔除工作,但内核很可能认为某块Page Cache更重要,而让你的进程开始Swap ,这时你的系统就会开始出现不稳定或者崩溃了。我们的经验是当你的Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了。

下图是Redis在读取或者写入快照文件dump.rdb后的内存数据图:

总结:

  1. 根据业务需要选择合适的数据类型,并为不同的应用场景设置相应的紧凑存储参数。
  2. 当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能以及最大的内存使用量。
  3. 如果需要使用持久化,根据是否可以容忍重启丢失部分数据在快照方式与语句追加方式之间选择其一,不要使用虚拟内存以及diskstore方式。
  4. 不要让你的Redis所在机器物理内存使用超过实际内存总量的3/5。
时间: 2024-11-10 10:15:07

Redis优化经验的相关文章

SQL优化经验

SQL 优化经验总结34条 我们要做到不但会写SQL,还要做到写出性能优良的SQL,以下为笔者学习.摘录.并汇总部分资料与大家分享! (1) 选择最有效率的表名顺序(只在基于规则的优化器中有效): ORACLE 的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表.如果有3个以上的表连接查询, 那就需要选择交叉表(intersection tabl

20多条MySQL 性能优化经验分享

当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能.这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库.希望下面的这些优化技巧对你有用. 1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存.这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的.当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了. 这里最主要

项目优化经验分享(八)TeamLeader经验总结

引言 通过前面的七篇博客,我把自己在项目优化过程的经验进行了分享,今天这篇博客,作为一个总结,就来讲讲作为一个TeamLeader,在项目管理中遇到的问题和解决经验! 正文 问题一:团队之间怎么沟通? 一个好的开发团队,首先要营造一个好的开发环境,团队之间要有良好的沟通互动,有时候在开发一期项目的时候需求还不是很明确,需要边做边确定,而这时就需要团队之间频繁积极的进行沟通,初步模型要积极进行评估讨论,不然就会出现辛苦几天而来的产品不符合需求,打回去重做.这不仅影响开发人员的心情,更重要的是影响开

项目优化经验分享(一)数据自己主动匹配

从今天開始.我将和大家分享一下近期经手项目的优化经验.今天我们分享的内容是:自己主动匹配! 引言: 输入框数据自己主动匹配大家应该非常熟悉,当我们在使用百度或google时,在搜索框中输入数据,就会得到对应提示,这给我们的搜索带来了非常大的方便,假如说我要搜索火车票订票官网.可是我不记得是全称是什么,12593?还是12596?,其实.我在搜索框中输入火车站就能够看到想要找的站点全称了,如图: 如此有用的功能,当然要拿来用到我们自己的系统里,来看看怎么实现吧! 思路: 1.引用jquery-ea

Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理

基于Lucene检索引擎我们开发了自己的全文检索系统,承担起后台PB级.万亿条数据记录的检索工作,这里向大家分享下Lucene底层原理研究和一些优化经验. 从两个方面介绍: 1. Lucene简介和索引原理 2. Lucene优化经验总结 1. Lucene简介和索引原理 该部分从三方面展开:Lucene简介.索引原理.Lucene索引实现. 1.1 Lucene简介 Lucene最初由鼎鼎大名Doug Cutting开发,2000年开源,现在也是开源全文检索方案的不二选择,它的特点概述起来就是

转: 移动直播技术秒开优化经验

移动直播技术秒开优化经验(含PPT) 2016-04-28 09:27 徐立,七牛创始合伙人兼产品副总裁,负责七牛直播云的整体研发,是国内 Go / Docker / Container 技术早期布道者,Go / Containers / Distributed Systems 技术的忠实爱好者和实践者.曾合著国内第一本 Go 语言图书<Go 语言编程>,翻译<Go 语言程序设计>. 现今移动直播技术上的挑战要远远难于传统设备或电脑直播,其完整的处理环节包括但不限于:音视频采集.美

darknet优化经验

目录 darknet优化经验 1. AlexeyAB改进项 2. Linux下编译选项 3. 训练经验 4. 提升检测效果 5. 总结 darknet优化经验 主要来自于:AlexeyAB 版本darknet 1. AlexeyAB改进项 提供window支持 相较于原版pjreddie版本darknet提升了训练速度 添加了二值化网络,XNOR(bit) ,速度快,准确率稍低https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov3-t

Redis优化高并发下的秒杀性能

本文内容 使用Redis优化高并发场景下的接口性能数据库乐观锁 随着双12的临近,各种促销活动开始变得热门起来,比较主流的有秒杀.抢优惠券.拼团等等.涉及到高并发争抢同一个资源的主要场景有秒杀和抢优惠券. 前提 活动规则 奖品数量有限,比如100个 不限制参与用户数 每个用户只能参与1次秒杀 活动要求 不能多发,也不能少发,100个奖品要全部发出去 1个用户最多抢1个奖品 遵循先到先得原则,先来的用户有奖品 数据库实现 悲观锁性能太差,本文不予讨论,讨论一下使用乐观锁解决高并发问题的优缺点.数据

一文带你了解Redis优化高并发下的秒杀性能

本文内容 使用Redis优化高并发场景下的接口性能数据库乐观锁 随着双12的临近,各种促销活动开始变得热门起来,比较主流的有秒杀.抢优惠券.拼团等等.涉及到高并发争抢同一个资源的主要场景有秒杀和抢优惠券. 前提 活动规则 奖品数量有限,比如100个 不限制参与用户数 每个用户只能参与1次秒杀 活动要求 不能多发,也不能少发,100个奖品要全部发出去 1个用户最多抢1个奖品 遵循先到先得原则,先来的用户有奖品 数据库实现 悲观锁性能太差,本文不予讨论,讨论一下使用乐观锁解决高并发问题的优缺点.数据