微服务
微服务是什么
微服务是一种架构风格,其中应用程序由多个小型、自治的的服务所构成,每个服务都围绕特定的功能,可以独立部署,扩展和管理。微服务架构的目的是将大型的应用程序拆分成多个小型、易于管理的服务以提高可维护性、灵活性和可扩展性。
微服务如何实现
微服务的实现需要服务之间能够相互通讯,可以使用RPC(远程过程调用)、RESFUL API、消息队列等方法实现,在GoFrame中可以基于开源框架GRPC进行实现。GRPC是谷歌开发的高性能RPC框架,基于Http/2标准和Protocol Buffers(protobuf)序列化协议,提供了简单易用的接口定义语言。
环境准备
1.Protocol Buffer 编译器安装
下载地址:Releases · 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
目录结构如下:
├── 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可能会频繁发生改变。Goframe社区组件提供了多种注册发现的组件如 etcd, zookeeper, polaris
等。etcd是一个开源分布式键值对存储系统,常用于注册发现,配置管理等场景,下面是使用etcd
进行注册发现的示例:
安装etcd:
下载地址:Releases · etcd-io/etcd (github.com)(滑下去...)
将下面这个目录添加到环境变量
验证是否安装成功:
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框架中,提供了如下几种负载均衡策略:
轮询(RoundRobin):将客户端请求依次轮流转发给服务集群中的节点,平均分担请求。
随机访问(Random):将客户端请求依次随机分发给服务集群中的某个节点。
最小连接数(LeastConnection):将客户端请求依次分发给当前服务集群中最小并发连接的节点。
权重访问(Weight):服务端服务注册时设置Weight参数,客户端将按Weight参数转发请求,能者多劳。
示例:
因为同一个端口只能由一个服务占用,所以要同一台机器上启动多个服务端,需要修改grpc服务器监听的端口
将manifest\config\config.yaml
中grpc的配置地址中的端口修改为空,它会随机分配端口,配置如下:
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次请求