目录

go获取阿里云rds慢日志

初始化项目目录结构

https://xieys.club/images/posts/image-20210702183002425.png

  • conf:用于存储配置文件
  • pkg:第三方包
  • templates: 用于存储html模板
  • models 模型文件

创建配置文件

在conf目录下创建conf.ini配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[rds]
RegionId = cn-shanghai
AccessKeyId = xxxx
AccessKeySecret = xxxx
DBInstanceID = rm-uf6vn151nl1056ie5,rr-uf66d5yofh1x53p5c,rm-uf6cyi1ds2rp4ujry,rr-uf6a28b0jqz3s7b53,rm-uf6qye4g6x8tbv961,rr-uf6m0hk10zm4alj30,rm-uf6u2lq543e8iogj3,rr-uf6754hbh1f13o8n5,rm-uf6zx1o759ehie878,rr-uf67z2v92b13i6z5l

[email]
EmailForm = service@lingcb.com
EmailTo = guangqiang@lingcb.com,guangqiang@lingcb.com,liaohailong@lingcb.com,amy@lingcb.com,ben@lingcb.com
EmailAddr = smtp.exmail.qq.com:25
EmailPubTo = stevengao@lingcb.com,yanggang@lingcb.com
EmailUserName = service@lingcb.com
EmailPassword = xxxx
EmailHost = smtp.exmail.qq.com

在pkg目录下创建settings目录,在settings目录下创建setting.go文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package settings

import (
	"github.com/go-ini/ini"
	"log"
)

type Rds struct {
	RegionId string
	AccessKeyId string
	AccessKeySecret string
	DBInstanceID []string
}

type Email struct {
	EmailForm string
	EmailTo []string
	EmailPubTo []string
	EmailAddr string
	EmailUserName string
	EmailPassword string
	EmailHost string
}

var (
	RdsSetting = &Rds{}
	EmailSetting = &Email{}
)

func Setup()(err error)  {
	Cfg,err := ini.Load("conf/conf.ini")
	if err != nil{
		log.Fatalf("Fail to parse 'conf/conf.ini:%v'",err)
	}
	if err = Cfg.Section("rds").MapTo(RdsSetting);err != nil{
		return err
	}

	if err = Cfg.Section("email").MapTo(EmailSetting);err != nil{
		return err
	}
	return nil
}

创建发送邮件的方法

在pkg目录下创建utils目录,在utils目录下新建sendMail.go文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package utils

import (
	"bytes"
	"getslow/models"
	"getslow/pkg/settings"
	"gopkg.in/gomail.v2"
	"html/template"
)
var s gomail.SendCloser

func  ConnSmtp()(err error)  {
	d := gomail.NewDialer(settings.EmailSetting.EmailHost,settings.EmailSetting.EmailPort,settings.EmailSetting.EmailUserName,settings.EmailSetting.EmailPassword)
	s , err = d.Dial()
	return
}

func SendMail(index int,dateStr,dbName string,data models.TemplateBody)error{

	//创建邮件消息体
	m := gomail.NewMessage()
	// 发件人
	m.SetHeader("From", settings.EmailSetting.EmailForm)

	var sendTo = []string{
		settings.EmailSetting.EmailTo[index/2],
	}
	sendTo = append(sendTo,settings.EmailSetting.EmailPubTo...)
	// 收件人(可以有多个)
	m.SetHeader("To", sendTo...)
	// 邮件主题
	m.SetHeader( "Subject",dateStr + " RDS实例:" + dbName + " 慢查询统计")



	t,_ := template.ParseFiles("templates/email.html")

	var body bytes.Buffer
	t.Execute(&body,data)
	// html形式的消息
	m.SetBody("text/html", body.String())
	// 以路径将文件作为附件添加到邮件中
	m.Attach(data.Path)

	// 发送邮件
	return gomail.Send(s,m)
}

去重函数,由于阿里云慢日志统计是按天统计,而我们一次性是获取7天的慢日志统计,所以每天都有可能重复的语句

在utils目录下创建removal.go文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package utils

import (
	"getslow/models"
	"sort"
)

