每一个可以努力的日子,都是一份厚礼。
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 也可以发表评论或引用到你的网站。除特殊说明外文章均为本人原创,并遵从署名-非商业性使用-相同方式共享创作协议,转载或使用请注明作者和来源,尊重知识分享。 |
批评不自由
则赞美无意义
博主,小弟一直不理解pool与bucket到底什么关系
我没研究过 ceph,刚看了下官方文档,也是会有 replicas,好像和 S3 里面的 bucket 类似。
博主,请问pool如何通过url访问?
你说的 pool 是 Amazon S3 服务中的概念么?不是很明白
奥,pool是ceph中的概念,因为ceph有S3的兼容接口,看到你这有讲S3的概念,就直接问了
哦,好像 ceph 会集成到 openstack 里。