SCF+騰訊雲API+企業微信機器人實現CDB慢查詢提醒
依賴
- 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
除錯獲取所有資料庫示例介面
點選線上呼叫選項卡。
將得到的JSON結果通過JSON轉Struct
轉換成GO語言需要的struct定義(參考下文程式碼實現)。
除錯獲取所有某個資料庫示例慢查詢日誌介面
將得到的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了
程式的簡單實現如下:
type InstanceResponse struct { Response struct { TotalCount int `json:"TotalCount"` Items[]struct { InstanceIDstring `json:"InstanceId"` ResourceIDstring `json:"ResourceId"` RegionIDint`json:"RegionId"` RegionNamestring `json:"RegionName"` QPSint`json:"Qps"` Regionstring `json:"Region"` InitFlagint`json:"InitFlag"` InstanceTypeint`json:"InstanceType"` InstanceNamestring `json:"InstanceName"` Vipstring `json:"Vip"` Vportint`json:"Vport"` WanStatusint`json:"WanStatus"` WanDomainstring `json:"WanDomain"` WanPortint`json:"WanPort"` Statusint`json:"Status"` CdbErrorint`json:"CdbError"` TaskStatusint`json:"TaskStatus"` EngineVersion string `json:"EngineVersion"` CreateTimestring `json:"CreateTime"` DeadlineTimestring `json:"DeadlineTime"` IsolateTimestring `json:"IsolateTime"` DeviceTypestring `json:"DeviceType"` Memoryint`json:"Memory"` Volumeint`json:"Volume"` CPUint`json:"Cpu"` AutoRenewint`json:"AutoRenew"` ZoneIDint`json:"ZoneId"` Zonestring `json:"Zone"` ZoneNamestring `json:"ZoneName"` VpcIDint`json:"VpcId"` SubnetIDint`json:"SubnetId"` UniqVpcIDstring `json:"UniqVpcId"` UniqSubnetIDstring `json:"UniqSubnetId"` ProjectIDint`json:"ProjectId"` PayTypeint`json:"PayType"` ProtectModeint`json:"ProtectMode"` DeployModeint`json:"DeployMode"` SlaveInfostruct { First struct { Region string `json:"Region"` ZoneID int`json:"ZoneId"` Zonestring `json:"Zone"` Vipstring `json:"Vip"` Vportint`json:"Vport"` } `json:"First"` Second interface{} `json:"Second"` } `json:"SlaveInfo"` MasterInfointerface{}`json:"MasterInfo"` RoInfo[]interface{} `json:"RoInfo"` RoGroups[]interface{} `json:"RoGroups"` DrInfo[]interface{} `json:"DrInfo"` BackupZoneIDint`json:"BackupZoneId"` ExClusterIDstring`json:"ExClusterId"` OfflineTimestring`json:"OfflineTime"` HourFeeStatus int`json:"HourFeeStatus"` RoVipInfointerface{}`json:"RoVipInfo"` PhysicalIDstring`json:"PhysicalId"` } `json:"Items"` RequestID string `json:"RequestId"` } `json:"Response"` } type SlowLogResponse struct { Response struct { TotalCount int `json:"TotalCount"` Items[]struct { Namestring `json:"Name"` IntranetURL string `json:"IntranetUrl"` InternetURL string `json:"InternetUrl"` Sizeint`json:"Size"` Typestring `json:"Type"` Datestring `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 }