{"id":37216096,"url":"https://github.com/link1st/grpcwithconsul","last_synced_at":"2026-01-15T00:59:15.282Z","repository":{"id":57538953,"uuid":"199386720","full_name":"link1st/gRPCwithConsul","owner":"link1st","description":"Use gRPC + Consul to do service discovery and RPC.","archived":false,"fork":true,"pushed_at":"2019-07-28T05:48:50.000Z","size":285,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-06-21T07:35:18.808Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"GrassInWind2019/gRPC-kafkaDemo","license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/link1st.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-29T05:49:34.000Z","updated_at":"2023-04-17T05:25:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/link1st/gRPCwithConsul","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/link1st/gRPCwithConsul","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link1st%2FgRPCwithConsul","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link1st%2FgRPCwithConsul/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link1st%2FgRPCwithConsul/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link1st%2FgRPCwithConsul/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/link1st","download_url":"https://codeload.github.com/link1st/gRPCwithConsul/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/link1st%2FgRPCwithConsul/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28440996,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T00:55:22.719Z","status":"ssl_error","status_checked_at":"2026-01-15T00:55:20.945Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-15T00:59:14.581Z","updated_at":"2026-01-15T00:59:15.269Z","avatar_url":"https://github.com/link1st.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 目录\n- [gRPC + consul](#gRPC+consul)\n- [服务发现及RPC过程](#服务发现及RPC过程)\n  * [服务发现及RPC示意图](#服务发现及RPC示意图)\n  * [RPC接口](#RPC接口)\n  * [client](#client)\n  * [server](#server)\n  * [相关函数原型](#相关函数原型)\n  * [gRPC其他相关代码说明](#gRPC其他相关代码说明)\n  * [本文其他相关代码说明](#本文其他相关代码说明)\n  * [本文github链接](#本文github链接)\n- [使用本文code简介](#使用本文code简介)\n  * [需要下载安装的如下](#需要下载安装的如下)\n  * [运行步骤](#运行步骤)\n  * [修改RPC接口](#修改RPC接口)\n  * [运行结果](#运行结果)\n  * * [server](#server)\n  * * [client](#client)\n# gRPC+consul\n本文使用consul做服务发现，gRPC来处理RPC(Remote Procedure Call)即远程过程调用。\ngRPC是google开源的一个高性能、通用的RPC框架，基于http2、protobuf设计开发，是一个跨编程语言的RPC框架，跨编程语言让client和server可以采用不同的编程语言开发。\n\n# 服务发现及RPC过程  \n本文使用的注记说明：  \nfuncA()--\u003efuncB()--\u003efuncC()  \n\u0026emsp;\u0026emsp;\u0026emsp;--\u003efuncD()--\u003efuncE()  \nfuncC()--\u003efuncF()  \n\u0026emsp;\u0026emsp;\u0026emsp;--\u003efuncG()  \n上面的函数调用关系为：funcA按序调用了funcB和funcD，funcB调用了funcC,funcD直接调用了funcE, funcC调用了funcF和funcG。 \n\n## 服务发现及RPC示意图\n![服务发现及RPC示意图.png](https://github.com/GrassInWind2019/gRPCwithConsul/blob/master/服务发现及RPC示意图.png)\n\n## RPC接口  \nRPC接口通过protobuf定义，使用的是proto3版本。  \n```\n//The request message containing the user's name\nmessage HelloRequest {\n    string name = 1;\n    int32  num1 = 2;\n    int32  num2 = 3;\n}\n//The response message\nmessage HelloResponse {\n    string message = 1;\n    int32  result = 2;\n}\n//service definition\nservice HelloService {\n    rpc SayHello(HelloRequest) returns(HelloResponse);\n}\n```\n\n## client\n1. client通过调用ConsulResolverInit注册了一个consul client,同时向gRPC注册了自定义的resolver即consulResolverBuilder。  \n2. client调用Dial与HelloService server建立连接。  \nDial连接server大致实现过程如下：  \n  Dial()--\u003eDialContext()--\u003enewCCResolverWrapper()  \n  newCCResolverWrapper()--\u003eBuild()  \n  Build()--\u003eresolveServiceFromConsul() 通过consul获取service的地址信息  \n  \u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003estart()--\u003eUpdateState()--\u003eupdateResolverState() 这个方法里会将consul获取的service地址信息存储到gRPC中  \n  \u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003ecsMonitor()创建一个goroutine每隔500ms向consul获取service信息，然后调用UpdateState更新地址  \n  updateResolverState()--\u003enewCCBalancerWrapper()--\u003ewatcher()这个watcher goroutine专门监控service的地址变化并更新连接  \n \u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003eupdateClientConnState()这个方法是将通过consul获取的地址信息中去除GRPCLB地址，然后通过通道发送给watcher去处理  \nwatcher()--\u003eUpdateClientConnState()这个方法会根据consul获取的新地址创建SubConn并调用Connect()去连接server，然后会检查当前的地址集合将失效的地址删除  \n3. client调用SayHello方法  \n实际上调用的是通过proto自动生成的代码中的helloServiceClient的SayHello方法。  \nSayHello()--\u003eInvoke()  \nInvoke()--\u003enewClientStream()--\u003enewAttemptLocked()--\u003egetTransport()--\u003ePick()该方法轮询可用连接来使用即roundrobin负载均衡    \n\u0026emsp;\u0026emsp;\u0026emsp;--\u003eSendMsg()该方法将请求发送给对应的server  \n\u0026emsp;\u0026emsp;\u0026emsp;--\u003eRecvMsg()该方法接收server回应的respoonse  \n \n 4. 小结\n client需要实现gRPC resolver相关接口，以使得gRPC能够获取service的地址。gRPC的resolver example: https://github.com/grpc/grpc-go/blob/master/examples/features/name_resolving/client/main.go  \n 本文的resolver example: https://github.com/GrassInWind2019/gRPCwithConsul/blob/master/serviceDiscovery/consulResolver.go  \nclient首先通过调用ConsulResolverInit向gRPC注册实现的resolver，然后调用Dial与server建立连接，然后再调用NewHelloServiceClient创建一个通过protoc自动生成的HelloService的client，最后就可以调用这个client的SayHello方法来实现RPC。  \n本文的client example: https://github.com/GrassInWind2019/gRPCwithConsul/blob/master/example/client/client.go\n  \n## server  \n1. 调用newHelloServiceServer来创建一个gRPC server及helloServiceServer。 \n2. server通过调用Listen来侦听指定的地址和端口。 \n2. 调用CreateConsulRegisterClient创建一个consul client， \n3. 调用RegisterServiceToConsul向consul server注册一个service。  \nRegisterServiceToConsul()--\u003eregisterServiceToConsul()  \nregisterServiceToConsul()--\u003eServiceRegister()通过调用consul client的ServiceRegister方法向consul server注册  \n \u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003eAgentServiceCheck()向consul server注册service的health check  \n \u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003e创建了一个goroutine并定期调用UpdateTTL向consul server表明service还是OK的。  \n4. 调用RegisterHelloServiceServer向gRPC注册一个service及它提供的方法。  \nRegisterHelloServiceServer()--\u003eRegisterService()--\u003eregister()  \nregister将service提供的方法根据名称保存到了一个map中。  \n```\nfunc (s *Server) register(sd *ServiceDesc, ss interface{}) {\n    ...\n\tsrv := \u0026service{\n\t\tserver: ss,\n\t\tmd:     make(map[string]*MethodDesc),\n        ...\n\t}\n\t//将待注册的service的MethodDesc对象保存到新创建的service的md中\n\tfor i := range sd.Methods {\n\t\td := \u0026sd.Methods[i]\n\t\tsrv.md[d.MethodName] = d\n\t}\n    ...\n\t//将新创建的service对象保存到server的m中\n\ts.m[sd.ServiceName] = srv\n}\nfunc RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {\n\ts.RegisterService(\u0026_HelloService_serviceDesc, srv)\n}\nvar _HelloService_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"HelloService_proto.HelloService\",\n\tHandlerType: (*HelloServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"SayHello\",\n\t\t\tHandler:    _HelloService_SayHello_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"HelloService.proto\",\n}\n```\n6. 调用Serve来为client提供服务。  \nServe()--\u003eAccept()接受client连接  \n\u0026emsp;\u0026emsp;\u0026emsp;--\u003e新创建一个goroutine来处理建立的连接--\u003ehandleRawConn()  \nhandleRawConn()--\u003enewHTTP2Transport()--\u003eNewServerTransport()--\u003enewHTTP2Server()这个方法会与client完成http2握手，然后创建一个goroutine专门用于发送数据。  \n\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003eserveStreams()--\u003eHandleStreams()--\u003eoperateHeaders()--\u003ehandleStream()会从接收到的stream中取出service和method名称，然后从server结构对应的map表中找出method handler。  \n```\nfunc (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream, trInfo *traceInfo) {\n\t//获取stream的Method名称\n\tsm := stream.Method()\n\tif sm != \"\" \u0026\u0026 sm[0] == '/' {\n\t\tsm = sm[1:]\n\t}\n\tpos := strings.LastIndex(sm, \"/\")\n\t...\n\t//获取service名称\n\tservice := sm[:pos]\n\t//获取method名称\n\tmethod := sm[pos+1:]\n\t//从gRPC server的m map中根据service名称找到对应的service对象\n\tsrv, knownService := s.m[service]\n\tif knownService {\n\t\t//再从service的md map中找到对应的MethodDesc对象，其中包含method handler\n\t\tif md, ok := srv.md[method]; ok {\n\t\t\ts.processUnaryRPC(t, stream, srv, md, trInfo)\n\t\t\treturn\n\t\t}\n\t\t...\n\t}\n\t...\n\tif err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {\n\t...\n\t}\n\t...\n}\n```\nprocessUnaryRPC()--\u003eNewContextWithServerTransportStream()创建一个context  \n\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003eHandler()实际就是调用SayHello  \n\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;\u0026emsp;--\u003esendResponse()将执行结果发送给client  \nsendResponse()--\u003eWrite()--\u003eput()--\u003eexecuteAndPut()将数据存入controlBuffer的list中，然后通知consumer即newHTTP2Server创建的那个goroutine调用get来取数据并发送出去。  \n\n```\n//  google.golang.org/grpc/internal/transport/controlbuf.go\nfunc (c *controlBuffer) executeAndPut(f func(it interface{}) bool, it interface{}) (bool, error) {\n\tvar wakeUp bool\n\tc.mu.Lock()\n\t...\n\tif c.consumerWaiting {\n\t\twakeUp = true\n\t\tc.consumerWaiting = false\n\t}\n\t//将数据加入list中\n\tc.list.enqueue(it)\n\tc.mu.Unlock()\n\tif wakeUp {\n\t\tselect {\n\t\t//通知consumer取数据\n\t\tcase c.ch \u003c- struct{}{}:\n\t\tdefault:\n\t\t}\n\t}\n\treturn true, nil\n}\n//  google.golang.org/grpc/internal/transport/controlbuf.go\nfunc (c *controlBuffer) get(block bool) (interface{}, error) {\n\tfor {\n\t\t...\n\t\tif !c.list.isEmpty() {\n\t\t\t//从list中取数据\n\t\t\th := c.list.dequeue()\n\t\t\tc.mu.Unlock()\n\t\t\treturn h, nil\n\t\t}\n\t\tc.consumerWaiting = true\n\t\tselect {\n\t\t//consumer等待producer生产数据\n\t\tcase \u003c-c.ch:\n\t\tcase \u003c-c.done:\n\t\t\tc.finish()\n\t\t\treturn nil, ErrConnClosing\n\t\t}\n\t}\n}\n```\n7.模拟service故障及恢复  \n通过faultSimulator每隔15s调用GracefulStop来停止正在运行的server来模拟service发生故障。  \n```\nfunc (hssMonitor *hsServerMonitor) faultSimulator() {\n\tt := time.NewTicker(15 * time.Second)\n\tfor {\n\t\tselect {\n\t\tcase \u003c-t.C:\n\t\t\tfmt.Println(\"time out! Stop servers!\")\n\t\t\tfor _, s := range hssMonitor.hsServers {\n\t\t\t\ts.gServer.GracefulStop()\n\t\t\t\tfmt.Printf(\"server %s:%d graceful stopped!\\n\", s.info.Addr, s.info.Port)\n\t\t\t}\n\t\t...\n\t\t}\n\t}\n}\n```\n通过helloServiceServerMonitor来监控server状态，若发生失败退出，则重新启动一个新的server。  \n```\n //   gRPCwithConsul/example/server/server.go\nfunc (hssMonitor *hsServerMonitor) startNewServer(hsPort int) {\n\thsServer := newHelloServiceServer(hsPort)\n\thssMonitor.hsServers = append(hssMonitor.hsServers, hsServer)\n\tgo startHelloServiceServer(hsServer)\n}\n//   gRPCwithConsul/example/server/server.go\nfunc (hssMonitor *hsServerMonitor) helloServiceServerMonitor() {\n\t...\n\tfor {\n\t\tfor i := 0; i \u003c serverNum; i++ {\n\t\t\tselect {\n\t\t\t//hello service server fault happened, start new server as recovery\n\t\t\tcase \u003c-hssMonitor.hsServers[i].ch:\n\t\t\t\t//通过改变port，来模拟service地址变化，client调用RPC接口依旧正常工作\n\t\t\t\tport[i] += 5\n\t\t\t\thssMonitor.hsServers[i].info.Port = port[i]\n\t\t\t\t//need new a gRPC server after stopping old one, otherwise will meet gRPC error:\n\t\t\t\t//Server.RegisterService after Server.Serve for \"HelloService_proto.HelloService\"\n\t\t\t\ts := grpc.NewServer()\n\t\t\t\thssMonitor.hsServers[i].gServer = s\n\t\t\t\t//start new server\n\t\t\t\tgo startHelloServiceServer(hssMonitor.hsServers[i])\n\t\t\t...\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(10 * time.Millisecond)\n\t}\n}\n```\n\n## 相关函数原型\n  ```\n   //  gRPCwithConsul/serviceDiscovery/consulResolver.go\n  func (r *consulResolver) start()\n  //  gRPCwithConsul/serviceDiscovery/consulResolver.go\n  func (crb *consulResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error)\n  //  gRPCwithConsul/example/HelloService_proto/HelloService.pb.go\n  func (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)\n  //  gRPCwithConsul/example/HelloService_proto/HelloService.pb.go\n  func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer)\n  //  gRPCwithConsul/serviceDiscovery/consulRegister.go\n  func CreateConsulRegisterClient(csAddr string) error\n  //  gRPCwithConsul/serviceDiscovery/consulRegister.go\n  func (csr *consulServiceRegister) registerServiceToConsul(info ServiceInfo) error\n  //   gRPCwithConsul/example/server/server.go\nfunc newHelloServiceServer(hsPort int) *helloServiceServer\n\n //  google.golang.org/grpc/resolver_conn_wrapper.go\nfunc newCCResolverWrapper(cc *ClientConn) (*ccResolverWrapper, error)\n//  google.golang.org/grpc/resolver_conn_wrapper.go\nfunc (ccr *ccResolverWrapper) UpdateState(s resolver.State)     \n//  google.golang.org/grpc/clientconn.go\nfunc (cc *ClientConn) updateResolverState(s resolver.State) error   \n//  grpc/grpc-go/balancer/roundrobin/roundrobin.go\nfunc (p *rrPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error)\n//  google.golang.org/grpc/server.go\nfunc (s *Server) Serve(lis net.Listener) error\n//  google.golang.org/grpc/server.go\nfunc (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) transport.ServerTransport\n//  google.golang.org/grpc/server.go\nfunc (s *Server) serveStreams(st transport.ServerTransport)\n//  google.golang.org/grpc/internal/transport/http2_server.go\nfunc (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.Context, string) context.Context)\n//   google.golang.org/grpc/internal/transport/http2_server.go\nfunc (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) error\n  ```  \n  ## gRPC其他相关代码说明\n  ```\n  google.golang.org/grpc/resolver.go\n  GRPCLB源码解释\n  const (\n\t// Backend indicates the address is for a backend server.\n\tBackend AddressType = iota\n\t// GRPCLB indicates the address is for a grpclb load balancer.\n\tGRPCLB\n)\n  func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState)  google.golang.org/grpc/balancer_conn_wrappers.go\n  {\n    ...\n    //通过通道发送给watcher goroutine去更新service地址\n    ccb.ccUpdateCh \u003c- ccs\n  }\n  func (ccb *ccBalancerWrapper) watcher()   google.golang.org/grpc/balancer_conn_wrappers.go\n  {\n    ...\n    case s := \u003c-ccb.ccUpdateCh:\n      ...\n      if ub, ok := ccb.balancer.(balancer.V2Balancer); ok {\n\t\t\t\tub.UpdateClientConnState(*s)\n\t\t\t}\n    ...\n  }\n    func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) {    google.golang.org/grpc/balancer/base/balancer.go\n  ...\n\tfor _, a := range s.ResolverState.Addresses {\n\t\taddrsSet[a] = struct{}{}\n\t\tif _, ok := b.subConns[a]; !ok {\n\t\t\t// a is a new address (not existing in b.subConns).\n\t\t\tsc, err := b.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{HealthCheckEnabled: b.config.HealthCheck})\n      ...\n\t\t\tb.subConns[a] = sc\n\t\t\tb.scStates[sc] = connectivity.Idle\n\t\t\tsc.Connect()\n\t\t}\n\t}\n\t//在通过consul获取service最新地址后，检查subConns中旧的地址是否失效\n\tfor a, sc := range b.subConns {\n\t\t// a was removed by resolver.\n\t\t//如果最新地址集合中不包含地址a，则代表a已失效\n\t\tif _, ok := addrsSet[a]; !ok {\n\t\t\tb.cc.RemoveSubConn(sc)\n\t\t\tdelete(b.subConns, a)\n\t\t}\n\t}\n}\n//roundrobin实现\n//grpc/grpc-go/balancer/roundrobin/roundrobin.go\nfunc (p *rrPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) {\n\tp.mu.Lock()\n\tsc := p.subConns[p.next]\n\t//轮流选择可用连接使用\n\tp.next = (p.next + 1) % len(p.subConns)\n\tp.mu.Unlock()\n\treturn sc, nil, nil\n}\n```\n## 本文其他相关代码说明  \n```\n func (crb *consulResolverBuilder) resolveServiceFromConsul() ([]resolver.Address, error) {\n  //调用consul API来获取指定service的地址信息\n\tserviceEntries, _, err := crb.client.Health().Service(crb.serviceName, \"\", true, \u0026consulapi.QueryOptions{})\n\tif err != nil {\n\t\tfmt.Println(\"call consul Health API failed, \", err)\n\t\treturn nil, err\n\t}\n\taddrs := make([]resolver.Address, 0)\n\tfor _, serviceEntry := range serviceEntries {\n    //将获取的地址信息组装成resolver.Address类型返回\n\t\taddress := resolver.Address{Addr: fmt.Sprintf(\"%s:%d\", serviceEntry.Service.Address, serviceEntry.Service.Port)}\n\t\taddrs = append(addrs, address)\n\t}\n\treturn addrs, nil\n}\nfunc (crb *consulResolverBuilder) csMonitor(cr *consulResolver) {\n\tt := time.NewTicker(500 * time.Millisecond)\n\t//Get service addresses from consul every 500 Millisecond and update them to gRPC\n\tfor {\n\t\tselect {\n\t\tcase \u003c-t.C:\n\t\t//resolve now\n\t\tcase \u003c-cr.rnCh:\n\t\t}\n\t\taddrs, err := crb.resolveServiceFromConsul()\n...\n\t\tcr.cc.UpdateState(resolver.State{Addresses: addrs})\n\t}\n}\n//   gRPCwithConsul/example/HelloService_proto/HelloService.pb.go\nfunc (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {    \n\tout := new(HelloResponse)\n\terr := c.cc.Invoke(ctx, \"/HelloService_proto.HelloService/SayHello\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n //  gRPCwithConsul/serviceDiscovery/consulRegister.go\nfunc (csr *consulServiceRegister) registerServiceToConsul(info ServiceInfo) error {\n\tserviceId := getServiceId(info.ServiceName, info.Addr, info.Port)\n\tasg := \u0026consulapi.AgentServiceRegistration{\n\t\tID:      serviceId,\n\t\tName:    info.ServiceName,\n\t\tTags:    []string{info.ServiceName},\n\t\tPort:    info.Port,\n\t\tAddress: info.Addr,\n\t}\n\t//register service to consul server\n\terr := ccMonitor.client.Agent().ServiceRegister(asg)\n   ...\n\t//向consul server注册 health check\n\tasCheck := consulapi.AgentServiceCheck{TTL: fmt.Sprintf(\"%ds\", info.CheckInterval), Status: consulapi.HealthPassing}\n\terr = ccMonitor.client.Agent().CheckRegister(\n\t\t\u0026consulapi.AgentCheckRegistration{\n\t\t\tID:                serviceId,\n\t\t\tName:              info.ServiceName,\n\t\t\tServiceID:         serviceId,\n\t\t\tAgentServiceCheck: asCheck})\n   ...\n\t//start a goroutine to update health status to consul server\n\tgo func(\u003c-chan struct{}) {\n\t\tt := time.NewTicker(info.UpdateInterval)\n\t\tfor {\n             select {\n\t\t\tcase \u003c-t.C:\n            ...\n\t\t\t}\n\t\t\t//向consul server报告service HealthPassing\n\t\t\terr = ccMonitor.client.Agent().UpdateTTL(serviceId, \"\", asCheck.Status)\n            ...\n\t\t}\n\t}(ch)\n\treturn nil\n}\n//   gRPCwithConsul/example/server/server.go\nfunc newHelloServiceServer(hsPort int) *helloServiceServer {\n\t//创建一个gRPC server\n\ts := grpc.NewServer()\n\tinfo := \u0026serviceDiscovery.ServiceInfo{\n\t\tAddr:           ip,\n\t\tPort:           hsPort,\n\t\tServiceName:    \"HelloService\",\n\t\tUpdateInterval: 5 * time.Second,\n\t\tCheckInterval:  20}\n\tch := make(chan struct{}, 1)\n\t//创建一个helloServiceServer\n\thsServer := \u0026helloServiceServer{\n\t\tinfo:    info,\n\t\tgServer: s,\n\t\tch:      ch}\n\treturn hsServer\n}\n```  \n\n# 本文github链接  \nhttps://github.com/GrassInWind2019/gRPCwithConsul  \n\u003ciframe src=\"https://ghbtns.com/github-btn.html?user=GrassInWind2019\u0026repo=gRPCwithConsul\u0026type=watch\u0026count=true\u0026size=large\" allowtransparency=\"true\" frameborder=\"0\" scrolling=\"0\" width=\"156px\" height=\"30px\"\u003e\u003c/iframe\u003e  \n\u003ciframe src=\"https://ghbtns.com/github-btn.html?user=GrassInWind2019\u0026repo=gRPCwithConsul\u0026type=fork\u0026count=true\u0026size=large\" allowtransparency=\"true\" frameborder=\"0\" scrolling=\"0\" width=\"156px\" height=\"30px\"\u003e\u003c/iframe\u003e  \n  \n# 使用本文code简介  \n## 需要下载安装的如下  \n1. Go语言  \n\t下载link: https://golang.google.cn/dl/  \n2. consul  \n\t consul github link: https://github.com/hashicorp/consul  \n    mkdir -p $GOPATH/src/github.com/hashicorp/  \n    cd $GOPATH/src/github.com/hashicorp/  \n    git clone git@github.com:hashicorp/consul.git  \n    下载速度只有几kB/s，需要几个小时才能下完  \n3. protobuf  \n    从https://github.com/protocolbuffers/protobuf/releases下载protobuf和protoc的包\n4. gRPC  \n\tmkdir -p $GOPATH/src/github.com/grpc/  \n\tcd $GOPATH/src/github.com/grpc/  \n\tgit clone git@github.com:grpc/grpc-go.git  \n\t下载速度只有几kB/s，需要几个小时才能下完  \n具体的安装步骤请自行搜索教程。\n5. 下载本文code  \n   mkdir -p $GOPATH/src/github.com/GrassInWind2019/  \n   cd $GOPATH/src/github.com/GrassInWind2019/  \n   git clone git@github.com:GrassInWind2019/gRPCwithConsul.git  \n## 运行步骤  \n1. consul.exe agent -dev   \n   我是在windows环境下运行的，首先启动consul server，这是最简单的方式。 \n2. 编译 gRPCwithConsul/example/server/server.go  \n使用liteIDE打开server.go，点击build按钮即可完成编译。 \nliteIDE下载link：https://sourceforge.net/projects/liteide/   \n3. 运行server  \n    windows 下可以用git bash运行，在$GOPATH/src/github.com/GrassInWind2019/gRPCwithConsul/example/server/目录下打开git bash,  ./server.exe即可。也可以直接在liteIDE点击运行按钮。  \n4. 编译gRPCwithConsul/example/client/client.go  \n使用liteIDE打开client.go，点击build按钮即可完成编译。 \n5. 运行client   \n\t在$GOPATH/src/github.com/GrassInWind2019/gRPCwithConsul/example/client/目录下打开git bash， ./client.exe   [可选参数name]。也可以直接在liteIDE点击运行按钮。  \n##  修改RPC接口  \n如果想修改RPC接口也就是修改HelloService.proto文件  \n修改完后需要利用protoc工具重新生成HelloService.pb.go  \n可使用如下命令  \nprotoc.exe --plugin=protoc-gen-go=$GOPATH/bin/protoc-gen-go.exe --go_out=plugins=grpc:. --proto_path .  HelloService.proto  \n一定要带上--go_out=plugins=grpc，否则生成的go文件会缺少gRPC相关的code比如编译会报错，找不到HelloService.RegisterHelloServiceServer。  \n## 运行结果  \n### server  \n![server.png](https://github.com/GrassInWind2019/gRPCwithConsul/blob/master/server.png)\n### client  \n![client.png](https://github.com/GrassInWind2019/gRPCwithConsul/blob/master/client.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flink1st%2Fgrpcwithconsul","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flink1st%2Fgrpcwithconsul","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flink1st%2Fgrpcwithconsul/lists"}