Goframe学习笔记(五)微服务
微服务
微服务是什么
微服务架构是一种软件设计模式,在现代应用开发中被广泛应用。它通过将一个复杂的应用系统分解为一组相互独立、功能明确的服务来实现系统的模块化设计与管理。每一项服务都专注于特定的功能模块,并能够独立运行以完成其任务。这一架构的主要目标是将复杂的系统分解为多个小型化且易于管理的服务单元
微服务如何实现
构建微服务时需要各服务之间能够实现通信。可以选择RPC(远程过程调用)、RESTful API以及消息队列等多种通信方式来实现。GoFrame支持基于开源GRPC框架来进行开发。GRPC是一种高性能的RPC框架,由Google开发,并基于Http/2标准结合Protocol Buffers序列化协议实现了高效的接口定义机制。
环境准备
1.Protocol Buffer 编译器安装
下载地址:发布版本 · protocolbuffers / protobuf (GitHub . com)

解压后将bin目录添加到环境变量

查看是否成功安装

2.Go协议编译插件安装,用于代码生成
go install google.golang.org/protobuf/cmd/protoc-gen-go@vlatest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
重要:将 %GOPATH%\bin 也添加到环境变量
注:若下载失败需设置代理
go env -w GOPROXY=https://goproxy.io,direct
后续操作基于模块支持
go env -w GO111MODULE=on
3.vscode安装protoc扩展

快速开始
使用命令gf init demo -u创建一个初始项目
在 manifest\config\config.yaml添加如下配置
grpc:
name: "demo" # 服务名称
address: ":8000" # 自定义服务监听地址
logPath: "./log" # 日志存储目录路径
logStdout: true # 日志是否输出到终端
errorLogEnabled: true # 是否开启错误日志记录
accessLogEnabled: true # 是否开启访问日志记录
errorStack: true # 当产生错误时,是否记录错误堆栈
在manifest\protobuf下创建文件夹以及协议文件:user\v1\user.proto
目录结构如下:
在协议文件user.proto中添加如下内容:
syntax = "proto3"; // 指定proto3语法编译
package user; // 协议包名
option go_package = "demo/api/user/v1"; // 生成go代码的文件的包名
service User{
rpc SayHello(HelloReq) returns (HelloRes) {} // rpc接口
}
// 请求体
message HelloReq {
string UserName = 1;
}
// 响应体
message HelloRes {
string Msg = 1;
}
注:属性后的数字是在协议编译生成的二进制源码中的唯一标识符中使用以确保各属性的独特性。为了使数据量最小化建议尽可能小地设置这些数字并将它们控制在[1,15]范围内以避免不必要的复杂性和潜在冲突
在命令行输入如下指令根据协议文件生成Go代码:
gf gen pb
可以看到生成3个文件:

