用Nginx實現Session共享的均衡負載

2016-03-28    分類:作業系統、程式設計開發、首頁精華4人評論發表於2016-03-28

本文由碼農網 – 邱康原創,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

前言

大學三年多,也做個幾個網站和APP後端,老是被人問到,如果使用者多了伺服器會不會掛,總是很尷尬的回答:“哈哈,我們的使用者還少,到了伺服器撐不住的時候,估計都上市了吧”。說是這麼說,但是對於有強迫症的我,這個問題一直迴響在我腦海裡,久久不散啊。如今大四下了,終於有時間來深入瞭解一下這個問題了。

貌似解決大訪問量的方案有硬體和軟體兩個大類的方法,硬體一般比較貴,學生黨就不去考慮了。還是想想怎麼用軟體解決吧。於是乎,Google,Baidu,balabala… 搜到最多的詞就是“均衡負載”,搭配的一般都是Nginx。找到了方向,那就擼起袖子幹活吧。

叢集搭建

首先在vmware12中安裝3臺debain,命名為debian1,debian2,debian3。一路預設就好(其實並不好,後面會說)。

vmware有個問題,一旦視窗獲得焦點,就自動關閉了小鍵盤,導致我設定root密碼的時候輸入為空(它也沒提示)。
後來我想用su命令才發現密碼錯誤,輸入空密碼一樣錯誤,就只有找回密碼了。

對於debian來說,這樣改:在grub介面游標指向待啟動的系統,然後按 e 鍵進行編輯,如圖:

在 quiet 後面加個1(注意要有空格),按F10,你就可以以root身份進入命令列介面的。

這時候就用passwd修改密碼,然後reboot就可以了。

終於開啟了,準備試試網路,發現無法訪問外網,但是windows主機可以,如果一路預設的話不應該出現問題,最有可能就是防毒軟體把vmware的服務程式給關了(裝了360…)。在windows中啟動Vmware的DHCP服務

然後虛擬機器要reboot一下來獲取ip。好了,現在虛擬機器可以訪問外網了。

安裝nginx,才發現根本連不上,一看才發現是老美的源,應該是一路預設惹的禍啊,修改為科大源(我為母校自豪,哈哈)。

vi /etc/apt/source.list

修改為:

deb http://mirrors.ustc.edu.cn/debian/ wheezy main non-free contrib
	deb http://mirrors.ustc.edu.cn/debian/ wheezy-proposed-updates main non-free contrib
	deb-src http://mirrors.ustc.edu.cn/debian/ wheezy main non-free contrib
	deb-src http://mirrors.ustc.edu.cn/debian/ wheezy-proposed-updates main non-free contrib

	deb http://mirrors.ustc.edu.cn/debian-security/ wheezy/updates main non-free contrib
	deb-src http://mirrors.ustc.edu.cn/debian-security/ wheezy/updates main non-free contrib

然後執行這個命令來更新: apt-get update

安裝: apt-get install nginx

啟動:/etc/init.d/nginx start

隨便用一個虛擬機器開啟一個瀏覽器開啟localhost,成功啟動,如圖:

vi用不慣 apt-get install vim 安裝vim報錯:

The following packages have unmet dependencies:
     vim : Depends: vim-common (= 2:7.3.547-7) but 2:7.4.488-7 is to be installed
    E: Unable to correct problems, you have held broken packages.

可見衝突了,解決:

先執行apt-get remove vim-common 解除安裝vim-common

再進行安裝vim,執行 apt-get install vim

找找nginx的根目錄,我們開啟配置檔案(和Apache一樣,配置檔案模組化的,不是一個單獨的nginx.conf)看一看

vim /etc/nginx/sites-enabled/default

中間有一行

root /usr/share/nginx/www;

這就是根目錄啦

修改index.html來區分三臺主機

用ipconfig 分別獲得 ip 地址,在windows中訪問

debian1  http://192.168.182.128/

debian2  http://192.168.182.129/

debian3  http://192.168.182.130/

基礎嘗試

先來一個小例子,以便對均衡負載產生一個直觀的感受吧。

我們把debian1作為主伺服器承擔請求分發的任務,即外部訪問的是debian1,然後debain1把請求傳送給debian2或者debain3,如下圖:

在debian1中修改配置檔案 :vim /etc/nginx/nginx.conf

在http配置項中加入如下

upstream site { 
	      server  192.168.182.129:80; 
	      server  192.168.182.130:80; 
	} 

	server{ 
	    listen 80;  
	    location / { 
	        proxy_pass         http://site;
	    } 
	}

這是選擇的輪詢的模式

儲存重啟nginx。

現在在windows中訪問debian1,http://192.168.182.128/。多次重新整理 可見如下兩圖依次出現:

說明傳送給 debian1 的請求的確是均勻分配到 debian2和debian3了,亦即輪詢。

