mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Initial commit.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.idea/*
|
||||
*.sublime*
|
||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
This is just a simple prototype. To get to production would need:
|
||||
|
||||
- Routing table in central storage (mongo or IronCache) so all routers can write to it and read to get updates.
|
||||
- Update routing table from central store every X minutes.
|
||||
- Remove failed routes and start new workers if it failed.
|
||||
- Ability to start new workers if none are running.
|
||||
- Ability to always keep a minimum number running at all times, like at least one (or not if on free account?).
|
||||
- Ability to start new workers based on some auto scaling scheme.
|
||||
- Authentication (same as always).
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
- start helloserver.go
|
||||
- start router.go
|
||||
- ruby worker.rb a couple times
|
||||
- ruby client.rb
|
||||
|
||||
What's going on?
|
||||
|
||||
- worker.rb connects to router and adds routes.
|
||||
- client.rb connects to router which checks the routing table, proxies the request to one of the destinations and returns the response.
|
||||
|
||||
The idea here is that IronWorker backend can tell the router that it started a process and to start routing requests. The endpoint should only be cached for 55 minutes or so.
|
||||
|
||||
168
balance.go
Normal file
168
balance.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const nRequester = 100
|
||||
const nWorker = 10
|
||||
|
||||
var roundRobin = flag.Bool("r", false, "use round-robin scheduling")
|
||||
|
||||
// Simulation of some work: just sleep for a while and report how long.
|
||||
func op() int {
|
||||
n := rand.Int63n(5)
|
||||
time.Sleep(time.Duration(n) * time.Second)
|
||||
return int(n)
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
fn func() int
|
||||
c chan int
|
||||
}
|
||||
|
||||
func requester(work chan Request) {
|
||||
c := make(chan int)
|
||||
for {
|
||||
time.Sleep(time.Duration(rand.Int63n(nWorker)) * time.Second)
|
||||
work <- Request{op, c}
|
||||
<-c
|
||||
}
|
||||
}
|
||||
|
||||
type Worker struct {
|
||||
i int
|
||||
requests chan Request
|
||||
pending int
|
||||
}
|
||||
|
||||
func (w *Worker) work(done chan *Worker) {
|
||||
for {
|
||||
req := <-w.requests
|
||||
req.c <- req.fn()
|
||||
done <- w
|
||||
}
|
||||
}
|
||||
|
||||
type Pool []*Worker
|
||||
|
||||
func (p Pool) Len() int { return len(p) }
|
||||
|
||||
func (p Pool) Less(i, j int) bool {
|
||||
return p[i].pending < p[j].pending
|
||||
}
|
||||
|
||||
func (p *Pool) Swap(i, j int) {
|
||||
a := *p
|
||||
a[i], a[j] = a[j], a[i]
|
||||
a[i].i = i
|
||||
a[j].i = j
|
||||
}
|
||||
|
||||
func (p *Pool) Push(x interface{}) {
|
||||
a := *p
|
||||
n := len(a)
|
||||
a = a[0 : n+1]
|
||||
w := x.(*Worker)
|
||||
a[n] = w
|
||||
w.i = n
|
||||
*p = a
|
||||
}
|
||||
|
||||
func (p *Pool) Pop() interface{} {
|
||||
a := *p
|
||||
*p = a[0 : len(a)-1]
|
||||
w := a[len(a)-1]
|
||||
w.i = -1 // for safety
|
||||
return w
|
||||
}
|
||||
|
||||
type Balancer struct {
|
||||
pool Pool
|
||||
done chan *Worker
|
||||
i int
|
||||
}
|
||||
|
||||
func NewBalancer() *Balancer {
|
||||
done := make(chan *Worker, nWorker)
|
||||
b := &Balancer{make(Pool, 0, nWorker), done, 0}
|
||||
for i := 0; i < nWorker; i++ {
|
||||
w := &Worker{requests: make(chan Request, nRequester)}
|
||||
heap.Push(&b.pool, w)
|
||||
go w.work(b.done)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Balancer) balance(work chan Request) {
|
||||
for {
|
||||
select {
|
||||
case req := <-work:
|
||||
b.dispatch(req)
|
||||
case w := <-b.done:
|
||||
b.completed(w)
|
||||
}
|
||||
b.print()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Balancer) print() {
|
||||
sum := 0
|
||||
sumsq := 0
|
||||
for _, w := range b.pool {
|
||||
fmt.Printf("%d ", w.pending)
|
||||
sum += w.pending
|
||||
sumsq += w.pending * w.pending
|
||||
}
|
||||
avg := float64(sum) / float64(len(b.pool))
|
||||
variance := float64(sumsq)/float64(len(b.pool)) - avg*avg
|
||||
fmt.Printf(" %.2f %.2f\n", avg, variance)
|
||||
}
|
||||
|
||||
func (b *Balancer) dispatch(req Request) {
|
||||
if *roundRobin {
|
||||
w := b.pool[b.i]
|
||||
w.requests <- req
|
||||
w.pending++
|
||||
b.i++
|
||||
if b.i >= len(b.pool) {
|
||||
b.i = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w := heap.Pop(&b.pool).(*Worker)
|
||||
w.requests <- req
|
||||
w.pending++
|
||||
// fmt.Printf("started %p; now %d\n", w, w.pending)
|
||||
heap.Push(&b.pool, w)
|
||||
}
|
||||
|
||||
func (b *Balancer) completed(w *Worker) {
|
||||
if *roundRobin {
|
||||
w.pending--
|
||||
return
|
||||
}
|
||||
|
||||
w.pending--
|
||||
// fmt.Printf("finished %p; now %d\n", w, w.pending)
|
||||
heap.Remove(&b.pool, w.i)
|
||||
heap.Push(&b.pool, w)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
work := make(chan Request)
|
||||
for i := 0; i < nRequester; i++ {
|
||||
go requester(work)
|
||||
}
|
||||
NewBalancer().balance(work)
|
||||
}
|
||||
8
client.rb
Normal file
8
client.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
require 'rest'
|
||||
|
||||
rest = Rest::Client.new
|
||||
rest.logger.level = Logger::DEBUG
|
||||
response = rest.get("http://localhost:8080/") # "http://www.github.com")
|
||||
|
||||
puts "body:"
|
||||
puts response.body
|
||||
22
goproxy1.go
Normal file
22
goproxy1.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/elazarl/goproxy"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
verbose := flag.Bool("v", true, "should every proxy request be logged to stdout")
|
||||
flag.Parse()
|
||||
proxy := goproxy.NewProxyHttpServer()
|
||||
proxy.Verbose = *verbose
|
||||
proxy.OnRequest().DoFunc(
|
||||
func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||
r.Header.Set("X-GoProxy", "yxorPoG-X")
|
||||
return r, nil
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8080", proxy))
|
||||
}
|
||||
21
helloserver.go
Normal file
21
helloserver.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", Hello)
|
||||
|
||||
http.Handle("/", r)
|
||||
log.Fatal(http.ListenAndServe(":8081", nil))
|
||||
}
|
||||
|
||||
func Hello(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintln(w, "Hello world!")
|
||||
}
|
||||
88
src/router/router.go
Normal file
88
src/router/router.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
|
||||
For keeping a minimum running, perhaps when doing a routing table update, if destination hosts are all
|
||||
expired or about to expire we start more.
|
||||
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var routingTable = map[string]Route{}
|
||||
|
||||
type Route struct {
|
||||
// TODO: Change destinations to a simple cache so it can expire entries after 55 minutes (the one we use in common?)
|
||||
Destinations []string
|
||||
}
|
||||
|
||||
// for adding new hosts
|
||||
type Route2 struct {
|
||||
Host string `json:"host"`
|
||||
Dest string `json:"dest"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// verbose := flag.Bool("v", true, "should every proxy request be logged to stdout")
|
||||
// flag.Parse()
|
||||
|
||||
r := mux.NewRouter()
|
||||
s := r.Headers("Iron-Router", "").Subrouter()
|
||||
s.HandleFunc("/", AddWorker)
|
||||
r.HandleFunc("/addworker", AddWorker)
|
||||
|
||||
r.HandleFunc("/", ProxyFunc)
|
||||
|
||||
http.Handle("/", r)
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
func ProxyFunc(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Println("HOST:", req.Host)
|
||||
host := strings.Split(req.Host, ":")[0]
|
||||
route := routingTable[host]
|
||||
// choose random dest
|
||||
if len(route.Destinations) == 0 {
|
||||
fmt.Fprintln(w, "No matching routes!")
|
||||
return
|
||||
}
|
||||
destUrls := route.Destinations[rand.Intn(len(route.Destinations))]
|
||||
// todo: should check if http:// already exists.
|
||||
destUrls = "http://" + destUrls
|
||||
destUrl, err := url.Parse(destUrls)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
proxy := httputil.NewSingleHostReverseProxy(destUrl)
|
||||
proxy.ServeHTTP(w, req)
|
||||
// todo: how to handle destination failures. I got this in log output when testing a bad endpoint:
|
||||
// 2012/12/26 23:22:08 http: proxy error: dial tcp 127.0.0.1:8082: connection refused
|
||||
}
|
||||
|
||||
func AddWorker(w http.ResponseWriter, req *http.Request) {
|
||||
log.Println("AddWorker called!")
|
||||
r2 := Route2{}
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
decoder.Decode(&r2)
|
||||
// todo: do we need to close body?
|
||||
fmt.Println("DECODED:", r2)
|
||||
|
||||
// todo: routing table should be in mongo (or IronCache?) so all routers can update/read from it.
|
||||
route := routingTable[r2.Host]
|
||||
fmt.Println("ROUTE:", route)
|
||||
route.Destinations = append(route.Destinations, r2.Dest)
|
||||
fmt.Println("ROUTE:", route)
|
||||
routingTable[r2.Host] = route
|
||||
fmt.Println("New routing table:", routingTable)
|
||||
fmt.Fprintln(w, "Worker added")
|
||||
}
|
||||
Reference in New Issue
Block a user