Goframe学习笔记(五)微服务

微服务

微服务是什么

微服务是一种架构风格,其中应用程序由多个小型、自治的的服务所构成,每个服务都围绕特定的功能,可以独立部署,扩展和管理。微服务架构的目的是将大型的应用程序拆分成多个小型、易于管理的服务以提高可维护性、灵活性和可扩展性。

微服务如何实现

微服务的实现需要服务之间能够相互通讯,可以使用RPC(远程过程调用)、RESFUL API、消息队列等方法实现,在GoFrame中可以基于开源框架GRPC进行实现。GRPC是谷歌开发的高性能RPC框架,基于Http/2标准和Protocol Buffers(protobuf)序列化协议,提供了简单易用的接口定义语言。

环境准备

1.Protocol Buffer 编译器安装

下载地址:Releases · protocolbuffers/protobuf (github.com)

52cd84ae66b64451962462e1086a01a7.png

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

查看是否成功安装

916bbe7e9a7f432faad0e356189d9ff7.png

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扩展

6a6947c2c7a24a51ae60bef45681a908.png

快速开始

使用命令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个文件:

fba2d9eb8c05480a851ae5b1d4a9b9b9.png

爆红是因为不会自动导入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文件复制进文件夹:

d027b5460a5e4af6a6a9068cfaeb6fd6.png

然后输入指令:

go mod init 
go mod tidy

运行服务端:

a1da1f81e405421a81a30d7dedb80bec.png

运行客户端:

aa0160ce872a4f689e89113ba9597bd3.png

可以看到回应:Hello World

注册发现

注册发现是一种网络架构模式,用于帮助服务找到彼此并进行通讯。在微服务架构中尤为重要,因为服务通常分布在不同的主机、容器或虚拟机中,并且它们的位置和IP可能会频繁发生改变。Goframe社区组件提供了多种注册发现的组件如 etcd, zookeeper, polaris 等。etcd是一个开源分布式键值对存储系统,常用于注册发现,配置管理等场景,下面是使用etcd进行注册发现的示例:

安装etcd:

下载地址:Releases · etcd-io/etcd (github.com)(滑下去...)

dcc0e584fc1e47ea96083e5b3b36c8b4.png

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

e2857909554a463ca0adcd425fcbf703.png

验证是否安装成功:

etcd --version

8fcb3a157f444a8ca8db3bcc1fcb0e1d.png

在命令行输入如下指令启动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)
}

运行服务端:

3d7ce7a556d843b2979cd784b6b486c5.png

运行客户端:

7002ab303e4f41c7b96bae946f7d57e2.png可以看到回应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次请求

2e42fba1b7254c00bd432aad45deed3b.png

b9494fc84074420d96d69a687cc1f2fd.png

  • 40
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值