Generic(제네릭)이란?

Go 1.18부터 도입된 제네릭(Generic) 기능은 하나의 함수나 타입이 다양한 타입을 처리할 수 있도록 해주는 기능입니다.

쉽게 말해, 함수나 구조체를 만들 때 타입을 고정하지 않고 나중에 사용할 때 타입을 지정할 수 있도록 하는 기능입니다.

이전까지는 interface{}를 활용한 방법이 있었지만, 타입 안정성(type safety)이 부족하고 형변환(casting)이 필요했습니다.

제네릭은 이러한 문제를 해결하고, 재사용 가능한 코드를 작성하는 데 큰 도움이 됩니다.

 

Generic이 필요한 이유

Go에서 Generic이 없던 시절에서는 다음과 같은 일이 자주 발생하였습니다.

func PrintInts(list []int) {
	for _, v := range list {
		fmt.Println(v)
	}
}

func PrintStrings(list []string) {
	for _, v := range list {
		fmt.Println(v)
	}
}

Generic이 없던 시절에는 interface{}를 이용하여 형변환을 이용하거나 아니면 위와 같이 PrintInts, PrintStrings 각각 타입에 맞는 함수를 정의하여 사용해야만 했습니다.

 

Generic 사용

Generic을 이용하면 타입에 대한 안정성을 유지하며 위와 같이 각 타입별로 함수를 정의하지 않고 다음과 같이 간단하게 코드를 구현하여 사용할 수 있습니다.

func PrintSlice[T any](s []T) {
	for _, v := range s {
		fmt.Println(v)
	}
}

 

Generic constraint

모든 타입이 아닌, 특정 조건을 가진 타입만 허용하고 싶을 때 다음과 같이 Number(숫자)에 관련된 타입만 정의하여 사용할 수 있습니다.

type Number interface {
	int | int8 | int16 | int32 | int64 | float32 | float64
}

func SumNumber[T Number](a, b T) T {
	return a + b
}

func main() {
	result := SumNumber[int](int(1), int(2))
	fmt.Println(result) // 3

	result2 := SumNumber[int8](int8(2), int8(3))
	fmt.Println(result2) // 5

	result3 := SumNumber[float32](float32(1.1), float32(2.2))
	fmt.Println(result3) // 3.3
}

 

 

Generic constraint 상세

~int는 int와 해당 타입의 별칭까지 포함하는 방법입니다. int 타입을 이용하여 새롭게 정의된 타입은 다음과 같이 정의하여 사용할 수 있습니다.

type Number interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64
}

func SumNumber[T Number](a, b T) T {
	return a + b
}

type TestType int

func main() {
	result := SumNumber(TestType(1), TestType(2))
	fmt.Println(result) // 3
}

 

Generic struct

구조체도 타입 매개변수를 받아 유연하게 설계할 수 있습니다.

type Pair[T, U any] struct {
	First  T
	Second U
}

func main() {
	p := Pair[int, string]{First: 1, Second: "hello"}
	fmt.Println(p)
}

 

 


실제 예제

실제 예제를 이용하여 다음과 같이 여러 사용법에 대하여 간단한 예제를 만들었습니다. 아래 예제를 참고하여 Generic 사용에 많은 참고 바랍니다.

 

실제 예제: 제네릭 Map 함수

func Map[T any, R any](arr []T, f func(T) R) []R {
	result := make([]R, len(arr))
	for i, v := range arr {
		result[i] = f(v)
	}
	return result
}

func main() {
    squared := Map([]int{1, 2, 3}, func(x int) int {
        return x * x
    })
    fmt.Println(squared) // [1 4 9]
}

 

실제 예제: Generic을 이용한 Interface 정의

type Comparable[T any] interface {
	Less(b T) bool
}

type Person struct {
	Name string
	Age  int
}

func (p Person) Less(other Person) bool {
	return p.Age < other.Age
}

func Min[T Comparable[T]](a, b T) T {
	if a.Less(b) {
		return a
	}
	return b
}

func main() {
	p1 := Person{"Alice", 25}
	p2 := Person{"Bob", 30}
	fmt.Println(Min(p1, p2)) // Alice
}

 

 

'Go언어' 카테고리의 다른 글

Go언어의 benchmark와 test  (0) 2025.03.19
[addlicense] golang 프로젝트에 라이센스 추가하기  (0) 2024.04.29
golang version upgrade (ubuntu)  (0) 2023.07.09
go work 사용해보기  (0) 2023.07.06
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30

benchmark와 test

Golang에서 benchmark와 test는 코드의 정확성과 성능을 보장하는 핵심 요소입니다. 특히, 대규모 시스템이나 고성능이 중요한 애플리케이션을 개발하는데 매우 중요한 기능입니다. 이번 문서에서는 benchmark와 test에 대한 사용법을 알아보겠습니다.

benchmark란

Golang에서 benchmark란 특정 코드, 함수, 알고리즘 또는 시스템의 성능을 측정하여 실행 속도, 처리량, 리소스 사용량(CPU, 메모리 등)을 분석하는 과정입니다. 이를 통해 코드의 최적화 가능성을 평가하고 여러 구현 방식 간의 성능 차이를 비교하며 병목 현상을 식별하여 효율적인 개선 방향을 찾을 수 있습니다. 특히 Golang에서는 go test -bench 명령어를 활용하여 특정 함수의 실행 시간을 반복적으로 측정하고 평균 실행 시간을 ns/op(나노초/연산) 단위로 확인하고 코드 변경 전후의 성능을 비교하여 보다 빠르고 최적화된 코드 구조를 설계하는 데 중요한 역할을 합니다. 이를 통해 애플리케이션의 전반적인 성능을 향상하고, 최적의 알고리즘을 선택하여 개발하는데 많은 도움이 됩니다.

 

benchmark의 중요성

  • 성능 최적화: 코드 실행 시간을 측정하여 병목 현상을 발견하고 개선할 수 있습니다.
  • 효율적인 리소스 사용: CPU, 메모리 사용량을 줄여 더 빠른 애플리케이션을 만들 수 있습니다.
  • 다양한 구현 방식 비교: 두 가지 이상의 알고리즘을 벤치마크하여 더 빠른 방법을 선택할 수 있습니다.
  • 최적화 후 성능 검증: 코드 변경 후 실제로 성능이 개선되었는지 수치적으로 확인할 수 있습니다.

 

test란

go test는 Go 언어에서 제공하는 기본 테스트 실행 도구로, 개발자가 작성한 테스트 코드(*_test.go 파일)를 자동으로 찾아 실행하며, 코드의 올바른 동작 여부를 검증하는 유닛 테스트(Unit Test), 기능 테스트(Functional Test), 통합 테스트(Integration Test) 등을 수행할 수 있는 기능입니다. 이를 통해 코드 변경 시 발생할 수 있는 예기치 않은 오류를 사전에 감지하고, 소프트웨어의 신뢰성을 유지하며, CI/CD 파이프라인과 연동하여 자동화된 테스트 환경을 구축하는 데 필수적인 역할을 수행하는 강력한 도구입니다.

 

test의 중요성

  • 버그 방지: 코드 수정 시, 기존 기능이 깨지지 않았는지 확인할 수 있습니다.
  • 안정성 확보: 배포 전에 문제를 발견하여 안정적인 소프트웨어를 제공할 수 있습니다.
  • 코드 유지보수성 향상: 테스트 코드가 있으면, 코드 변경 후에도 정상 동작 여부를 쉽게 확인할 수 있습니다.
  • TDD (Test-Driven Development) 가능: 테스트를 먼저 작성한 후 기능을 구현하는 방식으로, 안정적인 개발이 가능합니다.

 

예제

gin 프레임워크를 이용하여 간단한 프로젝트를 만들었습니다. 숫자를 받으면 1부터 해당 숫자까지 더하는 간단한 API를 구성하였고 API의 v1은 루프를 이용하여 1부터 하나씩 증가하여 덧셈을 수행하였으며 v2는 (n * (n + 1)) / 2 공식을 이용하여 해당 덧셈을 수행하는 방식으로 개발을 진행하였습니다. 이제 benchmark를 이용하여 각 버전의 API 성능을 측정하여 비교하는 방식에 대해 알아보겠습니다.

소스코드 : https://github.com/stdhsw/go-benchmark

 

프로젝트 구조

├── README.md
├── benchmarks
│   ├── benchmark_result.txt
│   └── benchmark_test.go
├── cmd
│   └── server
│       └── main.go
├── go.mod
├── go.sum
├── internal
│   ├── handlers
│   │   ├── v1
│   │   │   ├── parity_handler.go
│   │   │   └── sum_handler.go
│   │   └── v2
│   │       ├── parity_handler.go
│   │       └── sum_handler.go
│   └── services
│       ├── v1
│       │   ├── parity_service.go
│       │   └── sum_service.go
│       └── v2
│           ├── parity_service.go
│           └── sum_service.go
└── tests
    ├── v1
    │   ├── parity_handler_test.go
    │   └── sum_handler_test.go
    └── v2
        ├── parity_handler_test.go
        └── sum_handler_test.go

 

internal/service/v1/sum_service.go

package v1

// SumService - 1부터 입력된 숫자까지 합산
func SumService(n int) int {
	sum := 0
	for i := 1; i <= n; i++ {
		sum += i
	}
	return sum
}

 

internal/service/v2/sum_service.go

package v2

// SumService - 1부터 n까지의 합을 수학 공식을 이용해 계산
func SumService(n int) int {
	return (n * (n + 1)) / 2
}

 

Test 예제

tests/v1/sum_handler_test.go

tests/v2/sum_handler_test.go

func TestSumHandlerV1(t *testing.T) {
	router := gin.Default()
	router.GET("/v1/sum", v1.SumHandler)

	tests := []struct {
		query    string
		expected int
	}{
		{"10", 55},    // 1부터 10까지의 합: 55
		{"100", 5050}, // 1부터 100까지의 합: 5050
		{"1", 1},      // 1부터 1까지의 합: 1
	}

	for _, tt := range tests {
		req, _ := http.NewRequest("GET", "/v1/sum?number="+tt.query, nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		assert.Contains(t, w.Body.String(), fmt.Sprintf(`"sum":%d`, tt.expected))
	}
}

func TestSumHandlerV2(t *testing.T) {
	router := gin.Default()
	router.GET("/v2/sum", v2.SumHandler)

	tests := []struct {
		query    string
		expected int
	}{
		{"10", 55},
		{"100", 5050},
		{"1", 1},
	}

	for _, tt := range tests {
		req, _ := http.NewRequest("GET", "/v2/sum?number="+tt.query, nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		assert.Contains(t, w.Body.String(), fmt.Sprintf(`"sum":%d`, tt.expected))
	}
}

 

test 수행하는 방법

go test를 이용하여 현재 API의 결과에는 문제가 없는 것을 확인할 수 있습니다.

# 전체 테스트 실행
go test ./tests/...

# 출력 결과
ok      github.com/stdhsw/go-benchmark/tests/v1 1.049s
ok      github.com/stdhsw/go-benchmark/tests/v2 0.707s

 

Benchmark 예제

benchmarks/benchmakr_test.go

// BenchmarkSumV1 - v1의 Sum API 성능 측정
func BenchmarkSumV1(b *testing.B) {
	router := gin.Default()
	router.GET("/v1/sum", v1.SumHandler)

	req, _ := http.NewRequest("GET", "/v1/sum?number=10000", nil)

	for i := 0; i < b.N; i++ {
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
	}
}

// BenchmarkSumV2 - v2의 Sum API 성능 측정
func BenchmarkSumV2(b *testing.B) {
	router := gin.Default()
	router.GET("/v2/sum", v2.SumHandler)

	req, _ := http.NewRequest("GET", "/v2/sum?number=10000", nil)

	for i := 0; i < b.N; i++ {
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
	}
}

 

benchmark 수행하는 방법

프로젝트는 위와 같이 구성되어 있으며 benchmark 기능을 통해 v1과 v2의 성능을 측정하는 방법에 대해 알아보겠습니다.

# 벤치마크는 여러번의 수행을 통해 결과를 측정하기 때문에 출력 결과가 매우 많습니다.
# 파일로 저장하여 출력 결과 확인
go test -bench=. benchmarks/benchmark_test.go > benchmark_result.txt

 

benchmark 결과

[GIN-debug] GET    /v1/sum                   --> example.com/myproject/internal/handlers/v1.SumHandler (3 handlers)
[GIN] 2025/03/19 - 17:50:11 | 200 |      57.334µs |                 | GET      "/v1/sum?number=10000"
BenchmarkSumV1-12       	[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

(생략) ...

[GIN-debug] GET    /v2/sum                   --> example.com/myproject/internal/handlers/v2.SumHandler (3 handlers)
[GIN] 2025/03/19 - 17:50:13 | 200 |        7.75µs |                 | GET      "/v2/sum?number=10000"
BenchmarkSumV2-12       	[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

(생략) ...

Benchmark 결과를 보면 BenchmarkSumV1은 평균 57.334µs가 소요된 반면, BenchmarkSumV2는 7.75µs로 실행되어 훨씬 더 빠른 성능을 보여주었습니다. 이처럼 벤치마크를 활용하면 코드의 실행 시간을 정량적으로 비교하며, 보다 최적화된 성능을 유지하면서 개발을 진행할 수 있도록 도와줍니다.

 

benchmark 옵션

benchmark에서는 여러 옵션을 이용하여 상세한 설정으로 benchmark를 수행할 수 있습니다.

옵션 설명
-benchmem 메모리 사용량 확인
-benchtime 벤치마크 반복 횟수 조정
-cpuprofile CPU 프로파일링
-memprofile 메모리 프로파일링

 

마지막으로

Golang 개발에서 Test는 코드의 정확성을 보장하여 버그를 방지하고 유지보수성을 높이며, Benchmark는 코드의 성능을 측정하여 최적화된 알고리즘을 선택하고 리소스를 효율적으로 사용하게 해 주므로, 이 두 가지를 함께 활용하면 안정적이고 빠른 애플리케이션을 개발할 수 있으며, CI/CD와 결합하여 지속적인 코드 품질 관리를 가능하게 하고, 다양한 코드 변경에도 신뢰할 수 있는 결과를 유지할 수 있기 때문에, 테스트와 벤치마크는 필수적인 개발 요소라 할 수 있습니다.

오픈소스 프로젝트와 라이센스

오픈소스 프로젝트를 살펴보면, 각 코드 파일에 라이센스가 명시되어 있는 것을 볼 수 있습니다. 이 라이센스는 프로젝트에 사용된 코드의 사용 범위와 조건을 정의하여, 코드를 법적으로 보호하는 중요한 역할을 합니다. 라이센스를 각 코드 파일에 일일이 추가하는 작업은 번거로울 수 있습니다. 이러한 불편을 해소하기 위해 Google에서는 addlicense 도구를 제공하고 있으며, 이 도구를 사용하면 코드에 라이센스를 효율적으로 추가할 수 있습니다. 본 문서에서는 addlicense를 사용하여 Go 언어의 코드 파일에 라이센스를 추가하는 방법을 설명하겠습니다. 비록 예시는 Go 언어로 진행하지만, addlicense는 다양한 프로그래밍 언어에 적용 가능합니다.

 

이번 문서에서는 설치 방식의 사용법과 Docker Container 형식의 사용법에 대하여 정리하였습니다. 본인의 환경에 맞는 방법을 선택하여 진행하시면 됩니다.

 

addlicense 설치 방식의 사용법

Google에서 제공하는 addlicense 도구는 Go 언어로 개발되어 있습니다. 따라서 이 도구를 사용하기 전에는 Go 언어가 시스템에 설치되어 있어야 합니다. Go 언어가 이미 설치되어 있지 않다면, 사용하기 전에 반드시 Go 언어의 설치를 먼저 진행해야 합니다. Go 언어 설치는 공식 웹사이트에서 지원하는 안내에 따라 진행할 수 있습니다.

https://go.dev/doc/install

 

addlicense 설치

# addlicense 설치
go install github.com/google/addlicense@latest

 

addlicense 설치 확인

# 설치 확인
ls $(go env GOPATH)/bin
addlicense --help

 

addlicense 설치 확인 시 문제 발생한 경우

만약 command not found 발생 시 PATH를 추가합니다. (이미 추가되어있는 분은 수행하지 않습니다.)

# zsh: command not found: addlicense와 같은 문제가 발생할 경우 PATH에 go bin위치 추가
export PATH=$PATH:$(go env GOPATH)/bin

 

addlicense를 이용하여 라이센스 추가하기

프로젝트 디렉토리로 이동 후 실행

# addlicense 사용법 **로 모든 경로를 지정합니다.
addlicense -c '프로젝트명' -l apache ./**/*.go

 

addlicense 삭제

더 이상 addlicense가 필요 없을 경우 삭제하는 방법입니다.

# addlicense 삭제 방법
ls $(go env GOPATH)/bin
rm $(go env GOPATH)/bin/addlicense

 

Docker container 방식의 사용법

개발자로 근무하면서 다양한 도구와 라이브러리를 설치하고 관리하는 것은 종종 번거롭고 시간이 많이 소모되는 일입니다. 특히 설치한 프로그램을 나중에 제거할 때 발생하는 문제들로 인해 작업 효율이 떨어질 수 있습니다. 이러한 문제를 해결하기 위해 많은 개발자들이 설치와 설정, 실행을 간편화하는 방법으로 Container를 많이 사용하고 있습니다. Container를 사용하면 필요할 때만 특정 환경을 구성하고 작업이 끝나면 컨테이너를 간편하게 제거할 수 있습니다. 이번 섹션에서는 Docker 컨테이너를 사용하여 addlicense 도구를 실행하는 방법을 소개하겠습니다. 

 

도커 이미지 다운로드

docker pull ghcr.io/google/addlicense:latest

 

컨테이너로 실행

docker run -it -v ${PWD}:/src ghcr.io/google/addlicense -c "프로젝트명" **/*.go

 

결과  확인

아래 명령어로 수행한 결과 다음과 같이 라이센스가 추가되었습니다.

addlicense -c 'mytest' -l apache ./**/*.go

 

'Go언어' 카테고리의 다른 글

Go언어에서의 Generic 이해하기  (0) 2025.03.27
Go언어의 benchmark와 test  (0) 2025.03.19
golang version upgrade (ubuntu)  (0) 2023.07.09
go work 사용해보기  (0) 2023.07.06
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30

ubuntu 환경에서 golang version upgrade를 하는 방법에 대하여 간단하게 정리하였습니다.

0. 작업 준비

# apt update를 진행합니다.
sudo apt-get update

# wget이 설치되었는지 확인합니다.
# 다음과 같은 결과가 나오면 wget이 설치된 것 입니다.
# /usr/bin/wget
which wget

# wget이 없다면 설치하여 줍니다.
sudo apt-get install wget

1. 기존 golang 제거

sudo rm -rf /usr/local/go

2. go 공식 사이트에서 새로운 go 버전 다운로드

아래 사이트로 접속하여 새로운 go 버전의 링크를 가져옵니다.

https://go.dev/dl/

 

All releases - The Go Programming Language

 

go.dev

 

사이트에 접속하여 아래 빨간 줄에 우클릭을 통하여 "링크 주소 복사"를 합니다.

 

wget을 통하여 새로운 go 버전은 다운로드합니다.

wget https://golang.org/dl/go1.XX.X.linux-amd64.tar.gz

3. 새로운 golang 설치

다운로드한 tar 파일 압축을 해제합니다.

sudo tar -C /usr/local -xzf go1.XX.X.linux-amd64.tar.gz

 

~/.profile에 다음과 같이 PATH를 추가합니다.

export PATH=$PATH:/usr/local/go/bin

 

~/.profile을 수정한 후 적용합니다.

source ~/.profile

 

정상적으로 설치 되었는지 확인합니다.

go version

 

'Go언어' 카테고리의 다른 글

Go언어의 benchmark와 test  (0) 2025.03.19
[addlicense] golang 프로젝트에 라이센스 추가하기  (0) 2024.04.29
go work 사용해보기  (0) 2023.07.06
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30
Go언어 interface reflect  (0) 2021.08.15

go work를 사용하기 위해 반듯이 go1.18 이상의 go가 설치되어 있어야 합니다. go의 workspace는 회사에서 다른 개발자들과 협업을 하게 되면 모듈의 의존성에 대한 문제가 종종 발생하게 됩니다. 이럴 때 이러한 문제를 해결할 수 있는 것이 바로 go의 workspace입니다. 간단한 예시를 들자면 example이라는 모듈을 현재 회사에서 사용하고 있는데 example이라는 모듈에 기능을 추가하거나 기능을 변경할 경우 협업하고 있는 다른 개발자들에게 영향을 미치게 해서는 안 되는 경우가 있습니다. 이런 경우 "go work"를 통하여 다른 개발자들에게 영향을 미치지 않고 example 모듈을 수정하고 수정된 모듈을 이용하여 프로젝트 개발을 진행할 수 있습니다.

해당 글은 아래 링크 go document문서를 기반으로 똑같이 따라 하며 진행하였으며 개인적인 생각과 설명이 추가된 내용입니다. go work에 대한 원본 및 자세한 내용을 원하신다면 아래 공식 문서를 확인하거나 "$ go help work"를 통하여 보다 정확한 정보를 확인할 수 있습니다.

go workspace 링크 https://go.dev/doc/tutorial/workspaces

 

테스트 환경 구성하기

테스트를 진행할 수 있도록 환경을 구성합니다.

$ mkdir -r ~/workspace
$ cd ~/workspace

 

모듈 초기화하기
(hello라는 프로젝트를 생성합니다.)

$ mkdir hello
$ cd hello
$ go mod init example.com/hello

 

example 모듈 가져오기
(회사에서 사용하는 모듈이 example이라고 가정합니다.)

$ go get golang.org/x/example/hello/reverse

 

코드 작성하기

~/workspace/hello/hello.go 파일을 생성하고 다음과 같이 코드를 작성합니다.

package main

import (
    "fmt"

    "golang.org/x/example/hello/reverse"
)

func main() {
    fmt.Println(reverse.String("Hello"))
}

 

작성된 코드를 실행시켜 모듈이 잘 적용된 것을 확인해 봅시다.

$ cd ~/workspace/hello
$ go run example.com/hello

# 결과 #
olleH

 

workspace 생성하기

go work 명령어를 이용하여 workspace를 생성합니다. (작업 디렉토리는 ~/workspace입니다.)

$ cd ~/workspace
$ go work init ./hello

 

위 명령어를 수행하게 되면 다음과 같이 go.work파일이 생성됩니다. 참고로 저는 go1.20를 사용하고 있습니다.

go 1.20

use ./hello

 

모듈 다운받아 작업 진행하기

이제는 협업하는 다른 개발자들에게 모듈을 수정해도 영향을 미치지 않도록 모듈을 다운받아 모듈을 수정하는 작업을 진행할 예정입니다.
저장소 복제하기 (작업 디렉토리는 ~/workspace입니다.)

$ cd ~/workspace
$ git clone https://go.googlesource.com/example

 

workspace에 모듈 추가하기

$ go work use ./example/hello

 

위 작업을 진행하면 go.work파일이 다음과 같이 수정됩니다.

go 1.20

use (
    ./hello
    ./example/hello
)

 

모듈 수정 작업 진행하기

~/workspace/example/hello/reverse에 int.go라는 파일을 생성하여 다음과 같이 코드를 작성합니다.

package reverse

import "strconv"

// Int returns the decimal reversal of the integer i.
func Int(i int) int {
    i, _ = strconv.Atoi(String(strconv.Itoa(i)))
    return i
}

 

~/workspace/hello/hello.go 파일에 수정된 모듈을 적용합니다.

package main

import (
    "fmt"

    "golang.org/x/example/hello/reverse"
)

func main() {
    fmt.Println(reverse.String("Hello"), reverse.Int(24601))
}

 

수정된 코드를 실행하여 수정된 모듈이 적용된 것을 확인할 수 있습니다.

$ go run ./hello
olleH 10642

지금까지 같이 협업하는 개발자들에게 모듈 수정으로 영향을 미치지 않고 모듈을 수정하여 개발을 진행하도록 하는 workspace에 대하여 알아봤습니다.

 

마지막으로

개발을 완료하고 협업하고 있는 모든 개발자들이 github에 push 하고 merge 작업을 진행할 때 반듯이 go.work 파일이 있다면 버전을 확인 후 작업을 진행한 개발자들과 패키지 버전에 대한 논의를 진행 후 merge 작업을 진행해야 build 과정에서 패키지 버전에 대한 이슈를 예방할 수 있습니다.

'Go언어' 카테고리의 다른 글

[addlicense] golang 프로젝트에 라이센스 추가하기  (0) 2024.04.29
golang version upgrade (ubuntu)  (0) 2023.07.09
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30
Go언어 interface reflect  (0) 2021.08.15
Go언어 Cron  (0) 2021.06.15

Go언어 처음 설치라면 2번부터 진행하시면 됩니다.

1. Go 제거하기

# go언어 제거 하기
sudo apt-get purge golang*
sudo rm -rf /usr/local/go
sudo rm -rf $(echo $GOPATH)

# ~/bashrc 또는 ~/.profile에서 go 관련된 항목 제거(기존에 설정한 파일에서 작업)
source ~/.profile
source ~/.bashrc

# 제거 확인
go version

 

2. apt-get 업데이트 하기

sudo apt-get update
sudo apt-get -y upgrade

 

3. wget 설치 확인 및 wget 설치

# wget 설치 확인 & wget 없을 경우 wget 설치
which wget
sudo apt-get install wget

 

4. Go 최신 버전 설치하기

https://go.dev/dl/

 

Downloads - The Go Programming Language

Downloads After downloading a binary release suitable for your system, please follow the installation instructions. If you are building from source, follow the source installation instructions. See the release history for more information about Go releases

go.dev

 

위 링크에서 최신 버전(현재 최신 버전은 1.19.2)의 linux-amd64.tar.gz에 우클릭하여 링크 주소를 복사하고 wget으로 다운로드합니다.

# go 다운로드
wget https://golang.org/dl/go1.19.2.linux-amd64.tar.gz

# 압축 풀기
sudo tar -C /usr/local -xvf go1.19.2.linux-amd64.tar.gz

# 환경변수 설정
mkdir ~/go
echo "export GOROOT=/usr/local/go" >> ~/.profile
echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.profile
echo "export GOPATH=$HOME/go" >> ~/.profile
source ~/.profile
echo $GOPATH

# go version 확인하기
go version

 

 

 

 

 

'Go언어' 카테고리의 다른 글

golang version upgrade (ubuntu)  (0) 2023.07.09
go work 사용해보기  (0) 2023.07.06
Go언어 interface reflect  (0) 2021.08.15
Go언어 Cron  (0) 2021.06.15
Prometheus Go언어 Metric label  (0) 2021.06.14

reflect 사용 이유

reflect는 여러 가지의 사용방법이 있겠지만 저는 종종 협업을 하면서 상대방이 보낸 데이터의 타입 및 종류에 대하여 구분이 필요한 경우 사용하였습니다. 

 

reflect를 이용하여 타입 가져오기

인터페이스 형식으로 데이터를 받아 받은 데이터의 타입을 구분할 수 있습니다.

package main

import (
	"fmt"
	"reflect"
)

func ShowType(i interface{}) {
	fmt.Println(reflect.ValueOf(i).Type())
}

func main() {
	num := 100
	ShowType(num)

	job := "student"
	ShowType(job)
}
/* 출력결과
int
string
*/

코드설명

reflect.ValueOf().Type()을 사용하여 해당 타입이 어떤 타입인지 알 수 있습니다.

 

reflect를 이용하여 구조체 멤버 변수 확인하기

reflect를 사용하여 인터페이스로 받은 구조체의 멤버 변수들을 확인하도록 하겠습니다. 여기서 중요한 건 멤버 변수가 없는 일반 변수의 인터페이스를 넘기면 panic이 발생합니다.

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Job  string `json:"job"`
}

func (p Person) Print() {
	fmt.Println(p.Name, " is ", p.Job)
}

func ShowMetaData(i interface{}) {
	elements := reflect.ValueOf(i).Elem()

	for index := 0; index < elements.NumField(); index++ {
		typeField := elements.Type().Field(index)
		fmt.Println(typeField.Name, typeField.Type, typeField.Tag, elements.Field(index))
	}
}

func main() {
	p := &Person{
		Name: "Han",
		Age:  30,
		Job:  "Developer",
	}
	ShowMetaData(p)

	// panic 발생
	//num := 100
	//ShowMetaData(num)
}

/* 출력결과
Name string json:"name" Han
Age int json:"age" 30
Job string json:"job" Developer
*/

코드설명

ShowMetaData() 함수를 보면 interface의 value를 reflect.Value().Elem()을 통하여 elements들을 가져옵니다. 그리고 

elements.Type().Field()를 통하여 현재 필드의 데이터를 StructField형식으로 가져올 수 있습니다. StructField의 구조는 다음과 같습니다.

 

StructField의 구조

// A StructField describes a single field in a struct.
type StructField struct {
	// Name is the field name.
	Name string
	// PkgPath is the package path that qualifies a lower case (unexported)
	// field name. It is empty for upper case (exported) field names.
	// See https://golang.org/ref/spec#Uniqueness_of_identifiers
	PkgPath string

	Type      Type      // field type
	Tag       StructTag // field tag string
	Offset    uintptr   // offset within struct, in bytes
	Index     []int     // index sequence for Type.FieldByIndex
	Anonymous bool      // is an embedded field
}

Name : 구조체 멤버 변수의 이름

Type : 구조체 맴버변수의 타입

Tag : 구조체 맴버변수의 태그

Offset : 구조체 멤버 변수의 오프셋(맴버변수의 메모리 위치)

Index : 구조체 맴버변수의 선언 순서

 

reflect를 이용하여 멤버 변수 가져오기

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name   string `json:"name"`
	Age    int    `json:"age"`
	Job    string `json:"job"`
	Salary int    `json:"salary"`
}

func (p Person) Print() {
	fmt.Println(p.Name, " is ", p.Job)
}

func (p *Person) SetSalary(salary int) {
	p.Salary = salary
}

func (p Person) GetName() string {
	return p.Name
}

func ShowMethod(i interface{}) {
	value := reflect.ValueOf(i)

	for index := 0; index < value.NumMethod(); index++ {
		fmt.Printf("method name : %s\n", value.Type().Method(index).Name) // 1번
		fmt.Printf("method type : %v\n\n", value.Method(index).Type()) // 2번
	}
}

func main() {
	p := &Person{
		Name: "Han",
		Age:  30,
		Job:  "Developer",
	}
	ShowMethod(p)
}

/* 출력결과
method name : GetName
method type : func() string

method name : Print
method type : func()

method name : SetSalary
method type : func(int)
*/

코드설명

1번 주석을 보면 Type().Method()를 통하여 구조체의 메서드의 타입을 가져올 수 있습니다. 가져온 메서드 타입으로 메서드 명을 가져올 수 있습니다.

2번 주석을 보면 Method().Type()을 통해 실제 메서드의 구성을 알 수 있습니다.

 

 

'Go언어' 카테고리의 다른 글

go work 사용해보기  (0) 2023.07.06
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30
Go언어 Cron  (0) 2021.06.15
Prometheus Go언어 Metric label  (0) 2021.06.14
Prometheus Go언어 Metric 생성  (0) 2021.06.13

Cron이란

우리는 살면서 종종 정해진 시간에 정해진 일이 자동으로 수행되었으면 하는 일들이 많이 있습니다. Go언어에서는 이러한 일을 cron이 해결해 줍니다.

 

먼저 프로젝트 디렉토리에서 다음 명령어로 모듈을 가져옵니다.

 > go get github.com/robfig/cron
package main

import (
    "fmt"
    "github.com/robfig/cron"
    "time"
)

const (
    CronSpec = "30 * * * * *"
)

var (
    Data int
)

func PrintData() {
    t := time.Now().Format(time.ANSIC)
    fmt.Println(t, " : ", Data)
    Data++
}

func main() {
    c := cron.New()
    c.AddFunc(CronSpec, PrintData)
    c.Start()

    time.Sleep(time.Minute * 10)
    c.Stop()
}
/* 출력결과
Tue Jun 15 15:15:30 2021  :  0
Tue Jun 15 15:16:30 2021  :  1
Tue Jun 15 15:17:30 2021  :  2
*/

코드설명

cron.New()를 통하여 cron을 생성하고 AddFunc()를 통해 동작할 시간대와 동작할 함수를 등록합니다. "30 * * * * *"을 cron 시간으로 등록하였습니다. 이것은 출력결과를 보면 알 수 있듯이 매시간 매분 30초 일때 마다 함수가 동작합니다.

 

Cron Spec 구조

CronSpec을 "30 * * * * *"로 설정하였습니다. 여기서 많이 착가할 수 있는 부분이 30초 마다 동작을 수행하는 것으로 오해할 수 있습니다. 그러나 이것은 매 시간 매분 30초가 되었을 경우 수행하는 것을 의미합니다. 이제 Cron spec에 구조에 대하여 알아보겠습니다.

가장 왼쪽 부터 초, 분, 시, 일, 월, 요일 로 구성되어 있습니다.

Seconds Minute Hour Day of month Month Week
0 ~ 59 0 ~ 59 0 ~ 23 1 ~ 31 1 ~ 12 0 ~ 6

예제

"0 0 0 * * *" : 매일 0시 0분 0초에 수행합니다.

"0 0 0 1 1 *" : 1월 1일 0시 0분 0초에 수행합니다.

 

Cron Spec에서 사용되는 문자

별표 *

별표는 눈치채셨겠지만 모든 값을 뜻합니다.

 

콤마 ,

콤마는 해당 spec에 여러개의 값을 설정하는데 사용합니다. "10,20,30 * * * * *" 이렇게 설정을 한다면 매시 매분 10초, 20초, 30초 일때 수행합니다.

 

마이너스 -

마이너스는 값의 범위를 지정합니다. "10-15 * * * * *"일 경우 매시 매분 10초 11초 12초 13초 14초 15초 에 수행합니다.

 

슬래쉬 /

슬래쉬의 경우 해당 범위에서 수행하는 간격을 의미 합니다. "10-50/5 * * * * *" 일 경우 매시 매분에서 10초에서 50초 사이에 5초에 한번씩 수행합니다.

 

'Go언어' 카테고리의 다른 글

go work 사용해보기  (0) 2023.07.06
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30
Go언어 interface reflect  (0) 2021.08.15
Prometheus Go언어 Metric label  (0) 2021.06.14
Prometheus Go언어 Metric 생성  (0) 2021.06.13

Label이란

Label이란 메트릭에 Key-Value형식으로 Metric을 구분하는데 많이 사용된다. Kubernetes에서는 Pod, Node, Cluster, 또는 Application 등을 Metric에서 구분하는데 많이 사용된다.

 

Go언어 Metric에 Label 등록하기

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
	"time"
)

const (
	LabelAppName = "application_name"
	LabelFuncName = "function_name"
)

var (
	MyCounter *prometheus.CounterVec
)

func Init() {
	MyCounter = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "my_test_count",
		Help: "my test count",
	},[]string{
		LabelAppName,
		LabelFuncName,
	})
}

func RunFunc1() {
	labels := prometheus.Labels{
		LabelAppName: "my_app",
		LabelFuncName: "run_func1",
	}

	for {
		MyCounter.With(labels).Inc()
		time.Sleep(time.Second * 2)
	}
}

func RunFunc2() {
	labels := prometheus.Labels{
		LabelAppName: "my_app",
		LabelFuncName: "run_func2",
	}

	for {
		MyCounter.With(labels).Inc()
		time.Sleep(time.Second * 5)
	}
}

func main() {
	Init()
	go RunFunc1()
	go RunFunc2()

	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":30001", nil)
}

코드설명

기존 Metric의 코드와 크게 다르지 않다. prometheus.CounterVec를 통하여 메트릭을 생성하고 메트릭을 생성할 때 상수로 선언된 LabelAppName, LabelFuncName을 통하여 application_name, function_name이라는 Label을 등록하였습니다. 그리고 각각의 RunFunc1(), RunFunc2() 함수로 각각 별도의 function_name의 Label을 등록하였습니다.

Promql로 구분하여 검색하기

기존처럼 Metric 이름으로 검색하면 run_func1, run_func2가 모두 검색됩니다.

my_test_count

Label에 run_func1을 지정하여 특정 Label의 Metric을 검색하면 run_func1만 검색됩니다.

my_test_count{function_name="run_func1"}

 

결과 확인하기

 

'Go언어' 카테고리의 다른 글

go work 사용해보기  (0) 2023.07.06
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30
Go언어 interface reflect  (0) 2021.08.15
Go언어 Cron  (0) 2021.06.15
Prometheus Go언어 Metric 생성  (0) 2021.06.13

Prometheus Metric Type

Counter 카운터

Counter는 개수를 측정하거나 증가하는 값을 측정할 때 사용합니다. 카운터는 0으로 초기화는 가능하지만 값을 감소할 수 없고 증가만 할 수 있습니다. 대표적으로 함수 호출 횟수 및 접속 요청 수, 실패 개수를 측정할 때 많이 사용할 수 있습니다.

 

Gauge 게이지

Gauge는 현재의 값을 나타내는 데 사용합니다. 값이 증가할 수 있으며 감소할 수도 있습니다. 증가 감소 모두 가능하다 보니 여러 방면에서 사용할 수 있습니다. 대표적으로 현재 메모리 사용량, CPU 사용 시간, 저장된 데이터의 수, 스레드 개수 등 여러 방면에서 사용합니다.

 

Summary 서머리

Summary는 _count, _sum을 제공하여 평균을 측정할 수 있으며, 슬라이딩 시간 윈도우를 측정할 때 사용합니다.

 

