Pinboard 的 PHP/MySQL 架构

Pinboard 是一个提供在线书签服务的网站,和 Delicious 类似,不同的是 Pinboard 不是免费的,而且是从一开始就收费——采用有趣的渐进式收费,也就是说每增加一个人、后来的人就需多付0.001美元(按照 number of users * 0.001 的公式),这样的收费方式利用了人们的 “趁便宜赶快买,明天会更贵” 的心理,提供了一套独特的收费模式。让 VPSee 惊讶的是他们背后的技术出奇的简单,没有 Fotolog 那种 MySQL 集群+Memcached 集群,也没有 Netlog 那么复杂的数据库切分。在他们的 About 页面上,这位来自罗马利亚的创始人说:

Pinboard is written in PHP and Perl. The site uses MySQL for data storage, Sphinx for search, and Amazon S3 to store backups. There is absolutely nothing interesting about the Pinboard architecture or implementation; I consider that a feature!

数据

1亿6千多万个书签
5200多万个标签
9400多万个 urls
989 GB 的数据

平台

MySQL
PHP
Perl
Ubuntu
APC
Sphinx
Cron jobs
Amazon S3

硬件

服务器 1:64 GB, 主数据库(master),用来存储用户数据和搜索;
服务器 2:32 GB, 备用主数据库(failover master),用来爬 feeds 等一些后台任务
服务器 3:16 GB, web 服务器和从数据库(slave)
另外提一下,他们租用的这三台服务器有两台是从 DigitalOne 租用的,还有一台是从 ServerBeach 租的。

pingboard arch

架构

  • 他们运行的是 Ubuntu 操作系统;
  • 每台服务器上(一共3台)均保留一份整体数据库的拷贝;
  • 网站运行在 16GB 的服务器上,数据库完全放在内存里,页面装载时间提高了10倍;
  • 采用 master-master 数据库架构加上一个只读的 slave,所有写操作都在一个数据库上进行,第二个 master 数据库服务器主要用来计算,比如统计全局链接数,用户统计等;每天晚上数据库用 mysqldump 备份,然后备份的数据以压缩的形式储存在 Amazon S3 上。
  • Perl 脚本用来运行后台任务,比如下载 feeds、缓存页面、处理 email、生成 tag 云标签、备份数据等。他们选择 Perl 的理由是因为自己很熟悉而且有大量的库可以使用。像 “最受欢迎的书签” 这样的功能一般都是在晚上里通过后台的定时任务(cron job)完成。PHP 用来生成 HTML 页面,没有使用任何 templating engine,也没有使用任何框架(framework)。APC 用来做 PHP 缓存,没有用其他缓存技术,Sphinx 用来做搜索引擎。

经验

  • 使用成熟、老掉牙的技术,这样保证网站和程序运行快而且不会因为软件 bug 丢失数据。(VPSee 非常赞同这点,使用简单和可以理解的技术,我们相信技术是拿来用的,不是拿来炫的。)
  • 保持小规模会有趣得多,当你自己亲自提供客服支持和与客户打交道的时候你会发现很有价值;
  • 服务器成本用每 GB 内存(或存储)的价格来衡量,Pinboard 最初使用的是 Linode 和 Slicehost 的 VPS,后来发现 VPS 不够用,随着内存增大 VPS 越来越贵,价格不如独立服务器。(按照 VPSee 的个人经验,低端(<= 4GB)用 VPS 划算、高端(>=16GB)用独立服务器划算。)
  • 按照服务划分服务器,比如 web 服务器就拿来做 web 服务器,最好不要拿来干别的。

Bloglines 的技术选择和经验

VPSee 以前常用 Bloglines 的 RSS 阅读器订阅一些博客,后来改成 Google Reader 用了一段时间,再后来干脆直接在 Mac 上装个 NetNewWire 客户端,并且可以和 Google Reader 同步,非常方便阅读和管理。这篇文章介绍了 Bloglines 创始人 Mark Fletcher 的的一些创业经验、技术选择和架构等,值得学习学习,特别是对一边工作一边创业的小团队来说。

