Go實戰準備工作---建立攜程池和定時任務

棋佈發表於2020-10-08
我們的業務可能用不上攜程池,很多同事也不是很懂,之前和同事們溝通了一下,很多人對單例都理解不了,任務沒有必要,甚至用PHP寫單例都不會寫,也從沒想過使用,那可想而知,其他的設計模式一概不知了。這就是一直做PHP,沒做過其他語言的弊端,在此也建議其他人多多接觸下其他語言的概念,尤其是一些通用型的,業務上用不上,我們可以給在自己增加難度的來使用,不能總是守著一門指令碼做個五六年不放吧。雖然用不上,還是寫上,比如一些耗時的操作,我這邊會當作優化來處理。目前IOC(控制反轉)DI(依賴注入),等都沒用上過,這次的改寫都用上。之前做的Go開發工作都是改改bug,做些邊邊角料的活,這次除了系統的熟悉Go程式碼之外,也對一些常用的設計思想都接觸一下,一句話:沒難度給自己增加難度也要上,不然工作得多無趣,這也是一個成長的過程嘛。
鑑於我們用不上,先寫個Demo用下,後續優化的時候具體的再補上,但是對於Go而言,這一塊也是重點所在吧。昨天文章有大佬說濫用了程式碼塊,我也不是什麼老師,所以,這次我就我程式碼一次性貼出來,順便加上一個對外的函式介面,程式碼上我加上儘量詳細的註釋。參考網上的,不知道是誰寫的,但是 比我寫的要清楚多了,在基礎上我修改了部分程式碼。

//定義攜程池型別
type Pool struct {
    //對外接收Task的入口
    CommonTaskChan chan *CommonTask
    //協程池最大工作數量,限定Goroutine的個數
    ChannelNum int
    //協程池內部的任務就緒佇列
    JobChannels chan *CommonTask
}

//定義任務CommonTask型別,每一個任務CommonTask都可以抽象成一個函式
type CommonTask struct {
    thread func() error //一個無參的函式型別
}


//通過CreateTask來建立一個CommonTask
func CreateTask(f func() error) *CommonTask {
    t := CommonTask{
        thread: f,
    }
    return &t
}

//執行CommonTask任務的方法
func (t *CommonTask) Execute() {
    err:= t.thread() //呼叫任務所繫結的函式
    if err !=nil {
        fmt.Println("Execute err", err.Error())
    }
}

//建立一個協程池
func NewGoPool(cap int) *Pool {
    p := Pool{
        CommonTaskChan: make(chan *CommonTask),
        ChannelNum:    cap,
        JobChannels:  make(chan *CommonTask),
    }

    return &p
}

//協程池建立一個協程並且開始工作
func (p *Pool) start(taskId int) {
    //worker不斷的從JobChannels內部任務佇列中拿任務
    for task := range p.JobChannels {
        //如果拿到任務,則執行任務
        task.Execute()
        fmt.Println("taskId ID ", taskId, " 執行完畢任務")
    }
}

//讓協程池Pool開始工作
func (p *Pool) Run() {
    //1,首先根據協程池的協程數量限定,開啟固定數量的協程,
    for i := 0; i < p.ChannelNum; i++ {
        go p.start(i)
    }

    // 2、從協程池入口取外界傳遞過來的任務並且將任務送進JobChannels中
    for task := range p.CommonTaskChan {
        p.JobChannels <- task
    }

    //3, 執行完畢需要關閉JobChannels
    close(p.JobChannels)

    //4, 執行完畢需要關閉
    close(p.CommonTaskChan)
}

func CallDemo() {
    //建立一個Task
    t := CreateTask(func() error {
        fmt.Println(time.Now())
        return nil
    })

    //建立一個協程池,最大開啟3個協程worker
    p := NewGoPool(3)

    //開一個協程 不斷的向 Pool 輸送列印一條時間的task任務
    go func() {
        for {
            p.CommonTaskChan <- t
            time.Sleep(10 * time.Second)
        }
    }()
    //啟動協程池p
    p.Run()

}
其中,CallDemo就是呼叫的方式。這裡,實際上行就是一個10秒中的定時任務。結果顯示是:

