mako源码的example目录里有一个run_wsgi.py文件,是一个简单的的wsgi程序,它的主要功能就是以templates或htdocs目录作为根目录,启动一个简单的wsgi服务,该服务读入GET或POST中的参数,以及PATH_INFO作为模板文件名,然后将GET/POST的参数均放入模板context中进行模板渲染,因此,该功能可以在没有程序支持的情况下单独调试模板。
不过该程序有两个小问题:
1. 32行的 return [template.render(**d)] 最好改成return [template.render_unicode(**d).encode(‘utf-8’)],然后以utf-8格式输出。有些场景下,不转码会报错。
2. 对于非html文件,它的是读取run_wsgi.py同级目录的,这在项目中一般不合适,因此
u = re.sub(r'^\/+', '', uri)
filename = os.path.join(root, u)
这里需要看具体项目加上静态文件的目录。
在运行python run_wsgi.py之后,该程序会监听8000端口。因此,可以在浏览器中使用http://http://127.0.0.1:8000/d.html 查看d.html模板。
如果模板中有参数,就可以在链接中加入需要的参数值。如下为d.html模板:
test ${a}
则查看http://127.0.0.1:8000/d.html?a=mako,则会显示test mako
如果模板中有数组参数,如:
test ${a}
% for i, value in enumerate(names):
${i+1}. ${value}
% endfor
则可以使用链接http://127.0.0.1:8000/d.html?a=mako&names=tom&names=cat 进行查看,显示如下内容:
test mako
1. tom
2. cat
由此可以看出,通过url中参数的不同组合,可以在没有业务逻辑程序的支持下直接调试模板,方便了模板的编写和分工。
Tuesday, November 26, 2013
Sunday, November 24, 2013
mako二三事 -- Exceptions
mako模板运行前,会被编译成py文件,因此一旦有模板中有问题,python抛出的出错信息(如文件名、行号、出错代码等)都是针对该py文件的,因此mako需要将这些Exceptions信息转换成模板对应的信息。
如python抛出的exception内容为:
File "/tmp/basic.html.py", line 49, in render_body
__M_writer(unicode(no1_value))
mako转换过之后的exception内容为:
File "/dir1/basic.html", line 12, in render_body
None value=${no1_value}
mako.exceptions是mako用于处理异常的主要代码,主要函数包括:
一、text_error_template
text_error_template用于输出文本格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.text_error_template().render()
二、html_error_template
html_error_template用于输出HTML格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.html_error_template().render()
html_error_template函数带 full、css两个参数,默认都是True。
full参数表示是否生成完整的HTML页面结构,即带等信息。
css参数表示是否生成自带的CSS信息。
通过指定这两个参数,可以将异常页面更好的融合到应用系统中。
Template类、TemplateLookup类都有一个format_exceptions,置为True,则mako默认将所有exception都转出html格式,即便是用text_error_template输出。
三、RichTraceback
mako生成上述出错信息的底层信息类就是RichTraceback,可以通过RichTraceback生成用户自定义的出错信息格式。from mako.exceptions import RichTraceback
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
template = mylookup.get_template("basic.html")
print template.render()
except:
traceback = RichTraceback()
for (filename, lineno, function, line) in traceback.traceback:
print "File %s, line %s, in %s" % (filename, lineno, function)
print line, "\n"
print "%s: %s" % (str(traceback.error.__class__.__name__), traceback.error)
traceback.traceback为调用栈列表。
traceback.errorname为exception名称
traceback.lineno为模板行数
traceback.message为出错信息
如python抛出的exception内容为:
File "/tmp/basic.html.py", line 49, in render_body
__M_writer(unicode(no1_value))
mako转换过之后的exception内容为:
File "/dir1/basic.html", line 12, in render_body
None value=${no1_value}
mako.exceptions是mako用于处理异常的主要代码,主要函数包括:
一、text_error_template
text_error_template用于输出文本格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.text_error_template().render()
二、html_error_template
html_error_template用于输出HTML格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.html_error_template().render()
html_error_template函数带 full、css两个参数,默认都是True。
full参数表示是否生成完整的HTML页面结构,即带等信息。
css参数表示是否生成自带的CSS信息。
通过指定这两个参数,可以将异常页面更好的融合到应用系统中。
Template类、TemplateLookup类都有一个format_exceptions,置为True,则mako默认将所有exception都转出html格式,即便是用text_error_template输出。
三、RichTraceback
mako生成上述出错信息的底层信息类就是RichTraceback,可以通过RichTraceback生成用户自定义的出错信息格式。from mako.exceptions import RichTraceback
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
template = mylookup.get_template("basic.html")
print template.render()
except:
traceback = RichTraceback()
for (filename, lineno, function, line) in traceback.traceback:
print "File %s, line %s, in %s" % (filename, lineno, function)
print line, "\n"
print "%s: %s" % (str(traceback.error.__class__.__name__), traceback.error)
traceback.traceback为调用栈列表。
traceback.errorname为exception名称
traceback.lineno为模板行数
traceback.message为出错信息
mako二三事 -- 模板载入与encoding
mako是python中比较流行的一个模板引擎,网络上也有很多相关的介绍,因此这个系列文章主要关注mako中一些平常不太注意的内容。
一、字符串直接渲染
mako可以直接对一段字符串进行渲染,不过这没有多少实际意义,直接用用%或字符串拼接即可,基本用于测试演示用途。
from mako.template import Template
mytemplate = Template("hello, ${name}!")
print mytemplate.render(name="jack")
如果字符串中包含了中文等内容,需要使用unicode类型:
mytemplate = Template(u"你好, ${name}!")
print mytemplate.render(name="jack")
如果不希望使用unicode类型,则需要指定特定的input_encoding
mytemplate = Template("你好, ${name}!", input_encoding="utf-8")
print mytemplate.render(name="jack")
二、单模板渲染
mako也可以载入单个模板文件进行渲染,只要指定文件的绝对路径即可
from mako.template import Template
mytemplate = Template(filename='/dir1/a.html')
print mytemplate.render()
如果a.html有特定的编码格式,也需要指定input_encoding
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8")
print mytemplate.render()
Template中还可以指定模板编译之后的中间文件存储路径,可以方便模板语法问题的调试。
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8", module_directory='/tmp')
print mytemplate.render()
注意,mako会在/tmp目录下生成/dir1/a.html.py文件,也就是说会将filename中包含的文件路径在module_directory中生成一遍。
但是如果这个模板中包含了include之类的mako语法,这种载入方式就会因找不到文件而报错。
三、模板lookup
在mako最常用的还是TemplateLookup,为mako指定一个模板文件存放路径,而将模板查找工作交给mako处理。
from mako.template import Template
from mako.lookup import TemplateLookup
mylookup = TemplateLookup(directories=['/dir1','/dir2' ])
mytemplate = mylookup.get_template("a.html")
TemplateLookup中,可以指定多个路径,mako按顺序逐个查找,直到找到模板文件。使用了TemplateLookup之后,建议使用get_template函数得到模板。Template类中尽管可以指定lookup参数,但其他参数无法从lookup中取得预先设定的信息,会引起很多错误。
如有一下目录结构:
/dir1
a.html(模板中include了c.html)
c.html(模板有中文内容)
/dir2
b.html
那么
mylookup = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8")
mytemplate = Template(filename="a.html", lookup=mylookup) # 提示找不到a.html文件
mytemplate = Template(filename="/dir1/a.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 提示找不到模板c.html,lookup没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup) # 出错,提示编码出错,也就是说input_encoding没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup, input_encoding="utf-8") # 正确
print mytemplate.render() # 解析正确
mytemplate = Template(filename="/dir1/b.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="a.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="b.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("a.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("b.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render() # 解析正确
TemplateLookup也可以指定模板编译之后的中间文件存储路径。
mytemplate = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render()
其文件路径也是module_directory + get_template中指定的相对路径。
四、encoding
除了input_encoding之外,mako需要关注的output_encoding。在不指定输出编码时,render()输出unicode字符串。
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
u'\u4e2d\u6587\n'
输出utf-8
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xe4\xb8\xad\xe6\x96\x87\n'
输出gbk
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="gbk")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xd6\xd0\xce\xc4\n’
如果使用了render_unicode()函数输出,则output_encoding 参数被忽略
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render_unicode()
u'\u4e2d\u6587\n'
注:系统环境
OS: OS X 10.9
Python: 2.7.5
一、字符串直接渲染
mako可以直接对一段字符串进行渲染,不过这没有多少实际意义,直接用用%或字符串拼接即可,基本用于测试演示用途。
from mako.template import Template
mytemplate = Template("hello, ${name}!")
print mytemplate.render(name="jack")
如果字符串中包含了中文等内容,需要使用unicode类型:
mytemplate = Template(u"你好, ${name}!")
print mytemplate.render(name="jack")
如果不希望使用unicode类型,则需要指定特定的input_encoding
mytemplate = Template("你好, ${name}!", input_encoding="utf-8")
print mytemplate.render(name="jack")
二、单模板渲染
mako也可以载入单个模板文件进行渲染,只要指定文件的绝对路径即可
from mako.template import Template
mytemplate = Template(filename='/dir1/a.html')
print mytemplate.render()
如果a.html有特定的编码格式,也需要指定input_encoding
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8")
print mytemplate.render()
Template中还可以指定模板编译之后的中间文件存储路径,可以方便模板语法问题的调试。
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8", module_directory='/tmp')
print mytemplate.render()
注意,mako会在/tmp目录下生成/dir1/a.html.py文件,也就是说会将filename中包含的文件路径在module_directory中生成一遍。
但是如果这个模板中包含了include之类的mako语法,这种载入方式就会因找不到文件而报错。
三、模板lookup
在mako最常用的还是TemplateLookup,为mako指定一个模板文件存放路径,而将模板查找工作交给mako处理。
from mako.template import Template
from mako.lookup import TemplateLookup
mylookup = TemplateLookup(directories=['/dir1','/dir2' ])
mytemplate = mylookup.get_template("a.html")
TemplateLookup中,可以指定多个路径,mako按顺序逐个查找,直到找到模板文件。使用了TemplateLookup之后,建议使用get_template函数得到模板。Template类中尽管可以指定lookup参数,但其他参数无法从lookup中取得预先设定的信息,会引起很多错误。
如有一下目录结构:
/dir1
a.html(模板中include了c.html)
c.html(模板有中文内容)
/dir2
b.html
那么
mylookup = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8")
mytemplate = Template(filename="a.html", lookup=mylookup) # 提示找不到a.html文件
mytemplate = Template(filename="/dir1/a.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 提示找不到模板c.html,lookup没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup) # 出错,提示编码出错,也就是说input_encoding没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup, input_encoding="utf-8") # 正确
print mytemplate.render() # 解析正确
mytemplate = Template(filename="/dir1/b.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="a.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="b.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("a.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("b.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render() # 解析正确
TemplateLookup也可以指定模板编译之后的中间文件存储路径。
mytemplate = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render()
其文件路径也是module_directory + get_template中指定的相对路径。
四、encoding
除了input_encoding之外,mako需要关注的output_encoding。在不指定输出编码时,render()输出unicode字符串。
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
u'\u4e2d\u6587\n'
输出utf-8
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xe4\xb8\xad\xe6\x96\x87\n'
输出gbk
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="gbk")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xd6\xd0\xce\xc4\n’
如果使用了render_unicode()函数输出,则output_encoding 参数被忽略
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render_unicode()
u'\u4e2d\u6587\n'
注:系统环境
OS: OS X 10.9
Python: 2.7.5
Monday, November 18, 2013
使用Pyramid和Cornice构建多格式支持的RESTful API
在Pyramid中利用Cornice可以很方便的基于HTTP Accept header不同构建不同格式的响应内容。
1. 安装Cornice
pip install Cornice
2. 安装Cornice之后,会在系统中增加一个名为cornice的template,在学习时可以使用它创建一个简单的项目框架。
pcreate -t cornice myapp
创建好的项目相比pyramid的项目简单,只有一个__init__.py,一个views.py
3. 在views.py中定义了一个简单的api例子
hello = Service(name='hello', path='/', description="Simplest app")
@hello.get()
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World’}
在该例子中,定义了一个url为/的api,访问这个api链接,将返回一个application/json响应。
4. 在__init__.py中使用include包含了cornice包
from pyramid.config import Configurator
def main(global_config, **settings):
config = Configurator(settings=settings)
config.include("cornice")
config.scan("myapp.views")
return config.make_wsgi_app()
5. 运行该程序,查看结果
python setup.py develop
pserve --reload myapp.ini
我们通过curl访问该url:curl -D - http://0.0.0.0:6543,得到如下响应内容
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Mon, 18 Nov 2013 06:46:49 GMT
Server: waitress
{"Hello": "World"}
从Content-Type中可以看到,取得的内容为application/json格式
6. 构建一个文本格式返回结果
为了取得文本结果,我们需要将view的结果进行格式化,因此在__init__.py中加入如下renderer
class TextRenderer(object):
def __init__(self, info):
pass
def __call__(self, value, system):
request = system.get('request')
if request is not None:
response = request.response
response.content_type = 'text/plain'
name = value['Hello']
return u"Hello, {}!".format(name)
注:如果不需要进行类似上面的数据转化,可以直接使用string renderer。
然后,在main函数中加入config.add_renderer('text', TextRenderer)
7. 修改views.py 以接收不同的HTTP Header
@hello.get(accept='text/plain', renderer='text')
@hello.get(accept='application/json', renderer='json')
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World'}
上面的两个@分别表示当get中的HTTP Header不同时的处理(使用不同的renderer对数据进行不同渲染)
8. 使用不同的HTTP Header查看结果
$ curl -D - -H 'Accept: text/plain' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:41 GMT
Server: waitress
Hello, World!
$ curl -D - -H 'Accept: application/json' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:48 GMT
Server: waitress
{"Hello": "World"}
9. cornice与参数校验的整合
cornice可以很好的跟colander校验库结合使用。下面我们加入一个输入参数尝试一下。
9.1 安装colander
pip install colander
9.2 在views.py中增加一个校验规则
import colander
class NameSchema(colander.MappingSchema):
name = colander.SchemaNode(colander.String(),
validator=colander.Length(2))
该校验规则表示系统接收一个名为name的参数,且该参数为字符串类型,最小长度为2(Length参数为min,max,这里省略了max)
9.3 在views.py中增加参数处理方法
@hello.post(accept='text/plain', renderer='text', schema=NameSchema)
@hello.post(accept='application/json', renderer='json', schema=NameSchema)
def post_info(request):
"""Returns Hello in JSON."""
name = request.validated['name']
return {'Hello': name}
上面的方法为/这个url增加了post处理。
9.4 取得运行结果
curl -H 'Accept: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
结果:{"status": "error", "errors": [{"location": "body", "name": "name", "description": "name is missing"}]}
这是因为cornice默认接收的Content-Type是application/x-www-form-urlencoded,因此不能解析-d '{"name": "Alex"}' 这种参数
$ curl -H 'Accept: application/json' http://0.0.0.0:6543 -d 'name=Alex'
{"Hello": "Alex"}
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
{"Hello": "Alex"}
该例子中指定了Content-Type,因此系统能识别-d '{"name": "Alex"}'
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "A"}'
{"status": "error", "errors": [{"location": "body", "name": "name", "description": "Shorter than minimum length 2"}]}
本文是http://makina-corpus.com/blog/metier/multi-format-restful-api-with-cornice的一个摘要翻译,需要查看原文请自行前往。
1. 安装Cornice
pip install Cornice
2. 安装Cornice之后,会在系统中增加一个名为cornice的template,在学习时可以使用它创建一个简单的项目框架。
pcreate -t cornice myapp
创建好的项目相比pyramid的项目简单,只有一个__init__.py,一个views.py
3. 在views.py中定义了一个简单的api例子
hello = Service(name='hello', path='/', description="Simplest app")
@hello.get()
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World’}
在该例子中,定义了一个url为/的api,访问这个api链接,将返回一个application/json响应。
4. 在__init__.py中使用include包含了cornice包
from pyramid.config import Configurator
def main(global_config, **settings):
config = Configurator(settings=settings)
config.include("cornice")
config.scan("myapp.views")
return config.make_wsgi_app()
5. 运行该程序,查看结果
python setup.py develop
pserve --reload myapp.ini
我们通过curl访问该url:curl -D - http://0.0.0.0:6543,得到如下响应内容
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Mon, 18 Nov 2013 06:46:49 GMT
Server: waitress
{"Hello": "World"}
从Content-Type中可以看到,取得的内容为application/json格式
6. 构建一个文本格式返回结果
为了取得文本结果,我们需要将view的结果进行格式化,因此在__init__.py中加入如下renderer
class TextRenderer(object):
def __init__(self, info):
pass
def __call__(self, value, system):
request = system.get('request')
if request is not None:
response = request.response
response.content_type = 'text/plain'
name = value['Hello']
return u"Hello, {}!".format(name)
注:如果不需要进行类似上面的数据转化,可以直接使用string renderer。
然后,在main函数中加入config.add_renderer('text', TextRenderer)
7. 修改views.py 以接收不同的HTTP Header
@hello.get(accept='text/plain', renderer='text')
@hello.get(accept='application/json', renderer='json')
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World'}
上面的两个@分别表示当get中的HTTP Header不同时的处理(使用不同的renderer对数据进行不同渲染)
8. 使用不同的HTTP Header查看结果
$ curl -D - -H 'Accept: text/plain' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:41 GMT
Server: waitress
Hello, World!
$ curl -D - -H 'Accept: application/json' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:48 GMT
Server: waitress
{"Hello": "World"}
9. cornice与参数校验的整合
cornice可以很好的跟colander校验库结合使用。下面我们加入一个输入参数尝试一下。
9.1 安装colander
pip install colander
9.2 在views.py中增加一个校验规则
import colander
class NameSchema(colander.MappingSchema):
name = colander.SchemaNode(colander.String(),
validator=colander.Length(2))
该校验规则表示系统接收一个名为name的参数,且该参数为字符串类型,最小长度为2(Length参数为min,max,这里省略了max)
9.3 在views.py中增加参数处理方法
@hello.post(accept='text/plain', renderer='text', schema=NameSchema)
@hello.post(accept='application/json', renderer='json', schema=NameSchema)
def post_info(request):
"""Returns Hello in JSON."""
name = request.validated['name']
return {'Hello': name}
上面的方法为/这个url增加了post处理。
9.4 取得运行结果
curl -H 'Accept: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
结果:{"status": "error", "errors": [{"location": "body", "name": "name", "description": "name is missing"}]}
这是因为cornice默认接收的Content-Type是application/x-www-form-urlencoded,因此不能解析-d '{"name": "Alex"}' 这种参数
$ curl -H 'Accept: application/json' http://0.0.0.0:6543 -d 'name=Alex'
{"Hello": "Alex"}
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
{"Hello": "Alex"}
该例子中指定了Content-Type,因此系统能识别-d '{"name": "Alex"}'
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "A"}'
{"status": "error", "errors": [{"location": "body", "name": "name", "description": "Shorter than minimum length 2"}]}
本文是http://makina-corpus.com/blog/metier/multi-format-restful-api-with-cornice的一个摘要翻译,需要查看原文请自行前往。
Tuesday, October 15, 2013
使用PyCharm搭配Vagrant开发Pyramid程序
1. 软件安装
安装Virtual Box https://www.virtualbox.org/
安装Vagrant http://www.vagrantup.com/
确保PyCharm中的Vagrant插件已经激活。
2. 安装一个virtual box
在PyCharm Setting中,找到Vagrant项,点击左下角的+按钮,增加一个box。
下载完成后,Vagrant Boxes列表中即多一个box项。
3. 创建Vagrantfile
创建、打开Pyramid项目,在PyCharm中选择tools菜单 -> Vagrant -> Init in Project Root,即可在当前项目根目录创建一个Vagrantfile,打开该文件,加入:
config.vm.network :forwarded_port, guest: 6543, host: 6543
因Pyramid一般使用6543这个端口,如果项目端口选择不一样,可以自行配置。
如果配置了多个box,在init的时候,会弹出box选择项。
4. 配置虚拟机
运行Vagrant up启动虚拟机,登录到该虚拟机。
运行sudo apt-get install python-virtualenv 安装虚拟环境程序
运行virtualenv env创建一个虚拟环境
运行source env/bin/activate启动该虚拟环境
运行pip install pyramid==1.4.3 安装pyramid
根据项目不同,本步骤安装程序也可以不一样,这里只是演示一下在虚拟机里面也可以使用python虚拟环境。一般团队开发可以定制自己的专有box以便整个团队开发环境的一致。
5. 配置remote interpreter
在Settings中,找到Project Interpreter项
点击Configure Interpreters
选择Remote…
选择Fill from vagrant config
因上面使用了python虚拟环境,因此,上面的Python interpreter path需要变更为:
/home/vagrant/env/bin/python
到现在为止,就可以运行普通的python程序进行项目开发了。不过因为要配置Pyramid,还需要变更一个配置。
6. Pyramid运行配置变更
运行tools菜单中的Run setup.py Task,选择develop,将在虚拟机中运行python setup.py develop
打开项目的Run/Debug Configurations,变更Config file为如下类似内容
/home/vagrant/env/bin/pserve /Users/XXXX/Documents/PycharmProjects/test1/development.ini
(即在development文件前加上pserve,远程interpreter默认启动的是python,而不会像本地开发一样启动pserve)
7. 运行程序
现在在Pycharm中运行/调试程序,就可以看到其实程序是在虚拟机中运行,而非本机。
并且仍然可以在本机浏览器使用http://0.0.0.0:6543查看运行结果。
将PyCharm跟Vagrant结合之后,就可以将所有项目需要的各种包、软件都丢到虚拟机中,甚至可以每个项目配置一个box。于此同时,仍然可以保持PyCharm各种开发调试习惯不变。
Friday, September 27, 2013
AWS都收了哪些费用?
AWS 提供了一整套基础设施和应用程序服务,可以很方便的用于构建各种云端应用系统。在这些年的发展中,AWS也逐步形成了一套十分繁杂的计费系统。下面我们就来看看这笔账是怎么算的。
几个说明:
1. 这里只讨论最普通的费用,即北弗吉尼亚,linux机型。AW大多产品根据所在区域,运行系统不一样,如linux、windows等,每种的价格都会有差异;基本上了解清楚一种,其他都可以类推。
2. AWS的计费价格、计费内容会一直随着时间改变,比如推出新的产品、优惠、降价。基本上价格都在逐年递减。这里引用的价格均以成文日期的价格为准。
3. 所涉金额单位均为 $。
4. 本文仅仅是个人使用过程中的一个简单总结,因AWS本身十分庞大复杂,肯定会大量的遗留、错误和自言自语。看看就算了,千万别当真。落实到具体的问题还是根据最新数据逐个去推算比较准确。
为叙述清楚起见,按照AWS收费对象的不同,我们可以划分为如下三种收费类型:(AWS中各种服务最终的计费大多都是几种计费类型经过叠加形成的)
一、资源占用费
该收费的主要依据就是在一段时间内占用了一定的AWS系统资源。根据占用资源类型的不同,又可分为计算资源占用、存储资源占用两类。
1. 计算资源占用
这个类别里面最典型也是最复杂的就是EC2,其他像RDS、EMR、ElastiCache等基本就是在这个基础上派生出来的变种。如在价格上做一定差异或附加一个额外的收费等。另外CloudWatch(实例)、Route53(托管区域)、ELB等也会有些资源占用型收费内容,不过都比较简单直观,不用过多解释。
EC2在收费方式上,按客户使用方式上的需要,又提供了三种选择:
1) 按需实例:这个是最简单的方式,就是用多少时间算多少钱。定价按每个实例从启动到终止或停止使用的时间计算。运行未满一小时的实例,将按一小时计费。
这里要注意的是,从EBS启动的实例提供了停止的功能,停止了之后,计算资源是不收费了,但EBS这个存储资源还是需要继续收费的。非EBS启动实例没有停止功能,只能终止。
目前一个大型实例的价格为每小时 $0.240,每年的费用为$2102。这里的大型实例是指 m1.large实例,包含7.5 GiB 内存,4 个 EC2 计算单位(2 个虚拟内核,各含 2 个 EC2 计算单位),850 GB 本地实例存储,64 位平台,下同。
2) 预留实例:其实个人觉得这个叫折扣实例更合适,就是先出一部分钱,然后在一个时间段里给你一个折扣价,不过这个折扣价也是论个卖的。几个实例需要打折,就需要买几个预留实例。
预留实例可以按1年期买,也可以按3年期买。当然3年期平均算起来更划算。不过也要考虑到AWS经常会小降点价格,更主要的是要考虑自己的业务做不做得到3年 :)
在预留实例里面还可以根据使用率不同,分为低使用率预留实例、中使用率预留实例、高使用率预留实例,方便用户以有效的小时价格平衡预付款金额。这里的使用率可以看做你的服务器的开机时间。下面的表格就是一个大型实例在各种使用率下的价格计算。
根据上表内容,我们可以推算的出来如下使用规则:
* 全年开机时间97天(约2336小时)之内,按需付费比较划算(当然,这种零时性使用最划算的还是竞价实例)
* 全年开机时间大于97天,小于250天(约5980小时)之内,使用低使用率比较划算
* 大于250天,高使用率比较划算。
这里有个疑问,不知道中使用率的使用场景是什么,怎么算都不是价格最低的。可能在其他类型实例里面会便宜吧。
使用预留实例过程中经常会引起误解,需要牢记的主要有:
* 预留实例不是年付方式。而是预付一定费用,然后得到一个折扣的模式。这种付费模式可能国内不常见。比如一个大型实例,预付$243,那么在接下来的一年时间里就可以享受这种类型的一个实例每小时 $0.136的折扣,相对于按需付费便宜了些。
* 预付费是一次性支付的,不会因为实例关机而停止计费,但每小时 $0.136这个费用是关机就停止计算的。也就是说,如果你花了$243购买了一个预付实例,但接下来的一年,你一个该类实例都没开,那这个$243也就打水漂了。
* 购买了预留实例之后不是跟一个具体的实例绑定,而是根据当前有的所有实例进行动态映射的。比如你在某个AZ里购买了一个预留实例之后,随时可以在该AZ关、停、重建该实例。但如果在该实例运行过程中,又创建了一个同类实例,那新实例就会采用按需价,因为该预留实例已经被第一个实例使用了。
* 预留实例的类型必须是匹配的,购买了某个AZ的大型实例,就只适用该AZ中的大型实例,不同AZ之间是不能混用的。在2013-9-11号之前,已预留实例还不支持在一个区域不同AZ之间切换,不过现在支持了,至少提供了一定的自由度,不会因系统架构的调整造成预留实例的浪费。不过不同区域之间目前还是无法切换的。因此购买预留实例之前,需要有最基本的规划。
* 预留实例不仅仅可以用于自己创建的实例,也可用于EMR、AutoScale等系统自动创建的实例。这些实例的运行很可能不是连续的,所以就需要估算实例在线时间区间购买相应的预留实例。
* 预留实例在购买之后,价格就定了,前期费用、每小时费用都不会受AWS降价影响。
* 预留实例在某些AZ有可能买不到,这个在做系统架构前要心里有数,甚至不同账号情况都不一样。我出现过一个账号在某个AZ无法购买,但另一个账号却可以购买的情况。AWS解释是说AZ是一种逻辑上的zone,然后映射到不同的实体zone,所以账号之间可能不同。
* 有些3rd Party的价格可能比AWS提供的更优惠,不过买前最好算清楚。比如刚才看到一个27月的预留实例前期费用才630,不过每小时费用是0.052(小心算这个价差哦)。另外3rd Party购买的预留实例的费用在账单上是单独一栏的,这个看账单的时候要注意一下。
3) 竞价实例:
按Amazon的说法是投标未使用的 Amazon EC2 容量,所以竞价的价格可能会很低,比如目前大型实例的最低竞价价格为 $0.026 每小时,折算下来一个月才$18,远低于按需实例、预留实例的价格。不过这是一种竞价的方式,因此,这个价格可能会波动的很厉害。比如下图,这是当前的一个竞价历史价格图。
从图上可以看的出来,在某些时段,竞价价格可能会飙升的很厉害,比如9月17日,1c的价格就超过了$3,远远大于按需实例。不过总得来说,大多数时候,竞价价格还是很低的,而且就算某几个AZ价格高,也可以在其他AZ,其他区域找到便宜的实例。
在竞价的时候,可以设定一个自己可接受的最高价,启用、终止时间、实例信息等信息即可。不过在一次竞价之后是无法再次修改价格的,一旦AWS的价格高出竞价时设定的标价,实例就有可能被终止。因此,竞价实例是不稳定的。
当然,为了尽量不使实例被停止,也可以设置一个高出很多的价格,但这就要自己承担部分时间段的高价格。看看竞价实例历史价格就可以知道,那是个没有啥规律和理性的东西。
在使用竞价实例的时候,需要注意Persistent Request这个选项,不勾选时创建的竞价是一次性的,就是说,一旦系统价格高出你的出价,实例会终止,同时该竞价也会变成close状态。勾选之后,一旦系统高出你的出价,实例也会终止,但系统还会进一步检测之后的价格是否会回落,一旦回落到你的出价范围,将会重新启动一个实例为你服务。
因此,竞价实例的使用对应用的要求是比较高的,上面跑的应用需要做好随时被关机,重新加载的防范措施。
竞价实例除了单独启动加载之外,也非常适合在Auto Scaling、EMR等服务里面出现,通过两者的捆绑,可以用很廉价的方式解决访问高峰、批量计算等问题。
2. 存储资源占用
Amazon针对存储类别的不同,提供了一系列存储,合理的配置方案可以节省很多成本。在AWS账单中,经常会见到的存储有Glacier、S3、EBS、Snapshot等,其中在账单上Glacier被归到了S3里面,snapshot也算在了EBS里面,不过他们的价格都是不一样的。
1) Glacier
Glacier是目前AWS上最便宜的存储,不过从Glacier提取文件会有个等待时间,因此适合不怎么使用的存档数据。它的价格是$0.01/G/月左右,不论存储多少数据都是这个价格,还算便宜。
Glacier做的做好的地方是跟S3的配合,通过lifecycle的配置,基本可以跟S3无缝对接,使用者几乎感觉不到Glacier的存在。
2) S3
S3这个几乎是人尽皆知的东西了。1T之内的价格是$0.1/G/月左右,按49T、450T、500T、4000T、5000T这样有个阶梯价差(没参详明白为什么是这个阶梯划分法)。S3在不同的区域价格有微小差异,去冗余存储也会有价格差异。一般经常使用的数据存放S3,长时间不用的数据存放Glacier。
3) EBS
接下来的EBS就比较坑了。EBS跟S3,Glacier不同,EBS是根据申请块大小的计费,而后者是根据使用量计费的,这个在使用时要牢记。EBS的价格也是$0.1/G/月。在账单上,它的价格是按天折算的,所以虽然EBS用量一个月内没变化,账单上也在每天变。
仅从存储使用上来看,EBS的收费还算合理,但如果算上后面讲到的流量费、IO计次费就非常非常坑了。如果应用不优化的话,EBS的整体费用会出乎你的想象。
AWS还推出了预配置的 IOPS 卷,好吧,这应该是专门为高富帅准备的。曾经申请了100G做点测试,20来天就吃掉了$100。要清楚理解$0.10 每月预配置的 IOPS这个概念,这个是额外的收费。下面是AWS网站上对预配置的 IOPS收费的案例解释:
“例如,如果您预配置了含 1 000 个 IOPS 的卷,并在 30 天的月份中保留该卷 15 天,如在弗吉尼亚地区,您需要为预配置的 IOPS 支付 50 USD(每个预配置的 IOPS/月 0.10 USD * 预配置的 1 000 个 IOPS * 15 天/30)。 ”
4) Snapshot
按照AWS的解释,快照是通过增量压缩的方式存储到S3的。但收费上,不是跟S3列在一起的,不过两者的价格差不多。snapshot是一种很方便的系统备份方法,可以随时或用程序定时进行快照备份。
在使用存储资源的时候,需要注意:
* 分析数据类型,合理利用各不同存储,尽可能价格最优化
* 不要将实例配置的Ephemeral存储视而不见,优化程序尽可能多使用它
* 实在没办法,才去使用EBS
* 如果是备份类的数据,最好先压缩一下
资源占用型的服务还有ELB、EIP等会收点小钱,需要注意的是EIP在空闲时是要收费的,绑定到实例上才不收费。
二、流量费
资源占用型的收费还是比较合理的收费,毕竟用了人家的东西就要交钱,这很正常。流量上的收费就开始有点坑了。下面我们就来看看AWS在流量上收了哪些费用。
1. CDN(CloudFront)流量
CDN流量主要是指流出流量,每个区域价格也不尽想用,欧美10T内$0.12/G,其他地方接近$0.2/G。另外,从S3到CloudFront的流量也会收费,当然,也是小钱。不过CloudFront不算是一个很好的CDN选择。
2. 数据传输
数据传输主要是指流入、流出AWS,各个服务之间、区域间的数据流。
1) 流入流量,现在应该还是免费的。
2) 流出流量,10T之内$0.12/G。如果业务繁忙、跟其他服务器交互频繁,或需要经常备份出AWS,这个部分价格会迅速上涨。(这个不包括CDN)
3) EC2之间的流量,$0.01/G。别小看这一分钱,如果你的程序不好好控制的话,一个月几个T、十几个T是很简单的事。比如管理员为了数据安全,做了几份数据同步。
4) 区域之间的传输费用,$0.02/G。这里需要注意的是from不收费,to收费,但账单上都会列出,仅仅部分标0,所以这部分账单看看一大片,其实没啥内容。而且在这里,CloudFront也是当做一个区域来处理的,也就是说每个区域传到CloudFront的流量在这里收费。
另外像ELB流量、SES的附件流量也需要意思意思。反正基本上能想得到的流量AWS都帮你想到了,你还能想得到更多吗?
在流量使用上需要注意的是:
* 在备份的时候Snapshot是一个很好的选择,控制的好的话,可以很省存储空间,而且不会造成什么流量。
* 要保证数据安全的话,尽量用S3,S3还是比较稳定的。现在成熟的备份工具很多,而且也不会有啥流量产生。
* 优化程序,尽量减少不必要的数据传输
三、计次费
AWS还有很多需要计次的服务,比如SQS、SNS、SES等,这些都很正常,也很可以理解。但AWS还有个比较坑的计次,就是EBS类服务的IO计次费!
1. SQS、SNS、SES等计次费用
SQS、SNS这些经常会出现在AWS一些比较高级的服务中,比如SES中的Bounce mail就可以使用SNS进行通知,AutoScaling也利用了SNS。
SQS的收费是每 100 万个请求$0.50。要注意的是,它的请求数据量是有限制的。例如,1 次负载为 256 KB 的 API 调用将以 4 次请求计费。
SNS需要注意的是它有个API费用,同时有个通知费用,根据通知目标不同,收费也不同。比如现在每 100 万个 Amazon SNS API 请求是 $0.50(前100万免费),不过要是通知到mail的话,需要每 100 000 个 通知 收费$2.00,通知到SMS更贵。
SES更简单,每 1000 封$0.10。不过每天都有2000封的免费额度,不发垃圾信的话够用了。使用SES要注意的是,AWS刚开始分配的额度可能会比较低,它会慢慢给你加的。另外,还有个Max Send Rate的限定,超过发不出去的,写程序的时候要注意。
2. CDN计次费用
CloudFront除了流量要收费之外,还有个请求次数的收费。比如每10,000 HTTPS $0.01,每10,000 HTTP$0.0075。每个区域都会有不同(一般都比这高)。赶紧把那些小图片合到一个文件去吧。
3. EBS、RDS等 的 IO费用
这是一个很坑的地方,特别是把EBS当主硬盘使用的时候,天知道做了多少次IO啊。目前EBS上是每百万I/O请求 $0.10,看起来不多吧。等月底你看着数十亿,上百亿次请求的时候你就知道它的厉害了。当然,要是你用了RDS,你就会有更清醒的认识。而且你根本没法讲理,你算过你的MySQL这个月用了多少次IO么?
AWS除了提供上述应用之外,还有很多更高级的服务,不过从计费上看,基本都是在这些基础上的一些叠加、累计。有兴趣的可以自己去摸索、体会,会很有趣。理解清楚AWS的计费内容是我们用好AWS的一个前提条件,在业务发展的前期,EC2等资源占用费用会占主要比率,业务发展起来以后,后面两种计费将会迅速上涨。
几个合理利用AWS的小建议:
* 深入理解业务特性,合理选择专用主机、云计算,尽可能将动态的、变化的、瞬时的业务放在云上,而将稳定的、持续的业务放在专用主机上。
* 深入了解需要使用的每个服务的特性,局限和限制,将各种服务合理组合起来使用。
* 业务应用一定要要针对云计算的特性做优化,如果不优化,可能还不如专用主机来的划算。
* 多看看账单,分析理解其中每一项,确定优化目标。
总之,AWS不便宜,AWS网站上也明确说明云计算是“根据业务发展来扩展的较低可变成本来替代前期资本基础设施费用”,好好体会这句话。
几个说明:
1. 这里只讨论最普通的费用,即北弗吉尼亚,linux机型。AW大多产品根据所在区域,运行系统不一样,如linux、windows等,每种的价格都会有差异;基本上了解清楚一种,其他都可以类推。
2. AWS的计费价格、计费内容会一直随着时间改变,比如推出新的产品、优惠、降价。基本上价格都在逐年递减。这里引用的价格均以成文日期的价格为准。
3. 所涉金额单位均为 $。
4. 本文仅仅是个人使用过程中的一个简单总结,因AWS本身十分庞大复杂,肯定会大量的遗留、错误和自言自语。看看就算了,千万别当真。落实到具体的问题还是根据最新数据逐个去推算比较准确。
为叙述清楚起见,按照AWS收费对象的不同,我们可以划分为如下三种收费类型:(AWS中各种服务最终的计费大多都是几种计费类型经过叠加形成的)
一、资源占用费
该收费的主要依据就是在一段时间内占用了一定的AWS系统资源。根据占用资源类型的不同,又可分为计算资源占用、存储资源占用两类。
1. 计算资源占用
这个类别里面最典型也是最复杂的就是EC2,其他像RDS、EMR、ElastiCache等基本就是在这个基础上派生出来的变种。如在价格上做一定差异或附加一个额外的收费等。另外CloudWatch(实例)、Route53(托管区域)、ELB等也会有些资源占用型收费内容,不过都比较简单直观,不用过多解释。
EC2在收费方式上,按客户使用方式上的需要,又提供了三种选择:
1) 按需实例:这个是最简单的方式,就是用多少时间算多少钱。定价按每个实例从启动到终止或停止使用的时间计算。运行未满一小时的实例,将按一小时计费。
这里要注意的是,从EBS启动的实例提供了停止的功能,停止了之后,计算资源是不收费了,但EBS这个存储资源还是需要继续收费的。非EBS启动实例没有停止功能,只能终止。
目前一个大型实例的价格为每小时 $0.240,每年的费用为$2102。这里的大型实例是指 m1.large实例,包含7.5 GiB 内存,4 个 EC2 计算单位(2 个虚拟内核,各含 2 个 EC2 计算单位),850 GB 本地实例存储,64 位平台,下同。
2) 预留实例:其实个人觉得这个叫折扣实例更合适,就是先出一部分钱,然后在一个时间段里给你一个折扣价,不过这个折扣价也是论个卖的。几个实例需要打折,就需要买几个预留实例。
预留实例可以按1年期买,也可以按3年期买。当然3年期平均算起来更划算。不过也要考虑到AWS经常会小降点价格,更主要的是要考虑自己的业务做不做得到3年 :)
在预留实例里面还可以根据使用率不同,分为低使用率预留实例、中使用率预留实例、高使用率预留实例,方便用户以有效的小时价格平衡预付款金额。这里的使用率可以看做你的服务器的开机时间。下面的表格就是一个大型实例在各种使用率下的价格计算。
根据上表内容,我们可以推算的出来如下使用规则:
* 全年开机时间97天(约2336小时)之内,按需付费比较划算(当然,这种零时性使用最划算的还是竞价实例)
* 全年开机时间大于97天,小于250天(约5980小时)之内,使用低使用率比较划算
* 大于250天,高使用率比较划算。
这里有个疑问,不知道中使用率的使用场景是什么,怎么算都不是价格最低的。可能在其他类型实例里面会便宜吧。
使用预留实例过程中经常会引起误解,需要牢记的主要有:
* 预留实例不是年付方式。而是预付一定费用,然后得到一个折扣的模式。这种付费模式可能国内不常见。比如一个大型实例,预付$243,那么在接下来的一年时间里就可以享受这种类型的一个实例每小时 $0.136的折扣,相对于按需付费便宜了些。
* 预付费是一次性支付的,不会因为实例关机而停止计费,但每小时 $0.136这个费用是关机就停止计算的。也就是说,如果你花了$243购买了一个预付实例,但接下来的一年,你一个该类实例都没开,那这个$243也就打水漂了。
* 购买了预留实例之后不是跟一个具体的实例绑定,而是根据当前有的所有实例进行动态映射的。比如你在某个AZ里购买了一个预留实例之后,随时可以在该AZ关、停、重建该实例。但如果在该实例运行过程中,又创建了一个同类实例,那新实例就会采用按需价,因为该预留实例已经被第一个实例使用了。
* 预留实例的类型必须是匹配的,购买了某个AZ的大型实例,就只适用该AZ中的大型实例,不同AZ之间是不能混用的。在2013-9-11号之前,已预留实例还不支持在一个区域不同AZ之间切换,不过现在支持了,至少提供了一定的自由度,不会因系统架构的调整造成预留实例的浪费。不过不同区域之间目前还是无法切换的。因此购买预留实例之前,需要有最基本的规划。
* 预留实例不仅仅可以用于自己创建的实例,也可用于EMR、AutoScale等系统自动创建的实例。这些实例的运行很可能不是连续的,所以就需要估算实例在线时间区间购买相应的预留实例。
* 预留实例在购买之后,价格就定了,前期费用、每小时费用都不会受AWS降价影响。
* 预留实例在某些AZ有可能买不到,这个在做系统架构前要心里有数,甚至不同账号情况都不一样。我出现过一个账号在某个AZ无法购买,但另一个账号却可以购买的情况。AWS解释是说AZ是一种逻辑上的zone,然后映射到不同的实体zone,所以账号之间可能不同。
* 有些3rd Party的价格可能比AWS提供的更优惠,不过买前最好算清楚。比如刚才看到一个27月的预留实例前期费用才630,不过每小时费用是0.052(小心算这个价差哦)。另外3rd Party购买的预留实例的费用在账单上是单独一栏的,这个看账单的时候要注意一下。
3) 竞价实例:
按Amazon的说法是投标未使用的 Amazon EC2 容量,所以竞价的价格可能会很低,比如目前大型实例的最低竞价价格为 $0.026 每小时,折算下来一个月才$18,远低于按需实例、预留实例的价格。不过这是一种竞价的方式,因此,这个价格可能会波动的很厉害。比如下图,这是当前的一个竞价历史价格图。
从图上可以看的出来,在某些时段,竞价价格可能会飙升的很厉害,比如9月17日,1c的价格就超过了$3,远远大于按需实例。不过总得来说,大多数时候,竞价价格还是很低的,而且就算某几个AZ价格高,也可以在其他AZ,其他区域找到便宜的实例。
在竞价的时候,可以设定一个自己可接受的最高价,启用、终止时间、实例信息等信息即可。不过在一次竞价之后是无法再次修改价格的,一旦AWS的价格高出竞价时设定的标价,实例就有可能被终止。因此,竞价实例是不稳定的。
当然,为了尽量不使实例被停止,也可以设置一个高出很多的价格,但这就要自己承担部分时间段的高价格。看看竞价实例历史价格就可以知道,那是个没有啥规律和理性的东西。
在使用竞价实例的时候,需要注意Persistent Request这个选项,不勾选时创建的竞价是一次性的,就是说,一旦系统价格高出你的出价,实例会终止,同时该竞价也会变成close状态。勾选之后,一旦系统高出你的出价,实例也会终止,但系统还会进一步检测之后的价格是否会回落,一旦回落到你的出价范围,将会重新启动一个实例为你服务。
因此,竞价实例的使用对应用的要求是比较高的,上面跑的应用需要做好随时被关机,重新加载的防范措施。
竞价实例除了单独启动加载之外,也非常适合在Auto Scaling、EMR等服务里面出现,通过两者的捆绑,可以用很廉价的方式解决访问高峰、批量计算等问题。
2. 存储资源占用
Amazon针对存储类别的不同,提供了一系列存储,合理的配置方案可以节省很多成本。在AWS账单中,经常会见到的存储有Glacier、S3、EBS、Snapshot等,其中在账单上Glacier被归到了S3里面,snapshot也算在了EBS里面,不过他们的价格都是不一样的。
1) Glacier
Glacier是目前AWS上最便宜的存储,不过从Glacier提取文件会有个等待时间,因此适合不怎么使用的存档数据。它的价格是$0.01/G/月左右,不论存储多少数据都是这个价格,还算便宜。
Glacier做的做好的地方是跟S3的配合,通过lifecycle的配置,基本可以跟S3无缝对接,使用者几乎感觉不到Glacier的存在。
2) S3
S3这个几乎是人尽皆知的东西了。1T之内的价格是$0.1/G/月左右,按49T、450T、500T、4000T、5000T这样有个阶梯价差(没参详明白为什么是这个阶梯划分法)。S3在不同的区域价格有微小差异,去冗余存储也会有价格差异。一般经常使用的数据存放S3,长时间不用的数据存放Glacier。
3) EBS
接下来的EBS就比较坑了。EBS跟S3,Glacier不同,EBS是根据申请块大小的计费,而后者是根据使用量计费的,这个在使用时要牢记。EBS的价格也是$0.1/G/月。在账单上,它的价格是按天折算的,所以虽然EBS用量一个月内没变化,账单上也在每天变。
仅从存储使用上来看,EBS的收费还算合理,但如果算上后面讲到的流量费、IO计次费就非常非常坑了。如果应用不优化的话,EBS的整体费用会出乎你的想象。
AWS还推出了预配置的 IOPS 卷,好吧,这应该是专门为高富帅准备的。曾经申请了100G做点测试,20来天就吃掉了$100。要清楚理解$0.10 每月预配置的 IOPS这个概念,这个是额外的收费。下面是AWS网站上对预配置的 IOPS收费的案例解释:
“例如,如果您预配置了含 1 000 个 IOPS 的卷,并在 30 天的月份中保留该卷 15 天,如在弗吉尼亚地区,您需要为预配置的 IOPS 支付 50 USD(每个预配置的 IOPS/月 0.10 USD * 预配置的 1 000 个 IOPS * 15 天/30)。 ”
4) Snapshot
按照AWS的解释,快照是通过增量压缩的方式存储到S3的。但收费上,不是跟S3列在一起的,不过两者的价格差不多。snapshot是一种很方便的系统备份方法,可以随时或用程序定时进行快照备份。
在使用存储资源的时候,需要注意:
* 分析数据类型,合理利用各不同存储,尽可能价格最优化
* 不要将实例配置的Ephemeral存储视而不见,优化程序尽可能多使用它
* 实在没办法,才去使用EBS
* 如果是备份类的数据,最好先压缩一下
资源占用型的服务还有ELB、EIP等会收点小钱,需要注意的是EIP在空闲时是要收费的,绑定到实例上才不收费。
二、流量费
资源占用型的收费还是比较合理的收费,毕竟用了人家的东西就要交钱,这很正常。流量上的收费就开始有点坑了。下面我们就来看看AWS在流量上收了哪些费用。
1. CDN(CloudFront)流量
CDN流量主要是指流出流量,每个区域价格也不尽想用,欧美10T内$0.12/G,其他地方接近$0.2/G。另外,从S3到CloudFront的流量也会收费,当然,也是小钱。不过CloudFront不算是一个很好的CDN选择。
2. 数据传输
数据传输主要是指流入、流出AWS,各个服务之间、区域间的数据流。
1) 流入流量,现在应该还是免费的。
2) 流出流量,10T之内$0.12/G。如果业务繁忙、跟其他服务器交互频繁,或需要经常备份出AWS,这个部分价格会迅速上涨。(这个不包括CDN)
3) EC2之间的流量,$0.01/G。别小看这一分钱,如果你的程序不好好控制的话,一个月几个T、十几个T是很简单的事。比如管理员为了数据安全,做了几份数据同步。
4) 区域之间的传输费用,$0.02/G。这里需要注意的是from不收费,to收费,但账单上都会列出,仅仅部分标0,所以这部分账单看看一大片,其实没啥内容。而且在这里,CloudFront也是当做一个区域来处理的,也就是说每个区域传到CloudFront的流量在这里收费。
另外像ELB流量、SES的附件流量也需要意思意思。反正基本上能想得到的流量AWS都帮你想到了,你还能想得到更多吗?
在流量使用上需要注意的是:
* 在备份的时候Snapshot是一个很好的选择,控制的好的话,可以很省存储空间,而且不会造成什么流量。
* 要保证数据安全的话,尽量用S3,S3还是比较稳定的。现在成熟的备份工具很多,而且也不会有啥流量产生。
* 优化程序,尽量减少不必要的数据传输
三、计次费
AWS还有很多需要计次的服务,比如SQS、SNS、SES等,这些都很正常,也很可以理解。但AWS还有个比较坑的计次,就是EBS类服务的IO计次费!
1. SQS、SNS、SES等计次费用
SQS、SNS这些经常会出现在AWS一些比较高级的服务中,比如SES中的Bounce mail就可以使用SNS进行通知,AutoScaling也利用了SNS。
SQS的收费是每 100 万个请求$0.50。要注意的是,它的请求数据量是有限制的。例如,1 次负载为 256 KB 的 API 调用将以 4 次请求计费。
SNS需要注意的是它有个API费用,同时有个通知费用,根据通知目标不同,收费也不同。比如现在每 100 万个 Amazon SNS API 请求是 $0.50(前100万免费),不过要是通知到mail的话,需要每 100 000 个 通知 收费$2.00,通知到SMS更贵。
SES更简单,每 1000 封$0.10。不过每天都有2000封的免费额度,不发垃圾信的话够用了。使用SES要注意的是,AWS刚开始分配的额度可能会比较低,它会慢慢给你加的。另外,还有个Max Send Rate的限定,超过发不出去的,写程序的时候要注意。
2. CDN计次费用
CloudFront除了流量要收费之外,还有个请求次数的收费。比如每10,000 HTTPS $0.01,每10,000 HTTP$0.0075。每个区域都会有不同(一般都比这高)。赶紧把那些小图片合到一个文件去吧。
3. EBS、RDS等 的 IO费用
这是一个很坑的地方,特别是把EBS当主硬盘使用的时候,天知道做了多少次IO啊。目前EBS上是每百万I/O请求 $0.10,看起来不多吧。等月底你看着数十亿,上百亿次请求的时候你就知道它的厉害了。当然,要是你用了RDS,你就会有更清醒的认识。而且你根本没法讲理,你算过你的MySQL这个月用了多少次IO么?
AWS除了提供上述应用之外,还有很多更高级的服务,不过从计费上看,基本都是在这些基础上的一些叠加、累计。有兴趣的可以自己去摸索、体会,会很有趣。理解清楚AWS的计费内容是我们用好AWS的一个前提条件,在业务发展的前期,EC2等资源占用费用会占主要比率,业务发展起来以后,后面两种计费将会迅速上涨。
几个合理利用AWS的小建议:
* 深入理解业务特性,合理选择专用主机、云计算,尽可能将动态的、变化的、瞬时的业务放在云上,而将稳定的、持续的业务放在专用主机上。
* 深入了解需要使用的每个服务的特性,局限和限制,将各种服务合理组合起来使用。
* 业务应用一定要要针对云计算的特性做优化,如果不优化,可能还不如专用主机来的划算。
* 多看看账单,分析理解其中每一项,确定优化目标。
总之,AWS不便宜,AWS网站上也明确说明云计算是“根据业务发展来扩展的较低可变成本来替代前期资本基础设施费用”,好好体会这句话。
Thursday, September 26, 2013
Python开发中全局变量问题备忘
在项目中遇到的一个全局变量问题,简单记录下做个备忘。
场景:
根目录下有一个文件a.py,目录test;目录test中有__init__.py,b.py,c.py两个环境。其中a.py是主程序。
环境:
Python 2.7.1
程序:
b.py
----------------------------------------------------------------
num = 1
c.py
----------------------------------------------------------------
from b import num
def printnum1():
print 'printnum1: num = ', num
def printnum2():
from b import num
print 'printnum2: num(re-import) = ', num
a.py
----------------------------------------------------------------
import test
from test.b import num
from test.c import printnum1, printnum2
print 'num = ', num
num = 3
print 'num = ', num
print 'test.b.num = ', test.b.num
printnum1()
printnum2()
test.b.num = 4
print 'num = ', num
print 'test.b.num = ', test.b.num
printnum1()
printnum2()
输出结果:
----------------------------------------------------------------
num = 1
num = 3
test.b.num = 1
printnum1: num = 1
printnum2: num(re-import) = 1
num = 3
test.b.num = 4
printnum1: num = 1
printnum2: num(re-import) = 4
Subscribe to:
Posts (Atom)






