對於變化頻率很是快的數據來講,若是還選擇傳統的靜態緩存方式(File System等)展現數據,可能在緩存的存取上會有很大的開銷,並不能很好的知足須要,而Redis這樣基於內存的NoSQL數據庫,就很是適合擔任實時數據的容器。php
可是每每咱們又有數據可靠性的需求,採用MySQL做爲數據存儲,不會由於內存問題而引發數據丟失,同時也能夠利用關係數據庫的特性實現不少功能。mysql
因此就會很天然的想到是否能夠採用MySQL做爲數據存儲引擎,Redis則做爲Cache。而這種需求目前尚未看到有特別成熟的解決方案或工具,所以本文將嘗試採用Gearman+PHP+MySQL UDF的組合異步實現MySQL到Redis的數據複製。git
###MySQL到Redis數據複製方案 不管MySQL仍是Redis,自身都帶有數據同步的機制,像比較經常使用的MySQL的Master/Slave模式,就是由Slave端分析Master的binlog來實現的,這樣的數據複製其實仍是一個異步過程,只不過當服務器都在同一內網時,異步的延遲幾乎能夠忽略。github
那麼理論上咱們也能夠用一樣方式,分析MySQL的binlog文件並將數據插入Redis。可是這須要對binlog文件以及MySQL有很是深刻的理解,同時因爲binlog存在Statement/Row/Mixedlevel多種形式,分析binlog實現同步的工做量是很是大的。redis
所以這裏選擇了一種開發成本更加低廉的方式,借用已經比較成熟的MySQL UDF,將MySQL數據首先放入Gearman中,而後經過一個本身編寫的PHP Gearman Worker,將數據同步到Redis。比分析binlog的方式增長了很多流程,可是實現成本更低,更容易操做。sql
###Gearman的安裝與使用 Gearman是一個支持分佈式的任務分發框架。設計簡潔,得到了很是普遍的支持。一個典型的Gearman應用包括如下這些部分:數據庫
###Gearman構架json
Gearman Job Server:Gearman核心程序,須要編譯安裝並以守護進程形式運行在後臺 Gearman Client:能夠理解爲任務的收件員,好比我要在後臺執行一個發送郵件的任務,能夠在程序中調用一個Gearman Client並傳入郵件的信息,而後就能夠將執行結果當即展現給用戶,而任務自己會慢慢在後臺運行。 Gearman Worker:任務的真正執行者,通常須要本身編寫具體邏輯並經過守護進程方式運行,Gearman Worker接收到Gearman Client傳遞的任務內容後,會按順序處理。 之前曾經介紹過相似的後臺任務處理項目Resque。二者的設計其實很是接近,簡單能夠類比爲:ubuntu
Gearman Job Server:對應Resque的Redis部分 Gearman Client:對應Resque的Queue操做 Gearman Worker:對應Resque的Worker和Job 這裏之因此選擇Gearman而不是Resque是由於Gearman提供了比較好用的MySQL UDF,工做量更小。緩存
###安裝Gearman及PHP Gearman擴展 如下均以Ubuntu12.04爲例。
apt-get install gearman gearman-server libgearman-dev 檢查Gearman的運行情況:
/etc/init.d/gearman-job-server status
PHP的Gearman擴展能夠經過pecl直接安裝
pecl install gearman echo "extension=gearman.so" > /etc/php5/conf.d/gearman.ini service php5-fpm restart 可是實測發現ubuntu默認安裝的gearman版本太低,直接運行pecl install gearman會報錯
configure: error: libgearman version 1.1.0 or later required 所以Gearman + PHP擴展建議經過編譯方式安裝,這裏爲了簡單說明,選擇安裝舊版本擴展:
pecl install gearman-1.0.3 ###Gearman + PHP實例 爲了更容易理解後文Gearman的運行流程,這裏不妨從一個最簡單的Gearman實例來講明,好比咱們要進行一個文件處理的操做,首先編寫一個Gearman Client並命名爲client.php:
<?php $client = new GearmanClient(); $client->addServer(); $client->doBackground('writeLog', 'Log content'); echo '文件已經在後臺操做';
運行這個文件,至關於模擬用戶請求一個Web頁面後,將處理結束的信息返回用戶:
php client.php 查看一下Gearman的情況:
(echo status ; sleep 0.1) | netcat 127.0.0.1 4730
能夠看到輸出爲
writeLog 1 0 0
說明咱們已經在Gearman中創建了一個名爲writeLog的任務,而且有1個任務在隊列等待中。
而上面的4列分別表明當前的Gearman的運行狀態:
任務名稱 在等待隊列中的任務 正在運行的任務 正在運行的Worker進程 可使用watch進行實時監控:
watch -n 1 "(echo status; sleep 0.1) | nc 127.0.0.1 4730"
而後咱們須要編寫一個Gearman Worker命名爲worker.php:
<?php $worker = new GearmanWorker(); $worker->addServer(); $worker->addFunction('writeLog', 'writeLog'); while($worker->work()); function writeLog($job) { $log = $job->workload(); file_put_contents(__DIR__ . '/gearman.log', $log . "\n", FILE_APPEND | LOCK_EX); }
Worker使用一個while死循環實現守護進程,運行
php worker.php 能夠看到Gearman狀態變爲:
writeLog 0 0 1
同時查看同目錄下gearman.log,內容應爲從Client傳入的值Log content。
經過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
能夠看到從新編譯生成了 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是否生效。
Gearman PHP Worker將MySQL數據異步複製到Redis Redis做爲時下當熱的NoSQL緩存解決方案無需過多介紹,其安裝及使用也很是簡單:
apt-get install redis-server pecl install redis echo "extension=redis.so" > /etc/php5/conf.d/redis.ini
而後編寫一個Gearman Worker:redis_worker.php
#!/usr/bin/env php <? $worker = new GearmanWorker(); $worker->addServer(); $worker->addFunction('syncToRedis', 'syncToRedis'); $redis = new Redis(); $redis->connect('127.0.0.1', 6379); while($worker->work()); function syncToRedis($job) { global $redis; $workString = $job->workload(); $work = json_decode($workString); if(!isset($work->id)){ return false; } $redis->set($work->id, $workString); }
最後須要將Worker在後臺運行:
nohup php redis_worker.php &
經過這種方式將MySQL數據複製到Redis,經測試單Worker基本能夠瞬時完成。
注意點 在實際操做中發現,Gearman UDF在每次MySQL服務重啓後會丟失已經設置的服務器信息。由於時間有限沒有深刻的調查緣由,而用了曲線救國的解決方法,讓MySQL在每次服務啓動時自動運行一次設置語句:
vi /var/lib/mysql/init_file.sql
加入
SELECT gman_servers_set('127.0.0.1:4730');
而後在/etc/mysql/my.cnf的[mysqld]小節下加入
init-file=/var/lib/mysql/init_file.sql
而後重啓服務。