golang:Go-Micro+nacos实现微服务
参考:
一、启动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
服务端:
客户端: