├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── circle.yml ├── examples ├── README.md ├── client │ └── main.go ├── pb │ ├── hello.pb.go │ └── hello.proto ├── server.go ├── server1 │ └── main.go └── server2 │ └── main.go ├── go.mod ├── go.sum ├── register.go ├── resolver.go └── watcher.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | .DS_Store 3 | .vscode 4 | .idea 5 | 6 | # Debug files 7 | *.dSYM/ 8 | *.su 9 | debug 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.o 24 | *.ko 25 | *.obj 26 | *.elf 27 | 28 | # Precompiled Headers 29 | *.gch 30 | *.pch 31 | 32 | # Libraries 33 | *.lib 34 | *.a 35 | *.la 36 | *.lo 37 | 38 | # Shared objects (inc. Windows DLLs) 39 | *.dll 40 | *.so 41 | *.so.* 42 | *.dylib 43 | 44 | # Executables 45 | *.exe 46 | *.out 47 | *.app 48 | *.i*86 49 | *.x86_64 50 | *.hex 51 | 52 | # Test binary, build with `go test -c` 53 | *.test 54 | 55 | # Output of the go coverage tool, specifically when used with LiteIDE 56 | *.out 57 | 58 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 59 | .glide/ 60 | 61 | vendor/* 62 | !vendor/vendor.json 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | # - 1.7.x 5 | # - 1.8.x 6 | # - 1.9.x 7 | # - 1.10.x 8 | - 1.11.x 9 | - 1.12.x 10 | - tip 11 | 12 | install: 13 | - export PATH=$PATH:$HOME/gopath/bin 14 | # - go get -u google.golang.org/grpc 15 | # - go get -u github.com/coreos/etcd 16 | # - go get -u github.com/coreos/etcd/clientv3 17 | - go get -t -d -v ./... 18 | 19 | after_success: 20 | - bash <(curl -s https://codecov.io/bash) 21 | 22 | # notifications: 23 | # email: 24 | # - .com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grpclb 2 | 3 | [![Build Status](https://travis-ci.org/go-vgo/grpclb.svg)](https://travis-ci.org/go-vgo/grpclb) 4 | [![CircleCI Status](https://circleci.com/gh/go-vgo/grpclb.svg?style=shield)](https://circleci.com/gh/go-vgo/grpclb) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/go-vgo/grpclb)](https://goreportcard.com/report/github.com/go-vgo/grpclb) 6 | [![GoDoc](https://godoc.org/github.com/go-vgo/grpclb?status.svg)](https://godoc.org/github.com/go-vgo/grpclb) 7 | [![Release](https://github-release-version.herokuapp.com/github/go-vgo/grpclb/release.svg?style=flat)](https://github.com/go-ego/ego/releases/latest) 8 | [![Join the chat at https://gitter.im/go-vgo/grpclb](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-vgo/gt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 9 | 10 | A gRPC load balancer and service discovery for go 11 | 12 | ## Installation/Update 13 | 14 | ``` 15 | go get -u github.com/go-vgo/grpclb 16 | ``` 17 | ## Usage: 18 | 19 | #### [Look at an example](/examples) 20 | 21 | ## License 22 | 23 | Grpclb is primarily distributed under the terms of the Apache License (Version 2.0). -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | # circle.yml # 2 | # machine: 3 | # go: 4 | # version: 1.9.1 5 | 6 | version: 2 7 | 8 | jobs: 9 | build: 10 | docker: 11 | - image: govgo/go:1.10.3 12 | working_directory: /gopath/src/github.com/go-vgo/grpclb 13 | steps: 14 | - checkout 15 | # specify any bash command here prefixed with `run: ` 16 | - run: go get -v -t -d ./... 17 | - run: go test -v ./... 18 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # gt 2 | 3 | ## Open source project: 4 | [grpc-go](https://github.com/grpc/grpc-go) 5 | 6 | [etcd](https://github.com/coreos/etcd) 7 | 8 | ## Reference: 9 | [http://www.grpc.io/docs/](http://www.grpc.io/docs/) 10 | 11 | [https://github.com/grpc/grpc/blob/master/doc/load-balancing.md](https://github.com/grpc/grpc/blob/master/doc/load-balancing.md) 12 | 13 | [https://github.com/coreos/etcd/tree/master/Documentation](https://github.com/coreos/etcd/tree/master/Documentation) -------------------------------------------------------------------------------- /examples/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "strconv" 8 | "time" 9 | 10 | "google.golang.org/grpc" 11 | 12 | grpclb "github.com/go-vgo/grpclb" 13 | pb "github.com/go-vgo/grpclb/examples/pb" 14 | ) 15 | 16 | var ( 17 | serv = flag.String("service", "hello_service", "service name") 18 | reg = flag.String("reg", "http://127.0.0.1:2379", "register etcd address") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | r := grpclb.NewResolver(*serv) 24 | b := grpc.RoundRobin(r) 25 | 26 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 27 | conn, err := grpc.DialContext(ctx, *reg, grpc.WithInsecure(), grpc.WithBalancer(b)) 28 | 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | ticker := time.NewTicker(1 * time.Second) 34 | for t := range ticker.C { 35 | client := pb.NewGreeterClient(conn) 36 | resp, err := client.SayHello(context.Background(), &pb.HelloRequest{ 37 | Name: "world " + strconv.Itoa(t.Second()), 38 | }) 39 | 40 | if err == nil { 41 | fmt.Printf("%v: Reply is %s\n", t, resp.Message) 42 | } 43 | } 44 | 45 | defer cancel() 46 | } 47 | -------------------------------------------------------------------------------- /examples/pb/hello.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: hello.proto 3 | 4 | /* 5 | Package hello is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | hello.proto 9 | 10 | It has these top-level messages: 11 | HelloRequest 12 | HelloReply 13 | */ 14 | package hello 15 | 16 | import proto "github.com/gogo/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | import context "golang.org/x/net/context" 21 | import grpc "google.golang.org/grpc" 22 | 23 | import io "io" 24 | 25 | // Reference imports to suppress errors if they are not otherwise used. 26 | var _ = proto.Marshal 27 | var _ = fmt.Errorf 28 | var _ = math.Inf 29 | 30 | // This is a compile-time assertion to ensure that this generated file 31 | // is compatible with the proto package it is being compiled against. 32 | // A compilation error at this line likely means your copy of the 33 | // proto package needs to be updated. 34 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 35 | 36 | // The request message containing the user's name. 37 | type HelloRequest struct { 38 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 39 | } 40 | 41 | func (m *HelloRequest) Reset() { *m = HelloRequest{} } 42 | func (m *HelloRequest) String() string { return proto.CompactTextString(m) } 43 | func (*HelloRequest) ProtoMessage() {} 44 | func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptorHello, []int{0} } 45 | 46 | func (m *HelloRequest) GetName() string { 47 | if m != nil { 48 | return m.Name 49 | } 50 | return "" 51 | } 52 | 53 | // The response message containing the greetings 54 | type HelloReply struct { 55 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 56 | } 57 | 58 | func (m *HelloReply) Reset() { *m = HelloReply{} } 59 | func (m *HelloReply) String() string { return proto.CompactTextString(m) } 60 | func (*HelloReply) ProtoMessage() {} 61 | func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptorHello, []int{1} } 62 | 63 | func (m *HelloReply) GetMessage() string { 64 | if m != nil { 65 | return m.Message 66 | } 67 | return "" 68 | } 69 | 70 | func init() { 71 | proto.RegisterType((*HelloRequest)(nil), "hello.HelloRequest") 72 | proto.RegisterType((*HelloReply)(nil), "hello.HelloReply") 73 | } 74 | 75 | // Reference imports to suppress errors if they are not otherwise used. 76 | var _ context.Context 77 | var _ grpc.ClientConn 78 | 79 | // This is a compile-time assertion to ensure that this generated file 80 | // is compatible with the grpc package it is being compiled against. 81 | const _ = grpc.SupportPackageIsVersion4 82 | 83 | // Client API for Greeter service 84 | 85 | type GreeterClient interface { 86 | // Sends a greeting 87 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) 88 | } 89 | 90 | type greeterClient struct { 91 | cc *grpc.ClientConn 92 | } 93 | 94 | func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { 95 | return &greeterClient{cc} 96 | } 97 | 98 | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { 99 | out := new(HelloReply) 100 | err := grpc.Invoke(ctx, "/hello.Greeter/SayHello", in, out, c.cc, opts...) 101 | if err != nil { 102 | return nil, err 103 | } 104 | return out, nil 105 | } 106 | 107 | // Server API for Greeter service 108 | 109 | type GreeterServer interface { 110 | // Sends a greeting 111 | SayHello(context.Context, *HelloRequest) (*HelloReply, error) 112 | } 113 | 114 | func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { 115 | s.RegisterService(&_Greeter_serviceDesc, srv) 116 | } 117 | 118 | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 119 | in := new(HelloRequest) 120 | if err := dec(in); err != nil { 121 | return nil, err 122 | } 123 | if interceptor == nil { 124 | return srv.(GreeterServer).SayHello(ctx, in) 125 | } 126 | info := &grpc.UnaryServerInfo{ 127 | Server: srv, 128 | FullMethod: "/hello.Greeter/SayHello", 129 | } 130 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 131 | return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) 132 | } 133 | return interceptor(ctx, in, info, handler) 134 | } 135 | 136 | var _Greeter_serviceDesc = grpc.ServiceDesc{ 137 | ServiceName: "hello.Greeter", 138 | HandlerType: (*GreeterServer)(nil), 139 | Methods: []grpc.MethodDesc{ 140 | { 141 | MethodName: "SayHello", 142 | Handler: _Greeter_SayHello_Handler, 143 | }, 144 | }, 145 | Streams: []grpc.StreamDesc{}, 146 | Metadata: "hello.proto", 147 | } 148 | 149 | func (m *HelloRequest) Marshal() (dAtA []byte, err error) { 150 | size := m.Size() 151 | dAtA = make([]byte, size) 152 | n, err := m.MarshalTo(dAtA) 153 | if err != nil { 154 | return nil, err 155 | } 156 | return dAtA[:n], nil 157 | } 158 | 159 | func (m *HelloRequest) MarshalTo(dAtA []byte) (int, error) { 160 | var i int 161 | _ = i 162 | var l int 163 | _ = l 164 | if len(m.Name) > 0 { 165 | dAtA[i] = 0xa 166 | i++ 167 | i = encodeVarintHello(dAtA, i, uint64(len(m.Name))) 168 | i += copy(dAtA[i:], m.Name) 169 | } 170 | return i, nil 171 | } 172 | 173 | func (m *HelloReply) Marshal() (dAtA []byte, err error) { 174 | size := m.Size() 175 | dAtA = make([]byte, size) 176 | n, err := m.MarshalTo(dAtA) 177 | if err != nil { 178 | return nil, err 179 | } 180 | return dAtA[:n], nil 181 | } 182 | 183 | func (m *HelloReply) MarshalTo(dAtA []byte) (int, error) { 184 | var i int 185 | _ = i 186 | var l int 187 | _ = l 188 | if len(m.Message) > 0 { 189 | dAtA[i] = 0xa 190 | i++ 191 | i = encodeVarintHello(dAtA, i, uint64(len(m.Message))) 192 | i += copy(dAtA[i:], m.Message) 193 | } 194 | return i, nil 195 | } 196 | 197 | func encodeVarintHello(dAtA []byte, offset int, v uint64) int { 198 | for v >= 1<<7 { 199 | dAtA[offset] = uint8(v&0x7f | 0x80) 200 | v >>= 7 201 | offset++ 202 | } 203 | dAtA[offset] = uint8(v) 204 | return offset + 1 205 | } 206 | func (m *HelloRequest) Size() (n int) { 207 | var l int 208 | _ = l 209 | l = len(m.Name) 210 | if l > 0 { 211 | n += 1 + l + sovHello(uint64(l)) 212 | } 213 | return n 214 | } 215 | 216 | func (m *HelloReply) Size() (n int) { 217 | var l int 218 | _ = l 219 | l = len(m.Message) 220 | if l > 0 { 221 | n += 1 + l + sovHello(uint64(l)) 222 | } 223 | return n 224 | } 225 | 226 | func sovHello(x uint64) (n int) { 227 | for { 228 | n++ 229 | x >>= 7 230 | if x == 0 { 231 | break 232 | } 233 | } 234 | return n 235 | } 236 | func sozHello(x uint64) (n int) { 237 | return sovHello(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 238 | } 239 | func (m *HelloRequest) Unmarshal(dAtA []byte) error { 240 | l := len(dAtA) 241 | iNdEx := 0 242 | for iNdEx < l { 243 | preIndex := iNdEx 244 | var wire uint64 245 | for shift := uint(0); ; shift += 7 { 246 | if shift >= 64 { 247 | return ErrIntOverflowHello 248 | } 249 | if iNdEx >= l { 250 | return io.ErrUnexpectedEOF 251 | } 252 | b := dAtA[iNdEx] 253 | iNdEx++ 254 | wire |= (uint64(b) & 0x7F) << shift 255 | if b < 0x80 { 256 | break 257 | } 258 | } 259 | fieldNum := int32(wire >> 3) 260 | wireType := int(wire & 0x7) 261 | if wireType == 4 { 262 | return fmt.Errorf("proto: HelloRequest: wiretype end group for non-group") 263 | } 264 | if fieldNum <= 0 { 265 | return fmt.Errorf("proto: HelloRequest: illegal tag %d (wire type %d)", fieldNum, wire) 266 | } 267 | switch fieldNum { 268 | case 1: 269 | if wireType != 2 { 270 | return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) 271 | } 272 | var stringLen uint64 273 | for shift := uint(0); ; shift += 7 { 274 | if shift >= 64 { 275 | return ErrIntOverflowHello 276 | } 277 | if iNdEx >= l { 278 | return io.ErrUnexpectedEOF 279 | } 280 | b := dAtA[iNdEx] 281 | iNdEx++ 282 | stringLen |= (uint64(b) & 0x7F) << shift 283 | if b < 0x80 { 284 | break 285 | } 286 | } 287 | intStringLen := int(stringLen) 288 | if intStringLen < 0 { 289 | return ErrInvalidLengthHello 290 | } 291 | postIndex := iNdEx + intStringLen 292 | if postIndex > l { 293 | return io.ErrUnexpectedEOF 294 | } 295 | m.Name = string(dAtA[iNdEx:postIndex]) 296 | iNdEx = postIndex 297 | default: 298 | iNdEx = preIndex 299 | skippy, err := skipHello(dAtA[iNdEx:]) 300 | if err != nil { 301 | return err 302 | } 303 | if skippy < 0 { 304 | return ErrInvalidLengthHello 305 | } 306 | if (iNdEx + skippy) > l { 307 | return io.ErrUnexpectedEOF 308 | } 309 | iNdEx += skippy 310 | } 311 | } 312 | 313 | if iNdEx > l { 314 | return io.ErrUnexpectedEOF 315 | } 316 | return nil 317 | } 318 | func (m *HelloReply) Unmarshal(dAtA []byte) error { 319 | l := len(dAtA) 320 | iNdEx := 0 321 | for iNdEx < l { 322 | preIndex := iNdEx 323 | var wire uint64 324 | for shift := uint(0); ; shift += 7 { 325 | if shift >= 64 { 326 | return ErrIntOverflowHello 327 | } 328 | if iNdEx >= l { 329 | return io.ErrUnexpectedEOF 330 | } 331 | b := dAtA[iNdEx] 332 | iNdEx++ 333 | wire |= (uint64(b) & 0x7F) << shift 334 | if b < 0x80 { 335 | break 336 | } 337 | } 338 | fieldNum := int32(wire >> 3) 339 | wireType := int(wire & 0x7) 340 | if wireType == 4 { 341 | return fmt.Errorf("proto: HelloReply: wiretype end group for non-group") 342 | } 343 | if fieldNum <= 0 { 344 | return fmt.Errorf("proto: HelloReply: illegal tag %d (wire type %d)", fieldNum, wire) 345 | } 346 | switch fieldNum { 347 | case 1: 348 | if wireType != 2 { 349 | return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) 350 | } 351 | var stringLen uint64 352 | for shift := uint(0); ; shift += 7 { 353 | if shift >= 64 { 354 | return ErrIntOverflowHello 355 | } 356 | if iNdEx >= l { 357 | return io.ErrUnexpectedEOF 358 | } 359 | b := dAtA[iNdEx] 360 | iNdEx++ 361 | stringLen |= (uint64(b) & 0x7F) << shift 362 | if b < 0x80 { 363 | break 364 | } 365 | } 366 | intStringLen := int(stringLen) 367 | if intStringLen < 0 { 368 | return ErrInvalidLengthHello 369 | } 370 | postIndex := iNdEx + intStringLen 371 | if postIndex > l { 372 | return io.ErrUnexpectedEOF 373 | } 374 | m.Message = string(dAtA[iNdEx:postIndex]) 375 | iNdEx = postIndex 376 | default: 377 | iNdEx = preIndex 378 | skippy, err := skipHello(dAtA[iNdEx:]) 379 | if err != nil { 380 | return err 381 | } 382 | if skippy < 0 { 383 | return ErrInvalidLengthHello 384 | } 385 | if (iNdEx + skippy) > l { 386 | return io.ErrUnexpectedEOF 387 | } 388 | iNdEx += skippy 389 | } 390 | } 391 | 392 | if iNdEx > l { 393 | return io.ErrUnexpectedEOF 394 | } 395 | return nil 396 | } 397 | func skipHello(dAtA []byte) (n int, err error) { 398 | l := len(dAtA) 399 | iNdEx := 0 400 | for iNdEx < l { 401 | var wire uint64 402 | for shift := uint(0); ; shift += 7 { 403 | if shift >= 64 { 404 | return 0, ErrIntOverflowHello 405 | } 406 | if iNdEx >= l { 407 | return 0, io.ErrUnexpectedEOF 408 | } 409 | b := dAtA[iNdEx] 410 | iNdEx++ 411 | wire |= (uint64(b) & 0x7F) << shift 412 | if b < 0x80 { 413 | break 414 | } 415 | } 416 | wireType := int(wire & 0x7) 417 | switch wireType { 418 | case 0: 419 | for shift := uint(0); ; shift += 7 { 420 | if shift >= 64 { 421 | return 0, ErrIntOverflowHello 422 | } 423 | if iNdEx >= l { 424 | return 0, io.ErrUnexpectedEOF 425 | } 426 | iNdEx++ 427 | if dAtA[iNdEx-1] < 0x80 { 428 | break 429 | } 430 | } 431 | return iNdEx, nil 432 | case 1: 433 | iNdEx += 8 434 | return iNdEx, nil 435 | case 2: 436 | var length int 437 | for shift := uint(0); ; shift += 7 { 438 | if shift >= 64 { 439 | return 0, ErrIntOverflowHello 440 | } 441 | if iNdEx >= l { 442 | return 0, io.ErrUnexpectedEOF 443 | } 444 | b := dAtA[iNdEx] 445 | iNdEx++ 446 | length |= (int(b) & 0x7F) << shift 447 | if b < 0x80 { 448 | break 449 | } 450 | } 451 | iNdEx += length 452 | if length < 0 { 453 | return 0, ErrInvalidLengthHello 454 | } 455 | return iNdEx, nil 456 | case 3: 457 | for { 458 | var innerWire uint64 459 | var start int = iNdEx 460 | for shift := uint(0); ; shift += 7 { 461 | if shift >= 64 { 462 | return 0, ErrIntOverflowHello 463 | } 464 | if iNdEx >= l { 465 | return 0, io.ErrUnexpectedEOF 466 | } 467 | b := dAtA[iNdEx] 468 | iNdEx++ 469 | innerWire |= (uint64(b) & 0x7F) << shift 470 | if b < 0x80 { 471 | break 472 | } 473 | } 474 | innerWireType := int(innerWire & 0x7) 475 | if innerWireType == 4 { 476 | break 477 | } 478 | next, err := skipHello(dAtA[start:]) 479 | if err != nil { 480 | return 0, err 481 | } 482 | iNdEx = start + next 483 | } 484 | return iNdEx, nil 485 | case 4: 486 | return iNdEx, nil 487 | case 5: 488 | iNdEx += 4 489 | return iNdEx, nil 490 | default: 491 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 492 | } 493 | } 494 | panic("unreachable") 495 | } 496 | 497 | var ( 498 | ErrInvalidLengthHello = fmt.Errorf("proto: negative length found during unmarshaling") 499 | ErrIntOverflowHello = fmt.Errorf("proto: integer overflow") 500 | ) 501 | 502 | func init() { proto.RegisterFile("hello.proto", fileDescriptorHello) } 503 | 504 | var fileDescriptorHello = []byte{ 505 | // 195 bytes of a gzipped FileDescriptorProto 506 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9, 507 | 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x94, 0xb8, 0x78, 0x3c, 508 | 0x40, 0x8c, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x21, 0x21, 0x2e, 0x96, 0xbc, 0xc4, 0xdc, 509 | 0x54, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x30, 0x5b, 0x49, 0x8d, 0x8b, 0x0b, 0xaa, 0xa6, 510 | 0x20, 0xa7, 0x52, 0x48, 0x82, 0x8b, 0x3d, 0x37, 0xb5, 0xb8, 0x38, 0x31, 0x1d, 0xa6, 0x08, 0xc6, 511 | 0x35, 0xb2, 0xe7, 0x62, 0x77, 0x2f, 0x4a, 0x4d, 0x2d, 0x49, 0x2d, 0x12, 0x32, 0xe1, 0xe2, 0x08, 512 | 0x4e, 0xac, 0x04, 0xeb, 0x12, 0x12, 0xd6, 0x83, 0xd8, 0x8b, 0x6c, 0x8f, 0x94, 0x20, 0xaa, 0x60, 513 | 0x41, 0x4e, 0xa5, 0x12, 0x83, 0x93, 0xed, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 514 | 0x78, 0x24, 0xc7, 0xc8, 0x25, 0x96, 0x9c, 0x9f, 0xab, 0x97, 0x9b, 0x99, 0x92, 0x9a, 0xa8, 0x97, 515 | 0x55, 0xa4, 0x57, 0x92, 0x5a, 0x5c, 0xa2, 0x97, 0x5e, 0x54, 0x90, 0xec, 0xc4, 0x0f, 0xd6, 0x13, 516 | 0x9e, 0x5f, 0x94, 0x93, 0x12, 0x00, 0xf2, 0x4a, 0x00, 0xe3, 0x22, 0x26, 0x66, 0x0f, 0x9f, 0xf0, 517 | 0x24, 0x36, 0xb0, 0xcf, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x0d, 0x80, 0xce, 0x21, 0xe8, 518 | 0x00, 0x00, 0x00, 519 | } 520 | -------------------------------------------------------------------------------- /examples/pb/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "com.midea.jr.test.grpc"; 5 | option java_outer_classname = "HelloWorldProto"; 6 | option objc_class_prefix = "HLW"; 7 | 8 | package hello; 9 | 10 | // The greeting service definition. 11 | service Greeter { 12 | // Sends a greeting 13 | rpc SayHello (HelloRequest) returns (HelloReply) {} 14 | } 15 | 16 | // The request message containing the user's name. 17 | message HelloRequest { 18 | string name = 1; 19 | } 20 | 21 | // The response message containing the greetings 22 | message HelloReply { 23 | string message = 1; 24 | } -------------------------------------------------------------------------------- /examples/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-vgo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // https://github.com/go-vgo/gt/blob/master/LICENSE 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | package main 12 | 13 | import ( 14 | // "context" 15 | "flag" 16 | "fmt" 17 | "log" 18 | "net" 19 | "os" 20 | "os/signal" 21 | "syscall" 22 | "time" 23 | 24 | "golang.org/x/net/context" 25 | "google.golang.org/grpc" 26 | 27 | grpclb "github.com/go-vgo/grpclb" 28 | pb "github.com/go-vgo/grpclb/examples/pb" 29 | ) 30 | 31 | var ( 32 | serv = flag.String("service", "hello_service", "service name") 33 | port = flag.Int("port", 50001, "listening port") 34 | reg = flag.String("reg", "http://127.0.0.1:2379", "register etcd address") 35 | ) 36 | 37 | func main() { 38 | flag.Parse() 39 | 40 | lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port)) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | err = grpclb.Register(*serv, "127.0.0.1", *port, *reg, time.Second*10, 15) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | ch := make(chan os.Signal, 1) 51 | signal.Notify(ch, syscall.SIGTERM, 52 | syscall.SIGINT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGQUIT) 53 | go func() { 54 | s := <-ch 55 | log.Printf("receive signal '%v'", s) 56 | grpclb.UnRegister() 57 | os.Exit(1) 58 | }() 59 | 60 | log.Printf("starting hello service at %d", *port) 61 | s := grpc.NewServer() 62 | pb.RegisterGreeterServer(s, &server{}) 63 | s.Serve(lis) 64 | } 65 | 66 | // server is used to implement helloworld.GreeterServer. 67 | type server struct{} 68 | 69 | // SayHello implements helloworld.GreeterServer 70 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) ( 71 | *pb.HelloReply, error) { 72 | fmt.Printf("%v: Receive is %s\n", time.Now(), in.Name) 73 | return &pb.HelloReply{Message: "Hello " + in.Name}, nil 74 | } 75 | -------------------------------------------------------------------------------- /examples/server1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "golang.org/x/net/context" 15 | "google.golang.org/grpc" 16 | 17 | grpclb "github.com/go-vgo/grpclb" 18 | pb "github.com/go-vgo/grpclb/examples/pb" 19 | ) 20 | 21 | var ( 22 | serv = flag.String("service", "hello_service", "service name") 23 | port = flag.Int("port", 50002, "listening port") 24 | reg = flag.String("reg", "http://127.0.0.1:2379", "register etcd address") 25 | ) 26 | 27 | func main() { 28 | flag.Parse() 29 | 30 | lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port)) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | err = grpclb.Register(*serv, "127.0.0.1", *port, *reg, time.Second*10, 15) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | ch := make(chan os.Signal, 1) 41 | signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, 42 | syscall.SIGKILL, syscall.SIGHUP, syscall.SIGQUIT) 43 | go func() { 44 | s := <-ch 45 | log.Printf("receive signal '%v'", s) 46 | grpclb.UnRegister() 47 | os.Exit(1) 48 | }() 49 | 50 | log.Printf("starting hello service at %d", *port) 51 | s := grpc.NewServer() 52 | pb.RegisterGreeterServer(s, &server{}) 53 | s.Serve(lis) 54 | } 55 | 56 | // server is used to implement helloworld.GreeterServer. 57 | type server struct{} 58 | 59 | // SayHello implements helloworld.GreeterServer 60 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) ( 61 | *pb.HelloReply, error) { 62 | fmt.Printf("%v: Receive is %s\n", time.Now(), in.Name) 63 | return &pb.HelloReply{Message: "Hello " + in.Name}, nil 64 | } 65 | -------------------------------------------------------------------------------- /examples/server2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "golang.org/x/net/context" 15 | "google.golang.org/grpc" 16 | 17 | grpclb "github.com/go-vgo/grpclb" 18 | pb "github.com/go-vgo/grpclb/examples/pb" 19 | ) 20 | 21 | var ( 22 | serv = flag.String("service", "hello_service", "service name") 23 | port = flag.Int("port", 50003, "listening port") 24 | reg = flag.String("reg", "http://127.0.0.1:2379", "register etcd address") 25 | ) 26 | 27 | func main() { 28 | flag.Parse() 29 | 30 | lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port)) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | err = grpclb.Register(*serv, "127.0.0.1", *port, *reg, time.Second*10, 15) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | ch := make(chan os.Signal, 1) 41 | signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, 42 | syscall.SIGKILL, syscall.SIGHUP, syscall.SIGQUIT) 43 | go func() { 44 | s := <-ch 45 | log.Printf("receive signal '%v'", s) 46 | grpclb.UnRegister() 47 | os.Exit(1) 48 | }() 49 | 50 | log.Printf("starting hello service at %d", *port) 51 | s := grpc.NewServer() 52 | pb.RegisterGreeterServer(s, &server{}) 53 | s.Serve(lis) 54 | } 55 | 56 | // server is used to implement helloworld.GreeterServer. 57 | type server struct{} 58 | 59 | // SayHello implements helloworld.GreeterServer 60 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) ( 61 | *pb.HelloReply, error) { 62 | fmt.Printf("%v: Receive is %s\n", time.Now(), in.Name) 63 | return &pb.HelloReply{Message: "Hello " + in.Name}, nil 64 | } 65 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-vgo/grpclb 2 | 3 | require ( 4 | // github.com/coreos/etcd v3.3.10+incompatible 5 | github.com/gogo/protobuf v1.1.1 6 | github.com/golang/protobuf v1.2.0 7 | go.etcd.io/etcd v3.3.10+incompatible 8 | google.golang.org/grpc v1.16.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 3 | github.com/coreos/etcd v3.3.3+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 4 | github.com/coreos/etcd v3.3.10+incompatible h1:KjVWqrZ5U0wa3CxY2AxlH6/UcB+PK2td1DcsYhA+HRs= 5 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 6 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 7 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 8 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 10 | github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 11 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 12 | github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 16 | go.etcd.io/etcd v3.3.10+incompatible h1:9251umuNs2WD8vm8D3Tu9PFbzoT8evUluB3VgSRKWzI= 17 | go.etcd.io/etcd v3.3.10+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= 18 | golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 19 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= 20 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 21 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 22 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 23 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 24 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 25 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 26 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 27 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 28 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= 29 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 30 | google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= 31 | google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 32 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 33 | -------------------------------------------------------------------------------- /register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-vgo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // https://github.com/go-vgo/gt/blob/master/LICENSE 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | package grpclb 12 | 13 | import ( 14 | "context" 15 | "fmt" 16 | "log" 17 | "strings" 18 | "time" 19 | 20 | etcd3 "go.etcd.io/etcd/clientv3" 21 | "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes" 22 | ) 23 | 24 | var ( 25 | // Prefix should start and end with no slash 26 | Prefix = "etcd3_naming" 27 | client etcd3.Client 28 | serviceKey string 29 | 30 | stopSignal = make(chan bool, 1) 31 | ) 32 | 33 | // Opt registry grpc option 34 | type Opt struct { 35 | Name string 36 | Host string 37 | Port int 38 | Target string 39 | Interval time.Duration 40 | Ttl int 41 | } 42 | 43 | // Registry gRPC naming and discovery 44 | func Registry(opt Opt, args ...int) error { 45 | serviceValue := fmt.Sprintf("%s:%d", opt.Host, opt.Port) 46 | serviceKey = fmt.Sprintf("/%s/%s/%s", Prefix, opt.Name, serviceValue) 47 | 48 | // get endpoints for register dial address 49 | var ( 50 | err error 51 | client *etcd3.Client 52 | ) 53 | 54 | if len(args) > 0 { 55 | client, err = etcd3.New(etcd3.Config{ 56 | Endpoints: strings.Split(opt.Target, ","), 57 | }) 58 | } else { 59 | client, err = etcd3.NewFromURL(opt.Target) 60 | } 61 | 62 | if err != nil { 63 | return fmt.Errorf("grpclb: create etcd3 client failed: %v", err) 64 | } 65 | 66 | ctx, cancel := context.WithCancel(context.Background()) 67 | insertFunc := func() error { 68 | // minimum lease TTL is ttl-second 69 | resp, _ := client.Grant(context.TODO(), int64(opt.Ttl)) 70 | // should get first, if not exist, set it 71 | // context.Background() 72 | _, err := client.Get(ctx, serviceKey) 73 | if err != nil { 74 | if err == rpctypes.ErrKeyNotFound { 75 | // ctx context.TODO() 76 | _, err := client.Put(ctx, serviceKey, serviceValue, 77 | etcd3.WithLease(resp.ID)) 78 | 79 | if err != nil { 80 | log.Printf("grpclb: set service '%s' with ttl to etcd3 failed: %s", 81 | opt.Name, err.Error()) 82 | } 83 | return err 84 | } 85 | 86 | log.Printf("grpclb: service '%s' connect to etcd3 failed: %s", 87 | opt.Name, err.Error()) 88 | return err 89 | } 90 | 91 | // refresh set to true for not notifying the watcher 92 | // context.Background() 93 | _, putErr := client.Put(ctx, serviceKey, serviceValue, 94 | etcd3.WithLease(resp.ID)) 95 | 96 | if putErr != nil { 97 | log.Printf("grpclb: refresh service '%s' with ttl to etcd3 failed: %s", 98 | opt.Name, putErr.Error()) 99 | } 100 | 101 | return putErr 102 | } 103 | 104 | go func() error { 105 | err := insertFunc() 106 | if err != nil { 107 | log.Printf("insertFunc() err: %v", err) 108 | return err 109 | } 110 | 111 | // invoke self-register with ticker 112 | ticker := time.NewTicker(opt.Interval) 113 | 114 | for { 115 | select { 116 | case <-stopSignal: 117 | cancel() 118 | return nil 119 | case <-ticker.C: 120 | insertFunc() 121 | // return 122 | case <-ctx.Done(): 123 | ticker.Stop() 124 | client.Delete(context.Background(), serviceKey) 125 | //, &etcd3.DeleteOptions{Recursive: true} 126 | return nil 127 | } 128 | } 129 | }() 130 | 131 | return nil 132 | } 133 | 134 | // Register gRPC naming and discovery 135 | func Register(name, host string, port int, target string, 136 | interval time.Duration, ttl int, args ...int) error { 137 | serviceValue := fmt.Sprintf("%s:%d", host, port) 138 | serviceKey = fmt.Sprintf("/%s/%s/%s", Prefix, name, serviceValue) 139 | 140 | // get endpoints for register dial address 141 | var ( 142 | err error 143 | client *etcd3.Client 144 | ) 145 | 146 | if len(args) > 0 { 147 | client, err = etcd3.New(etcd3.Config{ 148 | Endpoints: strings.Split(target, ","), 149 | }) 150 | } else { 151 | client, err = etcd3.NewFromURL(target) 152 | } 153 | 154 | if err != nil { 155 | return fmt.Errorf("grpclb: create etcd3 client failed: %v", err) 156 | } 157 | 158 | ctx, cancel := context.WithCancel(context.Background()) 159 | insertFunc := func() error { 160 | // minimum lease TTL is ttl-second 161 | resp, _ := client.Grant(context.TODO(), int64(ttl)) 162 | // should get first, if not exist, set it 163 | // context.Background() 164 | _, err := client.Get(ctx, serviceKey) 165 | if err != nil { 166 | if err == rpctypes.ErrKeyNotFound { 167 | // ctx context.TODO() 168 | _, err := client.Put(ctx, serviceKey, serviceValue, 169 | etcd3.WithLease(resp.ID)) 170 | 171 | if err != nil { 172 | log.Printf("grpclb: set service '%s' with ttl to etcd3 failed: %s", 173 | name, err.Error()) 174 | } 175 | 176 | return err 177 | } 178 | 179 | log.Printf("grpclb: service '%s' connect to etcd3 failed: %s", 180 | name, err.Error()) 181 | 182 | return err 183 | } 184 | 185 | // refresh set to true for not notifying the watcher 186 | // context.Background() 187 | _, putErr := client.Put(ctx, serviceKey, serviceValue, 188 | etcd3.WithLease(resp.ID)) 189 | 190 | if putErr != nil { 191 | log.Printf("grpclb: refresh service '%s' with ttl to etcd3 failed: %s", 192 | name, putErr.Error()) 193 | } 194 | 195 | return putErr 196 | } 197 | 198 | go func() error { 199 | err := insertFunc() 200 | if err != nil { 201 | log.Printf("insertFunc() err: %v", err) 202 | return err 203 | } 204 | 205 | // invoke self-register with ticker 206 | ticker := time.NewTicker(interval) 207 | 208 | for { 209 | select { 210 | case <-stopSignal: 211 | cancel() 212 | return nil 213 | case <-ticker.C: 214 | insertFunc() 215 | // return 216 | case <-ctx.Done(): 217 | ticker.Stop() 218 | client.Delete(context.Background(), serviceKey) 219 | //, &etcd3.DeleteOptions{Recursive: true} 220 | return nil 221 | } 222 | } 223 | }() 224 | 225 | return nil 226 | } 227 | 228 | // UnRegister delete registered service from etcd 229 | func UnRegister() error { 230 | stopSignal <- true 231 | stopSignal = make(chan bool, 1) // just a hack to avoid multi UnRegister deadlock 232 | 233 | var err error 234 | if _, err := client.Delete(context.Background(), serviceKey); err != nil { 235 | log.Printf("grpclb: deregister '%s' failed: %s", serviceKey, err.Error()) 236 | } else { 237 | log.Printf("grpclb: deregister '%s' ok.", serviceKey) 238 | } 239 | 240 | return err 241 | } 242 | -------------------------------------------------------------------------------- /resolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-vgo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // https://github.com/go-vgo/gt/blob/master/LICENSE 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | package grpclb 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | "strings" 17 | 18 | etcd3 "go.etcd.io/etcd/clientv3" 19 | "google.golang.org/grpc/naming" 20 | ) 21 | 22 | // resolver is the implementation of grpc.naming.Resolver 23 | type resolver struct { 24 | serviceName string // service name to resolve 25 | } 26 | 27 | // NewResolver return resolver with service name 28 | func NewResolver(serviceName string) *resolver { 29 | return &resolver{serviceName: serviceName} 30 | } 31 | 32 | // Resolve to resolve the service from etcd, target is the dial address of etcd 33 | // target example: "http://127.0.0.1:2379,http://127.0.0.1:12379,http://127.0.0.1:22379" 34 | func (re *resolver) Resolve(target string) (naming.Watcher, error) { 35 | if re.serviceName == "" { 36 | return nil, errors.New("grpclb: no service name provided") 37 | } 38 | 39 | // generate etcd client 40 | client, err := etcd3.New(etcd3.Config{ 41 | Endpoints: strings.Split(target, ","), 42 | }) 43 | if err != nil { 44 | return nil, fmt.Errorf("grpclb: creat etcd3 client failed: %s", err.Error()) 45 | } 46 | 47 | // Return watcher 48 | return newWatcher(re, *client), nil 49 | // return &watcher{re: re, client: *client}, nil 50 | } 51 | -------------------------------------------------------------------------------- /watcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-vgo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // https://github.com/go-vgo/gt/blob/master/LICENSE 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | package grpclb 12 | 13 | import ( 14 | "context" 15 | "fmt" 16 | 17 | etcd3 "go.etcd.io/etcd/clientv3" 18 | "google.golang.org/grpc/naming" 19 | ) 20 | 21 | // watcher is the implementation of grpc.naming.Watcher 22 | type watcher struct { 23 | re *resolver // re: Etcd Resolver 24 | client etcd3.Client 25 | isInitialized bool 26 | ctx context.Context 27 | cancel context.CancelFunc 28 | } 29 | 30 | // newWatcher new watcher 31 | func newWatcher(re *resolver, cli etcd3.Client) naming.Watcher { 32 | ctx, cancel := context.WithCancel(context.Background()) 33 | 34 | w := &watcher{ 35 | re: re, 36 | client: cli, 37 | ctx: ctx, 38 | cancel: cancel, 39 | } 40 | 41 | return w 42 | } 43 | 44 | // Close do nothing 45 | func (w *watcher) Close() { 46 | w.cancel() 47 | } 48 | 49 | // Next to return the updates 50 | func (w *watcher) Next() ([]*naming.Update, error) { 51 | // prefix is the etcd prefix/value to watch 52 | prefix := fmt.Sprintf("/%s/%s/", Prefix, w.re.serviceName) 53 | 54 | // check if is initialized 55 | if !w.isInitialized { 56 | // query addresses from etcd 57 | resp, err := w.client.Get(w.ctx, prefix, etcd3.WithPrefix()) 58 | w.isInitialized = true 59 | if err == nil { 60 | addrs := extractAddrs(resp) 61 | //if not empty, return the updates or watcher new dir 62 | if l := len(addrs); l != 0 { 63 | updates := make([]*naming.Update, l) 64 | for i := range addrs { 65 | updates[i] = &naming.Update{Op: naming.Add, Addr: addrs[i]} 66 | } 67 | 68 | return updates, nil 69 | } 70 | } 71 | } 72 | 73 | // generate etcd Watcher 74 | rch := w.client.Watch(w.ctx, prefix, etcd3.WithPrefix()) 75 | for wresp := range rch { 76 | for _, ev := range wresp.Events { 77 | switch ev.Type { 78 | // case mvccpb.PUT: 79 | case etcd3.EventTypePut: 80 | return []*naming.Update{{Op: naming.Add, 81 | Addr: string(ev.Kv.Value)}}, nil 82 | // case mvccpb.DELETE: 83 | case etcd3.EventTypeDelete: 84 | return []*naming.Update{{Op: naming.Delete, 85 | Addr: string(ev.Kv.Value)}}, nil 86 | } 87 | } 88 | } 89 | 90 | return nil, nil 91 | } 92 | 93 | func extractAddrs(resp *etcd3.GetResponse) []string { 94 | addrs := []string{} 95 | 96 | if resp == nil || resp.Kvs == nil { 97 | return addrs 98 | } 99 | 100 | for i := range resp.Kvs { 101 | if v := resp.Kvs[i].Value; v != nil { 102 | addrs = append(addrs, string(v)) 103 | } 104 | } 105 | 106 | return addrs 107 | } 108 | --------------------------------------------------------------------------------