Histogram 히스토그램

Histogram은 시간 동안의 값을 측정하고 측정한 값을 통하여 quantile을 측정할 수 있습니다.

 

Go언어로 Metric 생성하기

먼저 다음 명령으로 모듈을 받습니다.

go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promauto
go get github.com/prometheus/client_golang/prometheus/promhttp

 

Counter 생성하기

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
	"time"
)

var (
	MyCounter prometheus.Counter
)

func Init() {
	MyCounter = promauto.NewCounter(prometheus.CounterOpts{
		Name: "my_test_count",
		Help: "my test count",
	})
}

func RunCounter() {
	for i := 0; ; i++ {
		MyCounter.Inc()
		//MyCounter.Add(10)
		time.Sleep(time.Second * 2)
	}
}

func main() {
	Init()
	go RunCounter()

	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":30001", nil)
}

코드설명

promauto.NewCounter를 통하여 카운터를 생성하고 카운터의 이름을 my_test_count로 만들었다. 카운터는 2초에 한 번씩 MyCounter.Inc()를 통하여 1씩 증가하고 있다. MyCounter.Add()를 이용하면 1 이상의 값을 증가시킬 수도 있다.

 

확인하기

Gauge 생성하기

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
	"time"
)

var (
	MyGauge prometheus.Gauge
)

