golang:Go-Micro+nacos实现微服务

参考:

nacos-sdk-go

grp文档

一、启动nacos

Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理的平台,Nacos 脱胎于阿里巴巴内部的 ConfigServer 和 Diamond ,是它们的开源实现。经历过双十一流量峰值和阿里巴巴经济体超大规模容量的考验,沉淀了阿里巴巴软负载团队在这个领域十年的经验,在稳定性和功能性上都有很好的保障。

可以参考nacos部署,部署完成后打开如下:

二、Go-Micro

Go Micro 是一个基于 Go 语言编写的、用于构建微服务的基础框架,提供了分布式开发所需的核心组件,包括 RPC 和事件驱动通信等。

它的设计哲学是「可插拔」的插件化架构,其核心专注于提供底层的接口定义和基础工具,这些底层接口可以兼容各种实现。例如 Go Micro 默认通过 consul 进行服务发现,通过 HTTP 协议进行通信,通过 protobuf 和 json 进行编解码,以便你可以基于这些开箱提供的组件快速启动,但是如果需要的话,你也可以通过符合底层接口定义的其他组件替换默认组件,比如通过 nacos, etcd 或 zookeeper 进行服务发现,这也是插件化架构的优势所在:不需要修改任何底层代码即可实现上层组件的替换。

1、安装protoc

这个工具也称为proto编译器,可以用来生成各种开发语言使用proto协议的代码。

首先下载protoc

brew install protobuf

下载完后,如下:

使用以下命令为Go安装协议编译器插件:

 go install google.golang.org/protobuf/cmd/protoc-gen-go
 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

安装完成后,可以进$gopath目录下查看是否有这俩个文件

2、编写proto文件

proto文件是符合Protocol Buffers语言规范的数据交换协议文件,就像以前WebService定义服务时使用的XML文件。现在一般都是用proto3了,这里创建一个名为 grpcnacos.proto 的文件,放到项目的protocol目录下:

syntax = "proto3";
import "google/protobuf/empty.proto";
package grpcnacos;

option go_package = ".;grpcnacos";
service Test{
  rpc Test(google.protobuf.Empty) returns( TestResponse) {};
}

message TestResponse{
  string msg = 1;
}

消息

默认情况下,gRPC 使用协议缓冲区, 谷歌成熟的结构化数据序列化开源机制(尽管它可以与其他数据格式如 JSON 一起使用)。这是它如何工作的快速介绍。如果您已经熟悉协议缓冲区,请随时跳到下一部分。

使用协议缓冲区的第一步是定义要在proto 文件中序列化的数据的结构:这是一个带有.proto扩展名的普通文本文件。协议缓冲区数据被构造为 消息,其中每条消息都是一个小的信息逻辑记录,包含一系列称为字段的名称-值对,例如,下面这个就是一个简单的例子:

message TestResponse{
  string msg = 1;
}

然后,一旦你指定了你的数据结构,你就可以使用协议缓冲区编译器protoc从你的原型定义中以你喜欢的语言生成数据访问类。这些为每个字段提供了简单的访问器,例如name()and set_name(),以及将整个结构序列化/解析到原始字节的方法。因此,例如,如果您选择的语言是 C++,则在上面的示例中运行编译器将生成一个名为Person. 然后,您可以在应用程序中使用此类来填充、序列化和检索Person协议缓冲区消息

服务定义

与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。默认情况下,gRPC 使用协议缓冲区作为接口定义语言 (IDL),用于描述服务接口和有效负载消息的结构。如果需要,可以使用其他替代方案。

gRPC 允许您定义四种服务方法:

  • 一元 RPC,其中客户端向服务器发送单个请求并获得单个响应,就像正常的函数调用一样。

    rpc Test(google.protobuf.Empty) returns( TestResponse) {};
    
  • 服务器流式 RPC,其中客户端向服务器发送请求并获取流以读回一系列消息。客户端从返回的流中读取,直到没有更多消息为止。gRPC 保证单个 RPC 调用中的消息顺序。

    rpc Test(google.protobuf.Empty) returns(stream TestResponse) {};
    
  • 客户端流式 RPC,其中客户端写入一系列消息并将它们发送到服务器,再次使用提供的流。一旦客户端完成了消息的写入,它就会等待服务器读取它们并返回它的响应。gRPC 再次保证了单个 RPC 调用中的消息顺序。

    rpc Test(stream TestRequest) returns(TestResponse) {};
    
    message TestResponse {
      string reply = 1;
    }
    
  • 双向流式 RPC,双方使用读写流发送一系列消息。这两个流独立运行,因此客户端和服务器可以按照他们喜欢的任何顺序读取和写入:例如,服务器可以在写入响应之前等待接收所有客户端消息,或者它可以交替读取消息然后写入消息,或其他一些读取和写入的组合。保留每个流中消息的顺序。

    rpc Test(stream TestRequest) returns(stream TestResponse) {};
    
    message TestResponse {
      string reply = 1;
    }
    

3、生成gRPC代理代码

项目根目录中执行如下命令,会在pkg目录下生成两个文件:grpcnacos.pb.go 和 grpcnacos_grpc.pb.go。

mkdir -p ../pkg/protocol/grpcnacos
protoc --go_out=../pkg/protocol/grpcnacos --go_opt=paths=source_relative     --go-grpc_out=../pkg/protocol/grpcnacos  --go-grpc_opt=paths=source_relative grpcnacos.proto

4、编写gRPC服务端程序

