分布式缓存之memcached以及LAMP的搭建

本文介绍了分布式缓存系统Memcached的简介、工作原理、内存管理和应用场景。Memcached是一个高性能的key-value存储系统,常用于缓存数据库查询结果以提升Web应用的速度和可扩展性。其工作原理包括独立运行的服务实例和客户端,通过哈希计算确定数据存储位置。内存管理采用Slab结构避免碎片化。Memcached支持分布式扩展和简单的Cache机制,适用于Web缓存和数据库缓存等场景。文章还提供了在LAMP环境中配置和使用Memcached的步骤。
摘要由CSDN通过智能技术生成

1.memcached简介
Memcached 是一个自由开源的,高性能,分布式内存对象缓存系统。
Memcached 是以 LiveJournal 旗下 Danga Interactive 公司的 Brad
Fitzpatric 为首开发的一款软件。现在已成为 mixi、hatena、Facebook、Vox、LiveJournal 等众多服务中
提高 Web 应用扩展性的重要因素。
Memcached 是一种基于内存的 key-value 存储,用来存储小块的任意数据(字符串、对象)。这
些数据可以是数据库调用、API 调用或者是页面渲染的结果。
Memcached 简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的
很多问题。它的 API 兼容大部分流行的开发语言。
本质上,它是一个简洁的 key-value 存储系统。
一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态 Web 应用的
速度、提高可扩展性。
在这里插入图片描述

2. Memcached 工作原理
Memcached 使用了 BSD 许可的服务端缓存实现。与其它服务端缓存实现不同的是,其主要由两
部分组成:独立运行的 Memcached 服务实例,以及用于访问这些服务实例的客户端。因此相较于普通服务端缓存实现中各个缓存都运行在服务实例之上的情况,Memcached 服务实例则是在服务实例之外独立运行的:
在这里插入图片描述

从上图中可以看出,由于 Memcached 缓存实例是独立于各个应用服务器实例运行的,因此应用服务实例可以访问任意的缓存实例。而传统的缓存则与特定的应用实例绑定,因此每个应用实例将只能访问特定的缓存。这种绑定一方面会导致整个应用所能够访问的缓存容量变得很小,另一方面也可能导致不同的缓存实例中存在着冗余的数据,从而降低了缓存系统的整体效率。
在运行时,Memcached 服务实例只需要消耗非常少的 CPU 资源,却需要使用大量的内存。因此在决定如何组织您的服务端缓存结构之前,您首先需要搞清当前服务中各个服务实例的负载情况。如果一个服务器的 CPU 使用率非常高,却存在着非常多的空余内存,那么我们就完全可以在其上运行一个 Memcached 实例。而如果当前服务中的所有服务实例都没有过多的空余内存,那么我们就需要使用一系列独立的服务实例来搭建服务端缓存。一个大型服务常常拥有上百个 Memcached 实例。而在这上百个 Memcached 实例中所存储的数据则不尽相同。由于这种数据的异构性,我们需要在访问由Memcached所记录的信息之前决定在该服务端缓存系统中到底由哪个Memcached实例记录了我们所想要访问的数据:

在这里插入图片描述

