8.12gRPC与consul配合使用

titledatecommentscategoriestagspermalink
gRPC与consul配合使用
2020/5/10
true
微服务
微服务
8.12

consul简介

Consul是一个服务网格(微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控)解决方案,它是一个一个分布式的,高度可用的系统,而且开发使用都很简便。它提供了一个功能齐全的控制平面,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心。

总之就是监控网络服务,提供注册查询等功能,形象一点就当是一个"监工",监工帮我们对接服务,如果需要查找某个服务就直接询问这个监工。客户端就不需要知道服务器的ip和port了。

consul安装方法

MAC系统使用brew安装,简单方便,而且配置好了环境变量

brew install consul

也可以去官网下载编译好的二进制文件,然后手动配置环境变量

grpc与consul配合完成服务注册和查询功能

编写proto

编写hello.proto文件

syntax = "proto3";

package proto;

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string result = 1;
}

service HelloService {
    rpc SayHello(HelloRequest) returns(HelloResponse);
}

编译proto文件

protoc --go_out=plugins=grpc:./ *.proto

向consul注册的功能

编写consul_register.go

package register

import (
  "LearnGrpc/discovery"
  "fmt"
  "log"
  "os"
  "os/signal"
  "strconv"
  "syscall"
  "time"

  consulapi "github.com/hashicorp/consul/api"
)

type ConsulRegister struct {
  Target string
  Ttl    int
}

func NewConsulRegister(target string, ttl int) *ConsulRegister {
  return &ConsulRegister{Target: target, Ttl: ttl}
}

func (cr *ConsulRegister) Register(info discovery.RegisterInfo) error {

  // initial consul client config
  config := consulapi.DefaultConfig()
  config.Address = cr.Target
  client, err := consulapi.NewClient(config)
  if err != nil {
    log.Println("create consul client error:", err.Error())
  }

  serviceId := generateServiceId(info.ServiceName, info.Host, info.Port)

  reg := &consulapi.AgentServiceRegistration{
    ID:      serviceId,
    Name:    info.ServiceName,
    Tags:    []string{info.ServiceName},
    Port:    info.Port,
    Address: info.Host,
  }

  if err = client.Agent().ServiceRegister(reg); err != nil {
    panic(err)
  }

  // initial register service check
  check := consulapi.AgentServiceCheck{TTL: fmt.Sprintf("%ds", cr.Ttl), Status: consulapi.HealthPassing}
  err = client.Agent().CheckRegister(
    &consulapi.AgentCheckRegistration{
      ID:                serviceId,
      Name:              info.ServiceName,
      ServiceID:         serviceId,
      AgentServiceCheck: check})
  if err != nil {
    return fmt.Errorf("LearnGrpc: initial register service check to consul error: %s", err.Error())
  }

  go func() {
    ch := make(chan os.Signal, 1)
    signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGQUIT)
    x := <-ch
    log.Println("LearnGrpc: receive signal: ", x)
    // un-register service
    cr.DeRegister(info)

    s, _ := strconv.Atoi(fmt.Sprintf("%d", x))
    os.Exit(s)
  }()

  go func() {
    ticker := time.NewTicker(info.UpdateInterval)
    for {
      <-ticker.C
      err = client.Agent().UpdateTTL(serviceId, "", check.Status)
      if err != nil {
        log.Println("LearnGrpc: update ttl of service error: ", err.Error())
      }
    }
  }()

  return nil
}

func (cr *ConsulRegister) DeRegister(info discovery.RegisterInfo) error {

  serviceId := generateServiceId(info.ServiceName, info.Host, info.Port)

  config := consulapi.DefaultConfig()
  config.Address = cr.Target
  client, err := consulapi.NewClient(config)
  if err != nil {
    log.Println("create consul client error:", err.Error())
  }

  err = client.Agent().ServiceDeregister(serviceId)
  if err != nil {
    log.Println("LearnGrpc: deregister service error: ", err.Error())
  } else {
    log.Println("LearnGrpc: deregistered service from consul server.")
  }

  err = client.Agent().CheckDeregister(serviceId)
  if err != nil {
    log.Println("LearnGrpc: deregister check error: ", err.Error())
  }

  return nil
}

