Konkurensi dengan Goroutine

Jan 18 2023
Pada artikel ini, saya akan berbicara tentang pustaka go kecil yang kami tulis untuk mempermudah penggunaan goroutine dan menulis kode yang jelas. Mari kita lihat bagaimana paket ini keluar.

Pada artikel ini, saya akan berbicara tentang pustaka go kecil yang kami tulis untuk mempermudah penggunaan goroutine dan menulis kode yang jelas. Mari kita lihat bagaimana paket ini keluar.

Sebagai Tim Pengindeks Trendyol, kami memiliki proyek yang ditulis dalam Scala yang mengumpulkan data dari banyak layanan mikro. Untuk mengurangi beban kerja tim saat ini dan kerumitan proyek, kami telah membentuk tim Penawaran Pengindeksan, di mana saya juga menjadi bagiannya.

Tim saya akan bertanggung jawab atas bisnis terkait harga dan stok. Ini berarti tim baru akan bekerja dengan 8 layanan mikro dan 4 tim berbeda untuk saat ini. Tentu saja, dengan divisi ini, kami tidak hanya memindahkan API tetapi juga konsumen dari bisnis yang relevan ke tim baru.

Kami memiliki beberapa API yang ditulis dalam Go, dan kami mengamati bahwa Go bekerja dengan baik dalam hal waktu boot, penggunaan memori, dan penggunaan CPU. Kami mendapatkan throughput tinggi dari API ini dengan sedikit sumber daya. Akibatnya, kami memutuskan untuk menulis API baru kami menggunakan Go.

Mari kita kembali ke topik utama kita. Kami memiliki situasi berikut saat mengirim permintaan ke layanan.

  • Kami perlu membagi permintaan menjadi potongan-potongan kecil dan mengirimkannya.
  • Beberapa layanan bersifat independen satu sama lain, sehingga kami dapat mengirim permintaan paralel.

Contoh di 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)
}

Potongan yang lebih kecil dan mengirimkannya

Contoh ini menunjukkan cara membagi permintaan dan mengirimkannya secara paralel. Hasil dari semua operasi paralel dikumpulkan dalam daftar, dan fungsi mengembalikan kesalahan jika ada operasi yang gagal.

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
}

Blok kode di bawah menunjukkan cara mengirim permintaan paralel ke dua layanan berbeda

/***--- 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
}

Terima kasih kepada Emre Odabas atas dukungan dan dorongannya dalam menulis artikel ini.