GraphQL有個特點,就是查詢返回的數據有多層的結構。在這個多層的複雜結構中,有些數據可能是重複的。比如說我們上一節中的例子,一個用戶03,他可以同時出現在用戶01和用戶02的好友中,那我們如果我們有一個複雜查詢,查詢用戶01,02,03和他們各自的好友,那麼用戶03就出現了三次,而且是出現在不同的層級中。

我們一般會用三個協程來分別查詢用戶01,02和03,這是第一層級。那麼在一個具體用戶的時候,用戶下每一個欄位又分為兩種情況,一種是查詢用戶數據時能立即得到欄位,比如說name,age等,我們叫它們立即數值,還有一種好友列表等我們有可能需要再執行一條查詢而得到的值,我們叫它們Future數值。其實用戶01,02,03本身也是Future數值。可以看出Future可以嵌套,每個Future會派生出一個自己的協程,而這個協程可能還會派生出子協程。

以上就是我們GraphQL後台服務的並發模型。以下是Future的實現,這個實現封裝了channel 和goroutine在內部,讓普通開發者不需要去管理具體的並發細節。

我本來有另外一個想法,但是當我知道了go2.0要支持范型以後,我覺得我還是省點心吧,我就抄襲了scala和twitter的future設計。但現在go還沒有范型,我就用interface{}來做返回值,等go2.0出來,我再改成范型的future。

實現的關鍵是不同的協程可以等待同一個正在執行的Future值,當這個Future結束後,所有相關的協程都能得到通知並繼續。

這是Future的介面。Value()用來得到Future實際的返回值,如果Future還在計算,比如說資料庫查詢還沒有返回,那麼Value()就會等待在原地。這其實是個同步方法。相當於一個await someFuture。

type Future interface {
Value() (interface{}, error)
Then(func(interface{}, error) (interface{}, error)) Future
OnSuccess(func(interface{})) Future
OnFailure(func(error)) Future
}

每個future struct保存以下的數據

type futureImpl struct {
value interface{}
err error
done chan struct{} //ignore the value in chan
}

用done這個channel來通知所有等待的協程。其實是很簡單的邏輯,在創建future時,我們傳入一個實際做事的函數,等函數運行好了,得到了返回結果,就關閉fv.done這個channel。

func MakeFuture(producer func() (interface{}, error)) Future {
fv := &futureImpl{
done: make(chan struct{}),
}
go func() {
fv.value, fv.err = producer()
close(fv.done) //notify all the waiting/running Value()
}()
return fv
}

channel一旦被關閉,就等於鬆開了所有等待 「<-fv.done」的協程。我們在Value()里可以這樣實現。

func (fv *futureImpl) Value() (interface{}, error) {
<-fv.done //waiting for the result
return fv.value, fv.err
}

在resolve一個User的時候,就很容易了。(以下代碼為GraphQL框架自動生成)

//for future resolver value
fu := future.MakeFuture(func() (interface{}, error) {

idValue, _ := field.Arguments.ForName("id").Value.Value(nil)

return r.Data.GetUser(ctx, idValue.(ID))
})

然後在所有需要用到這個User的協程里,只要有這個fu,就可以用fu.Value()來得到user的struct。

那同一個請求下的不同協程如何來共享fu呢?我們還需要一個請求作用域的緩存,下節再討論。而且根據graphql的特點,我們還需要一個匯總集中的Error數據,要不把不同協程、或者說是不同future的出錯信息匯總到一起,這個我也會在後面的章節討論。

並發執行的效果可以通過我們上一節介紹的opentracing圖來看,從頭用go寫一個GraphQL服務(3)OpenTracing追蹤

推薦閱讀:

查看原文 >>
相关文章