對於絕大多數發展中等的web 2.0網站來講,LAMP結構已經不能知足如今的須要了,新的架構組合是GLAMMP,G=Gearman(分佈式遠程過程處理),M=Memcached(高性能的分佈式的內存對象緩存系統)。
php
Gearman的高級特性
在一個 Web 應用程序內可能有許多地方都會用到 Gearman。能夠導入大量數據、發送許多電子郵件、編碼視頻文件、挖據數據並構建一箇中央日誌設施 — 全部這些均不會影響站點的體驗和響應性。能夠並行地處理數據。並且,因爲 Gearman 協議是獨立於語言和平臺的,因此您能夠在解決方案中混合編程語言。好比,能夠用 PHP 編寫一個 producer,用 C、Ruby 或其餘任何支持 Gearman 庫的語言編寫 worker。
一個鏈接客戶機和 worker 的 Gearman 網絡實際上可使用任何您能想象獲得的結構。不少配置可以運行多個代理並將 worker 分配到許多機器上。負載均衡是隱式的:每一個可操做的可用 worker(多是每一個 worker 主機具備多個 worker)從隊列中拉出做業。一個做業可以同步或異步運行並具備優先級。
Gearman 的最新版本已經將系統特性擴展到了包含持久的做業隊列和用一個新協議來經過 HTTP 提交工做請求。對於前者,Gearman 工做隊列保存在內存並在一個關係型數據庫內存有備份。這樣一來,若是 Gearman 守護程序故障,它就能夠在重啓後從新建立這個工做隊列。另外一個最新的改良經過一個 memcached 集羣增長隊列持久性。memcached 存儲也依賴於內存,但被分散於幾個機器以免單點故障。
Gearman 是一個剛剛起步卻頗有實力的工做分發系統。據 Gearman 的做者 Eric Day 介紹,Yahoo! 在 60 或更多的服務器上使用 Gearman 天天處理 600 萬個做業。新聞聚合器 Digg 也已構建了一個相同規模的 Gearman 網絡,天天可處理 400,000 個做業。Gearman 的一個出色例子能夠在 Narada 這個開源搜索引擎(參見 參考資料)中找到。html
大規模web架構gearman分佈式處理應用案例
mysql
業務服務壓力比較大,想把一些佔用資源的功能異步到遠程處理,好比記錄業務日誌,文件加密,文件分發到其餘文件服務器節點上,檢查文件服務器是否已同步,對用戶上傳的圖片進行剪裁生成多份縮略圖,視頻轉換,靜態內容生成,清除緩存等等,這些請求耗時長,佔用系統資源大,影響業務正常訪問。這些問題會常常遇到的,若是這些任務都在用戶請求過程當中完成,服務器撐不撐得住暫不考慮,單憑用戶體驗角度考慮來講,那是難以忍受的。linux
對於這種需求,咱們能夠經過分佈式計算,對任務進行拆分,轉移到多臺服務器上進行異步或同步處理。分佈式消息隊列有多種實現方式如rabbitmq、gearman等。 git
在使用異步處理以前,當咱們要發送郵件時,會直接這麼寫,代碼以下:github
這存在一個問題, 會長期阻塞在send函數,而無限制等待下去, 直到超時,極可能會拖垮服務器的。咱們可使用gearman,來改變這種發送郵件的方式。第一步,建立一個worker實例SENDMAIL,並向job server註冊,等待接收任務並執行發送郵件的操做。第二步,客戶端只須要將發送郵件的任務丟給job server便退出,沒你什麼事了。上代碼:web
完成上面的改造不要認爲解決了發送郵件會長期阻塞在send函數,而無限制等待下去的問題了。這其實只是解決了一部分,還有一個問題須要考慮進去,既然採用了異步方式,那麼應用程序是不知道郵件是否發送成功的,所以須要記錄任務執行的結果,能夠將結果寫入數據庫,按期的對發送失敗的郵件進行再次發送,或寫個異常處理的worker,捕獲發送郵件異常,進行屢次嘗試發送。redis
Gearman高級應用sql
須要當心的一件事情是數據的共享。Gearman 不進行所交換數據的任何轉換或操做。對於這裏使用的簡單字符串和整數沒有問題,可是不能共享 PHP 中的數組值並指望能在 Java 語言中被理解。對於這種類型的交互,可使用不少結構化數據標準中的一種,好比 JavaScript Object Notation (JSON) 或 XML。另外,若是您在處理來自數據庫的信息,只要共享 ID 或者找到須要處理的數據時要用到的信息便可,或者使用 memcached 這樣的透明方法(儘管可能仍然須要 JSON 或等價物)。shell
gearman+mysql實現持久化隊列
持久化隊列是在0.6版本中新添的一項功能,容許將隊列存放在drizzle或mysql中。0.7版本容許將隊列存放在memcached。0.9版本能夠將隊列存放在sqlite3或postgresql。
在gearman job服務器內部,全部的job隊列都是存放在內存中的,這就意味着一旦服務器重啓或崩潰,未執行的job將會丟失而不會被worker服務器執行。持久化隊列將後臺做業存放在一個外部持久的隊列中。持久化隊列只對後臺jobs有效,由於前臺jobs依附於客戶端。若是job服務器擋掉了,客戶端會檢測到,將會從其餘地方從新啓動這個前臺job或者返回錯誤。然後臺jobs沒有依附於客戶端,若是要想讓它運行則須要提交。
建立數據庫和表
create database gearman;
create table `gearman_queue` (
`unique_key` varchar(64) NOT NULL,
`function_name` varchar(255) NOT NULL,
`priority` int(11) NOT NULL,
`data` LONGBLOB NOT NULL,
`when_to_run` INT, PRIMARY KEY (`unique_key`)
);
啓動gearmand
gearmand -q libdrizzle –libdrizzle-host=127.0.0.1 –libdrizzle-user=gearman –libdrizzle-password=password –libdrizzle-db=gearman –libdrizzle-table=gearman_queue –libdrizzle-mysql
建立一個後臺job
gearman -f testqueue -b xx00
查看隊列
select * from gearman.gearman_queue
執行隊列中的job
gearman -f testqueue -w
查看隊列
select * from gearman.gearman_queue //這條job刪除掉了
假如,我直接向這張表裏插入一條記錄,隊列裏會多一條嗎?哈哈,我試過了,不會,除非gearmand重啓才能識別
注意:gearmand能支持哪些持久化方式,與源碼編譯時帶的的庫關係
源碼編譯
./configure --prefix=/usr/local/gearman --with-libevent-prefix=/usr/local/libevent
最後有個提示:
Configuration summary for gearmand version 0.14
* Installation prefix: /usr/local/gearman
* System type: unknown-linux-gnu
* Host CPU: x86_64
* C Compiler: gcc (GCC) 4.6.3
* Assertions enabled: yes
* Debug enabled: no
* Warnings as failure: no
* Building with libsqlite3 yes
* Building with libdrizzle no
* Building with libmemcached no
* Building with libpq no
* Building with tokyocabinet no
這裏最後面的五行說明了支持哪些持久化方式,這個示例裏支持libsqlite3,那麼能夠:
/usr/local/sbin/gearmand -l /usr/local/gearman/log/trace2.log --verbose INFO -p 4830 -q libsqlite3 --libsqlite3-db /usr/local/sqlite/bin/gearman --libsqlite3-table gearman_queue -d
很遺憾,我到如今也沒能編譯出對MySQL的持久化
隊列持久化與異步發郵件相結合
在Gearman已配置好了持久化隊列後,展現一個異步發郵件的功能。
先看看 client.php :
<?php
$client = new GearmanClient();
$client->addServer(); // 預設為 localhost
$emailData = array(
'name' => 'web',
'email' => 'member@example.com',
);
$imageData = array(
'image' => '/var/www/pub/image/test.png',
);
$client->doBackground('sendEmail', serialize($emailData));
echo "Email sending is done.\n";
$client->doBackground('resizeImage', serialize($imageData));
echo "Image resizing is done.\n";
接下來我們要製做 Worker ,如下就是 worker.php :
<?php
$worker = new GearmanWorker();
$worker->addServer(); // 預設為 localhost
$worker->addFunction('sendEmail', 'doSendEmail');
$worker->addFunction('resizeImage', 'doResizeImage');
while($worker->work()) {
sleep(1); // 無限迴圈,並讓 CPU 休息一下
}
function doSendEmail($job)
{
$data = unserialize($job->workload());
print_r($data);
sleep(3); // 模擬處理時間
echo "Email sending is done really.\n\n";
}
function doResizeImage($job)
{
$data = unserialize($job->workload());
print_r($data);
sleep(3); // 模擬處理時間
echo "Image resizing is really done.\n\n";
}
準備好 Client 和 Worker 的程式後,就能夠測試看看了。首先我們必須得先執行 worker.php ,讓它開始服務。
php worker.php
這時我們會看到 worker.php 停駐在螢幕上等待服務。
接著我們開啟另外一個 console 視窗來執行 client.php :
php client.php
會馬上出現如下結果:
Email sending is done.
Image Resizing is done.
而切換到執行 worker.php 的 console 時,就會看到如下執行結果:
Array
(
[who_send] => web
[get_email] => member@example.com
)
Email sending is really done.
Array
(
[image] => /var/www/pub/image/test.png
)
Image resizing is really done.
這表示 Worker 正常地處理 Client 的需求了。
現在試著把 worker.php 停掉 (Ctrl+C) ,然後再執行 client.php ,你們應該會發現 client.php 還是正常地完成它的工做;這是因為 Job Server 幫我們把需求先放在 Queue 裡,等待 Worker 啟動後再處理。
這時能夠查看 MySQL 的 gearman 資料庫,在 gearman_queue 資料表中應該就會看到如下結果:
這表示 Job Server 成功地將 Queue 保留在 MySQL 資料表中。
接著再執行 worker.php ,這時 Job Server 會得知 Worker 復活,趕緊將 Queue 裡面屬於該 Worker 應該執行的工做再發送出去以完成做業;而 Worker 完成做業後, Job Server 就會把 Queue 清空了。
是否是頗有趣呢?
Message Queue 這個架構的應用能夠說相當廣泛,尤爲在大流量的網站上,我們能透過它來來有效運用分散式的系統架構,以處理更多使用者的需求。
而目前 Gearman 可說是在 PHP 上一個很棒的 Message Queue 支援套件,並且 API 也相當完善;所以若是能善用 Gearman 的話,那麼我們在 PHP 網站的架構上就能夠有更大的延展性,也能有更多的可能性。
有機會就去試試看吧!
使用MySQL UDFs來調用gearman分佈式任務分發系統
當向表插入數據的時候,觸發執行某些任務
一.安裝gearman-mysql-udf
# apt-get install libmysql++-dev
# wget https://launchpad.net/gearman-mysql-udf/trunk/0.6/+download/gearman-mysql-udf-0.6.tar.gz
# tar zxvf gearman-mysql-udf-0.6.tar.gz
# ./configure –with-mysql=/usr/bin/mysql_config –libdir=/usr/lib/mysql/plugin
# make
# make install
# mysql
[codesyntax lang="sql"]
mysql> CREATE FUNCTION gman_do RETURNS STRING
SONAME 「libgearman_mysql_udf.so」;
mysql> CREATE FUNCTION gman_do_high RETURNS STRING
SONAME 「libgearman_mysql_udf.so」;
mysql> CREATE FUNCTION gman_do_low RETURNS STRING
SONAME 「libgearman_mysql_udf.so」;
mysql> CREATE FUNCTION gman_do_background RETURNS STRING
SONAME 「libgearman_mysql_udf.so」;
mysql> CREATE FUNCTION gman_do_high_background RETURNS STRING
SONAME 「libgearman_mysql_udf.so」;
mysql> CREATE FUNCTION gman_do_low_background RETURNS STRING
SONAME 「libgearman_mysql_udf.so」;
mysql> CREATE AGGREGATE FUNCTION gman_sum RETURNS INTEGER
SONAME 「libgearman_mysql_udf.so」;
mysql> CREATE FUNCTION gman_servers_set RETURNS STRING
SONAME 「libgearman_mysql_udf.so」;
mysql> SELECT gman_servers_set(「192.168.1.60:4730,192.168.1.60:4731″) as gman_servers; //設置gearman server
+————————————-+
| gman_servers |
+————————————-+
| 192.168.1.60:4730,192.168.1.60:4731 |
+————————————-+
mysql> create table udf_test(
-> id int unsigned auto_increment primary key,
-> val varchar(20) not null); //新建表
mysql> create trigger sendmail before insert on udf_test for each row set @return=gman_do_background(‘MAIL’,'undef’); //建立觸發器,當向表udf_test插入數據時候,執行任務。
[/codesyntax]
# perl -MCPAN -e shell
cpan> install Gearman::Worker //安裝Gearman::Worker模塊
cpan> install Mail::SendEasy //安裝Mail::SendEasy模塊
# vi WORKER_SENDMAIL.pl //建立worker任務
# perl WORKER_SENDMAIL.pl &
# perl WORKER_SENDMAIL.pl & //在後臺運行兩個worker
二.測試
1.向udf_test表插入數據
mysql> insert into udf_test value (」,’a');
2.查看是否收到郵件
三.Gearman server信息
# telnet 192.168.1.60 4730
Trying 192.168.1.60…
Connected to 192.168.1.60.
Escape character is ‘^]’.
status
MAIL 0 0 2
說明:第一列worker名稱;第二列jobs執行數量;第三列jobs隊列數量;第四列可用worker數量
經過Gearman實現MySQL到Redis的數據同步
對於變化頻率很是快的數據來講,若是還選擇傳統的靜態緩存方式(Memocached、File System等)展現數據,可能在緩存的存取上會有很大的開銷,並不能很好的知足須要,而Redis這樣基於內存的NoSQL數據庫,就很是適合擔任實時數據的容器。
可是每每咱們又有數據可靠性的需求,採用MySQL做爲數據存儲,不會由於內存問題而引發數據丟失,同時也能夠利用關係數據庫的特性實現不少功能。
因此就會很天然的想到是否能夠採用MySQL做爲數據存儲引擎,Redis則做爲Cache。而這種需求目前尚未看到有特別成熟的解決方案或工具,所以本文將嘗試採用Gearman+PHP+MySQL UDF的組合異步實現MySQL到Redis的數據複製。
MySQL到Redis數據複製方案
不管MySQL仍是Redis,自身都帶有數據同步的機制,像比較經常使用的MySQL的Master/Slave模式,就是由Slave端分析Master的binlog來實現的,這樣的數據複製其實仍是一個異步過程,只不過當服務器都在同一內網時,異步的延遲幾乎能夠忽略。
那麼理論上咱們也能夠用一樣方式,分析MySQL的binlog文件並將數據插入Redis。可是這須要對binlog文件以及MySQL有很是深刻的理解,同時因爲binlog存在Statement/Row/Mixedlevel多種形式,分析binlog實現同步的工做量是很是大的。
所以這裏選擇了一種開發成本更加低廉的方式,借用已經比較成熟的MySQL UDF,將MySQL數據首先放入Gearman中,而後經過一個本身編寫的PHP Gearman Worker,將數據同步到Redis。比分析binlog的方式增長了很多流程,可是實現成本更低,更容易操做。
經過MySQL UDF + Trigger同步數據到Gearman
MySQL要實現與外部程序互通的最好方式仍是經過MySQL UDF(MySQL user defined functions)來實現。爲了讓MySQL能將數據傳入Gearman,這裏使用了lib_mysqludf_json和gearman-mysql-udf的組合。
安裝lib_mysqludf_json
使用lib_mysqludf_json的緣由是由於Gearman只接受字符串做爲入口參數,能夠lib_mysqludf_json經過將MySQL中的數據編碼爲JSON字符串
apt-get install libmysqlclient-dev
wget https://github.com/mysqludf/lib_mysqludf_json/archive/master.zip
unzip master.zip
cd lib_mysqludf_json-master/
rm lib_mysqludf_json.so
gcc $(mysql_config --cflags) -shared -fPIC -o lib_mysqludf_json.so lib_mysqludf_json.c
#這裏的mysql_config是mysql的一個程序,通常在mysql安裝目錄的/usr/bin目錄下
能夠看到從新編譯生成了 lib_mysqludf_json.so 文件,此時須要查看MySQL的插件安裝路徑:
mysql -u root -pPASSWORD --execute="show variables like '%plugin%';"
+---------------+------------------------+
| Variable_name | Value |
+---------------+------------------------+
| plugin_dir | /usr/lib/mysql/plugin/ |
+---------------+------------------------+
而後將 lib_mysqludf_json.so 文件複製到對應位置:
cp lib_mysqludf_json.so /usr/lib/mysql/plugin/
最後登入MySQL運行語句註冊UDF函數:
CREATE FUNCTION json_object RETURNS STRING SONAME 'lib_mysqludf_json.so';
安裝gearman-mysql-udf
方法幾乎同樣:
apt-get install libgearman-dev
wget https://launchpad.net/gearman-mysql-udf/trunk/0.6/+download/gearman-mysql-udf-0.6.tar.gz
tar -xzf gearman-mysql-udf-0.6.tar.gz
cd gearman-mysql-udf-0.6
./configure --with-mysql=/usr/bin/mysql_config --libdir=/usr/lib/mysql/plugin/
make && make install
登入MySQL運行語句註冊UDF函數:
CREATE FUNCTION gman_do_background RETURNS STRING SONAME 'libgearman_mysql_udf.so';
CREATE FUNCTION gman_servers_set RETURNS STRING SONAME 'libgearman_mysql_udf.so';
最後指定Gearman服務器的信息:
SELECT gman_servers_set('127.0.0.1:4730');
經過MySQL觸發器實現數據同步
最終同步那些數據,如何同步,仍是須要根據實際狀況決定,好比我但願將數據表data的數據在每次更新時同步,那麼編寫Trigger以下:
DELIMITER $$
CREATE TRIGGER datatoredis AFTER UPDATE ON data
FOR EACH ROW BEGIN
SET @ret=gman_do_background('syncToRedis', json_object(NEW.id as `id`, NEW.volume as `volume`));
END$$
DELIMITER ;
嘗試在數據庫中更新一條數據查看Gearman是否生效。
須要說明一下,這裏的 gman_do_background函數調用,與gearman的一次通信是短鏈接,函數調用時鏈接,調用結束後鏈接就短掉, 若是併發壓力比較大,須要考慮一下
Gearman PHP Worker將MySQL數據異步複製到Redis
Redis做爲時下當熱的NoSQL緩存解決方案無需過多介紹,其安裝及使用也很是簡單:
apt-get install redis-server pecl install redisecho "extension=redis.so" > /etc/php5/conf.d/redis.ini
而後編寫一個Gearman Worker:redis_worker.php
最後須要將Worker在後臺運行:
nohup php redis_worker.php &
經過這種方式將MySQL數據複製到Redis,經測試單Worker基本能夠瞬時完成。