原文地址,
http://blog.farbox.com/post/farbox-structure 格式看起来可能会好一些。
## WEB服务器
### 分布式
- 我们的数据库使用的是MongoDB,是单独的服务器,可集群;
- 文件存储使用的是Amazon S3。
如此一来,真正迎接WEB端的流量纯粹就是堆服务器了,并且可以将WEB服务器堆在地球上的任一个角落,只要能连上网即可。
一台Web服务器中,由是由以下的一些技术方案组成的:
- Nginx,直面访客,并且处理掉一些垃圾请求;
- Gunicorn + Gevent,跑WEB APP的服务器,基于Gevent,并发能力较强;
- memcached, 自动缓存一些渲染(计算)好的结果,可以有效避免重复的请求与计算。
> btw, MongoDB的数据,我们使用的是AWS的弹性存储(EBS),(同区)定期备份数据,还不定期的跨区备份数据。
### WEB框架
WEB框架,我们使用了Flask,对应的模板引擎是Jinja2。
其实,我们之前最熟悉的是Django。但它太厚重了,已无法满足我们的需求。
我们需要在一个既有的框架上,补充与修改不少东西。比如FarBox的模板呈现,真实的数据存在于MongoDB中,而实际运行中,则会编译后存于内存,整个loader的逻辑,是我们自己重写的。
像`flask.g` `flask.request`这些proxy性质的方式,也让我们能按照自己的逻辑,对一些函数进行分类、分离,并能最后按需汇总。
而Jinja2对比Django的模板语法,更加pythonic;重要的是,它提供的沙盒环境,可相对有效的保证用户代码是可信任的。
所有的这些,如果使用Django,估计想死的心都有了。当然,不能否认的一点,Flask开始的时候,要自己写不算少的大大小小的组件,也是有点痛苦;虽然可以用一些别人的扩展,但感觉会是overhead,干脆就自己重写。
btw, MongoDB的驱动,我们使用原生的pymongo,而不是一些(伪)ORM的包。但我们也尽可能把所有的query都集中在一个文件中处理,因为这也涉及到了MongoDB的索引,分离太开,后期的维护会很痛苦。如此一来,完全没有必要使用ORM,同时还能减少query过程中的overhead.
- - - - - - - - - -
## 第三方API同步服务器
FarBox还需要主动同步第三方(比如Dropbox)上的数据,如果是图片,还会做一些额外的处理,比如exif信息读取,图片效果的微量优化等。我们希望这个过程也是能分布式的,因为很有可能同步工作会超出一台服务器所能承受的极限。
这时候,想当然要用到队列服务了。但是这次,我们没有选择既有的工具,而是直接自己重写了一套为FarBox定制的。其实也很是简单,就是把所有的任务颗粒度降低到单文件,并且根据实际情况(比如以account为一个group、API请求限制等等)来并发处理,以及处理各个worker之间可能的锁定、释放。
做这样的决定,是因为我们依赖的技术方案中,有两个基础:`MongoDB`+`Gevent`。
如此一来,同步服务器也可以多台分布进行工作,因为数据库是唯一、集中的。
其实,目前还存在的问题是,当多个连接连到MongoDB的时候,一来可能存在一定程度的不同步;二来,如果没有对多台同步服务器进行有效分派,有可能会产生MongoDB内部的读写冲突。但也不是什么大的问题,到时候遇到了,再解决也好。
我们使用`supervisor`(一个python写的进程管理工具)来控制同步脚本,同时,也将`supervisor`设置为开机启动的一个服务。
- - - - - - - - - -
## 错误跟踪与测试
### 错误跟踪
- [sentry](
https://github.com/getsentry/sentry)
> Disqus出产的工具,可以跟踪服务器运行过程中的异常。
> 我们并没有使用他们提供的在线服务,而是自己直接搭在一台服务器上。考虑可能哪天犯二,单bug但大批量地被捕获,费用就很高了,或者阻塞了其它正常需捕获的……
- Google Analytics
> 但我们在程序中处理的异常,sentry是不会捕获的;这些异常通常是404、403、500等页面。
> 对异常的处理,其逻辑也有可能是错误的;所以,我们这里又用到了Google统计的事件捕获脚本。
### 测试框架
其实,我们偷懒了,或者说时间不够。目前还没有写过一个测试用例,但慢慢这块需要补上。我们会使用两个技术方案,nose + tox,另外可能会上pylint + pep8.
我们前期开发的过程中,常见的情况就是修改完bug --> 直接部署 ---> 继续有bug --> 再修改再部署。
Web端,我们依赖于Gunicorn,可以不停止服务,平滑的重启服务。命令如下行,即能平滑重启:
sudo kill -HUP `cat /tmp/gunicorn.pid`
- - - - - - - - - -
## CDN服务
如论如何优化速度,国外的服务器,在面对静态文件、图片的处理时候,硬伤就出现了。
这方面,我们使用@
Livid 同学的[ORCA](
http://orca.io),目前来看,不能算极其稳定,但很过得去;并且价格极其厚道。
其实,我们也有考虑过一些不限流量的云端服务,比如香港的某云,美国的某云;但什么都不限制的,感觉总是不靠谱的。
另外,我们目前的流量并不算大。足以应对。
最重要的是,`自建服务`,是很凶残的方式,伤人伤己。我们深信不疑。
- - - - - - - - - -
## 自动部署
开始的时候,觉得人肉部署就好了, 省下自动部署的麻烦。
但慢慢地,人肉部署,在重复的劳作中,积累起来要消耗掉不少时间;而且还是有部署出错的概率。
另外,配置服务器,让我们崩溃了。所以,我们使用[Fabric](
http://fabfile.org)来实现自动部署。
以下的脚本,可以让我们自动创建一个AWS的EC2服务器,并且完成生产环境的部署。
:::python
instance = create_instance(ec2_type='m1.small', tag='farbox-web', security_groups=['WEB']) # 创建服务器
env.host_string = instance.public_dns_name
cook('instance_init') # init
cook('git') # 安装git以及相应的key
cook('packages') # 安装一些packages
cook('web_server') # 安装nginx等web服务器需要的软件
cook('web_supervisor') # 安装supervisor以及相应针对web的配置
hostname('farbox-web') # 修改hostname
yes,解放了!
等真需要新上一台服务器的时候,不用手工敲键盘了!
### [不?]停机维护
在自动部署实现之前,有一次,我们在优化MongoDB的连接池,需要重启下服务器。然后说,`1分钟`后回来,结果是个把小时后回来……
这个感觉有点丢人。
现在,在真的需要停机维护的时候,我们采用这样的方案:
1, 新建一台Web服务器,并配置好 --> 自动的
2,新建一台数据库服务器, 并配置好 --> 自动的
3,克隆一份已有的数据库盘,mount到新服中
4,切换(就是换个IP,基本上属于无缝的)
5,在旧服中完成升级 --> 自动的
6,一切顺利,再切换回来
如此一来,基本上不需要停机了。AWS的云端,在这个时候彰显了力量。
不过,随着数据的增加,这个流程要消耗的时间也会增加。
- - - - - - - - - -
## DNS
### DNS加速
DNS是一件非常重要的事情。别人访问你服务器内容的第一步是`域名解析`,然后才会被指向某个IP所对应的服务器。
DNS的优化有两个方面。一是DNS本身的优化,但并不算太重要,因为一般客户端都会做DNS的缓存;然仍存在优化的余地的,如果我们DNS解析是在国外服务器的,那么算上连接时间,单次查询会在0.5秒左右,而国内的DNS解析一般只要0.05秒左右。
我们比较看重DNS另外一方面的作用,就是节点加速以及负载均衡。负载均衡,不言而喻,比如现在有2台服务器,IP分别为A与B;那么第一次DNS查询返回A,第二次DNS查询返回B,或者平均概率随机返回A、B中的一个IP,这样就能实现WEB层次的负载均衡。
最能能起到加速的作用其实用DNS来自动匹配各个节点。比如现在的情况是:`两台服务器,I号位于纽约,II号位于香港`,那么某个IP是来自大陆的,它走哪个节点速度最快呢?肯定是香港。这个时候DNS起到的作用就是根据IP的来路,匹配最近的服务器节点。一般情况下,通过这个优化,能加快0.3秒左右的速度。
### DNS防范
这个防范本身是有特指的,至于指向哪个,不言而喻。
域名被封,这个基本无能为力。所幸的是,FarBox本身的结构是去中心化的,即使FarBox被完全封掉,也不会对其它的站点产生影响(甚至其它站点也能提供FarBox的服务);某个用户的域名被封,也同样仅影响其自身。
我们真正需要的是防止被封IP。所以,DNS记录的TTL(Time To Live)一般都设置在5分钟以内。以及绑定域名是通过CNAME的形式。
> 关于这段,其实挺让人心酸的。
### FarBox现状
我们目前并没有启用GEODNS,而是托管了两套DNS记录,国内的走DNSPOD,国外的走AWS Route53.
AWS Route53上有比较基础的GEO DNS的功能,但仅仅是匹配大区的,比如东亚、美国西部等等,而没有匹配到国家。
如果规模允许,我们会自建DNS服务;同时,我们也会考虑提供域名托管的服务,这样就再也不用用户自己去改DNS记录了。
Any way,DNS方面确实有可能自建服务,但取决于规模;否则效益太低。
- - - - - - - - - -
## 小心坑
- 我们把gevent从0.13.6升级到1.0rc2的时候,虽然之前听一些朋友说会有坑,但实际情况很稳定,比老版本要稳定很多。
- 当我们把pymongo从2.3升级到2.4的时候,结果遭遇了一个大的BUG,查询基本失败,后来找到原因,给他们的开发者报完BUG后,老老实实降级。
- Flask的路由配置中,int\float是不支持负数的,这个翻werkzeug的源码才发现的;结果呢,实际生产环境中,我们拒绝了所有西半球时区的用户(虽然现在也没有几个)……
- 因为小小的代码洁癖,结果导致bug; 因为一个机制的变更,结果不停导致bug; 因为设计逻辑与底层冲突,还是停不了的bug。
- 反正,就勇敢地冒险吧!