每一个可以努力的日子,都是一份厚礼。
Amazon S3 云存储服务Cloud Storage编程实践
Amazon Simple Storage Service (S3) 是一个云端存储平台,这是现在蓬勃发展的云计算的典型应用之一。用户可以将自己的数据上传到云端服务器,便可以随时随地地访问到这些数据,灵活高效。它按需收费,也就是说使用相应容量的存储空间,就花相应的钱。这里有具体的资费标准。对于企业用户来说,使用这项服务实际上可以大大降低成本,这些成本不仅仅包括自己购置服务器硬件、软件成本,还包括电力、为IT设施维护而雇佣的人力成本等等。
在Amazon S3中有如下几个概念,通过分别介绍,我们可以大致理解云存储的基本原理。
Buckets:一个bucket是一个用于存储的容器,我们可以不太恰当地理解为就是云端的文件夹。文件夹要求一个独特唯一的名字,这和注册邮箱名差不多,可以加前缀或者后缀来避免重名。bucket使得我们在一个高层级上组织命名空间,并在数据的访问控制上扮演重要角色。下面举个例子,假设一个名为photos/puppy.jpg的文件对象存储在名为johnsmith的bucket里,那么我们就可以通过这样一个url访问到这个对象:http://johnsmith.s3.amazonaws.com/photos/puppy.jpg
Objects:对象,也就是存储在S3里的基本实体。一个object包括object data和metadata。metadata是一系列的name-value对,用来描述这个object。默认情况下包括文件类型、最后修改时间等等,当然用户也可以自定义一些metadata。
Keys:即bucket中每一个object的独一无二的标识符。上面例子中提到的photos/puppy.jpg就是一个key。
Access Control Lists:访问控制表ACL。在S3中每一个bucket和object都有一个ACL,并且bucket和object的ACL是互相独立的。当用户发起一个访问请求,S3会检查ACL来核实请求发送者是否有权限访问这个bucket或object。
Regions:我们可以指定bucket的具体物理存储区域(Region)。选择适当的区域可以优化延迟、降低成本。Amazon在世界各地建立了数据中心,目前S3支持下列区域:US Standard,US (Northern California),EU (Ireland),APAC (Singapore)。
云端为了提高数据可靠性,常用手段是在多个不同的服务器建立同一份数据的冗余备份(replica)。这样即使某一个服务器挂了,用户仍然能够从别的服务器取得他的数据。使用多份数据副本将带来数据一致性的问题,如何保证每一份副本的内容是一致的?如何保证多个用户可以并发读写?这在分布式系统设计中是一个经典的问题,我将另写文章讨论。Amazon的US Standard Region为所有的requests提供了最终一致性(eventual consistency),EU 和 Northern California Regions 提供了写后读一致性(read-after-write consistency)。
回到应用层面上来。希望开通试用S3云存储服务的同学,可以去看看这篇帖子,有详细步骤和截图。虽然Amazon给用户提供了十分友好的Web界面控制台来管理云端数据和应用,作为开发人员,我们也可以使用boto提供的API建立与Amazon云计算存储平台S3交互。boto是一个Amazon云计算服务的python接口,当然也有其他语言比如C++的接口libAWS,Java接口,Ruby接口,PHP接口,等等。这些API不仅仅用于S3,也可以用于EC2等其他云计算服务的调用。下面是一个示例程序,拥有连接Amazon S3上传下载文件等基本功能。
#!/usr/bin/python # # Amazon S3 Interface # Author: Zeng, Xi # SID: 1010105140 # Email: [email protected] connected = 0 def connect(): access_key = raw_input('Your access key:').strip() secret_key = raw_input('Your secret key:').strip() from boto.s3.connection import S3Connection global conn conn = S3Connection(access_key, secret_key) global connected connected = 1 def creat(): if connected == 0: print 'Not connected!' elif connected == 1: bucket_name = raw_input('Bucket name:').strip() bucket = conn.create_bucket(bucket_name) def put(): if connected == 0: print 'Not connected!' elif connected == 1: local_file = raw_input('Local filename:').strip() bucket = raw_input('Target bucket name:').strip() from boto.s3.key import Key b = conn.get_bucket(bucket) k = Key(b) k.key = local_file k.set_contents_from_filename(local_file) def ls(): if connected == 0: print 'Not connected!' elif connected == 1: rs = conn.get_all_buckets() for b in rs: print b.name def lsfile(): if connected == 0: print 'Not connected!' elif connected == 1: bucket = raw_input('Bucket name:').strip() from boto.s3.key import Key b = conn.get_bucket(bucket) file_list = b.list() for l in file_list: print l.name def info(): if connected == 0: print 'Not connected!' elif connected == 1: bucket = raw_input('Bucket name:').strip() filename = raw_input('Filename:').strip() from boto.s3.bucketlistresultset import BucketListResultSet b = conn.get_bucket(bucket) brs = BucketListResultSet(bucket=b) for f in brs: key = b.lookup(f.name) print 'File: ' + f.name print 'size: ' + str(key.size) print 'last modified: ' + str(key.last_modified) print 'etag (md5): ' + str(key.etag) def permission(): if connected == 0: print 'Not connected!' elif connected == 1: while True: bucket = raw_input('Bucket name:').strip() permission = raw_input('Permission (private or public-read):').strip() if permission not in ['private', 'public-read']: print 'Input error!' elif permission in ['private', 'public-read']: break b = conn.get_bucket(bucket) b.set_acl(permission) def get(): if connected == 0: print 'Not connected!' elif connected == 1: bucket = raw_input('Source bucket name:').strip() s_file = raw_input('Source filename:').strip() d_file = raw_input('Local directory path and filename:').strip() from boto.s3.key import Key b = conn.get_bucket(bucket) key = b.lookup(s_file) key.get_contents_to_filename(d_file) def delete(): if connected == 0: print 'Not connected!' elif connected == 1: bucket = raw_input('Bucket name:').strip() conn.delete_bucket(bucket) def delfile(): if connected == 0: print 'Not connected!' elif connected == 1: bucket = raw_input('Bucket name:').strip() filename = raw_input('Filename:').strip() b = conn.get_bucket(bucket) b.delete_key(filename) def showMenu(): title = ''' Amazon S3 Service connect Get user credential and connect to Amazon S3 creat Creat bucket put Upload file to S3 ls List buckets lsfile List files in a bucket info Display information of a file permission Set bucket permissions get Download file from S3 delete Delete bucket delfile Delete file quit Quit Enter choice:''' while True: choice = raw_input(title).strip().lower() choices = ['connect','creat','put','ls','lsfile','info','permission','get','delete','delfile','quit'] if choice not in choices: print('Input Error!') else: if choice == 'quit': break elif choice == 'connect': connect() elif choice == 'creat': creat() elif choice == 'put': put() elif choice == 'ls': ls() elif choice == 'lsfile': lsfile() elif choice == 'info': info() elif choice == 'permission': permission() elif choice == 'get': get() elif choice == 'delete': delete() elif choice == 'delfile': delfile() if __name__ == '__main__': showMenu() |
对于个人用户来说,文件同步是一个很实用的功能。如果我们的电脑被窃或硬盘损坏,我们仍可以通过同步文件夹从云端获取以前的文件。云存储也带来了移动便利,在一些紧急场合,我们甚至可以使用手机来编辑文档。事实上已经有很多这方面的应用,国外的同步工具Dropbox十分流行,它其实就是以Amazon S3为存储后台的。国内115网盘之类应用也是层出不穷,金山发布了快盘、T盘,迅雷又宣布发布P盘……
下面的python代码就是使用boto API写的一个同步文件夹的示例程序。程序通过检查文件名、大小、MD5来判断云端的文件和本地文件夹中的是否相同。如果不同,则下载到本地文件夹。
#!/usr/bin/python # # Synchronize files between local machine and the cloud storage. # Author: Zeng, Xi # SID: 1010105140 # Email: [email protected] connected = 0 downloaded_files = "" total_size = 0 def connect(): access_key = raw_input('Your access key:').strip() secret_key = raw_input('Your secret key:').strip() from boto.s3.connection import S3Connection global conn conn = S3Connection(access_key, secret_key) global connected connected = 1 def sync(): if connected == 0: print 'Not connected!\n' connect() if connected == 1: bucket = raw_input('Bucket name:').strip() local_path = raw_input('Local directory path:').strip() from boto.s3.key import Key from hashlib import md5 b = conn.get_bucket(bucket) file_list = b.list() for l in file_list: try: F = open(local_path + l.name,"rb") except IOError, e: get(bucket, l.name, local_path, l.size) else: s = md5(F.read()).hexdigest() if "\""+str(s)+"\"" == str(l.etag): import os local_size = os.path.getsize(local_path + l.name) if int(local_size) == int(l.size): continue else: get(bucket, l.name, local_path, l.size) else: get(bucket, l.name, local_path, l.size) global downloaded_files global total_size print "Downloaded files:\n" print downloaded_files print "Total size:" print total_size def get(bucket, filename, local_path, size): global downloaded_files global total_size downloaded_files += filename + "\n" total_size += size from boto.s3.key import Key b = conn.get_bucket(bucket) key = b.lookup(filename) key.get_contents_to_filename(local_path + filename) if __name__ == '__main__': sync() |
下载以上程序源代码:S3接口、同步工具。
运行前请确认你已经安装了python和boto
Amazon的云计算不仅仅是S3数据存储,还包括EC2虚拟机,SimpleDB数据库等等很多服务。如果你有兴趣,可以查看下面的相关文章。
关于作者:我目前是一名在读研究生,如果你觉得我的文章对你有用,或我了解的知识对贵公司项目开发有帮助,或许你会有兴趣与我联系。
这篇文章由lovelucy于2011-01-04 18:48发表在云计算。你可以订阅RSS 2.0 也可以发表评论或引用到你的网站。除特殊说明外文章均为本人原创,并遵从署名-非商业性使用-相同方式共享创作协议,转载或使用请注明作者和来源,尊重知识分享。 |
批评不自由
则赞美无意义
Google Chrome 25.0.1364.97 Windows XP 大约11年前
博主,小弟一直不理解pool与bucket到底什么关系
Google Chrome 28.0.1500.68 Linux 大约11年前
我没研究过 ceph,刚看了下官方文档,也是会有 replicas,好像和 S3 里面的 bucket 类似。
Google Chrome 25.0.1364.97 Windows XP 大约11年前
博主,请问pool如何通过url访问?
Google Chrome 28.0.1500.63 Linux 大约11年前
你说的 pool 是 Amazon S3 服务中的概念么?不是很明白
Google Chrome 25.0.1364.97 Windows XP 大约11年前
奥,pool是ceph中的概念,因为ceph有S3的兼容接口,看到你这有讲S3的概念,就直接问了
Google Chrome 28.0.1500.68 Linux 大约11年前
哦,好像 ceph 会集成到 openstack 里。