func generateServiceId(name, host string, port int) string {
  return fmt.Sprintf("%s-%s-%d", name, host, port)
}

resolver功能

编写consul_resolver.go文件

package resolver

import (
  "context"
  "fmt"
  "log"
  "sync"
  "time"

  consulapi "github.com/hashicorp/consul/api"
  "google.golang.org/grpc/resolver"
)

type consulBuilder struct {
  address     string
  client      *consulapi.Client
  serviceName string
}

//NewConsulBuilder 创建consulbuilder
func NewConsulBuilder(address string) resolver.Builder {
  config := consulapi.DefaultConfig()
  config.Address = address
  client, err := consulapi.NewClient(config)
  if err != nil {
    log.Fatal("LearnGrpc: create consul client error", err.Error())
    return nil
  }
  return &consulBuilder{address: address, client: client}
}

func (cb *consulBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
  cb.serviceName = target.Endpoint

  adds, serviceConfig, err := cb.resolve()
  if err != nil {
    return nil, err
  }
  cc.NewAddress(adds)
  cc.NewServiceConfig(serviceConfig)

  consulResolver := NewConsulResolver(&cc, cb, opts)
  consulResolver.wg.Add(1)
  go consulResolver.watcher()

  return consulResolver, nil
}

func (cb consulBuilder) resolve() ([]resolver.Address, string, error) {

  serviceEntries, _, err := cb.client.Health().Service(cb.serviceName, "", true, &consulapi.QueryOptions{})
  if err != nil {
    return nil, "", err
  }

  adds := make([]resolver.Address, 0)
  for _, serviceEntry := range serviceEntries {
    address := resolver.Address{Addr: fmt.Sprintf("%s:%d", serviceEntry.Service.Address, serviceEntry.Service.Port)}
    adds = append(adds, address)
  }
  return adds, "", nil
}

func (cb *consulBuilder) Scheme() string {
  return "consul"
}

type consulResolver struct {
  clientConn           *resolver.ClientConn
  consulBuilder        *consulBuilder
  t                    *time.Ticker
  wg                   sync.WaitGroup
  rn                   chan struct{}
  ctx                  context.Context
  cancel               context.CancelFunc
  disableServiceConfig bool
}

//NewConsulResolver 生成resolver
func NewConsulResolver(cc *resolver.ClientConn, cb *consulBuilder, opts resolver.BuildOptions) *consulResolver {
  ctx, cancel := context.WithCancel(context.Background())
  return &consulResolver{
    clientConn:           cc,
    consulBuilder:        cb,
    t:                    time.NewTicker(time.Second),
    ctx:                  ctx,
    cancel:               cancel,
    disableServiceConfig: opts.DisableServiceConfig}
}

func (cr *consulResolver) watcher() {
  cr.wg.Done()
  for {
    select {
    case <-cr.ctx.Done():
      return
    case <-cr.rn:
    case <-cr.t.C:
    }
    adds, serviceConfig, err := cr.consulBuilder.resolve()
    if err != nil {
      log.Fatal("query service entries error:", err.Error())
    }
    (*cr.clientConn).NewAddress(adds)
    (*cr.clientConn).NewServiceConfig(serviceConfig)
  }
}

func (cr *consulResolver) Scheme() string {
  return cr.consulBuilder.Scheme()
}

func (cr *consulResolver) ResolveNow(rno resolver.ResolveNowOptions) {
  select {
  case cr.rn <- struct{}{}:
  default:
  }
}

func (cr *consulResolver) Close() {
  cr.cancel()
  cr.wg.Wait()
  cr.t.Stop()
}

type consulClientConn struct {
  resolver.ClientConn
  adds []resolver.Address
  sc   string
}

