【升职加薪秘籍】我在服务监控方面的实践(6)-业务维度的mysql监控

news2024/10/6 16:16:09

大家好,我是蓝胖子,关于性能分析的视频和文章我也大大小小出了有一二十篇了,算是已经有了一个系列,之前的代码已经上传到github.com/HobbyBear/performance-analyze,接下来这段时间我将在之前内容的基础上,结合自己在公司生产上构建监控系统的经验,详细的展示如何对线上服务进行监控,内容涉及到的指标设计,软件配置,监控方案等等你都可以拿来直接复刻到你的项目里,这是一套非常适合中小企业的监控体系。

在上一节我们是讲解了如何对应用服务进行监控,这一节我将会介绍如何对mysql进行监控,在传统监控mysql(对mysql整体服务质量的监控)的情况下,建立对表级别的监控,以及长事务,复杂sql的监控,并能定位到具体代码。

监控系列的代码已经上传到github

github.com/HobbyBear/easymonitor

无论是前文提到的 机器监控还是应用监控,我们都提到了四大黄金指标原则,对mysql 建立监控指标,我们依然可以从这几个维度去对mysql的指标进行分析。

四大黄金指标是流量,延迟,饱和度,错误数。

对于流量而言可以体现在mysql数据库操作的qps,数据库服务器进行流量大小。对于延迟,可以体现在慢查询记录上,饱和度可以用数据库的连接数,线程数,或者磁盘空间,cpu,内存等各种硬件资源来反映数据库的饱和情况。错误数则可以用,数据库访问报错信息来反应,比如连接不足,超时等错误。

由于我们是用的云数据库,上面提到的这些监控维度以及面板在云厂商那里其实都基本覆盖了,我称这些监控面板或者维度是数据库的传统监控指标。 这些指标能够反应数据库监控状况,但对于开发来讲,去进行问题排查还远远不足的,下面我讲下如果只有此类型的监控会有什么缺点以及我的解决思路。

传统监控指标痛点

在使用它们对mysql进行监控时当异常发生时,不是能很好的确定是哪部分业务导致的问题。比如,当你发现数据库的qps突然升高,但是接口qps比较低的时候,如何确定数据库qps升高的原因呢? 这中间存在的问题在于mysql的数据监控指标和应用服务代码逻辑没有很好的关联性,我们要如何去建立这种关联系?

答案就是建立表级别的监控,你可以发现传统的监控指标都是对mysql整体服务质量进行的监控,而应用业务逻辑代码本质上是对表进行操作,如果建立了表级别的监控,就能将业务与数据库监控指标联系起来。比如按表级别建立单个表的qps,当发现数据库整体的qps升高时,可以发现这是由于哪张表引起的,进而定位到具体业务,查看代码逻辑看看是哪部分逻辑会操作这张表那么多次。

下面我们就来看看如何建立表级别的监控。

建立表级别的监控

mysql的performance schema其实已经暴露了表级别的某些监控项,不过由于某些原因我们线上并没有开启它,并且由于直接使用performance schema暴露的监控指标不能定制化,所以我将介绍一种在应用服务端埋点的方式建立表级别的qps。我们生产上是golang的应用服务,所以我会用它来举例。

用github.com/go-sql-driver/mysql 在golang中开启一个mysql连接是这样做:

db, err = sql.Open("mysql", connStr)

sql.Open的第一个参数是驱动名,默认的驱动名是mysql,这个驱动是引入github.com/go-sql-driver/mysql时自动去创建的。

func init() {  
   sql.Register("mysql", &MySQLDriver{})  
}

所以,我们完全可以包装默认驱动,自定义一个自己的驱动,驱动实现了open接口返回一个连接Conn的接口类型。

type Driver interface {  
  Open(name string) (Conn, error)  
}

type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}

type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)

}

自定义的驱动类型在实现Open方法时,也可以自定义一个Conn连接类型,然后再实现它的查询接口,进行sql语句分析,解析表名后进行埋点统计。