Bloglines 是典型的车库文化,Mark Fletcher 当初是一边干着正式的工作一边开始自己的创业的,硅谷有很多 startup(创业公司)都是这样起步的,这是包括著名高科技风险投资公司 Y Combinator 创始人 Paul Graham 在内的很多创业大师都推荐的创业方式,在确定自己的创业/赚钱想法可行之前保持稳定的收入来源,这样有助于减少创业的风险和压力,毕竟不是每个人都是 Bill Gates,不是每个人都可以放弃大学或工作开始创业并取得成功的。让人惊奇的是,Bloglines 2005年卖给 Ask.com 的时候还不到10个人。遗憾的是,Ask.com 决定将在今年的10月1日关闭 Blogline.

创业经验

  • 激情,因为这辈子大部分时间都会花在工作和事业上,如果对自己所做的事情没有兴趣和激情的话是不可能坚持到最后的;
  • 采用廉价的技术,现在是互联网创业的好时候,硬件和软件(开源)越来越便宜;
  • Keep it simple,保持简单,使用简单的技术并让为用户觉得简单;(把简单的事情做好就是不简单
  • 夜晚工作,使用自己晚上或者周末的时间工作在自己的创业项目上,从亲朋好友那里寻找资金,免费的服务=更少的压力,免费服务 down 几个小时不会有人抱怨;
  • 雇佣一个律师
  • Web services API 是个好东西
  • 寻找帮助(尤其需要找个好的系统管理员)
  • 考虑外包(eLance.com)

软件选择

  • DBJ (http://cr.yp.to), qmail, djbdns, daemontools
  • ClearSilver (web templating package)
  • Berkeley DBs
  • Linux/Apache
  • C/C++/Bash/Python
  • Skiplist data structure(一个数据结构算法)
  • 避免使用 NFS
  • 避免在 MySQL 中使用表级别的锁,因为不 scale

硬件选择

  • 是租用独立服务器还是托管呢?Bloglines 选择了租用,起步的费用更少;
  • 一切为廉价硬件设计,Google 就是个用廉价硬件搭建服务器集群的好例子;
  • eBay 是个买便宜硬件的好地方;
  • APC PDUs;
  • HP ProCurve 很不错;
  • 避免使用 Seagate Ultra-SCSI 硬盘;
  • 一个有好的 ssh 客户端的手机,这样可以在任何地方 ssh 到服务器上。

存储选择

  • 关系数据库 vs. 文件,他们的所有博客文章都采用文件方式存储;
  • RAID vs. Redundant,他们在所有机器上都保留博客文章副本,如果一台机器 down 了不影响访问;
  • Linux software RAID 1,非常稳定。

系统管理

  • 网站服务器采用 DNS round robin,不必设置负载均衡;
  • 采用热备份冷处理,每小时备份,但是到线下再处理数据(比如 RSS 的订阅数可以每天线下统计,不必即时统计);
  • 小心服务器温度,如果硬盘出问题,可能是服务器过热、温度不适造成的。

监测 Linux 进程的实时 IO 情况

作为系统管理员和 VPS 服务商,经常会碰到服务器或者 VPS 磁盘 IO 繁忙的时候,VPSee 通常都会用一些工具来检测,其中一个常用的工具就是自己写的 iotop 脚本,可以很方便看到哪个进程在频繁 IO. 上周五收到一位网友的邮件和留言,问到这篇文章:如何查看进程 IO 读写情况?里的 WRITE 为什么会出现是 0 的情况,这是个好问题,VPSee 在这里好好解释一下。首先看看我们怎么样才能实时监测不同进程的 IO 活动状况。

block_dump

Linux 内核里提供了一个 block_dump 参数用来把 block 读写(WRITE/READ)状况 dump 到日志里,这样可以通过 dmesg 命令来查看,具体操作步骤是:

# sysctl vm.block_dump=1
or
# echo 1 > /proc/sys/vm/block_dump

然后就可以通过 dmesg 就可以观察到各个进程 IO 活动的状况了:

# dmesg -c
kjournald(542): WRITE block 222528 on dm-0
kjournald(542): WRITE block 222552 on dm-0
bash(18498): dirtied inode 5892488 (ld-linux-x86-64.so.2) on dm-0
bash(18498): dirtied inode 5892482 (ld-2.5.so) on dm-0
dmesg(18498): dirtied inode 11262038 (ld.so.cache) on dm-0
dmesg(18498): dirtied inode 5892496 (libc.so.6) on dm-0
dmesg(18498): dirtied inode 5892489 (libc-2.5.so) on dm-0

问题

一位细心的网友提到这样一个问题:为什么会有 WRITE block 0 的情况出现呢?VPSee 跟踪了一段时间,发现确实有 WRITE 0 的情况出现,比如:

# dmesg -c
...
pdflush(23123): WRITE block 0 on sdb1
pdflush(23123): WRITE block 16 on sdb1
pdflush(23123): WRITE block 104 on sdb1
pdflush(23123): WRITE block 40884480 on sdb1
...

答案

原来我们把 WRITE block 0,WRITE block 16, WRITE block 104 这里面包含的数字理解错了,这些数字不是代表写了多少 blocks,是代表写到哪个 block,为了寻找真相,VPSee 追到 Linux 2.6.18 内核代码里,在 ll_rw_blk.c 里找到了答案:

$ vi linux-2.6.18/block/ll_rw_blk.c

void submit_bio(int rw, struct bio *bio)
{
        int count = bio_sectors(bio);

        BIO_BUG_ON(!bio->bi_size);
        BIO_BUG_ON(!bio->bi_io_vec);
        bio->bi_rw |= rw;
        if (rw & WRITE)
                count_vm_events(PGPGOUT, count);
        else
                count_vm_events(PGPGIN, count);

        if (unlikely(block_dump)) {
                char b[BDEVNAME_SIZE];
                printk(KERN_DEBUG "%s(%d): %s block %Lu on %s\n",
                        current->comm, current->pid,
                        (rw & WRITE) ? "WRITE" : "READ",
                        (unsigned long long)bio->bi_sector,
                        bdevname(bio->bi_bdev,b));
        }

        generic_make_request(bio);
}

很明显从上面代码可以看出 WRITE block 0 on sdb1,这里的 0 是 bio->bi_sector,是写到哪个 sector,不是 WRITE 了多少 blocks 的意思。还有,如果 block 设备被分成多个区的话,这个 bi_sector(sector number)是从这个分区开始计数,比如 block 0 on sdb1 就是 sdb1 分区上的第0个 sector 开始。

Linux 下用 smartd 监测硬盘状况

和处理器、内存比较,硬盘是服务器上最慢的子系统、是最容易出现性能瓶颈的地方,也是最脆弱的部分。因为硬盘离处理器距离最远而且访问硬盘要涉及到一些机械操作,比如转轴、寻轨等,而机械是容易出故障的。作为 VPS 服务商和系统管理员来说,最害怕的就是硬盘出毛病,所以监测硬盘的健康状况、提前预警是件很重要的事情。我们 PC 服务器上差不多1.5年都会有硬盘坏掉,坏掉前一点征兆都没有,SUN 服务器上的情况要好得到,很多 SATA/SCSI 硬盘运行了5年都没问题,看样子品牌服务器还是贵得有理由的。VPSee 前段时间看过 Google 发表的一篇论文:Failure Trends in a Large Disk Drive Population 也证实了我们的经历,结论是所有坏掉的硬盘中只有60%可以被 S.M.A.R.T. 检测到,也就是说 S.M.A.R.T. 的测试结果只有60%是正确的,所以我们还不能完全依赖 S.M.A.R.T. 的监测结果。

目前市面上所有的硬盘都具有 S.M.A.R.T. (Self-Monitoring, Analysis and Reporting Technology) 特性,smartmontools 就是利用这一特性监测硬盘的软件包,包含 smartctl 和 smartd 两个程序,前者是前台命令行工具、后者是后台运行程序,smartmontools 不是 Linux 的专利,也支持 BSD, Solaris 等系统。

安装 smartmontools

在 CentOS/Fedora 下安装:

# yum install kernel-utils

在 Debian/Ubuntu 下安装:

# apt-get install smartmontools

使用 smartmontools

在使用 smartmontools 测试之前先检查一下硬盘是否具有 SMART 特性:

# smartctl -i /dev/sda

=== START OF INFORMATION SECTION ===
Device Model:     SEAGATE ST32500NSSUN250G 0741B58YP8
Serial Number:    5QE58YP8
Firmware Version: 3.AZK
User Capacity:    250,056,000,000 bytes
Device is:        Not in smartctl database [for details use: -P showall]
ATA Version is:   7
ATA Standard is:  Exact ATA specification draft version not indicated
Local Time is:    Thu Jul 22 22:39:07 2010 SAST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled

如果上面 SMART support 是 Disabled 状态的话,需要开启 SMART 的支持:

# smartctl -s on /dev/sda

=== START OF ENABLE/DISABLE COMMANDS SECTION ===
SMART Enabled.

检查硬盘状况,如果下面的结果不是 PASSED 的话你需要立刻警觉起来,马上备份所有数据,硬盘随时都可能出问题(不过值得注意的是就算结果是 PASSED 并不意味着硬盘100%就安全,PASS 不能代表没问题,没 PASS 代表一定有问题):

# smartctl -H /dev/sda

=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED

做个快速自检:

# smartctl -t short /dev/sda

=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===
Sending command: "Execute SMART Short self-test routine immediately in off-line mode".
Drive command "Execute SMART Short self-test routine immediately in off-line mode" successful.
Testing has begun.
Please wait 1 minutes for test to complete.
Test will complete after Thu Jul 22 22:51:00 2010

Use smartctl -X to abort test.

执行上面的自检命令后等待一段时间,可以通过下面命令来看进度和结果:

# smartctl -l selftest /dev/sda

=== START OF READ SMART DATA SECTION ===
SMART Self-test log structure revision number 1
Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
# 1  Short offline       Completed without error       00%     20949         -
# 2  Short offline       Completed without error       00%     20947         -

要做长时间自检的话(很耗时,建议放在凌晨时间段做):

# smartctl -t long /dev/sda

查看出错日志:

# smartctl -l error /dev/sda

=== START OF READ SMART DATA SECTION ===
SMART Error Log Version: 1
No Errors Logged

配置 smartmontools

在 CentOS/Fedora 下:

# vi /etc/smartd.conf
# /etc/init.d/smartd restart

在 Debian/Ubuntu 下:

# vi /etc/default/smartmontools
# vi /etc/smartd.conf
# /etc/init.d/smartmontools restart

可以通过修改以上的 smartmontools 的配置文件来定期对硬盘做健康检查,就像给人定期体检一样,体检过了并不代表就没病(很多疾病用体检的设备都查不到),所以这也符合 Google 的硬盘报告所说的情况,所有坏掉的硬盘中只有60%可以被 S.M.A.R.T. 检测到(所有生病的人中只有60%能在体检的时候发现)。

Linux 多核下绑定进程到不同 CPU(CPU Affinity)

上个星期介绍了在 Linux 多核下如何绑定硬件中断到不同 CPU,其实也可以用类似的做法把进程手动分配到特定的 CPU 上,平时在 Linux 上运行的各种进程都是由 Linux 内核统一分配和管理的,由进程调度算法来决定哪个进程可以开始使用 CPU、哪个进程需要睡眠或等待、哪个进程运行在哪个 CPU 上等。如果你对操作系统的内核和进程调度程序感兴趣的话,不妨看看那本经典的 Operating Systems Design and Implementation(Linus Torvalds 就是看了这本书受到启发写出了 Linux),从简单的 Minix 入手,hack 内核是件很有意思的事情,VPSee 以前修改过 Minix 内核的进程调度,学到了内核方面的很多东西。另外推荐一本课外读物:Just for Fun,Linus Torvalds 写的一本自传。

Linux 给我们提供了方便的工具用来手动分配进程到不同的 CPU 上(CPU Affinity),这样我们可以按照服务器和应用的特性来安排特定的进程到特定的 CPU 上,比如 Oracle 要消耗大量 CPU 和 I/O 资源,如果我们能分配 Oracle 进程到某个或多个 CPU 上并由这些 CPU 专门处理 Oracle 的话会毫无疑问的提高应用程序的响应和性能。还有一些特殊情况是必须绑定应用程序到某个 CPU 上的,比如某个软件的授权是单 CPU 的,如果想运行在多 CPU 机器上的话就必须限制这个软件到某一个 CPU 上。

安装 schedutils

在 CentOS/Fedora 下安装 schedutils:

# yum install schedutils

在 Debian/Ubuntu 下安装 schedutils:

# apt-get install schedutils

如果正在使用 CentOS/Fedora/Debian/Ubuntu 的最新版本的话,schedutils/util-linux 这个软件包可能已经装上了。

计算 CPU Affinity 和计算 SMP IRQ Affinity 差不多:

0x00000001    (CPU0)
0x00000002    (CPU1)
0x00000003    (CPU0+CPU1)
0x00000004    (CPU2)
...

使用 schedutils

如果想设置进程号(PID)为 12212 的进程到 CPU0 上的话:

# taskset 0x00000001 -p 12212

计算 SMP IRQ Affinity

前天我们讨论了如何绑定特定的硬件中断到特定的 CPU 上,分散和平衡各个中断到不同的 CPU 上以获取更大性能的处理能力。上篇限于篇幅的关系,没有来得及进一步说明 “echo 2 > /proc/irq/90/smp_affinity” 中的 ”2“ 是怎么来的,这其实是个二进制数字,代表 00000010,00000001 代表 CPU0 的话,00000010 就代表 CPU1, “echo 2 > /proc/irq/90/smp_affinity” 的意思就是说把 90 中断绑定到 00000010(CPU1)上。所以各个 CPU 用二进制和十六进制表示就是:

               Binary       Hex
    CPU 0    00000001         1
    CPU 1    00000010         2
    CPU 2    00000100         4
    CPU 3    00001000         8

如果我想把 IRQ 绑定到 CPU2 上就是 00000100=4:

# echo "4" > /proc/irq/90/smp_affinity

如果我想把 IRQ 同时平衡到 CPU0 和 CPU2 上就是 00000001+00000100=00000101=5

# echo "5" > /proc/irq/90/smp_affinity

需要注意的是,在手动绑定 IRQ 到 CPU 之前需要先停掉 irqbalance 这个服务,irqbalance 是个服务进程、是用来自动绑定和平衡 IRQ 的:

# /etc/init.d/irqbalance stop

还有一个限制就是,IO-APIC 有两种工作模式:logic 和 physical,在 logic 模式下 IO-APIC 可以同时分布同一种 IO 中断到8颗 CPU (core) 上(受到 bitmask 寄存器的限制,因为 bitmask 只有8位长。);在 physical 模式下不能同时分布同一中断到不同 CPU 上,比如,不能让 eth0 中断同时由 CPU0 和 CPU1 处理,这个时候只能定位 eth0 到 CPU0、eth1 到 CPU1,也就是说 eth0 中断不能像 logic 模式那样可以同时由多个 CPU 处理。

Linux 多核下绑定硬件中断到不同 CPU(IRQ Affinity)

硬件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理显然能很好的平衡性能。现在的服务器上动不动就是多 CPU 多核、多网卡、多硬盘,如果能让网卡中断独占1个 CPU (core)、磁盘 IO 中断独占1个 CPU 的话将会大大减轻单一 CPU 的负担、提高整体处理效率。VPSee 前天收到一位网友的邮件提到了 SMP IRQ Affinity,引发了今天的话题:D,以下操作在 SUN FIre X2100 M2 服务器+ 64位版本 CentOS 5.5 + Linux 2.6.18-194.3.1.el5 上执行。

什么是中断

中文教材上对 “中断” 的定义太生硬了,简单的说就是,每个硬件设备(如:硬盘、网卡等)都需要和 CPU 有某种形式的通信以便 CPU 及时知道发生了什么事情,这样 CPU 可能就会放下手中的事情去处理应急事件,硬件设备主动打扰 CPU 的现象就可称为硬件中断,就像你正在工作的时候受到 QQ 干扰一样,一次 QQ 摇头就可以被称为中断。

中断是一种比较好的 CPU 和硬件沟通的方式,还有一种方式叫做轮询(polling),就是让 CPU 定时对硬件状态进行查询然后做相应处理,就好像你每隔5分钟去检查一下 QQ 看看有没有人找你一样,这种方式是不是很浪费你(CPU)的时间?所以中断是硬件主动的方式,比轮询(CPU 主动)更有效一些。

好了,这里又有了一个问题,每个硬件设备都中断,那么如何区分不同硬件呢?不同设备同时中断如何知道哪个中断是来自硬盘、哪个来自网卡呢?这个很容易,不是每个 QQ 号码都不相同吗?同样的,系统上的每个硬件设备都会被分配一个 IRQ 号,通过这个唯一的 IRQ 号就能区别张三和李四了。

在计算机里,中断是一种电信号,由硬件产生,并直接送到中断控制器(如 8259A)上,然后再由中断控制器向 CPU 发送信号,CPU 检测到该信号后,就中断当前的工作转而去处理中断。然后,处理器会通知操作系统已经产生中断,这样操作系统就会对这个中断进行适当的处理。现在来看一下中断控制器,常见的中断控制器有两种:可编程中断控制器 8259A 和高级可编程中断控制器(APIC),中断控制器应该在大学的硬件接口和计算机体系结构的相关课程中都学过。传统的 8259A 只适合单 CPU 的情况,现在都是多 CPU 多核的 SMP 体系,所以为了充分利用 SMP 体系结构、把中断传递给系统上的每个 CPU 以便更好实现并行和提高性能,Intel 引入了高级可编程中断控制器(APIC)。

光有高级可编程中断控制器的硬件支持还不够,Linux 内核还必须能利用到这些硬件特质,所以只有 kernel 2.4 以后的版本才支持把不同的硬件中断请求(IRQs)分配到特定的 CPU 上,这个绑定技术被称为 SMP IRQ Affinity. 更多介绍请参看 Linux 内核源代码自带的文档:linux-2.6.31.8/Documentation/IRQ-affinity.txt

如何使用

先看看系统上的中断是怎么分配在 CPU 上的,很显然 CPU0 上处理的中断多一些:

# cat /proc/interrupts
           CPU0       CPU1
  0:  918926335          0    IO-APIC-edge  timer
  1:          2          0    IO-APIC-edge  i8042
  8:          0          0    IO-APIC-edge  rtc
  9:          0          0   IO-APIC-level  acpi
 12:          4          0    IO-APIC-edge  i8042
 14:    8248017          0    IO-APIC-edge  ide0
 50:        194          0   IO-APIC-level  ohci_hcd:usb2
 58:      31673          0   IO-APIC-level  sata_nv
 90:    1070374          0         PCI-MSI  eth0
233:         10          0   IO-APIC-level  ehci_hcd:usb1
NMI:       5077       2032
LOC:  918809969  918809894
ERR:          0
MIS:          0

为了不让 CPU0 很累怎么把部分中断转移到 CPU1 上呢?或者说如何把 eth0 网卡的中断转到 CPU1 上呢?先查看一下 IRQ 90 中断的 smp affinity,看看当前中断是怎么分配在不同 CPU 上的(ffffffff 意味着分配在所有可用 CPU 上):

# cat /proc/irq/90/smp_affinity
7fffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff

在进一步动手之前我们需要先停掉 IRQ 自动调节的服务进程,这样才能手动绑定 IRQ 到不同 CPU,否则自己手动绑定做的更改将会被自动调节进程给覆盖掉。如果想修改 IRQ 90 的中断处理,绑定到第2个 CPU(CPU1):

# /etc/init.d/irqbalance stop

# echo "2" > /proc/irq/90/smp_affinity

(上面的 echo “2″ 是怎么来的?为什么是 ”2“?请参考这篇:计算 SMP IRQ Affinity)过段时间在看 /proc/interrupts,是不是 90:eth0 在 CPU1 上的中断增加了(145)、在 CPU0 上的中断没变?不断打印 /proc/interrupts 就会发现 eth0 在 CPU0 上的中断数始终保持不变,而在 CPU1 上的中断数是持续增加的,这正是我们想要的结果:

# cat /proc/interrupts
           CPU0       CPU1
  0:  922506515          0    IO-APIC-edge  timer
  1:          2          0    IO-APIC-edge  i8042
  8:          0          0    IO-APIC-edge  rtc
  9:          0          0   IO-APIC-level  acpi
 12:          4          0    IO-APIC-edge  i8042
 14:    8280147          0    IO-APIC-edge  ide0
 50:        194          0   IO-APIC-level  ohci_hcd:usb2
 58:      31907          0   IO-APIC-level  sata_nv
 90:    1073399        145         PCI-MSI  eth0
233:         10          0   IO-APIC-level  ehci_hcd:usb1
NMI:       5093       2043
LOC:  922389696  922389621
ERR:          0
MIS:          0

有什么用

在网络非常 heavy 的情况下,对于文件服务器、高流量 Web 服务器这样的应用来说,把不同的网卡 IRQ 均衡绑定到不同的 CPU 上将会减轻某个 CPU 的负担,提高多个 CPU 整体处理中断的能力;对于数据库服务器这样的应用来说,把磁盘控制器绑到一个 CPU、把网卡绑定到另一个 CPU 将会提高数据库的响应时间、优化性能。合理的根据自己的生产环境和应用的特点来平衡 IRQ 中断有助于提高系统的整体吞吐能力和性能。

VPSee 经常收到网友来信问到如何优化 Linux、优化 VPS、这个问题不太好回答,要记住的是性能优化是一个过程而不是结果,不是看了些文档改了改参数就叫优化了,后面还需要大量的测试、监测以及持续的观察和改进。

MocoSpace 的架构

MocoSpace.com 是一家移动社交网站,有1200多万注册用户,每个月30亿的 PV ,是美国最大的移动社区。我们来看看 MocoSpace 是如何来架构他们的网站的。先来看看他们的统计数据,注意他们只有1个系统管理员,8个程序员,14台服务器(数据和原文来自 MOCOSPACE ARCHITECTURE – 3 BILLION MOBILE PAGE VIEWS A MONTH):

数据

每月30亿 PV
全美第4大流量的网站,继 MySpace, Facebook, Google 之后
75% 手机 Web, 25% Web
1200 万用户
每月600万独立访问
10万在线用户
每月上传1200万照片
每天接受和发送450万 email
8个程序员,2个测试员,1个系统管理员

平台和工具

CentOS + Red Hat
Resin application server, Java Servlets, JavaServer Pages, Comet
PostgreSQL
Memcached
ActiveMQ’s job + message queue,Red Hat 集群做 HA
Squid 静态内容缓存,曾试过 Varnish 但是 Varnish 不稳定
JQuery + Ajax
S3 用来存储用户照片和视频,现在用 Amazon S3 做外部存储是主流,EC2 用来做照片处理
F5 BigIP 负载均衡,用 gzip 压缩所有页面
Akamai CDN,每天 2TB 数据、2.5亿次请求。
Nagios 用来警告,Zabbix 用来监测
EMC SAN 用大量磁盘做 RAID 10 做需要高 IO 的数据库存储,用来替代高性能的 SSD,节省了大量成本
PowerMTA 做邮件传送,用Barracuda 做 spam 和 firewall
Subversion 做源代码控制,Hudson 做 continuous integration
FFMPEG 用来做视频处理
Selenium 用来自动测试浏览器
5x Dell 1950, 2x dual core, 16G RAM(Web 服务器)
5x Dell 6950/R905, 4x dual core, 32G RAM(Web 服务器)
2x Sun Fire X4600 M2 Server, 8x quad core, 256G RAM(数据库服务器)
2x Dell 6950, 4x dual core, 64G RAM(数据库服务器)

架构

他们的网站主要是面向手机应用的,所以他们遇到的一个大挑战是如何让他们的网站在几百种(从最新的 iPhone 到古董级的 Motorola Razrs)不同的手机设备上运行,屏幕大小、缺少相应的 Web 标准等都是问题。他们在几百种不同手机的数据上抽象出了一个表现层,只要用一套代码通过一个手机数据库(包括屏幕大小、允许的文件类型、允许打开的页面大小等)把处理好的页面发到对应的手机上。

他们也是通过 shard 数据库来分担负载的,以用户 key 作为 shard 的依据,通过查找一张全局表来找到用户所在的 shard,他们自己写了查询层,可以用来在不同的 shards 之间自由查询和关联数据。他们 offline 的时候检查数据的一致性,他们认为如果不是做银行系统的话,一致性不是那么重要,牺牲一点一致性来换回性能还是值得的。他们把大表划分成了小表,这样分散了锁表带来的问题。

他们使用多级缓存,从应用服务器里的缓存到分布式 memcached,当需要更新 memcached 的数据的时候,他们通过消息发送给每台应用服务器上的缓存,以做到数据一致。他们的服务器通过分布式消息队列来通讯,比如用户实时通过发消息告诉系统需要更新缓存等。

他们用专门的服务器来打造 social graph,并都放在内存里。

他们用 Kickstart 自动安装服务器,用 Puppet 来配置服务器,web 服务器、数据库服务器、cache 服务器等。

经验

  • 在增加服务器之前先确定现有的服务器硬件还能不能往上升级,可以挑选一些二手的 4U 服务器。
  • 理解瓶颈在那里?是 CPU 还是磁盘、网络 IO?数据库总是有磁盘 IO 问题。
  • 扩展 web 服务器很容易也很便宜,扩展数据库服务器就很麻烦了,找出数据库系统查询最多的、查询执行时间最长的,尽早跟踪和测试这些查询找出数据库性能瓶颈。他们使用 pgFouine log analyzer 和 PostgreSQL pg_stat_statements 工具来测量。
  • 不要让用户等待,尽量在后台处理。避免异步通讯,比如数据等待积累一定程度后再一次提交给数据库;S3 存储的延迟和错误都可能会很大,把失败的请求放在队列里,等队列积累到一定程度的时候再试,而不是失败一个试一个,减少开销。
  • 在设计阶段就考虑监测系统和性能,而不是到了部署的时候才开始监测。他们试过很多监测工具,Cacti, Ganglia, Hyperic, Zabbix, Nagios 等,最重要的是要找到自己用得顺手的工具。
  • 网站变大以后就要做好防黑客、防垃圾的准备。
  • 删除可能会开销很大,尽量软删除,而且用户删错了的话软删除容易恢复。
  • N+1 设计,永远不要少于两种方案。

优化 resolv.conf

DNS lookup 在把域名解析成 IP 过程中耽误了不少时间,尤其是访问比较复杂的网站的时候,比如某个页面包含多个 url,一次请求需要做多次 DNS 解析,并且从用户浏览器到 DNS server,以及 DNS server 和 DNS server 之间都会产生延迟或有可能发生的错误,Google 意识到了这点,作为 Google 雄心勃勃 make the web faster 计划的一部分,Google 去年年底发布了自己的 Google Public DNS,对于一些在我们的 VPS 上使用 vpn 浏览网页的用户,在自己 VPS 里面加入 Google Public DNS 可以加快访问网站的速度(虽然作为用户来说速度差异感觉不明显)。打开 Linux 的 inlcude/resolv.h 文件可以发现里面定义了可以追踪的 DNS server 数目:

$ vi /usr/include/resolv.h

/*
 * Global defines and variables for resolver stub.
 */
# define MAXNS                  3       /* max # name servers we'll track */

所以 /etc/resolv.conf 定义了3个 name server,option rotate 选项指在这3个 dns server 之间轮回查询,option timeout:1 选项设定 lookup timeout 的时间为1秒(默认为5秒),下面是 VPSee 使用的 DNS server 和 resolv.conf 配置:

$ cat /etc/resolv.conf

nameserver 8.8.8.8
nameserver 8.8.4.4
nameserver 4.2.2.2
option rotate
option timeout:1

需要同时设置 noatime 和 nodiratime 吗?

相信对性能、优化这些关键字有兴趣的朋友都知道在 Linux 下面挂载文件系统的时候设置 noatime 可以显著提高文件系统的性能。默认情况下,Linux ext2/ext3 文件系统在文件被访问、创建、修改等的时候记录下了文件的一些时间戳,比如:文件创建时间、最近一次修改时间和最近一次访问时间。因为系统运行的时候要访问大量文件,如果能减少一些动作(比如减少时间戳的记录次数等)将会显著提高磁盘 IO 的效率、提升文件系统的性能。Linux 提供了 noatime 这个参数来禁止记录最近一次访问时间戳。

给文件系统挂载的时候加上 noatime 参数能大幅提高文件系统性能:

# vi /etc/fstab

/dev/sda1        /             ext3     defaults,noatime,errors=remount-ro 0 0
devpts           /dev/pts      devpts   gid=5,mode=620             0 0
proc             /proc         proc     defaults                   0 0
/dev/sda2        swap          swap     defaults,noatime           0 0

修改设置后只需要重新挂载文件系统、不需要重启就可以应用新设置:

# mount -o remount /

# mount
/dev/sda1 on / type ext3 (rw,noatime,errors=remount-ro)
proc on /proc type proc (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)

网上很多资料都提到要同时设置 noatime 和 nodiratime,不知道这个结论来自哪里,其实不需要像设置 noatime 那样设置 nodiratime,最可靠的资料应该是源代码,VPSee 查了一下源代码,发现在内核源代码 linux-2.6.33/fs/inode.c 文件里有一个 touch_atime 函数,可以看出如果 inode 的标记位是 NOATIME 的话就直接返回了,根本就走不到 NODIRATIME 那里去,所以只设置 noatime 就可以了,不必再设置 nodiratime.

void touch_atime(struct vfsmount *mnt, struct dentry *dentry)
1405{
1406        struct inode *inode = dentry->d_inode;
1407        struct timespec now;
1408
1409        if (inode->i_flags & S_NOATIME)
1410                return;
1411        if (IS_NOATIME(inode))
1412                return;
1413        if ((inode->i_sb->s_flags & MS_NODIRATIME) && S_ISDIR(inode->i_mode))
1414                return;
1415
1416        if (mnt->mnt_flags & MNT_NOATIME)
1417                return;
1418        if ((mnt->mnt_flags & MNT_NODIRATIME) && S_ISDIR(inode->i_mode))
1419                return;
...
1435}