//去重函数,由于阿里云慢日志统计是按天统计,每天都有可能重复的语句
func Removal(sl models.SlowList)(total int,slowList models.SlowList){
	var result = map[string]models.SlowModel{}

	for _,v := range sl{
		value,ok := result[v.DBName+v.SQLText]
		if ok{
			result[v.DBName+v.SQLText] =  models.SlowModel{
				value.DBName,
				value.SQLText,
				value.MySQLTotalExecutionCounts + v.MySQLTotalExecutionCounts,
				value.MySQLTotalExecutionTimes + v.MySQLTotalExecutionTimes,
				(value.MaxExecutionTime + v.MaxExecutionTime)/2,
				(value.AvgExecutionTime + v.AvgExecutionTime)/2,
				value.TotalLockTimes + v.TotalLockTimes,
				(value.MaxLockTime + v.TotalLockTimes)/2,
				value.ParseTotalRowCounts + v.ParseTotalRowCounts,
				(value.ParseMaxRowCount + v.ParseMaxRowCount)/2,
				value.ReturnTotalRowCounts + v.ReturnTotalRowCounts,
				(value.ReturnMaxRowCount + v.ReturnMaxRowCount)/2,
			}

		}else {
			result[v.DBName+v.SQLText] = v
		}
	}
	for _,slow := range result{
		slowList = append(slowList,slow)
	}
	total = len(slowList)
	sort.Slice(slowList, func(i, j int) bool {
		//按总执行次数排序
		return  slowList[i].MySQLTotalExecutionCounts > slowList[j].MySQLTotalExecutionCounts
	})
	return total,slowList
}

在utils目录下创建request.go文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package utils

import (
	"fmt"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/rds"
)

type SlowLogs struct {
	DetailStartTime string
	DetailEndTime string
	Client *rds.Client
	Request *rds.DescribeSlowLogsRequest
}

func (s SlowLogs)GetTotal()int  {
	response , err := s.Client.DescribeSlowLogs(s.Request)
	if err != nil{
		fmt.Println(err.Error())
		return 0
	}
	return response.TotalRecordCount
}

func (s SlowLogs)GetSlowData(pageNum int)(response *rds.DescribeSlowLogsResponse,err error){
	s.Request.PageNumber = requests.NewInteger(pageNum)
	response , err = s.Client.DescribeSlowLogs(s.Request)
	return response,err
}

type SlowDetailRecords struct {
	DetailStartTime string
	DetailEndTime string
	Client *rds.Client
	Request *rds.DescribeSlowLogRecordsRequest
}

func (s SlowDetailRecords)GetTotal()int  {

	response , err := s.Client.DescribeSlowLogRecords(s.Request)
	if err != nil{
		fmt.Println(err.Error())
		return 0
	}
	return response.TotalRecordCount
}

func (s SlowDetailRecords)GetSlowDetailData(pageNum int)(response *rds.DescribeSlowLogRecordsResponse,err error){
	s.Request.PageNumber = requests.NewInteger(pageNum)
	response , err = s.Client.DescribeSlowLogRecords(s.Request)
	return response,err
}

在utils目录下创建Parse.go文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package utils

import (
	"getslow/models"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/rds"
)

func SlowParse(data []rds.SQLSlowLog)(slowList []models.SlowModel){
	for _,v := range data{
		slowList = append(slowList,models.SlowModel{
			DBName: v.DBName,
			SQLText: v.SQLText,
			MySQLTotalExecutionCounts: v.MySQLTotalExecutionCounts,
			MySQLTotalExecutionTimes: v.MySQLTotalExecutionTimes,
			MaxExecutionTime: v.MaxExecutionTime,
			AvgExecutionTime: v.MySQLTotalExecutionTimes / v.MySQLTotalExecutionCounts,
			TotalLockTimes: v.TotalLockTimes,
			MaxLockTime: v.MaxLockTime,
			ParseTotalRowCounts: v.ParseTotalRowCounts,
			ParseMaxRowCount: v.ParseMaxRowCount,
			ReturnTotalRowCounts: v.ReturnTotalRowCounts,
			ReturnMaxRowCount: v.ReturnMaxRowCount,
		})
	}
	return slowList
}

func SlowDetailParse(data []rds.SQLSlowRecord)(slowDetailList []models.SlowDetailModel){
	for _,v := range data{
		slowDetailList = append(slowDetailList,models.SlowDetailModel{
			DBName: v.DBName,
			SQLText: v.SQLText,
			QueryTimes: v.QueryTimes,
			LockTimes: v.LockTimes,
			ReturnRowCounts: v.ReturnRowCounts,
			ParseRowCounts: v.ParseRowCounts,
		})
	}
	return slowDetailList
}

下载依赖包

1
2
3
4
go get -u github.com/aliyun/alibaba-cloud-sdk-go/sdk
go get -u github.com/360EntSecGroup-Skylar/excelize/v2
go get -u github.com/go-ini/ini
go get -u gopkg.in/gomail.v2

添加数据模型

在models目录下添加slow.go文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package models

import (
	"fmt"
	"github.com/360EntSecGroup-Skylar/excelize/v2"
)