完整的定义驱动代码已经上传到文章开头的github地址,总之,你需要明白,通过对默认驱动的包装,我们可以在sql执行前后做一些自定义的监控分析。我们定义了3个钩子函数,分别针对sql执行前后以及报错做了监控分析。

对于sql埋点的原理更详细的讲解可以参考go database sql接口分析及sql埋点实现

// sql执行前做监控
if ctx, err = stmt.hooks.Before(ctx, stmt.query, list...); err != nil {  
   return nil, err  
}  
// sql执行  
results, err := stmt.execContext(ctx, args)  
if err != nil { 
   // sql 报错时做监控
   return results, handlerErr(ctx, stmt.hooks, err, stmt.query, list...)  
}
// sql执行后做监控
if _, err := stmt.hooks.After(ctx, stmt.query, list...); err != nil {  
   return nil, err  
}

在sql执行前,通过SqlMonitor.parseTable对sql语句的分析,解析出当前sql涉及的表名,以及操作类型,是insert,select,delete,还是update,并且如果sql涉及到了多张表,那么会对其打上MultiTable的标签(这在下面讲sql审计时会提到),sql执行前的钩子函数如下所示:

func (h *HookDb) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {  
   ctx = context.WithValue(ctx, ctxKeyBeginTime, time.Now())  

   // 得到sql涉及的表名,以及这条sql是属于什么crud的哪种类型
   tables, op, err := SqlMonitor.parseTable(query)  
   if err != nil || op == Unknown {  
      log.WithError(err).WithField(ctxKeySql, query).WithField("op", op).Error("parse sql fail")  
   }  
   if len(tables) >= 2 {  
      ctx = context.WithValue(ctx, ctxKeyMultiTable, 1)  
   }  
   if len(tables) >= 1 {  
      ctx = context.WithValue(ctx, ctxKeyTbName, tables[0])  
   }  
   if op != Unknown {  
      ctx = context.WithValue(ctx, ctxKeyOp, op)  
   }  
   return ctx, nil  
}

分析出了表名并且记录上了sql的执行时长,我们可以利用prometheus的histogram 类型的指标建立表维度的p99延迟分位数,并且能够知道表级别的qps数量,如下,我们可以在sql执行后的钩子函数里完成统计,MetricMonitor.RecordClientHandlerSeconds封装了这个逻辑。

