审计/操作/修改日志实现思路

审计日志类的需求一直都存在痛点,大部分采用与业务耦合的方式来实现。本文主要描述作者在工作中积累的几种方案,以及最近思考的新的实现方案,整体梳理成文。希望能给你提供思路,并且如果有更优雅的方式欢迎交流。

业务上的审计日志主要解决三个问题:

  1. 数据回溯,例如需要查询之前值是什么,由什么修改到了什么。
  2. 行为回溯,例如是触发了什么操作才导致的数据变化。
  3. 操作人员回溯,例如谁修改的。

以上三个方面最大的难点在于1,如果只是单纯记录本次修改后的值,实现方案会简单很多。但是如果要记录修改前的值复杂度就会高很多。

首先淘汰的方案

有些同学可能会想我可以每次都记录新的值,那么最终两次值之间的对比不就是新老值的不同了么?这种方案也是一个思路,但是存在三个问题:

  1. 如果不是新系统,那么日志的功能上线后第一批数据就是错的,因为老数据并没记录,存在冷启动的问题。
  2. 不和老值对比的情况下,就不知道发生了变化,就需要都记录下来。这样数据增长会非常快。
  3. 值的对比在获取数据过程中做,响应时间没办法保证,而且分页查询会比较复杂。

所以每次只记录新数据的方案先排除掉,不在考虑范围内,下面都是新老值都记录的实现方案。

第二淘汰的方案

最容易想到的方案,在业务代码中编写日志逻辑,和业务代码耦合严重。流程如下:


耦合业务流程.png

在该方案中有些环节可以简单处理,如果原上下文中存在更新后的值那么就不需要再查询,但是需要在方法之间不断的传递;新老值对比可以抽象工具类来实现,减少重复代码。
总体看还是侵入太大,本身不是业务流程,又是非核心功能,这种实现方式耦合太强。

第三种淘汰的方案

为了解决业务侵入的问题,想通过简单的配置来实现,产生了第三种方案。第三种方案采用业务集成SDK的方式,用来收集数据,上报到server来进行数据对比落库的方式实现。如下图:

方案三,低侵入

该方案通过mybatis的拦截器实现对update/insert/delete SQL拦截,将SQL解析。

  1. 如果是insert的那么不需要查询原值,只有新值。
  2. 如果是delete/update,那么需要解析sql,取where部分的语法节点来构造select查询来查询原值。

将查询到的原值和SQL发送到server端。server对数据和SQL进行解析就能提取新老值了,再进行对比便知道那些变更了,那些未变更。

这个方案侵入比较低,自动查询原值,并且为了减少计算负担将数据发送到server进行计算和落地。但是因为涉及到自动查询,这样就存在一个比较大的风险,如果业务做批量或者一次更新(update/delete)数据非常多,比如:

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

那么进行原值查询的时候就会爆炸,应用肯定会变的不可用,因为查询出了100W条数据在内存中。虽然该方案也做了一些防御的手段,比如查询结果大于100条,发生次数大于2次后日志功能就会关闭;并且在执行SQL中拼接了MAX_EXECUTION_TIME(例如:SELECT /*+ MAX_EXECUTION_TIME = 200 */ * FROM TABLE),如果查询原值时间超过200ms就会停止,减少服务本身的影响。但是种种的防御手段还是不能完全保证问题的出现。

之前采用这种方式时也是在不断的强调,不能批量,不能一次修改太多数据,由业务方自己控制,自己负责。😓

第四种方案

该方案并没实现,但是已验证可行,就是还没有把点串成线。在设计方案三时其实有想过这个方案但是当时问DBA告诉没有类似的办法,所以就放弃了,最近看到一些材料有了新的收获,就想起这个事是可行的。

如果想解决不在业务中进行大量的查询那么就把获取原值的操作外置吧。那怎么外置呢?binlog是一个可行的方案。那么如何将操作和binlog进行串起来呢?首先想到的是transaction id,但是尝试了下binlog中找到的id,有个xid但是应用作为client拿不到,client可以通过下面sql获取当前的trx_id,和xid不是一个概念。

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

那有没有其他ID能够串起来呢?GTID能满足需求。
GTID是5.6的新特性,开启GTID模式在主从切换时会更加准确高效。这里不介绍GTID的更多细节。

下面如果要串起来就需要在binlog中有GITD,并且在应用端也可以获取到GTID。

binlog中有GTID:
这里我放一个帖子如何开启GTID:
https://www.fordba.com/mysql57replication-mode-change-online-enable-and-disable-gtids-html.html
开启后我们在binlog event中查看如下:

GTID

应用端获取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的代码拉下来后修改client/mysql.cc,修改后的代码片段如下:

    else if( !batchmode )
      sprintf(buff,"Query OK, %lld %s affected",
          mysql_affected_rows(&mysql),
          mysql_affected_rows(&mysql) == 1LL ? "row" : "rows");
################新增开始####################
      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);
      }
################新增结束####################

然后再编译。以上步骤参考:
在mysql客户端显示gtid: https://blog.csdn.net/qq_28074313/article/details/88072410
Mac编译安装Mysql5.7.17: https://www.jianshu.com/p/1cc29d893cfc
最终实现结果,commit后GTID就直接输出了:

GTID-client

以上步骤证明client端可以获取到GTID。剩下的步骤就是如何解析到GTID了,暂时没找到好办法。根据mysql官方文档说明:

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也确实能抓到了,下图:


Wireshark

但是最后如何解析或者是不是mysql-connector-j已经帮我们解析了只是不知如何获取是下面的难点了,后面的部分待完善。

这种方案更加优雅,更加可以保证服务的可用性,并且在当前环境下公司内提供binlog的成熟解析方案已经成了标配,这样给该方案的时间又减少了很大的成本。综上来看是当前可以想到的审计日志类型的需求最优雅解法了,后面等完全实现再继续补充。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,303评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,478评论 3 415
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,230评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,936评论 1 309
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,688评论 6 409
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,174评论 1 323
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,243评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,402评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,932评论 1 334
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,771评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,971评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,514评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,209评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,631评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,863评论 1 283
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,640评论 3 391
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,949评论 2 373

推荐阅读更多精彩内容