LoginSignup
6
5

More than 5 years have passed since last update.

Go言語から、ZookeeperでのHA動作を試してみる

Last updated at Posted at 2016-01-23

アプリ冗長には、Apache Zookeeperが活用されているそうですが、その動作イメージが理解できていなかったので、調査してみました。

◼︎ Zookeeperを制御するには...

Go言語から、Zookeeperを制御するにあたり、Native Go Zookeeper Client Libraryのオープンソース活用を前提にしております。

◼︎ Zookeeper動作環境をつくる

  • Zookeeperをインストール(例えば、Mac OS Xにインストールする場合には...)
$ brew install zookeeper
  • 必要に応じて、Zookeeperを起動する
 $ zkServer start
JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo.cfg
Starting zookeeper ... STARTED
  • Native Go Zookeeper Client Libraryをインストールする
$ go get github.com/samuel/go-zookeeper/zk

◼︎ サンプルアプリを配置する

  • storedデータ確認用サンプルアプリ

Zookeeperに保管されるkey-valueストアの値を確認することができます。
HA動作を試すことを目的としているので、"/zookeeper-for-myApp"のみに限定しております。

sample_zookeeper_cli.go
package main

import (
    "github.com/samuel/go-zookeeper/zk"
    "os"
    "strings"
    "time"
    "fmt"
)

var RootPath string = "/zookeeper-for-myApp"
var zksStr string = os.Getenv("ZOOKEEPER_SERVERS")

func Connect(zks []string) *zk.Conn {
    conn, _, err := zk.Connect(zks, time.Second)
    if err != nil {
        panic(err)
    }
    return conn
}

func main() {
    zks := strings.Split(zksStr, ",")
    conn := Connect(zks)
    defer conn.Close()

    data, _, err := conn.Get(RootPath)
    fmt.Printf("%s: %s\n", RootPath, string(data))

    children, _, err := conn.Children(RootPath)
    if err != nil {
        panic(err)
    }
        for _, name := range children {
            data, _, err := conn.Get(RootPath+"/"+name)
            if err != nil {
                panic(err)
            }
        fmt.Printf("%s: %s\n", name, string(data))
    }
}
  • 冗長確認用サンプルアプリ

ユーザアプリがHA構成(Active-StandBy)で動作します。

sample_zookeeper_myApp.go
package main

import (
    "github.com/samuel/go-zookeeper/zk"
    "os"
    "os/signal"
    "strings"
    "strconv"
    "time"
    "log"
)

var RootPath string = "/zookeeper-for-myApp"
var zksStr string = os.Getenv("ZOOKEEPER_SERVERS")

func CreateRootPath(zks []string) *zk.Conn {
    flags := int32(0)
    acl := zk.WorldACL(zk.PermAll)
    conn, _, err := zk.Connect(zks, time.Second)
    conn.Create(RootPath, []byte{0}, flags, acl)
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    return conn
}


func JoinRootPath(zks []string) {
    acl := zk.WorldACL(zk.PermAll)
    conn, _, err := zk.Connect(zks, time.Second)
    if err != nil {
        panic(err)
    }
    lockPrefix := "/lock-"

    path, err := conn.CreateProtectedEphemeralSequential(RootPath+lockPrefix,
                                                         []byte{0}, acl)
    myData := getParse(path)
    ticker := time.NewTicker(time.Second)
    for {
        var isLeader bool = true
        children, _, err := conn.Children(RootPath)
        if err != nil {
            isLeader = false
        }
        if children == nil {
            isLeader = false
        }
        for _, c := range children {
            otherData := getParse(c)
            if myData > otherData {
                isLeader = false
                break
            }
        }

        if isLeader == true {
            log.Println("### I am Leader !! ###")
        }
        <-ticker.C
    }
}

func getParse(path string) int {
    splits := strings.Split(path, "-")
    seq := splits[len(splits)-1]
    data, _ := strconv.Atoi(seq)
    return data
}

func main() {
    zks := strings.Split(zksStr, ",")
    CreateRootPath(zks)
    quit_channel := make(chan os.Signal, 1)
    signal.Notify(quit_channel, os.Interrupt)
    go JoinRootPath(zks)
    <-quit_channel
}

◼︎ さっそく、動かしてみる

Zookeeper自体も冗長構成をとることも可能ですが、ここでは、シンプルにStandAlone構成としました。

zookeeper.001.jpeg

(1) 環境変数の設定
Zookeeperにアクセスする場合には、ZOOKEEPER_SERVERS環境変数を有効にしておく必要があります。ZookeeperをインストールしたホストのIPアドレスを指定します。

$ export ZOOKEEPER_SERVERS=192.168.100.201:2181

