微信号哪里买鱼虾蟹卡在哪里买??谁能告诉我?

用产品思维设计API(一)——RESTful就是个骗局
时间: 17:02:51
&&&& 阅读:299
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&用产品思维设计API(一)——RESTful就是个骗局
最近公司内部在重构项目代码,包括API方向的重构,期间遇到了很多的问题,不由得让我重新思考了下。
- 一个优雅的API该如何设计?
- 前后端分离之后,API真的解耦分离了吗?
- 不断的版本迭代,API的兼容性该如何做?
年前,我司内部的接口已经进入了一个完全的重构阶段,参考了市面上各大平台的API和文档,自己也总结出了很多的心得。这里向大家分享一下,接下来一个月,我们向从下面几个方面向大家介绍一个优雅的API(至少我认为挺优雅)该如何设计。
RESTful就是个骗局
数据解耦,才是前后分离的本质
版本控制,没有你想的这么简单
随意定义错误码,你还在这样干?
安全,就只能用HTTPS?
ps. 打一个广告,公司内部现在在招聘各种技术岗位,Java、Android、前端等,待遇保证能让你涨30%,有兴趣的朋友可以加我微信,二维码在文章最后。
Ok,今天是第一篇文章——再看RESTful。
回顾一下HTTP协议
基于HTTP协议的API使我们在开发APP、网站中最常见的形式,为了更好的了解如何设计一个良好的API,我们这里先简单的回顾一个HTTP协议。
先抓包看一个请求demo
我们用Fiddler抓了一下360浏览器的任务中心的API接口信息,如下是它的请求信息:
POST http://task.browser.360.cn/online/setpoint HTTP/1.1
Accept: */*
User-Agent: Mozilla/4.0 ( MSIE 6.0; Windows NT 5.1)
Pragma: no-cache
Content-Type: application/x-www-form-urlencoded
Host: task.browser.360.cn
Content-Length: 449
Cookie: T=s%3Ddaf1a2e6347e01ebccc72def%26t%3D%26lm%3D%26lf%3D1%26sk%3Dadcc05bc7709%26mt%3D%26rc%3D4%26v%3D2.0%26a%3D1; zsmodel=MI%20NOTE%20LTE; zsosv=4.4.2; __guid=63000.1.9001
stamp=&qt=Q%3Du%3Dfgpubh%26n%3D%25PO%25QR%25P9%25R1%25P0%25RS%25O5%25P4%25PO%25N7%25O9%25S8%26le%3Dp3EwnT91WGDjZGLmYzAioD%3D%3D%26m%3DZGZlWGWOWGWOWGWOWGWOWGWOZwDl%26qid%3D%26im%3D1%5Ft013ba372ccf308e7b6%26src%3D360se%26t%3D1%0D%0AT%3Ds%3D76f8cf66a8fe%26t%3D%26lm%3D%26lf%3D1%26sk%3De79c0ecb263d447d96e85f%26mt%3D%26rc%3D9%26v%3D2%2E0%26a%3D1&verify=6fce1222e64cefa2b5b3d24d65fa9eb1
得到的服务器响应结果,如下所示:
HTTP/1.1 200 OK
Server: nginx/1.6.3
Date: Mon, 19 Dec :58 GMT
Content-Type: text/ charset=utf-8
Transfer-Encoding: chunked
Connection: close
{"errno":"0","errmsg":"success","lastpoint":"-20-42"}
这里,没有任何文档我们也能够直接的看出来。大概步骤是:
1. 浏览器向
发起了一个 POST请求
2. 该请求中附带了一些cookie信息,也带了一些自定义的消息体
3. 响应结果是正确的,返回给浏览器一个JSON格式的数据。
Http协议的构成
对于一个完整的请求(Request)、响应(Response)来说,还是有一定的套路的,这里我们看一下HTTP请求和响应的规范格式。
对于上面360浏览器的demo,结合两个协议图文,我们能够看到更多的信息:
请求Request
请求的方式 POST
请求的地址 URL
请求的头部信息,headers(cookie\UA等在于此处存着)
附属体信息 (通常为自定义上传的信息)
响应Response
状态码 (http协议的状态码)
相应头部信息headers (时间、数据格式、编码等信息)
附属体信息(通常为相应的自定义数据体)
OK,既然我们已经了解了HTTP协议的请求与响应的构成,理论上我们已经可以利用上述的逻辑加入完成自己的API设计了。
设计之前,我们需要思考一下,我们需要设计吗?目前市面上最流行的RESTful API协议为什么我们不用?
再看RESTful
在API设计上,如果有一样东西获得广泛认可的话,那就是 RESTful 原则。
REST的关键原则与将你的API分割成逻辑资源紧密相关,并采用所用并理解的原理。使用HTTP请求控制这些资源,其中,这些方法(GET, POST, PUT, PATCH, DELETE)具有特殊含义。
举例来说,有一个API提供公司内部(company)的信息,还包括各种部门(departments
)和雇员(employees)的信息,则它的路径应该设计成下面这样。
https://api.example.com/company
https://api.example.com/departments
https://api.example.com/employees
基于请求的方式和路径来作为常见的CURD(增删改查)
GET /company:列出所有公司
POST /company:新建一个公司
GET /company/ID:获取某个指定公司的信息
PUT /company/ID:更新某个指定公司的信息(提供该公司的全部信息)
PATCH /company/ID:更新某个指定公司的信息(提供该公司的部分信息)
DELETE /company/ID:删除某个公司
GET /company/ID/employees:列出某个指定公司的所有雇员
DELETE /company/ID/employees/ID:删除某个指定公司的指定雇员
对于请求后的响应结果,RESTful也做了一个很好得定义:
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
RESTful API设计确实比较不多,对于一些简单的APP来说,能够快速的开发,并满足他们的绝大部分需求。
但是,请来一个神,就必须将神供着!
我们在使用之中也出现了很多头痛的问题,如:
问题1:多表多条件联查接口,如何设计?
很多情况下,我们的API并不是简简单单的查询(select)数据库中的数据,直接返回给调用者。我们可能会涉及到多表联查(left join , inner join)、排序(order by)、条件判断(where)、合并(union)等等。
如:找一个在我公司中找32~40岁,月收入在8000元,且在IT部门的人,属于北京事业群,将他们的工作KPI按逆序排列输出。
N多条件在一起的时候,API请求路径就会变得很难设计。
当然,网上有采用如下方式
/api/company?field1=abc&field2__like=%abc%&field3__gt=100&field4__not=999
但是,调用者调用API的时候,就感觉像是写SQL,API本身亦变得不可维护。
问题2:多逻辑请求接口,很难命名
不要小看命名的问题,我们要从名字上便于理解,又不要太长,又不能随意的缩写,其实很难。
如针对上述我们要求查询的逻辑来说,整个结构请求的路径就会变为如下所示:
GET /company/:ID/departments/:ID/employees/:ID/AREA/:ID/KPI/:ID?agemin=32&agemax=40&income=8000&order=api&sort=desc
虽然能够请求,但是已经很难从请求路径上看出我们的请求目的是什么,背离了所见即所需求的目的。
问题3:完整资源对象的返回,并不是调用者所需要的
如果一个数据库的字段很多,如我们的产品表,将近40~50个字段,每一个字段的类型、输出格式化都不一样,这个时候,如果直接将bean打印出来,如下所示:
出现几个问题
太多冗余字段:APP需要的PC不需要,PC需要的,H5不需要
每个字段都很难理解:调用API的人,估计看文档要看疯了,需求稍微一遍,前端样式也要跟着改,再看一次文档(韬哥认为这个是最重要的)
整个库设计完全暴露:安全安全!
"totalCount": 15,
"id": 7956,
"sTitle": "信证-通汇安盈一号",
"productTypeId": 3,
"zdPrice2": 2,
"nianHuaShouYiStart": 8.5,
"nianHuaShouYiEnd": 9.5,
"jdt": 100,
"touZiLingYu": "基础设施",
"level": 99,
"saleStatus": 121,
"category": 30,
"jdTime": " 09:48:13",
"raiseProgress": "【日9时更新】本期为第六期,本期规模不限,本期已封帐,目前年前总剩余额度2520万,需要资产证明,有下期,下期无缝对接中;",
"touZiMenkan": 100,
"collectCount": 1,
"hasCollect": false,
"redPack": "",
"fundType": "基础设施",
"visitCount": 24631,
"docPreviewCount": 11,
"producttagids_intarray": "\t13\t11",
"productTags": "11,二年期;13,固定收益类;",
"title": "信证-通汇安盈一号(第6期)",
"statusId": 40,
"qiXian": 24,
"peibi": "",
"pmName": "王佳慧",
"pmUserName": "王佳慧",
"daXiao": "小额畅打",
"lingYu": null,
"shouYiType": "固定收益类",
"payStatus": "半年付息",
"downLoad": "Upload/ProductPDF//信证-通汇安盈_473.zip",
"groupName": null,
"zdPrice": 1,
"addr": null,
"companyId": 272,
"adminId": 473,
"groupMaxPrice": 0,
"nianHuaShouYiExt": "",
"companyName": "汇蕴",
"fxList": [
"title": "100万≤X<300万",
"price": "2.0%",
"isFloat": false,
"earningRate": "8.5%",
"packingRate": 8.5
"title": "300万≤X<1000万",
"price": "1.5%",
"isFloat": false,
"earningRate": "9%",
"packingRate": 9
"title": "1000万≤X",
"price": "1.0%",
"isFloat": false,
"earningRate": "9.5%",
"packingRate": 9.5
"sourceRepayment": "1、项目公司运营收入\n2、项目公司股东自有资金\n3、银行贷款资金置换",
"fundInvest": "用于佛(山)清(远)从(化)高速公路北段工程建设项目的建设,以期获得投资收益",
"windControl": "1、央企中电建路桥出具完工承诺,保证项目按期运营\n2、投资人成为融资主体股东",
"highlights": "?【省级重点】标的是广东省基建-高速公路建设,是从“十一五”就提出的建设规划,此项目为广东省重点工程,由政府牵头并给予大力支持,项目公司也与清远市交通运输局签订《特许经营协议》,项目违约成本极高; \n?【项目把控实力】中证基金是项目公司第一大股东,对整体项目有掌控权,有助于资金安全及还款的及时有效;同时一同参与的有中电建、龙浩集团;\n?【管理人背景】管理人由中证基金99%控股,中证基金股东为中信证券、华夏资本、中诚信托;\n?【央企增信】央企中电建路桥出具完工承诺,保证项目能够按期运营;\n?【银行资金】项目已有意向银行资金83.36亿贷款划拨计划,安全边际较高;",
"productOrganizationId": 318,
"productOrganizationPic": "Upload/productOrganization//logo_473.png",
"bqqsr": " 00:00:00",
"province": 440000,
"proviceName": "广东省",
"cityName": "佛山市",
"dyl": -1,
"addrId": null,
"updateTime": " 09:48:13",
"listingTime": " 11:41:00",
"parentId": 7300,
"phase": 6,
"issuerCompanyId": 0,
"issuerCompanyName": "",
"issuerId": 0,
"issuerPhone": "",
"issuerName": "",
"project": 3,
"attr": 7,
"jianBan": "",
"appointmentCount": 53,
"downloadCount": 385,
"sendEmailCount": 21,
"cashback": "",
"tagsArray": [
"id": "13",
"name": "固定收益类"
"id": "11",
"name": "二年期"
"bestEarningRate": "9.5%",
"bestEarningRate_fore": 9,
"bestEarningRate_back": "5",
"bestPrice": "2.0%",
"bestPrice_fore": 2,
"bestPrice_back": "0",
"bestGroupPrice": "待定",
"bestGroupPrice_fore": "待定",
"bestGroupPrice_back": 0,
"bestEmployeePrice": "待定",
"bestEmployeePrice_fore": "待定",
"bestEmployeePrice_back": 0,
"saleStatusName": "募集中",
"isHot": false,
"isHotSale": true,
"isRecommend": false,
"packingRate": 0,
"returnCash": 0
"isSuccess": true,
"code": 0,
"runSpanTime": 97
像上面这个demo数据一样。泥他妈这么多字段,让前端、APP都去理解?是否前端也要参与数据库设计?前后端分离的意义在哪?
sorry,忍不住吐槽。
问题4:安全,还是安全
安全这个方向的问题太多,很难一一排查,如见到此类问题就想把协议换为HTTPS的人来说,你基本是还不了解安全。对ROOT后的Android系统来说,HTTPS并没有这么神秘。API上必须考虑安全性。
- 查询ID是不是该用主键?
- 所有下发的字段显示是否应该直接下发?
- 登录限制,频率限制
- 加密措施,如何做?
- 请求路径将表内关系完全暴露,响应结果将表结构暴露,SQL注入\数据爬虫\Replay攻击 防范要求太高。
当然,RESTful API规则上还有很多关于过滤、错误码(我最不能接受的就是状态码,很多运营商直接都劫持了,把你坑死)的说明,这里我们就不一一列举了。
上述一系列的问题,让我们在整个系统开发的过程中,使用倍感困惑。也许是自己对RESTful的了解不够到位,或者使用上无法掌握其精髓。所以,我们最后在设计自己API的时候,采取的是类RESTful协议,用其思想,并在RESTful的基础是做了很多的自定义操作功能。
request设计
整个设计上借鉴了RESTful的思想,将操作同样分为CURD(增删改查),如对用户(User)进行操作
- sheme命名
user/create
user/delete
user/update
user/login
getUserKpi?agemin=30&agemax=45
user/list?query=周%&pageSize=10&start=0
通用请求头部,自定义header
当然,最重要的是,我们的API都需要监控,根据版本号做兼容等,我们需要在request的header之中自定义一些信息,这里我们定义为json格式的信息。如下:
"userId": "1000",
"platform": "android",
"imei": "xxxxx",
"appVersion": "1.0",
"cityId": "0",
"platformVersion": "4.2",
"deviceId": "xxxx",
"channel": "xxx",
"protoVersion": 1
请求类型平台。目前有:iOS、Android、pcweb、h5
appVersion
请求来源的version,这里通常指的是app的版本号
定位到的城市id
platformVersion
改设备的版本号,如:Android4.4.4,、iOS10.0.2
分发渠道标识
protoVersion
协议版本号
response设计
相比request,response的设计规范相对简单很多(返回内容设计还是比较重要的,下一篇向大家介绍)。
还是根据RESTful的方式,我们将返回结果分为两部分,错误码和实际结果,如下所示:
"msg": "ok",
"cmd": "user/info"
请求action
如何设计body里面的结果才是response的关键,整个API数据解耦的难题。这里不做详细介绍了,下一篇给大家一一分享下。
有问题,或者想吐槽的,请加韬哥微信:
* @author zhoushengtao(周圣韬)
* @since 日 凌晨0:53:13
* @weixin stchou_zst
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&国之画&&&& &&&&chrome插件
版权所有 京ICP备号-2
迷上了代码!RESTful api 当前用户资源uri设计怎么选择
来源:博客园
【 有这样一种情况,会员的银行卡、会员的提现记录,会员的充值记录,这种属于当前用户下的记录api(oauth认证) 设计成
1、api/members/current/bankcards
从accesstoken获取当前会员id
2、api/members/{memberid}/bankcards
如果设计成1,似乎不符合 restful 风格
如果设计成2,岂不是当前登录会员可以请求其他其他memberid的银行卡列表,如果在action里再检查一遍 memberid 是否与 accesstoken 里的 memberid 一致,是否画蛇添足?】
你这两种设计都不是已授权用户获取自己的基本信息的 REST API,而只是类 REST API。
你设计的 1 风格的 URL,描述为“使用 Access Token 访问 current 的 bankcards”,那么这个 URL 的定义就“信息冗余”了,“current”是完全没有必要出现的。我假设你确实描述的是 OAuth 认证中访问用户资源的场景,那么你拿到的 Access Token 的含义是“Resource Owner允许我获取他的 bankcars 信息”,因此,下一步就是直接向 Resource Server 提交 Access Token,所以最佳的 API 设计是: api/members/bankcards?accesstoken=xxxxxxxxx。
设计很多时候是一种权衡,第1种是更合理的选择
免责声明:本站部分内容、图片、文字、视频等来自于互联网,仅供大家学习与交流。相关内容如涉嫌侵犯您的知识产权或其他合法权益,请向本站发送有效通知,我们会及时处理。反馈邮箱&&&&。
学生服务号
在线咨询,奖学金返现,名师点评,等你来互动怎么做能提供RESTful的安全性?
在企业级应用中,iOS和Java之间用RESTful通信,如果想提高安全性,用什么比较好?OAuth是不是一种选择?还有其他的吗?
RESTful 的安全性其实就是 HTTP 的安全性
可以自己搞个简单的:如你要请求一个url,必须前一步调用一个认证获取一个token(可以是一串唯一uuid)准入的令牌。在把token带入你要请求的url的header上。
OAuth2 并不能提供安全性,OAuth2提供的是第三方应用接入的扩展。
提高安全性可以通过https + token ,每次请求都对token做验证。
引用来自“Black_JackQ”的评论可以自己搞个简单的:如你要请求一个url,必须前一步调用一个认证获取一个token(可以是一串唯一uuid)准入的令牌。在把token带入你要请求的url的header上。你这样行不通吧,获取token的url,客户端也是可以发现的,我先请求你的token url,然后以后的请求我也把token 带人请求的url header。等于还是伪造了,你的请求,个人觉得,只能防止一点点,还得从服务器防火墙上屏蔽一些ip请求之类的。
引用来自“Black_JackQ”的评论可以自己搞个简单的:如你要请求一个url,必须前一步调用一个认证获取一个token(可以是一串唯一uuid)准入的令牌。在把token带入你要请求的url的header上。引用来自“铂金浪子”的评论你这样行不通吧,获取token的url,客户端也是可以发现的,我先请求你的token url,然后以后的请求我也把token 带人请求的url header。等于还是伪造了,你的请求,个人觉得,只能防止一点点,还得从服务器防火墙上屏蔽一些ip请求之类的。这个token是独立的业务逻辑,比如可以是通过用户名和密码登录后才能取到一个token。
引用来自“Black_JackQ”的评论可以自己搞个简单的:如你要请求一个url,必须前一步调用一个认证获取一个token(可以是一串唯一uuid)准入的令牌。在把token带入你要请求的url的header上。引用来自“铂金浪子”的评论你这样行不通吧,获取token的url,客户端也是可以发现的,我先请求你的token url,然后以后的请求我也把token 带人请求的url header。等于还是伪造了,你的请求,个人觉得,只能防止一点点,还得从服务器防火墙上屏蔽一些ip请求之类的。引用来自“Black_JackQ”的评论这个token是独立的业务逻辑,比如可以是通过用户名和密码登录后才能取到一个token。好吧,当我没说,假如有一种情况是无需登录的,该咋控制才能避免恶意提交呢?
在我的实际项目中,我大概实现了下面几种:
1、静态签名,最初级安全性最弱,就是对请求参数进行HMAC-SHA1签名(用一个静态分发的key做seed),这种可以防篡改,但不能防重放
2、动态签名,在方案1的基础上增加一些随机因子参数,比如时间戳、随机字符串等,可以做到防篡改、防重放(服务端会记录最近2小时内请求参数中的时间戳和随机字符串,重复请求会直接被拒绝)。这种一般针对服务端&=&服务端对接,可能还需要一个IP地址白名单
3、动态口令方案:先生成一个动态口令otp,otp有一个失效时间,失效后需要重新请求生成,然后将otp拼在请求参数后面,当然还是需要前面的签名过程
4、OAuth2(一种第三方应用授权协议,各种开放平台都支持)的access_token,这个不多说
5、access_token+ticket方案,由Client服务端请求得到access_token后,再根据access_token得到一个ticket,ticket的安全级别比access_token低,访问权限也比access_token低。然后把ticket交给Client。微信公众平台的JS SDK中用到了这种方案。access_token和ticket都有一个失效时间
/Dreampie/Resty 可以看看其中的resty-security 是基于api的uri做的安全过滤
引用来自“优雅先生”的评论
在我的实际项目中,我大概实现了下面几种:
1、静态签名,最初级安全性最弱,就是对请求参数进行HMAC-SHA1签名(用一个静态分发的key做seed),这种可以防篡改,但不能防重放
2、动态签名,在方案1的基础上增加一些随机因子参数,比如时间戳、随机字符串等,可以做到防篡改、防重放(服务端会记录最近2小时内请求参数中的时间戳和随机字符串,重复请求会直接被拒绝)。这种一般针对服务端&=&服务端对接,可能还需要一个IP地址白名单
3、动态口令方案:先生成一个动态口令otp,otp有一个失效时间,失效后需要重新请求生成,然后将otp拼在请求参数后面,当然还是需要前面的签名过程
4、OAuth2(一种第三方应用授权协议,各种开放平台都支持)的access_token,这个不多说
5、access_token+ticket方案,由Client服务端请求得到access_token后,再根据access_token得到一个ticket,ticket的安全级别比access_token低,访问权限也比access_token低。然后把ticket交给Client。微信公众平台的JS SDK中用到了这种方案。access_token和ticket都有一个失效时间
请问你说的这些有没有什么好的资料和书可以推荐?英语的也可以,多谢
--- 共有 1 条评论 ---
没有系统化的资料,主要通过自己看博客看论文+借鉴现有的同类产品的做法(比如微博开放平台、腾讯开放平台)。
引用来自“优雅先生”的评论
在我的实际项目中,我大概实现了下面几种:
1、静态签名,最初级安全性最弱,就是对请求参数进行HMAC-SHA1签名(用一个静态分发的key做seed),这种可以防篡改,但不能防重放
2、动态签名,在方案1的基础上增加一些随机因子参数,比如时间戳、随机字符串等,可以做到防篡改、防重放(服务端会记录最近2小时内请求参数中的时间戳和随机字符串,重复请求会直接被拒绝)。这种一般针对服务端&=&服务端对接,可能还需要一个IP地址白名单
3、动态口令方案:先生成一个动态口令otp,otp有一个失效时间,失效后需要重新请求生成,然后将otp拼在请求参数后面,当然还是需要前面的签名过程
4、OAuth2(一种第三方应用授权协议,各种开放平台都支持)的access_token,这个不多说
5、access_token+ticket方案,由Client服务端请求得到access_token后,再根据access_token得到一个ticket,ticket的安全级别比access_token低,访问权限也比access_token低。然后把ticket交给Client。微信公众平台的JS SDK中用到了这种方案。access_token和ticket都有一个失效时间
为什么要用HMAC-SHA1加密?HMAC-SHA1有什么优点吗?可以用SHA1或者MD5?撰写安全合格的REST API - 知乎专栏
{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"title":"撰写安全合格的REST API","author":"tchen","content":"两周前因为公司一次裁人,好几个人的活都被按在了我头上,这其中的一大部分是一系列REST API,撰写者号称基本完成,我测试了一下,发现尽管从功能的角度来说,这些API实现了spec的显式要求,但是从实际使用的角度,欠缺的东西太多(各种各样的隐式需求)。REST API是一个系统的backend和frontend(或者3rd party)打交道的通道,承前启后,有很多很多隐式需求,比如调用接口与RFC保持一致,API的内在和外在的安全性等等,并非提供几个endpoint,返回相应的json数据那么简单。仔细研究了原作者的代码,发现缺失的东西实在太多,每个API基本都在各自为战,与其修补,不如重写(并非是程序员相轻的缘故),于是我花了一整周,重写了所有的API。稍稍总结了些经验,在这篇文章里讲讲如何撰写「合格的」REST API。RFC一致性REST API一般用来将某种资源和允许的对资源的操作暴露给外界,使调用者能够以正确的方式操作资源。这里,在输入输出的处理上,要符合HTTP/1.1(不久的将来,要符合HTTP/2.0)的RFC,保证接口的一致性。这里主要讲输入的method/headers和输出的status code。MethodsHTTP协议提供了很多methods来操作数据:GET: 获取某个资源,GET操作应该是幂等(idempotence)的,且无副作用。POST: 创建一个新的资源。PUT: 替换某个已有的资源。PUT操作虽然有副作用,但其应该是幂等的。PATCH(RFC5789): 修改某个已有的资源。DELETE:删除某个资源。DELETE操作有副作用,但也是幂等的。幂等在HTTP/1.1中定义如下:Methods can also have the property of \"idempotence\" in that (aside from error or expiration issues) the side-effects of N & 0 identical requests is the same as for a single request. 如今鲜有人在撰写REST API时,简单说来就是一个操作符合幂等性,那么相同的数据和参数下,执行一次或多次产生的效果(副作用)是一样的。现在大多的REST framwork对HTTP methods都有正确的支持,有些旧的framework可能未必对PATCH有支持,需要注意。如果自己手写REST API,一定要注意区分POST/PUT/PATCH/DELETE的应用场景。Headers很多REST API犯的比较大的一个问题是:不怎么理会request headers。对于REST API,有一些HTTP headers很重要:Accept:服务器需要返回什么样的content。如果客户端要求返回\"application/xml\",服务器端只能返回\"application/json\",那么最好返回status code 406 not acceptable(RFC2616),当然,返回application/json也并不违背RFC的定义。一个合格的REST API需要根据Accept头来灵活返回合适的数据。If-Modified-Since/If-None-Match:如果客户端提供某个条件,那么当这条件满足时,才返回数据,否则返回304 not modified。比如客户端已经缓存了某个数据,它只是想看看有没有新的数据时,会用这两个header之一,服务器如果不理不睬,依旧做足全套功课,返回200 ok,那就既不专业,也不高效了。If-Match:在对某个资源做PUT/PATCH/DELETE操作时,服务器应该要求客户端提供If-Match头,只有客户端提供的Etag与服务器对应资源的Etag一致,才进行操作,否则返回412 precondition failed。这个头非常重要,下文详解。Status Code很多REST API犯下的另一个错误是:返回数据时不遵循RFC定义的status code,而是一律200 ok + error message。这么做在client + API都是同一公司所为还凑合可用,但一旦把API暴露给第三方,不但贻笑大方,还会留下诸多互操作上的隐患。以上仅仅是最基本的一些考虑,要做到完全符合RFC,除了参考RFC本身以外,erlang社区的webmachine或者clojure下的liberator都是不错的实现,是目前为数不多的REST API done right的library/framework。(liberator的decision tree,沿袭了webmachine的思想,见:)安全性前面说过,REST API承前启后,是系统暴露给外界的接口,所以,其安全性非常重要。安全并单单不意味着加密解密,而是一致性(integrity),机密性(confidentiality)和可用性(availibility)。请求数据验证我们从数据流入REST API的第一步 —— 请求数据的验证 —— 来保证安全性。你可以把请求数据验证看成一个巨大的漏斗,把不必要的访问统统过滤在第一线:Request headers是否合法:如果出现了某些不该有的头,或者某些必须包含的头没有出现或者内容不合法,根据其错误类型一律返回4xx。比如说你的API需要某个特殊的私有头(e.g. X-Request-ID),那么凡是没有这个头的请求一律拒绝。这可以防止各类漫无目的的webot或crawler的请求,节省服务器的开销。Request URI和Request body是否合法:如果请求带有了不该有的数据,或者某些必须包含的数据没有出现或内容不合法,一律返回4xx。比如说,API只允许querystring中含有query,那么\"?sort=desc\"这样的请求需要直接被拒绝。有不少攻击会在querystring和request body里做文章,最好的对应策略是,过滤所有含有不该出现的数据的请求。数据完整性验证REST API往往需要对backend的数据进行修改。修改是个很可怕的操作,我们既要保证正常的服务请求能够正确处理,还需要防止各种潜在的攻击,如replay。数据完整性验证的底线是:保证要修改的数据和服务器里的数据是一致的 —— 这是通过Etag来完成。Etag可以认为是某个资源的一个唯一的版本号。当客户端请求某个资源时,该资源的Etag一同被返回,而当客户端需要修改该资源时,需要通过\"If-Match\"头来提供这个Etag。服务器检查客户端提供的Etag是否和服务器同一资源的Etag相同,如果相同,才进行修改,否则返回412 precondition failed。使用Etag可以防止错误更新。比如A拿到了Resource X的Etag X1,B也拿到了Resource X的Etag X1。B对X做了修改,修改后系统生成的新的Etag是X2。这时A也想更新X,由于A持有旧的Etag,服务器拒绝更新,直至A重新获取了X后才能正常更新。Etag类似一把锁,是数据完整性的最重要的一道保障。Etag能把绝大多数integrity的问题扼杀在摇篮中,当然,race condition还是存在的:如果B的修改还未进入数据库,而A的修改请求正好通过了Etag的验证时,依然存在一致性问题。这就需要在数据库写入时做一致性写入的前置检查。访问控制REST API需要清晰定义哪些操作能够公开访问,哪些操作需要授权访问。一般而言,如果对REST API的安全性要求比较高,那么,所有的API的所有操作均需得到授权。在HTTP协议之上处理授权有很多方法,如HTTP BASIC Auth,OAuth,HMAC Auth等,其核心思想都是验证某个请求是由一个合法的请求者发起。Basic Auth会把用户的密码暴露在网络之中,并非最安全的解决方案,OAuth的核心部分与HMAC Auth差不多,只不过多了很多与token分发相关的内容。这里我们主要讲讲HMAC Auth的思想。回到Security的三个属性:一致性,机密性,和可用性。HMAC Auth保证一致性:请求的数据在传输过程中未被修改,因此可以安全地用于验证请求的合法性。HMAC主要在请求头中使用两个字段:Authorization和Date(或X-Auth-Timestamp)。Authorization字段的内容由\":\"分隔成两部分,\":\"前是access-key,\":\"后是HTTP请求的HMAC值。在API授权的时候一般会为调用者生成access-key和access-secret,前者可以暴露在网络中,后者必须安全保存。当客户端调用API时,用自己的access-secret按照要求对request的headers/body计算HMAC,然后把自己的access-key和HMAC填入Authorization头中。服务器拿到这个头,从数据库(或者缓存)中取出access-key对应的secret,按照相同的方式计算HMAC,如果其与Authorization header中的一致,则请求是合法的,且未被修改过的;否则不合法。GET /photos/puppy.jpg HTTP/1.1\nHost: johnsmith.\nDate: Mon, 26 Mar :58 +0000\n\nAuthorization: AWS AKIAIOSFODNN7EXAMPLE:frJIUN8DYpKDtOLCwo//yllqDzg=\n(Amazon HMAC图示,见:)在做HMAC的时候,request headers中的request method,request URI,Date/X-Auth-Timestamp等header会被计算在HMAC中。将时间戳计算在HMAC中的好处是可以防止replay攻击。客户端和服务器之间的UTC时间正常来说偏差很小,那么,一个请求携带的时间戳,和该请求到达服务器时服务器的时间戳,中间差别太大,超过某个阈值(比如说120s),那么可以认为是replay,服务器主动丢弃该请求。使用HMAC可以很大程度上防止DOS攻击 —— 无效的请求在验证HMAC阶段就被丢弃,最大程度保护服务器的计算资源。HTTPSHMAC Auth尽管在保证请求的一致性上非常安全,可以用于鉴别请求是否由合法的请求者发起,但请求的数据和服务器返回的响应都是明文传输,对某些要求比较高的API来说,安全级别还不够。这时候,需要部署HTTPS。在其之上再加一层屏障。其他做到了接口一致性(符合RFC)和安全性,REST API可以算得上是合格了。当然,一个实现良好的REST API还应该有如下功能:rate limiting:访问限制。metrics:服务器应该收集每个请求的访问时间,到达时间,处理时间,latency,便于了解API的性能和客户端的访问分布,以便更好地优化性能和应对突发请求。docs:丰富的接口文档 - API的调用者需要详尽的文档来正确调用API,可以用swagger来实现。hooks/event propogation:其他系统能够比较方便地与该API集成。比如说添加了某资源后,通过kafka或者rabbitMQ向外界暴露某个消息,相应的subscribers可以进行必要的处理。不过要注意的是,hooks/event propogation可能会破坏REST API的幂等性,需要小心使用。各个社区里面比较成熟的REST API framework/library:Python: django-rest-framework(django),eve(flask)。各有千秋。可惜python没有好的类似webmachine的实现。Erlang/Elixir: webmachine/ewebmachine。Ruby: webmachine-ruby。Clojure:liberator。其它语言接触不多,就不介绍了。可以通过访问该语言在github上相应的awesome repo(google awesome XXX,如awesome python),查看REST API相关的部分。如果您觉得这篇文章不错,请点赞。多谢!欢迎订阅公众号『程序人生』(搜索微信号 programmer_life)。每篇文章都力求原汁原味,北京时间中午12点左右,美西时间下午8点左右与您相会。","updated":"T00:51:54.000Z","canComment":false,"commentPermission":"anyone","commentCount":49,"collapsedCount":0,"likeCount":987,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","titleImage":"/ebb9bedab3_r.jpg","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[],"adminClosedComment":false,"titleImageSize":{"width":980,"height":327},"href":"/api/posts/","excerptTitle":"","column":{"slug":"prattle","name":"迷思"},"tipjarState":"inactivated","annotationAction":[],"sourceUrl":"","pageCommentsCount":49,"snapshotUrl":"","publishedTime":"T08:51:54+08:00","url":"/p/","lastestLikers":[{"bio":"Engineer","isFollowing":false,"hash":"cebc3caafdf5b04fdb0e86eefe19408c","uid":76,"isOrg":false,"slug":"zcranberry","isFollowed":false,"description":"","name":"zcranberry","profileUrl":"/people/zcranberry","avatar":{"id":"9ef26d170","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"py 日语 数据 交易 古龙 飘渺录 龙族 悬疑","isFollowing":false,"hash":"6da2f8703a34ecdda02ca87b2cf9edd5","uid":44,"isOrg":false,"slug":"megyouhan","isFollowed":false,"description":"愿有一精神分裂的自己 \n然后我就再也不孤单了","name":"梦有寒桑","profileUrl":"/people/megyouhan","avatar":{"id":"v2-2ae88cb5cde4b33ef6dfdb3","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"N1居然挂了","isFollowing":false,"hash":"76705cdfa19ad38eb8c47ea","uid":04,"isOrg":false,"slug":"emeng","isFollowed":false,"description":"","name":"emeng","profileUrl":"/people/emeng","avatar":{"id":"9df436818","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"Android Dev","isFollowing":false,"hash":"721a1bbca77ba981c825d8","uid":08,"isOrg":false,"slug":"jiang-chen-77","isFollowed":false,"description":"Blog: https://guang2.github.io","name":"江晨","profileUrl":"/people/jiang-chen-77","avatar":{"id":"5cc34ee38","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"","isFollowing":false,"hash":"694d3f525aef05ed75863","uid":60,"isOrg":false,"slug":"pennylau","isFollowed":false,"description":"","name":"PennyLau","profileUrl":"/people/pennylau","avatar":{"id":"5cfbc184aecbb","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":"两周前因为公司一次裁人,好几个人的活都被按在了我头上,这其中的一大部分是一系列REST API,撰写者号称基本完成,我测试了一下,发现尽管从功能的角度来说,这些API实现了spec的显式要求,但是从实际使用的角度,欠缺的东西太多(各种各样的隐式需求)。R…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/74d97aea6c172cd9f7fec_r.jpg","links":{"comments":"/api/posts//comments"},"topics":[],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"蛰伏中。微信公众号: programmer_life","isFollowing":false,"hash":"2e4d9d422ded9d21f7daccb","uid":40,"isOrg":false,"slug":"tchen","isFollowed":false,"description":"Contact me: /in/tyrchen\nGithub: /tyrchen","name":"陈天","profileUrl":"/people/tchen","avatar":{"id":"30f449a83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"prattle","name":"迷思"},"content":"一次旅行,就像一个梦,虚幻中透着真实。过去的一周,九天时间,我带着全家自驾了加州沿海的几个景区:卡梅尔,一号公路,丹麦村,圣巴巴拉,保罗盖帝中心,洛杉矶以及圣地亚哥。那些抱怨『程序人生』最近光是「程序」,没了「人生」的同学,咱们今天多谈谈风月。(一)请假是半个月前的事,家里人都说你初来乍到(我刚内部换了个Team才一个月),就请一周假是不是不好。我觉得既然入乡就要随俗。老美是典型地工作和生活完全切割的,一个会开到一半一看到点了撂下一句我要去接娃了就开溜,一点不含糊。工作再忙,duang,spring break来了,立马带着孩子消失两周,电话不接,邮件不看的。这点,中国人印度人要差的远。我(前)老板 []是个老美,自然通情达理,不但痛快批了,还问我:就玩一周?是不是太紧了?你完全可以多休几天的啊!整的我热泪盈眶。(二)在美国旅游,交通工具基本上就是汽车(加飞机)。我team里有两个哥们是业余pilot,都超过一年半驾龄,其中一个还在考advanced pilot,就是恶劣天气下也能开飞机的那种。记得之前朋友圈里狂转几个女生开飞机游美东的经历,这在国人看来,是惊世骇俗之举,但在老美这里,并不是太稀奇的事。考个pilot,除了毅力外,就是砸钱,找个本地飞行俱乐部的教练,准备好一到三万美金(飞机每小时的租金 + 教练的陪练费),花个一年左右的业余时间,就能拿到pilot license。如果我再年轻十岁,肯定会考一个,现在不成,家人的反对声太大,怕那玩意不安全,只好把这个念想藏起来。自己开不了飞机的,可以买张机票飞到目的地再租车玩。我们一开始想去西雅图,看看传说中的奥林匹克公园,但四月份似乎西雅图天气都不怎么好,就此作罢。最近听说一个创业公司flightcar提供机场租车服务,把共享经济进一步延伸:长途旅行要在目的地租车,自己的车闲置在机场的停车场还得交停车费,一来一去不少银子。用他们的服务,你自己的车不但不用交停车费,如果被租出去你还可以每天收租子。对于美国这样信用体系完善,车又仅仅是个代步工具的国度,这样的项目还真有市场。扯远了。既然西雅图偏冷,那就干脆往南,沿一号公路一路开到圣地亚哥 —— 这就是我们最终的选择。(三)记得\"Founder at work\"里有一段Jessica Livingston(或者其他什么人)讲起PayPal早年跟欺诈斗争的故事:如果按照大银行们的作派,养个庞大的团队应对欺诈,光这项开支就能让PayPal这样的创业公司入不敷出,再加上问用户要SSN,家庭地址和用户他妈的娘家姓这样的严酷流程会让用户流失殆尽。所以不可能先生们泼冷水说就凭你们,省省吧,没法做起来的。PayPal创始人另辟蹊跷,他们采用的方式是:我认可欺诈带来的风险,但和大银行不同,我假定人人都是好人,我把好人中混进来的大坏蛋收拾掉就可以。所以我不审核,邮件注册就可以;我也不养庞大的团队,通过算法来筛出严重的欺诈。我们一路上的酒店供应早餐的方式与PayPal如出一辙。相比国内或者欧洲的酒店,吃个早餐要报房间号或者出示房卡不同,这里的酒店都是敞开大门,早餐随便享用。在oxnard的酒店吃早餐,几个衣衫褴褛的老者进来拿早餐,准备早餐的墨西哥大妈并未有任何阻止的行为。不知怎的,我就想到了PayPal的这个故事。风险控制不是必须控制到0%才是赢家,风险控制本身也需要支出。按照帕累托法则,最佳的风控手段应该是容许一定程度的并不产生巨大影响的风险的发生。(四)由于带着小宝,我们此行以各种乐园为主:去了迪士尼,乐高乐园,圣地亚哥海洋世界,动物园和野生动物园。老美的旅游经济不像国内,光靠景区门票和天价餐饮谋生。他们把公园「主题」的吸金能力发挥到极限,其代表就是迪士尼。在迪士尼,基本上五六岁到十几岁的小女娃都是各种公主装,衣服加各种配饰配齐了没个七八十美元打不住,一家如果再两三个女娃,那么一进门就一大笔花费。大人们也忙不迭凑热闹,脑袋上顶个米奇耳朵,二十几美金也就没了,再整个优先免排队权,整个和童话人物亲密接触的机会,duang,duang,duang,大把银子就此消失。这是个很好的警示:赚钱方式如此之多,如果只盯着一两种玩法,太局限。推而广之,同样是砸钱,你看看uber运营推广的十八变(这个不多说了,最近朋友圈关于uber的文章也是各种刷屏),再看看滴滴快滴的简单粗暴,高下立现。(五)老美把show文化做到了极限,universal studio / disney那些hot的ride在等待时各种助兴活动(比如说排队等待的小黑屋里擎天柱给你分配接下来的任务)暂且不表,连seaworld看个海狮表演,观众入场后,表演开始前十几二十分钟,都有极其专业的演员上演各种搞笑桥段,让你欢笑连连,本来枯燥的等待时间,变得丰富多彩。这show文化的背后,是对用户体验的极度重视。这和前面说的吸金能力是遥相呼应的。用户掏了钱,把一天交给游乐园,游乐园只有把他们服务爽,才能站着把钱赚了。出来玩,最糟心的就是玩一个三分钟的项目,却在没头没尾的人流中排队等待一个小时。如果排队无法避免,那就想法取悦郁闷(或者愤怒)的用户。在这点上,nexmo(一个短信服务的创业公司)做得很不错。我试用他们的api,发现对unicode支持不好,就没再用。过了几天,一个运营妹纸给我发邮件说「亲,你是不是遇到什么问题了 blablabla」,我就礼貌地回「你们貌似不支持中文,等中文支持好了我再试用」。结果人家回复「可能是我们的文档没表达清楚,unicode需要这么用 —— 一段贴心的代码,手把手教我中文该怎么发」。用户获取的过程不可避免的是个漏斗状。我会再尝试nexmo,也许有一天真正需要时会用他们的服务,正如我几乎肯定还会去迪士尼,seaworld一样。(六)旅程进行了一半,惊闻自己H1B没有抽中,默默地关了邮箱,继续没心没肺地玩。生活就是这样,什么都不是轻而易举。自从来到美国后,每个月都有一些和我息息相关的坏消息。哈佛幸福课有一节说information并不重要,重要的是interpretation,以及之后的perception。这饱含着阿Q精神的积极心理学,确实对我帮助很大 —— 开复老师不是也说:用胸怀接受不能改变的事情嘛。既然生活一定要你如履薄冰,那么,就掐着指头,把每天过得更有意思一些,更阳光一些。有个朋友听说我在圣地亚哥玩了五天还没玩够,惊讶道:那个地方那么好玩么?有空我一定去看看。他在美国已经呆了十多年了,却问这样一个问题。他并非没有旅行的念想,阿拉斯加的冰川,夏威夷的海风,黄石的美景,都听他挂在嘴边,只是,头些年被绿卡和房子拴着努力工作,有了绿卡(当然也有了房)后,觉得北美哪里似乎都唾手可得,反而不曾珍惜。这就跟我在北京住了十多年,三里屯的酒吧,后海的游船,还没有刚刚飘过来管我叫「叔」的小年轻来的熟悉一个道理。(七)看到这,你会发现,自己上当了,这不是游记。[]如果您觉得这篇文章不错,请点赞。多谢!欢迎订阅公众号『程序人生』(搜索微信号 programmer_life)。每篇文章都力求原汁原味,北京时间中午12点左右,美西时间下午8点左右与您相会。. 嗯,玩了一周,回来老板已经变ex了. 想看游记,去我朋友圈看","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T11:26:48+08:00","url":"/p/","title":"南游漫记","summary":"一次旅行,就像一个梦,虚幻中透着真实。过去的一周,九天时间,我带着全家自驾了加州沿海的几个景区:卡梅尔,一号公路,丹麦村,圣巴巴拉,保罗盖帝中心,洛杉矶以及圣地亚哥。 那些抱怨『程序人生』最近光是「程序」,没了「人生」的同学,咱们今天多谈…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":0,"likesCount":0},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/c0c95da9dff897ad133f30a2ca1ef6b7_r.jpg","links":{"comments":"/api/posts//comments"},"topics":[],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"蛰伏中。微信公众号: programmer_life","isFollowing":false,"hash":"2e4d9d422ded9d21f7daccb","uid":40,"isOrg":false,"slug":"tchen","isFollowed":false,"description":"Contact me: /in/tyrchen\nGithub: /tyrchen","name":"陈天","profileUrl":"/people/tchen","avatar":{"id":"30f449a83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"prattle","name":"迷思"},"content":"如今的互联网软件越来越碎片化(micro services),Queue无处不在,服务依赖越来越多,使得软件功能的开发,到软件功能的部署,中间有很长的一段路。这段路,是持续集成(Continuous Integration)和持续发布(Continous Delivery)的基石,一般由devOps包圆了,对从不涉身其中的dev而言,看上去就像ops们用了黑魔法,设了道传送门一样,让写好的代码biu的一下就变成了运行在浏览器,或者手机上的鲜活页面。本着不懂点devOps的dev不是好pm的态度,本文简单讲讲软件发布过程中的两个黑魔法:打包(packaging)和部署(deploying)。我们先看「打包」。打包打包字面上的理解是把你的应用和其依赖的组件组织在一起,以便于分发到目标系统上。客户端软件的时代,如office 97烧录成一个iso(便于刻在光盘上)就是个典型的打包的过程;互联网时代,一个java项目生成 jar,python项目生成 wheel/egg,也是打包的过程。打包的意义在于制作可以重复使用的软件。所有琐碎的活儿都在打包的时候完成了,而在要部署的目标系统上,无需使用源代码,无需处理依赖,无需编译,只要把打包好的软件「安装」好即可。我们知道,在计算机领域,合格的程序员倾向于消除一切重复的工作。打包的过程,实际上是一系列手工操作的合集,因此必然有相应的工具来帮助提高打包的效率。打包软件的元老级人物应该是 make。这个常常伴随C/C++项目的任务管理工具的一大主要用途就是打包。当然,make 接近于 *nix shell 的语法并非人见人爱,于是各个语言都提供语言本身的任务管理工具,如grunt,rake。简单的应用,打包的过程可以很快,因为只需应用本身的编译和依赖处理,秒级就可以完成;但复杂的应用可能需要数个钟头。比如说,一个嵌入式软件需要用 buildroot 把 OS,dependencies 及应用软件深度整合在一个可以直接烧录在硬件的 firmware 里,其 full build 可能需要几个小时。另外一个例子是一个复杂的系统可能会使用 ansible/puppet/chef 这样的工具将多个代码库的不同部分装进不同的 aws ec2 instances 中,安装依赖,配置系统时钟,配置 nginx,supervisor 等等服务,然后把这些 instances 制作成一个个 AMI(Amazon Machine Images),供日后部署之用。这往往也需要耗费半个小时到几个小时的时间。打包的过程中,包括之后部署的过程中,还需要一样东西:资源管理工具。因为,就像 micro service 需要 consul 这样的工具提供服务发现,docker 需要 docker registry 提供 repository 一样,打包好的软件需要一个合适的地方放置,便于版本管理,部署以及可能的回退。这个工具可以是S3,可以是自行开发的 repository,也可以用开源的 archiva 或 artifactory。后两者做java的同学应该有所耳闻。这样的资源管理工具有什么用?以python为例,如果你的软件会打包出很多私有的 egg/wheel 包,这些包无法被公开放置在 pypi 上,那么你可以用 artifactory(或achiva)取代 pypi,成为你 pip install 的 index server。artifactory 上没有的包(公共包)会自动去 pypi 上取。同样的,debian 的 index server,docker 的 registry 都可以用这样的工具构建。部署不少人把「打包」和「部署」两件事混在一起,是因为二者经常在一起执行:打包之后,不待喘息,就立刻部署。但部署的动作其实是独立的,一份打包好的软件,按使用场景,可能会有多种部署。互联网软件的部署,往往是相当复杂的,光线上环境而言,就有开发环境,测试环境,以及生产环境。这还不算生产环境中可能存在的各种版本(提供外部API的同学应该心有戚戚焉),所以,部署往往是比打包更让人头疼的事情。我们举个具体的例子:一个线上的日程系统,运行在 aws 里,主要使用 dynamodb,elasticache,ec2 和 s3。开发环境无需考虑 scaling,以单台服务器承载所有服务,没有 ELB / auto-scaling,数据是线上数据的子集;测试环境有 ELB,服务分布在不同的EC2上,每种服务都有两台服务器做HA,但没有 auto-scaling;线上服务则有 ELB / auto-scaling。一个新功能的开发和集成的过程中,开发环境可能会被部署多次;当集成完成后,系统会被部署到生产测试环境;而测试结束后,系统可以以蓝绿发布(blue green deployment)的方式部署到生产环境;或者,选取一定样本的用户做灰度发布(gated launch 或者 A/B test)。在aws的世界里,部署的主要工具是 cloudformation / elastic beanstalk,因为在打包的过程中,已经通过 ansible/puppet/chef/docker 等生成好了 AMI 或者 docker image;在非aws的世界里,ansible等工具也被用于部署。如之前例子所示,部署主要是做资源的调配。开发环境毋须消耗太多资源,所以分配少一些;生产环境是现金奶牛,必须保证资源的全力供应。当然,部署并不单单是资源的调配,它还是服务的 ochestration(嗯,这词比较高大上)。拿 logging 为例,如何把分散在各个服务器上的日志集中起来用于查询和分析,就是部署的一项任务。蓝绿发布的思想其实比较简单,就是提供两套一样的生产环境(production / staging),通过DNS对流量进行切换。如下图:(图片来自:)当 staging 足够稳定时,可以通过DNS切换,把流量从 production 转入 staging。待稳定后,staging 变为 production,原有的 production 转为 staging,可以被 shutdown,也可以被用于下一个 release。如果使用AWS,可以通过 route53 进行 DNS redirection,或者 ELB 的 auto-scaling group进行蓝绿发布。蓝绿发布的好处是一旦发现问题,可以迅速回滚。灰度发布在王淮的《打造Facebook》一书中有介绍:基本思想就是发布的时候控制发布的人群及其比例。比如先1%的用户,再扩大到5%,直至100%。人群可以根据多种属性来筛选,如:年龄、性别、国家、城市、语言、学历、工作单位等。灰度发布的缺点是如果系统有不可逆的更改,则不能使用;对蓝绿发布而言,可以使用,但是系统不能回滚。关于打包和发布的基础知识,就讲这么些。真正操作起来还是挺复杂的。就拿部署的速度而言,就有很多学问:层层缓存,最小化任务集合,对 full build 和 incremental build 采取不同的优化方式等。有同学可能会有疑问:如果打包和部署都已经自动化了,速度快一点,慢一点又有什么影响?殊不知以互联网的速度,如果你做一次部署要两小时,人家只需要五分钟,那么一天八小时内,你能部署四次,人家最多可以部署九十六次。效率提升差出来一到两个量级后,对开发人员的效率而言,会产生质的变化。如果您觉得这篇文章不错,请点赞。多谢!欢迎订阅公众号『程序人生』(搜索微信号 programmer_life)。每篇文章都力求原汁原味,北京时间中午12点左右,美西时间下午8点左右与您相会。","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T11:24:14+08:00","url":"/p/","title":"从开发者的角度看:打包和部署","summary":"如今的互联网软件越来越碎片化(micro services),Queue无处不在,服务依赖越来越多,使得软件功能的开发,到软件功能的部署,中间有很长的一段路。这段路,是持续集成(Continuous Integration)和持续发布(Continous Delivery)的基石,一般由devOps包…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":0,"likesCount":0}},"annotationDetail":null,"commentsCount":49,"likesCount":987,"FULLINFO":true}},"User":{"tchen":{"isFollowed":false,"name":"陈天","headline":"Contact me: /in/tyrchen\nGithub: /tyrchen","avatarUrl":"/30f449a83_s.jpg","isFollowing":false,"type":"people","slug":"tchen","bio":"蛰伏中。微信公众号: programmer_life","hash":"2e4d9d422ded9d21f7daccb","uid":40,"isOrg":false,"description":"Contact me: /in/tyrchen\nGithub: /tyrchen","profileUrl":"/people/tchen","avatar":{"id":"30f449a83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{},"columns":{"prattle":{"following":false,"canManage":false,"href":"/api/columns/prattle","name":"迷思","creator":{"slug":"tchen"},"url":"/prattle","slug":"prattle","avatar":{"id":"d","template":"/{id}_{size}.jpg"}}},"columnPosts":{},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{}}

我要回帖

更多关于 微信鱼虾蟹游戏规则 的文章

 

随机推荐