Serverless Single Page Apps Fast, Scalable, and Available
目录
[隐藏]Intro[编辑]
- Avoid Yaks(yak shaving)
- Move Faster with Tests(work with confidence)
- TDD:Red-Green-Refactor
Starting Simple[编辑]
- Benefits of a Serverless Design
- 无服务器维护开销,focused on app
- Easy to Scale(?)
- Highly Available
- Low Cost(不过这只是相对的,带宽IO和存储会随着用户数上升的)
- (Micro)Service Friendly
- Less Code
- Limitations
- Vendor Lock-In
- Odd Logs
- Different Security Model
- Different Identity Model
- Loss of Control
- Big Scale: Big Money?
- 本书项目:https://github.com/benrady/learnjs
- Running Locally
- learnjs $ ./sspa server
- https://github.com/livereload/LiveReload 有这个必要吗
- Deploying to Amazon S3
- $ sudo pip install awscli
- $ aws configure --profile admin (需要access key和secret key)
- Attach Policy(设置该用户能够访问哪些aws服务)
- Creating an S3 Bucket
- learnjs $ ./sspa create_bucket learnjs.benrady.com
- $ ./sspa deploy_bucket learnjs.benrady.com
- 这里用户的域名是怎么关联到实际的虚拟主机 http://learnjs.benrady.com.s3-website-us-east-1.amazonaws.com的?
- 哦,创建一个CNAME项。。。
Routing Views with Hash Events[编辑]
- Build seams into the design
- Jasmine http://jasmine.github.io
- Running Tests in Production(一个不错的想法!)
- var learnjs = {};
-
learnjs.showView = function(hash) {
- var problemView = $('<div class="problem-view">').text('Coming soon!');
- $('.view-container').empty().append(problemView);
-
learnjs.showView = function(hash) {
- Adding Routes
- Adding View Parameters
- spy?这使用的是ES6 Proxy技术实现的吗?
- spyOn(learnjs, 'problemView'); ... expect(learnjs.problemView).toHaveBeenCalledWith('42');
- spy?这使用的是ES6 Proxy技术实现的吗?
- 测试:Fast, Informative, Reliable, and Exhaustive
- Favor testability over encapsulation!
- 测试hashchange事件:
- learnjs.appOnReady();
- spyOn(learnjs, 'showView');
- $(window).trigger('hashchange');
- expect(learnjs.showView).toHaveBeenCalledWith(window.location.hash);
- 实现onhashchange:window.onhashchange = function() { learnjs.showView(window.location.hash); };
- SPA的核心还是很简单的~
- Next Steps:
- Jasmine Matchers
- Jasmine-jQuery
- Test Doubles
- Routing Libraries https://github.com/flatiron/director http://visionmedia.github.io/page.js/
- JavaScript Testing Alternatives
- Hash Change Events
- window.history.push/popState
Essentials of SPA[编辑]
- var view = $('.templates .problem-view').clone(); //不过这里实际上并不是HTML5模板元素;
- CSS:.templates { display: none; }
- 定义data model:
- low object mapping impedance
- HTML5 data attributes
- Creating an Application Shell
- This flash of markup is annoying at best, and confusing at worst.
- Using Custom Events
- $('.view-container>*').trigger(name, args);
- view.bind('removingView', function() { ... })
- Next Steps
- Web Accessibility
- Creating a Home Screen Icon
- CSS Animations
- Form Validation
使用Amazon Cognito作为身份服务[编辑]
- Creating an Identity Pool(基本上可以理解为匿名用户?)
- learnjs $ ./sspa create_pool conf/cognito/identity_pools/learnjs
- ==> learnjs/4001/conf/cognito/identity_pools/learnjs/pool_info.json
- "IdentityPoolId": "us-east-1:71958f90-67bf-4571-aa17-6e4c1dfcb67d",
- "SupportedLoginProviders": { "accounts.google.com": "ABC123ADDYOURID.apps.googleusercontent.com", ...
- ==> learnjs/4001/conf/cognito/identity_pools/learnjs/pool_info.json
- IAM Roles and Policies(?app用户并不是直接使用aws服务,而是通过app代理来间接使用aws吧?)
- assume_role_policy.json(内容略)
- Cognito uses Amazon's Security Token Service (STS) to generate temporary AWS credentials for our users.
- "Arn": "arn:aws:iam::730171000947:role/learnjs_cognito_authenticated" ?
- learnjs $ ./sspa create_pool conf/cognito/identity_pools/learnjs
- Fetching a Google Identity
- You might find Google’s process for adding a Sign-In button a bit...intrusive.(必须在页面上插入一个UI按钮)
- <script src="https://apis.google.com/js/platform.js" async defer></script>
- <meta name="google-signin-client_id" content="ABC123ADDYOURID.apps.googleusercontent.com"/>
- 定义JSONP回调:function googleSignIn() { ... }
- 最后:<span class="g-signin2" data-onsuccess="googleSignIn"></span>
- Requesting AWS Credentials
- 理论上说来,Cognito可以管理一个全局的identity池,而不是app特定的。。。(因为OAuth2本身是全局的)
- function googleSignIn(googleUser) {
- var id_token = googleUser.getAuthResponse().id_token;
- ... //请求将得到的token加入到身份池,然后app得到一个Cognito user ID(注意,这里体现的DDD的领域转换原则)
- Refreshing Tokens:gapi.auth2.getAuthInstance().signIn(...) --> ...
- => var deferred = new $.Deferred(); ... //这里的代码感觉有点过时了,ES6 Promise现在应该可以用了
- Creating a Profile View(略,配图似乎有点问题?)
- Next Steps
- Developer-Authenticated Identities
存储数据in DynamoDB[编辑]
- 一维主键:hash primary key
- 两维:hash attribute + range(排序)
- 属性名 <= 255 bytes
- DynamoDB supports a range of types for attribute values
- In addition, you can have attributes with document types, like Lists and Maps(属性值可以嵌套?K-V Store变成了文档数据库?)
- ... but out of the box it's essentially a big hash map.
- Strong vs. Eventual Consistency
- conf/dynamodb/tables/learnjs/config.json
- 3个顶级属性:AttributeDefinitions, KeySchema, and ProvisionedThroughput
- read/write unit:每秒<4KB的读(写<1KB??)
- app开发者需确保key在hash空间均匀分布,否则可能ProvisionedThroughputExceededException
- dynamodb create-table
- 3个顶级属性:AttributeDefinitions, KeySchema, and ProvisionedThroughput
- Secondary Indexes and Query vs. Scan
- 二级索引:global and local
- Authorizing DynamoDB Access:conf/dynamodb/tables/learnjs/role_policy.json
- 感觉这里基于aws Web服务来写Web应用就是戴着一堆镣铐在跳舞(处处是配额限制、安全过滤等等)
- 写文档:new AWS.DynamoDB.DocumentClient().put(item)
- Data Access and (服务器端)Validation
- By expanding the conditions clause in our IAM policy,
- We can only enforce rules about the type of request, where it's from, who made it, and things like that.
- 不能验证数据本身的格式 => 可使用Lambda来创建定制的Web服务...
用Lambda来构建微服务[编辑]
- With Lambda, Amazon has jumped firmly onto the microservices bandwagon.
- 写一个带2个参数的函数,保存为.js,然后与其node依赖打包为zip,上传,OK。
- Accessing the underlying Linux environment from your Lambda service is not only permitted but encouraged
- exports.echo = function(json, context) { context.succeed(["Echo: " + JSON.stringify(json)]); }
- Limits
- Each Lambda function gets 512MB of ephemeral disk space on the filesystem, located at /tmp
- 默认并发请求:100?
- a single execution cannot take more than 300 seconds
- How we pay?
- gigabyte-second
- For example, 128MB内存、100ms运行、100,000次调用, 则125GBS, 约$0.02
- Making asynchronous requests in parallel, rather than serially(程序越并行执行越节省!)
- Deploy First
- currently uses Node.js v4.3.2
- learnjs $ ./sspa build_bundle
- learnjs $ ./sspa create_service conf/lambda/functions/echo
- 创建IAM role learnjs_lambda_exec,以允许lambda访问DynamoDB:
- $ aws --profile admin iam list-policies //列出所有预定义的策略;
- $ aws --profile admin iam attach-role-policy \
- --role-name learnjs_lambda_exec \
- --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
- 编写
- LastEvaluatedKey:超过1MB响应限制(分页)——但是这次每次请求服务都会来全表scan,没有cache层吗?
- learnjs $ ./sspa deploy_bundle
- 调用
- new AWS.Lambda().invoke(params); //FunctionName: 'learnjs_popularAnswers', Payload: JSON.stringify({problemNumber: problemId})
- Using the Amazon API Gateway
- Add API endpoints
Serverless Security[编辑]
- Securing Your AWS Account!
- Disabling Any Root Access Keys
- Managing Users with Profiles
- Securing AWS Credentials
- 本地文件系统加密?OSX FileVault、Ubuntu EncryptedHome
- Set Up Multifactor Authentication
- Query Injection Attacks
- Cross-Site Scripting(XSS)Attacks(导致向目标网站页面注入恶意JS)
- Sandboxing JavaScript Using Web Workers
- worker.js:postMessage(eval(e.data));
- Sandboxing JavaScript Using Web Workers
- Cross-Site Request Forgery(请求伪造,泄露敏感数据)
- 要求请求必须是POST,且满足特定编码格式?
- Cross-Origin Requests and the Same-Origin Policy(POST请求的url必须与当前origin一致)
- Wire Attacks and Transport Layer Security
- Sidejacking Attacks(==> 全站https?)
- Denial-of-Service Attacks
- Protecting S3 with CloudFront
- Distributed Denial-of-Service(DDoS)
- => CloudWatch?
- Next Steps
- Using Signed URLs
Scaling Up[编辑]
- Monitor Web Services(CloudWatch的内部原理是什么?http前端反向代理?)
- Send the alert to Amazon's Simple Notification Service (SNS)
- $ aws --profile admin sns create-topic --name EmailAlerts
- $ aws --profile admin sns subscribe \
- --topic-arn « your_topic_arn » \
- --protocol email \
- --notification-endpoint you@example.com
- $ aws --profile admin cloudwatch put-metric-alarm ...
- Send the alert to Amazon's Simple Notification Service (SNS)
- Unexpected Expenses*
- Analyze S3 Web Traffic
- Logging S3 Requests(创建bucket):
- $ aws --profile admin s3 mb s3://learnjs-logging.benrady.com
- $ aws s3api put-bucket-acl --bucket learnjs-logging.benrady.com --grant-write URI=http://acs.amazonaws.com/groups/s3/LogDelivery --grant-read-acp URI=...
- $ aws s3api put-bucket-logging --generate-cli-skeleton > conf/s3/ « your.bucket.name » /logging.json
- $ aws --profile admin s3api put-bucket-logging --cli-input-json "file://conf/s3/learnjs.benrady.com/logging.json"
- $ aws s3 sync s3://learnjs-logging.benrady.com/ logs
- 分析S3 access logs
- Response Code Frequency:$ cat logs/* | cut -d ' ' -f 13 | sort | uniq -c
- Popular Resources:$ cat logs/2016-01-1[123]* | cut -d ' ' -f 11 | sort | uniq -c| sort -rn
- Usage by Time of Day:
- $ cat logs/* | awk '{ print $3 }' | tr ':' ' ' | awk '{ print $2 ":" $3}' | sort | uniq -c
- Logging S3 Requests(创建bucket):
- Optimize for Growth
- learnjs $ aws s3api put-object --profile admin --acl 'public-read' --bucket learnjs.benrady.com --cache-control 'max-age=31536000' --key vendor.js --body public/vendor.js
- 不过,感觉这个针对特定url path的静态缓存策略不是很灵活啊?
- Invalidating Caches with Versioned Filenames
- learnjs $ aws s3api put-object --profile admin --acl 'public-read' --bucket learnjs.benrady.com --cache-control 'max-age=31536000' --key vendor.js --body public/vendor.js
- Costs of the Cloud
- ... This means the loading cost of our app, including request and transfer fees, will be $0.24 for every 10,000 users
- To put this in perspective, the AWS Free Tier for DynamoDB lets us perform up to 2.1 million writes per day at no cost.
- Microservice Costs
- the Amazon Free Tier provides for 400,000 gigabyte-seconds of execution at no cost.(但不包括额外的read capacity)
- !Buying capacity based on average usage means your app will be broken half of the time.
- Creating a Client Logging Web Service
- 捕获window.onerror,发送到CloudWatch(注意,你不能信任这些log,因为它们有可能是hacker伪造的?)