Riak 简介,第 2 部分: 将 Riak 集成为 Web 应用程序的重负荷缓存服务器

使用 Riak 作为一个缓存服务器,帮助缓解应用程序和数据库服务器上的负载

本文是有关 Riak 的两部分系列中的第 2 部分,Riak 是以 Erlang 编写的一个高度可扩展的分布式数据存储,它以 Amazon 的高可用性键值存储 Dynamo 为基础。对于负载较重的网站,一个可扩展的缓存解决方案可以减轻应用程序和数据库服务器上的负载。这尤其适用于经常被读取但只是偶尔更新的数据。探讨一个在线投注站点的深入示例,以及如何使用 Riak 来实现一个缓存解决方案。您还将了解到如何将 Riak 与一个现有的网站集成,并了解搜索等其他 Riak 功能,以及如何用它来直接服务用户请求。如果您想继续关注本文的示例,则需要建立一个可用的 Riak 集群。您可以在本系列的 Riak 简介,第 1 部分:与语言无关的 HTTP API 找到在本地建立集群的步骤。

Simon Buckle, 独立顾问, Freelance

Simon Buckle 的照片Simon Buckle 是一名独立顾问。他的兴趣包括分布式系统、算法和并发。他持有伦敦 Imperial College 的计算硕士学位。请从 simonbuckle.com 查看他的网站。



2012 年 7 月 02 日

简介

某些类型的数据表现出使自己适合于被缓存的访问模式。例如,在线投注站点具有一个有趣的负载特征:用户常常请求提供赔率和投注单,而这些信息相对来说很少被更新。

本系列中的其他文章

查看 Riak 简介 系列中的更多文章。

这些情况需要具有以下特征的高度可扩展的系统,以应对高负荷的要求:

  • 该系统充当一个可靠的缓存,以减少对应用服务器和数据库的需求
  • 缓存项目是可搜索的,所以您可以更新它们或使它们失效
  • 任何解决方案都能被轻松地集成到现有站点

Riak 对于这样的解决方案是一个不错的选择。

Riak 对于实现这样一个缓存解决方案并非惟一的候选者;有许多不同的缓存可用。其中较为流行的一种是 memcached;然而,与 Riak 不同,memcached 不提供任何类型的数据复制,这意味着,如果保存特定项目的服务器停机,该项目会变得不可用。另一种流行的键/值存储是 Redis,它也可作为缓存使用,通过主从配置支持复制;Riak 没有一个主人(节点)的概念,因此,这使系统对故障更有弹性。


网站集成

任何解决方案都需要很容易地被集成到现有网站。能够做到这一点很重要,因为并不一定有可能(或者甚至有需要)将您现有的全部数据迁移到 Riak。如前所述,某些类型的数据适合缓存,在一个键/值存储的情况下,如果您通过一个主键访问数据则更是如此。这是一种更适合迁移到 Riak 的数据。

正如在本系列的有关 Riak 的 Riak 简介,第 1 部分:与语言无关的 HTTP API 所述,PHP、Ruby 和 Java™ 等语言中提供了大量客户端库;这些库提供一个 API,使集成 Riak 非常简单。在本例中,我演示了 PHP 库的使用,以展示如何将 Riak 与现有网站集成。

图 1 显示了本例需要考虑的设置。我忽略了负载均衡、防火墙等细节。在本例中,服务器本身只是安装了一个 LAMP 堆栈的简单的前端箱。

我将假设,Riak 仅在内部使用(不能从外面访问它),且在一个非敌对的环境中运行,所以不存在身份验证等与安全相关的问题。该假设并不是像它看起来那么差劲,因为不管怎样 Riak 并没有任何内置的授权;您真的应该将身份验证等安全措施委托给应用程序。

图 1. 一个简单的网站集成
该图显示了服务器如何与关系数据库及 Riak 集群进行交互

下面是一个基本示例,演示您可以如何将 Riak 集成到您的现有网站。您将创建一个简单的表单,在提交表单时,根据在表单中输入的值,该表单将使用 PHP 客户端存储 Riak 中的对象。

图 2 显示了一个简单的表单示例,管理员可能会使用它在系统中创建一个投注项。用 HTML 创建该表单,并让它对 清单 1 中的 PHP 脚本执行一个 POST;您可以将本文所附的 源代码 中的类似表单作为一个起点。表单中输入的 “key” 字段将被用作在桶中存储的对象的键。

图 2. 创建投注的示例表单
带有 Key、Odds 和 Description 输入字段及一个 Create 按钮的表单屏幕截图

清单 1 的示例 PHP 代码显示了如何使用 PHP 客户端库来集成 Riak。将 PHP 客户端库路径(在 require_once 中指定)更改为您安装它的位置。在本例中,我只是将它与 PHP 脚本放在同一目录中。默认情况下,所有的客户端库都期待在端口 8098 上提供 Riak。

清单 1. 集成 Riak 的示例 PHP 代码
<?php

require_once('./riak.php');

# Could do check here to see if the current user has the
# appropriate credentials ? delegated to application.

