【菜鳥玩Linux】通過MySQL自動同步刷新Redis

来源:http://www.cnblogs.com/zhxilin/archive/2016/09/30/5923671.html
-Advertisement-
Play Games

在服務端開發過程中,一般會使用MySQL等關係型資料庫作為最終的存儲引擎,Redis其實也可以作為一種鍵值對型的資料庫,但在一些實際場景中,特別是關係型結構並不適合使用Redis直接作為資料庫。這倆家伙簡直可以用“男女搭配,幹活不累”來形容,搭配起來使用才能事半功倍。本篇我們就這兩者如何合理搭配以及... ...


在服務端開發過程中,一般會使用MySQL等關係型資料庫作為最終的存儲引擎,Redis其實也可以作為一種鍵值對型的資料庫,但在一些實際場景中,特別是關係型結構並不適合使用Redis直接作為資料庫。這倆家伙簡直可以用“男女搭配,幹活不累”來形容,搭配起來使用才能事半功倍。本篇我們就這兩者如何合理搭配以及他們之間數據如何進行同步展開。

一般地,Redis可以用來作為MySQL的緩存層。為什麼MySQL最好有緩存層呢?想象一下這樣的場景:在一個多人線上的游戲里,排行榜、好友關係、隊列等直接關係數據的情景下,如果直接和MySQL正面交手,大量的數據請求可能會讓MySQL疲憊不堪,甚至過量的請求將會擊穿資料庫,導致整個數據服務中斷,資料庫性能的瓶頸將掣肘業務的開發;那麼如果通過Redis來做數據緩存,將大大減小查詢數據的壓力。在這種架子里,當我們在業務層有數據查詢需求時,先到Redis緩存中查詢,如果查不到,再到MySQL資料庫中查詢,同時將查到的數據更新到Redis里;當我們在業務層有修改插入數據需求時,直接向MySQL發起請求,同時更新Redis緩存。

在上面這種架子中,有一個關鍵點,就是MySQL的CRUD發生後自動地更新到Redis里,這需要通過MySQL UDF來實現。具體來說,我們把更新Redis的邏輯放到MySQL中去做,即定義一個觸發器Trigger,監聽CRUD這些操作,當操作發生後,調用對應的UDF函數,遠程寫回Redis,所以業務邏輯只需要負責更新MySQL就行了,剩下的交給MySQL UDF去完成。

一. 什麼是UDF

UDF,是User Defined Function的縮寫,用戶定義函數。MySQL支持函數,也支持自定義的函數。UDF比存儲方法有更高的執行效率,並且支持聚集函數。

UDF定義了5個API:xxx_init()、xxx_deinit()、xxx()、xxx_add()、xxx_clear()。官方文檔(http://dev.mysql.com/doc/refman/5.7/en/adding-udf.html)給出了這些API的說明。相關的結構體定義在mysql_com.h里,它又被mysql.h包含,使用時只需#include<mysql.h>即可。他們之間的關係和執行順序可以以下圖來表示:

1. xxx()

這是主函數,5個函數至少需要xxx(),對MySQL操作的結果在此返回。函數的聲明如下:

char *xxx(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error);

long long xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

double xxx(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error);

SQL的類型和C/C++類型的映射:

SQL Type C/C++ Type
STRING char *
INTEGER long long
REAL double

2. xxx_init()

xxx()主函數的初始化,如果定義了,則用來檢查傳入xxx()的參數數量、類型、分配記憶體空間等初始化操作。函數的聲明如下:

my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message);

3. xxx_deinit()

xxx()主函數的反初始化,如果定義了,則用來釋放初始化時分配的記憶體空間。函數的聲明如下:

void xxx_deinit(UDF_INIT *initid);

4. xxx_add()

在聚合UDF中反覆調用,將參數加入聚合參數中。函數的聲明如下:

void xxx_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null,char *error);

5. xxx_clear()

在聚合UDF中反覆調用,重置聚合參數,為下一行數據的操作做準備。函數的聲明如下:

void name_clear(UDF_INIT *initid, char *is_null, char *error);

二. UDF函數的基本使用

在此之前,需要先安裝mysql的開發包:

[root@localhost zhxilin]# yum install mysql-devel -y

