審計(jì)/操作/修改日志實(shí)現(xiàn)思路

審計(jì)日志類(lèi)的需求一直都存在痛點(diǎn)月杉,大部分采用與業(yè)務(wù)耦合的方式來(lái)實(shí)現(xiàn)。本文主要描述作者在工作中積累的幾種方案白对,以及最近思考的新的實(shí)現(xiàn)方案,整體梳理成文握联。希望能給你提供思路,并且如果有更優(yōu)雅的方式歡迎交流每瞒。

業(yè)務(wù)上的審計(jì)日志主要解決三個(gè)問(wèn)題:

  1. 數(shù)據(jù)回溯金闽,例如需要查詢(xún)之前值是什么,由什么修改到了什么剿骨。
  2. 行為回溯代芜,例如是觸發(fā)了什么操作才導(dǎo)致的數(shù)據(jù)變化。
  3. 操作人員回溯浓利,例如誰(shuí)修改的挤庇。

以上三個(gè)方面最大的難點(diǎn)在于1钞速,如果只是單純記錄本次修改后的值,實(shí)現(xiàn)方案會(huì)簡(jiǎn)單很多嫡秕。但是如果要記錄修改前的值復(fù)雜度就會(huì)高很多渴语。

首先淘汰的方案

有些同學(xué)可能會(huì)想我可以每次都記錄新的值,那么最終兩次值之間的對(duì)比不就是新老值的不同了么昆咽?這種方案也是一個(gè)思路遵班,但是存在三個(gè)問(wèn)題:

  1. 如果不是新系統(tǒng),那么日志的功能上線(xiàn)后第一批數(shù)據(jù)就是錯(cuò)的潮改,因?yàn)槔蠑?shù)據(jù)并沒(méi)記錄,存在冷啟動(dòng)的問(wèn)題腹暖。
  2. 不和老值對(duì)比的情況下汇在,就不知道發(fā)生了變化,就需要都記錄下來(lái)脏答。這樣數(shù)據(jù)增長(zhǎng)會(huì)非掣庋常快。
  3. 值的對(duì)比在獲取數(shù)據(jù)過(guò)程中做殖告,響應(yīng)時(shí)間沒(méi)辦法保證阿蝶,而且分頁(yè)查詢(xún)會(huì)比較復(fù)雜。

所以每次只記錄新數(shù)據(jù)的方案先排除掉黄绩,不在考慮范圍內(nèi)羡洁,下面都是新老值都記錄的實(shí)現(xiàn)方案。

第二淘汰的方案

最容易想到的方案爽丹,在業(yè)務(wù)代碼中編寫(xiě)日志邏輯筑煮,和業(yè)務(wù)代碼耦合嚴(yán)重。流程如下:


耦合業(yè)務(wù)流程.png

在該方案中有些環(huán)節(jié)可以簡(jiǎn)單處理粤蝎,如果原上下文中存在更新后的值那么就不需要再查詢(xún)真仲,但是需要在方法之間不斷的傳遞;新老值對(duì)比可以抽象工具類(lèi)來(lái)實(shí)現(xiàn)初澎,減少重復(fù)代碼秸应。
總體看還是侵入太大,本身不是業(yè)務(wù)流程碑宴,又是非核心功能软啼,這種實(shí)現(xiàn)方式耦合太強(qiáng)。

第三種淘汰的方案

為了解決業(yè)務(wù)侵入的問(wèn)題延柠,想通過(guò)簡(jiǎn)單的配置來(lái)實(shí)現(xiàn)焰宣,產(chǎn)生了第三種方案。第三種方案采用業(yè)務(wù)集成SDK的方式捕仔,用來(lái)收集數(shù)據(jù)匕积,上報(bào)到server來(lái)進(jìn)行數(shù)據(jù)對(duì)比落庫(kù)的方式實(shí)現(xiàn)盈罐。如下圖:

方案三,低侵入

該方案通過(guò)mybatis的攔截器實(shí)現(xiàn)對(duì)update/insert/delete SQL攔截闪唆,將SQL解析盅粪。

  1. 如果是insert的那么不需要查詢(xún)?cè)担挥行轮怠?/li>
  2. 如果是delete/update悄蕾,那么需要解析sql票顾,取where部分的語(yǔ)法節(jié)點(diǎn)來(lái)構(gòu)造select查詢(xún)來(lái)查詢(xún)?cè)怠?/li>

將查詢(xún)到的原值和SQL發(fā)送到server端。server對(duì)數(shù)據(jù)和SQL進(jìn)行解析就能提取新老值了帆调,再進(jìn)行對(duì)比便知道那些變更了奠骄,那些未變更。

這個(gè)方案侵入比較低番刊,自動(dòng)查詢(xún)?cè)岛郏⑶覟榱藴p少計(jì)算負(fù)擔(dān)將數(shù)據(jù)發(fā)送到server進(jìn)行計(jì)算和落地。但是因?yàn)樯婕暗阶詣?dòng)查詢(xún)芹务,這樣就存在一個(gè)比較大的風(fēng)險(xiǎn)蝉绷,如果業(yè)務(wù)做批量或者一次更新(update/delete)數(shù)據(jù)非常多,比如:

update user set status = 1 where city = 110000;
-- Query OK, 1000000 row affected

那么進(jìn)行原值查詢(xún)的時(shí)候就會(huì)爆炸枣抱,應(yīng)用肯定會(huì)變的不可用熔吗,因?yàn)椴樵?xún)出了100W條數(shù)據(jù)在內(nèi)存中。雖然該方案也做了一些防御的手段佳晶,比如查詢(xún)結(jié)果大于100條桅狠,發(fā)生次數(shù)大于2次后日志功能就會(huì)關(guān)閉;并且在執(zhí)行SQL中拼接了MAX_EXECUTION_TIME(例如:SELECT /*+ MAX_EXECUTION_TIME = 200 */ * FROM TABLE),如果查詢(xún)?cè)禃r(shí)間超過(guò)200ms就會(huì)停止轿秧,減少服務(wù)本身的影響垂攘。但是種種的防御手段還是不能完全保證問(wèn)題的出現(xiàn)。

之前采用這種方式時(shí)也是在不斷的強(qiáng)調(diào)淤刃,不能批量晒他,不能一次修改太多數(shù)據(jù),由業(yè)務(wù)方自己控制逸贾,自己負(fù)責(zé)陨仅。??

第四種方案

該方案并沒(méi)實(shí)現(xiàn),但是已驗(yàn)證可行铝侵,就是還沒(méi)有把點(diǎn)串成線(xiàn)灼伤。在設(shè)計(jì)方案三時(shí)其實(shí)有想過(guò)這個(gè)方案但是當(dāng)時(shí)問(wèn)DBA告訴沒(méi)有類(lèi)似的辦法,所以就放棄了咪鲜,最近看到一些材料有了新的收獲狐赡,就想起這個(gè)事是可行的。

如果想解決不在業(yè)務(wù)中進(jìn)行大量的查詢(xún)那么就把獲取原值的操作外置吧疟丙。那怎么外置呢颖侄?binlog是一個(gè)可行的方案鸟雏。那么如何將操作和binlog進(jìn)行串起來(lái)呢?首先想到的是transaction id览祖,但是嘗試了下binlog中找到的id孝鹊,有個(gè)xid但是應(yīng)用作為client拿不到,client可以通過(guò)下面sql獲取當(dāng)前的trx_id展蒂,和xid不是一個(gè)概念又活。

SELECT tx.trx_id
FROM information_schema.innodb_trx tx
WHERE tx.trx_mysql_thread_id = connection_id();

那有沒(méi)有其他ID能夠串起來(lái)呢?GTID能滿(mǎn)足需求锰悼。
GTID是5.6的新特性柳骄,開(kāi)啟GTID模式在主從切換時(shí)會(huì)更加準(zhǔn)確高效。這里不介紹GTID的更多細(xì)節(jié)箕般。

下面如果要串起來(lái)就需要在binlog中有GITD耐薯,并且在應(yīng)用端也可以獲取到GTID。

binlog中有GTID:
這里我放一個(gè)帖子如何開(kāi)啟GTID:
https://www.fordba.com/mysql57replication-mode-change-online-enable-and-disable-gtids-html.html
開(kāi)啟后我們?cè)赽inlog event中查看如下:

GTID

應(yīng)用端獲取GTID比較困難
下面幾篇mysql官方文檔證明是可以獲取到的:

  1. https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_session_track_gtids
  2. https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_session_track_state_change
  3. https://dev.mysql.com/doc/refman/8.0/en/session-state-tracking.html
  4. https://dev.mysql.com/doc/c-api/8.0/en/mysql-session-track-get-first.html
    最后一篇文章的代碼片段中起到了很大的作用:
