• Stars
    star
    206
  • Rank 190,504 (Top 4 %)
  • Language
    Ruby
  • Created over 13 years ago
  • Updated over 13 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Zero-config reverse proxies: let's get there!

Zero-config reverse proxies: let's get there!

All reverse proxy servers have a common pain point: they have to know which backends requests can be routed to. Need to bring up a new appserver behind Nginx, HAProxy, or equivalent? Update your config, then HUP or reboot the server - painful. Why can't we decouple this relationship once and for all? Nginx, HAProxy and similar tools are all great, but what is missing is a simple protocol that would allow us to decouple these frontends from explicitly specifying each and every routing rule for every backend. Specifically, what we talking about?

upstream backend {
  server 10.0.0.20:8000;
  server 10.0.0.20:8001;
  server 10.0.0.20:8002;
}

server {
  listen 0.0.0.0:80;
  location / {
    proxy_pass https://backend;
  }

Looks familiar? We specified three backend app servers in this Nginx config. How do you add more? Update the config - ugh. Ideally, we should be able to bring up additional backends without modifying the frontend servers and requests "should just flow" to the new server - wouldn't that be nice? Below is an (admittedly somewhat frankenstein) experiment to allow us to do exactly that.

Architecture / Assumptions

Ultimately this functionality should be nothing more than a module in Nginx, Apache, or equivalent, but for the sake of a prototype, this is built via/with an async Goliath app server which parses the HTTP protocol and feeds us incoming data.

Converting HTTP to a stream oriented protocol: HTTP is not built for stream multiplexing, but protocols like SPDY attempt to solve this by introducing an explicit concept of streams and stream IDs into each packet. Hence, Goliath parses the HTTP request and converts the incoming request into SPDY protocol (Control + Data packets) - in other words, router.rb is an HTTP -> SPDY proxy.

Decoupling frontends from backends: Usually a router needs to be aware of all the backends before it can decide where to route the request (i.e. Nginx example above and exactly what we want to avoid). Instead, our router does something smarter: it uses ZeroMQ XREP/XREQ sockets to break this dependency. Specifically, the router binds an XREQ socket on port 8000 when it first comes up. The router accepts HTTP requests and pushes SPDY packets to port 8000 - the router knows nothing about where or how many backends there are.

Next, we have hello.rb which is a simple example of a worker process, in this case written in Ruby, but it could be any language or runtime. What does it do? It connects to port 8000 (via an XREP socket) when it comes up and waits to receive 0MQ messages, which are actually carrying SPDY frames - 0MQ + SPDY are a great match here, since both are message and stream oriented.

That's it, and the best part is.. Start the router, and then start as many workers as you want, or shut them down at will. 0MQ will do all the work for connection setup and teardown. Our router knows nothing about how many workers there are, and our worker knows nothing about how many frontends there are. Now all we need is this as an Nginx module! A quick visual representation of what is happening here:

arch

(If you are not familiar with 0MQ: XREQ socket automatically load-balances incoming requests (round-robin) to all the XREP workers. As implemented the example code will buffer the incoming HTTP request before it is dispatched to the worker but will/can stream the response back from the worker without waiting for complete response).

Summary

Server/Router:

  • Incoming HTTP requests are parsed via an async Goliath web-server handler (router.rb)
  • The router converts incoming HTTP request into SPDY protocol
    • Each HTTP request is assigned a unique stream-id (as per SPDY spec)
  • The router binds an XREQ 0MQ socket on port 8000 and forwards all SPDY packets there

Workers:

  • Any number of workers connect (via XREP) to port 8000
  • Worker accepts an HTTP request (delivered in SPDY format) and generates a response
  • Worker sends an unbuffered stream of response SPDY packets back to the router
  • Router processes the SPDY response and emits an async HTTP response back to the client

Example

$> bundle install

$> ruby hello.rb worker-1
$> ruby hello.rb worker-2

$> ruby router.rb -sv -p 9000
$>
$> curl http://localhost:9000/
   Hello from worker-1
$> curl http://localhost:9000/
   Hello from worker-2

What happened here? First, notice that we started the workers before the router was up! The order doesn't matter (thanks to 0MQ). Next, we started the router, which is listening to HTTP requests on port 9000, and forwarding SPDY requests to port 8000. Next we dispatch some queries, and ZMQ does its job and load balances the requests between the workers.

Now kill one of the workers, and send a new request.. ZMQ does all the cleanup work for us, and now all the requests are going to the single live server. Now start five more workers, and once again, we get transparent load balancing. No config reloads, no worries.

Related:

More Repositories

1

videospeed

HTML5 video speed controller (for Google Chrome)
JavaScript
3,812
star
2

ga-beacon

Google Analytics collector-as-a-service (using GA measurement protocol).
Go
3,536
star
3

gharchive.org

GH Archive is a project to record the public GitHub timeline, archive it, and make it easily accessible for further analysis.
Ruby
2,680
star
4

em-websocket

EventMachine based WebSocket server
Ruby
1,690
star
5

decisiontree

ID3-based implementation of the ML Decision Tree algorithm
Ruby
1,437
star
6

em-http-request

Asynchronous HTTP Client (EventMachine + Ruby)
Ruby
1,219
star
7

em-synchrony

Fiber aware EventMachine clients and convenience classes
Ruby
1,041
star
8

http-2

Pure Ruby implementation of HTTP/2 protocol
Ruby
894
star
9

bugspots

Implementation of simple bug prediction hotspot heuristic
Ruby
853
star
10

agent

Agent is an attempt at modelling Go-like concurrency, in Ruby
Ruby
729
star
11

vimgolf

Real Vim ninjas count every keystroke - do you?
Ruby
678
star
12

em-proxy

EventMachine Proxy DSL for writing high-performance transparent / intercepting proxies in Ruby
Ruby
662
star
13

node-spdyproxy

SPDY forwarding proxy - fast and secure
JavaScript
527
star
14

bloomfilter-rb

BloomFilter(s) in Ruby: Native counting filter + Redis counting/non-counting filters
C
472
star
15

async-rails

async Rails 3 stack demo
Ruby
465
star
16

istlsfastyet.com

Is TLS fast yet? Yes, yes it is.
HTML
422
star
17

hackernews-button

Embeddable Hacker News button + vote counter for your site
Go
415
star
18

http-client-hints

Ruby
402
star
19

spdy

SPDY is a protocol designed to reduce latency of web pages
Ruby
315
star
20

hpbn.co

High Performance Browser Networking (O'Reilly)
HTML
299
star
21

webp-detect

WebP with Accept negotiation
C++
242
star
22

autoperf

Ruby driver for httperf - automated load and performance testing
Ruby
179
star
23

PubSubHubbub

Asynchronous PubSubHubbub Ruby Client
Ruby
175
star
24

heroku-buildpack-dart

Heroku buildpack for Dart
Shell
170
star
25

rack-speedtracer

SpeedTracer middleware for server side debugging
Ruby
155
star
26

textquery

Evaluate any text against a collection of match rules
Ruby
145
star
27

tokyo-recipes

Lean & mean Tokyo Cabinet recipes (with Lua)
Lua
143
star
28

slowgrowl

Surface slow code paths in your Rails 3 app via Growl
Ruby
116
star
29

mneme

Mneme is an HTTP web-service for recording and identifying previously seen records - aka, duplicate detection.
Ruby
108
star
30

RRRDTool

Round robin database pattern via Redis sorted sets
Ruby
79
star
31

pregel

Single-node implementation of Google's Pregel framework for graph processing.
Ruby
74
star
32

gmetric

Pure Ruby interface for generating Ganglia gmetric packets
Ruby
69
star
33

rack-aggregate

Rack response-time statistics aggregator middleware
Ruby
67
star
34

em-jack

An Evented Beanstalk Client
Ruby
64
star
35

rb-pagerank

Code from RailsConf '09 pres: Building Mini Google in Ruby
Ruby
54
star
36

closure-sprockets

Sprockets processor for Google's Closure tools
Python
54
star
37

netinfo-monitor

Displays network quality as reported by Network Information API.
JavaScript
48
star
38

shopify-core-web-vitals

This embedded app provides a report on how real-world Google Chrome users experience the Shopify-powered storefront, as captured by the Chrome UX Report, and enables the site owner to benchmark their site against a custom list of competitors.
Ruby
48
star
39

libsnappy

Snappy, a fast compressor/decompressor (courtesy of Google)
Ruby
46
star
40

hydra5

Load-balanced (multi-headed) SOCKS5 proxy
Ruby
42
star
41

zdevice

ZDevice is a Ruby DSL for assembling ZeroMQ routing devices, with support for the ZDCF configuration syntax
Ruby
42
star
42

ruby2lolz

Ruby to Lolcode translator, kthnxbai.
Ruby
38
star
43

bmr-wordcount

Browser Map-Reduce: distributed word count example
Ruby
33
star
44

resource-hints

Moved to...
JavaScript
32
star
45

gitter

XML history generator for CodeSwarm
32
star
46

em-socksify

Transparent proxy support for any EventMachine protocol
Ruby
31
star
47

em-handlersocket

EventMachine HandlerSocket MySQL plugin for direct read/write of InnoDB tables
Ruby
29
star
48

canicrawl

Hosted robots.txt permissions verifier
Go
23
star
49

udacity-webperf

JavaScript
17
star
50

omnipipe

web pipes for your browser's omnibar!
Ruby
12
star
51

issue-tracker

W3C webperf issue tracker
JavaScript
11
star
52

contextual

runtime contextual HTML autoescaper
Ruby
10
star
53

presentations

Slides, notes, code examples from some of the bigger conferences & talks.
9
star
54

libgeohash

Ruby FFI wrapper for libgeohash
Ruby
7
star
55

performance-observer

JavaScript
7
star
56

ImageQuote

Convert text quotes to images
Ruby
7
star
57

resourcehints.info

HTML
2
star
58

igrigorik

1
star