taskid的順序是隨機的哈

對於PHP而言,做一步操作,或者定時任務,只能藉助第三方的工具,重啟一個程式來定時執行介面,或者swoole的work程式來實現,藉助的是C擴充套件。上面建立協程池的時候,其實就可以看出,這是一個10秒鐘的定時任務,時間可以修改。go裡面其實有內建的time包來處理定時任務,直接上示例:
ticker := time.NewTicker(time.Minute * 1)
go func() {
    for _ = range ticker.C {
        fmt.Printf("ticked at %v", time.Now())
    }
}()
這個可以因人而異,根據業務需求來做,其實第三方的就比較穩定,設定的定時格式也是相當豐富的,重點是不需要做保活或者其他的確認機制等。比如:supervisor,我們目前用的就是這個玩意,沒有守護程式之類的開發,水平不高,基本屬於寫業務的能手。這個有時間可以來研究一波,畢竟對於多程式的概念,我要不是做Android開發的,也是很少接觸這個概念的。當然,移動端不一定都會接觸多程式,筆者剛好有這個業務需求,所以研究過一段時間。裝逼了,不好意思。
我們目前討論的並沒有決定使用,因為我們之前的專案已經使用了,所以就簡單的寫上,後續決定使用的框架再來重新封裝。使用起來比我們想象的要簡單的多。
    engine := gin.Default()
    //loginGroup := engine.Group("/index.php/Login")
    loginGroup := engine.Group("/Login")
    loginGroup.POST("/login", controller.Login)
    err := engine.Run(":8001")
    if err != nil {
        log.Fatal(err)
    }

注意介面的對映,我們有時候會加上index.php,這個是可以設定處理的哈。Group是設定請求組,方便統一處理,也可以對組編寫過濾器和驗證器。埠最好放ymal配置檔案,上篇已經解釋過了。沒錯,
上面簡短的程式碼就可以直接使用了。簡單看下login的邏輯程式碼,沒什麼用處,只是做個演示。

func Login(context *gin.Context) {
    var login LoginInfo
    var loginParams LoginParams
    header := context.Request.Header
    accessToken := header.Get("accessToken")
    fmt.Println("---header/---" + accessToken)
    err := context.ShouldBind(&loginParams)
    if err != nil {
        utils.ReturnError(context, "登入失敗: "+err.Error())
        return
    }
    sqlStr := "select id,LoginId,Pwd,FullName,DepartmentId,deleted ,session_id from c_login where LoginId=?"
    rowObj := utils.DBClientPool.GetDB("callout").QueryRow(sqlStr, loginParams.Name)
    err = rowObj.Scan(&login.id, &login.LoginId, &login.Pwd, &login.FullName, &login.DepartmentId, &login.deleted, &login.sessionId)
    if err != nil {
        utils.ReturnError(context, "登入失敗: "+err.Error())
        return
    }

    fmt.Println(login)
    if login.deleted == 1 {
        panic("deleted")
    }
    utils.ReturnSuccess(context, "登入成功")
}
目前準備好這幾種工具就可以準備開始寫業務了,因為也寫業務的過程中會有很多細節上的問題需要時間解決。當然,也有點迫不及待改寫了,目測400+ 的介面數量,年前寫完時間上怕是不夠,主要是和各系統之間的互動問題需要重新改寫,優化的點也是個頭痛的問題,畢竟資料庫的優化除了加索引之外就剩下sql語句的優化了,而這次換成Go,我想的全程不涉及原始sql語句,使用模型和預編譯方式處理,這也是規範的必要性。後續寫程式碼的過程中需要新增的工具或者第三方的,再來詳細描述。
本作品採用《CC 協議》,轉載必須註明作者和本文連結
生命有限,隨心而活,寫程式碼不香嗎?

相關文章