写点什么

从简单到复杂:大型 Rails 与 VoIP 系统架构与部署实践

  • 2011-09-19
  • 本文字数:4364 字

    阅读完需:约 14 分钟

复杂的系统最初都是从简单开始的。本篇是我们团队关于 Rails 系统重构、测试与部署系列文章的最后一篇。在此与大家分享一下我们在系统部署与维护方面的一些经验,希望大家批评指正。

回顾

2008 年初,我加入一个 Rails 团队—— Idapted 。Idapted 是国内最早的 Rails 团队之一,带头人 Jonathan Palley 颇有创新和冒险精神,他一个人写了几乎全部早期的系统原型,包括但不限于 Rails 后台、VoIP 客户端和 Flex 前端等等。

我刚刚加入团队时,面对眼花缭乱的技术简直不知所措。对我来说一切都是全新的。我作为一个系统管理员,职责是管理三台服务器:一台在国内,主要是用作 HTTP 代理;两台在国外,分别是 SVN 和 Rails 应用。当然,当时只一个 Rails 系统,与数据库都在一台服务器上。

业务简介

在详细介绍之前,我先来简单说明一下公司的业务,以便于读者更好地理解与之相关的技术。我们主要的业务是做一对一的在线英语口语培训。老师在美国,学生主要在中国。学生学习的过程一般分为三个阶段:预习、一对一连线和复习。预习和复习阶段主要是使用 Flex 呈现课件内容(那时候还没有 HTML5),连线阶段也使用 Flash,只不过增加了 VoIP 应用。在美国,老师使用基于 SIP 的 VoIP 客户端;而在中国,学生则可以通过电话或手机、Skype、Google Talk 等与老师连线实时对话,通话过程全程录音。老师除了在通话过程中实时纠正学生的发音和语法外,还会在在录音的相关位置做上标记和反馈,学生就可以在复习时掌握这些内容。

架构

业务决定架构。最初我们也把 Rails 部署到国内的服务器上,但这样美国的老师访问起来就很慢。所以后来我们就把 Rails 移到美国,而国内的服务器就只作 HTTP 反向代理;另外美国的 VoIP 环境比国内要好,让它靠近 Rails 服务是理所当然的。

随着 Rails 项目的代码越来越多,我们决定将系统拆分成三个部分:Admin、Trainer 与 Student,分别负责管理员功能、老师平台及学生。VoIP 后台也由开源的 Asterisk 换成了当时比较年轻的 FreeSWITCH 。同时我们也把系统迁移到了新的美国服务器上:Database + VoIP + Rails。

工具

Rails 系统使用的是典型的 Nginx + Mongrel + MySQL,部署使用 Capistrano 。为了更方便的部署系统我基于 Capistrano 写了一个小工具,可以通过类似cap admin/trainer/student deploy方式部署系统。后来随着系统被拆分的越来越多,我们又发现了一个好的工具 Webistrano

为了方便测试,我们在北京的办公室放了两台 PC 服务器,使用 Xen 虚拟化技术,虚拟机与生产系统的服务器一一对应,并使用真实数据进行测试,保证测试环境与生产环境的表现完全一样。同时我们也把 SVN 服务器移到了办公室,这样提交代码就快多了。

迭代

接下来随着业务的发展,我们对 Rails 系统进行了更进一步的拆分,最终产生了数十个 Rails 应用,《From 1 to 30: How to Refactor 1 Monolithic Application into 30 Independently Maintainable Applications》便是我们在RailsConf 2010 的演讲主题。

在拆分过程中我们也尝试了好多不同的架构和部署方案,有成功的喜悦也有失败的惨痛经历。其中一件事情让我印象深刻:拆分后,各Rails App 之间的通信就主要靠共享只读数据库和HTTP Rest Service(大部分使用 Rails 中的ActiveResource 实现),而当时我们也正在尝试基于 Phusion Passenger 的部署方式。因为我们的系统和 Passenger 都存在 BUG,因此整个经常莫名奇妙地失去反应。最后我们得出的教训就是:当代码和运行环境都不够稳定的时候,定位问题往往需要花费大量的时间,而这是可以避免的。

