Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save youqingkui/dec78f51330b4bc120c3a5ab635f2dd2 to your computer and use it in GitHub Desktop.
Save youqingkui/dec78f51330b4bc120c3a5ab635f2dd2 to your computer and use it in GitHub Desktop.
AWS Lambda初触
2017年3月16日 by Ron
我是来自AWS 广东社区微信2群的Ron.第一次去参加AWS分享会的时候,第一个上来的就是关于Lambda的.但当时我听完后.
觉得这东西耦合度太高,不好.就一直没在去深入了解.那一次技术分享会,还有卓动--张穗文先生分享的Kinesis + RedShift做
用户日志分析,和有米科技的实时日志分析,那个时候真的感叹跟AWS相见恨晚.他们两分享的东西都非常棒,直接驱使我来后自己
去尝试开发Kinesis,SQS等.总体来说,AWS组件很多.但我毕竟刚接触没多久,所以很多AWS的组件和相关概念都还很陌生,
或者说根本就不知道是什么东西.以下在阐释的时候,如有偏差或者错误,欢迎拍砖(我Email: luodm03@qq.com)
在这过去的半年多时间里.所有广州社区的分享会,我都去了.最近一次正好听到胖虎的Lambda的分享会.
回来的路上我已经满脑子的开始想我最近的在做的一个东西,是否能用Lambda来实现.
我要做的东西其实很简单,把游戏的存档云同步到S3,当用户需要的时候,又同步回去,或者同步到其他电子设备.
回来的当天晚上开始啃官方文档,但是由于心急想实现一个Demo出来,文档没啃的太仔细.导致后面走了很多弯路.
1, Lambda的第一个坑
当你在创建一个Lambda的时候,你指定了安全性是打开的(即不用验证)
图1: http://imgur.com/sLaJF8B (由于不知道怎么插入图片,估计各位得将就了)
但你设置好Lambda和API Gateway(下面简称APIG)后.在aws的控制测试是完全没问题的.顺利的看到返回结果.
但是当我自己写个脚本去访问这个地址的时候.总发现这样的结果{"message":"Missing Authentication Token"}
于是乎,开始查阅Lambda和APIG的各种文档,都没找到,我就觉得很奇怪了.
开始折腾sigV4,整整折腾了一个下午,脚本看到服务器成功返回结果.
但我想,如果要游戏客户端去实现sigV4,还是比较麻烦的,就去问胖虎.他说默认是不需要验证的.
于是.我又重新回来摸索.这个时候,看文档才发现文档推荐安装Postman来调试(强烈推荐安装)
折腾来,折腾去,最后我在APIG哪里,把对应的Resource删掉,重新建立,并且关联Lambda(千万要记得点Deploy API)
Postman就能访问成功了.这里,说多了都是泪啊.....
sigV4相关参考文档请点击:
http://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-signed-request-examples.html
2, 我对于收到Request后Container是如何执行Lambda的一些理解.
在我对APIG + Lambda的架构中,我发现他其实跟我原来的Nginx + uWsgi + python是非常类似的.
首先一个HTTP Request Post到Nginx,然后Nginx根据你这个请求的url,把这个请求路由到对应的uWsgi去
然后uwsgi在把这个Request抛给指定的处理逻辑类.
但Lambda会有一些限制(文档里面都有说到). 例如内存大小,/tmp目录,环境变量等.
/tmp目录我目前的理解是每个Container都有独自的.他的大小应该是512M.
当你Request_1在/tmp目录写了个临时文件. 接着你想在Request_2中把他读取回来,
经过我测试,这样的不行.所以大家千万不要依赖于/tmp目录来做依赖性逻辑
另外,Request命中那个Container,你是无法得知的.这点要注意(我不确定一定是这样)
3, 关于reuse connection的问题
lambda是无状态,所以很多场景中,需要保存或者读取一些相关的信息.
例如,当你的去校验登录session的时候你可能需要连接DB.
但其实,如果每次都需要去建立连接,这样的效率是非常低下的(每100ms都是钱啊)
于是乎引入了一个能否重用连接的问题了
经过我查证,我在aws的社区中看到一段代码,然后根据示例代码,写成我python的,并做了相关测试.大家看代码以下代码
import json
import httplib
host = ""
conn = httplib.HTTPConnection(host, 8080) # 把这个连接的对象放在全局
print "conn ==> ", id(conn)
def upload_handler(event, context):
"""
:paramevent:
:paramcontext:
:return:
"""
print "connid ==> ", id(conn) # 多次执行Request,你会发现这个id是不变的.然后我也用netstat 在对端看到,他确实只有1条连接
conn.request("POST", "/listen")
response = conn.getresponse()
data = response.read()
return json.dumps({"errMsg": "upload success", "sha1": "00000"})
在这个示例代码中,有全局的conn,它即可以下次命中这个Container的时候,会重新使用
全局部分的代码,Container不会每次都执行,只是在第一次建立这个Container的时候才会执行
那么问题来了,如果当这个连接异常中断,或者对端因为长时间不用而关闭.
如果发生异常,Container会销毁吗?下次是否还会有Request在这个Container执行?
于是我重启对端http服务器(让连接中断)
发现他应该还是再次命中这个Container的时候(因为那个id(conn)值没有变),他是会抛出异常的.
那怎么解决这样的问题的.我是这样做的
import json
import httplib
import traceback
host = ""
conn = httplib.HTTPConnection(host, 8080)
print "conn ==> ", id(conn)
def upload_handler(event, context):
"""
:paramevent:
:paramcontext:
:return:
"""
print "connid ==> ", id(conn)
try:
conn.request("POST", "/listen")
response = conn.getresponse()
data = response.read()
except:
global conn
conn = httplib.HTTPConnection(host, 8080) # 重新给全局对象conn赋值.
print "connid ==> ", id(conn) # 你会发生这个ID变了
# 然后再次执行请求,或者放入DLQ,或者返回错误.都可以(就看业务逻辑怎么做了)
return json.dumps({"errMsg": "upload success", "sha1": "00000"})
在这个示例代码里面,不完全正确
因为我try catch了所有异常,不管是否网络异常,还是别的异常,他都会重新建立这个HTTP连接
如果要精确,则应该捕捉网络异常,然后才重新建立HTTP连接(其他异常根据业务逻辑自行处理)
从上述的实践中,我的总结是这样.
如果你的Container存在全局网络连接(不管你是http, 还是DB连接),在使用的时候出了异常,
是需要做处理的,如果不处理.下次执行其他Request是会有问题的
的的
这里需要说明一下.如果重用连接,性能会有很大的提升.在我的示例中,数据较小,
如果重用连接 Billed Duration: 100 ms.
如果不重用连接 Billed Duration: 300 ms.
4, 关于如何自定义域名
我在这里也走了很多弯路,最初是在我本Region(新加坡)申请的ACM.
但我到了这个界面,我就发现只能是美东1区能用,我就哭了.后来我才知道.是去美东1区申请这个证书
然后其他Region都可以使用
图2: http://imgur.com/KnOFgoq
然后界面的上方有一段这样的提示(开始看到是英文,就没理它, 哭)
To use your own domain name for an API and Stage, create a Custom Domain Name backed by an ACM certificate. Add Base Path Mappings to map a URL to an API and Stage. You can leave the path empty to use the root (no additional mappings will be allowed) and you can leave the Stage empty to specify it in the URL (e.g. www.example.com/my-base-path/MyStage). To use an ACM certificate with API Gateway, you must request or import the certificate in the US East (N. Virginia) region.
申请的时候,可以这么做会方便点.假设你拥有的域名叫xxx.com
你去申请的时候,最好是申请*.xxx.com(注意那个*.). 申请完后,其他区域就能很方便使用了
接着我重新回来新加坡Region区域设置.
图3: http://imgur.com/4l4Gt2R
这个时候我点了Save.需要等40分钟后,会在Cloudfront生效
Save之后的是这样的
图4: http://imgur.com/hbqpwXR
你会得到一个Distribution Domain Name. 例如是12dsdedvgtlkj.cloudfront.net
这个即是你做CNAME用的
你这个时候去在AWS的R53,或者在自己的DNS解析哪里设置
api.aws.xxx.com ==> 映射 ==> 12dsdedvgtlkj.cloudfront.net
等40分钟,你可以在这里测试你的CNAME是否生效
https://www.ssllabs.com/
或者用nslookup + curl测试
图5: http://imgur.com/lKgI4gt
在这个场景中,我最初做完以上几步,发现是跟Cloudfront连接是正常了
但是我在Postman里面测试我的APIG + lambda.发现他却返回这样的
{
"message": "Forbidden"
}
于是乎.我就想, 我的Request到了Cloudfront,相当于是到了自己架设的Nginx前端
但是Nginx前端怎么样把我这个Request路由到我指定的uWsig啊
在这里就是cloudfront如何将这个Request抛给APIG,然后执行对应的lambda啊
然后我就发现有Base Path Mappings的设置
(这一步我的理解就是相当于要给Nginx一个配置,让他知道这个路径的应该抛给那个uWsgi去处理, 我不确定是否正确)
图6: http://imgur.com/9WOZIz2
当做完这步,我在Postman测试.发现成功得到响应
其实都是不看文档惹的祸 ->__->
5, 关于lambda跑在VPC后,无法访问我S3的问题
这里首先要说一个环境变量,Role,User三个概念
每一个lambda是可以设置他的执行环境变量的,
例如我python的是可以通过os.environ 获取回来
我最初为了访问我的S3,创建了一个User然后用key + secret去做的
后来胖虎告诉我,可以指定一个Role就行了(最初我压根不知道User和Role是两个的区别,看一下文档就知道了)
于是,我给lambda一个Role(这个Role一定要有CloudWatchLog的权限,千万别忘记了)
我在VPC跑的一切正常
但我把lambda跑在VPC里面后,我就发现他无法访问的S3,后来群里的小伙伴告诉我,要去VPC里面建一个S3的EndPoint就解决了
我对VPC的理解基本是0,这里没办法跟大家解释太多
但为什么自定义域名需要ACM证书
这个其实自己用Nginx搭建HTTPS服务器的一样的原理,只不过你是去找CA颁发证书
那为什么要这个证书,就与HTTPS握手有关系了.这里会涉及到挺多概念的
假设你在某个Region使用了api.aws.xxx.com这个自定义域名,你就不能再别的区域在去用这个域名
否则Cloudfront那边应该是会有问题的
6,关于API Gateway的Resource和Stage
我开始编辑好Resource,没有点击Deploy API.到时测试的时候一直有问题
其实说白了就是,Resource是给你编辑的,你点击Deploy API之后才发布到Stage
但发布到Stage你是需要指定一个"版本"的, 例如他默认的prod.
7, 关于构建zip包的一些建议
例如我python代码中使用import requests这个模块
首先需要把这个模块安装在目录的里面
pip install requests -t 项目路径
举例子说明.当你写一个upload.py后(这里个py里面用到requests这个模块)
其实就是你只要把upload.py 和 requests模块的相关东西全部zip到同一层目录
然后传到lambda就可以执行了.
本质上我是这么理解的,当你代码import requests的时候
python2.7是从当前路径开始搜索这个模块的
所以只要你zip在同一层,lambda即能找到这个模块
这了有一个题外话,这些安装的lib,都是你本机环境的,例如linux的是so
windows应该是dll. 但是为什么这些lib传入到lambda之后,他都可以执行
今天胖虎也稍微透露了一下.但我目前还无法理解,有理解这个的同学,欢迎分享==>luodm03@qq.com
8, 相关参考文档
构建包的中文连接
https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html
python的例子(创建部属包)
https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html
python 创建部署程序包(中文)
https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/with-s3-example-deployment-pkg.html
S3中文文档
http://docs.amazonaws.cn/AmazonS3/latest/UG/HowThisGuideIsOrganized2.html
重用(关于一些lambda Container的重用)
https://aws.amazon.com/cn/blogs/compute/container-reuse-in-lambda/
Best practices – AWS Lambda function
https://cloudncode.blog/2017/03/02/best-practices-aws-lambda-function/
9, 关于Nginx + uWsgi + Python遇到的问题
当Nginx收到请求后, Nginx可以通过http协议或者wsgi的协议把这个Request抛到uwsgi里面
但是如果uWsgi在处理不过来的时候,我的理解是放入uWsgi的队列里面的
假设uWsgi处理的不够快,那客户端那边又发生HTTP超时,然后重试,那服务器基本就拥塞至死
在APIG把请求抛给lambda处理的时候,假设100个(这个是AWS默认数量)Conatainer都处于忙碌状态
那这个请求也会放入类似的队列概念,当你发现你100个Container都处理不过来的时候,
发个工单请求开大这个Container数量吧.
10, 总结
lambda之前我理解他耦合度高,不愿意接受.其实如果从Nginx+uWsgi+python的角度去理解,
本质上我认为是一样的了.没什么很大区别了.而且APIG可以导出对应语言的SDK给你(例如object-c)
并且Lambda的在数据量瞬间爆发的时候,这个好处是自己假设Nginx+uWsig+python无法比拟的
特别感谢卓动--张穗文, 胖虎, 微信昵称AWS-Tomcat(这位架构师在AWS会议上,不厌其烦的跟我解释了很多)
特别感谢群里的热心帮助过我的小伙伴.
看不到图,觉得麻烦的朋友.留下你的邮箱.我直接有空发你原稿
@youqingkui
Copy link
Author

i like python

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment