这个共分 5 部分的系列文章向您介绍了如何使用 Perl 和 Apache 访问 Amazon 的 Simple Storage Service (S3) 和 SimpleDB,从而构建一个照片共享网站。在本期文章中,通过学习 URL 如何为上传的文件创建 SimpleDB 记录,实现站点与 SimpleDB 的交互。同时了解如何以 SimpleDB 记录的形式创建、编辑、删除某个用户的照片评论。
距离我的上一期文章已经有一段时间了,现在我们来回顾一下:
第 1 部分解释了 S3/SimpleDB 架构以及如何通过实际示例使用它们。 第 2 部分展示了如何通过 HTML 表单,从一个网页中将文件上传到 S3,从而最小化服务器负载。
现在,让我们真正开始深入了解照片共享网站与 Amazon SimpleDB 的交互,首先了解成功的 URL 如何为上传的文件创建 SimpleDB 记录,然后了解如何以 SimpleDB 记录形式创建、编辑、删除某个用户的照片评论。(记住,我们并不是要比较 SimpleDB 和 Google 的 BigTable 或 CouchDB 之类的独立解决方案)。
和此前的文章一样,我将使用 share.lifelogs.com 作为域名。
查看数据库结构
回顾一下 第 1 部分,我们设置了一个如清单 1 所示的表结构:
清单 1. 来自第 1 部分的表结构
share_photos:"http://developer.amazonwebservices.com/connect/images/amazon/logo_aws.gif" { user: "ted", name: "Amazon Logo"}"http://images.share.lifelogs.com/funny.jpg" { user: "bob", name: "Funny Picture", s3bucket: "images.share.lifelogs.com" }share_users:"ted" { given: "Ted", family: "Zlatanov" }"bob" { given: "Bob", family: "Leech" }share_comments:"random-string"{ url: "http://images.share.lifelogs.com/funny.jpg", comment: "Ha ha", posted_when: "2009-03-01T19:00:00+05" }"random-string2"{ user: "ted", url: "http://developer.amazonwebservices.com/connect/images/amazon/logo_aws.gif", comment: "No it doesn't", posted_when: "2009-03-01T20:00:01+05" }"random-string3"{ url: "http://developer.amazonwebservices.com/connect/images/amazon/logo_aws.gif", comment: "No it doesn't", reply_to: "random-string2", posted_when: "2009-03-01T20:00:01+05" }
在这个版本的结构中,您可以将所有有关某个主题的评论存储到一个 comment 结构内部,但是,两名用户同时删除或编辑同一主题的评论是非常危险的。
如我在第 1 部分中谈到的一样,这种结构需要进行修改。SimpleDB 的一大优点就在于其灵活性,因此让我们利用这一点。正如 Emerson 所说,“只有无知的人才会愚蠢地坚持”。当然,他也说过 “我痛恨引用名人语录”。多么无聊的超验主义者。
我发现 SimpleDB 暂存器(scratchpad)比不上从代码直接生成调用,但是您也许会喜欢它。查看参考资料了解暂存器 URL。您还可以购买一些 SimpleDB 管理工具,对 Firefox 使用免费工具,或者尝试 typica shell。参考资料小节提供了所有相关内容。
上传和修改照片
首先,注意 SimpleDB 允许对单一键使用多个值。因此我提到的照片 URL 或评论文本可以是数组而不是单个值。我们不会使用这个特性,为了保持简单对每个键使用一个值。
让我们首先从照片更新开始。每次完成一次上传后,将运行代码来向 share_photos 表添加新照片。第 2 部分展示了 S3 上传表单;下一次我们将把此表单连接到本文编写的代码中。
现在,让我们编写一个简单的脚本来添加照片。您将获得一个用户名、一个 URL、一个照片名和一个可选的 S3 bucket 名。S3 bucket 就是指表中的一个字段;URL 将用于显示和使用照片。我们希望使用一个复制 URL 来拒绝上传。可以这样做吗?
分布式数据存储的一个问题就是,您放入其中的数据可能无法超越网络的局限性。就好象从日本呼叫欧洲一样(或做任何体验到网络延迟或语音信号延迟的操作):当对方与您保持同步时,每说一句话都会产生轻微的延迟。您可以不间断地开始讲话,但是另一端的回答将会与您的声音重叠,令您感觉自己好像是一个参加重要会议的主管。尽管延迟性可以为您带来这种良好的感觉,但是它不利于展开有序的会话。
同样地,如果将数据放到 SimpleDB 系统中,当数据流入数据中心时也会产生停顿。目前,Amazon 还没有确认这一点,但是据我所知,它将构成银河意识(galactic consciousness)的一部分,并改善 Runu 星球(距离 Betelgeuse 3 个秒差距,左转并继续逼近,直到您看见它)上类似于蛞蝓的生物的生活。因此每次使用 SimpleDB 时,您就会为 Runu 星球上类似蛞蝓生物做了一件好事。并且您会认为这篇文章不像是教学文章。
无论如何,在提高银河意识的同时,您的数据是实时的,并且可以通过查询查看。但是如果在所有一切发生之前发出查询,那么不仅不会提高银河意识,还会得到旧的数据。因此,并不像处理典型的 DB2 数据库(在其中保持数据,并且 ACID 确保事务正如数据库声称的那样被提交)那么简单。使用 SimpleDB 和其他 “非常一致的” 数据库,您不得不忍受这种不确定性。
重点在于,更新照片没有这么简单。我们希望使用一个复制 URL 拒绝上传,但这并不是总是可行的。假设 Alice 上传 http://horsey.com/wilbur.png,与此同时,Bob 也上传了 http://horsey.com/wilbur.png。如果 Alice 的上传排在前面而 Bob 没有注意到它,那么 Bob 的上传将覆盖前者。那么我们该怎么做呢?
首先,您会问,这样有什么危害?用户可能会有些不方便,但是这没什么大不了。而且,同时上传的机率也很小。是的,我们希望用户满意,如果我们过分追求质量的话,也许这个问题将纠缠我们终生。
我们不会把这种痛苦带到坟墓中,相反,我们将针对照片修改表设计,如清单 2 所示:
清单 2. 修改后的表设计
share_photos:"random-string10"{ url: "http://developer.amazonwebservices.com/connect/images/amazon/logo_aws.gif", user: "ted", name: "Amazon Logo"}"random-string11"{ url: "http://images.share.lifelogs.com/funny.jpg", user: "bob", name: "Funny Picture", s3bucket: "images.share.lifelogs.com" }
您到目前为止所看到的 random-strings 将为 UUID。它不够完美,但是至少在 URL 相同的情况下照片不会出现冲突。但是等等……照片评论会怎样?很简单;我们只修改外键,如清单 3 所示:
清单 3. 修改外键
share_comments:"random-string3"{ image_id: "random-string10", comment: "No it doesn't", reply_to: "random-string2", posted_when: "2009-03-01T20:00:01+05" }
现在我们需要注意到 share_photos 中有多个条目使用相同的 URL,但是除此以外系统一切正常。
我们并不是向您展示人为修改的最终版表,而是将所有表遍历一遍。这使我们可以展示的 SimpleDB 灵活性并展示最佳状态的敏捷开发:投入、测试、优化、重复。但是,我们并没有对每一件事情 进行计划,而仅仅做好通往下一个阶段的计划:
我们任何时刻都没有忘记全局性。 在制定或修改架构决策时,不会像对待特定于任务的决策那样随意。
那么照片上传很简单,是吗?只需要使用给定的 URL、照片名和用户名向 SimpleDB 添加一个新条目。S3 bucket 是可选的。这可以通过 PutAttributes 调用完成。
修改照片也很简单,但是目前我们只修改名称。这也是通过 PutAttributes 完成的。
添加和修改评论
参考前面小节有关 share_comments 表的内容。非常简单:添加一个评论需要评论文本、照片 ID、父评论 ID(可选)和一个用户名。目前为止,修改评论意味着只能对评论文本进行修改。
独立脚本
我包含了一个独立的 Perl 脚本(simple_go.pl;可以从本文结束部分的下载小节获得)来执行前面列出的任务(添加和修改照片、添加和修改评论)。它不会创建域,因此您需要通过外部方式创建 SimpleDB domains share_photos.share.lifelogs.com 和 share_comments.share.lifelogs.com。这可以通过任何 SimpleDB 管理工具完成。注意 --domain switch 将修改 share.lifelogs.com 以获得完整的域名(存储在 $full_domain)。
脚本使用 CPAN Data::UUID 模块生成新的惟一标识符。
脚本在处理错误方面有些随意,在任何可能的情况下都会选择 die()。这种方式非常懒惰并且叫人看不起,因此您不应选择这样做,除非您在撰写文章并向人们展示写文章的程序员有多么糟糕。
最后一个任务是提交 SELECT 语句并删除一个项。我将展示如何实现它们,因为这些任务很简单,并且您稍后就将用到。
要列出照片,需要像清单 4 一样调用脚本:
清单 4. 列出照片
./simple_go.pl -l -i --ak=accesskey --sk=secretkey
注意:确保您运行脚本的机器不会被其他人使用。他们会查看进程列表并获知您的 Amazon 秘密密匙。类似地,如果位于一个保存历史的 shell 中,您的秘密密匙将出现在您的历史文件中。更好的方法是传递一个文件名并从该文件中获得密码,但是为了保持简单性,我没有实现这种方法。
要列出评论:
清单 5. 列出评论
./simple_go.pl -l -c --ak=accesskey --sk=secretkey
目前为止,一切都很简单。脚本在内部调用 $service->select() 方法,解析结果,使用 show_list() 输出数据。执行所有操作的前提就是键只有一个值(注意我们在 put() 方法中指定 Replace=true),因此这并不是一个通用 SimpleDB 脚本。
为什么不选择通用脚本?我们不需要它。现在让我们使用一个简单的解决方法。如果需要多个值,可以稍后采用脚本或编写些新代码。这个脚本是创建网站数据库结构的基础。
不要在实际站点中尝试使用这个脚本(“我将仅调用 system() 并让错误进入到日志文件中”)。是的,它包括几百行代码并且可以工作,但是必须毫不犹豫地扔掉所有原型,这样才可以编写一个真正的程序。这个脚本也不应该有例外,即使我们已经有点喜欢上它以一个空格为缩进的散漫(或者说 “有创意”)布局。
回到脚本中来。创建一个新照片非常简单(bucket 是可选的):
清单 6. 创建一个新照片
./simple_go.pl -i --ak=accesskey --sk=secretkey -u ted --url="any url" --name="any name you like" --bucket=mybucket
编辑照片名字(-l -i 提供了一个 ID 25EC17B8-0F6B-11DE-A1A1-944E07F9DEC1)。这将创建一个具有惟一 UUID 的照片:
清单 7. 创建具有惟一 UUID 的新照片
./simple_go.pl -i --ak=accesskey --sk=secretkey --name="new name" --id=25EC17B8-0F6B-11DE-A1A1-944E07F9DEC1
类似地,创建评论也很简单(user 和 refcommentid 是可选的):
清单 8. 创建一个评论
./simple_go.pl -c --ak=accesskey --sk=secretkey -u ted --refimageid="any image ID" --text="the text" --refcommentid='any comment ID'
同样,评论 ID 将是一个惟一 UUID。编辑评论的文本也执行类似的操作:(-l -c 提供了 ID 4BE2EA0A-0F6B-11DE-976B-A542FC6BD07C):
清单 9. 创建具有惟一 UUID 的评论
./simple_go.pl -c --ak=accesskey --sk=secretkey --text="the text" --id=4BE2EA0A-0F6B-11DE-976B-A542FC6BD07C
最后,像下面这样删除照片或评论:
清单 10. 删除图像和评论
./simple_go.pl --delete -i --ak=accesskey --sk=secretkey --id=25EC17B8-0F6B-11DE-A1A1-944E07F9DEC1./simple_go.pl --delete -c --ak=accesskey --sk=secretkey --id=4BE2EA0A-0F6B-11DE-976B-A542FC6BD07C
结束语
本期文章展示了如何在一个 SimpleDB 数据库中创建、编辑和删除照片和评论,SimpleDB 数据构成了我们所构建的照片共享站点的基础。
我们确立了松散的模式,并实现一个工具来添加、列举、修改和删除照片和评论。我们使用 UUID 作为照片和评论的主键,防止出现两个用户同时上传相同的照片 URL。
我们还确定将对每个键使用一个单一值,因为目前的模式不需要使用多个值,并且我们希望保持简单性。这一缺陷可能需要在以后解决,但是目前我们将继续保持下去。
在第 4 部分中,您将看到如何将所有功能集成到 mod_perl 网站中。
本文的样例脚本,simple_go.zip:
temp_10030721452849.zip