总体架构

后来,在 Idapted 被 Eleutian Technology 收购以后,我们把代码仓库从 SVN 迁移到了 github 。同时也把服务器迁移到了一个新的数据中心。最后的架构如下图:

技术架构

下面来详细谈一下我们系统的架构和使用的技术,以下基本都是来自我们的实际经验。至于运行环境,所有 Rails 都是运行在 Ubuntu Linux 上,我们只使用 LTS 版,如 8.04 和 10.04。Rails2 的应用使用 Ruby Enterprise Edition 1.7,Rails3 则使用 Ruby 1.9。

反向代理

最后我们选择了 Nginx + Unicorn 的方式。Nginx 现在几乎已经成为事实上的标准了,而 Unicorn 更是非常优雅,它不仅稳定高效,而且可以很方便的添加和减少进程。不管起多少个进程,都只占用一个 TCP 端口,非常方便与 Nginx 联合部署。

为避免 Javascript 跨域请求安全性问题,以及使所有的 Rails App 看起来协调统一,我们将所有 App 部署在同一个域名下不同的子目录中:

复制代码
upstream app1 {
app1.lan:3010;
app2.lan:3010
}
upstream app2 {
app1.lan:3020;
}
upstream app3 {
app1.lan:3030;
}
location /app1 {
include /usr/local/nginx/conf/proxy_headers.conf;
proxy_pass http://app1;
}
location /app2 {
include /usr/local/nginx/conf/proxy_headers.conf;
proxy_pass http://app2;
}
location /app3 {
include /usr/local/nginx/conf/proxy_headers.conf;
proxy_pass http://app3;
}

静态内容与文件服务器

我们使用 Squid 在国内服务器对普通的静态内容做缓存。另外,我们建立了自己的文件服务器用于存放用户上传文件,大部分是录音(录音也是使用统一的上传文件接口)。文件服务器上的文件会实时备份到 Amason S3。

对于文件服务器的缓存,我们使用了 Nginx 的 cache + sendfile 功能。如下图,国内服务器 N1 收到 GET 请求后 (1),使用 http_proxy 请求国外服务器上的 Rails 应用进行鉴权 (2)。鉴权通过后 Rails 返回 sendfile HTTP 头 (3)。N1 则在本地缓存中查找对应的文件,如果存在则直接返回文件 (4-1),如果不存在,它会再发出一个 http_proxy GET 请求 (4-2) 到国外服务器上的 Nginx(N2),N2 返回文件到 N1,N1 将文件发送给用户同时缓存在本地。

配置如下:

复制代码
location /file {
include /usr/local/nginx/conf/proxy_headers.conf;
proxy_pass http://file-rails-server:60020;
}
location /file-internal {
internal;
proxy_set_header X-Real-IP $remote_addr;
proxy_store on;
proxy_store_access user:rw group:rw all:r;
proxy_temp_path /tmp/nginx_temp;
alias /home/app/shared/file-internal;
proxy_set_header X-debug1 $request_filename;
proxy_set_header X-Uri $uri;
proxy_set_header Host www.idapted.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
if (!-f $request_filename) {
proxy_pass http://file-nginx-server;
}
}

国外服务器上的情形和这个类似。由于服务器空间有限,录音文件又会占用很大的空间,因此我们会定期删除服务器上长时间未访问的文件,如果偶尔有人访问的话,则通过我们自己编写的一个 idp_proxy 实时从 S3 上取回。

VoIP 架构

如前所述,VoIP 平台使用的是 FreeSWITCH,采用 CentOS 5.5 双机冷备份,并在一台服务器上启动多个 FreeSWITCH 实例,以支持 Skype、Google Talk 等。详细的内容请参考这篇Blog

Erlang

