[转载]杨建:网站加速--动态应用篇 (上)

--提高性能的同时为你节约10倍以上成本
From: http://blog.sina.com.cn/iyangjian

一, 引子
二,整体结构图
三,系统结构综述
四,环境配置以及底层基础类库
五, Memcache & Mysql 经常使用场景案例
六,更多待续 ......
-----------------------------------------------------------------------------------------

一, 引子

张三丰当初传授传授张无忌太极剑法的时候,刚传授完,就问是否已经忘记。直到张无忌说招式已经忘光了,才算学会。当时很不理解,如今终于明白了,招随心出,不该受到固定招式的限制。总有人问我什么样的架构是否是就好,或者某个颇有名的网站用什么架构,咱们就要用吗?甚至以为直接拿个别人的配置文件或参数,性能就会日新月异。那么请问你知作别人当时为何那么作吗?作架构亦如学太极剑法,也请你学习别人的架构的时候,理解了就忘记掉,由于任何架构自己都有环境局限性,而后用心思考,分析,真正理解你所处的环境,从实际需求出发,你才能作出最适合你应用的架构。

不少人对写server很感兴趣,甚至一开始就直接考虑从底层协议进行优化,我说远还没到那个程度,请找出你真正的瓶颈。优化必定是从宏观到微观的,有时候一个系统结构,业务逻辑,或策略的优化,带来的效果甚至更为可观。对于天天请求处理量小于100亿的系统,我都不建议本身去写server。若是说写高性能server的技能是我手中的那把太极剑的话,那么发挥出太极剑法的威力并不依赖于那把剑,而是取决于对心法的理解。那把剑既然能够是专用server,固然也能够是开源的那几款经典server,取决于你对设计容量,以及硬件成本,维护成本,时间等多方面的预期。架构自己就是一个取舍的过程。

如今有这样的一个项目,就拿我上一篇文章里提到的自选股来举例吧,用户能够在各个市场里建立本身的多个投资组合,而后在组合里定制本身关注的股票。描述一下环境以及需求。
环境:注册用户小几千万,同时在线预计峰值不到10万。
需求:
1,3G,IM,Mail,Web 任何一个地方以及不一样的主流浏览器更新数据,其余地方当即可见。
2,北京,天津,上海,深圳,任何一个IDC数据更新,其余三个IDC当即可见。
3,各IDC附近用户,获取股票列表的平均响应速度要控制在20ms之内。
4,IDC间专线中断服务不能受影响。
5,IDC内部的相同功能的服务器,容许宕掉一半,服务彻底不受影响。
6,IDC容许宕掉一到两个,受灾IDC 95%的用户服务影响不超过5分钟。
7,另外,须要开发人员可以在这个系统上快速开发,要具有易用性,良好扩展性以及移植性。

基于以上的需求构建的实现,动态应用篇侧重点主要是如何快速响应,同步更新,如何容灾,消除安全隐患,让系统更稳定,如何容易迁移和扩展应用,如何让程序员容易使用这个平台。这个系统性能不是主要关注的问题(10万同时在线,确实比较微量,况且仅有的很少的压力,也经过后面介绍的各类缓存机制转移的差很少了),架构上采用当前主流经典架构(Nginx+PHP Fast-CGI+APC+Mysql+Memcache+LVS+Linux),各层次之间低偶合,每一层都具有单独优化的空间, 随着用户量的增长,我再逐步推出动态应用处理并发和压力方面的文章。


二,整体结构图

[转载]杨建:网站加速--动态应用篇 <wbr>(上)


(实际结构中去掉了中继slave,改为全部slave直连master,这样结构更简单,易于管理和故障恢复。)

三,系统结构综述


此系统主要分为几个低偶合的层次组装而成,具有多IDC分布的特性。从底往上依次为DB层,MC池子层,Nginx+PHP Fast-CGI层,LVS层,而后经过DNS接入用户。每一层都具有良好的扩展性以及灾备能力。php


