Đồng thời với Goroutines

Jan 18 2023
Trong bài viết này, tôi sẽ nói về một thư viện go nhỏ mà chúng tôi đã viết để giúp sử dụng goroutine dễ dàng hơn và viết mã rõ ràng. Hãy xem gói này ra đời như thế nào.

Trong bài viết này, tôi sẽ nói về một thư viện go nhỏ mà chúng tôi đã viết để giúp sử dụng goroutine dễ dàng hơn và viết mã rõ ràng. Hãy xem gói này ra đời như thế nào.

Với tư cách là Nhóm lập chỉ mục Trendyol, chúng tôi đã có một dự án được viết bằng Scala thu thập dữ liệu từ nhiều dịch vụ siêu nhỏ. Để giảm bớt khối lượng công việc của nhóm hiện tại và sự phức tạp của dự án, chúng tôi đã thành lập nhóm Cung cấp lập chỉ mục mà tôi cũng là thành viên.

Nhóm của tôi sẽ chịu trách nhiệm về giá cả và hoạt động kinh doanh liên quan đến chứng khoán. Điều này có nghĩa là nhóm mới sẽ làm việc với 8 dịch vụ siêu nhỏ và 4 nhóm khác nhau vào lúc này. Tất nhiên, với sự phân chia này, chúng tôi không chỉ chuyển API mà cả người tiêu dùng của doanh nghiệp có liên quan sang nhóm mới.

Chúng tôi có một số API được viết bằng Go và chúng tôi nhận thấy rằng Go hoạt động tốt về thời gian khởi động, mức sử dụng bộ nhớ và mức sử dụng CPU. Chúng tôi nhận được thông lượng cao từ các API này với ít tài nguyên. Do đó, chúng tôi đã quyết định viết API mới của mình bằng Go.

Hãy quay lại chủ đề chính của chúng ta. Chúng tôi gặp các tình huống sau khi gửi yêu cầu đến các dịch vụ.

  • Chúng ta cần chia yêu cầu thành các phần nhỏ hơn và gửi chúng.
  • Một số dịch vụ độc lập với nhau, vì vậy chúng tôi có thể gửi yêu cầu song song.

Ví dụ trong Scala

// Future.sequence
def ChunkRequestAndGetAll(ids: Array[String]): Future[Seq[String]] = {
  Future.sequence(ids.grouped(chunkSize).map(chunkedIds => {
    Future {
      APICall(chunkedIds)
    }
  }).toSeq)
}

// for-yield
def ForYieldExample() = {
  val f1: Future[String] = Future {APICall("call")}
  val f2: Future[Int] = Future {AnotherAPICall()}

  val result = for {
    r1 <- f1
    r2 <- f2
  } yield (anyFunc(r1, r2))

  Await.result(result, 5.seconds)
}

khối nhỏ hơn và gửi chúng

Ví dụ này minh họa cách tách một yêu cầu và gửi nó song song. Kết quả của tất cả các thao tác song song được thu thập trong một danh sách và hàm trả về lỗi nếu bất kỳ thao tác nào không thành công.

func ChunkRequestAndGetAll(ids []string) ([]string, error) {
 var chunkOfIds [][]string = chunk(ids, chunkSize)
 
 ch := make(chan result, len(chunkOfIds))
 for i := range chunkOfIds {
  chunkedIds := chunkOfIds[i]
  go func() {
   response, apiErr := APICall(chunkedIds)
   ch <- result{response, apiErr}
  }()
 }

 log.Println("Waiting for results...")
 results := make([]string, len(ids))
 var err error
 for i := 0; i < len(ids); i++ {
  rs := <-ch
  if rs.err != nil {
   err = rs.err
   break
  }
  results[i] = rs.response
 }

 return results, err
}

func ChunkRequestAndGetAll(ids []string) ([]string, error) {
 var chunkOfIds [][]string = chunk(ids, chunkSize)
 
futures := make([]*future.Future[string], len(chunkOfIds))
 for i := range chunkOfIds {
  chunkedIds := chunkOfIds[i]
  futures[i] = future.Run(func() (string, error) {
   return APICall(chunkedIds)
  })
 }

 log.Println("Waiting for result...")
 results, err := future.GetAll(futures)
 return results, err
}

Khối mã dưới đây trình bày cách gửi yêu cầu song song tới hai dịch vụ khác nhau

/***--- With directly use channel and goroutine ---***/
func DirectlyUse() error {
 ch1 := make(chan result[string])
 go func() {
  response, err := APICall("call")
  ch1 <- result[string]{response, err}
 }()

 ch2 := make(chan result[int64])
 go func() {
  response, err := AnotherAPICall()
  ch2 <- result[int64]{response, err}
 }()

 res1 := <-ch1
 if res1.err == nil {
  return res1.err
 }
 res2 := <-ch2
 if res2.err != nil {
  return res2.err
 }
 //somethings
}

/***--- With future struct ---***/
func WithFutureStruct() error {
 fut1 := future.Run(func() (string, error) {
  return APICall("call")
 })

 fut2 := future.Run(func() (int64, error) {
  return AnotherAPICall()
 })

 res1, err1 := fut1.Get()
 res2, err2 := fut2.Get()

 if err1 == nil {
  return err1
 }

 if err2 != nil {
  return err2
 }
 //somethings
}


/***--- Alternative future struct ---***/
func WithFutureStruct() error {
 fut1 := future.Run(func() (any, error) {
  return APICall("call")
 })

 fut2 := future.Run(func() (any, error) {
  return AnotherAPICall()
 })

 err := future.WaitFor(fut1, fut2)
 if err != nil {
  return err
 }
 result1 := future.GetResult[string](fut1)
 result2 := future.GetResult[int64](fut2)
 //somethings
}

Cảm ơn Emre Odabas đã hỗ trợ và khuyến khích anh ấy viết bài báo này.