という感じで、環境変数を有効にします。

(2) sample_zookeeper_myApp.goの起動
まずは、192.168.100.201側から起動します。

192.168.100.201側
$ go run sample_zookeeper_myApp.go 
2016/01/23 08:49:33 ### I am Leader !! ###
2016/01/23 08:49:34 ### I am Leader !! ###
2016/01/23 08:49:35 ### I am Leader !! ###
2016/01/23 08:49:36 ### I am Leader !! ###

.. snip

このタイミングで、Zookeeperでのstoredデータを確認しておきます。

$ go run sample_zookeeper_cli.go 
/zookeeper-for-myApp: 
_c_d7f13bfb7352e81a199896a8da87623d-lock-0000000018: 

すると、"_c_d7f13bfb7352e81a199896a8da87623d-lock-0000000018"を保管されました。ここでは、"0000000018"という数値に注目しておいてください。

続いて、192.168.100.202側も、起動します。何も表示されません。StandByモードで動作しているためです。

192.168.100.202側
$ go run sample_zookeeper_myApp.go

再び、Zookeeperでのstoredデータを確認します。

$ go run sample_zookeeper_cli.go 
/zookeeper-for-myApp: 
_c_d7f13bfb7352e81a199896a8da87623d-lock-0000000018: 
_c_2937f250274512907861a5d5ffde2481-lock-0000000019: 

今度は、"_c_2937f250274512907861a5d5ffde2481-lock-0000000019"が追加されました。

先ほどの"0000000018"よりも、値が大きい、"0000000019"という数値に注目してください。HA構成で動作するユーザアプリが、「Activeモードで動作するのか?」、「StandByモードで動作するのか?」の判定は、このstoredデータの末尾の数値の大小比較で決定されます。
ちなみに、判定ロジックの挙動については、以下の記事が参考になると思います。
Chengwei's Words: How Zookeeper Leader Election Works

(3) Active側アプリの強制停止
192.168.100.201側のアプリを強制停止してみます。

192.168.100.201側
$ go run sample_zookeeper_myApp.go 

.. snip

2016/01/23 08:49:58 ### I am Leader !! ###
2016/01/23 08:49:59 ### I am Leader !! ###
2016/01/23 08:50:00 ### I am Leader !! ###
2016/01/23 08:50:01 ### I am Leader !! ###
2016/01/23 08:50:02 ### I am Leader !! ###
2016/01/23 08:50:03 ### I am Leader !! ###
2016/01/23 08:50:04 ### I am Leader !! ###
^C

すると、先ほどまで、StandByモードで動作していた、192.168.100.202側では、Active動作を開始するようになりました。(なお、HA動作によるActiveノード切り替えに約6秒程度の時間を要している様子も併せて、確認できると思います。)

192.168.100.202側
$ go run sample_zookeeper_myApp.go
2016/01/23 08:50:10 ### I am Leader !! ###
2016/01/23 08:50:11 ### I am Leader !! ###
2016/01/23 08:50:12 ### I am Leader !! ###
2016/01/23 08:50:13 ### I am Leader !! ###
2016/01/23 08:50:14 ### I am Leader !! ###
2016/01/23 08:50:15 ### I am Leader !! ###
2016/01/23 08:50:16 ### I am Leader !! ###
2016/01/23 08:50:17 ### I am Leader !! ###
2016/01/23 08:50:18 ### I am Leader !! ###
2016/01/23 08:50:19 ### I am Leader !! ###
2016/01/23 08:50:20 ### I am Leader !! ###
2016/01/23 08:50:21 ### I am Leader !! ###

再び、Zookeeperでのstoredデータを確認します。

$ go run sample_zookeeper_cli.go 
/zookeeper-for-myApp: 
_c_2937f250274512907861a5d5ffde2481-lock-0000000019: 

今度は、"_c_2937f250274512907861a5d5ffde2481-lock-0000000019"しか保管されておりません。

192.168.100.201側のアプリを強制停止したことにより、"_c_d7f13bfb7352e81a199896a8da87623d-lock-0000000018"が削除されたことがわかります。

以上より、サンプルアプリによるHA構成(Active-StandBy)が、正しく動作することが確認できました。

◼︎ 終わりに、

Zookeeperは、所謂、分散Key-Valueストアなのですね。
Zookeeperを活用したHA構成を実現するには、storedデータを随時ルックアップして、HA冗長の切り替え判定ロジックを実装する必要があります。ただし、Native Go Zookeeper Client Libraryのオープンソース活用すれば、そんなに稼働をかけずに、オリジナルな判定ロジックを実装したHA構成を実現することができるようになります。

◼︎ 参照元

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5