实现服务接口:

package service

import (
	"context"
	"go-naocs-demo/pkg/protocol/grpcnacos"
	emptypb "google.golang.org/protobuf/types/known/emptypb"
	"log"
)

type Service struct {
	grpcnacos.UnimplementedTestServer
}

func (s Service) Test(ctx context.Context, empty *emptypb.Empty) (*grpcnacos.TestResponse, error) {
	log.Println("收到一个请求")
	return &grpcnacos.TestResponse{Msg: "test"}, nil
}、

实现服务端注册:

package main

import (
	"fmt"
	"github.com/nacos-group/nacos-sdk-go/clients"
	"github.com/nacos-group/nacos-sdk-go/common/constant"
	"github.com/nacos-group/nacos-sdk-go/vo"
	"go-naocs-demo/internal/service"
	"go-naocs-demo/pkg/protocol/grpcnacos"
	"google.golang.org/grpc"
	"log"
	"net"
)

func main() {
	server := grpc.NewServer()
	service := service.Service{}
	grpcnacos.RegisterTestServer(server, service)
	port := GenFreePort()
	listen, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		log.Fatalf("监听端口:%d失败: %s", port, err.Error())
	}
	// 创建serverConfig
	// 支持多个;至少一个ServerConfig
	serverConfig := []constant.ServerConfig{
		{
			IpAddr: "127.0.0.1",
			Port:   8848,
		},
	}

	// 创建clientConfig
	clientConfig := constant.ClientConfig{
		NamespaceId:         "", // 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId。当namespace是public时,此处填空字符串。
		TimeoutMs:           50000,
		NotLoadCacheAtStart: true,
		LogLevel:            "debug",
	}

	// 创建服务发现客户端的另一种方式 (推荐)
	namingClient, err := clients.NewNamingClient(
		vo.NacosClientParam{
			ClientConfig:  &clientConfig,
			ServerConfigs: serverConfig,
		},
	)
	if err != nil {
		log.Fatalf("初始化nacos失败: %s", err.Error())
	}
	success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{
		Ip:          "127.0.0.1",
		Port:        uint64(port),
		ServiceName: "test-server",
		Weight:      10,
		Enable:      true,
		Healthy:     true,
		Ephemeral:   true,
		Metadata:    map[string]string{"name": "test"},
		ClusterName: "DEFAULT",       // 默认值DEFAULT
		GroupName:   "DEFAULT_GROUP", // 默认值DEFAULT_GROUP
	})
	if err != nil {
		log.Fatalf("注册服务失败: %s", err.Error())
	}

	log.Println("success: ", success)
	log.Printf("服务启动成功;PORT:%d\n", port)
	_ = server.Serve(listen)
}

// GenFreePort 获取一个空闲的端口;端口避免写死,因为要启动多个实例,测试负载均衡
func GenFreePort() int {
	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
	if err != nil {
		panic(err)
	}
	listen, err := net.ListenTCP("tcp", addr)
	if err != nil {
		panic(err)
	}
	defer listen.Close()
	return listen.Addr().(*net.TCPAddr).Port
}

5、编写client客户端

package main

import (
	"context"
	"fmt"
	"github.com/nacos-group/nacos-sdk-go/clients"
	"github.com/nacos-group/nacos-sdk-go/common/constant"
	"github.com/nacos-group/nacos-sdk-go/vo"
	"go-nacos-client-demo/pkg/protocol/grpcnacos"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/protobuf/types/known/emptypb"
	"log"
)

func main() {
	// 创建serverConfig
	// 支持多个;至少一个ServerConfig
	serverConfig := []constant.ServerConfig{
		{
			IpAddr: "127.0.0.1",
			Port:   8848,
		},
	}

	// 创建clientConfig
	clientConfig := constant.ClientConfig{
		// 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId。当namespace是public时,此处填空字符串。
		NamespaceId:         "",
		TimeoutMs:           5000,
		NotLoadCacheAtStart: true,
		LogLevel:            "debug",
	}

	// 创建服务发现客户端的另一种方式 (推荐)
	namingClient, err := clients.NewNamingClient(
		vo.NacosClientParam{
			ClientConfig:  &clientConfig,
			ServerConfigs: serverConfig,
		},
	)
	if err != nil {
		log.Fatalf("初始化nacos失败: %s", err.Error())
	}

	// SelectOneHealthyInstance将会按加权随机轮询的负载均衡策略返回一个健康的实例
	// 实例必须满足的条件:health=true,enable=true and weight>0
	instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{
		ServiceName: "test-server",
		GroupName:   "DEFAULT_GROUP",     // 默认值DEFAULT_GROUP
		Clusters:    []string{"DEFAULT"}, // 默认值DEFAULT
	})
	log.Printf("获取到的实例IP:%s;端口:%d", instance.Ip, instance.Port)
	conn, err := grpc.Dial(fmt.Sprintf("%s:%d", instance.Ip, instance.Port), grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("监听%s:%d失败:%s", instance.Ip, instance.Port, err.Error())
	}
	client := grpcnacos.NewTestClient(conn)
	res, err := client.Test(context.Background(), &emptypb.Empty{})
	if err != nil {
		log.Fatalf("调用TestClient失败: %s", err.Error())
	}
	log.Println(res.Msg)
}

6、运行结果

调用服务方法:test

服务端:

客户端:

posted @ 2022-08-01 17:40  小学程序员  阅读(2566)  评论(0编辑  收藏  举报