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
캐싱 원리grpcClients.Load(target)
target
(예: "server1:50051"
)에 대한 gRPC 클라이언트가 sync.Map
에 저장되어 있는지 확인grpc.Dial(target)
sync.Map
에 없으면, 새로운 gRPC 연결을 생성하고 클라이언트를 만든 후, sync.Map
에 저장하여 이후 요청에서 재사용할 수 있도록 함grpcClients.Store(target, client)
sync.Map
에 저장하여 다음 요청에서 동일한 클라이언트를 재사용할 수 있도록 함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.Range()
를 사용하여 모든 클라이언트를 순회하면서 연결을 닫습니다.sync.Map
캐싱 장점grpc.Dial()
을 매번 호출하지 않아 성능 최적화