SCF+腾讯云API+企业微信机器人实现CDB慢查询提醒

依赖

背景

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"}

打包和部署

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了

SCF 配置,关系到你需要付的成本哟
SCF运行的触发条件配置

程序的简单实现如下:

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
}

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券