session共享

上面的例子可以說簡單到沒有什麼實用價值,大型網站一般不可能是純靜態的,一般都涉及到使用者登入的問題,那麼就涉及到session的問題了。你想使用者在A登陸了,A記住了使用者的登入狀態,可是下一次使用者請求被分配到B去了怎麼辦?顯然不可能讓使用者再登陸一次。所以要實現session共享。一般有幾個解決辦法:

  1. iphash,把特定ip傳送給特定主機,就不存在session這個問題了,因為1個使用者對應1臺主機。但是某時刻當來自某個IP地址的請求特別多,那麼將導致某臺負載伺服器的壓力可能非常大,而其他負載伺服器卻空閒的不均衡情況,這就違背了我們負載均衡的初衷。
  2. 搭建redis叢集或者memcached叢集,用叢集自帶的同步方法來幫我們在不同的主機中同步session,這樣就相當於把原來的一份session變成了N分session(有點浪費,哈哈),session的同步就依賴於NoSql叢集的同步了。
  3. 不使用session,換作cookie。但是秉承著防禦性程式設計的原則,我們不能相信使用者輸入,因為cookie可能被禁用,甚至篡改。
  4. 單獨設定一個session伺服器,負載伺服器得到一個sessionid過後,去session伺服器獲得會話狀態,然後根據狀態來響應使用者請求,如果會話狀態為空,則在session伺服器中設定一個會話狀態,然後返回給使用者一個sessionid。

我準備採用方案4,即用debian1作為分發伺服器,同時作為session伺服器(用redis實現),負載伺服器每次都要向分發伺服器請求使用者的session對應的會話狀態,以此決定響應方式。

php 環境搭建

在debain2,3中搭建php環境

先在2中修改

命令 :

apt-get update    #更新源
apt-get install php5		#安裝php5
apt-get install php5-cli		#安裝php5 命令列工具
apt-get install php5-fpm

最後一句就報錯了:

The following packages have unmet dependencies:
	 gnupg : Depends: libreadline6 (>= 6.0) but it is not going to be installed
	   Recommends: gnupg-curl but it is not going to be installed
	 	php5-fpm : Depends: libssl1.0.0 (>= 1.0.0) but it is not going to be installed
         Depends: php5-common (= 5.4.45-0+deb7u2) but it is not going to be installed
         Depends: ucf but it is not going to be installed
         Depends: tzdata but it is not going to be installed
          PreDepends: dpkg (>= 1.16.1~) but it is not going to be installed

搞了好久也沒解決,還是換個fastcgi管理工具吧(回頭再來啃一啃):

apt-get install spawn-fcgi

啟動spawn-fcgi:

/usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -C 5 -u www-data -g www-data -f /usr/bin/php-cgi

說明:

-a : PHP FastCGI 繫結IP地址

-p : PHP FastCGI 指定埠

-u : PHP FastCGI 使用者名稱

-g : PHP FastCGI 使用者組

-f : 指向 PHP5 fastcgi

另外

vim /etc/rc.local

加入上述命令使得它開機自啟

配置nginx的php選項(還是看官網比較好,不要到處亂搜):

location ~ [^/]\.php(/|$) {
	fastcgi_split_path_info ^(.+?\.php)(/.*)$;
	if (!-f $document_root$fastcgi_script_name) {
		return 404;
	}

	fastcgi_pass 127.0.0.1:9000;
	fastcgi_index index.php;
	include fastcgi_params;
}

重啟:/etc/init.d/nginx restart

在根目錄中加入1.php

<?php
	phpinfo();
	?>

訪問,終於成功了,淚奔

接下來對debain3 如法炮製。總算是完成這一步了。

redis 環境搭建

在debain1中搭建redis伺服器

命令:

wget http://download.redis.io/releases/redis-2.8.12.tar.gz
    	tar xzf redis-2.8.12.tar.gz
    	cd redis-2.8.12
    	make

編譯成功,執行:./src/redis-server redis.conf

修改配置 開啟 redis.conf

  • 把 bind 127.0.0.1 修改為 bind 0.0.0.0 即任意主機可以訪問
  • 找到“requirepass”欄位,在後面加上密碼 password

重啟redis伺服器:

./src/redis-cli -h 127.0.0.1 -p 6379 shutdown #關閉
./src/redis-server redis.conf 開啟

這時你會發現如果用redis客戶端直接訪問會報錯

要輸入密碼後在能正常使用,如圖:

在debain2,3中配置phpredis

命令:

apt-get install php5-dev #php開發者工具,後面編譯需要
	    wget https://github.com/nicolasff/phpredis/archive/master.tar.gz
	    tar xvf master.tar.gz
	    cd phpredis-master/
	    phpize
	    ./configure --enable-redis
	    make && make install

然後修改配置:

