redis資料庫數據同步
㈠ Redis 如何保持和 Mysql 數據一致
redis在啟動之後,從資料庫載入數據。
讀請求:
不要求強一致性的讀請求,走redis,要求強一致性的直接從mysql讀取
寫請求:
數據首先都寫到資料庫,之後更新redis(先寫redis再寫mysql,如果寫入失敗事務回滾會造成redis中存在臟數據)
在並發不高的情況下,讀操作優先讀取redis,不存在的話就去訪問MySQL,並把讀到的數據寫回Redis中;寫操作的話,直接寫MySQL,成功後再寫入Redis(可以在MySQL端定義CRUD觸發器,在觸發CRUD操作後寫數據到Redis,也可以在Redis端解析binlog,再做相應的操作)
在並發高的情況下,讀操作和上面一樣,寫操作是非同步寫,寫入Redis後直接返回,然後定期寫入MySQL
1.當更新數據時,如更新某商品的庫存,當前商品的庫存是100,現在要更新為99,先更新資料庫更改成99,然後刪除緩存,發現刪除緩存失敗了,這意味著資料庫存的是99,而緩存是100,這導致資料庫和緩存不一致。
解決方法:
這種情況應該是先刪除緩存,然後在更新資料庫,如果刪除緩存失敗,那就不要更新資料庫,如果說刪除緩存成功,而更新資料庫失敗,那查詢的時候只是從資料庫里查了舊的數據而已,這樣就能保持資料庫與緩存的一致性。
2.在高並發的情況下,如果當刪除完緩存的時候,這時去更新資料庫,但還沒有更新完,另外一個請求來查詢數據,發現緩存里沒有,就去資料庫里查,還是以上面商品庫存為例,如果資料庫中產品的庫存是100,那麼查詢到的庫存是100,然後插入緩存,插入完緩存後,原來那個更新資料庫的線程把資料庫更新為了99,導致資料庫與緩存不一致的情況
解決方法:
遇到這種情況,可以用隊列的去解決這個問,創建幾個隊列,如20個,根據商品的ID去做hash值,然後對隊列個數取摸,當有數據更新請求時,先把它丟到隊列里去,當更新完後在從隊列里去除,如果在更新的過程中,遇到以上場景,先去緩存里看下有沒有數據,如果沒有,可以先去隊列里看是否有相同商品ID在做更新,如果有也把查詢的請求發送到隊列里去,然後同步等待緩存更新完成。
這里有一個優化點,如果發現隊列里有一個查詢請求了,那麼就不要放新的查詢操作進去了,用一個while(true)循環去查詢緩存,循環個200MS左右,如果緩存里還沒有則直接取資料庫的舊數據,一般情況下是可以取到的。
1、讀請求時長阻塞
由於讀請求進行了非常輕度的非同步化,所以一定要注意讀超時的問題,每個讀請求必須在超時間內返回,該解決方案最大的風險在於可能數據更新很頻繁,導致隊列中擠壓了大量的更新操作在裡面,然後讀請求會發生大量的超時,最後導致大量的請求直接走資料庫,像遇到這種情況,一般要做好足夠的壓力測試,如果壓力過大,需要根據實際情況添加機器。
2、請求並發量過高
這里還是要做好壓力測試,多模擬真實場景,並發量在最高的時候QPS多少,扛不住就要多加機器,還有就是做好讀寫比例是多少
3、多服務實例部署的請求路由
可能這個服務部署了多個實例,那麼必須保證說,執行數據更新操作,以及執行緩存更新操作的請求,都通過nginx伺服器路由到相同的服務實例上
4、熱點商品的路由問題,導致請求的傾斜
某些商品的讀請求特別高,全部打到了相同的機器的相同丟列里了,可能造成某台伺服器壓力過大,因為只有在商品數據更新的時候才會清空緩存,然後才會導致讀寫並發,所以更新頻率不是太高的話,這個問題的影響並不是很大,但是確實有可能某些伺服器的負載會高一些。
img
搜索微信號(ID:芋道源碼),可以獲得各種 Java 源碼解析。
並且,回復【書籍】後,可以領取筆者推薦的各種 Java 從入門到架構的書籍。
㈡ Redis 緩存怎麼與資料庫進行數據同步
redis與memcached相比,比僅支持簡單的key-value數據類型,同時還提供list,set,zset,hash等數據結構的存儲;
redis支持數據的備份,即master-slave模式的數據備份;
㈢ php redis做mysql的緩存,怎麼非同步redis同步到mysql資料庫
對於變化頻率非常快的數據來說,如果還選擇傳統的靜態緩存方式(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的方式增加了不少流程,但是實現成本更低,更容易操作。
Gearman的安裝與使用
Gearman是一個支持分布式的任務分發框架。設計簡潔,獲得了非常廣泛的支持。一個典型的Gearman應用包括以下這些部分:
Gearman Job Server:Gearman核心程序,需要編譯安裝並以守護進程形式運行在後台
Gearman Client:可以理解為任務的收件員,比如在後台執行一個發送郵件的任務,可以在程序中調用一個Gearman Client並傳入郵件的信息,然後就可以將執行結果立即展示給用戶,而任務本身會慢慢在後台運行。
Gearman Worker:任務的真正執行者,一般需要自己編寫具體邏輯並通過守護進程方式運行,Gearman Worker接收到Gearman Client傳遞的任務內容後,會按順序處理。
以前曾經介紹過類似的後台任務處理項目Resque。兩者的設計其實非常接近,簡單可以類比為:
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
* gearmand is running
說明Gearman已經安裝成功。
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.0or later required
因此Gearman + PHP擴展建議通過編譯方式安裝,這里為了簡單說明,選擇安裝舊版本擴展:
pecl install gearman-1.0.3
Gearman + PHP實例
為了更容易理解後文Gearman的運行流程,這里不妨從一個最簡單的Gearman實例來說明,比如要進行一個文件處理的操作,首先編寫一個Gearman Client並命名為client.php:
<?php
$client =newGearmanClient();
$client->addServer();
$client->doBackground('writeLog','Log content');
echo '文件已經在後台操作';
運行這個文件,相當於模擬用戶請求一個Web頁面後,將處理結束的信息返回用戶:
php client.php
查看一下Gearman的狀況:
(echo status ; sleep 0.1)| netcat127.0.0.14730
可以看到輸出為
writeLog 100.
說明已經在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 =newGearmanWorker();
$worker->addServer();
$worker->addFunction('writeLog','writeLog');while($worker->work());function writeLog($job){
$log = $job->workload();file_put_contents(__DIR__ .'/gearman.log', $log ." ", FILE_APPEND | LOCK_EX);}
Worker使用一個while死循環實現守護進程,運行
php worker.php
可以看到Gearman狀態變為:
writeLog 001
同時查看同目錄下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 =newGearmanWorker();
$worker->addServer();
$worker->addFunction('syncToRedis','syncToRedis');
$redis =newRedis();
$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)){returnfalse;}
$redis->set($work->id, $workString);}
最後需要將Worker在後台運行:
nohup php redis_worker.php &
通過這種方式將MySQL數據復制到Redis,經測試單Worker基本可以瞬時完成。
㈣ canal+Kafka實現mysql與redis數據同步
前言
上篇文章簡單介紹canal概念,本文結合常見的緩存業務去講解canal使用。在實際開發過程中,通常都會把數據往redis緩存中保存一份,做下簡單的查詢優化。如果這時候資料庫數據發生變更操作,就不得不在業務代碼中寫一段同步更新redis的代碼,但是這種 數據同步的代碼和業務代碼糅合在一起 看起來不是很優雅,而且還會出現數據不一致問題。那能不能把這部分同步代碼從中抽離出來,形成獨立模塊呢?答案是肯定的,下面通過canal結合Kafka來實現mysql與redis之間的數據同步。
架構設計
通過上述結構設計圖可以很清晰的知道用到的組件:MySQL、Canal、Kafka、ZooKeeper、Redis。
Kafka&Zookeeper搭建
首先在 官網 下載Kafka:
Kafka內部自帶了zookeeper,所以暫不需要去下載搭建zookeeper集群,本文就使用Kafka自帶zookeeper來實現。
通過上述zookeeper啟動命令以及Kafka啟動命令把服務啟動,可以通過以下簡單實現下是否成功:
Canal搭建
canal搭建具體可以參考上文,這里只講解具體的參數配置:
找到/conf目錄下的canal.properties配置文件:
然後配置instance,找到/conf/example/instance.properties配置文件:
經過上述配置後,就可以啟動canal了。
測試
環境搭建完成後,就可以編寫代碼進行測試。
1、引入pom依賴
2、封裝Redis工具類
在application.yml文件增加以下配置:
封裝一個操作Redis的工具類:
3、創建MQ消費者進行同步
創建一個CanalBean對象進行接收:
最後就可以創建一個消費者CanalConsumer進行消費:
測試Mysql與Redis同步
mysql對應的表結構如下:
啟動項目後,新增一條數據:
可以在控制台看到以下輸出:
如果更新呢?試一下Update語句:
同樣可以在控制台看到以下輸出:
經過測試完全么有問題。
總結
既然canal這么強大,難道就沒缺點嘛?答案當然是存在的啦,比如:canal只能同步增量數據、不是實時同步而是准實時同步、MQ順序問題等; 盡管有一些缺點,畢竟沒有一樣技術或者產品是完美的,最重要是合適。比如公司目前有個視圖服務提供寬表搜索查詢功能就是通過 同步Mysql數據到Es採用Canal+Kafka的方式來實現的。
㈤ redis數據如何同步到資料庫
同步到資料庫,這應該是直接進行授權證,應該就可以進行通話,而且應該同步上的,應該比較樸素,應該能告知這些同步的。
㈥ Redis 緩存怎麼與資料庫進行數據同步
這種writer-reader架構,一般思路是在緩存更新階段由writer來解決一致性問題,當資料庫數據變化時,同步更新redis並確保緩存更新成功。
作為完整性判斷,可以不檢查全部的屬性,而對數據使用一個自增的版本號(或時間戳)來判斷是否最新。
作為後置的檢測,可以優化來降低掃描的代價,如只針對最近一個時間周期內(如10min)資料庫中更新過的數據,這個集合應該比較小,去redis中進行檢查的代價會比較低。