home

버퍼있는 채널에 관하여

예시 코드

그냥 요청이 들어오면 이메일을 보내는 척 하는 예시 코드입니다. 프레임워크 안쓰고 net/http go 내장 라이브러리를 사용했어요

package main

import (
	"fmt"
	"net/http"
	"time"
	"encoding/json"
)

type Job struct {
	Email string
	Body string
}

type EmailRequest struct {
	Email string `json:"email"`
	Body string `json:"body"`
}

var jobQueue = make(chan Job, 100)

func startWorker() {
	for job := range jobQueue {
		time.Sleep(2 * time.Second)
		fmt.Printf("Sending email to %s with body %s\n", job.Email, job.Body)
	}
}

func handleEmailRequest(w http.ResponseWriter, r *http.Request) {
	var emailRequest EmailRequest
	err := json.NewDecoder(r.Body).Decode(&emailRequest)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	newJob := Job{Email: emailRequest.Email, Body: emailRequest.Body}
	
	// select를 사용하여 채널이 가득 찬 경우를 처리
	select {
	case jobQueue <- newJob:
		// 채널에 성공적으로 추가됨
		fmt.Println("Job added to queue")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("이메일 전송 요청이 접수되었습니다. (바로 응답)"))
	default:
		// 채널이 가득 참
		http.Error(w, "서버가 너무 바쁩니다. 잠시 후 다시 시도해주세요.", http.StatusServiceUnavailable)
	}
}

func main() {
	go startWorker()
	http.HandleFunc("/send-email", handleEmailRequest)
	
	fmt.Println("엔드포인트: POST /send-email")
	
	err := http.ListenAndServe(":30000", nil)
	if err != nil {
		fmt.Printf("서버 시작 실패: %v\n", err)
	}
}

go의 버퍼있는 채널을 사용하게되면 정말 간편하게 작업 큐를 만들 수 있는데요

우체통에 비교하는 경우가 많은 것 같습니다.

var jobQueue = make(chan Job, 100)

를 사용해서 최대 100개의 Job을 넣을 수 있게 하고, go startWorker()가 돌면서 요청이 들어오면 지속적으로 jobQueue를 뽑아서 job을 실행합니다.

여기서 중요한 것이 저 select라는 놈입니다.

select case default

select문의 default 케이스는 채널이 준비될 때까지 기다리지 않고, 즉시 실행하는 탈출구와 비슷합니다. 그래서 case문이 실패하면 default 문으로 빠지는데, default가 없으면 채널의 버퍼가 빌 때까지 무한 대기합니다. 따라서, 논블록을 위해서라면 default가 필요합니다.

실행

이렇게 비동기로 버퍼에서 job을 잘 빼서 이메일을 보내는 척 합니다.

만약 버퍼가 꽉 찼다면 다음과 같이 응답을 빠르게 줍니다. 로드쉐딩을 해서 부하를 줄여줄 수 있겠네요