Skip to content

Commit 9dfe571

Browse files
committed
initial commit
0 parents  commit 9dfe571

13 files changed

+2352
-0
lines changed

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Compiled Object files, Static and Dynamic libs (Shared Objects)
2+
*.o
3+
*.a
4+
*.so
5+
6+
# Folders
7+
_obj
8+
_test
9+
10+
# Architecture specific extensions/prefixes
11+
*.[568vq]
12+
[568vq].out
13+
14+
*.cgo1.go
15+
*.cgo2.c
16+
_cgo_defun.c
17+
_cgo_gotypes.go
18+
_cgo_export.*
19+
20+
_testmain.go
21+
22+
*.exe
23+
*.test

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright (c) 2014 Ewan Chou.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
Stadis - Stand-alone Distributed System
2+
=======================================
3+
The easiest way to learn, develop and test a distributed system.
4+
5+
Why should I use it?
6+
====================
7+
Testing distributed system is very expensive.
8+
9+
It takes a large amount of resources, takes a lot of time to deploy and config.
10+
11+
More importantly, as your application is running on multiple machine,
12+
it's nearly impossible to coordinate the network state with your application state.
13+
as a result, there will be lots of error handling code leave untested,
14+
which can cause serious problem once that actually happen.
15+
16+
With stadis, you can run a multi-data-center distributed system on a single machine.
17+
18+
You can change the topology and node state at any time by a single API call.
19+
Everything happens exactly the way you want.
20+
Every network error handling code can be easily covered.
21+
22+
Get started
23+
===========
24+
Require Go 1.2+ installed.
25+
26+
Stadis can be used as a library in Go application.
27+
28+
For applications written in other programming language, stadis can be used as a proxy server.
29+
30+
###Use stadis as a library in Go program.
31+
32+
Hello world example
33+
34+
package main
35+
36+
import (
37+
"fmt"
38+
"github.com/coocood/stadis"
39+
"io"
40+
"log"
41+
"net/http"
42+
"os"
43+
"time"
44+
)
45+
46+
func main() {
47+
//Start a api server.
48+
go http.ListenAndServe(stadis.Cli.ApiAddr, stadis.NewApiServer())
49+
//Setup the http client dialer.
50+
http.DefaultTransport.(*http.Transport).Dial = stadis.NewDialFunc("matter.metal.gold", 5*time.Second)
51+
52+
eagleListener, err := stadis.Listen("tcp", "localhost:8585", "animal.air.eagle")
53+
if err != nil {
54+
log.Fatal(err)
55+
}
56+
57+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
58+
w.Write([]byte("hello\n"))
59+
})
60+
61+
go http.Serve(eagleListener, nil)
62+
63+
time.Sleep(time.Millisecond)
64+
before := time.Now()
65+
resp, err := http.Get("http://localhost:8585/")
66+
if err != nil {
67+
log.Fatal(err)
68+
}
69+
io.Copy(os.Stdout, resp.Body)
70+
resp.Body.Close()
71+
//the latency should be a little more than 888ms which is define at stadis.DefaultConfig.
72+
fmt.Println("latency:", time.Now().Sub(before))
73+
}
74+
75+
###Run stadis as a API/proxy server.
76+
77+
Build and Run
78+
79+
go get github.com/coocood/stadis
80+
go build github.com/coocood/stadis/stadiserver
81+
./stadiserver
82+
83+
Now, the stadis API server is started at port `8989`.
84+
85+
Then start a trivial http server as the origin server on port `12345` by run
86+
87+
go run $GOROOT/src/pkg/net/http/triv.go
88+
89+
Then call the REST API to open a proxy
90+
91+
curl -X POST 'http://localhost:8989/proxy?clientName=matter.metal.gold&proxyName=animal.air.eagle&proxyPort=8586&originAddr=localhost:12345'
92+
93+
We can ask the API server for the dial state:
94+
95+
curl 'http://localhost:8989/dialState?clientName=matter.metal.gold&serverPort=8586'
96+
97+
You will get dial state like `{"Latency":444000000,"OK":true}`, the latency unit is nano second.
98+
Normally you should sleep for that amount of time in your program before actually dial the proxy server.
99+
If 'OK' is 'false' you should return error or throw an exception.
100+
101+
Then request the proxy server and record the time.
102+
103+
time curl -i 'http://localhost:8586/counter'
104+
105+
The real time should be a little more than 444ms, which is the roundtrip time from 'matter.metal.gold' to 'animal.air.eagle'.
106+
107+
Configuration
108+
===============
109+
Stadis server does not use any config file, it starts with a default configuration, and you can update it by REST API call.
110+
111+
Stadis API server maintains a virtual topology which has three level:'DataCenter', 'Rack' and 'Host'.
112+
All of them has a 'NodeState' of attributes 'Name', 'Latency', 'InternalDown' and 'ExternalDown' .
113+
The topology contains multiple 'DataCenter', which contains multiple 'Rack', which in turn contains
114+
multiple 'Host'.
115+
116+
We all know naming is hard, so I did the hard work for you.
117+
After spent many hours, I managed to come up with 39 node names.
118+
The topology has three data centers, each data center has three racks, each rack has three hosts.
119+
120+
{
121+
"DcDefault":{"Latency":100000000},
122+
"RackDefault":{"Latency":10000000},
123+
"HostDefault":{"Latency":1000000},
124+
"DataCenters":[
125+
{
126+
"Name":"animal",
127+
"Racks":[
128+
{
129+
"Name":"land",
130+
"Hosts":[{"Name":"tiger"},{"Name":"lion"},{"Name":"wolf"}]
131+
},
132+
{
133+
"Name":"sea",
134+
"Hosts":[{"Name":"shark"},{"Name":"whale"},{"Name":"cod"}]
135+
},
136+
{
137+
"Name":"air",
138+
"Hosts":[{"Name":"eagle"},{"Name":"crow"},{"Name":"owl"}]
139+
}
140+
]
141+
},
142+
{
143+
"Name":"plant",
144+
"Racks":[
145+
{
146+
"Name":"fruit",
147+
"Hosts":[{"Name":"apple"},{"Name":"pear"},{"Name":"grape"}]
148+
},
149+
{
150+
"Name":"crop",
151+
"Hosts":[{"Name":"corn"},{"Name":"rice"},{"Name":"wheat"}]
152+
},
153+
{
154+
"Name":"flower",
155+
"Hosts":[{"Name":"rose"},{"Name":"lily"},{"Name":"lotus"}]
156+
}
157+
]
158+
},
159+
{
160+
"Name":"matter",
161+
"Racks":[
162+
{
163+
"Name":"metal",
164+
"Hosts":[{"Name":"gold"},{"Name":"silver"},{"Name":"iron"}]
165+
},
166+
{
167+
"Name":"gem",
168+
"Hosts":[{"Name":"ruby"},{"Name":"ivory"},{"Name":"pearl"}]
169+
},
170+
{
171+
"Name":"liquid",
172+
"Hosts":[{"Name":"water"},{"Name":"oil"},{"Name":"wine"}]
173+
}
174+
]
175+
}
176+
]
177+
}
178+
179+
In default configuration, each data center has 100ms latency, each rack has 10ms latency, each host has 1ms latency.
180+
181+
So the latency from 'matter.metal.gold' to 'animal.air.eagle' should be "1ms+10ms+100ms+100ms+10ms+1ms = 222ms"
182+
183+
The round trip time should be 444ms, so the total time to create a connection
184+
and then make a http request from 'gold' to 'eagle' should be a little more than 888ms.
185+
186+
REST API
187+
================
188+
189+
- Update configuration with json payload like the default config shown above.
190+
191+
POST /config
192+
193+
194+
- Update node state with json body like `{"Latency":10000000,"InternalDown":false,"ExternalDown":true}`
195+
196+
POST /nodeState?name=%s
197+
198+
The 'name' parameter is the node name in the topology, e.g. "animal.air.eagle".
199+
200+
201+
- Get node state:
202+
203+
GET /nodeState?name=%s
204+
205+
206+
- Start a proxy:
207+
208+
POST /proxy?clientName=%s&proxyName=%s&proxyPort=%s&originAddr=%s
209+
210+
'clientName' defines where the client process is located in the topology.
211+
'proxyName' defines where the proxy is located in the topology.
212+
'proxyPort' will be opened and accepts incoming connection.
213+
'originAddr' is the origin server address for the proxy, it can be an address in another machine,
214+
eg. `192.168.1.100:12345`, so if you can not run the origin server on your localhost, you can proxy it.
215+
216+
217+
- Stop a proxy:
218+
219+
DELETE /proxy?proxyPort=%s
220+
221+
222+
- Get dial state from a client to server.
223+
224+
GET /dialState?clientName={clientName}&serverPort={serverPort}
225+
226+
The response is a json object like `{"Latency":10000000,"OK":true}`
227+
228+
229+
- Get the current connection state after that connection has been created.
230+
231+
GET /connState?clientPort={clientPort}&serverPort={serverPort}
232+
233+
Performance
234+
============
235+
Stadis adds an extra layer on top of tcp connection, the throughput is greatly decreased.
236+
On my laptop, a proxy connection with 1ms latency gets about 30MB/s throughput.
237+
I think it is sufficient for most of applications for testing purpose.
238+
Higher latency gets lower throughput which is pretty much the way raw connections work.
239+
240+
LICENSE
241+
========
242+
The MIT License

0 commit comments

Comments
 (0)