/img/avatar.jpg

간단한 로드밸런서와 HTTP 프록시 서버 구현

레이트 리미터

Limiter interface

package limiter

type Limiter interface {
	TryTake([]byte) (bool, int)
}

레이트 리미터 객체가 구현해야할 인터페이스로 Limiter 인터페이스가 있습니다. 바이트 슬라이스를 받아서 해당 슬라이스를 기반으로 레이트 리밋을 계산하여 이번 요청이 사용 가능하면 true와 적절한 status code를 반환합니다.

slide count struct

type SlideCount struct {
	lock       *lock.Lock
	unit       int64
	maxConnPer float64
	prevTime   int64
	prevCount  int64
	curCount   int64
	nextTime   int64
}

func New(maxConnPer float64, unit time.Duration) limiter.Limiter {
	now := int64(time.Now().UnixNano())
	return &SlideCount{
		lock:       new(lock.Lock),
		unit:       int64(unit),
		maxConnPer: maxConnPer,
		prevTime:   now - int64(unit),
		prevCount:  0,
		curCount:   0,
		nextTime:   now + int64(unit),
	}
}

슬라이드 카운트 구조체는 sliding window count 방식의 레이트 리밋을 구현한 것입니다. 일반적인 케이스와 달리 제 주관적인 해석이 들어가 있으므로 코드가 좀 다릅니다.

전략 패턴에 대해서

행위 패턴의 하나로, 어떤 문제를 해결함에 어떤 방법을 사용하는 지 적절히 선택, 혹은 작성할 수 있게 해주는 패턴


코틀린으로 평균을 구하는 계산기 만들기

이 계산기의 평균을 구하는 방법은 총 3가지가 있습니다.

  1. 산술 평균 : (a + b) / 2

  2. 기하 평균 : root2(a * b)

  3. 조화 평균 : 2ab / (a + b)

이 3가지를 각각 따로 입력 받아 실행하는 평균만 구하는 계산기의 코틀린 코드입니다.

import kotlin.math.sqrt

fun main(args: Array<String>) {
    val (a, b) = Pair(30, 80)
    val arithmeticCalculator = Calculator(ArithmeticMean())
    println(arithmeticCalculator.average.calculate(a, b))

    val geometricCalculator = Calculator(GeometricMean())
    println(geometricCalculator.average.calculate(a, b))

    val harmonicCalculator = Calculator(HarmonicMean())
    println(harmonicCalculator.average.calculate(a, b))
}

class Calculator(val average: Average)

interface Average {
    fun calculate(a: Int, b: Int): Int
}

class ArithmeticMean: Average {
    override fun calculate(a: Int, b: Int): Int {
        return (a + b) / 2
    }
}

class GeometricMean: Average {
    override fun calculate(a: Int, b: Int): Int {
        return sqrt(a.toDouble() * b.toDouble()).toInt()
    }
}

class HarmonicMean: Average {
    override fun calculate(a: Int, b: Int): Int {
        return (2*a*b) / (a + b)
    }
}

ArithmeticMean, GeometricMean, HarmonicMean 클래스는 Average 인터페이스를 상속 받아 평균을 구하는 메서드를 오버라이드하고 있습니다.

고루틴 풀링

이 글은 제 레포를 기반으로 작성되었습니다.

왜?

오픈톡방에서 고루틴에 대한 이야기가 나왔었습니다. 고루틴을 안전하게 관리하기 위한 보일러플레이트에 대한 것과 join과 반환값의 처리에 대한 것이었습니다.

그래서 한번 해당 건에 대해 나름의 해답을 라이브러리로 만들어봤습니다.

gopool

GoPool

type GoPool struct {
	pool    sync.Pool
	max     int64
	count   int64
	running int64
    sync.Mutex
}

고풀 구조체는 고루틴을 풀링할 sync.Pool, 그리고 int64 타입의 max, count, running을 가집니다.
max는 최대 고루틴 수, count는 현재 생성된 고루틴 수, running은 현재 실행되고 있는 고루틴 수를 의미합니다.

logstream

이 글은 제 로그 라이브러리를 작성하며 생각한 것을 작성한 것입니다.

개요

logstream 라이브러리는 로그를 생성하고 소비하는 패턴을 구현하기 위해 만든 일종의 고 프로그램 전역에서 돌아가는 메시지 큐입니다. 다른 메시지 큐와 마찬가지로 여러 생산자가 토픽에 값을 추가하고 소비자가 토픽에서 값을 꺼내서 사용합니다.

