Golangでロードバランサーを実装する

概要

この記事はMakuake Advent Calendar 2021の24日目の記事です。(大遅刻しました・・) ラウンドロビンで負荷分散するロードバランサーをGolangで自作してみるという話です。

ロードバランサーとは何か

ロードバランサーはリクエストを複数のサーバーへ振り分けて負荷分散する(ロードバランシング)機能を持ったサーバーです。

スクリーンショット 2022-01-01 23 05 20

サービスの可用性を高めてくれるリバースプロキシの一種です。 ロードバランサーの種類は大きく分けて2種類あります。アプリケーション層で負荷分散するL7ロードバランサーと、トランスポート層で負荷分散するL4ロードバランサーです。 ロードバランサーは、ロードバランシングの他、パーシステンス(セッション維持)とヘルスチェックの機能を兼ね備えています。

ロードバランシングの種類

負荷分散には静的な方式と動的な方式のものでそれぞれ種類があります。 静的なものの代表的な方式としては、リクエストを均等に振り分けるRound Robinという方式があります。 動的なものの代表的な方式としては、リクエストの未処理数が最小のサーバーに振り分けるLeast Connectionという方式があります。

パーシステンスの種類

パーシステンスはロードバランサーの複数の振り分け先のサーバー間でセッションを維持するための機能です。 大きく分けてSource address affinity persistenceという方式とCookie persistenceという2つの方式があります。 Source address affinity persistenceは送信元IPアドレスを見て振り分け先のサーバーを固定する方式です。 Cookie persistenceはセッション維持のためのCookieを発行して、Cookieを見て振り分け先のサーバーを固定する方式です。

ヘルスチェックの種類

ヘルスチェックはロードバランサーが振り分け先のサーバーの稼働状況を確認する機能です。 ロードバランサーから振り分け先のサーバーにヘルスチェックするアクティブ型のヘルスチェック方式と、クライアントからのリクエストに対するレスポンスを監視する方式です。 アクティブチェックは利用するプロトコルによってはL3チェック、L4チェック、L7チェックといった種類に分別することができます。

実装

L4ロードバランサーをパッケージとして実装します。 ロードバランシングの種類はラウンドロビンで、ヘルスチェックはアクティブチェック・パッシブチェックのそれぞれ対応します。 パーシステンスはの対応はしません。

今回実装したコードはgithub.com/bmf-san/godonにあります。

リバースプロキシを実装

ロードバランサーはリバースプロキシの一種です。まずは簡単なリバースプロキシの実装から始めます。

Golangではhttputilを利用することで簡単に実装することができます。

ここでは説明を省きますが、pkg.go.dev/net/http/httputil#ReverseProxyをよく読んでおくと良いかと思います。

Configの実装

簡単なロードバランサーなので複雑な設定を持ちませんが、jsonから設定を読み込むような設定の機能を実装しておきます。

ラウンドロビンの実装

次にラウンドロビンの実装をします。

均等にバックエンドのサーバーにリクエストを振り分けるのみで、バックエンドのサーバーの生死は問わない形で実装します。

sync.Mutexを利用しているのは、複数のGoroutineが変数にアクセスすることによる競合状態を回避するためです。

試しにsync.Mutexを外してgo run -race server.goでサーバー起動、複数端末から同時にリクエストするとrace conditionを確認することができます。

アクティブチェックの実装

ここまでの実装では、ロードバランサーは異常なバックエンドに対してもリクエストを転送するようなロジックとなっています。

実際のユースケースでは異常なバックエンドにわざわざリクエストを転送してほしくはないので、異常なバックエンドを検知して、振り分け先から外れるようにします。

ロードバランサーがバックエンドにリクエストを転送したときにエラーを検知すると呼び出されるErrorHandlerを実装します。 ErrorHandlerでは、正常にレスポンスを返さないバックエンドにフラグを立てて、もう一度ロードバランサーにリクエストを転送してもらうような形にしています。 ロードバランサーはフラグの立っているバックエンドにはリクエストを転送しないようにロジックを調整しています。

パッシブチェックの実装

最後にパッシブチェックの実装をします。 パッシブチェックは、インターバルを指定してバックエンドサーバーのレスポンスを監視するだけです。 異常が検知されたバックエンドは、アクティブチェックのときと同じようにフラグが立てられます。

パッシブチェックを実装し終えた全てのコードは以下になります。

所感

リトライの実装やパーシステンスの対応などができていませんが、Golangでは比較的簡単にロードバランサーを実装できることが分かったかと思います。

参考