用heartbeat實現浮動地址的雙機互備
領導提出來要對一地方門戶網站做熱備,雖然未必真的會實施,但還是有點意思的。
其實做熱備或者說好聽些叫高可用,也就是堆機器,加一台資料庫,做主從方式,網站程序里稍微做些手腳,資料庫的熱備基本也沒什麼問題了,當然要是做網站的能考慮CMS類的,就更好了,不過這些都有專人負責,我就不去管了。怎麼做WEB伺服器的熱備,並且提高訪問速度才是我的臨時工作。
把現在的百兆交換機換成千兆的,這樣的話,單純做WEB服務,一台伺服器應該能夠輕鬆達到200M的流量,而現在的情況是4台WEB加起來也不過只有70M左右的流量,所以用一台伺服器對4台WEB進行緩存加速從帶寬上看是可行的。後面的4台機器每兩台一組做主備,比如域名1主用在WEB1上,備用在WEB2上,域名2主用在WEB2上備用在WEB1上,以此類推,CACHE上做後面的健康監測並進行請求分發。
Cache伺服器內存應該儘可能大,提高性能。
出於Cache伺服器的可用性考慮,準備上兩台伺服器,之間用heartbeat做主備,一個浮動地址,平時在主用伺服器上,主用掛了以後浮動到備用上。如果覺得備用機平時閑著太可惜,也可以在主用上進行一下負載分擔,把緩存功能分擔到主備兩台機器上,這樣帶來的另一個好處就是主備切換的時候,緩存不需要從零開始建立。
大致的結構如下:
找了兩台低端伺服器做實驗,centos4.6,雙網卡,一個接外網,一個用來心跳監測。
兩台機器,機器名分別為cache1和cache2,也就是uname -n看到的,在hosts里加上另外一台機器,以便通過ping cache1和ping cache2能通
安裝heartbeat
為了省事,直接用yum安裝,很方便
yum install heartbeat
裝完heartbeat-2.1.3以後,在/etc/ha.d目錄下編輯一個ha.cf,內容是
use_logd yes
auto_failback on
bcast eth1
node cache1 cache2
crm on
心跳監測通過eth1進行。
編輯一個logd.cf,內容是
logfacility daemon
編輯一個authkeys,內容是
auth 1
1 sha1 mykeystring
把authkeys屬性改成600。
配置文件的其他項目這裡都用默認值了。
也可以從/usr/share/doc/heartbeat-2.1.3下複製這兩個配置文件的樣板來根據需要修改。
為了實現地址浮動,需要在/var/lib/heartbeat/crm目錄下做一個cib.xml,用來描述資源管理。
我是在原來預設的文件上修改的
增加部分為:
上面的主要意思應該也就是加一個IP地址資源,這個IP地址是192.168.11.10,也就是兩個CACHE上的浮動地址,這個浮動地址是在eth0上的。主用是在cache1上。
這個配置是在cache1上的,主用(resource location)在cache1上。
如果做雙地址浮動,可以實現雙機互備(而不是主備),再加一個IP地址資源在cache2上就可以。
cache2上的配置和cache1上差不多,ha.cf,logd.cf,authkeys內容都是一樣的。
配置完成後,在rc.local里寫上/etc/init.d/heartbeat start,然後兩台機器都重啟一下。
這時候可以用crm_mon看一下,我的輸出是這樣的,cache1上:
Last updated: Sat Apr 12 20:12:07 2008
Current DC: cache2 (b3b95984-acb7-4ca6-ab40-c9a6c18e192a)
2 Nodes configured.
1 Resources configured.
============
Node: cache2 (b3b95984-acb7-4ca6-ab40-c9a6c18e192a): online
Node: cache1 (15347a3b-44a4-4eb3-5ab1-e6cf15fab0b4): online
ip_resource (heartbeat::ocf:IPaddr): Started cache1
cache2上的輸出:
Last updated: Sat Apr 12 20:08:13 2008
Current DC: cache2 (b3b95984-acb7-4ca6-ab40-c9a6c18e192a)
2 Nodes configured.
1 Resources configured.
============
Node: cache2 (b3b95984-acb7-4ca6-ab40-c9a6c18e192a): online
Node: cache1 (15347a3b-44a4-4eb3-5ab1-e6cf15fab0b4): online
ip_resource (heartbeat::ocf:IPaddr): Started cache1
這時候主用在cache1上,在cache1上用ifconfig能看到浮動地址。
如果這時候我重啟cache1,來模擬宕機,在cache2上crm_mon輸出
Last updated: Sat Apr 12 20:14:35 2008
Current DC: cache2 (b3b95984-acb7-4ca6-ab40-c9a6c18e192a)
2 Nodes configured.
1 Resources configured.
============
Node: cache2 (b3b95984-acb7-4ca6-ab40-c9a6c18e192a): online
Node: cache1 (15347a3b-44a4-4eb3-5ab1-e6cf15fab0b4): OFFLINE
ip_resource (heartbeat::ocf:IPaddr): Started cache2
這時候在cache2上ifconfig能看到浮動IP已經到了cache2上。
當cache1啟動起來回復正常以後,浮動IP又回到了cache1上。
我的E文很爛,看heartbeat文檔半懂不懂的,特別是cib.xml部分,看了兩個小時一點頭緒都沒有,看著一堆example糊裡糊塗亂寫亂一通,竟然成了,自己都覺得神奇。
接下來的事情就比較簡單了:
在兩台Cache上分別配置相同的varnish和nginx,nginx做前端,承擔日誌記錄和負載分擔功能,把web請求均勻分擔到主備兩台緩存的varnish上,也就是在nginx配置里設置upstream分別為浮動地址和備機真實地址,主備兩台緩存一起為主用伺服器上的nginx提供請求響應。我把主機頭判斷和後台伺服器的主備監測放到varnish上實現。
如果主用伺服器宕機,對外web地址浮動到備用伺服器上,這時候雖然nginx仍然以為自己是在向兩個後端進行請求的負載分擔,但實際是分別發送到備機的真實IP和浮動IP上,其實也就是備機上緩存服務。當主機恢復以後,浮動地址回到主用伺服器上,緩存服務的負載分擔也恢復。
而後端web服務的宕機,由varnish里的restart來實現。這個部分還沒有仔細檢驗過,只是以前玩varnish的時候稍稍試了下。準備下周有空進行試驗。
在保證同等理想的帶寬條件下,進行了一個小測試:從同一個客戶端發起單線程的HTTP請求,大約每次測試取100個以上的頁面,頁面大小大約為3K左右,沒有圖片,都是文本HTML,伺服器端不壓縮,取耗時的平均值。實際上通過HTTP取頁面的過程在單位的網路條件下,網路上的延時可以忽略。
直接訪問原伺服器(IIS6+ASP+SQLServer),平均耗時是902ms/page,這個時間是包括HTTP取頁面以及分析提取頁面中鏈接的過程。基本上每個頁面都要查庫(新聞發布頁面)。
通過cache訪問,在cache建立過程中,也就是cache中沒有被緩存對象時,測得平均耗時是907ms/page。
如果cache里已經有被請求的對象,則平均耗時是135ms/page。
可見緩存對提高WEB響應速度還是非常有幫助的。
《解決方案》
nginx和varnish的詳細配置:
nginx的配置文件如下:
user www www;
worker_processes 2;
error_log /usr/local/nginx/logs/error.log;
pid /usr/local/nginx/var/nginx.pid;
worker_rlimit_nofile 51200;
events
{
use epoll;
worker_connections 51200;
}
http
{
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr $remote_user - [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" ';
log_format timing '$remote_addr $remote_user - [$time_local] $request '
'upstream_response_time $upstream_response_time '
'msec $msec request_time $request_time';
log_format up_head '$remote_addr $remote_user - [$time_local] $request '
'upstream_http_content_type $upstream_http_content_type';
access_log /usr/local/nginx/logs/access.log main;
keepalive_timeout 30;
server_tokens off;
sendfile on;
#tcp_nopush on;
tcp_nodelay on;
upstream bk1 {
server 192.168.111.10:8080;
server 192.168.111.20:8080;
}
server
{
listen 80;
client_max_body_size 50M;
server_name .mysite.cn .mysite.com
index index.htm index.html index.asp index.aspx;
root /www/default;
#error_page 403 500 502 503 504 /404.html;
#limit_conn httplimit 10;
location /status {
stub_status on;
access_log off;
#auth_basic "NginxStatus";
#auth_basic_user_file conf/htpasswd;
}
location /varnish {
alias /www/default/varnish.txt;
default_type text/plain;
}
location /crm {
alias /www/default/crm.txt;
default_type text/plain;
}
location / {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 50m;
client_body_buffer_size 256k;
proxy_connect_timeout 30;
proxy_send_timeout 30;
proxy_read_timeout 60;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
#proxy_next_upstream error timeout invalid_header http_500 http_503 http_404;
proxy_max_temp_file_size 128m;
if (!-f $request_filename) {
proxy_pass http://bk1;
}
}
}
}
Varnish的配置文件如下:
acl purge {
"localhost";
}
backend bk1 {
set backend.host = "192.168.111.110";
set backend.port = "80";
}
backend bk2 {
set backend.host = "192.168.111.111";
set backend.port = "80";
}
sub vcl_recv {
if (req.http.host ~ "mysite.cn") {
if (req.restarts == 0) {
set req.backend=bk1;
}
else {
set req.backend=bk2;
}
}
else {
if (req.http.host ~ "mysite.com"") {
if (req.restarts == 0) {
set req.backend=bk2;
}
else {
set req.backend=bk1;
}
}
else {
error 200 "No cahce for this domain";
}
}
if (req.request == "PURGE") {
if (!client.ip ~ purge) {
error 405 "Not allowed.";
}
else {
lookup;
}
}
if (req.request != "GET" && req.request != "HEAD") {
pipe;
}
if (req.http.Expect) {
pipe;
}
if (req.http.Authenticate ){
pass;
}
if (req.http.Cache-Control ~ "no-cache") {
pass;
}
if(req.url ~ "purge.php"){
pass;
}
if (req.url ~ "\.(htm|html|jpg|jpeg|gif|png|tiff|tif|svg|swf|ico|css|js|vsd|doc|ppt|pps|xls|pdf|mp3|mp4|m4a|ogg|mov|avi|wma|flv|wmv|sxw|zip|gz|bz2|tgz|tar|rar|odc|odb|odf|odg|odi|odp|ods|odt|sxc|sxd|sxi|sxw|dmg|torrent|deb|msi|iso|rpm)$") {
lookup;
}
lookup;
}
sub vcl_pipe {
if (bereq.http.x-forwarded-for) {
set bereq.http.X-forwarded-for =
bereq.http.X-forwarded-for ", "
regsub(client.ip, ":.*", "");
} else {
set bereq.http.X-forwarded-for =
regsub(client.ip, ":.*", "") ;
}
pipe;
}
sub vcl_pass {
if (bereq.http.x-forwarded-for) {
set bereq.http.X-forwarded-for =
bereq.http.X-forwarded-for ", "
regsub(client.ip, ":.*", "");
} else {
set bereq.http.X-forwarded-for =
regsub(client.ip, ":.*", "");
}
pass;
}
sub vcl_hash {
set req.hash += req.url;
if (req.http.host) {
set req.hash += req.http.host;
} else {
set req.hash += server.ip;
}
hash;
}
sub vcl_hit {
if (req.request == "PURGE") {
set obj.ttl = 0s;
error 200 "Purged.";
}
if (!obj.cacheable) {
pass;
}
#set obj.http.X-TTL = obj.ttl;
deliver;
}
sub vcl_miss {
if (req.request == "PURGE") {
error 404 "Not in cache.";
}
fetch;
}
sub vcl_fetch {
if (obj.status >400 && req.restarts == 0 )
{
restart;
}
if (!obj.valid) {
error;
}
if (!obj.cacheable) {
pass;
}
if (obj.status == 404) {
pass;
}
if (obj.ttl < 120000s) {
set obj.ttl = 120000s;
}
if (req.url ~ "\.(html|htm|jpg|jpeg|gif|png|tiff|tif|svg|swf|ico|css|js|vsd|doc|ppt|pps|xls|pdf|mp3|mp4|m4a|ogg|mov|avi|wma|flv|wmv|sxw|zip|gz|bz2|tgz|tar|rar|odc|odb|odf|odg|odi|odp|ods|odt|sxc|sxd|sxi|sxw|dmg|torrent|deb|msi|iso|rpm)$") {
insert;
}
if (obj.http.Pragma ~ "no-cache" || obj.http.Cache-Control ~ "no-cache" || obj.http.Cache-Control ~ "private") {
set obj.http.MYCACHE ="force-cache";
insert;
#pass;
}
insert;
}
sub vcl_deliver {
deliver;
}
sub vcl_timeout {
discard;
}
sub vcl_discard {
discard;
}
[火星人 ] 用heartbeat實現浮動地址的雙機互備的反向web緩存已經有346次圍觀