引语

随着使用Redis的深入,我们不可避免的需要深入了解优化Redis的内存,本章将重点讲解Redis的内存优化之道,同时推荐大家阅读memory-optimization一文。

想要高效的使用Redis,就需要充分理解计算机的内存技术及网络和硬盘延迟,以便追踪性能瓶颈、处理资源规划及分配。

配置优化

在Redis.io网站上的官方文档内存优化中,其中一条建议是在32位模式下编译Redis替代64位实例。

对于同样小于3GB的数据集,32位的比64位版本的要小,因为32位实例的指针大小只有64位版本的一半,它的内存空间占用要少些,但假若内存超过了4GB,但32位实例还是被限制在4GB一下,若使用不当很有可能造成Redis崩溃,数据丢失等情况。如果想要高效的运行Redis,我们首先需要了解的就是配置文件redis.conf中的指令,redis.conf中的大多数配置都做了详细说明,与内存相关的优化配置如下:

# 从RDB版本5开始,一个CRC64的校验文件就被放在了文件末尾。
# 这能够保证文件格式的完整性,但是当存储或加载rdb文件时会有约10%左右的性能下降
# 所以,为了保证性能最大化,可以关掉这个配置项。
rdbchecksum yes # Redis 每100ms会使用1ms的cpu时间来对redis的hash进行rehash,这样可以降低内存的使用。
# 如果你的使用场景有严格实时性需要,不能接受redis有任何延迟的话,把这项配置为no。
activerehashing yes

首先讨论一下rdbchecksum,rdb是redis的持久化方式的一种(另外一种叫aof),在对于数据安全性要求不高时,可以将其设置为no(顺便普及一下redis备份的相关知识:传送门);activerehashing用于配置是否激活重置hash,可以通过配置此项来释放内存。

使用Hash

官网中推荐尽可能的使用Hash,因为Redis存储小于100个字段的Hash结构上,其存储效率非常高。

使用Key过期

Key过期算是最有效的一种手段之一了,并不是所有的Key都需要一直存在于内存中,当Key不大可能再被使用或长时间内不再使用,我们可以使用过期策略来让Redis自动删除它。

配置回收策略

当内存超出限制时,我们可能希望Redis自动进行GC,当我们配置了内存容量时,通过redis.conf中的

maxmemory 100mb

来实现,设置为0时表示没有内存限制,64位默认为0,32位则默认为3G,当达到阈值时,将触发回收策略:

# noveicition:当内存达到限制时返回错误;
# allkeys-lru:回收最近最少使用的键;
# volatile-lru:只回收有设置过期时间的最近最少使用的键;
# allkeys-random:回收随机键;
# volatile-random:随机回收设置过期时间的键;
# volatile-ttl:回收设置过期时间的键,优先回收离TLL时间最短的键。
maxmemory-policy noeviction

LRU是我们常见的淘汰算法,每次淘汰最近最少使用的元素,发散一下,贴一段LRU的简单实现:

    /// <summary>
/// LRU 缓存。
/// </summary>
public class LRUCache
{
int _curSize = 0; //当前缓存大小
int _limit = 5; //元素上限
LRUNode _headNode; //数据头
Dictionary<string, LRUNode> _nodeDic; //缓存链表 public LRUCache()
{
_headNode = new LRUNode("", "");
_headNode.Prev = _headNode;
_headNode.Next = _headNode;
_nodeDic = new Dictionary<string, LRUNode>();
} public string Get(string key)
{
if (_nodeDic.ContainsKey(key))
{
var curNode = _nodeDic[key];
MoveToHead(curNode); //更新节点的使用频率
return curNode.Val;
}
return "";
} public void Set(string key, string val)
{
LRUNode curNode;
if (_nodeDic.ContainsKey(key))
{
curNode = _nodeDic[key];
MoveToHead(curNode);
}
else
{
curNode = new LRUNode(key, val);
AddToHead(curNode);
_curSize++;
if (_curSize > _limit)
{
RemoveLast(curNode);
_curSize--;
}
_nodeDic.Add(key, curNode);
}
} public int GetTotalSize()
{
return _nodeDic.Count;
} //新增节点
void AddToHead(LRUNode node)
{
node.Prev = _headNode;
node.Next = _headNode.Next; _headNode.Next.Prev = node;
_headNode.Next = node;
} //将节点移除
void RemoveFromList(LRUNode node)
{
node.Prev.Next = node.Next;
if (node.Next != null)
node.Next.Prev = node.Prev;
} //移除最后一个
void RemoveLast(LRUNode node)
{
var deNode = _headNode.Prev;
RemoveFromList(deNode);
_nodeDic.Remove(deNode.Key);
} //移动到表头
void MoveToHead(LRUNode node)
{
RemoveFromList(node);
AddToHead(node);
}
} /// <summary>
/// LRU 链表。
/// </summary>
public class LRUNode
{
public LRUNode Prev; //前节点
public LRUNode Next; //后节点
public string Key;
public string Val; public LRUNode(string key, string val)
{
Key = key;
Val = val;
}
}