/* extract any available session state-change information */
enum enum_session_state_type type;
for (type = SESSION_TRACK_BEGIN; type <= SESSION_TRACK_END; type++)
{
################從這####################
  const char *data;
  size_t length;

  if (mysql_session_track_get_first(mysql, type, &data, &length) == 0)
  {
    /* print info type and initial data */
    printf("Type=%d:\n", type);
    printf("mysql_session_track_get_first(): length=%d; data=%*.*s\n",
           (int) length, (int) length, (int) length, data);
################到這####################
    /* check for more data */
    while (mysql_session_track_get_next(mysql, type, &data, &length) == 0)
    {
      printf("mysql_session_track_get_next(): length=%d; data=%*.*s\n",
             (int) length, (int) length, (int) length, data);
    }
  }
}

將mysql的代碼拉下來(lái)后修改client/mysql.cc隘世,修改后的代碼片段如下:

    else if( !batchmode )
      sprintf(buff,"Query OK, %lld %s affected",
          mysql_affected_rows(&mysql),
          mysql_affected_rows(&mysql) == 1LL ? "row" : "rows");
################新增開(kāi)始####################
      const char *data;
      size_t length;
      if(mysql_session_track_get_first(&mysql, SESSION_TRACK_GTIDS, &data, &length) == 0)
      {
          printf("mysql_session_track_get_first: length=%d;data=%*.*s\n", (int)length, (int)length, (int)length, data);
      }
################新增結(jié)束####################

然后再編譯。以上步驟參考:
在mysql客戶(hù)端顯示gtid: https://blog.csdn.net/qq_28074313/article/details/88072410
Mac編譯安裝Mysql5.7.17: http://www.reibang.com/p/1cc29d893cfc
最終實(shí)現(xiàn)結(jié)果鸠踪,commit后GTID就直接輸出了:

GTID-client

以上步驟證明client端可以獲取到GTID丙者。剩下的步驟就是如何解析到GTID了,暫時(shí)沒(méi)找到好辦法营密。根據(jù)mysql官方文檔說(shuō)明:

Controls whether the server returns GTIDs to the client, enabling the client to use them to track the server state. Depending on the variable value, at the end of executing each transaction, the server’s GTIDs are captured and returned to the client as part of the acknowledgement.

GTID作為ack的一部分返回械媒,使用Wireshark也確實(shí)能抓到了,下圖:


Wireshark

但是最后如何解析或者是不是mysql-connector-j已經(jīng)幫我們解析了只是不知如何獲取是下面的難點(diǎn)了评汰,后面的部分待完善纷捞。

這種方案更加優(yōu)雅,更加可以保證服務(wù)的可用性被去,并且在當(dāng)前環(huán)境下公司內(nèi)提供binlog的成熟解析方案已經(jīng)成了標(biāo)配主儡,這樣給該方案的時(shí)間又減少了很大的成本。綜上來(lái)看是當(dāng)前可以想到的審計(jì)日志類(lèi)型的需求最優(yōu)雅解法了惨缆,后面等完全實(shí)現(xiàn)再繼續(xù)補(bǔ)充糜值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坯墨,隨后出現(xiàn)的幾起案子寂汇,更是在濱河造成了極大的恐慌,老刑警劉巖捣染,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骄瓣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡耍攘,警方通過(guò)查閱死者的電腦和手機(jī)榕栏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)畔勤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人臼膏,你說(shuō)我怎么就攤上這事硼被。” “怎么了渗磅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵嚷硫,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我始鱼,道長(zhǎng)仔掸,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任医清,我火速辦了婚禮起暮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘会烙。我一直安慰自己负懦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布柏腻。 她就那樣靜靜地躺著纸厉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪五嫂。 梳的紋絲不亂的頭發(fā)上颗品,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音沃缘,去河邊找鬼躯枢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛槐臀,可吹牛的內(nèi)容都是我干的锄蹂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼水慨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼败匹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起讥巡,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掀亩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后欢顷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體槽棍,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炼七。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缆巧。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豌拙,靈堂內(nèi)的尸體忽然破棺而出陕悬,到底是詐尸還是另有隱情,我是刑警寧澤按傅,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布捉超,位于F島的核電站,受9級(jí)特大地震影響唯绍,放射性物質(zhì)發(fā)生泄漏拼岳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一况芒、第九天 我趴在偏房一處隱蔽的房頂上張望惜纸。 院中可真熱鬧,春花似錦绝骚、人聲如沸耐版。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粪牲。三九已至,卻和暖如春蛾魄,著一層夾襖步出監(jiān)牢的瞬間虑瀑,已是汗流浹背湿滓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工滴须, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叽奥。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓扔水,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親朝氓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子魔市,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容