title | date | comments | categories | tags | permalink | ||
---|---|---|---|---|---|---|---|
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就能看到最终结果,如下图所示