Skip to content

Commit

Permalink
add optimistic mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ramondeklein committed Dec 5, 2024
1 parent 64f8e79 commit e7d6f6e
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 16 deletions.
20 changes: 17 additions & 3 deletions http-tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func InternalTrace(req *http.Request, resp *http.Response, reqTime, respTime tim
}

// Trace gets trace of http request
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request, endpoint string) TraceInfo {
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request, backend *Backend) TraceInfo {
// Setup a http request body recorder
reqHeaders := r.Header.Clone()
reqHeaders.Set("Host", r.Host)
Expand All @@ -248,6 +248,20 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ
rw.LogBody = logBody
f(rw, r)

if backend.optimistic {
// when running in optimistic mode the node will be taken out
// of the pool when it returns one of the following HTTP
// response status:
// - 502 Bad gateway (i.e. can't connect to remote)
// - 503 Service unavailable
// - 504 Gateway timeout
if rw.StatusCode == http.StatusBadGateway || rw.StatusCode == http.StatusServiceUnavailable || rw.StatusCode == http.StatusGatewayTimeout {
if backend.setOffline() {
go backend.healthCheck()
}
}
}

rq := traceRequestInfo{
Time: time.Now().UTC(),
Method: r.Method,
Expand All @@ -270,7 +284,7 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ

t.ReqInfo = rq
t.RespInfo = rs
t.NodeName = endpoint
t.NodeName = backend.endpoint
t.CallStats = traceCallStats{
Latency: rs.Time.Sub(rw.StartTime),
Rx: reqBodyRecorder.Size(),
Expand All @@ -286,7 +300,7 @@ func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Requ

// Log only the headers.
func httpTraceHdrs(f http.HandlerFunc, w http.ResponseWriter, r *http.Request, backend *Backend) {
trace := Trace(f, false, w, r, backend.endpoint)
trace := Trace(f, false, w, r, backend)
doTrace(trace, backend)
}

Expand Down
38 changes: 25 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func (l logMessage) String() string {

// Backend entity to which requests gets load balanced.
type Backend struct {
ctxt context.Context
siteNumber int
endpoint string
proxy *reverse.Proxy
Expand All @@ -165,6 +166,7 @@ type Backend struct {
healthCheckURL string
healthCheckDuration time.Duration
healthCheckTimeout time.Duration
optimistic bool
Stats *BackendStats
}

Expand All @@ -173,12 +175,12 @@ const (
online
)

func (b *Backend) setOffline() {
atomic.StoreInt32(&b.up, offline)
func (b *Backend) setOffline() bool {
return atomic.SwapInt32(&b.up, offline) != offline
}

func (b *Backend) setOnline() {
atomic.StoreInt32(&b.up, online)
func (b *Backend) setOnline() bool {
return atomic.SwapInt32(&b.up, offline) != online
}

// Online returns true if backend is up
Expand Down Expand Up @@ -312,18 +314,19 @@ func getHealthCheckURL(endpoint, healthCheckPath string, healthCheckPort int) (s
}

// healthCheck - background routine which checks if a backend is up or down.
func (b *Backend) healthCheck(ctxt context.Context) {
func (b *Backend) healthCheck() {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
timer := time.NewTimer(b.healthCheckDuration)
defer timer.Stop()
for {
select {
case <-ctxt.Done():
case <-b.ctxt.Done():
return
case <-timer.C:
err := b.doHealthCheck()
if err != nil {
if err := b.doHealthCheck(); err != nil {
console.Errorln(err)
} else if b.optimistic && b.Online() {
return
}
// Add random jitter to call
timer.Reset(b.healthCheckDuration + time.Duration(rng.Int63n(int64(b.healthCheckDuration))))
Expand Down Expand Up @@ -358,8 +361,7 @@ func (b *Backend) doHealthCheck() error {
logMsg(logMessage{Endpoint: b.endpoint, Status: "down", Error: err})
}
// observed an error, take the backend down.
b.setOffline()
if b.Stats.DowntimeStart.IsZero() {
if b.setOffline() {
b.Stats.DowntimeStart = time.Now().UTC()
}
} else {
Expand Down Expand Up @@ -438,6 +440,7 @@ type healthCheckOptions struct {
healthCheckPort int
healthCheckDuration time.Duration
healthCheckTimeout time.Duration
optimistic bool
}

func (m *multisite) renewSite(ctx *cli.Context, tlsMaxVersion uint16, opts healthCheckOptions) {
Expand Down Expand Up @@ -906,6 +909,8 @@ func configureSite(ctxt context.Context, ctx *cli.Context, siteNum int, siteStrs
endpoints = append(endpoints, strings.Replace(target.String(), hostName, ip, 1))
}
}

optimistic := ctx.GlobalBool("optimistic")
for _, endpoint := range endpoints {
endpoint = strings.TrimSuffix(endpoint, slashSeparator)
target, err := url.Parse(endpoint)
Expand Down Expand Up @@ -956,10 +961,10 @@ func configureSite(ctxt context.Context, ctx *cli.Context, siteNum int, siteStrs
if err != nil {
console.Fatalln(err)
}
backend := &Backend{siteNum, endpoint, proxy, &http.Client{
backend := &Backend{ctxt, siteNum, endpoint, proxy, &http.Client{
Transport: proxy.Transport,
}, 0, healthCheckURL, opts.healthCheckDuration, opts.healthCheckTimeout, &stats}
go backend.healthCheck(ctxt)
}, 0, healthCheckURL, opts.healthCheckDuration, opts.healthCheckTimeout, optimistic, &stats}
go backend.healthCheck()
proxy.ErrorHandler = backend.ErrorHandler
backends = append(backends, backend)
connStats = append(connStats, newConnStats(endpoint))
Expand Down Expand Up @@ -993,6 +998,7 @@ func sidekickMain(ctx *cli.Context) {
})
log2.SetReportCaller(true)

optimistic := ctx.GlobalBool("optimistic")
healthCheckPath := ctx.GlobalString("health-path")
healthReadCheckPath := ctx.GlobalString("read-health-path")
healthCheckPort := ctx.GlobalInt("health-port")
Expand Down Expand Up @@ -1082,6 +1088,7 @@ func sidekickMain(ctx *cli.Context) {
healthCheckPort,
healthCheckDuration,
healthCheckTimeout,
optimistic,
})
m.displayUI(!globalConsoleDisplay)

Expand Down Expand Up @@ -1179,6 +1186,7 @@ func sidekickMain(ctx *cli.Context) {
healthCheckPort,
healthCheckDuration,
healthCheckTimeout,
optimistic,
})
default:
console.Infof("caught signal '%s'\n", signal)
Expand Down Expand Up @@ -1240,6 +1248,10 @@ func main() {
Name: "rr-dns-mode",
Usage: "enable round-robin DNS mode",
},
cli.BoolFlag{
Name: "optimistic",
Usage: "only perform health requests when nodes are down",
},
cli.StringFlag{
Name: "auto-tls-host",
Usage: "enable auto TLS mode for the specified host",
Expand Down

0 comments on commit e7d6f6e

Please sign in to comment.