`

memcache 原理

 
阅读更多

 

1.1 为什么引入缓存

在传统的后端架构中,由于请求量以及响应时间要求不高,我们经常采用单一的db的结构。如下图1 所示,应用服务器直接存取DB。这种架构简单,但也存在着如图中所描述的问题,即DB存在性能瓶颈,随着请求量的增加,单DB无法继续稳定提供服务。

对于请求量不大的场景,我们可以通过对DB进行读写分离、一主多从、硬件升级(SSD)等方式提升系统的承载能力以及冗余能力,但这几种提升方式存在着以下几个缺陷:

1、性能提升有限。很难得到量级上的提升,而大部分互联网产品直接面对着千万级用户访问,单一使用db的结构,难以达到性能要求

2、成本高昂,为了达到N倍的承载能力的提升,需要至少N倍以上DB服务器

那么我们是否有更好的方式来做呢?

相信大家在学习操作系统的时候,一定看过如图2 类似的一组数据:

从图2中可以看到,一次内存寻址的时间大概在100ns,顺序从内存中获取1MB数据的时间大概在250000ns。对应的,我们可以看到一次磁盘寻址以及顺序读取磁盘的速度大概在千万ns级别。这里我们可以得出一个结论,也即内存数据的获取速度大概在磁盘的获取速度的两个数量级。也因此我们可以通过引入缓存中间件,来提高系统整体的承载能力,原有的单层db结构也可以变为如图3所示的缓存+db结构。

通过在应用服务与DB中间引入缓存层,我们可以得到如下三个好处:

1、读取速度得到提升

2、系统扩展能力得到大幅增强。我们可以通过加缓存,来让系统的承载能力提升

3、总成本下降,单台缓存即可承担原来的多台DB的请求量,大大节省了机器成本

常见的缓存服务有本地缓存,memcached,redis等。他们各有自己的特点,本文以下内容主要是结合微博对于memcached一些使用来做讲解


1.2 Memcached

1.2.1 Memcached介绍

Memcached 是一个高性能的分布式内存对象缓存系统,广泛应用于动态Web应用,以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高网站的访问速度。

Memcached都有哪些特点呢?

1、本身是一个kv存储系统。存储的时候,需要指定key以及对应的value;获取的时候,只能通过key获取

2、协议简单,只支持简单的命令如:set、get、cas、delete、stats、incr、decr等,而无类似于mysql的select、join等复杂的sql命令。所有的命令以及数据都是以文本的形式传递,这也就意味着mc的协议无需特殊的客户端解析工具。我们可以通过在终端telnet进行命令传递以及数据获取。

3、支持设定过期时间

4、支持LRU等淘汰算法

 

1.2.2 Memcached的安装以及使用

Memcached的安装可以通过在官网上下载源码的方式安装:http://memcached.org/

相关安装教程可以参考官网安装步骤,亲测有效~:http://code.google.com/p/memcached/wiki/NewInstallFromSource

安装完成后,可以在linux终端执行以下命令启动 ./memcached -l 11211

新启一个终端,敲入命令telnet localhost 11211,即进入命令行模式

下面我们一起来试试Memcached hello world调用:

因本文篇幅有限,具体命令不再展开描述,大家如果感兴趣可以通过官网的链接:http://code.google.com/p/memcached/wiki/NewCommands 了解。

 

1.2.3 Memcached内存分配原理介绍

掌握Memcached的安装、使用命令,其实对大部分的同学来说已经足以开展相关开发工作了。但当碰到一些线上问题的时候,单纯的会用Memcached是无法快速、合理的分析问题所在的。所以接下来我们将介绍Memcached的内存分配管理原理。

Memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存。Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块, 以完全解决内存碎片问题。

在介绍memcached的内存分配原理之前,需要跟大家说明以下几个关键的名词的概念:

item:一个待存储的元素,按字节计算大小,可以理解为一个物品

Chunk:用于缓存item的内存空间。可以理解为一个储物格

Slab Class:特定大小的chunk的组。可以理解为储物格按大小进行分类,如80B作为一类,96B作为一类....

Page:分配给Slab class的内存空间,默认是1MB。分配给Slab之后根据slab class的大小切分成chunk。可以理解为一个page是一个固定大小的柜子,上面可以按slab class进行分割,一个柜子只能按一个slab class进行分割。柜子上的格子数为柜子大小/ 储物格的大小

介绍完上述的几个基本概念后,我们可以来看看mc在分配内存的时候是怎么处理的。

如图5为一个memcached示例在启动的时候,可以指定的一些参数,初始大小为slab class的起始大小,增长因子为下一个slab class是初始因子的倍数。如图中所示,初始大小为80B,增长因子为1.5。则mc在启动后,会按下图生成slab class表。

完成初始后,当某一个请求到来的时候——如图中所示由一个123B大小的元素希望找到存储空间,memcached会通过slab class表找到最合适的slab class:比元素大的最少的那个,在图中场景下为180B,即使所需的空间只要123B。

此时Memcached示例并没有分配任何的空间给180B的slab进行管理。所以为了能让请求的元素能存储上,Memcached实例会分配1 个page给180这个slab(在默认情况下为1MB实际内存)。

180B slab class在获取到1MB的空间后,会按照自己的大小对page进行分隔,也即1MB/180=5828个具体的存储空间(chunk)。此时,123B的请求就可以被存储起来了。

随着时间的慢慢推移,memcached的内存空间会逐步被分配完,如下图8所示:

我们可以看到,memcached划分给每个slab的page数是不均等的,存在部分的slab是可能一个page都分配不到的。

假设所有的内存都分配完,同时每个slab内部的chunk也都分配完了。此时又来了一个新的元素123B,那么就会触发memcached的淘汰机制了。

memcached首先会查看180B的slab是否存在过期的元素,如果存在,则先清理部分,预留空位。如果180B这个slab的数据都比较热(没有过期),则按LRU进行淘汰。需要注意的是,淘汰是在slab内部进行的,也即在上面的场景中只有180Bslab内部进行淘汰剔除,对于其他的slab,是没有受到影响的。memcached也不会回收比较空余的其他slab的page。也即一个page被分配给某个slab后,他将一直被这个slab所占用,永远无法被mc回收,直到memcached重启。

这个特性被称为Memcached的钙化问题:Memcached在线上跑了一段时间后,内存按原始访问模式分配内存。当访问模式变更后,原有的分配模式可能导致缓存频繁出现数据剔除问题。最典型的场景即为内存尚有空余,但一直有数据被剔除,命中率一直上不去。对于这种情况,解决方法为重启缓存。

 

1.2.4 小结

上面两节中我们主体介绍了Memcached的使用以及内部的一些内存分布原理。相信大家在自己动手操作过后,会有一个较深的影响。在实际的使用中,除了关注缓存本身的一个使用外,我们还会有其他一些关注点。如高可用、扩展性等,这些关注点的实现并不是单纯依靠单一服务节点即可。为了能够满足上述的几个关注点,我们需要引入分布式缓存结构。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics