Golang模仿七牛圖片處理API

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

之前一直在用qiniu的儲存服務,產生圖片的縮圖,模糊圖,視頻的webp,現在需要把儲存移到s3上,那麼這些圖片,視頻處理就要自己動手寫了,本文梳理一下大致的思路。

分析需求

先看一下qiniu的介面是如何處理圖片的,例如先截取視頻第一秒的圖片,再把圖片縮減,最後儲存到一個新的key,命令可以這麼寫 vframe/jpg/offset/1|imageMogr2/thumbnail/400x|saveas/xxx, 可以看到三個操作之間用 | 符號分割,類似unix 的 pipe 操作。

上面的操作算作一個cmd, 一次API請求可以同時處理多個cmd,cmd之間用分號分割, 處理完畢後,在回調中把處理結果返回,例如

{    "id": "xxxxx",     "pipeline": "xxx",     "code": 0,     "desc": "The fop was completed successfully",     "reqid": "xTsAAFnxUbR5J10U",     "inputBucket": "xxx",     "inputKey": "xxxxx",     "items": [        {            "cmd": "vframe/jpg/offset/1|imageMogr2/thumbnail/400x|saveas/ZmFtZS1wcml2YXRlOm1vbWVudC9jb3Zlci9zbmFwL3ZpZGVvL2M5YzdjZjQ5LTU3NGQtNGZjMS1iZDFkLTRkYjZkMzlkZWY1Ni8wLzA=",             "code": 0,             "desc": "The fop was completed successfully",             "hash": "FhdN6V8EI4vW4XJGALSfxutvMEIv",             "key": "xx",             "returnOld": 0        },         {            "cmd": "vframe/jpg/offset/1|imageMogr2/thumbnail/400x|imageMogr2/blur/45x8|saveas/ZmFtZS1wcml2YXRlOm1vbWVudC9jb3Zlci9zbmFwL3ZpZGVvL2M5YzdjZjQ5LTU3NGQtNGZjMS1iZDFkLTRkYjZkMzlkZWY1Ni8wLzBfYmx1cg==",             "code": 0,             "desc": "The fop was completed successfully",             "hash": "FgNiRzrCsa7TZx1xVSb_4d5TiaK3",             "key": "xxx",             "returnOld": 0        }    ]}

分解需求

這個程式大致需要這麼幾個部分:

  1. 一個http介面,接受任務,接受後把任務扔到隊列,返回一個job ID。 worker非同步處理任務,worker的個數 和 每個worker 並行的處理的個數 能夠配置,worker有重試機制。

  2. 從 job payload 中解析出需要做的任務,解析出每個cmd, 最好能並存執行每一個 cmd, 記錄每一個cmd的結果

  3. 每個cmd中有多個 operation, 並且用 pipe 串連,前一個operaion的輸出是後一個operation的輸入

可以把 1 和 2,3 分開來看,1 比較獨立,之前寫過一個worker的模型,參考的是這篇文章 Handling 1 Million Requests per Minute with Go,比較詳細,是用 go channel 作為queue的,我加了一個 beanstalk 作為 queue的 providor。還有一點改進是,文章中只提供了worker數量的設定,我再加了一個參數,設定每個worker可以並存執行的協程數。所以下面主要講講3, 2的解決辦法

Pipe

可以參考這個庫 pipe, 用法如下:

p := pipe.Line(    pipe.ReadFile("test.png"),    resize(300, 300),    blur(0.5),)output, err := pipe.CombinedOutput(p)if err != nil {    fmt.Printf("%v\n", err)}buf := bytes.NewBuffer(output)img, _ := imaging.Decode(buf)imaging.Save(img, "test_a.png")

還是比較方便的,建一個 Cmd struct, 利用正則匹配一下每個 Operation 的參數,放入一個 []Op slice, 最後執行,struct和方法如下:

type Cmd struct {    cmd    string    saveas string    ops    []Op    err    error}type Op interface {    getPipe() pipe.Pipe}type ResizeOp struct {    width, height int}func (c ResizeOp) getPipe() pipe.Pipe {    return resize(c.width, c.height)}//使用方法cmdStr := `file/test.png|thumbnail/x300|blur/20x8`cmd := Cmd{cmdStr, "test_b.png", nil, nil}cmd.parse()cmd.doOps()

sync.WaitGroup

單個cmd處理解決後,就是多個cmd的並行問題,沒啥好想的,直接用 sync.WaitGroup 就可以完美解決。一步一步來,我們先看看這個struct的使用方法:

func main() {    cmds := []string{}    for i := 0; i < 10000; i++ {        cmds = append(cmds, fmt.Sprintf("cmd-%d", i))    }    results := handleCmds(cmds)    fmt.Println(len(results)) // 10000}func doCmd(cmd string) string {    return fmt.Sprintf("cmd=%s", cmd)}func handleCmds(cmds []string) (results []string) {    fmt.Println(len(cmds)) //10000    var count uint64    group := sync.WaitGroup{}    lock := sync.Mutex{}    for _, item := range cmds {        // 計數加一        group.Add(1)        go func(cmd string) {            result := doCmd(cmd)            atomic.AddUint64(&count, 1)            lock.Lock()            results = append(results, result)            lock.Unlock()                        // 計數減一            group.Done()        }(item)    }    // 阻塞    group.Wait()    fmt.Printf("count=%d \n", count) // 10000    return}

group本質大概是一個計數器,計數 > 0時, group.Wait() 會阻塞,直到 計數 == 0. 這裡還有一點要注意,就是 results = append(results, result) 的操作是線程不安全的,清楚這裡 results 是共用的,需要加鎖來保證同步,否則最後 len(results) 不為 10000。

我們建一個BenchCmd, 來存放 cmds. 如下:

type BenchCmd struct {    cmds      []Cmd    waitGroup sync.WaitGroup    errs      []error    lock      sync.Mutex}func (b *BenchCmd) doCmds() {    for _, item := range b.cmds {        b.waitGroup.Add(1)        go func(cmd Cmd) {            cmd.parse()            err := cmd.doOps()            b.lock.Lock()            b.errs = append(b.errs, err)            b.lock.Unlock()            b.waitGroup.Done()        }(item)    }    b.waitGroup.Wait()}

最後的調用就像這樣:

var cmds []Cmdcmd_a := Cmd{`file/test.png|thumbnail/x300|blur/20x8`, "test_a.png", nil, nil}cmd_b := Cmd{`file/test.png|thumbnail/500x1000|blur/20x108`, "test_b.png", nil, nil}cmd_c := Cmd{`file/test.png|thumbnail/300x300`, "test_c.png", nil, nil}cmds = append(cmds, cmd_a)cmds = append(cmds, cmd_b)cmds = append(cmds, cmd_c)bench := BenchCmd{    cmds:      cmds,    waitGroup: sync.WaitGroup{},    lock:      sync.Mutex{},}bench.doCmds()fmt.Println(bench.errs)

這隻是一個初級的實驗,思考還不夠全面,並且只是模仿API,qiniu應該不是這麼做的,耦合更低,可能各個Cmd都有各自處理的叢集,那pipe這個庫就暫時沒法解決了,目前的局限在於 每個Cmd必須都在一個進程中。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.