func Init() {
	MyGauge = promauto.NewGauge(prometheus.GaugeOpts{
		Name: "my_test_gauge",
		Help: "my test gauge",
	})
}

func RunGauge() {
	for i := 0; ; i++ {
		MyGauge.Set(float64(i))
		time.Sleep(time.Second * 2)
	}
}

func main() {
	Init()
	go RunGauge()

	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":30001", nil)
}

코드설명

promauto.NewGauge를 통하여 my_test_gauge를 생성하였습니다. 2초마다 i의 값으로 gauge값을 세팅합니다.

 

 

Summary 생성하기

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"math/rand"
	"net/http"
	"time"
)

var (
	MySummary prometheus.Summary
)

func Init() {
	MySummary = promauto.NewSummary(prometheus.SummaryOpts{
		Name: "my_test_summary",
		Help: "my test summary",
	})
}

func RunSummary() {
	for {
		MySummary.Observe(float64(rand.Int63n(10)))
		time.Sleep(time.Second * 2)
	}
}

func main() {
	Init()
	go RunSummary()

	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":30001", nil)
}

코드설명

promauto.NewSummary를 통하여 my_test_summary를 생성하고 2초마다 0 ~ 10까지의 난수로 Observe를 통하여 값을 등록합니다.

 

확인하기

 