//慢日志统计
type SlowModel struct {
	DBName string
	SQLText string
	MySQLTotalExecutionCounts int64
	MySQLTotalExecutionTimes int64
	MaxExecutionTime int64
	AvgExecutionTime int64
	TotalLockTimes int64
	MaxLockTime int64
	ParseTotalRowCounts int64
	ParseMaxRowCount int64
	ReturnTotalRowCounts int64
	ReturnMaxRowCount int64
}

type SlowList []SlowModel



type TemplateBody struct {
	TotalRecordCount  int
	TRData  SlowList
	Path string
}

//慢日志详细
type SlowDetailModel struct {
	DBName string
	SQLText string
	QueryTimes int64
	LockTimes int64
	ReturnRowCounts int64
	ParseRowCounts int64
}

type SlowDetailList []SlowDetailModel

type SlowDetail struct {
	Instance string
	SlowDetailList
}

func (s SlowDetail)Save()  {
	f := excelize.NewFile()
	//更改sheet1名字
	f.SetSheetName("Sheet1",s.Instance)
	f.SetSheetRow(s.Instance,"A1",&[]interface{}{"数据库名","SQL语句","执行时长(秒)","锁定时长","解析行数","返回行数"})
	for i,v := range s.SlowDetailList{
		axis := fmt.Sprintf("A%d",i+2)
		f.SetSheetRow(s.Instance,axis,&[]interface{}{
			v.DBName,
			v.SQLText,
			v.QueryTimes,
			v.LockTimes,
			v.ParseRowCounts,
			v.ReturnRowCounts,
		})
	}
	f.SetActiveSheet(1)
	xlsx_name := fmt.Sprintf("%s.xlsx",s.Instance)
	// 根据指定路径保存文件
	if err := f.SaveAs(xlsx_name); err != nil {
		fmt.Println(err)
	}
}

添加html模板

在templates目录下添加email.html文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title></title>
</head>
<body>
<!-- 最外层table-->
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" style="">
    <tr>
        <td align="center" valign="top">
            <!-- 定宽table-->
            <table border="1" cellpadding="0" cellspacing="0" width="">
                <caption style="padding: 20px; font-weight: 600; font-size: 26px;">总记录:{{.TotalRecordCount}}</caption>
                <thead>
                <tr>
                    <th align="center" style="padding:5px;">数据库</th>
                    <th align="center" width="50%" style="padding:5px;">SQL语句</th>
                    <th align="center" style="padding:5px;">MySQL总执行次数</th>
                    <th align="center" style="padding:5px;">MySQL总执行时间</th>
                    <th align="center" style="padding:5px;">最大执行时长</th>
                    <th align="center" style="padding:5px;">平均执行时间</th>
                    <th align="center" style="padding:5px;">锁定总时长/秒</th>
                    <th align="center" style="padding:5px;">最大锁定时长/秒</th>

                    <th align="center" style="padding:5px;">解析SQL总行数</th>
                    <th align="center" style="padding:5px;">解析SQL最大行数</th>

                    <th align="center" style="padding:5px;">返回SQL总行数</th>
                    <th align="center" style="padding:5px;">返回SQL最大行数</th>


                </tr>
                </thead>
                <tbody>
                {{range .TRData}}
                <tr>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.DBName}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.SQLText}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.MySQLTotalExecutionCounts}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.MySQLTotalExecutionTimes}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.MaxExecutionTime}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.AvgExecutionTime}}</td>

                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.TotalLockTimes}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.MaxLockTime}}</td>

                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.ParseTotalRowCounts}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.ParseMaxRowCount}}</td>

                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.ReturnTotalRowCounts}}</td>
                    <td align="center" style="padding:5px;word-wrap:break-word; word-break:break-all;">{{.ReturnMaxRowCount}}</td>

                </tr>
                {{end}}
                </tbody>
            </table>
        </td>
    </tr>
</table>
</body>
</html>

创建主程序

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package main