除 Rails 外,我们在好多地方也使用了 Erlang。Erlang 是一种函数式语言,除了我们喜欢它的简洁和优雅,更重要的是,FreeSWITCH 有一个原生的 Erlang 接口,它最初就是用来处理电信业务的,用它控制 VoIP 再适合不过。

在我们系统中,Erlang 开发的程序类似于一种中间件,它位于 FreeSWITCH 及 Rails 系统之间,通过原生接口与 FreeSWITCH 通信,与 Rails 通信则通过 HTTP REST 接口及共享数据库。另外,我们的 VoIP 客户端也使用一个基于 TCP 的简单协议与 Erlang 通信。

监控与分析

监控总是最关键的一点。如果你不知道你的服务器在干什么,你就不知道该怎么做。我们使用 monit、munin 及 nagios 进行监控,通过短信、Email/IM 等方式接收事件通知。

另外,我们也自己开发了一些监控与分析系统,如 VoIP 行为的关联分析,通过对系统日志的分析及协议的跟踪,来帮助我们分析通话质量问题及断线原因。

其他技术

Flex

所有 Flex 代码都在服务器上编译,避免由不同开发者不同版本的编译器编译的模块加载时出现问题。

虚拟化

由于 Xen 的发展不明朗,我们在新的生产环境中使用了 LXC 及 KVM 虚拟化技术。两者只是在不同的服务器上进行简单的负荷分担,在虚拟化方面我们没有进行更多地研究。

我们还使用了其它常用及不常用的技术,如 memcache iwatch 等, 在这里就不费笔墨一一赘述了。

小结

系统架构是一门很深的学问,部署和维护也同样重要。我最初也没有太多经验,所有经验都是在不断的开发,维护中不断学习和积累起来的。当然,从失败和错误中学习是最快最好的方法。以下是我们总结的几点经验:

谨慎使用最新的技术

敏捷往往与激进联系起来,Rails 和 FreeSWITCH 发展很快,我们总是使用最新的版本,保证我们总是能使用那些最新的特性。当然,前面已经说过,当环境和代码都不稳定时,查找 Bug 的难度要增大好多倍。所以我们在操作系统及 HTTP 服务器的选择上又比较保守,这样就保证在敏捷的同时又最大限度的相对稳定。

测试环境尽量与生产环境一致

许多错误在测试阶段没有发现,都是由于测试环境与生产环境不一致引起的。因此,我们会花相当多的时间保证测试环境与生产环境在软件版本上保持一致,甚至,我们还使用虚拟化技术使网络拓扑结构保持一致,比如用虚拟机模拟物理服务器使其在数量上保持一致,并使用真实数据进行测试。

结对操作

我们不仅在开发阶段实施结对编程,在重要的部署维护阶段也是双人结对操作。人都是会犯错误的,我就曾经在单独操作时点错了按钮而几乎犯下大错。因此,我们通过引入双人结对操作的方式,最大限度地降低人为因素在部署维护阶段的影响,对重要操作的定义也更为严格。

从错误中学习,让开发人员也参与进来

我们会把系统故障及处理过程记下来,做成 Case 供大家学习,并在每周一次的 Code Review 时间与开发人员交流。每个人都知道系统架构与维护并不完全是架构师和管理员的责任,能够开发出易于部署和维护的系统更是开发人员的责任,好的系统是整个团队紧密合作的结晶。

让管理层理解系统维护的重要性

作为系统维护人员,最郁闷的可能就是系统不出问题。这可不是开玩笑。一般来说,系统不出问题是由于系统维护工作做的好;但管理层也许会误认为管理人员整天没事做。所以,处理好工作与老板的关系是一门艺术,也是团队成功的关键。

关于作者

杜金房,前 Eleutian Technology(前 Idapted)核心技术架构师,主要负责系统底层架构及 VoIP 系统开发。业余时间创办了 FreeSWITCH-CN 。曾任职烟台电信 / 网通,负责交换机及网管系统维护工作。

高超,Eleutian Technology(前 Idapted)高级网络工程师,负责系统底层架构与监控系统开发与维护。