DNS:html

从大的结构上来讲,此系统分布在4个主要IDC,网通电信各2 机器数量按照网通:电信 12的比例配置,同一运营商下的两IDC机器数量等同。这样在宕掉一个IDC的状况下,能够经过切换DNS,临时访问相近的IDC,达到IDC间灾备。mysql


LVS:nginx

每一个IDC的接入层经过LVS做四层负载均衡。IDC内部任何接入机器宕掉,能够经过failover机制在一分钟内自动摘除。程序员

 

Nginx+PHP Fast-CGI:算法

经过fpm管理PHP Fast-CGI进程,Nginxunix域协议与fpm通讯。转发 *.php的请求以及响应。使用APC做为OP代码加速器,加速php响应。PHP直接与MC池子或Mysql层进行交互。sql

 

MC池子层:shell

每一个IDC内部的多个MC机是做为一个总体来管理的,也就是说,假如你有100个数据,4MC机,那么每一个MC上只会存25个数据,而不会每一个都存100个。好处是,得到了至关于之前4倍的内存池, 增大了缓存命中率。更额外的好处是,减小了,本IDC内部各MC间数据同步的时间开销,使得本IDC任何一服务器更新的数据,同IDC内其余服务器当即可见。也使得IDC之间MC的同步更为节约,每一个IDC一个池子,每一个池子只须要写一份数据。编程

 

MC池子增减机器的问题:若是是传统的hash算法根据key值,将数据平均的hash4台机器上,那么按照新的hash规则,增长一台机器后,将会有近80%的缓存失效,形成大量数据迁移。因此咱们改用Consistent Hashing,首先求出memcached服务器(节点)的哈希值,并将其配置到0232次方的圆上。而后用一样的方法求出存储数据的键的哈希值,并映射到圆上。而后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。若是超过232次方仍然找不到服务器,就会保存到第一台memcached服务器上。它能最大限度地抑制键的从新分布。并且,有的实现还采用了虚拟节点的思想,使得分布更加均匀。由服务器台数(m)和增长的服务器台数(n)计算增长服务器后的命中率计算公式以下:(1 - n/(n+m) ) * 100  ,按这个计算,增长一台机器后还将获得80%的命中率。更较幸运的是这个算法已经被php所支持,能够经过php.ini设置浏览器

 

宕机重启后初始化的问题:若是初始化的数据只从一台DB上得到,那么在高峰期间必将压垮DB,因此,咱们须要将这个压力,分散到本IDC的多台DB上。细节会在基础类库中进行讲述。

 

异地MC池子的数据同步:已经封装在基础类库中,更新MC时指定特定的参数,就会在其余异地MC池子上也进行更新。因此咱们若是在北京添了一个数据,那么在深圳也会马上看到。


DB层:

主从结构集群,主库可切换,从库互为备份。从DB宕掉一台,能够自动跳过,不影响服务,目前经过基础类库实现,之后会采用内部DNS全部从库直接和主库相连,结构简单,方便管理。不过写的数据量相对比较小的,并且咱们容许DB延迟(由MC来保证明时性,或者异地原本就能够接受短暂的延迟),这里将不是问题。

 

 

MCDB写队列:

在专线中断时不影响写操做(读都是本地的,固然也不受影响),并且在更新数据时可以快速返回(由于不须要远程通讯)。