爆红是因为不会自动导入mod,手动输入下面指令导入。
go mod tidy
在controller\user\user.go 中添加SayHello方法的具体实现代码:
func (*Controller) SayHello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error) {
res = &v1.HelloRes{
Msg: "Hello " + req.UserName ,
}
return
}
将cmd\cmd.go内容修改为:
package cmd
import (
"context"
"demo/internal/controller/user"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/v2/os/gcmd"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := grpcx.Server.New() // 创建grpc服务器
user.Register(s) // 注册 user grpc服务
s.Run() // 启动服务器
return nil
},
}
)
在 %GOPATH%\src 下创建文件夹及文件:client\client.go 并输入一下内容:
package main
import (
v1 "client/user/v1"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
var (
ctx = gctx.New()
conn = grpcx.Client.MustNewGrpcClientConn("demo")
client = v1.NewUserClient(conn)
)
res, err := client.SayHello(ctx, &v1.HelloReq{UserName: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Msg)
}
将之前协议编译生成的api文件复制进文件夹:

然后输入指令:
go mod init
go mod tidy
运行服务端:

运行客户端:

可以看到回应:Hello World
注册发现
注冊是一種網路架構模式的存在,在於幫助各服務建立聯絡並實現通訊連線。在微服務架構中則扮演著關鍵角色,在此架構下服務通常會分佈於不同的主机、容器或虛拟機中,并且它們的位置與IP可能会發生 frequent 變化。Goframe社區提供的丰富的一階组件支援註冊功能。其中 most prominente is the widely known open-source 分布式 key-value storage system EtCD(EtCD)。EtCD is a versatile system 幾乎被廣泛應用於註冊 discovery 領域,并提供配置管理等多種功能场景;以下將示范如何使用 EtCD来进行註冊 discovery:
安装etcd:
访问途径:Releases(滑动至底部查看更多)

将下面这个目录添加到环境变量

验证是否安装成功:
etcd --version

在命令行输入如下指令启动etcd(etcd的默认地址为127.0.0.1:2379):
etcd
下载Goframe的etcd支持:
go get github.com/gogf/gf/contrib/registry/etcd/v2
cmd\cmd.go:
package cmd
import (
"github.com/gogf/gf/contrib/registry/etcd/v2" // 导入etcd包
"context"
"demo/internal/controller/user"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/v2/os/gcmd"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) // 注册etcd
s := grpcx.Server.New()
user.Register(s)
s.Run()
return nil
},
}
)
client\client.go:
package main
import (
"github.com/gogf/gf/contrib/registry/etcd/v2" // 导包
v1 "client/user/v1"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) // 注册etcd
var (
ctx = gctx.New()
conn = grpcx.Client.MustNewGrpcClientConn("demo")
client = v1.NewUserClient(conn)
)
res, err := client.SayHello(ctx, &v1.HelloReq{UserName: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Msg)
}
运行服务端:

运行客户端:

可以看到回应Hello World
负载均衡
作为计算机网络技术的一种,在实际应用中实现资源的有效分配是其核心功能之一。当一个网络系统面临高流量和高负载的挑战时,在客户端端引入负载均衡技术能够有效地将用户的请求均匀分配至多台服务器上,在这种情况下不仅能够提升响应速度还能有效缓解单个服务器的压力并保证系统的整体可靠性
在Goframe框架中,提供了如下几种负载均衡策略:
负载均衡调度策略(Round Robin):通过依次循环地发送客户端请求至服务集群中的各个节点,并均匀地分担请求量
随机访问(Random):将客户端请求依次随机分发给服务集群中的某个节点。
最少连接数(LeastConnection)定义为将客户端请求按顺序分配至当前服务集群中并行度最低的节点。
权重访问机制(Weight):在服务注册阶段配置权重值;客户端根据配置的权重值进行请求转发;从而实现资源优化分配。
示例:
由于同一台设备上的单个IP地址只能绑定给一个服务进程,因此若要在一台服务器上运行多个服务程序,则需要对GRPC服务器监听的本地IP地址进行相应设置
注意:如果需要进一步了解此功能的具体实现细节,请参考官方文档或技术手册
grpc:
name: "demo" # 服务名称
address: "" # 自定义服务监听地址 ------ 修改为空
logPath: "./log" # 日志存储目录路径
logStdout: true # 日志是否输出到终端
errorLogEnabled: true # 是否开启错误日志记录
accessLogEnabled: true # 是否开启访问日志记录
errorStack: true # 当产生错误时,是否记录错误堆栈
将cmd\cmd.go修改为:
package cmd
import (
"context"
"demo/internal/controller/user"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/v2/os/gcmd"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := grpcx.Server.New()
user.Register(s)
s.Run()
return nil
},
}
)
使用如下命令启动Server1:
gf run main.go -p 8000
注:-p 用于指定网络服务监听端口(区别于grpc服务监听端口)
新开一个终端并使用如下命令启动Server2:
gf run main.go -p 8001
将client\client.go修改为:
package main
import (
v1 "client/user/v1"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
var (
ctx = gctx.New()
// 随机访问
conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRandom())
// 轮询
//conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRoundRobin())
// 最小连接数
//conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithLeastConnection())
// 权重访问,服务注册时需要设置Weight参数。
//conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithWeight())
client = v1.NewUserClient(conn)
)
for i := 0; i <10; i++ {
res, err := client.SayHello(ctx, &v1.HelloReq{UserName: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Msg)
}
}
可以看到在10次随机访问时:Serve1接收到7次请求,Serve2接收到3次请求