:本文是 idapted 公司 Rails 系列技术文章的第三篇,前两篇分别为《Rails 系统重构:从单一复杂系统到多个小应用集群》《如何进行高效的 Rails 单元测试》

2011-09-19 00:006163

评论 1 条评论

发布
用户头像
杜老师的这篇分享,这么多年后看都是十分精彩的
2021-02-07 20:26
回复
没有更多了
发现更多内容

用简单的描述带你理解运算放大器

矜辰所致

运算放大器 6 月 优质更文活动

ModelBox实战开发:RK3568实现摄像头虚拟背景

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 企业号 6 月 PK 榜

华为云黄瑾:做强坚实数据底座,GaussDB与产业携手共进

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 6 月 PK 榜

观点碰撞燃爆会场|2023开放原子全球开源峰会区块链分论坛圆满落幕

开放原子开源基金会

区块链 开源 开放原子全球开源峰会 开放原子

基于双层缓存(DLC)机制解决热点缓存并发重建问题

xfgg

Java' 6 月 优质更文活动

618夏日“折”学家活动上线!开通表盘会员解锁百变腕间风格

最新动态

【参考设计】2KW AC/DC数字电源方案

元器件秋姐

设计 电路 方案 电源 数字电源

LED透明屏和LED玻璃屏的区别

Dylan

分辨率 视频 图像 屏幕亮度 LED

数字化转型的“整体规划”有多重要?

优秀

低代码 数字化转型 数字化

【零售电商系列】走进亚马逊之三方物流与售后

小诚信驿站

6 月 优质更文活动

强化学习从基础到进阶-常见问题和面试必知必答[1]:强化学习概述、序列决策、动作空间定义、策略价值函数、探索与利用、Gym强化学习实验

汀丶人工智能

人工智能 深度学习 强化学习 深度强化学习 6 月 优质更文活动

赋能中国软件,共筑开放生态|2023开放原子全球开源峰会软硬协同开源分论坛成功举办

开放原子开源基金会

开源 开放原子全球开源峰会 开放原子 软硬协同开源

GPT-4满分通过MIT本科数学考试!这套提示词火了

Openlab_cosmoplat

算法 ChatGPT

四大技巧,教你如何加速手动UI测试

龙智—DevSecOps解决方案

UI测试 用户界面测试

如何在 Apifox 中正确指定 query 参数?

Apifox

Java 程序员 Apifox API 接口工具

可观测性最佳实践 | 警惕!未知的风险正在摧毁你的系统

观测云

可观测性 运维监控 观测云 云原生可观测 可观测性用观测云

龙智携手Atlassian亮相DevOps国际峰会:释放团队潜力,以协作挑战不可能

龙智—DevSecOps解决方案

DevOps ITSM ITSM软件 工作管理

Typora绿化版

源字节1号

开源 软件开发 前端开发 后端开发 小程序开发

华为云邓明昆:云原生时代,以开源赋能数字化转型

华为云开源

开源 云原生 数字化

NFTScan | 06.12~06.18 NFT 市场热点汇总

NFT Research

热点 NFT\

大会预告 | 2023云边协同大会将于6月30日在京召开

信通院IOMM数字化转型团队

边缘计算 AIOT 分布式云 云边协同

强化学习从基础到进阶-案例与实践[1]:强化学习概述、序列决策、动作空间定义、策略价值函数、探索与利用、Gym强化学习实验

汀丶人工智能

人工智能 深度学习 强化学习 深度强化学习 6 月 优质更文活动

Seata Saga 模式快速入门和最佳实践

阿里巴巴云原生

阿里云 云原生 seata

即时通讯技术文集(第17期):社交软件红包技术专题 [共12篇]

JackJiang

网络编程 即时通讯 IM

优化开发工作流的三大实用技巧,助力效率提升

龙智—DevSecOps解决方案

版本控制 版本管理

openGemini:顺势而动,蓄势而发

华为云开源

数据库 开源

从简单到复杂:大型Rails与VoIP系统架构与部署实践_Ruby_杜金房_InfoQ精选文章