铸造车间 - Memcached

作者:Reuven M. Lerner

现代 Web 开发人员的口号之一是可扩展性。 无论是关注关于 Twitter 服务器的最新消息,还是编写我们自己的应用程序,开发人员始终在考虑他们的系统是否具有可扩展性。

在 2008 年的春季和夏季,这个问题尤为突出,因为 Ruby on Rails(我首选的 Web 开发平台)因其 RAM 使用量和相对较慢的执行速度而受到批评。 Twitter 在 2008 年上半年经历的大规模服务器问题被广泛描述为源于 Twitter 对 Rails 的使用(尽管 Twitter 技术团队否认),并导致人们猜测 Rails 不能用于可扩展的应用程序。《RailsEnvy》每周播客的主持人之一在每集中都讽刺地说“Rails 不能扩展”,因为这句话太常被说了。

毫无疑问,Rails 比许多其他应用程序开发框架更消耗资源。 这部分是由于 Ruby 语言本身需要改进——这些改进看起来将在来年实现。 并且,Rails 框架确实比其某些同类框架(如 Django)使用更多的 CPU 和内存,这归因于其提供的功能的性质。

但是,我认为,将 Rails 称为资源密集型与称其为天生不可扩展之间存在差异。 可扩展性更多地与应用程序的架构和设计有关,使其能够从包含 Web 服务器和数据库服务器的单个盒子自然增长到服务器网络。 用 C 语言编写的 Web 应用程序可能执行速度非常快,因此,可以在单个服务器上处理更大的负载,但这并不意味着该应用程序本质上更具可扩展性。 在某个时候,即使是高效的 C 程序也会达到其容量,如果它在设计时没有考虑到这一点,那么更高效的应用程序将是可扩展性较差的应用程序。

因此,我倾向于将可扩展性视为一个架构问题,它忽略了实现应用程序的特定编程语言,并且与执行速度和效率问题不同。 您可以拥有用低效框架编写的高度可扩展的程序,但这确实需要更多的规范,并要求程序员仔细考虑他们编写代码的方式。 即使您从一台计算机开始,以可扩展的方式设计软件也可以让您将负载(和任务)分配到多个专用服务器上。

与可扩展性有关的最重要问题之一实际上与编写程序的 Web 应用程序框架几乎没有关系。 大多数现代 Web 应用程序使用关系数据库进行持久数据存储,这意味着数据库服务器可能成为瓶颈。 即使数据库服务器没有达到其极限,但事实是,关系数据库处理查询、检索一个或多个适当的行并将它们发送回查询应用程序也需要时间。

如果您的应用程序是高度动态的,则每个页面可能使用多达十几个 SQL 调用,这不仅会给您的数据库带来压力,还会显着降低您服务每个 HTTP 请求的速度。 更长的请求时间意味着您的用户将等待更久,并且您的服务器将需要更多进程来处理相同数量的请求。

一种解决方案是使用多个数据库服务器。 有一些解决方案可以将来自开源数据库(例如,PostgreSQL 或 MySQL)的多个服务器连接在一起,更不用说商业数据库(如 Oracle 和 MS-SQL)的专有(且昂贵)解决方案。 但是,这是一项棘手的事情,许多解决方案都涉及所谓的主从复制,其中一个数据库服务器(主服务器)用于数据修改,另一个数据库服务器(从服务器)可用于读取和检索信息。 这可能会有所帮助,但这并不总是您需要的解决方案。

但是,还有另一种解决方案——一种易于理解且相对容易实现的解决方案:memcached(发音为“mem-cash-dee”)。 Memcached 是一个开源的分布式存储系统,它充当跨网络的哈希表。 您几乎可以将任何您喜欢的东西存储在 memcached 中,并且可以快速轻松地检索它。 有许多编程语言的客户端库,因此无论您喜欢使用什么框架,都可能有一个 memcached 解决方案适合您。

本月,我们将快速了解一下 memcached。 当集成到 Web 应用程序中时,它应该有助于使该应用程序更具可扩展性——这意味着它可以处理大量用户,分布在大量服务器上,而无需您重写大量代码。 Ruby on Rails 的 2.1 版本甚至将 memcached 支持集成到框架中,使其在您的应用程序中更容易使用 memcached。

Memcached

正如我之前提到的,您可以将 memcached 视为一个网络可访问的哈希表。 就像哈希表一样,它具有键和值,每个键存储一个值。 也像哈希表一样,存储和检索数据的方法不多。 您可以设置键值对; 您可以根据键检索值,并且可以删除键。

这似乎是一组有限的功能。 而且,如果您将 memcached 视为您的主要数据存储,那确实是有限的。 但是,这正是重点。 Memcached 从未被设计为通用数据库或用作应用程序的主要持久存储机制。 相反,它的目的是缓存您已经从关系数据库中检索到并且您可能需要在不久的将来再次检索的信息。

换句话说,memcached 允许您使您的应用程序更具可扩展性,让您利用数据经常从数据库中重复获取(通常由多个用户获取)这一事实。 通过首先查询 memcached 并在必要时才访问数据库,您可以减少数据库的负载并提高 Web 应用程序的有效速度。

您的主要成本是将 memcached 集成到您的应用程序中所花费的时间、您分配给 memcached 的 RAM 以及您专用于 memcached 的服务器。 您想要分配给 memcached 的服务器数量当然取决于您网站的大小和规模。 您在开始时可能只需要一个 memcached 服务器,但您很可能需要扩展到十个、一百个甚至数百个 memcached 服务器(正如我听说 Facebook 使用的那样)以最大化应用程序的速度和效率。

