JAVA經過Gearman實現MySQL到Redis的數據同步(異步複製)

MySQL到Redis數據複製方案

不管MySQL仍是Redis,自身都帶有數據同步的機制,像比較經常使用的 MySQL的Master/Slave模式 ,就是由Slave端分析Master的binlog來實現的,這樣的數據複製其實仍是一個異步過程,只不過當服務器都在同一內網時,異步的延遲幾乎能夠忽略。php

那麼理論上咱們也能夠用一樣方式,分析MySQL的binlog文件並將數據插入Redis。可是這須要對binlog文件以及MySQL有很是深刻的理解,同時因爲 binlog存在Statement/Row/Mixedlevel多種形式 ,分析binlog實現同步的工做量是很是大的。java

所以這裏選擇了一種開發成本更加低廉的方式,借用已經比較成熟的MySQL UDF,將MySQL數據首先放入Gearman中,而後經過一個本身編寫的PHP Gearman Worker,將數據同步到Redis。比分析binlog的方式增長了很多流程,可是實現成本更低,更容易操做。mysql

Gearman的安裝與使用

Gearman 是一個支持分佈式的任務分發框架。設計簡潔,得到了很是普遍的支持。一個典型的Gearman應用包括如下這些部分:git

 

  • Gearman Job Server:Gearman核心程序,須要編譯安裝並以守護進程形式運行在後臺github

  • Gearman Client:能夠理解爲任務的收件員,好比我要在後臺執行一個發送郵件的任務,能夠在程序中調用一個Gearman Client並傳入郵件的信息,而後就能夠將執行結果當即展現給用戶,而任務自己會慢慢在後臺運行。redis

  • Gearman Worker:任務的真正執行者,通常須要本身編寫具體邏輯並經過守護進程方式運行,Gearman Worker接收到Gearman Client傳遞的任務內容後,會按順序處理。spring

之前曾經介紹過相似的 後臺任務處理項目Resque 。二者的設計其實很是接近,簡單能夠類比爲:sql

  • Gearman Job Server:對應Resque的Redis部分json

  • Gearman Client:對應Resque的Queue操做服務器

  • Gearman Worker:對應Resque的Worker和Job

這裏之因此選擇Gearman而不是Resque是由於Gearman提供了比較好用的MySQL UDF,工做量更小。

 

一、安裝依賴

 yum install -y boost-devel gperf libevent-devel libuuid-devel

 yum install mysql-devel -y

二、下載gearman

 wget https://launchpad.net/gearmand/1.2/1.1.12/+download/gearmand-1.1.12.tar.gz

三、編譯安裝,指定mysqlclient的連接路徑

  tar -zxvf gearmand-1.1.12.tar.gz 

  cd gearmand-1.1.12

   ./configure  

make && make install

 