然后我们运行:

        static void Main(string[] args)
{
LRUCache _chache = new LRUCache();
for (int i = 0; i < 20; i++)
{
_chache.Get("13"); _chache.Set(i.ToString(), i.ToString());
} for (int i = 0; i < 20; i++)
{
Console.WriteLine(_chache.Get(i.ToString()));
} Console.ReadLine();
}

猜猜结果是什么?循环输出的是“13、16、17、18、19”,因为13一直在被使用,所以不会被删除掉,反而去删除了15。

硬件和网络延迟

在应用程序中,性能问题很容易被误认为是Redis数据库内存不足造成的。其实该问题很有可能是有关客户端应用程序和后端服务器之间硬件和网络延迟的问题。

当我们遇见Redis延迟时,一般可以从下面三个方向去查证:

  1. 命令延迟:前文中我们提到过不同命令的时间复杂度不同,时间复杂度为O(1)的执行起来就非常快,而为O(n)的就可能因为数据量比较大而延迟;
  2. 往返延迟:由于网络拥塞导致的命令收发延迟;
  3. 客户端延迟:例如多个客户端同时尝试连接到Redis而客户端连接数达到限制需要等待导致的并发延迟。

那么若是遇见了这些问题,该如何定位呢?接下来我将介绍一些性能问题排查是经常用到的命令:

info

info

info会输出和Redis相关的基本信息,如版本号、内存使用、CPU、集群等,大多数情况我们比较关心的是memory,所以你可以使用:

info memory

来简化输出,memory中的内容如下:

used_memory:812168   # Redis分配器分配的内存总量,以字节为单位
used_memory_human:793.13K # 以Kb为单位,为了方便阅读
used_memory_rss:7708672 # 操作系统上显示已分配的内存总量
used_memory_peak:813008 # Redis消耗内存的峰值
used_memory_peak_human:793.95K # 友好的显示
used_memory_lua:36864 # Lua引擎使用的内存大小
mem_fragmentation_ratio:9.49 # 内存碎片率
mem_allocator:jemalloc-3.6.0 # 在编译时Redis使用的内存分配器

理想情况下used_memory_rss的值应该只比used_memory稍微高一点儿,但是两者值相差较大是,表示存在内存碎片,可以通过mem_fragmentation_ratio看出来,内存大于1是合理的,但超过1.5则表示有较多的内存碎片,或许有人会问我的是9.38是不是要重启,我也发现了这个问题,新搭的虚拟机,完全没必要,猜测是系统给redis预先分配了固定内存导致的;目前最好的办法就是重启redis回收内存。当used_memory的值应该比used_memory_rss高时,则表示Redis的部分内存被操作系统换出到交换空间了,这种情况下,操作就可能会出现明显的延迟,还有可能出现数据丢失的危险。stats中的total_commands_processed显示了redis当前处理的命令总数,可以通过定期记录数目计算出QPS来分析性能是否下降。client中的connected_clients节点展示了当前有多少个链接,如果连接数超出预期,则表示客户端可能没有有效释放链接,Redis默认允许的客户端最大连接数为10000。

slowlog

有时候我们想知道到底是哪些操作导致了Redis堵塞,这个时候我们可以通过slowlog命令来定位,默认情况下若一个命令执行时间操作10ms就会被记录,示例中我们取前10条慢日志:

slowlog get 10

如果你想降低或提高10ms这个阈值的话,请使用如下命令:

config set slowlog-log-slower-than # 也可以通过config get slowlog-log-slower-than来查看当前的配置,

latency-monitor-threshold

Redis中提供了一个特殊模式来监控命令延迟,即“latency-monitor-threshold”指令,该指令设置了以毫秒为单位的限制,超过该限制的所有或部分命令及Redis示例的活动均会被记录下来。该指令默认为0,不自动监控,所以我们首先需要如下配置:

config set latency-monitor-threshold 100

通过latency latest命令我们可以查看到事件名、最近延迟的Unix时间戳、最近的延迟、最大延迟等。我们可以通过debug来人为制造一些慢命令来进行测试:

debug sleep 1
debug sleep .25

latency-monitor-threshold可以和slow-log配合使用,想详细了解可以去查看latency的相关命令。

网络优化

书中并未提及,但是既然前面涉及到了,这里也给出一些优化建议来减少网络延迟:

  1. 使用长链接、不要频繁的连接和断开客户端到服务器的连接;
  2. 相比较管道而言,可以优先使用多参数命令,如mset、mget、hmset、hmget等;
  3. 使用管道;
  4. 如果存在数据依赖而不方便使用管道时,可以考虑使用Lua脚本来进行优化。

对于优化这一块,是不是考虑搞个番外来单独讲呢?

