依赖
- SCF 无服务器云函数:https://console.cloud.tencent.com/scf
- CDB 云关系型数据库:https://console.cloud.tencent.com/cdb
- 企业微信机器人:https://work.weixin.qq.com/api/doc#14812
背景
CDB数据库在腾讯云控制台可以看到每个数据库示例的操作日志,其中我们可以下载到:
- 没有使用索引的查询
- 查询时间超过指定时间的查询
但是,为了找到这些慢查询日志的下载路径,在不介入API调用的情况下,需要在控制台点开每个数据库示例,找到慢查询日志的下载TAB页,进行慢查询日志的下载,在集中对数据库进行索引和查询优化的时候,这种操作是需要一定成本哒。
机器人方案
为了能够每天早晨或者某个时间段自动汇总所有数据库示例的慢查询日志的下载链接给到开发同学下载统一分析,我们可以利用腾讯云CDB备份相关的接口来完成CDB慢查询日志的获取,汇总所有的查询结果,通过企业微信群机器人WebHook将消息发送到指定群。
考虑到SCF代码部署的方便性,采用golang来进行功能的开发。
流程
- 腾讯云API调试工具
- golang开发IDE/EDITOR
调试获取所有数据库示例接口
在线调试地址:https://console.cloud.tencent.com/api/explorer?Product=cvm&Version=2017-03-12&Action=DescribeInstances
点击在线调用选项卡。
将得到的JSON结果通过JSON转Struct
转换成GO语言需要的struct定义(参考下文代码实现)。
调试获取所有某个数据库示例慢查询日志接口
在线调试地址:https://console.cloud.tencent.com/api/explorer?Product=cdb&Version=2017-03-20&Action=DescribeSlowLogs
将得到的JSON结果通过JSON转Struct
转换成GO语言需要的struct定义(参考下文代码实现)。
测试通过后,将代码集成
- SCF 支持多线程运行,因此我们可以使用go协程,并行请求CDB示例的慢查询结果,减少SCF运行时间,省钱呐
func main() {
cloudfunction.Start(MainFunc)
}
- main函数有固定写法,需要采用标准姿势,如果不采用标准姿势,会导致SCF执行超时,浪费钱哟
不按照标准姿势的你,可能会遇到SCF在超时结束
之后,得到这样的返回值撒
{"errorCode":-1,"errorMessage":"Time limit exceeded"}
打包和部署
代码语言:txt复制Golang 环境的云函数,仅支持 zip 包上传,可以选择使用本地上传 zip 包或通过 COS 对象存储引用 zip 包。zip 包内包含的应该是编译后的可执行二进制文件,二进制文件需要在 zip 包根目录,注意打包成zip包的时候不要多了一层文件夹呀。 Golang 编译可以在任意平台上通过制定 OS 及 ARCH 完成跨平台的编译,因此在 Linux,Windows 或 MacOS 下都可以进行编译。 在 Linux 或 MacOS 下通过如下方法完成编译及打包: SCF需要和你的数据库在同一个地域,比如都在广州或者上海GOOS=linux GOARCH=amd64 go build -o main main.go zip main.zip main
在 Windows 下可使用如下命令编译
set GOOS=linux
set GOARCH=amd64
go build -o main main.go
然后,就是配置SCF了
程序的简单实现如下:
代码语言:txt复制type InstanceResponse struct {
Response struct {
TotalCount int `json:"TotalCount"`
Items []struct {
InstanceID string `json:"InstanceId"`
ResourceID string `json:"ResourceId"`
RegionID int `json:"RegionId"`
RegionName string `json:"RegionName"`
QPS int `json:"Qps"`
Region string `json:"Region"`
InitFlag int `json:"InitFlag"`
InstanceType int `json:"InstanceType"`
InstanceName string `json:"InstanceName"`
Vip string `json:"Vip"`
Vport int `json:"Vport"`
WanStatus int `json:"WanStatus"`
WanDomain string `json:"WanDomain"`
WanPort int `json:"WanPort"`
Status int `json:"Status"`
CdbError int `json:"CdbError"`
TaskStatus int `json:"TaskStatus"`
EngineVersion string `json:"EngineVersion"`
CreateTime string `json:"CreateTime"`
DeadlineTime string `json:"DeadlineTime"`
IsolateTime string `json:"IsolateTime"`
DeviceType string `json:"DeviceType"`
Memory int `json:"Memory"`
Volume int `json:"Volume"`
CPU int `json:"Cpu"`
AutoRenew int `json:"AutoRenew"`
ZoneID int `json:"ZoneId"`
Zone string `json:"Zone"`
ZoneName string `json:"ZoneName"`
VpcID int `json:"VpcId"`
SubnetID int `json:"SubnetId"`
UniqVpcID string `json:"UniqVpcId"`
UniqSubnetID string `json:"UniqSubnetId"`
ProjectID int `json:"ProjectId"`
PayType int `json:"PayType"`
ProtectMode int `json:"ProtectMode"`
DeployMode int `json:"DeployMode"`
SlaveInfo struct {
First struct {
Region string `json:"Region"`
ZoneID int `json:"ZoneId"`
Zone string `json:"Zone"`
Vip string `json:"Vip"`
Vport int `json:"Vport"`
} `json:"First"`
Second interface{} `json:"Second"`
} `json:"SlaveInfo"`
MasterInfo interface{} `json:"MasterInfo"`
RoInfo []interface{} `json:"RoInfo"`
RoGroups []interface{} `json:"RoGroups"`
DrInfo []interface{} `json:"DrInfo"`
BackupZoneID int `json:"BackupZoneId"`
ExClusterID string `json:"ExClusterId"`
OfflineTime string `json:"OfflineTime"`
HourFeeStatus int `json:"HourFeeStatus"`
RoVipInfo interface{} `json:"RoVipInfo"`
PhysicalID string `json:"PhysicalId"`
} `json:"Items"`
RequestID string `json:"RequestId"`
} `json:"Response"`
}
type SlowLogResponse struct {
Response struct {
TotalCount int `json:"TotalCount"`
Items []struct {
Name string `json:"Name"`
IntranetURL string `json:"IntranetUrl"`
InternetURL string `json:"InternetUrl"`
Size int `json:"Size"`
Type string `json:"Type"`
Date string `json:"Date"`
} `json:"Items"`
RequestID string `json:"RequestId"`
} `json:"Response"`
}
func main() {
cloudfunction.Start(MainFunc)
}
func MainFunc() {
credential := common.NewCredential(
"",
"",
)
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "cdb.tencentcloudapi.com"
client, _ := cdb.NewClient(credential, "ap-guangzhou", cpf)
insRequest := cdb.NewDescribeDBInstancesRequest()
insParams := `{"Limit":100}`
err := insRequest.FromJsonString(insParams)
if err != nil {
panic(err)
}
responseIns, err := client.DescribeDBInstances(insRequest)
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Println(err)
return
}
if err != nil {
panic(err)
}
var response InstanceResponse
err = json.Unmarshal([]byte(responseIns.ToJsonString()), &response)
if err != nil {
fmt.Println(err)
return
}
instanceCount := len(response.Response.Items)
if instanceCount == 0 {
fmt.Println("no instance")
return
}
var wg sync.WaitGroup
wg.Add(instanceCount)
slowLogChan := make(chan SlowLogResponse, instanceCount)
for idx := 0; idx < instanceCount; idx {
go SlowLogRequest(response.Response.Items[idx].InstanceID, slowLogChan, &wg)
}
wg.Wait()
close(slowLogChan)
var msgBuf bytes.Buffer //消息buf
for res := range slowLogChan {
if len(res.Response.Items) > 1 && res.Response.Items[1].Size > 0 {
logName, err := url.QueryUnescape(res.Response.Items[1].Name)
if err != nil {
continue
}
msgBuf.WriteString(fmt.Sprintf(">Name: %s \n", logName))
msgBuf.WriteString(fmt.Sprintf(">Size: %d B\n", res.Response.Items[1].Size))
msgBuf.WriteString(fmt.Sprintf("[点击下载](%s) \n\n", res.Response.Items[1].InternetURL))
}
}
msgStr := msgBuf.String()
if msgStr == "<nil>" || msgStr == "" {
msgStr = "<font color="success">很好~</font>昨天没有产生慢查询日志\n"
} else {
msgStr = "### 昨日CDB慢查询提醒 \n\n" msgStr
}
sendMarkdownToUs(msgStr)
fmt.Println("exit...")
return
}
func sendMarkdownToUs(msgStr string) {
param := fmt.Sprintf(`{"msgtype": "markdown",
"markdown": {
"content":"%s"
}}`, msgStr)
fmt.Println(param)
resp, err := http.Post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的企业微信机器人key",
"application/json",
strings.NewReader(param))
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("send msg error")
}
return
}
func SlowLogRequest(instanceId string, ch chan SlowLogResponse, wg *sync.WaitGroup) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
wg.Done()
}()
credential := common.NewCredential(
"",
"",
)
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "cdb.tencentcloudapi.com"
client, _ := cdb.NewClient(credential, "ap-guangzhou", cpf)
request := cdb.NewDescribeSlowLogsRequest()
params := fmt.Sprintf(`{"InstanceId":"%s", "Limit":100}`, instanceId)
err := request.FromJsonString(params)
if err != nil {
panic(err)
}
response, err := client.DescribeSlowLogs(request)
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Println(err)
return
}
if err != nil {
panic(err)
}
var slowLogResponse SlowLogResponse
err = json.Unmarshal([]byte(response.ToJsonString()), &slowLogResponse)
if err != nil {
fmt.Println(err)
return
}
ch <- slowLogResponse
return
}