四、啓動gearmand服務端 (啓動之時,在/var/log/下建立gearmand.log日誌文件。-l 指定日誌文件  -d後臺運行 -L 0.0.0.0 綁定到IPV4

gearmand -L 0.0.0.0 -l /var/log/gearmand.log -d

五、查看是否啓動成功

ps -ef | grep gearman

六、查看是否安裝成功,查看gearman版本信息

gearmand -V

 

七、MySQL UDF + Trigger同步數據到Gearman (https://github.com/mysqludf)

安裝lib_mysqludf_json(lib_mysqludf_json能夠把MySQL表的數據以json數據格式輸出)

wget https://github.com/mysqludf/lib_mysqludf_json/archive/master.zip

unzip master.zip

cd lib_mysqludf_json-master/

rm -rf lib_mysqludf_json.so

八、編譯 mysql_config 這是mysql的配置文件,能夠 find /usr -name mysql_config 搜索下在什麼位置

gcc $(/usr/local/mysql/bin/mysql_config  --cflags) -shared -fPIC -o lib_mysqludf_json.so lib_mysqludf_json.c

九、拷貝lib_mysqludf_json.so到MySQL的plugin目錄

(能夠登錄MySQL,輸入命令"show variables like '%plugin%'"查看plugin位置)

cp lib_mysqludf_json.so /usr/local/mysql/lib/plugin/

 

演示lib_mysqludf_json功能

登陸mysql

mysql -uroot -h127.0.0.1 -p

註冊UDF函數

CREATE FUNCTION json_object RETURNS STRING SONAME "lib_mysqludf_json.so";

CREATE FUNCTION json_array RETURNS STRING SONAME "lib_mysqludf_json.so";

CREATE FUNCTION json_members RETURNS STRING SONAME "lib_mysqludf_json.so";

CREATE FUNCTION json_values RETURNS STRING SONAME "lib_mysqludf_json.so";

//json_array|json_members|json_values函數註冊方式與json_object同樣.

select json_object(id,file_save_type,base_dir) as sys_file_save_config from sys_file_save_config;

ERROR 1123 (HY000): Can't initialize function 'json_object'; Invalid json member name - name cannot be empty

以上錯誤這樣解決,給每一個成員名稱使用別名便可:

select json_object(id as id ,file_save_type as fileSaveType,app_id as appID) as sys_file_save_config from sys_file_save_config;

 

 

十、安裝gearman-mysql-udf (https://launchpad.net/gearman-mysql-udf)

  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 

   cd gearman-mysql-udf-0.6

十一、安裝libgearman-devel

   yum install libgearman-devel -y

   若是沒有yum源,添加epel.repo yum源

  [epel]

name=Extra Packages for Enterprise Linux 6 - $basearch

#baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch

mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-6&arch=$basearch

failovermethod=priority

enabled=1

gpgcheck=1

gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6

 

[epel-debuginfo]

name=Extra Packages for Enterprise Linux 6 - $basearch - Debug

#baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch/debug

mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-debug-6&arch=$basearch

failovermethod=priority

enabled=0

gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6

gpgcheck=1

 

[epel-source]

name=Extra Packages for Enterprise Linux 6 - $basearch - Source

#baseurl=http://download.fedoraproject.org/pub/epel/6/SRPMS

mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-source-6&arch=$basearch

failovermethod=priority

enabled=0

gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6

gpgcheck=1

    

十二、編譯安裝

(能夠登錄MySQL,輸入命令"show variables like '%plugin%'"查看plugin位置, mysql_config的配置文件,以及插件庫所在路徑,編譯以後會在此路徑生成.so文件)

./configure --with-mysql=/usr/local/mysql/bin/mysql_config --libdir=/usr/local/mysql/lib/plugin/

make && make install

 

演示gearman-mysql-udf功能

mysql -uroot -p

CREATE FUNCTION gman_do_background RETURNS STRING SONAME "libgearman_mysql_udf.so";

CREATE FUNCTION gman_servers_set RETURNS STRING SONAME "libgearman_mysql_udf.so"; 

CREATE FUNCTION gman_do RETURNS STRING SONAME "libgearman_mysql_udf.so"; 

CREATE FUNCTION gman_do_high RETURNS STRING SONAME "libgearman_mysql_udf.so"; 

CREATE FUNCTION gman_do_low RETURNS STRING SONAME "libgearman_mysql_udf.so"; 

CREATE FUNCTION gman_do_high_background RETURNS STRING SONAME "libgearman_mysql_udf.so"; 

CREATE FUNCTION gman_do_low_background RETURNS STRING SONAME "libgearman_mysql_udf.so"; 

CREATE FUNCTION gman_sum RETURNS STRING SONAME "libgearman_mysql_udf.so"; 

//函數gman_do|gman_do_high|gman_do_low|gman_do_high_background|gman_do_low_background|gman_sum註冊方式相似,請參考gearman-mysql-udf-0.6/README 

//指定gearman job server地址 

SELECT gman_servers_set('127.0.0.1:4730'); 

 

若是出現異常信息:

ERROR 1126 (HY000): Can't open shared library 'libgearman_mysql_udf.so' (errno: 11 libgearman.so.8: cannot open shared object file: No such file or directory)

表示系統找不到 libgearman.so 文件,通常so都在/usr/local/lib目錄下,修改配置文件/etc/ld.so.conf,將/usr/local/lib目錄加入進去便可:

$ cat /etc/ld.so.conf

include ld.so.conf.d/*.conf

/usr/local/lib

$ /sbin/ldconfig -v | grep gearman*

 

1三、MySQL Trigger調用Gearman UDF實現同步

建立觸發器

DELIMITER $$

CREATE TRIGGER test_data_to_redis AFTER UPDATE ON test FOR EACH ROW BEGIN

    SET@ret=gman_do_background('syncToRedis', json_object(NEW.id AS `id`, NEW.phone AS`phone`));

END$$;

 

DELIMITER $$

CREATE TRIGGER test_data_to_redis2 AFTER INSERT ON test

  FOR EACH ROW BEGIN

    SET @ret=gman_do_background('syncToRedis2', json_object(NEW.id AS `id`, NEW.phone AS`phone`)); 

  END$$

DELIMITER ;

 

DELIMITER $$

CREATE TRIGGER test_data_to_redis3 BEFORE DELETE ON test

  FOR EACH ROW BEGIN

    SET @ret=gman_do_background('syncToRedis3', json_object(OLD.id AS `id`, OLD.phone AS`phone`)); 

  END$$

DELIMITER ;

 

  說明以及問題:此類採用了gearman官網的java-gearman-service(地址:https://launchpad.net/gearman-java),目前release版本是0.6.6。java-gearman-servic.jar包中,即包括gearman server,還包括client和work客戶端API。

  問題:config類爲spring注入的配置文件類,在worker.addFunction中,若是經過config類的屬性,而且屬性是從配置文件來的就會有問題。不知道爲啥,寫死就是OK的。此類鏈接遠程的gearman job server。

  

  jar包須要添加到本地jar倉庫:

mvn install:install-file -Dfile=C:\software\java-gearman-service-0.6.6.jar -DgroupId=org.gearman.jgs -DartifactId=java-gearman-service -Dversion=0.6.6 -Dpackaging=jar

 

import java.util.concurrent.TimeUnit;

 

import org.gearman.Gearman;

import org.gearman.GearmanFunction;

import org.gearman.GearmanFunctionCallback;

import org.gearman.GearmanServer;

import org.gearman.GearmanWorker;

 

/**

 * *ECHO_HOST = "192.168.125.131"爲安裝了Gearman並開啓geramand服務的主機地址

 *int ECHO_PORT = 4730默認端口爲4730

 *

 * @author Administrator

 *

 */

public class EchoWorker implements GearmanFunction {

 

// function name

public static final String ECHO_FUNCTION_NAME = "syncToRedis";

 

// job server地址

public static final String ECHO_HOST = "192.168.1.245";

 

// job server監聽的端口

public static final int ECHO_PORT = 4730;

 

public static void main(String[] args) {

// 建立一個Gearman實例

Gearman gearman = Gearman.createGearman();

/*

 * 建立一個jobserver

 * 

 * Parameter 1: job server的IP地址 Parameter 2: job server監聽的端口

 * 

 * job server收到client的job,並將其分發給註冊worker

 * 

 */

GearmanServer server = gearman.createGearmanServer(EchoWorker.ECHO_HOST, EchoWorker.ECHO_PORT);

// 建立一個Gearman的worker

GearmanWorker worker = gearman.createGearmanWorker(); // 正題來了,建立work節點。

worker.setReconnectPeriod(2, TimeUnit.SECONDS); // 設置超時重連時間

worker.setMaximumConcurrency(5); // 最大併發數

// 告訴工人如何執行工做(主要實現了GearmanFunction接口)

worker.addFunction(EchoWorker.ECHO_FUNCTION_NAME, new EchoWorker());

// worker鏈接服務器

worker.addServer(server);

}

 

@Override

public byte[] work(String function, byte[] data, GearmanFunctionCallback callback) throws Exception {

// work方法實現了GearmanFunction接口中的work方法,本實例中進行了字符串的反寫

if (data != null) {

String str = new String(data);

System.out.println(str);

StringBuffer sb = new StringBuffer(str);

return sb.reverse().toString().getBytes();

} else {

return "未接收到data".getBytes();

}

}

}

 

 

import org.gearman.Gearman;  

import org.gearman.GearmanClient;  

import org.gearman.GearmanJobEvent;  

import org.gearman.GearmanJobReturn;  

import org.gearman.GearmanServer;  

  

public class EchoClient {  

    public static void main(String... args) throws InterruptedException {  

            //建立一個Gearman實例  

            Gearman gearman = Gearman.createGearman();  

            //建立一個Gearman client               

            GearmanClient client = gearman.createGearmanClient();  

            /*  

             * 建立一個jobserver  

             *   

             * Parameter 1: job server的IP地址  

             * Parameter 2: job server監聽的端口  

             *   

             *job server收到client的job,並將其分發給註冊worker  

             *  

             */  

            GearmanServer server = gearman.createGearmanServer(  

                            EchoWorker.ECHO_HOST, EchoWorker.ECHO_PORT);  

             // 告訴客戶端,提交工做時它能夠鏈接到該服務器  

            client.addServer(server);  

            /*  

             * 向job server提交工做  

             *   

             * Parameter 1: gearman function名字  

             * Parameter 2: 傳送給job server和worker的數據  

             *   

             * GearmanJobReturn返回job發熱結果  

             */  

            GearmanJobReturn jobReturn = client.submitJob(  

                            EchoWorker.ECHO_FUNCTION_NAME, ("Hello World!").getBytes());  

            //遍歷做業事件,直到咱們打到最後文件               

            while (!jobReturn.isEOF()) {  

  

                    //下一個做業事件  

                    GearmanJobEvent event = jobReturn.poll();  

  

                    switch (event.getEventType()) {  

  

                    case GEARMAN_JOB_SUCCESS:     //job執行成功  

                            System.out.println(new String(event.getData()));  

                            break;  

                    case GEARMAN_SUBMIT_FAIL:     //job提交失敗  

                          

                    case GEARMAN_JOB_FAIL:        //job執行失敗  

                            System.err.println(event.getEventType() + ": "  

                                            + new String(event.getData()));  

                    default:  

                    }  

            }  

            //關閉  

            gearman.shutdown();  

    }  

}  

 

http://gearman.org/download/

php方案:https://www.tuicool.com/articles/B7Jjaa

 

 

相關文章
相關標籤/搜索