【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

从简单到复杂:大型 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:006007

评论 1 条评论

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

AlertDialog(对话框)详解

智趣匠

android AlertDialog 对话框

软件测试/测试开发丨持续交付-Pipeline入门

测试人

软件测试 自动化测试 测试开发

CMS系统是什么?

源字节1号

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

架构实战营 - 模块五作业(微博评论)

🐢先生

架构实战营

凭借这份阿里2023版Java架构师面试指南,我一周时间斩获了5个Offer!

Java永远的神

程序员 程序人生 后端 架构师 java面试

希望计算机专业同学都知道这些宝藏博主

程序员大彬

自学编程 计算机 计算机专业

软件测试/测试开发丨持续交付-Jenkinsfile 语法

测试人

软件测试 自动化测试 测试开发

AR市场为何频频“呼唤”苹果?

Alter

AR

树状数组模板与练习

timerring

算法

关于如何提升研发效能的一些思考

阿呆

python中进程、线程、协程的实践

阿呆

测试人社区软件测试技术沙龙,基于代码链路分析的白盒精准测试方案

测试人

软件测试 自动化测试 精准测试 测试开发

软件测试/测试开发丨持续交付-Blue Ocean 应用

测试人

软件测试 自动化测试 测试开发

中台的悖论

agnostic

中台

SpringBoot启动之准备系统环境environmentPrepared

石臻臻的杂货铺

spring springboot

开源可观测性平台SigNoz

骑牛上青山

开源 调用链 OpenTelemetry signoz

Bytebase 体验官之狂飙的 ChatGPT

朱亚光

软件测试/测试开发丨MockServer 服务框架设计

测试人

软件测试 自动化测试 测试开发

百度“文心一言”发布两天12家企业签约,申请测试企业破9万

科技热闻

Java泛型介绍

TaurusCode

Java泛型

「百幄」系列 | 在线会议套件,让政企协作更安全高效

融云 RongCloud

通信 办公 政企 数智化转型 在线会议

DUIN开源的镜像更新通知工具

mengzyou

container DevOps image

Golden Gate 发布项目白皮书,测试网络即将上线

股市老人

CPU基础知识详解

timerring

cpu

能快速构建和定制网络拓扑图的WPF开源项目-NodeNetwork

沙漠尽头的狼

开源WPF项目 网络拓扑图

站在工作的角度体验一下文心一言

IT蜗壳-Tango

IT蜗壳 ChatGPT 文心一言 文心一言测试

系统设计的端到端原则

俞凡

架构

简单的文件同步工具:SyncTime激活版

真大的脸盆

Mac Mac 软件 同步文件工具 同步工具

域名备案不备案的区别是什么?

源字节1号

运维 软件开发 前端开发 后端开发 小程序开发

欢迎来到 Python 入门级教程!

阿呆

Python

如何把握未来技术的演进方向

Ethan

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