Histogram 생성하기

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"math/rand"
	"net/http"
	"time"
)

var (
	MyHistogram prometheus.Histogram
)

func Init() {
	MyHistogram = promauto.NewHistogram(prometheus.HistogramOpts{
		Name: "my_test_histogram",
		Help: "my test histogram",
	})
}

func RunHistogram() {
	for {
		MyHistogram.Observe(rand.Float64())
		time.Sleep(time.Second * 2)
	}
}

func main() {
	Init()
	go RunHistogram()

	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":30001", nil)
}

코드설명

역시나 promauto.NewHistogram을 통해 생성합니다. 2초마다 난수를 등록합니다.

 

확인하기

다른 방식의 Metric 생성

지금까지 promauto를 통하여 Metric을 생성했습니다. 그러나 promauto를 사용하지 않고 다른 방식으로 Metric을 생성하는 방법을 알아보겠습니다.

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
	"time"
)

var (
	MyCounter prometheus.Counter
)

func Init() {
	MyCounter = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "my_test_count",
		Help: "my test count",
	})
}

func RunFunc() {
	for {
		MyCounter.Inc()
		time.Sleep(time.Second * 2)
	}
}

func main() {
	Init()
	prometheus.MustRegister(MyCounter)

	go RunFunc()

	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":30001", nil)
}

코드설명

promauto.NewCounter가 아닌 prometheus.NewCounter를 통하여 Metric을 생성했습니다. promauto를 사용하지 않으면 prometheus.MustRegister를 통하여 Collector를 등록해야 됩니다.

 

 

'Go언어' 카테고리의 다른 글

go work 사용해보기  (0) 2023.07.06
Ubuntu(Linux)에서 Go 재설치  (0) 2022.10.30
Go언어 interface reflect  (0) 2021.08.15
Go언어 Cron  (0) 2021.06.15
Prometheus Go언어 Metric label  (0) 2021.06.14

+ Recent posts