Memcached 架构

作者: 技术的游戏 | 来源:发表于2023-05-21 23:39 被阅读0次

    Memcached是一种内存中的键值存储,最初是用Perl编写的,后来重写为C语言。它受到Facebook、Netflix和Wikipedia等公司的欢迎,因为它简单易用。

    虽然当谈论到软件描述时,“简单”这个词已经失去了意义,但我认为Memcached是少数真正简单的软件之一。Memcached没有像持久性或丰富的数据类型这样的花哨特性。甚至分布式缓存也是客户端的责任,而不是Memcached服务器的责任。

    Memcached的后端只有一个任务,即内存中的键值存储。

    缓存是逃避行为

    Memcached被用作缓存来存储耗时的数据库查询或昂贵的HTTP响应。虽然缓存对于可扩展性至关重要,但它应该被视为最后的选择,让我来解释一下。

    我认为为每个遇到的耗时查询都运行一个缓存是一种逃避行为。我相信理解性能下降的原因才是关键,否则缓存只是一个权宜之计。如果是数据库查询,看看执行计划,是否需要索引?是否有很多逻辑读取,你是否可以重写查询或包含额外的预测性过滤器以最小化搜索空间?

    如果是RPC调用,考虑一下为什么调用本来就是冗余的,你是否可以在客户端消除这些调用。经常情况下,当使用不当时,库和框架会发出一系列查询。这也是为什么黑盒子必须被理解的原因。

    在尝试了所有的调优选项后,可能需要使用缓存,这时候Memcached是最适合的选择。

    Memcached架构

    在本文中,我将深入探讨Memcached的架构以及开发人员为保持其简单性和功能削减所做的努力。我将对某些组件发表自己的观点,认为它们应该是可选的。

    本文将涵盖以下主题:

    • 内存管理
    • 线程
    • 分布式缓存

    什么是Memcached?

    Memcached是一个用作缓存的键值存储。它被设计为简单,因此在某些方面有一些限制。这些限制也可以看作是特性,因为它们使Memcached变得透明。

    在Memcached中,键是字符串,长度限制为250个字符。值可以是任意类型,但默认情况下,值的大小限制为1 MB。键还具有过期日期或存活时间(TTL)。然而,不应依赖于此,因为最近最少使用(LRU)算法可能在访问之前删除过期的键。Memcached适用于缓存昂贵的查询,但不应依赖于其作为持久性或可靠存储。在构建应用程序时,始终假设Memcached不具备你所需的功能。为最坏情况做计划,希望发生最好的情况。

    内存管理

    当分配诸如数组、字符串或整数等项目时,它们通常会随机分配到进程内存中的某些位置。这会在物理内存中留下一些未使用的小内存间隙,这个问题被称为碎片化。

    当已分配项目之间的间隙继续增加时,就会发生碎片化。这使得很难找到足够大的连续内存块来存储新项目。从技术上讲,可能有足够的内存来存储项目,但内存分散在物理空间的各个位置。

    这是否意味着如果没有连续内存块存在,项目无法存储?实际上并非如此,借助虚拟内存的帮助,操作系统给出了应用程序正在使用连续内存块的错觉。在幕后,该块被映射到物理内存中的小区域。

    碎片化会导致程序运行变慢,因为系统需要组装内存碎片。虚拟内存映射的开销以及获取本应是单个内存块的多次I/O操作的成本相对较高。这就是为什么我们要尽量避免内存碎片化。

    Memcached通过预分配1 MB大小的内存页面来避免碎片化,这也是为什么默认情况下值被限制为1 MB。

    操作系统认为Memcached正在使用分配的内存,但实际上Memcached尚未在其中存储任何内容。当创建新项目时,Memcached会将项目写入分配的页面,强制使项目彼此相邻。这通过将内存管理移至Memcached而不是操作系统来避免碎片化。

    页面被分割成相等大小的块(Chunk)。每个块的大小由slab class确定。slab class定义了块的大小,例如Slab class 1的块大小为72字节,而slab class 43的块大小为1MB。

    项目由键、值和一些元数据组成,并存储在块中。例如,如果项目的大小为40字节,将使用整个块来存储该项目。最接近40字节项目的块大小为72字节,即slab class 1,在块中会有32字节的未使用空间。因此,客户端应该聪明地选择适合块的大小的项目,以尽量减少未使用空间。

    Memcached试图通过将项目放入最合适的slab class来尽量减少未使用空间。每个slab class都有多个页面。例如,对于slab class 1,每页有14,563个块,因为每个块的大小为72字节。如果一个项目小于等于72字节,它将完美地适应该块。但如果项目更大,比如900KB,它不适合slab class 1。因此,Memcached会寻找适合该项目的slab class。最接近1MB块大小的是slab class 43,项目将放在该块中。整个项目适应单个页面。

    注意我们不需要为项目分配内存,因为内存已经预先分配好了。

    我们来看一个新的例子,假设有一个大小为40字节的新项目,但为该slab class分配的所有页面都已满,因此无法插入该项目。

    Memcached通过分配一个新页面并将项目存储在空闲块中来处理这种情况。

    线程

    Memcached接受远程客户端连接,因此必须具备网络功能。Memcached使用TCP作为其主要的传输协议。虽然也支持UDP,但默认情况下已禁用,因为在2018年发生过一次称为反射攻击的攻击事件。

    Memcached监听线程创建一个TCP套接字来监听11211端口。它有一个线程旋转并监听传入的连接。该线程创建套接字并接受传入的连接。

    然后,Memcached将连接分配给线程池中的一个线程。当建立新连接时,Memcached从池中分配一个线程,并将连接的文件描述符分配给该线程。该工作线程现在负责从连接中读取数据。

    如果向连接发送数据流或请求获取一个键,则该线程轮询文件描述符以读取请求。每个线程可以承载一个或多个连接,并且线程池中的线程数量可以进行配置。

    多年前,线程管理更为关键,因为异步工作负载在2002年并不像2022年那样丰富。您看,当线程从连接中读取数据时,这个操作在2000年代初曾是阻塞的。线程在获取数据之前无法执行其他任何操作。工程师们意识到这是不可扩展的,于是异步I/O应运而生。几乎所有的读取调用现在都是异步的,这意味着线程可以在一个连接上调用读取操作,然后继续处理其他连接。

    然而,在Memcached中,线程管理仍然很重要,因为读取/写入涉及到一些CPU时间,例如哈希和LRU计算。如果一个线程为所有连接执行这些工作,可能无法扩展。

    分布式缓存

    Memcached服务器是隔离的,服务器之间不会互相通信。我非常喜欢这种设计的简洁和优雅之处。如果要分发你的键,客户端必须自行处理,你可以构建自己的Memcached客户端来实现这一点。

    服务器之间的通信会显著复杂化架构,就像在ZooKeeper中一样。

    总结

    在本文中,我们讨论了Memcached的架构。我们讨论了内存管理的重要性,以及使用slabs和pages来分配内存以避免碎片化的方法。我们还讨论了LRU(最近最少使用)算法,我个人认为它应该是用户可以选择禁用的选项。接下来,我们讨论了线程对于处理大量连接时如何提高性能的重要性。我们通过一些Memcached的读写示例来说明这一点,并介绍了锁定对它们的影响。最后,我们讨论了使用Node进行分布式缓存架构的演示。

    如果你喜欢我的文章,点赞,关注,转发!

    相关文章

      网友评论

        本文标题:Memcached 架构

        本文链接:https://www.haomeiwen.com/subject/merzsdtx.html