具体来讲就是将MC的异地写,以及DB的写操做封装成直接追加写本地队列文件。每一个机器上有个守护进程,每0.1秒检查一次,有则rename,而后开始将队列数据逐条往深圳数据中心post,同时每成功一条记录当前offset,以便意外宕掉后接着上次的后面处理。(优化点的能够改成批量确认,冒进点的,可使用内存盘优化写速度)。若是线路不通,或者数据中心写失败,post程序将sleep and retry,线路恢复则继续,不会丢数据。这里有个细节须要注意,就是往数据中心同步的时候,尽可能发往同一台机器,也即绑定,以保证序列的顺序,这个保证能够经过对post守护进程的pid取mod来实现,若是绑定的机器出了故障,则应该选定另一台机器绑定,一旦检测到之前的机器好了,则应该切回来,以保证应有的负载均衡。另外,最外层的接入端也一样须要注意这个问题,咱们对lvs作了会话保持,以确保同一用户在至关长的一段时间内,会访问到同一台机器。对于仅仅使用DNS轮询,又没有使用长链接的服务,若是在短期内作数据更新,确定会出现乱序的问题。除非你各服务器时间都很精准,并且,每一个记录写的时候打上精确到ms的时间戳。


而后,深圳数据中心接收到post的数据后,根据消息类型按必定规则的命名分别存放,这样即便增长一种新的消息,也能够方便的使用队列。好比收到MC的消息后,要写三份,分别发往三个IDC,各自不受影响。DB,能够根据不一样的port实例子来建立队列,省得有一个port宕了,影响其余。具体实现细节能够本身调整,一样得有个程序按期(好比每0.1秒,0.01秒也无所谓,对cpu的消耗很是小)检查队列文件存在就rename,而后往master里写。


以上则能保证,DB的master宕掉,或者专线的中断状况下,用户服务基本不受影响,只是异地的同步会延迟一些。额外收获是未来作DB升级调整的时候,能够用队列分流,分别作不一样的处理。

 

祛除安全隐患:
无论你的系统设计的多么完美,疏忽这一条足以至命。
以上设计看起来彷佛已经没什么问题了,各类容灾,异常也基本考虑到了,不幸的是,这个平台并非仅仅给设计者本身使用,若是有个新手使用了 file_get_contents($url);  若是url所依赖的服务器负载太重,那么整个系统都有被拖垮的危险。动态应用的一个铁律就是,凡是依赖本系统外部资源的地方必须加超时限制,尽量的减小依赖。file_get_contents的超时不是很精确,推荐使用curl的库进行封装,能够设置connect超时,也能够设置整个函数的执行时间超时。我通常会设置my_curl();函数的最大默认执行时间为一秒,由于是从内网拉取数据,一秒还拉不到,确定有问题。另外,别忘记了mysql读服务,当某一idc的一台slave链接数满了之后,若是没设置过mysql.connect_timeout=1;那这个IDC的服务会整个被拖垮,由于默认的是60秒。(mysql的写由队列保证,不存在此问题)。

另外,咱们项目还有个特殊性,即,每次拉取用户股票信息时,须要从后台专线到深圳进行身份合法性验证,因为历史的缘由,这个验证系统暂不具有多IDC分别的能力。假设北京的专线断了,虽然用户的数据是没问题,可是验证却通不过,会致使获取数据失败。一样这个验证是要加超时的,另外为了在专线断掉的状况下依然正常提供服务,就须要在得不到验证的状况下,要经过代理的手段到邻近的IDC去验证。同时,咱们也支持另一种验证方式,对于已经经过验证的用户,专线中断一小段时间,好比一小时,服务是能够不受影响。

总之就是尽可能低偶合,少依赖,加超时。另一个维护的安全,则由安全中心把关,我就很少说了。


,环境配置以及底层基础类库

 

PHP网络版环境:IDC之间差别化的Nginx环境变量配置,使得相同的应用程序在不一样的IDC运行时,使用各自IDC内部的MC以及DB等资源。


 

PHP  Shell版环境:

PHP  Shell是指经过命令行方式执行的php脚本程序。

好比/path/php/bin/php test.php

或在php程序第一行加上  

#!/path/php/bin/php 

而后赋予php脚本可执行权限,使做为shell程序运行。

因为 php 做为 shell 运行时,没法继承 nginx 配置的环境变量。因此它必须依赖一个独立的配置文件。

 


因为图片里含有账户等敏感信息,就不在此贴了。