$client = new RiakClient('192.168.1.1', 8098);
$bucket = $client->bucket('odds');

$bet = $bucket->newObject($_POST['key']);        
$data = array(
    'odds' => $_POST['odds'],
    'description' => $_POST['description']
);
$bet->setData($data);

# Save the object to Riak
$bet->store();

echo "Thanks!";
?>

将代码保存为一个 PHP 文件(按您喜欢的方式命名),将其和表单上传到您的网站上的某个位置,例如,http://www.yoursite.com/riak-test.php。填写示例表单,并提交它。为了证明它有效,尝试使用您在创建项目时在表单中输入的键直接从 Riak 中检索(参见 清单 2)。

清单 2. 从 Riak 中检索项目
$ curl -i http://localhost:8098/riak/odds/<key>
...
{ "odds":"", "description":"" }

虽然该集成示例使用了 PHP 客户端,但其方法与 Java 或 Ruby on Rails 等其他语言或应用程序框架类似。


直接向请求提供服务

除了使用客户端库将 Riak 集成到当前设置外,还可以从 Riak 向用户请求直接提供服务,并将它用作一个简单的 HTTP 引擎。为了演示这一点,我将创建一个简单的演示,向您展示如何从 Riak 直接请求页面。

下载本文的 源代码。请确保 Riak 正在运行,然后执行脚本 load.sh。这个脚本会将所有的 HTML 和 JavaScript 文件复制到一个名称为 demo 的桶中。本例使用 JavaScript 客户端。

要查看演示,请在您的浏览器中打开以下 URL:http://localhost:8098/riak/demo/demo.html

如果您在表单中输入了一些值来创建一个投注,并提交了表单,则会将一个 JSON 对象存储在 Riak 中。对象的属性将与表单中的字段对应。您会被重定向到一个显示您刚刚创建的对象值的页面。

清单 3 显示通过您输入的值来创建对象的代码。keyoddsdescription 等值来自在表单中输入的值。

清单 3. JavaScript 客户端库在 Riak 中的示例用法
client.bucket("odds", function(bucket) {
    var key = $('#key').val();
    bucket.get_or_new(key, function(status, object) {
        object.contentType = 'application/json';
        object.body = { 'odds': $('#odds').val(), 'description': $('#desc').val() };
        object.store(function(status, object, request) {
            if (status == 'ok') {
                window.location = "http://localhost:8098/riak/odds/"+key;
            } else {
            alert("Failed to create object.");
        }
        }); 
    });
});

如前所述,我假设,Riak 在一个可信的环境中运行。在这种情况下,Riak 中用于存储和检索项目所添加的页面就不会产生安全问题;但是,您并不希望这种功能在没有某种形式的身份验证的前提下就完全暴露在 Internet 中。

虽然这是一个简单的示例,但它使您了解到了 Riak 如何可以直接向页面请求提供服务。例如,您可以使用 JSONP 或跨源资源共享(AJAX 请求被相同的域策略限制在页面所驻留的同一台服务器上)等技术,也可以代理通过服务器向 Riak 发送的请求,从而在您现有的 Web 页面中直接包括存储在 Riak 中的数据,以获取所需的数据。


使用 Riak 作为缓存

缓存用于提供数据的快速访问。如果缓存中包含了请求的数据(缓存命中),应用程序可以通过从缓存中读取值来快速向请求提供服务,这比从数据库中检索值更快。如果缓存中没有数据(缓存未命中),那么应用程序通常必须在数据库中检索数据。一般情况下,您可以从缓存中服务的请求越多,系统将会越快。Riak 具有多项特性,这使其成为缓存解决方案实现的一个不错的选择。

其中一个这样的 Riak 特性是其可插拔的 (pluggable) 存储后端;存储后端决定如何存储数据。有若干个可用的存储后端,但我不打算在这里全部一一介绍(有关更多信息,请参阅 参考资料)。默认存储后端是 Bitcask,这是一个 Erlang 应用程序,提供一个 API,用于存储和检索受散列表支持的数据,该散列表提供了数据的快速访问;数据是永久性的。

有一个后端也许与本文关系更紧密:Memory 后端。Memory 后端使用一个内存表来存储其所有的数据(它在内部使用 Erlang 的 ets 表),并且,在启用时,使 Riak 的行为像一个设定了有效期的 LRU 缓存。比起必须在磁盘上检索数据,使用内存存储的优势在于它明显快得多。当数据被存储在内存中(它不是永久的)且一个节点出现故障时,在该节点中存储的数据将丢失。若您将它用作缓存,这就不是一个问题了(应用程序总是可以从数据库检索数据),就像您将 Riak 用作主数据存储一样。Riak 在集群中跨多个节点复制数据,因此它仍然是可用的。

Riak 自带 Memory 后端。为了使用 Memory 后端,请打开集群中每个节点的 app.config,定位属性 storage_backend,并将其从 riak_kv_bitcask_backend 更改为 riak_kv_memory_backend。现在将 清单 4 中的代码添加到文件的末尾。