如上图所示,用户需要通过一个 Memcached 客户端来完成对缓存服务所记录信息的访问。该客
户端知道服务端缓存系统中所包含的所有 Memcached 服务实例。在需要访问具有特定键值的数据时,该客户端内部会根据所需要读取的数据的键值,如“foo”,以及当前 Memcached 缓存服务的配置来计算相应的哈希值,以决定到底是哪个 Memcached 实例记录了用户所需要访问的信息。在决定记录了所需要信息的 Memcached 实例之后,Memcached 客户端将从配置中读取该 Memcached 服务实例所在地址,并向该 Memcached 实例发送数据访问请求,以从该 Memcached 实例中读取具有键值“foo”的信息。而对数据的记录也使用了类似的流程:假设用户希望通过服务端缓存记录数据“bar”,并为其指定键值“foo”。那么 Memcached 客户端将首先对用户所赋予的键值“foo”及当前服务端缓存所记录的可用服务实例个数执行哈希计算,并根据哈希计算结果来决定存储该数据的 Memcached 服务实例。接下来,客户端就会向该实例发送请求,以在其中记录具有键值“foo”的数据“bar”。
这样做的好处则在于,每个 Memcached 服务实例都是独立的,而彼此之间并没有任何交互。在
这种情况下,我们可以省略很多复杂的功能逻辑,如各个节点之间的数据同步以及结点之间消息的广播等等。这种轻量级的架构可以简化很多操作。如在一个节点失效的时候,我们仅仅需要使用一个新的 Memcached 节点替代老节点即可。而在对缓存进行扩容的时候,我们也只需要添加额外的服务并修改客户端配置。
这些记录在服务端缓存中的数据是全局可见的。也就是说,一旦在 Memcached 服务端缓存中成
功添加了一条新的记录,那么其它使用该缓存服务的应用实例将同样可以访问该记录:
在这里插入图片描述

在 Memcached 中,每条记录都由四部分组成:记录的键,有效期,一系列可选的标记以及表示记录内容的数据。由于记录内容的数据中并不包含任何数据结构,因此我们在 Memcached 中所记录的数据需要是经过序列化之后的表示。

3.内存管理
通常情况下,一个内存管理算法所最需要考虑
的问题就是内存的碎片化(Fragmentation):在长时间地分配及回收之后,被系统所使用的内存将趋向于散落在不连续的空间中。这使得系统很难找到连续内存空间,一方面增大了内存分配失败的概率,另一方面也使得内存分配工作变得更为复杂,降低了运行效率。
为了解决这个问题,Memcached 使用了一种叫 Slab 的结构。在该分配算法中,内存将按照 1MB的大小划分为页,而该页内存则会继续被分割为一系列具有相同大小的内存块。
在这里插入图片描述

因此 Memcached 并不是直接根据需要记录的数据的大小来直接分配相应大小的内存。在一条新的记录到来时,Memcached 会首先检查该记录的大小,并根据记录的大小选择记录所需要存储到的Slab 类型。接下来,Memcached 就会检查其内部所包含的该类型 Slab。如果这些 Slab 中有空余的块,那么 Memcached 就会使用该块记录该条信息。如果已经没有 Slab 拥有空闲的具有合适大小的块,那么 Memcached 就会创建一个新的页,并将该页按照目标 Slab 的类型进行划分。
Memcached 使用这种方式来分配内存的好处则在于,其可以降低由于记录的多次读写而导致的碎片化。反过来,由于 Memcached 是根据记录的大小选择需要插入到的块类型,因此为每个记录所分配的块的大小常常大于该记录所实际需要的内存大小,进而造成了内存的浪费。当然,您可以通过Memcached 的配置文件来指定各个块的大小,从而尽可能地减少内存的浪费。
但是需要注意的是,由于默认情况下 Memcached 中每页的大小为 1MB,因此其单个块最大为1MB。除此之外,Memcached 还限制每个数据所对应的键的长度不能超过 250个字节。
一般来说,Slab 中各个块的大小以及块大小的递增倍数可能会对记录所载位置的选择及内存利用率有很大的影响。例如在当前的实现下,各个 Slab 中块的大小默认情况下是按照 1.25 倍的方式来递增的。也就是说,在一个 Memcached 实例中,某种类型 Slab 所提供的块的大小是 80K,而提供稍大一点空间的 Slab 类型所提供的块的大小就将是 100K。如果现在我们需要插入一条 81K 的记录,那么Memcached 就会选择具有 100K 块大小的 Slab,并尝试找到一个具有空闲块的 Slab 以存入该记录。
同时您也需要注意到,我们使用的是 100K 块大小的 Slab 来记录具有 81K 大小的数据,因此记录该数据所导致的内存浪费是 19K,即 19%的浪费。而在需要存储的各条记录的大小平均分布的情况下,这种内存浪费的幅度也在 9%左右。该幅度实际上取决于我们刚刚提到的各个 Slab 中块大小的递增倍数。在 Memcached 的初始实现中,各个 Slab 块的递增倍数在默认情况下是 2,而不是现在的 1.25,从而导致了平均 25%左右的内存浪费。而在今后的各个版本中,该递增倍数可能还会发生变化,以优化 Memcached 的实际性能。
如果您一旦知道了您所需要缓存的数据的特征,如通常情况下数据的大小以及各个数据的差异幅度,那么您就可以根据这些数据的特征来设置上面所提到的各个参数。如果数据在通常情况下都比较小,那么我们就需要将最小块的大小调整得小一些。如果数据的大小变动不是很大,那么我们可以将块大小的递增倍数设置得小一些,从而使得各个块的大小尽量地贴近需要存储的数据,以提高内存的利用率。
还有一个值得注意的事情就是,由于 Memcached 在计算到底哪个服务实例记录了具有特定键的数据时并不会考虑用来组成缓存系统中各个服务器的差异性。如果每个服务器上只安装了一个Memcached 实例,那么各个 Memcached 实例所拥有的可用内存将存在着数倍的差异。但是由于各个实例被选中的概率基本相同,因此具有较大内存的 Memcached 实例将无法被充分利用。我们可以通过在具有较大内存的服务器上部署多个 Memcached 实例来解决这个问题:

