不通过应用程序而纯粹从简单服务器配置。。。transparent to your apps

1. 用 apache mod_rewrite 实现根据用户ip自动选择镜像

这是一个非常简单的实现–如果用户来自中国,自动重定向到位于中国的服务器,否则转到海外的服务器。并且自动检测服务器状态,如果一个服务器倒了所有traffic都会转到另外的server.

在 apache config 里加一个 virtual host:

    <virtualhost *:80>
        ServerAdmin webmaster@dummy-host.example.com
        DocumentRoot /hosting/balance
               ServerName balance
    <filesmatch "\.(php|htm|html|pl|asp)">
        Allow from all
     </filesmatch>

        RewriteEngine on
    RewriteLog rewrite.log
    RewriteLogLevel 9
        RewriteMap    lb      prg:/hosting/balance/ip_prg.php
        RewriteRule   ^(.*)$ ${lb:$1} [P]
        CustomLog "logs/balance_access.log" combined
        ErrorLog "logs/balance_error.log"
    </virtualhost>

then I have a simple php script ip_prg.php (as configured in apache shown above) to do the redirection:

while( $url = trim(fgets(STDIN)))
{
     
        $protocol="http";
        if (strtolower($_SERVER["HTTPS"]) == "on")
        {
                $protocol="https";
        }
        $status_www = intval(file_get_contents("/2way/mirror1"));
        $status_www1 = intval(file_get_contents("/2way/mirror2"));



        if ($status_www == 0 || $status_www1 == 0){
                if (isFromChina() && trim($status_www) == '0'){
                        $server = "mirror1.domain.com";
                }else if (! isFromChina() && trim($status_www1) == '0'){
                        $server = "mirror2.domain.com";
                }else if (! isFromChina() && trim($status_www) == '0'){
                         $server = "mirror1.domain.com";
                }else if (isFromChina() && trim($status_www1) == '0'){
                        $server = "mirror2.domain.com";
                }
        }else {
              //site not avilable.. redirect to some notice or matenace pagehere
             ...
        }




         $address = $protocol."://".$server.($_SERVER["REQUEST_URI"] != '' ? $_SERVER["REQUEST_URI"] : '');
         if(fwrite(STDOUT, $address."\n" ) === FALSE)
         {
                                log2( "can't write fff");
         }
}

重定向的好处是随后的traffic不再经过load balance server.

视乎需要,也可以用 mod_proxy 来proxy request达到对用户透明,而不用显式重定向。

但是如果用proxy的话,专门的reverse proxy 软件更容易。 下面说说用varnishd 来实现类似功能:

2. 用varnishd proxy 作load balance或者根据ip分配流量

以前简单介绍过varnishd 这个reverse proxy. 比较起squid之类的老软件来, varnish一是在性能上有不少提高,而且非常灵活–配置是通过vcl语言编写的script而非传统的配置文件。

简单vcl进行round robin load balance 的例子:

/* defined backends */
backend mirror1 {
  .host = "mirror1.example.com";
  .probe = {
                .url = "/";
                .interval = 5s;
                .timeout = 1 s;
                .window = 5;
                .threshold = 3;
  }
}

backend mirror2 {
  .host = "mirror2.example.com";
  .probe = {
                .url = "/";
                .interval = 5s;
                .timeout = 1 s;
                .window = 5;
                .threshold = 3;
  }
}

sub vcl_recv {
  remove req.http.X-Forwarded-For;
  set    req.http.X-Forwarded-For = client.ip;

  if (req.requst.host ~ "^(www.)?domain.com") {
         set req.backend = bal;
  }
  if (req.request == "GET" && req.url ~ "\.(jpg|jpeg|gif|ico|html|htm|css|js|pdf|xls|vsd|doc|ppt|iso)$") {
    lookup;
  }
  if (req.http.Expect) {
    pipe;
  }
  if (req.http.Cache-Control ~ "no-cache") {
     pass;
  }
....

用client ip来决定的话, 需要安装一个geoip varnish plugin ( http://github.com/cosimo/varnish-geoip ), 然后vcl可以改为:

include "/etc/varnish/geoip.vcl";
/* define backends mirror1 and mirror2 */
...
/* now the vcl_recv() callback */
sub vcl_recv {
 remove req.http.X-Forwarded-For;
 set    req.http.X-Forwarded-For = client.ip;

   C{
     vcl_geoip_set_header(sp);
   }C

  if (req.http.host ~ "^(www.)?domain.com$") {
       if (req.http.X-Geo-IP ~ "China") {
           set req.backend = mirror1;
       }else {
           set req.backend = mirror2;
       }
  }

  if (req.request == "GET" && req.url ~ "\.(jpg|jpeg|gif|ico|html|htm|css|js|pdf|xls|vsd|doc|ppt|iso)$") {
    lookup;
  }
  if (req.http.Expect) {
    pipe;
  }
  if (req.http.Cache-Control ~ "no-cache") {
     pass;
  }
....

最后一段代码大意如是, 没测试过。。。嗯, 懒:)