Designing Load Balancing and Keep‑Alive Strategies in Go

This article walks through implementing three load‑balancing algorithms (random, round‑robin, weighted round‑robin) in Go, explains the weighted selection math with step‑by‑step examples, and presents two service‑liveness solutions—heartbeat via Docker/Kafka and HTTP health checks—complete with code snippets and deployment commands.

Nullbody Notes
Nullbody Notes
Nullbody Notes
Designing Load Balancing and Keep‑Alive Strategies in Go

Through this project you can learn how to implement load‑balancing algorithms in Go and how a scheduler can maintain live nodes.

Load‑balancing algorithms: random, round‑robin, weighted round‑robin.

loadbalance

Load‑balancing algorithms: random algorithm, round‑robin algorithm, weighted round‑robin algorithm.

Random algorithm
// Random
type Rand struct {
    addrs []string
}

func (r *Rand) Add(param []string) error {
    if len(param) != 1 {
        return ErrParam
    }
    r.addrs = append(r.addrs, param[0])
    return nil
}

// Random seed, return one address each time
func (r *Rand) Get() (string, error) {
    if len(r.addrs) == 0 {
        return "", ErrNoAddr
    }
    rand.Seed(time.Now().UnixNano())
    idx := rand.Intn(len(r.addrs))
    return r.addrs[idx], nil
}
Round‑robin algorithm
type RoundRobin struct {
    addrs []string
    curIdx int
}

func (r *RoundRobin) Add(param []string) error {
    if len(param) != 1 {
        return ErrParam
    }
    r.addrs = append(r.addrs, param[0])
    return nil
}

func (r *RoundRobin) Get() (string, error) {
    if len(r.addrs) == 0 || r.curIdx >= len(r.addrs) {
        return "", ErrNoAddr
    }
    addr := r.addrs[r.curIdx]
    r.curIdx = (r.curIdx + 1) % len(r.addrs) // curIdx +1 each time
    return addr, nil
}
Weighted round‑robin algorithm

Implementation principle: each node has a weight; the higher the weight, the more often it is selected. The expected selection count ≈ (node weight / total weight) × total requests.

Example: weights weight[a=1,b=2,c=5]; initial curWeight[a=0,b=0,c=0]; sumWeight = 8.

First request: curWeight becomes [a=1,b=2,c=5]; select c; then c.curWeight -= sumWeight → [a=1,b=2,c=-3].

Second request: curWeight → [a=2,b=4,c=2]; select b; then b.curWeight -= sumWeight → [a=2,b=-4,c=2].

Third request: curWeight → [a=3,b=-2,c=7]; select c; then c.curWeight -= sumWeight → [a=3,b=-2,c=-1].

Fourth request: curWeight → [a=4,b=0,c=4]; a and c tie, prefer a.

type WeigthRoundRobin struct {
    weightAddrs []*weightAddr
}

type weightAddr struct {
    addr      string // address
    weight    int    // weight
    curWeight int    // used for calculation
}

func (w *WeigthRoundRobin) Add(param []string) error {
    if len(param) != 2 {
        return ErrParam
    }
    weight, err := strconv.Atoi(param[1])
    if err != nil {
        return err
    }
    w.weightAddrs = append(w.weightAddrs, &weightAddr{addr: param[0], weight: weight, curWeight: 0})
    return nil
}

func (w *WeigthRoundRobin) Get() (string, error) {
    if len(w.weightAddrs) == 0 {
        return "", ErrNoAddr
    }
    maxWeight := math.MinInt
    idx := 0
    sumWeight := 0
    for k, weightAddr := range w.weightAddrs {
        sumWeight += weightAddr.weight // total weight
        weightAddr.curWeight += weightAddr.weight // add weight
        if weightAddr.curWeight > maxWeight { // record max
            maxWeight = weightAddr.curWeight
            idx = k
        }
    }
    w.weightAddrs[idx].curWeight -= sumWeight // subtract total weight
    return w.weightAddrs[idx].addr, nil // return address with max weight
}

Effective Service Maintenance Scheme (Reference)

Scheme 1: Heartbeat

Prerequisite configuration:

1. Start Docker

docker-compose -f docker-compose-env.yml up -d zookeeper
docker-compose -f docker-compose-env.yml up -d kafka

2. Modify local hosts

vim /etc/hosts
# add line
127.0.0.1 kafka

3. Optional Kafka scripts (located in /opt/kafka/bin inside the container)

# Create topic
./kafka-topics.sh --create --zookeeper zookeeper:2181 --replication-factor 1 --partitions 3 --topic easy_topic
# List topics
./kafka-topics.sh --list --zookeeper zookeeper:2181
# Consume from beginning
./kafka-console-consumer.sh --bootstrap-server kafka:9092 --from-beginning --topic easy_topic
# Consumer group (from latest)
./kafka-console-consumer.sh --bootstrap-server kafka:9092 --consumer-property group.id=testGroup --topic easy_topic
# Produce messages
./kafka-console-producer.sh --broker-list kafka:9092 --topic easy_topic

The scheduler subscribes to Kafka messages and maintains a list of alive services , then distributes requests according to the load‑balancing strategy.

func main() {
    // Service sends heartbeat
    go heart.RunHeartBeat()
    // Scheduler receives heartbeat
    go heart.ListenHeartbeat()

    // Use load balancer to get address
    lb := loadbalance.LoadBalanceFactory(loadbalance.BalanceTypeRand)
    go func() {
        for {
            time.Sleep(5 * time.Second)
            for _, v := range heart.GetAddrList() {
                lb.Add([]string{v})
            }
            fmt.Println(lb.Get())
        }
    }()

    sigusr1 := make(chan os.Signal, 1)
    signal.Notify(sigusr1, syscall.SIGTERM)
    <-sigusr1
}

Scheme 2: Health Check

Maintain active nodes by sending HTTP requests to services.

func main() {
    health.AddAddr("https://www.sina.com.cn/", "https://www.baidu.com/", "http://www.aajklsdfjklsd")
    go health.HealthCheck()

    time.Sleep(50 * time.Second)

    alist := health.GetAliveAddrList()
    for i := 0; i < len(alist); i++ {
        fmt.Println(alist[i])
    }

    var block = make(chan bool)
    <-block
}
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.

DockerLoad BalancingGoKafkaheartbeatround robinhealth checkweighted round robin
Nullbody Notes
Written by

Nullbody Notes

Go backend development, learning open-source project source code together, focusing on simplicity and practicality.

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.