博文大綱:
1、MemCache簡介php
- 一、協議
- 二、事件處理
- 三、存儲方式
- 四、通訊分佈式
- 五、memcached的應用場景
- 六、memcached應用中的工做流程
- 七、memcached的一致性Hash算法
2、部署LNMP動靜分離&&memcache緩存服務器- 一、環境準備
- 二、部署Nginx服務器
- 三、部署PHP服務器
- 四、部署MySQL數據庫
- 五、部署Memcached服務器
- 六、部署memcache客戶端
- 七、使用 memcache 實現 session 共享
- 八、測試memcached緩存數據庫
MemCache 是一個自由、源碼開放、高性能、分佈式的分佈式內存對象緩存系統,用於動態Web應用以減輕數據庫的負載。它經過在內存中緩存數據和對象來減小讀取數據庫的次數,從而提升了網站訪問的速度。 MemCaChe 是一個存儲鍵值對的 HashMap,在內存中對任意的數據(好比字符串、對象等)所使用的 key-value 存儲,數據能夠來自數據庫調用、API調用,或者頁面渲染的結果。MemCache 設計理念就是小而強大,它簡單的設計促進了快速部署、易於開發並解決面對大規模的數據緩存的許多難題,而所開放的 API 使得 MemCache用於 Java、C/C++/C#、Perl、Python、PHP、Ruby 等大部分流行的程序語言。
另外,說一下爲何會有 Memcache 和 memcached 兩種名稱?其實 Memcache 是這個項目的名稱(也時它客戶端的名稱),而 memcached 是它服務器端的主程序文件名。html
memcached是一個鍵/值系統,系統相對於MySQL簡單不少,雖然MySQL也有緩存,可是數據庫的SQL解析會耗費性能,查詢慢於memcached,另外MySQL的緩存設計得更加複雜,由於要考慮事務,日誌,存儲引擎等模塊,它的性能也沒有memcached好。前端
memcached只作一件事情,簡單高效,在cache上比MySQL強,這應該容易理解。mysql
memcached做爲高速運行的分佈式緩存服務器,具備如下的特色:linux
- 協議簡單;
- 基於libevent的事件處理;
- 內置內存存儲方式;
- memcached不互相通訊的分佈式。
memcached的服務器客戶端通訊並不使用複雜的XML等格式,而使用簡單的基於文本行的協議。
所以,經過telnet也能在memcached上保存數據、取得數據。nginx
libevent是個程序庫,它將Linux的epoll、BSD類操做系統的kqueue等事件處理功能封裝成統一的接口。即便對服務器的鏈接數增長,也能發揮O(1)的性能。memcached使用這個libevent庫,所以能在Linux、BSD、Solaris等操做系統上發揮其高性能。算法
爲了提升性能,memcached中保存的數據都存儲在memcached內置的內存存儲空間中。因爲數據僅存在於內存中,所以重啓memcached、重啓操做系統會致使所有數據消失。另外,內容容量達到指定值以後,就基於LRU(Least Recently Used)算法自動刪除不使用的緩存。memcached自己是爲緩存而設計的服務器,所以並無過多考慮數據的永久性問題。sql
memcached儘管是「分佈式」緩存服務器,但服務器端並無分佈式功能。各個memcached不會互相通訊以共享信息。那麼,怎樣進行分佈式呢?這徹底取決於客戶端的實現。數據庫
1)數據庫的前端緩存應用:讓它來分擔數據的併發壓力,當數據更新時,可使程序通知緩存進行更新
2)session會話共享的共享存儲vim
它是一種內存緩存,可經過API的方式讀取內存中緩存的這些數據,當用戶須要讀取數據時,會首先訪問memcached緩存,若是緩存中有數據就直接返回給前端的應用程序,若是沒有,再轉發給後臺端的服務器,這時服務器除了返回數據給用戶,就會將數據更新給memcached緩存。
若是實際生產環境中,緩存服務器須要重啓(或者斷電),那麼緩存中的數據將會丟失,那麼這時替換的服務器併發壓力會擴大,可能會致使引入的服務器也跟着停機,沒法提供服務,那麼這時咱們的處理流程是這樣的:
首先從負載均衡中將WEB應用停掉- - - >讓負載均衡再也不轉發數據給WEB - - >接着啓動緩存服務器- - - - > 經過程序把數據庫的內容初始化到緩存服務器中- - - - >而後將網頁應用啓用- - - - >重啓數據庫服務器
一致性 Hash 算法經過一個叫作一致性 Hash 環的數據結構實現 Key 到緩存服務器的 Hash 映射。簡單地說,一致性哈希將整個哈希值空間組織成一個虛擬的圓環(這個環被稱爲一致性Hash 環),如假設某空間哈希函數 H 的值空間是 0~2^ 32 -1(即哈希值是一個 32 位無符號整型),整個哈希空間以下:
下一步將各個服務器使用 H 進行一個哈希計算,具體可使用服務器的 IP 地址或者主機名做爲關鍵字,這樣每臺機器能肯定其在上面的哈希環上的位置了,而且是按照順時針排列,這裏咱們假設三臺節點 memcache經計算後位置以下:
接下來使用相同算法計算出數據的哈希值 h,並由此肯定數據在此哈希環上的位置。假如咱們有數據 A、B、C、D、4 個對象,通過哈希計算後位置以下:
根據一致性哈希算法,數據 A 就被綁定到了 server01 上,D 被綁定到了 server02 上,B、C在 server03 上,是按照順時針找最近服務節點方法。
這樣獲得的哈希環調度方法,有很高的容錯性和可擴展性:
假設 server03 宕機:
能夠看到此時 C、B 會受到影響,將 B、C 節點被重定位到 Server01。通常的,在一致性哈希算法中,若是一臺服務器不可用,則受影響的數據僅僅是此服務器到其環空間中前一臺服務器(即順着逆時針方向行走遇到的第一臺服務器)之間數據,其它不會受到影響。
考慮另一種狀況,若是咱們在系統中增長一臺服務器 Memcached Server 04:
此時 A、D、C 不受影響,只有 B 須要重定位到新的 Server04。通常的,在一致性哈希算法中,若是增長一臺服務器,則受影響的數據僅僅是新服務器到其環空間中前一臺服務器(即順着逆時針方向行走遇到的第一臺服務器)之間數據,其它不會受到影響。
綜上所述,一致性哈希算法對於節點的增減都只需重定位環空間中的一小部分數據,具備較好的容錯性和可擴展性。
一致性哈希的缺點:在服務節點太少時,容易由於節點分部不均勻而形成數據傾斜問題。咱們能夠採用增長虛擬節點的方式解決。
更重要的是,集羣中緩存服務器節點越多,增長/減小節點帶來的影響越小,很好理解。換句話說,隨着集羣規模的增大,繼續命中原有緩存數據的機率會愈來愈大,雖然仍然有小部分數據緩存在服務器中不能被讀到,可是這個比例足夠小,即便訪問數據庫,也不會對數據庫形成致命的負載壓力。
下載我提供的源碼包,並將對應的源碼包上傳至各個服務器。
[root@nginx ~]# yum -y erase httpd #卸載自帶的httpd服務 [root@nginx ~]# yum -y install openssl-devel pcre-devel #安裝所需依賴 [root@nginx ~]# cd /usr/src [root@nginx src]# rz #上傳Nginx源碼包 [root@nginx src]# tar zxf nginx-1.14.0.tar.gz [root@nginx src]# cd nginx-1.14.0/ [root@nginx nginx-1.14.0]# ./configure --prefix=/usr/local/nginx --user=www --group=www && make && make install #進行編譯安裝 [root@nginx nginx-1.14.0]# useradd -M -s /sbin/nologin www #建立運行用戶 [root@nginx nginx-1.14.0]# ln -sf /usr/local/nginx/sbin/nginx /usr/local/sbin/ #對命令作軟連接 [root@nginx nginx-1.14.0]# nginx #啓動Nginx服務 [root@nginx nginx-1.14.0]# netstat -anput | grep 80 #肯定80端口在監聽 #如下是配置nginx與PHP服務器關聯 [root@nginx nginx-1.14.0]# cd /usr/local/nginx/conf/ [root@nginx conf]# vim nginx.conf #編輯主配置文件 #在server{ }字段中添加如下內容 location ~ \.php$ { root /var/www/html; #指定PHP服務器的網頁存放路徑 fastcgi_pass 192.168.20.3:9000; #指定PHP服務器監聽端口 fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi.conf; } #更改完成後,保存退出便可 [root@nginx conf]# nginx -s reload #重啓使更改生效
[root@php ~]# rz #上傳源碼包libmcrypt及php-5.6.27 [root@php ~]# tar zxf libmcrypt-2.5.7.tar.gz -C /usr/src #解包 [root@php ~]# tar zxf php-5.6.27.tar.gz -C /usr/src #解包 [root@php ~]# yum -y install libxml2-devel openssl-devel bzip2-devel #安裝依賴 [root@php ~]# cd /usr/src/libmcrypt-2.5.7 #切換至解壓後的路徑 [root@php libmcrypt-2.5.7]# ./configure --prefix=/usr/local/libmcrypt && make && make install #編輯安裝 [root@php libmcrypt-2.5.7]# cd ../php-5.6.27/ #進入PHP源碼包解壓後的路徑 [root@php php-5.6.27]# ./configure --prefix=/usr/local/php5.6 --with-mysql=mysqlnd --with-pdo-mysql=mysqlnd --with-mysqli=mysqlnd --with-openssl --enable-fpm --enable-sockets --enable-sysvshm --enable-mbstring --with-freetype-dir --with-jpeg-dir --with-png-dir --with-zlib --with-libxml-dir=/usr --enable-xml --with-mhash --with-mcrypt=/usr/local/libmcrypt --with-config-file-path=/etc --with-config-file-scan-dir=/etc/php.d --with-bz2 --enable-maintainer-zts && make && make install #編譯安裝PHP #接下來爲調整PHP的配置文件及控制服務的啓停 [root@php php-5.6.27]# cp php.ini-production /etc/php.ini #複製源碼中中提供的PHP配置文件 [root@php php-5.6.27]# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm #複製其服務控制腳本文件 [root@php php-5.6.27]# chmod +x /etc/init.d/php-fpm #賦予執行權限 [root@php php-5.6.27]# chkconfig --add php-fpm #添加爲系統服務,以便支持systemctl管理 [root@php php-5.6.27]# chkconfig php-fpm on #開啓 [root@php php-5.6.27]# cp /usr/local/php5.6/etc/php-fpm.conf.default /usr/local/php5.6/etc/php-fpm.conf #複製php-fpm提供的默認配置文件並編輯它 [root@php php-5.6.27]# vim /usr/local/php5.6/etc/php-fpm.conf #修改一下,進行優化 listen = 192.168.20.3:9000 #監聽地址是本機的IP9000端口 pm.max_children = 50 #最大啓動的進程數 pm.start_servers = 5 #初始啓動進程數 pm.min_spare_servers = 5 #最小空閒進程 pm.max_spare_servers = 35 #最大空閒進程 #修改完成後,保存退出便可 [root@php php-5.6.27]# systemctl start php-fpm #啓動PHP服務 [root@php php-5.6.27]# netstat -anput | grep php-fpm #確認其9000端口已啓動 [root@php ~]# mkdir -p /var/www/html #建立其網頁存放目錄,須和nginx服務器的網頁存放路徑同樣 [root@php php-5.6.27]# vim /var/www/html/index.php #編寫PHP測試頁面 <?php phpinfo(); ?> [root@php php-5.6.27]# vim /var/www/html/index1.php #鏈接數據庫的測試腳本 <?php $link=mysqli_connect('192.168.20.5','ljz','pwd@123'); if($link) echo "恭喜你,數據庫鏈接成功!!!"; else echo "connect shibai"; mysqli_close($link); ?>
至此,便可訪問Nginx服務器的80端口來查看php服務器上定義的兩個網頁文件(在訪問鏈接數據庫的腳本文件時,須要先部署數據庫,並建立用來鏈接的用戶):
訪問index.php,看到如下頁面說明php運行正常:
訪問index1.php,看到如下頁面,說明LNMP之間的協調工做沒有問題(我在訪問下面的地址前,已經部署了MySQL數據庫,而且建立了相應的用戶):
我這裏部署一個簡易的MySQL數據庫,提供測試功能便可。
[root@mysql ~]# rz #上傳我提供的mysql.sh腳本文件及mysql-5.7.22-linux.....源碼包 [root@mysql ~]# sh mysql.sh #執行mysql.sh腳本,執行後,須要等待較長時間 Starting MySQL... SUCCESS! #當出現此提示信息時,則表示MySQL部署成功 mysql: [Warning] Using a password on the command line interface can be insecure. #安裝MySQL成功後,默認的數據庫root密碼爲123 [root@mysql ~]# mysql -uroot -p123 #登陸MySQL數據庫 mysql> create database bbs; mysql> grant all on bbs.* to ljz@"192.168.20.%" identified by 'pwwd@123';
[root@mamcache ~]# cd /usr/src #切換至指定目錄 [root@mamcache src]# rz #上傳所需源碼包 [root@mamcache src]# ls #就是上傳如下的後面兩個源碼包 debug kernels libevent-2.0.22-stable.tar.gz memcached-1.4.33.tar.gz [root@mamcache src]# tar zxf libevent-2.0.22-stable.tar.gz #解包 [root@mamcache src]# tar zxf memcached-1.4.33.tar.gz #解包 [root@mamcache src]# cd libevent-2.0.22-stable/ #切換至解壓後的目錄 [root@mamcache libevent-2.0.22-stable]# ./configure && make && make install #編譯安裝 [root@mamcache libevent-2.0.22-stable]# cd ../memcached-1.4.33/ #切換至memcached目錄 [root@mamcache memcached-1.4.33]# ./configure --prefix=/usr/local/memcached --with-libevent=/usr/local && make && make install #編譯安裝 [root@mamcache ~]# ls /usr/local/memcached/bin/memcached #確認該命令已安裝 /usr/local/memcached/bin/memcached [root@mamcache ~]# ln -sf /usr/local/memcached/bin/memcached /usr/local/bin/ #對命令作軟連接 [root@memcache memcached-1.4.33]# memcached -d -m 1024 -l 192.168.20.4 -p 11211 -u root -c 10240 -P /usr/local/memcached/memcached.pid #啓動memcached服務,上述啓動參數說明以下: # -d 選項是啓動一個守護進程。 # -m 分配給 Memcache 使用的內存數量,單位是 MB,默認 64MB。 # -l 監聽的 IP 地址。(默認:INADDR_ANY,全部地址) # -p 設置 Memcache 的 TCP 監聽的端口,最好是 1024 以上的端口。 # -u 運行 Memcache 的用戶,若是當前爲 root 的話,須要使用此參數指定用戶。 # -c 選項是最大運行的併發鏈接數,默認是 1024。 # -P 設置保存 Memcache 的 pid 文件路徑。 # -M 內存耗盡時返回錯誤,而不是刪除項 # -f 塊大小增加因子,默認是 1.25 # -n 最小分配空間,key+value+flags 默認是 48 # -h 顯示幫助 [root@mamcache ~]# netstat -anput | grep 11211 #肯定TCP及udp端口在監聽
[root@php ~]# cd /usr/src [root@php src]# rz #上傳memcache-3.0.8.tgz源碼包 [root@php src]# tar zxf memcache-3.0.8.tgz #解包 [root@php src]# cd memcache-3.0.8/ #進入解壓後的目錄 [root@php memcache-3.0.8]# /usr/local/php5.6/bin/phpize #生成該命令,以便生成configure文件 #若在執行上述命令時報錯,則須要執行「yun -y install autoconf "安裝提示的autoconf包。 [root@php memcache-3.0.8]# ./configure --enable-memcache --with-php-config=/usr/local/php5.6/bin/php-config && make && make install #上述命令執行後,會顯示memcache.so存放的路徑 [root@php memcache-3.0.8]# vim /etc/php.ini #編輯主配置文件 #在配置文件末尾添加上述內容,以便指定memcache.so文件的存放路徑 extension = /usr/local/php5.6/lib/php/extensions/no-debug-zts-20131226/memcache.so [root@php memcache-3.0.8]# systemctl restart php-fpm #重啓php服務,使更改生效 [root@php memcache-3.0.8]# cd /var/www/html/ [root@php html]# vim test3.php #編寫測試文件 <?php $memcache = new Memcache; $memcache->connect('192.168.20.4', 11211) or die ("Could not connect"); $version = $memcache->getVersion(); echo "Server's version: ".$version."<br/>"; $tmp_object = new stdClass; $tmp_object->str_attr = 'test'; $tmp_object->int_attr = 123; $memcache->set('key', $tmp_object, false, 600) or die ("Failed to save data at the server"); echo "Store data in the cache (data will expire in 600 seconds)<br/>"; $get_result = $memcache->get('key'); echo "Data from the cache:<br/>"; var_dump($get_result); ?> #編輯完成後,保存退出便可,此測試腳本是顯示memcached的版本 #而且向裏面插入了一個緩存時間爲600秒的鍵值對「test=123」,其ID爲「key」
客戶端訪問編輯的test3.php文件,會看到如下內容:
在memcached服務器上安裝Telnet命令,並登錄緩存庫,查看是否能夠獲得其鍵值對(如下操做在memcached服務器上進行操做):
[root@memcache ~]# yum -y install telnet #安裝Telnet命令 [root@memcache ~]# telnet 192.168.20.4 11211 #登錄到memcached的11211端口 get key #查詢ID爲「key」的鍵值對,能夠看到咱們測試腳本寫入的「test=123」 VALUE key 1 66 O:8:"stdClass":2:{s:8:"str_attr";s:4:"test";s:8:"int_attr";i:123;} END #在進行上面的get驗證時,須要將test3.php文件中插入的鍵值對的保存時間值改大一些 #或者從新訪問一下,以避免緩存失效,查詢不到
至此,LNMP動靜分離&&memcache緩存服務器已經基本部署完成,接下來,配置PHP與memcached服務器溝通保存session會話
接下來實現PHP與memcached服務器溝通保存session會話。
[root@php ~]# vim /etc/php.ini #在配置文件末尾添加下面內容 ession.save_handler = memcache session.save_path = "tcp://192.168.20.4:11211?persistent=1&weight=1&timeout=1&retry_interval=15" #註上面寫入的內容解釋以下: # session.save_handler:設置 session 的儲存方式爲 memcache 。 #默認以文件方式存取 session數據。 #session.save_path: 設置 session 儲存的位置 #使用多個 memcached server 時用逗號」,」隔開, #能夠帶額外的參數」persistent」、」weight」、」timeout」、」retry_interval」等等, #相似這樣的:"tcp://host:port?persistent=1&weight=2,tcp://host2:port2"。 [root@php html]# systemctl restart php-fpm #重啓使更改生效 [root@php html]# vim /var/www/html/test2.php #編寫測試文件 <?php session_start(); if (!isset($_SESSION['session_time'])) { $_SESSION['session_time'] = time(); } echo "session_time:".$_SESSION['session_time']."<br />"; echo "now_time:".time()."<br />"; echo "session_id:".session_id()."<br />"; ?> #編寫完成後,保存退出便可
客戶端訪問編寫的test2.php測試文件,以下:
一樣,使用Telnet命令在memcached服務器上進行查詢其session_id的值,以下:
[root@memcache ~]# telnet 192.168.20.4 11211 #登陸緩存監聽的端口 get naapo2eet2d9s4to4mt7hchnr1 #執行get命令 VALUE naapo2eet2d9s4to4mt7hchnr1 0 26 session_time|i:1572450021; #能夠看到,查詢到的session_time和咱們網頁訪問到的值是同樣的,說明其被緩存了
在MySQL數據庫上建立用於測試的表(全部操做都在MySQL數據庫上)以下:
mysql> create database testdb1; #建立一個庫 mysql> use testdb1; #切換至建立的庫中 mysql> create table test1(id int not null auto_increment,name varchar(20) default null,primary key (id)) engine=innodb auto_increment=1 default charset=utf8; #建立表,共兩列,分別是ID號和姓名 mysql> insert into test1(name) values ('tom1'),('tom2'),('tom3'),('tom4'),('tom5'); #向表中插入測試數據 mysql> select * from test1; #肯定插入的數據 +----+------+ | id | name | +----+------+ | 1 | tom1 | | 2 | tom2 | | 3 | tom3 | | 4 | tom4 | | 5 | tom5 | +----+------+ 5 rows in set (0.00 sec) mysql> desc test1; #能夠執行此語句查看test1的表結構 mysql> grant select on testdb1.* to user@'%' identified by 'pwd@123'; #對test1表建立一個只讀的數據庫用戶,用於接下來的測試
接下來就是測試的工做了,這裏有個 php 腳本,用於測試memcache 是否緩存數據成功
在PHP服務器上編寫如下測試文件:
客戶端訪問用於測試的腳本文件,第一次訪問的頁面以下:
客戶端刷新後,會看到如下頁面:
在查詢到的緩存過時前,能夠在memcache上經過get 獲取到對應的緩存數據,以下(在memcache服務器上進行操做):
[root@memcache ~]# telnet 192.168.20.4 11211 #登陸緩存監聽的端口 get d8c961e9895ba4b463841924dbcefc2b #執行get命令 VALUE d8c961e9895ba4b463841924dbcefc2b 0 251 a:5:{i:0;a:2:{s:2:"id";s:1:"1";s:4:"name";s:4:"tom1";}i:1;a:2:{s:2:"id";s:1:"2";s:4:"name";s:4:"tom2";}i:2;a:2:{s:2:"id";s:1:"3";s:4:"name";s:4:"tom3";}i:3;a:2:{s:2:"id";s:1:"4";s:4:"name";s:4:"tom4";}i:4;a:2:{s:2:"id";s:1:"5";s:4:"name";s:4:"tom5";}}
———————— 本文至此結束,感謝閱讀 ————————