清单 4. 使用 Memory 后端
{memory_backend, [
    {max_memory, 4096},	%% 4GB of memory
    {ttl, 86400}        %% Time in seconds
]}

将值更改为适合于您的设置的值。重新启动集群中的节点。

在 Riak 集群内也可以运行多个存储后端。这非常有用,因为这意味着可以针对不同的桶使用不同的后端。例如,您可以配置一个桶(让我们称之为 cache)来使用 Memory 后端,但对于其他桶(那些应当保存数据的桶),则使用 Bitcask。

既然您已经让 Riak 设置的行为像缓存一样,那么您需要一些方法来访问集群中的数据,以便更新它,或出于某种原因使它失效(在它的有效期结束前)。


查找什么内容吗?

正如您已经看到的,当使用 HTTP 界面检索在 Riak 中存储的数据时,您要构造一个 URL,其中包括桶的名称以及您要检索的对象的键,然后在该 URL 上执行一个 HTTP GET。当您知道键是什么时,这就完全足够了!但是,有时您并不知道要检索的对象的键,或者您要检索满足一定条件的一组对象。那么,您需要一种方法来搜索在集群中保存的对象。

您已经看到如何通过存储在集群中的文档运行一个 Map/Reduce 作业来查询数据。一般来说,执行查询的时间与集群中的文档数量成正比;文档越多,查询这些文档所需要的时间越长。对于时间不敏感的查询,这不是一个问题。我这样说的意思是,用户并不指望立即得到答复的查询。对于像搜索这样的操作,每次都(动态)搜索所有文档是不可行的;获得结果的时间可能是几分钟,也可能是几小时!

幸运的是,Riak 对该问题已经有一个解决方案:Riak Search。Riak Search 提供搜索存储在整个集群中的文档时所需要的功能。搜索这个主题对于本文来说过于庞大,无法深入讨论,但从高层次来说,它的工作方式是这样的:文档被标记化(Riak Search 使用标准的 Lucene 分析器),并被添加到一个反向索引。然后,根据用户输入的搜索项查询该索引。当新文件被添加时,它们也被索引并添加到索引中。

Riak Search 默认被禁用。在您可以使用它之前,您需要先启用它。在集群中的每个节点上,打开 rel/riakN/etc/app.config,定位属性 riak_search 并将它设置为 true。您需要重新启动集群中的节点。

Riak 通过使用提交前 (pre-commit) 和提交后 (post-commit) 挂钩,允许您指定文档被添加到桶之前和之后要运行的函数的名称。例如,在将文档添加到桶之前,您可能要检查文档是否有特定的必需字段。要搜索一个文档,需要先对其进行索引。要做到这一点,需在存储文档的桶上安装一个 pre-commit 挂钩。要做到这一点,请运行以下命令:$ rel/riak/bin/search-cmd install <bucket name>

这将在桶上安装一个提交前挂钩 riak_search_kv_hook。现在,每当文档被添加到该桶,它就会被分析,并被添加到索引。空白分析器是默认的分析器;它基于空白将字符处理成标记,然后标记被索引。有一些不同的分析器可供使用,您也可以定义自己的分析器。

在许多情况下,Riak Search 知道如何索引您的数据。例如,开箱即用的,如果一个 JSON 对象被添加到某个桶,每个属性的值将被索引,并且可以在查询字符串中使用属性名称来查询。搜索示例请参见 清单 5。对于更复杂的结构,您可以定义自己的模式,告诉 Riak Search 如何索引数据。

当您已索引一些文档后,您需要能够对它们发出查询。一种方法是从 Erlang shell 运行查询。例如,在 清单 5 中的查询搜索与赛马有关的所有投注的赔率桶;您通过查询存储项的 description 属性完成该搜索。

清单 5. 搜索与赛马有关的投注的赔率桶
$ rel/riak/bin/riak attach

search:search(<<"odds">>, <<"description:horse">>).

此外,Riak Search 还为文档搜索提供了一个 Solr 兼容的 HTTP API。Apache Solr 是一个流行的企业搜索服务器,带有一个类似于 REST 的 API。通过使 API 与 Solr 兼容,应该可以断开 Solr(如果您使用它),并改为使用 Riak Search 支持搜索。例如,要使用 Solr 界面搜索特定活动的赔率,您可以这样做:$ curl "http:localhost:8098/solr/odds/select?start=0&q=description:horse"

利用搜索设置,您现在即使不知道正在查找的项目的主键,也可以在数据存储中定位这些项目。


结束语

本系列的其他文章

查看 Riak 简介 系列中的更多文章。

Riak 的扩展能力和可靠地复制数据的能力(加上搜索等其他特性)使其成为实现重负载站点的缓存解决方案的理想选择。您可以很容易地将它集成到现有站点。利用其直接为请求提供服务的能力,您可以使用 Riak 减少和消除应用程序和数据库服务器上的负载。


下载

描述名字大小
本文源代码riakpt2sourcecode.zip85KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Web development
ArticleID=823723
ArticleTitle=Riak 简介,第 2 部分: 将 Riak 集成为 Web 应用程序的重负荷缓存服务器
publish-date=07022012