Mastering Nginx Load Balancing: Round‑Robin & Weighted Algorithms with Go
This article explains Nginx’s basic round‑robin and weighted round‑robin load‑balancing algorithms, compares their characteristics, and provides complete Go implementations—including configuration snippets, core structs, and test code—to help developers integrate efficient request distribution into their backend services.
Nginx Load Balancing – Round Robin
Explanation of the basic round‑robin algorithm, its characteristics, and a simple Nginx configuration example.
upstream cluster {
server 192.168.0.14;
server 192.168.0.15;
}
location / {
proxy_set_header X-Real-IP $remote_addr; // return real IP
proxy_pass http://cluster;
}In round‑robin each request is sent to the next server in the list, providing an even distribution when servers have similar capacity.
Go Implementation of Round Robin
type RoundRobinBalance struct {
curIndex int
rss []string
}
func (r *RoundRobinBalance) Add(params ...string) error {
if len(params) == 0 {
return errors.New("params len at least 1")
}
addr := params[0]
r.rss = append(r.rss, addr)
return nil
}
func (r *RoundRobinBalance) Next() string {
if len(r.rss) == 0 {
return ""
}
lens := len(r.rss)
if r.curIndex >= lens {
r.curIndex = 0
}
curAdd := r.rss[r.curIndex]
r.curIndex = (r.curIndex + 1) % lens
return curAdd
}Test code demonstrates adding four backend addresses and printing the result of successive
Next()calls.
func main() {
rb := new(RoundRobinBalance)
rb.Add("127.0.0.1:80")
rb.Add("127.0.0.1:81")
rb.Add("127.0.0.1:82")
rb.Add("127.0.0.1:83")
fmt.Println(rb.Next())
fmt.Println(rb.Next())
// ...
}Nginx Load Balancing – Weighted Round Robin
Weighted round‑robin assigns a weight to each server, allowing servers with higher capacity to receive more requests.
http {
upstream cluster {
server 192.168.1.2 weight=5;
server 192.168.1.3 weight=3;
server 192.168.1.4 weight=1;
}
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://cluster;
}
}The algorithm maintains three variables for each node: Weight (configured weight), currentWeight (dynamic weight updated each request), and effectiveWeight (adjusted weight used for health checks).
Sum all effectiveWeight values to obtain totalWeight .
Increase each node’s currentWeight by its effectiveWeight and select the node with the highest currentWeight .
Subtract totalWeight from the selected node’s currentWeight .
A Go implementation mirrors this logic with
WeightRoundRobinBalanceand
WeightNodestructs.
type WeightNode struct {
weight int // configured weight
currentWeight int // dynamic weight
effectiveWeight int // starts as weight, adjusted on failures
addr string
}
type WeightRoundRobinBalance struct {
curIndex int
rss []*WeightNode
}
func (r *WeightRoundRobinBalance) Add(params ...string) error {
if len(params) != 2 {
return errors.New("params len need 2")
}
addr := params[0]
w, err := strconv.ParseInt(params[1], 10, 64)
if err != nil {
return err
}
node := &WeightNode{
weight: int(w),
effectiveWeight: int(w),
currentWeight: int(w),
addr: addr,
}
r.rss = append(r.rss, node)
return nil
}
func (r *WeightRoundRobinBalance) Next() string {
if len(r.rss) == 0 {
return ""
}
totalWeight := 0
var maxNode *WeightNode
for i, node := range r.rss {
totalWeight += node.effectiveWeight
node.currentWeight += node.effectiveWeight
if maxNode == nil || node.currentWeight > maxNode.currentWeight {
maxNode = node
r.curIndex = i
}
}
maxNode.currentWeight -= totalWeight
return maxNode.addr
}Running the test program shows the request sequence respecting the configured weights.
Open Source Linux
Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.