/img/006/logstream.png

생산자(producer)는 코드 상 어디라도 될 수 있으며 스트림을 내포하고 있는 객체는 글로벌큐(global queue)로 전역 객체로 생성될 것입니다. 소비자(customer)는 글로벌큐 내부 스트림을 통해 순차적으로 생산자가 만들어낸 로그를 받아서 소비합니다. 소비는 가급적이면 모든 소비자 중 하나가 한번 소비하면 로그가 사라지는 게 아니라 모든 소비자에게 돌아가는 걸 목표로 합니다.

스마트폰 파괴 방법과 매개변수 덕타핑

개요

인터페이스 비교로 에러를 처리하는 방식에 기반하여 인터페이스로 매개변수를 덕타이핑하는 건 어떨까 해서 한번 시도해봤습니다.

아이디어

아이디어는 쉽습니다. 베이스가 되는 인터페이스와 구조체를 작성합니다. 이후 필요에 따라 매개변수를 얻을 수 있는 새로운 인터페이스를 제공하고 해당 인터페이스에 대한 구조체를 작성합니다. 그렇다면 동일한 함수 원형에 여러가지 타입의 매개변수를 넘겨줄 수 있을 것입니다.

구현

스마트폰을 파괴하는 코드를 작성해보겠습니다.

PhoneBreaker

package phonebreaker

type PhoneBreakable interface {
	Break(PhoneBreakingTool) int
}

type PhoneBreakingTool interface {
	Hand() int
}

간단하게 PhoneBreakable 인터페이스로 휴대폰을 파괴할 수 있는 행위를 정의해 놓았습니다.
그리고 가장 간단한 파괴 도구로 Hand()를 가지는 툴 인터페이스도 만들어 놓았습니다.

Option과 default parameter

option

option 패키지는 직전 포스트에 작성한 result 패키지에서 에러 메시지가 빠진 형태입니다.

구조체

type Option[T any] struct {
	value any
}

func Some[T any](value T) *Option[T] {
	return &Option[T]{value: value}
}

func None[T any]() *Option[T] {
	return &Option[T]{value: nil}
}

구조체는 result와같이 any 타입을 가진 멤버 하나만 존재합니다. Some 생성자는 인자의 타입에 따른 Option 구조체를 생성하고 멤버 변수에 인자를 대입합니다. None 생성자는 타입 인자만 하나 받으며 해당 타입에 대한 Option 구조체를 반환하지만 멤버 변수는 nil이 대입됩니다.

Result를 활용한 에러처리

이 글은 제가 작성한 레포인 generics-for-go를 기반으로 쓰여졌습니다.

개요

예전 고 1.18 dev 버전이 나왔을 때 한 레포를 만들어서 제네릭 관련 함수를 찍어낸 적이 있습니다. 그 이후 한동안 잊고 지내다가 1.18 beta가 나오게 되고 이제 큰 피처의 변화는 없을 거라 판단해서 제네릭으로 몇가지 장난을 하던 도중, 러스트같은 언어에서 자주 보이는 Option과 Result를 만들어 보고 싶었습니다.

예전에도 당연히 interface{}만으로 구현에 도전해봤지만 사실 타입 단언(Type Assertion)을 해야한다는 것에서 그다지 사용성이 좋지 못 했고 이로 인해 코드 수만 늘어났습니다. 하지만 제네릭이라는 수단이 추가되니 생각할 폭이 넓어지고 재밌는 문법이 떠올랐습니다.

에러 처리 in go

쉬운 에러 처리

package main

import (
	"errors"
)

func NewError() error {
	return errors.New("this is new error!")
}

func ThrowError() error {
	_, err := os.Open("not-exist")
	return err
}

func main() {
	err := NewError()
	if err != nil {
		log.Println(err)
	}

	err = ThrowError()
	if err != nil {
		log.Println(err)
	}
}

가장 쉬운 에러 처리 방법은 errors.New 함수로 에러 메시지를 담은 error 인터페이스 인스턴스를 직접 만드는 것과 반환된 에러를 그대로 상위 스택에 넘겨주는 방법이 있습니다. 하지만 이 2가지 방식은 각기 다른 문제점이 있습니다.