//NewConsulClientConn 新连接
func NewConsulClientConn() resolver.ClientConn {
  return &consulClientConn{}
}

func (cc *consulClientConn) NewAddress(addresses []resolver.Address) {
  cc.adds = addresses
}

func (cc *consulClientConn) NewServiceConfig(serviceConfig string) {
  cc.sc = serviceConfig
}

//GenerateAndRegisterConsulResolver 生成
func GenerateAndRegisterConsulResolver(address string, serviceName string) (schema string, err error) {
  builder := NewConsulBuilder(address)
  target := resolver.Target{Scheme: builder.Scheme(), Endpoint: serviceName}
  _, err = builder.Build(target, NewConsulClientConn(), resolver.BuildOptions{})
  if err != nil {
    return builder.Scheme(), err
  }
  resolver.Register(builder)
  schema = builder.Scheme()
  return
}

编写register.go

package discovery

import "time"

type RegisterInfo struct {
  Host           string
  Port           int
  ServiceName    string
  UpdateInterval time.Duration
}

type Register interface {
  Register(info RegisterInfo) error
  DeRegister(info RegisterInfo) error
}

编写server.go

package main

import (
  "LearnGrpc/discovery"
  "LearnGrpc/discovery/register"
  "LearnGrpc/examples/proto"
  "context"
  "fmt"
  "net"
  "time"

  "google.golang.org/grpc"
  "google.golang.org/grpc/reflection"
)

type server struct {
}

var count int = 0

//服务端实现的方法,给远程客户端调用
func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) {
  count++
  fmt.Println("client called! num: ", count)
  return &proto.HelloResponse{Result: "hi," + in.Name + "!"}, nil
}

const (
  host       = "127.0.0.1"
  port       = 8081
  consulPort = 8500
)

//以下都是固定套路
func main() {
  listen, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(host), port, ""})
  if err != nil {
    fmt.Println(err.Error())
  }
  s := grpc.NewServer()

  //向consul注册服务,注册之后客户端才能向consul查询
  cr := register.NewConsulRegister(fmt.Sprintf("%s:%d", host, consulPort), 15)
  cr.Register(discovery.RegisterInfo{
    Host:           host,
    Port:           port,
    ServiceName:    "HelloService",
    UpdateInterval: time.Second})

  proto.RegisterHelloServiceServer(s, &server{})
  reflection.Register(s)
  if err := s.Serve(listen); err != nil {
    fmt.Println("failed to serve:" + err.Error())
  }
}

编写client.go

package main

import (
  "LearnGrpc/discovery/resolver"
  "LearnGrpc/examples/proto"
  "context"
  "fmt"
  "log"
  "time"

  "google.golang.org/grpc"
  "google.golang.org/grpc/balancer/roundrobin"
)

func main() {

  //consul的默认端口是8500,启动后可在本地访问localhost:8500
  //HelloService就是proto文件中定义的服务
  schema, err := resolver.GenerateAndRegisterConsulResolver("127.0.0.1:8500", "HelloService")
  if err != nil {
    log.Fatal("init consul resovler err", err.Error())
  }

  //建立连接
  conn, err := grpc.Dial(fmt.Sprintf("%s:///HelloService", schema), grpc.WithInsecure(), grpc.WithBalancerName(roundrobin.Name))
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()

  //客户端,这是固定套路
  c := proto.NewHelloServiceClient(conn)

  name := "熊猫阿宝"

  for {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()

    //调用远端的方法
    r, err := c.SayHello(ctx, &proto.HelloRequest{Name: name})
    if err != nil {
      log.Println("could not greet: %v", err)

    } else {
      log.Printf("Hello: %s", r.Result)
    }
    time.Sleep(time.Second)
  }

}

完成以上文件后,首先用以下命令启动consul

consul agent -dev

再启动 server和client就能看到最终结果,如下图所示

gRPC与consul

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值