vim /etc/php5/apache2/php.ini

在Dynamic Extensions 後面新增extension=redis.so

重啟服務:還真沒找到成熟的解決辦法,只有採取笨辦法了

lsof -i :9000  #列出該埠相關資訊,包含PID
	kill -9 pid  # 把上一步顯示出來的pid挨個殺死
	/usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -C 5 -u www-data -g www-data -f /usr/bin/php-cgi  #啟動

測試:在debian3的nginx根目錄新增1.php 程式碼如下:

<?php
	 $redis_host = '192.168.182.128';
	 $redis_port = 6379;
	 $redis_psw = 'password';

	 $redis = new Redis ();
	 $redis->connect ( $redis_host, $redis_port );
	 $redis->auth ( $redis_psw );
	 $redis->set('a',1);
	 echo $redis->get('a');

	?>

結果如下:

可見是成功了,對debian2如法炮製,效果一樣

邏輯實現

負載伺服器檢視客戶端是否帶有sessionid這個引數,如果有,則去session伺服器獲取會話狀態並返回結果,否則產生一個session和會話狀態存入session伺服器並返回sessionid給客戶端。這是一個大概的邏輯輪廓,細節就不討論了,實現如下:

  1. 修改debian2,3的nginx配置檔案使得預設路徑是index.php 而非 index.html,同時刪掉原有的index.html,加入index.php。重啟。
  2. index.php 程式碼如下:
    <?php
    //初始化連線
    $redis_host = '192.168.182.128';
    $redis_port = 6379;
    $redis_psw = 'password';
    $redis = new Redis ();
    $redis->connect ( $redis_host, $redis_port );
    $redis->auth ( $redis_psw );
    $sessionid = ceil($_GET['sessionid']);
    $hostname = 'debian2';//debian3就要改成debian3
    
    if(empty($sessionid)){//沒有sessionid
    	$sessionid = rand(10000000,99999999);//簡便起見產生8位數字作為有效id
    	$status = '已經登陸,由 '.$hostname.' 設定session';
    	$redis->set($sessionid,$status);
    	$data = array('當前站點'=>$hostname,'sessionid'=>$sessionid,'info'=>'這是您第一次登陸');
    echo json_encode($data,JSON_UNESCAPED_UNICODE);
    	exit();
    }
    
    $status = $redis->get($sessionid);
    
    if(empty($status)){//sessionid無效
    	$sessionid = rand(10000000,99999999);//簡便起見產生8位數字作為有效id
    	$status = '已經登陸,由 '.$hostname.' 設定session';
    	$redis->set($sessionid,$status);
    	$data = array('當前站點'=>$hostname,'sessionid'=>$sessionid,'info'=>'這是您第一次登陸');
    echo json_encode($data,JSON_UNESCAPED_UNICODE);
    	exit();
    }
    
    $data = array('當前站點'=>$hostname,'sessionid'=>$sessionid,'info'=>$status);
    echo json_encode($data,JSON_UNESCAPED_UNICODE);
    exit();
    
    ?>
  3. 測試

首次訪問debian1

再次訪問debain1,這裡就出了點問題,不知道為什麼,一直髮送到到debian2,連續嘗試很多次,沒有一次請求到debian3,根本就沒有輪詢啊。但是過了幾分鐘再次訪問debian1

請求就傳送到debian3了,我估計是用了php過後,nginx把一小段時間內的請求傳送到同一主機了,但是一大段時間上還是輪詢的。

但是我換了個瀏覽器過後,又變成每次輪詢了。一頭汗…… 所以這還是和瀏覽器有關的?(暫時搞不定。回頭再看看,先換個瀏覽器)

首次訪問debian1

再次訪問debian1

帶上sessionid首次訪問debian1

帶上sessionid再次次訪問debian1

可見的確是達到了均衡負載同時session共享的目的。

總結

這篇文章寫下來可真是費了些力氣,中間出了好多錯,不過一個一個有耐心的解決掉,最後出來的結果還是令人挺有成就感的。畢竟心裡的一塊大石算是落了。以後有空再嘗試一下其他幾種方法。

PS : 修改配置檔案的時候,一定要先備份再修改,不然出了問題都不能恢復。

更新

啃了好久,終於找到上面包依賴(衝突)的問題了(得感謝我的一位同學)。都是源惹的禍,我當時是直接找了一段程式碼放進source.list, 實際上把wheezy全部改成jessie,就行了,因為我的debian是8.3 而wheezy代表debian7。用了錯誤的源會導致很多的相容問題,趕緊換回來。換回來過後安裝php相關的元件就沒有任何問題了。

本文連結:http://www.codeceo.com/article/nginx-session-load-balanced.html
本文作者:碼農網 – 邱康
原創作品,轉載必須在正文中標註並保留原文連結和作者等資訊。]

相關文章