http-ringpop
is a small reverse-proxy that makes it easy to shard incoming requests across
multiple backends. You decide what sharding logic you need.
Initial issue - spread out incoming HTTP requests from different clients between different instances of application. Requests from one client should be handled on the same backend.
http-ringpop
works as a proxy (sidecar) in front of your HTTP backend instance- when it receives request - it decides what backend instance should handle this request (based on incoming IP)
- if request should be hanlded on local backend - it forwards request to it.
- if request should be hanlded on another instance - it forwards request to its ringpop sidecar.
Ringpop sidecar for HTTP backend
could be useful if you already have HTTP backend,
but you need to add sharding / caching / data aggregation.
Ringpop will take ownership of the scalability and availability.
Requests forwarding based on ringpop approach with gossip under the hood.
This application is based on Uber's Ringpop.
┌───────────┐
│ Client │
└───────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ HTTP Ringpop │◀───▶│ HTTP Ringpop │◀───▶│ HTTP Ringpop │
└────────────────┘ └────────────────┘ └────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ HTTP Backend │ │ HTTP Backend │ │ HTTP Backend │
│ shard 1 │ │ shard 2 │ │ shard 3 │
└────────────────┘ └────────────────┘ └────────────────┘
Build
go build -o simple-backend cmd/backend-example/main.go
go build -o ringpop cmd/ringpop/main.go
Run 3 HTTP backends:
./simple-backend --listen.http=:4000
./simple-backend --listen.http=:4001
./simple-backend --listen.http=:4002
Run ringpop on 3 nodes locally:
./ringpop --listen.http="127.0.0.1:3000" --backend.url="http://127.0.0.1:4000/" --listen.ringpop="127.0.0.1:5000" --listen.debug=":6000" --discovery.json.file=./etc/hosts.json
./ringpop --listen.http="127.0.0.1:3001" --backend.url="http://127.0.0.1:4001/" --listen.ringpop="127.0.0.1:5001" --listen.debug=":6001" --discovery.json.file=./etc/hosts.json
./ringpop --listen.http="127.0.0.1:3002" --backend.url="http://127.0.0.1:4002/" --listen.ringpop="127.0.0.1:5002" --listen.debug=":6002" --discovery.json.file=./etc/hosts.json
Test
curl http://localhost:3000/ -i
You will see something like that (request received by one instance but handled by another):
HTTP/1.1 200 OK
Content-Length: 50
Content-Type: text/plain; charset=utf-8
X-Ringpop-Handled-By: 127.0.0.1:5002
X-Ringpop-Received-By: 127.0.0.1:5000
You can find out examples in k8s directory.
If you're running ringpop over Kubernetes note following thing: each instance have to know on what real IP it's running (its critical). For example, when instances will be discovered from DNS records, it will be something like this: 10.27.27.42:5000 10.27.35.133:5000 Current IP could be detected correctly only in particular cases. So, if current IP will be detected automatically as 127.0.0.1 this node will try to join to itself.
Also, one important thing is that you should use ClusterIP: none
for service,
that will be used for DNS discovery, because in this case nslookup myawesomeservice
will return list of A-records for all service endpoints.
make test
make build
docker build -t http-ringpop:1.0.0 .
Pre-built image on Dockerhub: ozontech/http-ringpop:1.0.0
Flags:
--listen.http= ... hostPort to listen calls from incoming
http requests. By default ":3000".
--backend.url= ... URL of your http backend.
By default "http://127.0.0.1:4000/".
--listen.ringpop= ... hostPort to listen gossip requests inside
hashring. By default ":5000".
--listen.debug= ... hostPort to listen calls from incoming debug
http requests (metrics, etc.).
By default ":6000".
--log.level= ... Log level, by default - INFO (4).
--discovery.json.file= ... Discovery hosts from static file.
--discovery.dns.host= ... Discovery hosts from DNS by hostname.
--discovery.dns.port= ... Ringpop port that will be added to discovered
hosts from DNS.