我們定義一個最簡單的UDF主函數:

 1 /*simple.cpp*/
 2 #include <mysql.h>
 3 
 4 extern "C" long long simple_add(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
 5 {
 6     int a = *((long long *)args->args[0]);
 7     int b = *((long long *)args->args[1]);
 8     return a + b;
 9 }
10 
11 extern "C" my_bool testadd_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
12 {
13     return 0;
14 }

由於mysql提供的介面是C實現的,我們在C++中使用時需要添加:

extern "C" { ... }

接下來編譯成動態庫.so:

[zhxilin@localhost mysql-redis-test]$ g++ -shared -fPIC -I /usr/include/mysql -o simple_add.so simple.cpp

-shared 表示編譯和鏈接時使用的是全局共用的類庫;

-fPIC編譯器輸出位置無關的目標代碼,適用於動態庫;

-I /usr/include/mysql 指明包含的頭文件mysql.h所在的位置。

編譯出simple_add.so後用root拷貝到/usr/lib64/mysql/plugin下:

[root@localhost mysql-redis-test]# cp simple_add.so /usr/lib64/mysql/plugin/

緊接著可以在MySQL中創建函數執行了。登錄MySQL,創建關聯函數:

mysql> CREATE FUNCTION simple_add RETURNS INTEGER SONAME 'simple_add.so';
Query OK, 0 rows affected (0.04 sec)

測試UDF函數:

mysql> select simple_add(10, 5);
+-------------------+
| simple_add(10, 5) |
+-------------------+
|                15 |
+-------------------+
1 row in set (0.00 sec)

可以看到,UDF正確執行了加法。

創建UDF函數的語法是 CREATE FUNCTION xxx RETURNS [INTEGER/STRING/REAL] SONAME '[so name]';

刪除UDF函數的語法是 DROP FUNCTION simple_add;

mysql> DROP FUNCTION simple_add;
Query OK, 0 rows affected (0.03 sec)

三. 在UDF中訪問Redis

跟上述做法一樣,只需在UDF里調用Redis提供的介面函數。Redis官方給出了Redis C++ Client (https://github.com/mrpi/redis-cplusplus-client),封裝了Redis的基本操作。

源碼是依賴boost,需要先安裝boost:

[root@localhost dev]# yum install boost boost-devel

然後下載redis cpp client源碼:

[root@localhost dev]# git clone https://github.com/mrpi/redis-cplusplus-client

使用時需要把redisclient.h、anet.h、fmacros.h、anet.c 這4個文件考到目錄下,開始編寫關於Redis的UDF。我們定義了redis_hset作為主函數,連接Redis並調用hset插入哈希表,redis_hset_init作為初始化,檢查參數個數和類型。

 1 /* test.cpp */
 2 #include <stdio.h>
 3 #include <mysql.h>
 4 #include "redisclient.h"
 5 using namespace boost;
 6 using namespace std;
 7 
 8 static redis::client *m_client = NULL;
 9 
10 extern "C" char *redis_hset(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
11     try {
12         // 連接Redis
13         if(NULL == m_client) {
14             const char* c_host = getenv("REDIS_HOST");
15             string host = "127.0.0.1";
16             if(c_host) {
17                 host = c_host;
18             }
19             m_client = new redis::client(host);
20         }        
21 
22         if(!(args->args && args->args[0] && args->args[1] && args->args[2])) {
23             *is_null = 1;
24             return result;
25         }
26 
27         // 調用hset插入一個哈希表
28         if(m_client->hset(args->args[0], args->args[1], args->args[2])) {
29             return result;
30         } else {
31             *error = 1;
32             return result;
33         }
34     } catch (const redis::redis_error& e) {
35         return result;
36     }
37 }
38 
39 extern "C" my_bool redis_hset_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
40     if (3 != args->arg_count) {
41         // hset(key, field, value) 需要三個參數
42         strncpy(message, "Please input 3 args for: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
43         return -1;
44     }
45     if (args->arg_type[0] != STRING_RESULT  || 
46         args->arg_type[1] != STRING_RESULT  || 
47         args->arg_type[2] != STRING_RESULT) { 
48         // 檢查參數類型
49         strncpy(message, "Args type error: hset('key', 'field', 'value');", MYSQL_ERRMSG_SIZE);
50         return -1;
51     }
52 
53     args->arg_type[0] = STRING_RESULT;
54     args->arg_type[1] = STRING_RESULT;
55     args->arg_type[2] = STRING_RESULT;
56 
57     initid->ptr = NULL;
58     return 0;
59 }

編譯鏈接:

[zhxilin@localhost mysql-redis-test]$ g++ -shared -fPIC -I /usr/include/mysql -lboost_serialization -lboost_system -lboost_thread -o libmyredis.so anet.c test.cpp

編譯時需要加上-lboost_serialization -lboost_system -lboost_thread, 表示需要鏈接三個動態庫:libboost_serialization.so、libboost_system.so、libboost_thread.so,否則在運行時會報缺少函數定義的錯誤。

編譯出libmyredis.so之後,將其拷貝到mysql的插件目錄下並提權:

[root@localhost mysql-redis-test]# cp libmyredis.so /usr/lib64/mysql/plugin/ & chmod 777 /usr/lib64/mysql/plugin/libmyredis.so 

完成之後登錄MySQL,創建關聯函數測試一下:

mysql> DROP FUNCTION IF EXISTS `redis_hset`;
Query OK, 0 rows affected (0.16 sec)

mysql> CREATE FUNCTION redis_hset RETURNS STRING SONAME 'libmyredis.so';
Query OK, 0 rows affected (0.02 sec)

先刪除老的UDF,註意函數名加反引號(``)。調用UDF測試,返回0,執行成功:

mysql> SELECT redis_hset('zhxilin', 'id', '09388334');
+-----------------------------------------+
| redis_hset('zhxilin', 'id', '09388334') |
+-----------------------------------------+
| 0                                                     |
+-----------------------------------------+
1 row in set (0.00 sec)

打開redis-cli,查看結果:

127.0.0.1:6379> HGETALL zhxilin
1) "id"
2) "09388334"