func (h *HookDb) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {  
  // ....
   if tbnameInf := ctx.Value(ctxKeyTbName); tbnameInf != nil && len(tbnameInf.(string)) != 0 {  
      tableName = tbnameInf.(string)  
      MetricMonitor.RecordClientHandlerSeconds(TypeMySQL, string(ctx.Value(ctxKeyOp).(SqlOp)), tbnameInf.(string), h.dbName, now.Sub(beginTime).Seconds())  
      // .....
   }

我们可以利用这个指标在grafana上完成表级别的监控面板。

Pasted image 20230803181121.png

对于数据库还有要需要注意的地方,那就是长事务和复杂sql,慢sql的监控,往往出现上述情况时,就容易出现数据库的性能问题。现在我们来看看如何监控它们。

长事务监控

首先,来看下长事务的监控,我们可以为连接类型实现BeginTx方法,对原始driver.ConnBeginTx 事务类型进行包装,让事务携带上开始时间。

func (conn *Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {  
   tx, err := conn.Conn.(driver.ConnBeginTx).BeginTx(ctx, opts)  
   if err != nil {  
      return tx, err  
   }  
   return &DriveTx{tx: tx, start: time.Now()}, nil  
}

接着,在提交事务的时候,判断时间是不是超过某个8s,超过了则记录一条错误日志,并把堆栈信息也打印出来,这样方便定位是哪段逻辑产生的长事务。由于我们的错误等级的日志会被收集起来自动报警,这样就完成了长事务的实时监控报警。

func (d *DriveTx) Commit() error {  
   err := d.tx.Commit()  
   d.cost = time.Now().Sub(d.start).Milliseconds()  
   if d.cost > 8000 {  
      data := log.Fields{  
         Cost:       d.cost,  
         MetricType: "longTx",  
         Stack:      fmt.Sprintf("%+v", getStack()),  
      }  
      log.WithFields(data).Errorf("mysqlongTxlog ")  
   }  
   return err  
}

sql审计

接着,我们看下sql审计如何做,mysql可以打开sql审计的配置项,不过打开后将会采集所有执行的sql语句,这样会导致sql太多,我们往往只用关心那些影响性能的sql或者让数据产生变化的sql。

代码如下,我们在sql完成执行后,通过sql的执行时长,对慢sql进行告警出来,并且对涉及到两个表的sql进行日志打印,也会对修改数据的sql语句(insert,update,delete)进行记录,这对我们排查业务数据会很有帮助。

func (h *HookDb) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {  
   beginTime := time.Now()  
   if begin := ctx.Value(ctxKeyBeginTime); begin != nil {  
      beginTime = begin.(time.Time)  
   }  
   now := time.Now()  
   tableName := ""  
   if tbnameInf := ctx.Value(ctxKeyTbName); tbnameInf != nil && len(tbnameInf.(string)) != 0 {  
      tableName = tbnameInf.(string)  
      MetricMonitor.RecordClientHandlerSeconds(TypeMySQL, string(ctx.Value(ctxKeyOp).(SqlOp)), tbnameInf.(string), h.dbName, now.Sub(beginTime).Seconds())
   }  
   // 对慢sql进行实时监控,超过1s则认为是慢sql
   slowquery := false  
   if now.Sub(beginTime).Seconds() >= 1 {  
      slowquery = true  
      data := log.Fields{  
         Cost:        now.Sub(beginTime).Milliseconds(),  
         "query":     truncateKey(1024, query),  
         "args":      truncateKey(1024, fmt.Sprintf("%v", args)),  
         MetricType:  "slowLog",  
         "app":       h.app,  
         "dbName":    h.dbName,  
         "tableName": tableName,  
         "op":        ctx.Value(ctxKeyOp),  
      }  
      log.WithFields(data).Errorf("mysqlslowlog")  
   }  
   op := ctx.Value(ctxKeyOp).(SqlOp)  
   multitable := ctx.Value(ctxKeyMultiTable)  
   if !slowquery && (multitable != nil && multitable.(int) == 1) && op == Select {  
      //  对复杂sql进行监控,如果不是慢sql,但是sql涉及到两个表也会日志进行记录
      data := log.Fields{  
         Cost:        now.Sub(beginTime).Milliseconds(),  
         "query":     truncateKey(1024, query),  
         "args":      truncateKey(1024, fmt.Sprintf("%v", args)),  
         MetricType:  "multiTables",  
         "app":       h.app,  
         "dbName":    h.dbName,  
         "tableName": tableName,  
         "op":        ctx.Value(ctxKeyOp),  
      }  
      log.WithFields(data).Warnf("mysqlmultitableslog")  
   }  
   // 对修改数据的sql进行日志记录  
   if op != Select && op != Unknown {  
      data := log.Fields{  
         Cost:        now.Sub(beginTime).Milliseconds(),  
         "query":     truncateKey(1024, query),  
         "args":      truncateKey(1024, fmt.Sprintf("%v", args)),  
         MetricType:  "oplog",  
         "app":       h.app,  
         "dbName":    h.dbName,  
         "tableName": tableName,  
         "op":        ctx.Value(ctxKeyOp),  
      }  
      log.WithFields(data).Infof("mysqloplog")  
   }  
   return ctx, nil  
}

总结

这一节我们完成了对mysql的监控,不过这个监控指标是在传统数据库监控项基础上建立的,目的是为了让监控指标更加容易反映到业务上,方便问题定位,在下一节我将会演示如何对redis进行监控,与mysql监控类似,我们也需要从业务维度思考对redis的监控。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/913253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑出现msvcp120.dll丢失的解决方法,教你三招快速解决

msvcp120.dll丢失是一件很常见的问题,出现msvcp120.dll丢失会导致电脑无法在正常运行,那么应该怎么解决这个问题呢,有什么办法可以快速的解决呢,今天教你三招快速解决msvcp120.dll丢失的方法。 一.msvcp120.dll文件丢失可能导致的…

深度学习入门(五):经典网络Alexnet实现

介绍 AlexNet是一个深度卷积神经网络架构,于2010年代初在深度学习重新引起人们关注时发挥了重要作用。它因在2012年的ImageNet大规模视觉识别挑战(ILSVRC)中获胜而闻名。 实现 创建了一个AlexNet网络的实例,并将一个随机生成的…

云服务之PaaS:PaaS怎么帮助企业走向云

一、前言 企业想要平滑地向云迁移,需要选择一个适合自己的云基础框架。PaaS(平台即服务)作为云计算的重要组成部分,可以为用户提供完整的应用生命周期管理和相关资源服务。开发人员可以在PaaS框架构建和自定义应用程序,这为企业大大地节省了金…

金桥跨越相伴岁月 桂冠加冕爱意时光 GP芝柏表铭刻相爱传奇 演绎浪漫七夕

两个多世纪以前,康士坦特芝勒德 (Constant Girard) 与玛莉亚柏雷戈 (Marie Perregaux) 喜结连理,两颗心灵在爱意中交织,二人将姓氏结合,创立“Girard-Perregaux”芝柏表,成为数百年来瑞士高级制表中仅有的以夫妻双人姓…

龙迅LT7911UX TYPE-C/DP转MIPI/LVDS,内有HDCP

1. 描述 LT7911UX是一种高性能的Type-C/DP1.4a到MIPI或LVDS芯片。HDCP RX作为HDCP中继器的上游端,可以与其他芯片的HDCP TX协同工作,实现中继器的功能。 对于DP1.4a输入,LT7911UX可以配置为1/2/4车道。自适应均衡使其适用于长电缆应用&#…

设计模式(9)建造者模式

一、 1、概念:将一个复杂对象的构造与它的表示分离,使得同样的构造过程可以创建不同的表示。建造者模式主要用于创建一些复杂的对象,这些对象内部构建间的顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化;建造…

FUSE简单了解

FUSE是什么? FUSE(filesystem in userspace)是一个用户态文件系统框架。由内核模块(fuse.ko)、用户态库(libfuse.*)和挂载工具组成(fusermount) fuse最重要的特性是fuse可以允许安全、非特权挂…

设计师私藏的5个设计网站,你一定要知道。

这5个网站设计师都在用,找素材、找灵感一步到位,赶紧收藏~ 菜鸟图库 https://www.sucai999.com/?vNTYxMjky 菜鸟图库是一个非常大的素材库,站内包含设计、办公、自媒体、图片、电商等各行业素材。网站还为新手设计师提供免费的素材&#xf…

【C语言进阶(4)】指针和数组笔试题

文章目录 Ⅰ 一维数组Ⅱ 字符数组题型 1题型 2题型 3 Ⅲ 二维数组 数组名的意义 sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址。除了上述…

*****独立看门狗总结实验

1、看门狗定时计算 看门狗的喂狗时间(也就是看门狗溢 出时间)的计算方式为: Tout((42^prer) rlr) /32 其中 Tout 为看门狗溢出时间(单位为 ms)。 prer 为看门狗时钟预分频值(IWDG_PR 值)&#…

OpenGL —— 2.3、绘制第一个三角形(附源码,glfw+glad)(更新!)

源码效果 C源码 vertexShader.glsl #version 330 corelayout(location 0) in vec3 aPos; layout(location 1) in vec3 aColor;out vec4 outColor;void main() {gl_Position vec4(aPos.x, aPos.y, aPos.z, 1.0);outColor vec4(aColor, 1.0); };fragmentShader.glsl #versi…

离散Fourier变换的一种理解方法

1. 离散Fourier变换的定义 一个信号 x 的离散Fourier变换(Discrete Fourier Transform,简记为DFT)定义为 , 其逆(inverse) Fourier变换(简记为 IDFT)定义为 。 (译注:符号“≜”表示“根据定义,左边等于右边”。) 其中&#x…

电脑连接手机热点无法访问 GitHub

电脑连接手机热点无法访问 GitHub 解决方案 修改本地请求配置 修改 Windows 中 hosts 文件,路径: C:\Windows\System32\drivers\etc 添加内容: 140.82.112.4 www.github.com 140.82.112.4 github.com 提示:Windows 有权限限制&am…

智慧班牌云平台源码 (人脸识别、信息发布、校园风采、家校互通、教务管理、考勤管理)

电子班牌是一款智慧校园的管理工具,也是校园的多媒体展示平台,智慧电子班牌系统是专为学校智慧教育设计的一款智慧校园的管理工具,融合了多媒体信息发布、校园风采、家校互通、教务管理、考勤管理、日常办公等一系列应用。具备智慧教育功能和…

Mybatis的动态SQL及关键属性和标识的区别(对SQL更灵活的使用)

( 虽然文章中有大多文本内容,想了解更深需要耐心看完,必定大有受益 ) 目录 一、动态SQL ( 1 ) 是什么 ( 2 ) 作用 ( 3 ) 优点 ( 4 ) 特殊标签 ( 5 ) 演示 二、#和$的区别 2.1 #使用 ( 1 ) #占位符语法 ( 2 ) #优点 2.…

Mac Flutter web环境搭建

获取 Flutter SDK 下载以下安装包来获取最新的 stable Flutter SDK将文件解压到目标路径, 比如: cd ~/development $ unzip ~/Downloads/flutter_macos_3.13.0-stable.zip 配置 flutter 的 PATH 环境变量: export PATH"$PATH:pwd/flutter/bin" // 这个命…

java版数字藏品深色UI仿鲸探数藏盲盒合成短视频卡牌模式支持高并发

此版本为JAVA开发的版本 系统稳定 数据库MYSQL 前端uniapp 支持百万级用户,急速搭建 主要功能介绍 艺术品发售 藏品发售用户可以购买 后台藏品可设置不同稀有度 二级市场 用户的藏品可以直接挂售到二级市场商城 其他用户可以购买自己的藏品 合成 可以通过…

STM32 F103C8T6学习笔记12:红外遥控—红外解码-位带操作

今日学习一下红外遥控的解码使用,红外遥控在日常生活必不可少,它的解码与使用也是学习单片机的一个小过程,我们将通过实践来实现它。 文章提供源码、测试工程下载、测试效果图。 目录 红外遥控原理: 红外遥控特点: …

MyBatis进阶:掌握MyBatis动态SQL与模糊查询、结果映射,让你在面试中脱颖而出!!

目录 一、引言 二、MyBatis动态SQL 2.1.if元素使用 2.2.foreach元素使用 三、MyBatis模糊查询 ①使用#{字段名} ②使用${字段名} ③使用concat{%,#{字段名},%} 总结 四、MyBatis结果映射 4.1.案例演示 4.1.1.resultType进行结果映射 4.1.2.resultMap进行结果映射 …

【Java从入门到精通|1】从特点到第一个Hello World程序

写在前面 在计算机编程领域,Java是一门广泛应用的高级编程语言。它以其强大的跨平台性能、丰富的库和生态系统以及易于学习的语法而备受开发者欢迎。本文将引导您逐步了解Java的特点、如何安装和配置开发环境,以及如何编写您的第一个Java程序。 一、Java…