使用 Memcached

在我的 Ubuntu 系统上,我能够使用以下命令安装 memcached:

apt-get install memcached

然后,我使用以下命令启动了 memcached:

/usr/bin/memcached -vv -u reuven

-vv 选项启用非常详细的日志记录,使我能够准确地了解从服务器的角度来看发生了什么。 -u 标志让我设置 memcached 将在其下运行的用户; 出于安全原因,它不能以 root 用户身份运行。

现在,让我们编写一个简短的客户端程序来存储和检索值。 我将用 Ruby 编写客户端程序,尽管您可以使用几乎任何您喜欢的语言(包括 Perl、Python 或 PHP)。 我使用了 memcache-client Ruby gem 连接到 memcached 服务器,我是通过键入以下命令安装的

sudo gem install memcache-client

这是一个连接到 memcached 服务器、存储一个值然后检索该值的简短程序

#!/usr/bin/ruby

# Load necessary libraries
require 'rubygems'
require 'memcache'

# Create the memcached client
CACHE = MemCache.new 'localhost:11211'

# Set a value
CACHE.set('foo', 'bar')

# Retrieve a value
value = CACHE.get('foo')
puts "Value = '#{value}'"

如您所见,我们做的第一件事是创建一个 memcached 服务器的客户端。 您可以指定一个或多个服务器; 在这种情况下,我们表明只有一个服务器,在 localhost 上运行,端口为 11211。 您可能会惊讶地发现,尽管 memcached 被描述为分布式缓存机制,但各个 memcached 服务器之间永远不会相互通信。 相反,客户端决定它将在哪个服务器上存储特定数据,并且它使用相同的算法来确定应该查询哪个服务器来检索该数据。

因此,在这个程序中,我们连接到我们的服务器,设置一个值(很像我们在哈希表中设置值一样),然后检索它。 这没什么令人兴奋的,尽管 memcached 服务器可能在另一台计算机上的事实已经使事情变得有趣。

这是前一个程序的略微变体。 请注意 CACHE.set 的第三个参数,以及之后的 sleep 调用

#!/usr/bin/ruby

require 'rubygems'
require 'memcache'

CACHE = MemCache.new 'localhost:11211'

CACHE.set('foo', 'bar', 3)

sleep 5

value = CACHE.get('foo')
puts "Value = '#{value}'"

这次,输出看起来像这样

Value = ''

嗯? 我们的值怎么了? 我们不是设置了吗? 是的,我们设置了,但是我们告诉 memcached 在三秒钟后使该值过期。 这是 memcached 使其易于集成到 Web 应用程序中的一个重要方式。 您可以指定 memcached 应该继续将此数据视为有效的时间长度。 通过不传递过期时间,memcached 永远保留该值。 允许数据过期可确保缓存的数据有效。

您应该在缓存中保留数据多长时间是一个只有您才能回答的问题,这可能取决于您存储的对象类型。 来自您在线商店的订单可能应该在短时间内过期,因为它们可能会随着用户访问您的网站而更改。 但是,关于用户的信息一旦注册就不太可能更改,因此长期保留这些信息可能是有意义的。

对我来说,将 memcached 描述为复杂对象(例如订单或人员)的存储库似乎很奇怪。 然而,memcached 完全能够处理此类对象,前提是它们由客户端软件编组和解组。 因此,我们可以有以下简短的程序

#!/usr/bin/ruby

require 'rubygems'
require 'memcache'

CACHE = MemCache.new 'localhost:11211'

CACHE.set('foo', [:a, :b, 'c', [1,2,3],
        {:blah => 5, :blahblah => 10}, Time.now])

value = CACHE.get('foo')
puts "Value = '#{value.map{ |i| i.class}.join(', ')}'"

果然,我们看到 memcached 很高兴设置和检索各种类的值。 这意味着即使我们创建一个复杂的类,我们也可以将其存储在 memcached 中并在以后检索它。

结论

Memcached 是几乎所有 Web 应用程序扩展策略的重要组成部分。 它可以显着减少访问某些类型信息所需的时间,从而加快用户的响应时间,并释放关系数据库服务器以执行其他作业。 确切地决定哪些对象可以并且应该存储在 memcached 中,以及确定它们应该在缓存中保留多长时间才过期,是每个单独的应用程序都必须解决的问题。

下个月,我将解释 memcached 支持是如何集成到 Ruby on Rails 中的,这使得在您自己的应用程序中利用这项技术非常容易——并且,我敢说,帮助您的应用程序变得真正可扩展。

资源

memcached 的主页位于 www.danga.com/memcached。 该站点包含指向软件(服务器和客户端)、文档以及关于 memcached 的文章的链接。

我使用的 Ruby 客户端称为 memcache-client,它通过 RubyForge 在 rubyforge.org/projects/seattlerb 上可用。 此页面适用于 Seattle.rb 运行的所有项目,包括 memcache-client。

我还没有机会阅读或评论它,但是有一本关于 memcached 的书,不出所料,名为 Using memcached,由 Josef Finsel 撰写,并由 Pragmatic Programmers 在其“星期五”系列中以仅 PDF 格式出版。

Reuven M. Lerner,一位长期的 Web/数据库开发人员和顾问,是西北大学学习科学专业的博士候选人,研究在线学习社区。 他最近(与他的妻子和三个孩子)在芝加哥地区生活四年后返回他们在以色列莫迪因的家。

加载 Disqus 评论