四. 通過MySQL觸發器刷新Redis

 在上一節的基礎上,我們想讓MySQL在增刪改查的時候自動調用UDF,還需要藉助MySQL觸發器。觸發器可以監聽INSERT、UPDATE、DELETE等基本操作。在MySQL中,創建觸發器的基本語法如下:

CREATE TRIGGER trigger_name
trigger_time
trigger_event ON table_name
FOR EACH ROW
trigger_statement

trigger_time表示觸發時機,值為AFTERBEFORE

trigger_event表示觸發的事件,值為INSERTUPDATEDELETE等;

trigger_statement表示觸發器的程式體,可以是一句SQL語句或者調用UDF。

在trigger_statement中,如果有多條SQL語句,需要用BEGIN...END包含起來:

BEGIN
[statement_list]
END

由於MySQL預設的結束分隔符是分號(;),如果我們在BEGIN...END中出現了分號,將被標記成結束,此時沒法完成觸發器的定義。有一個辦法,可以調用DELIMITER命令來暫時修改結束分隔符,用完再改會分號即可。比如改成$:

mysql> DELIMITER $

我們開始定義一個觸發器,監聽對Student表的插入操作,Student表在上一篇文章中創建的,可以查看上一篇文章。

mysql > DELIMITER $
      > CREATE TRIGGER tg_student 
      > AFTER INSERT on Student 
      > FOR EACH ROW 
      > BEGIN
      > SET @id = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'id', CAST(new.Sid AS CHAR(8))));
      > SET @name = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'name', CAST(new.Sname AS CHAR(20))));
      > Set @age = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'age', CAST(new.Sage AS CHAR))); 
      > Set @gender = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'gender', CAST(new.Sgen AS CHAR))); 
      > Set @dept = (SELECT redis_hset(CONCAT('stu_', new.Sid), 'department', CAST(new.Sdept AS CHAR(10))));    
      > END $

創建完觸發器可以通過show查看,或者drop刪除:

mysql> SHOW TRIGGERS;
mysql> DROP TRIGGER tg_student;

接下來我們調用一句插入語句,然後觀察Redis和MySQL數據的變化:

mysql> INSERT INTO Student VALUES('09388165', 'Rose', 19, 'F', 'SS3-205');
Query OK, 1 row affected (0.27 sec)

MySQL的結果:

mysql> SELECT * FROM Student;
+----------+---------+------+------+---------+
| Sid      | Sname   | Sage | Sgen | Sdept   |
+----------+---------+------+------+---------+
| 09388123 | Lucy    |   18 | F    | AS2-123 |
| 09388165 | Rose    |   19 | F    | SS3-205 |
| 09388308 | zhsuiy  |   19 | F    | MD8-208 |
| 09388318 | daemon  |   18 | M    | ZS4-630 |
| 09388321 | David   |   20 | M    | ZS4-731 |
| 09388334 | zhxilin |   20 | M    | ZS4-722 |
+----------+---------+------+------+---------+
6 rows in set (0.00 sec)

