Cloud Native 10 min read

Mastering etcd with Go: From Basics to Distributed Locks

This article introduces etcd as a reliable distributed key‑value store built on Raft, outlines its key features and common use cases such as service discovery and configuration management, and provides a complete Go tutorial covering dependency setup, server launch, client implementation, read/write testing, and distributed lock usage.

FunTester
FunTester
FunTester
Mastering etcd with Go: From Basics to Distributed Locks

etcd is a distributed key‑value store developed by CoreOS that provides reliable configuration management and service discovery for distributed systems, built on the Raft consensus algorithm to guarantee strong consistency and high availability.

Key Features and Typical Use Cases

Simple key‑value data model with watch and atomic transaction support.

Strong consistency via Raft, ensuring only one leader handles writes.

High availability through automatic leader election and majority quorum.

Scalable cluster size with dynamic node addition or removal.

Watch/notification mechanism for real‑time configuration changes.

RBAC‑based access control, authentication and transport encryption.

RESTful HTTP API and multi‑language client libraries for easy integration.

Common scenarios include service discovery, configuration management, distributed locking, leader election, cluster state storage (e.g., Kubernetes), metadata storage for big‑data pipelines, and general coordination in distributed systems.

Go Language Practice

Dependency

Add the client library: go get go.etcd.io/etcd/client/v3 The go.mod file will include:

go.etcd.io/etcd/api/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/v3 v3.5.14 // indirect

Setup

Initialize a global logger using zap:

var Logger *zap.SugaredLogger

func init() {
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "T",
        LevelKey:       "L",
        NameKey:        "log",
        CallerKey:      "C",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.CapitalLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.StringDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    }
    encoder := zapcore.NewConsoleEncoder(encoderConfig)
    atomicLevel := zap.NewAtomicLevel()
    atomicLevel.SetLevel(zap.InfoLevel)
    core := zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), atomicLevel)
    Logger = zap.New(core, zap.AddCaller(), zap.Development()).Sugar()
}

Server

Start a single‑node etcd instance (for demo) with:

brew install etcd
etcd

The default client port is 2379.

Client

Define a global client and a close helper:

const timeOut = 5 * time.Second
var cli *clientv3.Client

func init() {
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: timeOut,
    })
    if err != nil {
        panic("failed to connect to server")
    }
    cli = client
}

func close() {
    if err := cli.Close(); err != nil {
        Logger.Error("failed to close connection")
    }
}

Read/Write Test

A simple test writes a key, reads all keys, prints them, and deletes them:

func TestEtcd(t *testing.T) {
    defer close()
    ctx, cancel := context.WithTimeout(context.Background(), timeOut)
    _, _ = cli.Put(ctx, "key", "value")
    cancel()

    ctx, cancel = context.WithTimeout(context.Background(), timeOut)
    resp, _ := cli.Get(ctx, "", clientv3.WithPrefix())
    cancel()

    for _, kv := range resp.Kvs {
        Logger.Infof("%s: %s", kv.Key, kv.Value)
        cli.Delete(context.Background(), string(kv.Key))
    }
}

Sample output shows the key/value pair and successful deletion.

Distributed Lock

Using etcd’s concurrency package to acquire and release a lock:

func TestLock(t *testing.T) {
    defer close()
    session, _ := concurrency.NewSession(cli)
    defer session.Close()
    mutex := concurrency.NewMutex(session, "/funtester/")
    ctx := context.Background()
    if err := mutex.Lock(ctx); err != nil {
        Logger.Error("lock failed:", err)
        return
    }
    Logger.Info("lock acquired")
    if err := mutex.Unlock(ctx); err != nil {
        Logger.Error("unlock failed:", err)
        return
    }
    Logger.Info("unlock succeeded")
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

service discoveryGoConfiguration Managementdistributed-lockRaftetcddistributed key-value store
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.