前言
大學三年多,也做個幾個網站和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用不慣安裝vim: apt-get install 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共享。一般有幾個解決辦法:
- iphash,把特定ip發送給特定主機,就不存在session這個問題了,因爲1個用戶對應1臺主機。但是某時刻當來自某個IP地址的請求特別多,那麼將導致某臺負載服務器的壓力可能非常大,而其他負載服務器卻空閒的不均衡情況,這就違背了我們負載均衡的初衷。
- 搭建redis集羣或者memcached集羣,用集羣自帶的同步方法來幫我們在不同的主機中同步session,這樣就相當於把原來的一份session變成了N分session(有點浪費,哈哈),session的同步就依賴於NoSql集羣的同步了。
- 不使用session,換作cookie。但是秉承着防禦性編程的原則,我們不能相信用戶輸入,因爲cookie可能被禁用,甚至篡改。
- 單獨設置一個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/cgi/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給客戶端。這是一個大概的邏輯輪廓,細節就不討論了,實現如下:
- 修改debian2,3的nginx配置文件使得默認路徑是index.php 而非 index.html,同時刪掉原有的index.html,加入index.php。重啓。
-
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(); ?>
- 測試
首次訪問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相關的組件就沒有任何問題了。
更新 03/30
上面修改root密碼的方法我後來又試了一下,沒成功,原來之所以成功估計是因爲原來我的root密碼正好是空,所以在進入單用戶模式的時候不需要輸入密碼。後來我的密碼不是空,進入單用戶模式一樣要輸入root密碼,自然達不到找回密碼的目的。最好還是用U盤啓動,用U盤裏的系統來修改本機root密碼。
加Java架構師進階交流羣獲取Java工程化、高性能及分佈式、高性能、深入淺出。高架構。
性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的直播免費學習權限
都是大牛帶飛 讓你少走很多的彎路的 對了 小白勿進 最好是有開發經驗
羣號是: 558787436 注:加羣要求
1、具有工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。
3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。
5.阿里Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶着大家全面、科學地建立自己的技術體系和技術認知!