底层基础类库:

底层基础类库,起到粘合剂的做用,将环境配置,服务器资源等所有结合起来,使得这些资源以及配置信息对上层开发人员透明,无须考虑。总的来讲有如下一些功能。

 

1,  两环境融合,完美无缺。 php 网络环境和shell使用同一基础类库,代码无任何一行差别。使得平时编写的php网络程序,以及类库积累,能够方便的直接用来作shell编程,进行复用。具体原理是,类库须要用到配置信息时,先经过if( isset ($_ENV["SERVER_SOFTWARE"]) )变量判断本身是否网络环境,若是是就直接使用配置项好比:_SERVER["DB_stock_host"] ,若不是,则先将配置文件数据项,section名和下面的字段相加转化成 _SERVER["DB_stock_host"] = “m3306_sz_gtimg_cn” ; 跟网络环境一致后,再继续后面操做。

 

[DB_stock]

host = m3306_sz_gtimg_cn

类库目前除支持模拟DNS外,还直接兼容真实域名以及IP地址,方便未来进行数据迁移。之因此没有直接使用 DNS是也有历史缘由的。

 

2,  DB资源的封装。

 

在没有内部DNS的状况下,将DB读写分离,账户选择,链接的创建(包括 什么时候候真正创建链接,创建长链接,仍是短链接,链接的绑定,以及生命周期),负载均衡以及failvoer等封装成对用户透明的以下简单用法:

 

//指定以读(r) (w) 的方式打开一个库。不指定的状况下,默认是”r”方式打开。

$db_r =new MYSQL(testdb,r); 

 

<?

require_once (dirname(__FILE__).'/../mysql.php');

 

$db_w =new MYSQL("test","w");

 

$arr = array(

              "id" => "8",

              "name" => "yangjian8"

           );

 

if( $db_w->insert("test",$arr) )

{

       echo "query ok ...<br>";

}

else

{

       echo "query failed ...<br>";

       echo "errno=$db_w->errno<br> errmsg=$db_w->errmsg<br>";

}

 

echo "read ............<br>";

 

$db_r =new MYSQL("test","r");

$sql = "select * from test";

 

if( $result = $db_r->query($sql))

{

       while ($row = mysql_fetch_array($result, MYSQL_BOTH))

       {

              printf ("id: %s name: %s<br>", $row[id], $row[name]);

       }

       $db_r->free();  //free result. if you not free it,it will auto free at the end of the php script.

}

else   //if not success,you can print the error info.

{

       echo "errno=$db_r->errno<br> errmsg=$db_r->errmsg<br>";

}

 

3,  MC池子资源的封装。

简化MC池子使用方法,支持异地MC数据同步。

//if rset=1,the mc data will sync to other idcs, default 0.

function set($key, $value, $flag, $expire, $r_set=0)

 

<?php

require_once (dirname(__FILE__).'/../memcache.php');

 

$mc = new MC("test");

for($i=0;$i<2;$i++)

{

       $mc->set("key".$i,"value".$i,0,100);

}

 

for($i=0;$i<2;$i++)

{

       echo $mc->get("key".$i);

       echo "<br>";

}

 

DB以及MC的异地写已经封装进去,对上层开发人员来讲都是透明的。

 

 

注意:我在引用头文件时候,都会使用相似require_once (dirname(__FILE__).'/../mysql.php'); 的方法。我暂且管它叫动态绝对路径。它的好处是,

跟相对路径比:当一个头文件被多层引用时,目录结构又不一致,不会找不到。

也不会去搜索全部可能的目录,执行多余的fstat以及open操做。

跟绝对路径相比:若是我将整个项目,包括头文件所有平移到别的目录,不须要挨个修改文件。

固然,你也能够采用另一个方法,将本项目相关的配置信息绝对路径放在一个统一的文件里,而后经过任何一种方法引用那个文件。

相关文章
相关标签/搜索