least-connected load-balancing, per-host rate-limiting & load from config

This commit is contained in:
simon 2019-05-29 09:50:38 -04:00
parent 730616da98
commit 6f0637dc3d
4 changed files with 147 additions and 11 deletions

53
config.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"encoding/json"
"golang.org/x/time/rate"
"io/ioutil"
"os"
"time"
)
type HostConfig struct {
Every string `json:"every"`
Burst int `json:"burst"`
Headers map[string]string `json:"headers"`
}
type ProxyConfig struct {
Name string `json:"name"`
Url string `json:"url"`
}
var config struct {
Addr string `json:"addr"`
Hosts map[string]HostConfig `json:"hosts"`
Proxies []ProxyConfig `json:"proxies"`
}
func loadConfig() {
configFile, err := os.Open("config.json")
handleErr(err)
configBytes, err := ioutil.ReadAll(configFile)
handleErr(err)
err = json.Unmarshal(configBytes, &config)
handleErr(err)
}
func applyConfig(proxy *Proxy) {
for host, conf := range config.Hosts {
duration, err := time.ParseDuration(conf.Every)
handleErr(err)
proxy.Limiters.Store(host, rate.NewLimiter(rate.Every(duration), conf.Burst))
}
}
func handleErr(err error) {
if err != nil {
panic(err)
}
}

25
config.json Normal file
View File

@ -0,0 +1,25 @@
{
"addr": "localhost:5050",
"proxies": [
{
"name": "p0",
"url": ""
},
{
"name": "p1",
"url": "http://localhost:3128"
}
],
"hosts": {
"*": {
"every": "10s",
"burst": 1,
"headers": {}
},
"reddit.com": {
"every": "100s",
"burst": 1,
"headers": {}
}
}
}

66
main.go
View File

@ -7,6 +7,7 @@ import (
"golang.org/x/time/rate" "golang.org/x/time/rate"
"net/http" "net/http"
"net/url" "net/url"
"sort"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -18,10 +19,25 @@ type Balancer struct {
} }
type Proxy struct { type Proxy struct {
Name string Name string
Url *url.URL Url *url.URL
Limiters sync.Map Limiters sync.Map
HttpClient *http.Client HttpClient *http.Client
Connections int
}
type ByConnectionCount []*Proxy
func (a ByConnectionCount) Len() int {
return len(a)
}
func (a ByConnectionCount) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a ByConnectionCount) Less(i, j int) bool {
return a[i].Connections < a[j].Connections
} }
func LogRequestMiddleware(h goproxy.FuncReqHandler) goproxy.ReqHandler { func LogRequestMiddleware(h goproxy.FuncReqHandler) goproxy.ReqHandler {
@ -41,8 +57,8 @@ func (p *Proxy) getLimiter(host string) *rate.Limiter {
limiter, ok := p.Limiters.Load(host) limiter, ok := p.Limiters.Load(host)
if !ok { if !ok {
every, _ := time.ParseDuration("100ms") every, _ := time.ParseDuration("1ms")
limiter = rate.NewLimiter(rate.Every(every), 0) limiter = rate.NewLimiter(rate.Every(every), 1)
p.Limiters.Store(host, limiter) p.Limiters.Store(host, limiter)
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
@ -65,6 +81,8 @@ func (b *Balancer) chooseProxy(host string) *Proxy {
_ = simplifyHost(host) _ = simplifyHost(host)
sort.Sort(ByConnectionCount(b.proxies))
return b.proxies[0] return b.proxies[0]
} }
@ -79,14 +97,31 @@ func New() *Balancer {
balancer.server.OnRequest().Do(LogRequestMiddleware( balancer.server.OnRequest().Do(LogRequestMiddleware(
func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
p := balancer.chooseProxy(r.Host) sHost := simplifyHost(r.Host)
p := balancer.chooseProxy(sHost)
p.Connections += 1
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"proxy": p.Name, "proxy": p.Name,
"connexions": p.Connections,
}).Trace("Routing request") }).Trace("Routing request")
limiter := p.getLimiter(sHost)
reservation := limiter.Reserve()
if !reservation.OK() {
logrus.Warn("Could not reserve")
}
delay := reservation.Delay()
if delay > 0 {
logrus.WithFields(logrus.Fields{
"time": delay,
}).Trace("Sleeping")
time.Sleep(delay)
}
proxyReq := preprocessRequest(cloneRequest(r)) proxyReq := preprocessRequest(cloneRequest(r))
resp, err := p.HttpClient.Do(proxyReq) resp, err := p.HttpClient.Do(proxyReq)
p.Connections -= 1
//TODO: handle err //TODO: handle err
if err != nil { if err != nil {
@ -166,12 +201,21 @@ func NewProxy(name, stringUrl string) (*Proxy, error) {
func main() { func main() {
logrus.SetLevel(logrus.TraceLevel) logrus.SetLevel(logrus.TraceLevel)
loadConfig()
balancer := New() balancer := New()
p0, _ := NewProxy("p0", "http://localhost:3128") for _, proxyConf := range config.Proxies {
//p0, _ := NewProxy("p0", "") proxy, err := NewProxy(proxyConf.Name, proxyConf.Url)
handleErr(err)
balancer.proxies = append(balancer.proxies, proxy)
balancer.proxies = []*Proxy{p0} applyConfig(proxy)
logrus.WithFields(logrus.Fields{
"name": proxy.Name,
"url": proxy.Url,
}).Info("Proxy")
}
balancer.Run() balancer.Run()
} }

14
web.py Normal file
View File

@ -0,0 +1,14 @@
from flask import Flask
import time
app = Flask(__name__)
@app.route("/")
def hello():
time.sleep(3)
return "Hello World!"
if __name__ == "__main__":
app.run(port=9999)