在这里插入图片描述

当然,由于缓存系统拥有有限的资源,因此其会在某一时刻被服务所产生的数据填满。如果此时缓存系统再次接收到一个缓存数据的请求,那么它就会根据 LRU(Least recently used)算法以及数据的过期时间来决定需要从缓存系统中移除的数据。而 Memcached 所使用的过期算法比较特殊,又被称为延迟过期(Lazy expiration):当用户从 Memcached 实例中读取数据的时候,其将首先通过配置中所设置的过期时间来决定该数据是否过期。如果是,那么在下一次写入数据却没有足够空间的时候,Memcached 会选择该过期数据所在的内存块作为新数据的目标地址。如果在写入时没有相应的记录被标记为过期,那么 LRU 算法才被执行,从而找到最久没有被使用的需要被替换的数据。
这里的 LRU 是在 Slab 范围内的,而不是全局的。假设 Memcached 缓存系统中的最常用的数据都存储在 100K 的块中,而该系统中还存在着另外一种类型的 Slab,其块大小是 300K,但是存在于其中的数据并不常用。当需要插入一条 99K 的数据而 Memcached 已经没有足够的内存再次分配一个 Slab实例的时候,其并不会释放具有 300K 块大小的 Slab,而是在 100K 块大小的各个 Slab 中找到需要释放的块,并将新数据添加到该块中。
**4.memcached特点**
内存存储速度快,对于内存的要求高,所缓存的内容非持久化。对于 CPU 要求很低,所以常常采用将 Memcached 服务端和一些 CPU 高消耗 Memory 低消耗应用部属在一起 。(否则会互相挤占资源)集中式 Cache避开了分布式 Cache 的传播问题,但是需要非单点保证其可靠性,这需要 cluster 的工作,可以将多个 Memcached 作为一个虚拟的 cluster ,同时对于 cluster 的读写和普通的 memcached 的读写性能没有差别。

分布式扩展
Memcached 很突出的一个优点,就是采用了可分布式扩展的模式。可以将部属在一台机器上的多个 Memcached 服务端或者部署在多个机器上的 Memcached 服务端组成一个虚拟的服务端,对于调用者来说完全屏蔽和透明。提高的单机器的内存利用率 。
Socket 通信
传输内容的大小以及序列化的问题需要注意,虽然 Memcached 通常会被放置到内网作为
Cache, Socket 传输速率应该比较高(当前支持 Tcp 和 udp 两种模式,同时根据客户端的不同可以选择使用 nio 的同步或者异步调用方式),但是序列化成本和带宽成本还是需要注意。这里也提一下序列化,对于对象序列化的性能往往让大家头痛,但是如果对于同一类的 Class 对象序列化传输,第一次序列化时间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值