gRPC 클라이언트 객체

gRPC 클라이언트는 grpc.Dial() 을 통해 생성되는데, 이 과정에서 네트워크 연결을 초기화하고, TLS 핸드셰이크를 수행하며, 내부적으로 커넥션 풀을 관리합니다. 즉, grpc.Dial() 을 매번 호출하면 연결이 생성되어 비효율적일 수 있습니다.

conn, err := grpc.Dial("server-address:50051", grpc.WithInsecure())
if err != nil {
    log.Fatalf("Failed to connect: %v", err)
}
client := pb.NewMyServiceClient(conn)

sync.Map을 활용한 gRPC 클라이언트 캐싱

클라이언트 생성 비용 절감을 위해 sync.Map을 활용하면 멀티 쓰레드 환경에서도 안전하게 클라이언트를 공유할 수 있습니다.

import (
    "sync"
    "google.golang.org/grpc"
    pb "example.com/project/proto"
)

// gRPC 클라이언트 캐싱을 위한 전역 변수
var grpcClients sync.Map

// gRPC 클라이언트 반환 함수
func getGRPCClient(target string) (pb.MyServiceClient, error) {
    // 1. sync.Map에서 클라이언트를 찾음
    if client, ok := grpcClients.Load(target); ok {
        return client.(pb.MyServiceClient), nil
    }

    // 2. 없으면 새로운 gRPC 클라이언트를 생성
    conn, err := grpc.Dial(target, grpc.WithInsecure())
    if err != nil {
        return nil, err
    }
    client := pb.NewMyServiceClient(conn)

    // 3. 생성된 클라이언트를 sync.Map에 저장
    grpcClients.Store(target, client)
    return client, nil
}

sync.Map 캐싱 원리

  1. grpcClients.Load(target)
  2. grpc.Dial(target)
  3. grpcClients.Store(target, client)

gRPC 클라이언트 연결 종료

sync.Map을 사용하면 gRPC 클라이언트 객체를 계속 캐싱하기 때문에, 특정 시점에서 연결을 닫아야 할 수도 있습니다.

// gRPC 클라이언트 캐싱을 위한 전역 변수
var grpcClients sync.Map

func closeAllGRPCConnections() {
    grpcClients.Range(func(key, value interface{}) bool {
        if client, ok := value.(pb.MyServiceClient); ok {
            if conn, ok := client.(*grpc.ClientConn); ok {
                conn.Close()
            }
        }
        return true
    })
}

sync.Map 캐싱 장점