V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
FarBox
V2EX  ›  程序员

分享FarBox.com的技术构架

  •  
  •   FarBox · 2013-01-17 18:55:08 +08:00 · 9607 次点击
    这是一个创建于 4088 天前的主题,其中的信息可能已经有所发展或是发生改变。
    原文地址, 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。
    - 反正,就勇敢地冒险吧!
    28 条回复    2014-08-01 16:10:23 +08:00
    zythum
        1
    zythum  
       2013-01-17 18:59:48 +08:00
    看上去很高级的样子
    jason52
        2
    jason52  
       2013-01-17 19:00:02 +08:00
    楼主,我的test.farbox.com最近上不去了。肿么回事啊。
    zxsky1
        3
    zxsky1  
       2013-01-17 19:03:30 +08:00
    高端,等我都能读懂的时候再详细读一下。
    levon
        4
    levon  
       2013-01-17 19:19:27 +08:00
    对很多人来说,这些都不实用,一个网站成功与否主要不在部署上
    FarBox
        5
    FarBox  
    OP
       2013-01-17 19:41:30 +08:00 via iPhone
    @jason52 能打开的呀。

    @levon 同意。善用工具工具很重要,这些技术方案在解决我们已经碰到的问题。:)
    qiongqi
        6
    qiongqi  
       2013-01-17 19:48:21 +08:00
    mark, 虽然是写php的
    summer
        7
    summer  
       2013-01-17 21:46:33 +08:00 via iPhone
    感觉lz着魔了。
    xiaojay
        8
    xiaojay  
       2013-01-17 21:59:58 +08:00
    很好的服务,很好的分享。
    会不会加入sns元素,类似tumblr?
    twm
        9
    twm  
       2013-01-17 22:19:30 +08:00 via iPad
    mk
    koon_kai
        10
    koon_kai  
       2013-01-17 22:51:18 +08:00
    LZ的技术架构很不错,很值得参考学习,正在用farbox的服务。
    feihu
        11
    feihu  
       2013-01-17 23:07:27 +08:00 via iPhone
    一个小建议:讲这个的时候能否加一个成本进去,这样看完后也能知道大致情况。
    subdragon
        12
    subdragon  
       2013-01-17 23:12:22 +08:00
    create_instance应该是libcloud的函数?
    lepture
        13
    lepture  
       2013-01-17 23:26:38 +08:00 via iPhone
    pylint + pep8 建议换作 flake8。这应该是在编码时就做的事,而不是事后的弥补。

    如果你使用版本控制工具,可添加 precommit hook ,在提交之前做好校验。
    ameba
        14
    ameba  
       2013-01-17 23:40:17 +08:00
    rookie 各种读不懂
    ipconfiger
        15
    ipconfiger  
       2013-01-18 00:44:44 +08:00
    创建站点后没有默认的站点文件生成,很是不爽
    liuhang0077
        16
    liuhang0077  
       2013-01-18 03:03:19 +08:00
    看完了 不错 可以试试我们的香港vps 对速度提升有一定作用
    sarices
        17
    sarices  
       2013-01-18 08:51:49 +08:00
    ...这个网站没有退出功能
    yyai3
        18
    yyai3  
       2013-01-18 09:41:17 +08:00
    很用心,值得学习~ 感谢
    zzcflying
        19
    zzcflying  
       2013-01-18 19:09:48 +08:00
    楼主。mango的性能我一直不敢恭维呀。为什么不用redis。
    coosir
        20
    coosir  
       2013-01-18 23:48:52 +08:00
    @ipconfiger 有的啊,就是反应比较慢
    内容存在网盘是我以前一直渴望的,可是文章的分类标签等操作感觉还不是很灵活
    skyahead
        21
    skyahead  
       2013-01-18 23:55:19 +08:00
    请问farbox多少用户/流量??
    ledzep2
        22
    ledzep2  
       2013-01-19 11:28:23 +08:00
    用python的人口味都惊人的一致啊.
    goinaction
        23
    goinaction  
       2013-01-19 14:35:07 +08:00
    来两张图也许更直观:D
    jo32
        24
    jo32  
       2013-01-27 23:43:19 +08:00 via iPad
    Great work!
    jinwyp
        25
    jinwyp  
       2013-01-28 00:46:15 +08:00
    学习
    m
        26
    m  
       2013-01-29 03:39:03 +08:00
    @FarBox 我放了一个普通zip文件在网站根目录,但下载下来储存的文件名成了一串sha1

    另外我注意到http://farbox.com/pages/price 里价格按照请求数计算,那我现在在哪能看到我的farbox的请求数呢?
    hit9
        27
    hit9  
       2013-04-13 14:16:12 +08:00
    很好的服务,希望加入社区化功能
    tylr
        28
    tylr  
       2014-08-01 16:10:23 +08:00
    @FarBox 请问免费用户两个月必须付费才能继续使用吗?5刀一年价格倒是不贵,但请问能提供支付宝支付途径吗?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3190 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 13:04 · PVG 21:04 · LAX 06:04 · JFK 09:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.