# 深入理解Redis(二)——内存管理的建议与技巧的更多相关文章

  1. Android笔记--Bitmap(二)内存管理

    Bitmap(二) 内存管理 1.使用内存缓存保证流畅性 这种使用方式在ListView等这种滚动条的展示方式中使用最为广泛, 使用内存缓存 内存缓存位图可以提供最快的展示.但代价就是占用一定的内存空 ...

  2. 自己写的书《深入理解Android虚拟机内存管理》,不出版只是写着玩

    百度网盘地址:https://pan.baidu.com/s/1jI4xZgE 我给起的书名叫做<深入理解Android虚拟机内存管理>.本书分为两个部分,前半部分主要是我对Linux0. ...

  3. 理解 iOS 的内存管理

    远古时代的故事 那些经历过手工管理内存(MRC)时代的人们,一定对 iOS 开发中的内存管理记忆犹新.那个时候大约是 2010 年,国内 iOS 开发刚刚兴起,tinyfool 大叔的大名已经如雷贯耳 ...

  4. 深入理解Linux中内存管理

    前一段时间看了<深入理解Linux内核>对其中的内存管理部分花了不少时间,但是还是有很多问题不是很清楚,最近又花了一些时间复习了一下,在这里记录下自己的理解和对Linux中内存管理的一些看 ...

  5. 【Linux】深入理解Linux中内存管理

    主题:Linux内存管理中的分段和分页技术 回顾一下历史,在早期的计算机中,程序是直接运行在物理内存上的.换句话说,就是程序在运行的过程中访问的都是物理地址. 如果这个系统只运行一个程序,那么只要这个 ...

  6. cocos2d-x游戏引擎核心之二——内存管理

    (一) cocos2d-x 内存管理 cocos2d里面管理内存采用了引用计数的方式,具体来说就是CCObject里面有个成员变量m_uReference(计数); 1, m_uReference的变 ...

  7. 深入redis内部--内存管理

    1. Redis内存管理通过在zmalloc.h和zmalloc.c中重写c语言对内存的管理来完成的. redis内存管理 c内存管理 原型 作用 zmalloc malloc void *mallo ...

  8. 深入理解Java虚拟机—内存管理机制

    前面说过了类的加载机制,里面讲到了类的初始化中时用到了一部分内存管理的知识,这里让我们来看下Java虚拟机是如何管理内存的. 先让我们来看张图 有些文章中对线程隔离区还称之为线程独占区,其实是一个意思 ...

  9. 我理解的Linux内存管理

    众所周知,内存管理是Linux内核中最基础,也是相当重要的部分.理解相关原理,不管是对内存的理解,还是对大家写用户态代码都很有帮助.很多书上.很多文章都写了相关内容,但个人总觉得内容太复杂,不是太容易 ...

随机推荐

  1. php base64互转pdf

    /* * base64转pdf */ function base642pdf($formTxt,$toPdf) { $file = file_get_contents($formTxt);//读 $d ...

  2. Django - orm外键操作

    1.orm外键操作 创建外键: 备注:ForeignKey两个参数,1个为关联的表名,1个为关联的字段名: 在django2.0后,定义外键和一对一关系的时候需要加on_delete选项,此参数为了避 ...

  3. 洛谷——P1759 通天之潜水

    P1759 通天之潜水   题目背景 直达通天路·小A历险记第三篇 题目描述 在猴王的帮助下,小A终于走出了这篇荒山,却发现一条波涛汹涌的河拦在了自己的面前.河面上并没有船,但好在小A有n个潜水工具. ...

  4. 关于linux内核用纯c语言编写的思考

    在阅读linux2.6 版本内核的虚拟文件系统和驱动子系统的时候,我发现内核纯用c语言编写其实也是有一点不方便,特别是内核中大量存在了对象的概念,比如说文件对象,描述起来使用对象描述,但是对象在c语言 ...

  5. Python网络请求urllib和urllib3详解

    Python网络请求urllib和urllib3详解 urllib是Python中请求url连接的官方标准库,在Python2中主要为urllib和urllib2,在Python3中整合成了urlli ...

  6. 【hiho一下 第145周】智力竞赛

    [题目链接]:http://hihocoder.com/contest/hiho145/problem/1 [题意] [题解] 设f[i][j]表示做对i道题,做错j道题能够到达的最好状态是什么; 这 ...

  7. (41)Spring Boot 使用Java代码创建Bean并注册到Spring中【从零开始学Spring Boot】

    已经好久没有讲一些基础的知识了,这一小节来点简单的,这也是为下节的在Spring Boot中使用多数据源做准备. 从Spring 3.0开始,增加了一种新的途径来配置Bean Definition,这 ...

  8. 【学QT】 3 - DEBUG集子

    1. [root@localhost helloqt]# make g++ -Wl,-rpath,/usr/local/qt/lib -o cfconv .obj/main.o .obj/cfconv ...

  9. Spring MVC-处理程序映射(Handler Mapping)-简单的Url处理程序映射(Simple Url Handler Mapping)示例(转载实践)

    以下内容翻译自:https://www.tutorialspoint.com/springmvc/springmvc_simpleurlhandlermapping.htm 说明:示例基于Spring ...

  10. Spring MVC-表单(Form)标签-文件上传(File Upload)示例(转载实践)

    以下内容翻译自:https://www.tutorialspoint.com/springmvc/springmvc_upload.htm 说明:示例基于Spring MVC 4.1.6. 以下示例显 ...