Redis的結果:

127.0.0.1:6379> HGETALL stu_09388165
 1) "id"
 2) "09388165"
 3) "name"
 4) "Rose"
 5) "age"
 6) "19"
 7) "gender"
 8) "F"
 9) "department"
10) "SS3-205"

以上結果表明,當MySQL插入數據時,通過觸發器調用UDF,實現了自動刷新Redis的數據。另外,調用MySQL插入的命令,可以通過C++實現,進而就實現了在C++的業務邏輯里,只需調用MySQL++的介面就能實現MySQL資料庫和Redis緩存的更新,這部分內容在上一篇文章已經介紹過了。

 

總結

通過實踐,能體會到MySQL和Redis是多麼相親相愛吧!^_^

本篇文章講了從最基礎的UDF開始,再到通過UDF連接Redis插入數據,再進一步介紹通過MySQL Trigger自動更新Redis數據的整個思路,實現了一個目標,即只在業務代碼中更新MySQL資料庫,進而Redis能夠自動同步刷新。

MySQL對UDF函數和觸發器的支持,使得實現Redis數據和MySQL自動同步成了可能。當然UDF畢竟是通過插件的形式運行在MySQL中的,並沒有過多的安全干預,一旦插件發生致命性崩潰,有可能MySQL也會掛,所以在編寫UDF的時候需要非常謹慎!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、添加自定義字體 1.把字體文件拖到工程中。 2.plist 文件中添加欄位:<Array>Fonts provided by application</Array> 把字體文件全名添加到數組中。 3.使用以下代碼找出添加的字體名稱。 4.使用 [UIFont fontWithName] 方法獲取 ...
  • Android中Listview點擊item不變顏色以及設置listselector 無效 這是同一個問題,Listview中點擊item是會變顏色的,因為listview設置了預設的listselector,有一個預設的顏色,同理如果點擊沒顏色變化我們怎麼設置listselector也不會變顏色的 ...
  • 關於iOS模塊化開發解決方案網上也有一些介紹,但真正落實在在具體的實例卻很少看到,計劃編寫系統文章來介紹關於我對模塊化解決方案的理解,裡面會有包含到一些關於解耦、路由、封裝、私有Pod管理等內容;並編寫的一個實例項目放在git進行開源[jiaModuleDemo],裡面現在已經放著一些封裝的功能模塊 ...
  • 死鎖的定義: 1、一般的死鎖 一般的死鎖是指多個線程的執行必須同時擁有多個資源,由於不同的線程需要的資源被不同的線程占用,最終導致僵持的狀態,這就是一般死鎖的定義。 package com.cxt.thread; public class TestDeadLock extends Thread{ b ...
  • redis下載地址:https://github.com/MSOpenTech/redis/releases。 Redis 的配置文件位於 Redis 安裝目錄下,文件名為redis.windows.conf,以前好像是 redis.conf,不過我下載的版本是redis.windows.conf, ...
  • 分組函數 什麼是分組函數 分組函數作用於一組數據,並對一組數據返回一個值 組函數類型:主要有6種 平均 計數 最大 最小 求和 方差 組函數語法 AVG(平均值)和 SUM (合計)函數 可以對 數值型數據 使用AVG 和 SUM 函數。 MIN(最小值)和 MAX(最大值)函數 可以對 任意數據類 ...
  • 【知識點整理】Oracle中NOLOGGING、APPEND、ARCHIVE和PARALLEL下,REDO、UNDO和執行速度的比較 1 BLOG文檔結構圖 2 前言部分 2.1 導讀和註意事項 各位技術愛好者,看完本文後,你可以掌握如下的技能,也可以學到一些其它你所不知道的知識,~O(∩_∩)O~ ...
  • 最近抽時間把Redis學了一下,所以就在網上找了一些資料。然後找到 尚矽谷 周陽 老師的視頻教程,覺得裡面的講的挺好。所以就把他視頻當中的資料教程整理出來。 單機MySQL的美好時代 在90年代,一個網站的訪問量一般都不大,用單個資料庫完全可以輕鬆應付。 在那個時候,更多的都是靜態網頁,動態交互類型 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...