Memcache在 web社区中是一个非常著名的系统,而且有一个好的原因:它速度快、稳定、轻量级,而且如果你在网站服务器安装了memcache后它似乎会自动将网站访 问速度提升10倍。虽然这似乎有点不可思议,但是:定制一个好的缓存策略对网站或应用很有用。如果你只是想知道如果在你的网站中应用memcache,那 很不幸,本文并不是教你如何使用memcache。我们将抽丝剥茧,看看是什么使memcache如此神奇?
尽管memcache本身并不是一个非常复杂的软件,但它却有许多很好的功能,这要花很长时间来谈论。我将主要关注一下四个方面:
-
Big-O
-
LRU
-
内存分配(Memory allocation)
-
一致性哈希(Consistent hashing)
Big-O
memcache大部分函数(add、get、set、flush)的时间复杂度都为o(1)。这意味着它们都是时间常数的函数,与缓存中有多少对象无关,这些函数所花的时间与缓存中只有一条数据时所花的时间相同。更多关于big-o的信息,请阅读博客(https://www.adayinthelifeof.nl/2009/12/21/big-o-notation/)。 使用o(1)函数会有很多好处(memcache一直保持快速),但是也有一些缺点。比如:你不能在memcache中不能通过迭代且迭代所有对象(如果 非要这么做,那是对memcache的滥用,但这又是另一个话题)。迭代器有可能是一个时间复杂度为o(N)的操作;这意味这如果缓存的对象翻倍,那么所 花的时间也要翻倍。这也正式memcache不支持迭代函数的原因。
LRU算法
当 你启动一个memcache守护进程之前,你需要告诉它你需要多少内存。memcache将在启动的时候正确分配内存,因此如果你需要1G内存来存放缓存 数据的话,那么这1G内存会被直接分配而且不用用作其他用途(就像apache或者数据库实例一样)。但是由于我们已经告诉memcache它可以使用多 少内存,memcache有可能会将它的所有内存来存储我们的数据。那么,如果我们需要增加更多的数据会发生什么情况呢?
也 许你已经知道,memcache将会删除老的数据来为新数据提供空间,但它需要知道哪些数据对象可以被删除。是我们最近放入的占用空间最大的对象?或者是 最先放入缓存的对象,这样我们就可以得到一个基于先进先出算法的缓存系统?真实情况是,memcache使用了一种更先进的技术,叫做LRU:最近最少使 用。简单地说就是:它删除最长时间没有没使用的对象。这不一定是最大的对象,而且甚至不一定是最先放入缓存的数据。
在 memcache内部,所有对象都有一个“计数器”。这个计数器持有一个时间戳;每次一个新的对象被创建后,这个计数器会被设置为当前时间。当一个对象被 获取后,memcache也会重置该计数器为当前时间。一旦memcache需要“删除"一个对象来为新的对象腾出空间时,它将找到最小的计数器。这个对 象即为没有被获取或离上次获取的时间最长(也许不需要那么多,否则计数器的值将接近当前时间戳)。
实际上这将创建一个使用缓存非常有效的简单系统。如果它没有被使用,它被从系统系统剔除。
AdamPresley写了一篇博客来教大家如何在php项目中使用这种机制。在memcache中,对这种系统的使用略有不同,它被用来保证绝大部分函数的时间复杂度保持在o(1)。
内存分配
内 存分配是大部分php开发人员研究之外的一个方面。这也是为什么高级编程语言更简单的原因:大多数工作由底层的系统如编译器或操作系统来完成。但由于 memcache是用C写的,所以它必须自己去做内存分配和管理。幸运的是,大多数的工作(必须)可以委托给操作系统来做,所以,实际上我们只用去使用一 个函数(malloc函数)来分配内存、一个在有些情况下不需要的函数(free函数)来释放内存以及可能会用到一个函数(realloc函数)来重新设 置当前内存块大小。
现 在,你所创建的C项目一切都妥当了,在这个项目中你在创建字符串之前需要为它分配内存空间,然后对它做操作,最后释放你所使用的内存。但是高性能的系统如 memcache使用这种方式会有问题。主要原因是malloc函数和free函数在这种系统中实际上并没有被优化。这种情况下内存很容易出现碎片,也就 是说会有很多内存溢出;这就像你在磁盘上写入或者删除文件时会造成大量磁盘空间碎片一样(但平心而论,这种情况取决于你所使用的系统)。
现 在,你所创建的C项目一切都妥当了,在这个项目中你在创建字符串之前需要为它分配内存空间,然后对它做操作,最后释放你所使用的内存。但是高性能的系统如 memcache使用这种方式会有问题。主要原因是malloc函数和free函数在这种系统中实际上并没有被优化。这种情况下内存很容易出现碎片,也就 是说会有很多内存溢出;这就像你在磁盘上写入或者删除文件时会造成大量磁盘空间碎片一样(但平心而论,这种情况取决于你所使用的系统)。你还记得你隔一段 时间必须整理一次磁盘的时代吗?基本上,同样的事情在内存中同样存在;这意味着最后由于内存碎片将导致内存分配越来越慢以及大量内存不能被使用。
目 前,为了解决malloc函数问题,memcache默认使用它自带的内存管理器(你也可以让memcache使用标准的malloc函数,但是这种做法 是不明智的)。Memcache的内存管理器将通过一个malloc调用从操作系统分配你设置的最大值(比如64M,或者更多);从这时起 memcache使用自带的被称作slab分配器的内存管理系统。
Slab分配
当 memcache启动时,它会将分配给它的内存重新分配为更小的部分被称作页(page)。每页大小为1M(凑巧的是,在memcache中你可以存储的 单个对象最大内存也是1M)。每个页可以被分配给一个slab类或者页可以被收回(变成一个空闲页)。slab类决定多大的对象可以被存储在特定的内存页 中。同时每个页都会被指定一个特定的sub-class,这样个页被分为更小的块(chunk)。在slab中的每个块都有相同的大小,因此在同一个页中 不能有两种不同大小的块。例如,一个页的块大小为64字节(slab类1),另一个页的块大小为128字节(slab类2)等等,直到拥有最大slab的 页只有一个块(1M大小的块)。每种slab类可以存在于多个页中,但是一旦一个页被分配了一个slab类之后(也就是说已经被分成了块),那么该页就不 能再次被分配其他slab类。
最 小的块大小从80字节开始,并且块大小的增长使用参数1.25(往上进位直到达到下一个2的整数幂大小)。因此,80字节之后下一个最小的块大小为 100,依次类推;你可以增加"-vv"参数在mecache启动时来观察它。你也可以通过-f来设置增长参数(译注:改变1.25的值)以及使用-s来 设置块的初始化大小;但是,除非你非常明确需要这样做,否则不要改变初始参数值。
你 实际上看到的是slab类的数量、slab内块的大小以及有多少块(很显然:块越大,块的数量就越少)。Memcache会为每个slab类初始化一个 page,其他页保持空闲(如果slab类需要一个页,就指定一个)。memcache已经对内存做了分区之后就可以往slab中增加数据了。假设我有一 个105字节大小的对象(这包括memcache的开销,所以能存储在memcache中的数据会比实际少一些),memcache就会知道该对象应该使 用slab类3中块来存储该对象,因为slab类3的大小适合105字节的对象,也就是说我们有128-105=23字节的内存未被使用,而这23字节的 内存也不能被用来做其他的事。那个块被标记为已使用,这就是那个对象。这是我们使用slab分配器需要付出的代价,但是内存却不会有碎片。实际上,这是速 度和内存浪费之间的权衡。
那 么,一旦一个页被使用完(这个页中的所有块都被放满数据)同时我们又要增加其他一些数据,memcache将会获取一个新的空闲页并将该页分配给特性的 slab类,再将其分成块并使用第一个有效的块来存储数据。但是如果一旦页被使用完,那么memcache就会使用LRU算法来清除已经存在的块来腾出空 间。也就是说,如果我们需要一个128字节的块,它就会清除一个128字节的块出来,尽管可能有256字节块比这个128字节的块中的数据更老。此外,每 个slab类都有自己的LRU算法。
所以我们假设:
如 果你在所有缓存中都使用的是128字节大小的块来存储数据(以上例子中的slab类3),memcache将会分配所有的页到那个slab类。实际上可能 其他的slab类只有一个页(在初始化时分配),也就是说当已经有一个1M的对象存储在块大小为1M的页中时,第二个1M的对象没有新的页可分配。此 时,memecache必须使用LRU算法来移除一个对象,而由于只有一个1M的slab类对应的页,那么第一个1M的对象被移除。到了本文最后,你就会 知道slab分配器是如何工作的。
一致性哈希(Consistent hashing)
你的web应用和同时和多个不同的memcache服务器来交互,你只需要将你的应用升级为一组memcache服务器的ip即可,因此它默认使用所有的memcache服务器。
当 我们添加一个对象到memcache中时,它会自动选择一个可以存储该数据的服务器。当只有一台mecache服务器时这个选择非常容易,但是当你有多个 服务器时memcache必须找到一种方式来存储对象。对于这种情况有多种不同的算法,例如:使用轮循系统将每个存储操作按顺序保存至下一个服务器(第一 个对象保存在服务器1中,第二个保存至服务器2中,第三个保存至服务器1中,以此类推)。但当我们想获取指定数据时这样的一个系统怎么知道正确的服务器?
memcache所做的非常简单,然而该负载均衡技巧非常有效:为每个存储或获取的键(key)来创建一个哈希(你可能会认为是md5(key),但事实上,这是一个更专业快速的哈希方法)。至此,我们创建的散列是均匀分布的,所以我们可以使用模函数找出那个服务器存储了我们需要的对象:
在php中,代码如下:
非 常好。现在,我们仅仅通过这个简单的公式就可以推断出哪个服务器持有指定的键。这个机制的问题:一旦$servercount(服务器的数量)发生变化, 几乎所有的键也都会改变服务器;也许一些键服务器id保持不变,但这纯属巧合。事实上,当你改变了memcache服务器数量(不论是增加或减少,都没关 系),你的后端都会增加大量请求,因为所有的键一次性全部都失效了。
现在,让我们来拥抱一致性哈希。通过使用这种算法,在服务器增加或减少时我们不用担心键会改变服务器。以下是它的工作原理:
一 致性哈希使用一个类似像时钟的计时器。一旦它到达“12”,它会重新回滚至“1”。假设这个计时器是16位,那么它的取值范围是65535。如果我们设想 这是一个时钟,数字0和65535就像时钟上的12点,32200将在6点钟,48000就在9点钟,依此类推。我们称之为连续时钟。
在这个连续时钟上,我们为每个服务器放置(相对)大量的"点"。这些点是随机放置的,就好比我们有一个许多点的时钟一样。
作为例子,让我展示一个拥有3台服务器(s1,s2,s3),每台服务器有2个点的连续时钟:
如 果这个连续时钟是16位的数字,s1上的点或多或少在10到29000之间,s2上的点将在39000到55000之间,s3上的点将在8000到 48000之间。现在,当我们存储一个键时,我们创建了一个16位数字的哈希值,这个数字可以绘制在连续时钟之上。假设我们有四个键(k1到k4),得到 的4个哈希值分别是:15000、52000、34000、38000。如下图中的红点所示:
找 到一个key应该存储的服务器,我们唯一需要做的就是按照连续时钟顺时针方向找,直到我们达到了“服务器”点上。对于k1来说,我们沿着连续时钟来找直到 我们找到s1。K2将找到s2。k3将找到s3,k4将找到s3。到此为止,并没有什么特别的事情发生。实际上,看起来我们本来用模数算法很容易做的事情 使用这种方式多做了很多额外的工作。
在这里,一致性哈希是有优势的:假设服务器2将从memcache服务器池中移除,那么如果想要获取k1将会发生什么?没有奇快的事情发生,我们标记的k1仍然在连续时钟上相同的位置,它首先遇到的服务器点仍然是s1。
然而,当获取k3时,由于k3存储在s2上,它将错误s2(因为s2已经被移除),那么它将移动到s3上。对k2来说同理,它将移动至s1上。
事实上,我们在连续时钟上放置的服务器点越多,在一个服务器被移除(或增加)时丢失的键就会越少。最好的服务器节点数量应该100到200之间,此后如果服务器再多就会是连续时钟上的查找更慢(这当然是一个非常快的过程)。添加的服务器越多,一致性哈希将执行的越好。
不像使用标准取模算法时会导致几乎所有的key发生移动,一致性哈希算法也许只会导致10%~25%的键失效(这个数字还会随着你服务器的增加而快速下降),换句话说,后端服务器(如数据库)的压力会比使用取模算法时更小。
结论
很高兴能在某些系统上做一些深入了解,我们认为是理所当然的。就像在“现实生活”中,事情更加复杂化而且由许多复杂的解决方法组成,这些也许都可以帮助你自己的(发展)生活。像LRU以及一致性哈希这样的算法并不难理解,只是现在你知道它们的存在从长远来看可以帮助你成为一名更好的开发者。
1.本文由程序员学架构摘译
2. 本文译自https://www.adayinthelifeof.nl/2011/02/06/memcache-internals/
3. 转载请务必注明本文出自:程序员学架构(微信号:archleaner )
4. 更多文章请扫码:
相关推荐
Memcached是一款高性能的分布式内存对象缓存系统,它主要用于减少动态数据库驱动网站...了解其内部结构和使用方式对于开发者来说是至关重要的,能够帮助他们更有效地利用这一工具来优化应用的响应速度和并发处理能力。
了解Memcache的源码有助于我们更深入地理解其内部机制,例如如何实现内存管理、如何处理并发请求等。源码分析可以帮助开发者优化性能,定制特殊需求,或开发新的扩展功能。 ### 七、工具支持 有许多工具可用于监控...
通过阅读和分析memcache的源码,开发者不仅可以了解其内部工作原理,还能学习到内存管理、网络编程、并发控制等多方面的知识,这对于提升个人技能和解决实际问题大有裨益。提供的分析PPT和word文档则提供了更直观的...
“源码”标签可能意味着本文将深入探讨MemCache的内部机制,包括其数据结构、缓存淘汰策略(如LRU,Least Recently Used)、网络通信协议等方面。理解这些有助于开发者优化使用或者为社区贡献代码。 **六、使用工具...
源码分析可以帮助开发者更深入地理解其内部运作机制,以便进行定制化开发或性能调优。 PHP是流行的服务器端脚本语言,尤其在Web开发领域广泛应用。与Memcache的整合,使得PHP可以利用Memcache的高速缓存能力,提升...
1. **源码分析**:可能深入到Memcache的源代码级别,理解其内部工作原理,帮助优化配置或自定义监控脚本。 2. **工具推荐**:介绍一些用于监控Memcache的开源工具,如`memcached-top`,一个实时显示Memcache统计信息...
通过深入研究memcached的源代码,我们可以理解其内部机制,从而更好地利用和优化这个工具。《memcached代码分析.pdf》这样的文档将详细讲解这些概念和实现细节,对于理解和改进缓存系统具有很高的参考价值。
对于Memcached来说,研究源代码可以帮助我们深入理解其内部机制,包括: 1. **数据结构与算法**:Memcached如何高效地存储和检索键值对,可能涉及哈希表、LRU(Least Recently Used)缓存替换策略等。 2. **网络...
标题《Scaling Memcache At Facebook》和描述提到的是一篇关于Facebook如何扩展Memcached系统以支持世界上最大的社交网络的文章。在深入分析Facebook的Memcached扩展策略之前,我们先来了解一下Memcached的背景知识...
对于深入理解Memcached的使用,你可以阅读源码来了解其内部实现。例如,`spymemcached`库的源码可以帮助你理解如何封装Memcached协议,以及如何处理网络通信和序列化。 ### 7. 工具支持 为了便于测试和调试,可以...
《深入剖析 Memcached:基于1.4版本的源码解析》 Memcached 是一款高性能、分布式内存对象缓存系统,广泛应用于互联网服务中,用于缓解数据库的读写压力,提高应用性能。本文将针对 Memcached 的1.4版本进行源码...
### Memcached内存分析、调优、集群 #### 1. Memcached背景 Memcached是一款高性能的分布式内存对象缓存系统,旨在通过减轻数据库负载来加速动态Web应用的响应速度。它通过在内存中缓存数据和对象来减少读取数据库...
2. 采用“提前”使用互斥锁(mutex key):在 value 内部设置 1 个超时值(timeout1), timeout1 比实际的 memcache timeout(timeout2)小。当从 cache 读取到 timeout1 发现它已经过期时候,马上延长timeout1 并重新设置...
通过LVS,可以将海量的用户请求智能分发至多个nginx服务器集群,每个集群内部遵循与NGINX相似的架构模式,但共享memcache集群和mysql服务器集群,实现了资源的高效利用。队列服务器的设置,确保了memcache数据的异步...
- **开启缓存**:使用如 `mod_cache` 和 `mod_memcache` 等模块加速静态资源加载速度。 - **优化连接管理**:调整 `MaxClients` 和 `KeepAliveTimeout` 等参数以提高并发处理能力。 **MySQL优化:** - **集群部署**...
2. **服务器类型**:攻击者可能针对不同的服务器类型,如Redis、MongoDB、MemCache或HTTP服务器,因为它们可能监听内部网络且不安全地暴露了外部接口。 3. **利用方式**: - **指纹识别**:通过枚举和分析响应来...
内部算法均设计为O(1)复杂度,即算法执行时间和CPU使用量不随并发客户端数量或数据大小变化而变化。Memcached使用了一种称为slab分配器的内存管理机制,它预先分配出大块内存,然后将其切分为适合特定类别的对象的...
系统系统层都使用Python 来撰写API 层的东西,包含建置AP、分析封包内容。Back-End用nginx 来跑PHP,当做web server。使用sqlite 储存所有操作的资料,包含但不限于:连线device 名称,重要POST 资讯...etc ;使用...