import (
	"fmt"
	"getslow/models"
	"getslow/pkg/settings"
	"getslow/pkg/utils"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/rds"
	"net/http"
	"os"
	"time"
)
var (
	PageSize = 100
	MaxPage int
	Message = []string{"订单主库","订单从库","营销主库","营销从库","erp主库","erp从库","java主库","java从库","crm主库","crm从库"}
)
func main()  {
	if err := settings.Setup();err != nil{
		fmt.Println(err)
		return

	}
	//获取慢日志明细时间区间
	DetailStartTime := time.Now().Add(-(1*24*time.Hour)).Format("2006-01-02T00:00Z")
	DetailEndTime := time.Now().Format("2006-01-02T00:00Z")

	//获取慢日志统计时间区间
	//由于取7天的数据,如果数据量多有时候会导致取不到数据,所以这里就只取4天的数据
	SlowStartTime := time.Now().Add(-(3 * 24 * time.Hour)).Format("2006-01-02Z")
	SlowEndTime := time.Now().Add(-(1 * 24 * time.Hour)).Format("2006-01-02Z")
	fmt.Println(SlowStartTime,SlowEndTime)


	//	// 创建client 实例
	client,err := rds.NewClientWithAccessKey(
		settings.RdsSetting.RegionId,
		settings.RdsSetting.AccessKeyId,
		settings.RdsSetting.AccessKeySecret,
		)
	if err != nil{
		fmt.Println(err)
		return
	}
	var slowLogList models.SlowList
	var slowDetailList models.SlowDetailList
	//创建慢日志统计请求并设置参数
	SlowRequest := rds.CreateDescribeSlowLogsRequest()
	SlowRequest.Scheme = "https"
	SlowRequest.StartTime = SlowStartTime //按照阿里云的api文档 这里的时间结尾都会加上 Z
	SlowRequest.EndTime = SlowEndTime
	SlowRequest.PageSize = requests.NewInteger(PageSize)
	slowLogs := utils.SlowLogs{
		DetailStartTime: SlowStartTime,
		DetailEndTime: SlowEndTime,
		Client: client,
		Request: SlowRequest,
	}


	//创建慢日志详细请求并设置参数
	RecordRequest := rds.CreateDescribeSlowLogRecordsRequest()
	RecordRequest.Scheme = "https"
	RecordRequest.StartTime = DetailStartTime //按照阿里云的api文档 这里的时间结尾都会加上 Z
	RecordRequest.EndTime = DetailEndTime
	//每页条数,范围:30~100
	RecordRequest.PageSize = requests.NewInteger(PageSize)

	slowDetail := utils.SlowDetailRecords{
		DetailStartTime: DetailEndTime,
		DetailEndTime: DetailEndTime,
		Client: client,
		Request: RecordRequest,
	}
	if err := utils.ConnSmtp();err != nil{
		panic(err)
	}

	for index,value := range settings.RdsSetting.DBInstanceID {
		slowLogs.Request.PageNumber = requests.NewInteger(1)
		slowLogs.Request.DBInstanceId = value
		if total := slowLogs.GetTotal();total > 0{
			if total % PageSize >0{
				MaxPage = total / PageSize + 1
			}
		}else {
			fmt.Printf("实例:%s 没有查到慢日志\n",value)
			continue
		}
		for sl:=1;sl<=MaxPage;sl++{
			response ,err := slowLogs.GetSlowData(sl)
			if err != nil{
				fmt.Println(err.Error())
				continue
			}
			if response.GetHttpStatus() != http.StatusOK {
				fmt.Println(response.GetHttpStatus())
				continue
			}
			if response.TotalRecordCount == 0 {
				fmt.Printf("实例: %s 没有慢日志查询\n",value)
				continue
			}
			slowLogList = append(slowLogList,utils.SlowParse(response.Items.SQLSlowLog)...)
		}
		total,slows := utils.Removal(slowLogList)
		if total > 0 {
			// 页码,范围:大于0并且不超过int的最大值
			slowDetail.Request.PageNumber = requests.NewInteger(1)
			slowDetail.Request.DBInstanceId = value
			if total := slowDetail.GetTotal();total > 0{
				if total % PageSize >0{
					MaxPage = total / PageSize + 1
				}

			}
			for di:=1;di<=MaxPage;di++{
				response ,err := slowDetail.GetSlowDetailData(di)
				if err != nil{
					fmt.Println(err.Error())
					continue
				}
				if response.GetHttpStatus() != http.StatusOK {
					fmt.Println(response.GetHttpStatus())
					continue
				}
				if response.TotalRecordCount == 0 {
					fmt.Printf("实例: %s 没有慢日志查询\n",value)
					continue
				}
				slowDetailList = append(slowDetailList,utils.SlowDetailParse(response.Items.SQLSlowRecord)...)
			}

			slowDetail := models.SlowDetail{
				Instance: value,
				SlowDetailList: slowDetailList,
			}
			slowDetail.Save()
			body := models.TemplateBody{
				TotalRecordCount: total,
				TRData: slows,
				Path: fmt.Sprintf("./%s.xlsx",value),
			}
			err = utils.SendMail(index,time.Now().Format("2006-01-02"),Message[index],body)
			if err != nil{
				fmt.Println("email---",err.Error())
			}
			os.Remove(fmt.Sprintf("./%s.xlsx",value))
			time.Sleep(2*time.Second)
		}

	}


}