반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 함수포인터
- TiDB
- 컴퓨터 강좌
- Golang
- Pointer
- SQLite
- TiKV
- 긴옵션
- bash
- go
- UNIX
- 전처리기
- kernel
- OS 커널
- 약어
- UNIX Internals
- DBMS 개발
- newSQL
- Symbol
- 구조와 원리
- 포인터변수
- getopts
- FreeBSD
- Programming
- 포인터
- DBMS
- Preprocessor
- 한빛미디어
- 커널
- Windows via c/c++
Archives
- Today
- Total
sonumb
Golang - gRPC 예제 본문
개요
gRPC 개념은 아래 글들에서 찾아보자.
- https://corgipan.tistory.com/6
- http://m.blog.naver.com/alice_k106/221617347519
- https://devjin-blog.com/golang-grpc-server-1/
- https://blog.banksalad.com/tech/production-ready-grpc-in-golang/
- https://medium.com/naver-cloud-platform/nbp-%EA%B8%B0%EC%88%A0-%EA%B2%BD%ED%97%98-%EC%8B%9C%EB%8C%80%EC%9D%98-%ED%9D%90%EB%A6%84-grpc-%EA%B9%8A%EA%B2%8C-%ED%8C%8C%EA%B3%A0%EB%93%A4%EA%B8%B0-1-39e97cb3460
- https://medium.com/naver-cloud-platform/nbp-%EA%B8%B0%EC%88%A0-%EA%B2%BD%ED%97%98-%EC%8B%9C%EB%8C%80%EC%9D%98-%ED%9D%90%EB%A6%84-grpc-%EA%B9%8A%EA%B2%8C-%ED%8C%8C%EA%B3%A0%EB%93%A4%EA%B8%B0-2-b01d390a7190
- https://incredible-larva.tistory.com/entry/gRPC-google-RPC-%EB%B0%B0%EA%B2%BD%EB%B6%80%ED%84%B0-%ED%99%9C%EC%9A%A9%EA%B9%8C%EC%A7%80
✅ protobuf의 버전2와 3의 차이점이 여러 가지가 있는데 그중 하나가 "버전 3이 더 많은 언어를 지원"한다는 점이다. (마지막 글 참조)) |
예제
컴퓨팅 자원은 한정적이므로, 자원을 공유하는 아키텍처를 생각해 볼 수 있다.
특히 멀티프로세스 아키텍처 기반에서 서비스를 구현하는 경우, 파일, 공유 메모리, 심지어 서비스까지 공유 리소스로 정의할 수 있다.
특정 프로세스가 비정상 종료되는 경우, 다른 프로세스가 이 공유 리소스를 회수/복구하지 않으면 불가능 상태로 영원히 남을 것이다.
OS의 경우, 어느 프로세스가 죽으면 다른 프로세스가 커널모드로 진입한 후, 할당된 메모리/파일을 반환한다.
이와 비슷하게, MSA 구조에서도 이러한 리소스를 감시 및 복구하는 프로세스가 필요하다.
(도입이 길었지만, gRPC 예제란 걸 잊지 말자😅)
이때, 프로세스 감시 및 리소스 복구를 해주는 관리 프로세스는 2개로 나누어 구현해본다.
- 메인데몬: 비정상 종료 감지
- 서브데몬: 복구절차 수행
⚠️ 두 가지 프로세스로 나눈 이유 복구하는 중에 감시/감지는 계속되어야 한다. 나누지 않고 하나의 프로세스에서 처리하는 상황이라고 하자. 복구 도중 비정상 종료가 발생하면, 감시/감지 기능도 같이 종료된다. 따라서 두 가지 서비스를 각각의 프로세스로 구현한다. (✅그런데, 두 기능을 분리하면, 복구 서비스를 하는 데몬이 죽은 경우가 가장 문제가 된다. 이런 경우, "1) 다른 리소스 감시/감지는 계속 실행, 2) "복구 데몬 재실행"을 우선적으로 실행, 3) 감시/복구 작업를 재진행" 의 절차로 이문제를 극복할 수 있다.) |
그리고 비정상 종료를 감지한 이후, 이에 대한 "복구"는 gRPC를 이용하며, 다른 프로세스에게 요청될 것이다.
이 과정은 아래 그림의 1~5 과정으로 기술된다.
소스 코드
앞서 설명한 개념을 목업으로 구현해보자!
protobuf/subdaemon.proto
// 아래 내용으로 protobuf/subdaemon.proto 경로로 파일 작성
// $ protoc -I protobuf/ protobuf/subdaemon.proto --go_out=plugins=grpc:protobuf
// 명령으로 go 용 파일 생성
syntax = "proto3";
package subdaemonpb;
// SubDaemonGrpc 서비스 선언
service SubDaemonGrpc {
// 연결 메시지
rpc SayHello (HelloRequest) returns (HelloReply) {}
// 복구 서비스
rpc RecoveryProcess (RecoveryProcessRequest) returns (RecoveryProcessReply) {}
// 서브데몬 종료
rpc SubDaemonStop (SubDaemonStopRequest) returns (SubDaemonStopReply) {}
}
// todo: 모든 응답 메시지에 ErrorType 을 추가하고
// - fatal, abort, debug, info으로 나누어서, 메인데몬 종료 여부등등으로 나누고,
// - 작업을 재시도/abort 등도 할 수 있도록 해야 한다.
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
message RecoveryProcessRequest {
int32 pid = 1;
// etc
}
message RecoveryProcessReply {
bool success = 1;
string message = 2;
}
message SubDaemonStopRequest {
}
message SubDaemonStopReply {
bool success = 1 ;
string message = 2;
}
subdaemon_mockup.go
개념적으로는 두개의 프로세스로 설명하였지만, 이 목업은 한 프로세스에서 여러 고루틴으로 구현될 것이다.
main() 함수에서 2개의 고루틴을 실행하는데,
- main() 함수 내 고루틴: gRPC 서버 고루틴: 서브데몬의 gRPC 호출을 처리
- clientGoroutine(): 메인 데몬의 역활. 즉, 서브데몬 gRPC 호출
와 같다.
최종적으로 메인 데몬이 서브데몬의 종료(SubDaemonStop())를 호출하게 되는데 종료 채널에 시그널을 발생시한다.
메인 함수가 이 이벤트를 수신하였다면, gRPC 인스턴스의 GracefulStop()을 호출하여 종료한다.
package main
import (
"context"
"log"
"net"
pb "github.com/foobar/go_exam/protobuf"
"google.golang.org/grpc"
"time"
"fmt"
"os"
"errors"
)
const (
port = "127.0.0.1:50051"
)
// SubDaemonGrpcImpl는 protobuf 실제 구현
type SubDaemonGrpcImpl struct{
server *grpc.Server
stopCh chan struct{}
//subd SubDaemonServer
}
func (s *SubDaemonGrpcImpl) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func (s *SubDaemonGrpcImpl) SubDaemonStop(ctx context.Context, in *pb.SubDaemonStopRequest) (*pb.SubDaemonStopReply, error) {
s.stopCh <- struct{}{}
return &pb.SubDaemonStopReply{
Success: true,
Message: "SubDaemonStop Success",
}, nil
}
func (s *SubDaemonGrpcImpl) RecoveryProcess(ctx context.Context, in *pb.RecoveryProcessRequest) (*pb.RecoveryProcessReply, error) {
// err := s.subd.ReocveryMgr.RecoveryWithPid(in.GetPid())
// if err != nil {
// return &pb.SubDaemonStopReply{
// Success: false;
// errorType: fatal, abort, info
// Message: err.Error(),
// }, nil
// }
//
err := errors.New("not yet implemented")
return &pb.RecoveryProcessReply{
Success: false,
Message: err.Error(),
}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
srv := &SubDaemonGrpcImpl{s, make(chan struct{})}
pb.RegisterSubDaemonGrpcServer(s, srv)
go func () {
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
println("server stopped")
} ()
clientGoroutine := func () {
conn , err := grpc.Dial(port, grpc.WithInsecure(), grpc.WithBlock())
//conn , err := grpc.Dial(port)
if err != nil {
log.Fatalln(err.Error())
}
subdGrpc := pb.NewSubDaemonGrpcClient(conn)
ctx, _ := context.WithCancel(context.Background())
// ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
// 1. 핸드쉐이크를 위한 Hello Msg
rep, err := subdGrpc.SayHello(ctx, &pb.HelloRequest{Name:"Hi"})
if err != nil {
log.Fatalln(err.Error())
}
fmt.Println(rep)
time.Sleep(3*time.Second)
// 2. 프로세스 복구 요청
recoveryReq := &pb.RecoveryProcessRequest{Pid: int32(os.Getpid())}
recoveryRep, err := subdGrpc.RecoveryProcess(ctx, recoveryReq)
if err != nil {
log.Fatalln(err.Error())
}
if recoveryRep.GetSuccess() != true {
fmt.Println("Warn:", recoveryRep.Message)
} else {
fmt.Println(recoveryRep)
}
time.Sleep(1*time.Second)
// 3. 서브데몬 종료 요청
stopRep, err := subdGrpc.SubDaemonStop(ctx, &pb.SubDaemonStopRequest{})
if err != nil {
log.Fatalln(err.Error())
}
fmt.Println(stopRep)
time.Sleep(1*time.Second)
fmt.Println("Stop Client")
conn.Close()
}
go clientGoroutine()
println("main: wait-for-stop")
ticker := time.NewTicker(time.Second)
for stopLoop := false ; stopLoop != true ; {
select {
case <-ticker.C:
println("server-ticker")
case <-srv.stopCh:
fmt.Println("SubDaemonGrpcServer Received Stop Message -> Do Stop")
s.GracefulStop()
fmt.Println("SubDaemonGrpcServer Received Stop Message -> Do Stop -- done")
stopLoop = true
}
}
time.Sleep(3*time.Second)
println("end-main")
}
go.mod
module github.com/foobar/go_exam
⛔️ go.mod 파일이 없으면 아래와 같은 메시지와 함께 빌드가 되지 않는다. |
$ go build subdaemon_mockup.go
go: finding module for package github.com/foobar/go_exam/protobuf
subdaemon_mockup.go:7:2: cannot find module providing package github.com/foobar/go_exam/protobuf: module github.com/foobar/go_exam/protobuf: git ls-remote -q origin in /Users/sonumb/go/pkg/mod/cache/vcs/420d97389aec40b542b69c679343083fd3c8679b219de4a1cae489c66c97ce40: exit status 128:
fatal: could not read Username for 'https://github.com': terminal prompts disabled
Confirm the import path was entered correctly.
실행 및 결과
$ protoc -I protobuf/ protobuf/subdaemon.proto --go_out=plugins=grpc:protobuf
$ go build subdaemon_mockup.go ; ./subdaemon_mockup
main: wait-for-stop
2021/11/12 15:30:16 Received: Hi
message:"Hello Hi"
server-ticker
server-ticker
server-ticker
Warn: not yet implemented
server-ticker
SubDaemonGrpcServer Received Stop Message -> Do Stop
success:true message:"SubDaemonStop Success"
SubDaemonGrpcServer Received Stop Message -> Do Stop -- done
server stopped
Stop Client
end-main
$
반응형