├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── go.mod ├── round_robin.go └── round_robin_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.11.x" 4 | - master 5 | env: 6 | - GO111MODULE=on 7 | install: true 8 | script: 9 | - go test -v ./ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # round-robin 2 | round-robin is balancing algorithm written in golang 3 | 4 | ## Installation 5 | 6 | ```shell 7 | go get github.com/hlts2/round-robin 8 | ``` 9 | 10 | ## Example 11 | 12 | ```go 13 | rr, _ := roundrobin.New( 14 | &url.URL{Host: "192.168.33.10"}, 15 | &url.URL{Host: "192.168.33.11"}, 16 | &url.URL{Host: "192.168.33.12"}, 17 | &url.URL{Host: "192.168.33.13"}, 18 | ) 19 | 20 | rr.Next() // {Host: "192.168.33.10"} 21 | rr.Next() // {Host: "192.168.33.11"} 22 | rr.Next() // {Host: "192.168.33.12"} 23 | rr.Next() // {Host: "192.168.33.13"} 24 | rr.Next() // {Host: "192.168.33.10"} 25 | rr.Next() // {Host: "192.168.33.11"} 26 | ``` 27 | ## Author 28 | [hlts2](https://github.com/hlts2) 29 | 30 | ## LICENSE 31 | round-robin released under MIT license, refer [LICENSE](https://github.com/hlts2/round-robin/blob/master/LICENSE) file. 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hlts2/round-robin 2 | 3 | go 1.21.0 4 | -------------------------------------------------------------------------------- /round_robin.go: -------------------------------------------------------------------------------- 1 | package roundrobin 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | "sync/atomic" 7 | ) 8 | 9 | // ErrServersNotExists is the error that servers dose not exists 10 | var ErrServersNotExists = errors.New("servers dose not exist") 11 | 12 | // RoundRobin is an interface for representing round-robin balancing. 13 | type RoundRobin interface { 14 | Next() *url.URL 15 | } 16 | 17 | type roundrobin struct { 18 | urls []*url.URL 19 | next uint32 20 | } 21 | 22 | // New returns RoundRobin implementation(*roundrobin). 23 | func New(urls ...*url.URL) (RoundRobin, error) { 24 | if len(urls) == 0 { 25 | return nil, ErrServersNotExists 26 | } 27 | 28 | return &roundrobin{ 29 | urls: urls, 30 | }, nil 31 | } 32 | 33 | // Next returns next address 34 | func (r *roundrobin) Next() *url.URL { 35 | n := atomic.AddUint32(&r.next, 1) 36 | return r.urls[(int(n)-1)%len(r.urls)] 37 | } 38 | -------------------------------------------------------------------------------- /round_robin_test.go: -------------------------------------------------------------------------------- 1 | package roundrobin 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "reflect" 7 | "sync" 8 | "testing" 9 | ) 10 | 11 | func TestRoundRobin(t *testing.T) { 12 | tests := []struct { 13 | urls []*url.URL 14 | iserr bool 15 | expected []string 16 | want []*url.URL 17 | }{ 18 | { 19 | urls: []*url.URL{ 20 | {Host: "192.168.33.10"}, 21 | {Host: "192.168.33.11"}, 22 | {Host: "192.168.33.12"}, 23 | }, 24 | iserr: false, 25 | want: []*url.URL{ 26 | {Host: "192.168.33.10"}, 27 | {Host: "192.168.33.11"}, 28 | {Host: "192.168.33.12"}, 29 | {Host: "192.168.33.10"}, 30 | }, 31 | }, 32 | { 33 | urls: []*url.URL{}, 34 | iserr: true, 35 | want: []*url.URL{}, 36 | }, 37 | } 38 | 39 | for i, test := range tests { 40 | rr, err := New(test.urls...) 41 | 42 | if got, want := !(err == nil), test.iserr; got != want { 43 | t.Errorf("tests[%d] - RoundRobin iserr is wrong. want: %v, but got: %v", i, test.want, got) 44 | } 45 | 46 | gots := make([]*url.URL, 0, len(test.want)) 47 | for j := 0; j < len(test.want); j++ { 48 | gots = append(gots, rr.Next()) 49 | } 50 | 51 | if got, want := gots, test.want; !reflect.DeepEqual(got, want) { 52 | t.Errorf("tests[%d] - RoundRobin is wrong. want: %v, got: %v", i, want, got) 53 | } 54 | } 55 | } 56 | 57 | func BenchmarkRoundRobinSync(b *testing.B) { 58 | resources := []*url.URL{ 59 | {Host: "127.0.0.1"}, 60 | {Host: "127.0.0.2"}, 61 | {Host: "127.0.0.3"}, 62 | {Host: "127.0.0.4"}, 63 | {Host: "127.0.0.5"}, 64 | {Host: "127.0.0.6"}, 65 | {Host: "127.0.0.7"}, 66 | {Host: "127.0.0.8"}, 67 | {Host: "127.0.0.9"}, 68 | {Host: "127.0.0.10"}, 69 | } 70 | 71 | for i := 1; i < len(resources)+1; i++ { 72 | b.Run(fmt.Sprintf("RoundRobinSliceOfSize(%d)", i), func(b *testing.B) { 73 | rr, err := New(resources[:i]...) 74 | if err != nil { 75 | b.Fatal(err) 76 | } 77 | // Adding WaitGroup complexity as this helps in comparing Sync and Async RoundRobinAccess (see BenchmarkRoundRobinASync as well) 78 | wg := &sync.WaitGroup{} 79 | for i := 0; i < b.N; i++ { 80 | wg.Add(1) 81 | defer wg.Done() 82 | rr.Next() 83 | } 84 | }) 85 | } 86 | } 87 | 88 | func BenchmarkRoundRobinASync(b *testing.B) { 89 | resources := []*url.URL{ 90 | {Host: "127.0.0.1"}, 91 | {Host: "127.0.0.2"}, 92 | {Host: "127.0.0.3"}, 93 | {Host: "127.0.0.4"}, 94 | {Host: "127.0.0.5"}, 95 | {Host: "127.0.0.6"}, 96 | {Host: "127.0.0.7"}, 97 | {Host: "127.0.0.8"}, 98 | {Host: "127.0.0.9"}, 99 | {Host: "127.0.0.10"}, 100 | } 101 | 102 | for i := 1; i < len(resources)+1; i++ { 103 | b.Run(fmt.Sprintf("RoundRobinSliceOfSize(%d)", i), func(b *testing.B) { 104 | rr, err := New(resources[:i]...) 105 | if err != nil { 106 | b.Fatal(err) 107 | } 108 | wg := &sync.WaitGroup{} 109 | for i := 0; i < b.N; i++ { 110 | wg.Add(1) 111 | go func() { 112 | defer wg.Done() 113 | rr.Next() 114 | }() 115 | } 116 | wg.Wait() 117 | }) 118 | } 119 | } 120 | --------------------------------------------------------------------------------