在前面使用gRPC一步一步搭建使用流程,但是以上这些具体的方案都是为了解决微服务实践过程中具体的某个问题而提出的,实现微服务架构的项目开发。在具体的项目开发过程中,开发者聚焦的是业务逻辑的开发和功能的实现,大量的环境配置,调试搭建等基础性工作会耗费相当一部分的精力,因此有必要将微服务架构中所涉及到的,相关的解决方案做集中管理和维护。这就是我们要学习的Micro。

Go-Micro

介绍

Micro是一个简化分布式开发的微服务生态系统,该系统为开发分布式应用程序提供了高效,便捷的模块构建。主要目的是简化分布式系统的开发。micro是一个微服务工具包,是由一系列的工具包组成的,而Go-Micro是用在Go中编写微服务的插件式RPC框架。它提供了用于服务发现,客户端负载平衡,编码,同步和异步通信库。

特性

  • Registry:主要负责服务注册和发现功能。我们之前学习过的consul,就可以和此处的Registry结合起来,实现服务的发现功能。
  • Selector:selector主要的作用是实现服务的负载均衡功能。当某个客户端发起请求时,将首先查询服务注册表,返回当前系统中可用的服务列表,然后从中选择其中一个节点进行查询,保证节点可用。
  • Broker:Broker是go-micro框架中事件发布和订阅的接口,主要是用消息队列的方式实现信息的接收和发布,用于处理系统间的异步功能。
  • Codec:go-micro中数据传输过程中的编码和解码接口。go-micro中有多重编码方式,默认的实现方式是protobuf,除此之外,还有json等格式。
  • Transport:go-micro框架中的通信接口,有很多的实现方案可以选择,默认使用的是http形式的通信方式,除此以外,还有grpc等通信方式。
  • Client和Server:分别是go-micro中的客户端接口和服务端接口。client负责调用,server负责等待请求处理。

安装

1.安装consul

consul环境是go-micro默认使用的服务发现方式。

2.安装protobuf和依赖

protobuf在前面已经安装过,就不再提及

3.Go-micro安装
go get github.com/micro/go-micro

创建微服务

服务的定义

在micro框架中,服务用接口来进行定义,服务被定义为Service,完整的接口定义如下:

type Service interface {
	Init(...Option)
	Options() Options
	Client() client.Client
	Server() server.Server
	Run() error
	String() string
}

在该接口中,定义了一个服务实例具体要包含的方法,分别是:Init、Options、Client、Server、Run、String等6个方法。

初始化服务实例

micro框架,除了提供Service的定义外,提供创建服务实例的方法供开发者调用:

service := micro.NewService()

如上是最简单一种创建service实例的方式。NewService可以接受一个Options类型的可选项参数。NewService的定义如下:

func NewService(opts ...Option) Service {
	return newService(opts...)
}
Options可选项配置

关于Options可配置选项,有很多可以选择的设置项。micro框架包中包含了options.go文件,定义了详细的可选项配置的内容。最基本常见的配置项有:服务名称,服务的版本,服务的地址,服务:

//服务名称
func Name(n string) Option {
	return func(o *Options) {
		o.Server.Init(server.Name(n))
	}
}

//服务版本
func Version(v string) Option {
	return func(o *Options) {
		o.Server.Init(server.Version(v))
	}
}

//服务部署地址
func Address(addr string) Option {
	return func(o *Options) {
		o.Server.Init(server.Address(addr))
	}
}

//元数据项设置
func Metadata(md map[string]string) Option {
	return func(o *Options) {
		o.Server.Init(server.Metadata(md))
	}
}

完整的实例化对象代码如下所示:

func main() {
	//创建一个新的服务对象实例
	service := micro.NewService(
		micro.Name("helloservice"),
		micro.Version("v1.0.0"),
	)
}

开发者可以直接调用micro.Name为服务设置名称,设置版本号等信息。在对应的函数内部,调用了server.Server.Init函数对配置项进行初始化。

定义服务接口,实现服务业务逻辑

在前面的文章中,已经学习掌握了使用protobuf定义服务接口,并对服务进行具体实现。使用protobuf定义服务接口并自动生成go语言文件,需要经过以下几个步骤,我们通过示例进行说明:

我们依然通过案例来讲解相关的知识点:在学校的教务系统中,有学生信息管理的需求。学生信息包含学生姓名,学生班级,学习成绩组成;可以根据学生姓名查询学生的相关信息,我们通过rpc调用和学生服务来实现该案例。

定义.proto文件

使用proto3语法定义数据结构体和服务方法。具体定义内容如下:

syntax = 'proto3';
package message;

//学生数据体
message Student {
    string name = 1; //姓名
    string classes = 2; //班级
    int32 grade = 3; //分数
}

//请求数据体定义
message StudentRequest {
    string name = 1;
}

//学生服务
service StudentService {
    //查询学生信息服务
    rpc GetStudent (StudentRequest) returns (Student);
}
2、编译.proto文件

在原来学习gRPC框架时,我们是将.proto文件按照grpc插件的标准来进行编译。而现在,我们学习的是go-micro,因此我们可以按照micro插件来进行编译。micro框架中的protobuf插件,我们需要单独安装。

  • 安装micro框架的protobuf插件
go get github.com/micro/protobuf/proto
  go get github.com/micro/protobuf/protoc-gen-go
  • 指定micro插件进行编译
protoc --go_out=plugins=micro:. message.proto
3、编码实现服务功能

在项目目录下,实现StudentService定义的rpc GetStudent功能。新建server.go文件,具体实现如下:

//学生服务管理实现
type StudentManager struct {
}

//获取学生信息的服务接口实现
func GetStudent(ctx context.Context, request *message.StudentRequest, response *message.Student) error {

	studentMap := map[string]message.Student{
		"davie":  message.Student{Name: "davie", Classes: "软件工程专业", Grade: 80},
		"steven": message.Student{Name: "steven", Classes: "计算机科学与技术", Grade: 90},
		"tony":   message.Student{Name: "tony", Classes: "计算机网络工程", Grade: 85},
		"jack":   message.Student{Name: "jack", Classes: "工商管理", Grade: 96},
	}

	if request.Name == "" {
		return errors.New(" 请求参数错误,请重新请求。")
	}

	student := studentMap[request.Name]
	
	if student.Name != "" {
		response = &student
	}
	return errors.New(" 未查询当相关学生信息 ")
}

运行服务

在之前的学习过程中,我们是通过自己编写server.go程序,注册服务,并实现请求的监听。现在,我们用micro框架来实现服务的运行。完整的运行服务的代码如下:

func main() {
	service := micro.NewService(micro.Name("student_service"), micro.Version("1.0.0"))

	// 初始化
	service.Init()

	// 注册服务
	message.RegisterStudentServiceHandler(service.Server(), new(StudentManager))

	// 启动
	err := service.Run()
	if err != nil {
		log.Fatal(err)
	}
}

客户端调用

客户端可以构造请求对象,并访问对应的服务方法。具体方法实现如下:

func main() {
	service := micro.NewService(
		micro.Name("student.client"),
	)
	service.Init()

	studentService := message.NewStudentServiceClient("student_service", service.Client())

	res, err := studentService.GetStudent(context.TODO(), &message.StudentRequest{Name: "davie"})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(res.Name)
	fmt.Println(res.Classes)
	fmt.Println(res.Grade)
}

运行结果

运行服务端

运行server.go文件中的main函数,服务注册成功,并输出如下日志:

2019/08/26 22:50:18 Transport [http] Listening on [::]:54343
2019/08/26 22:50:18 Broker [http] Connected to [::]:54344
2019/08/26 22:50:19 Registry [mdns] Registering node: student_service-346b454c-998d-4e85-a8cc-befbc0198eef
运行客户端

客户端负责发起请求和功能调用,运行client.go程序,程序正常输出。

etcd undefined: resolver.BuildOption

若在运行是发生以上错误

注册服务到consul

默认注册到mdns

在我们运行服务端的程序时,我们可以看到Registry [mdns] Registering node:xxx这个日志,该日志显示go-micro框架将我们的服务使用默认的配置注册到了mdns中。mdns是可简单翻译为mdns,是go-micro的默认配置选项。

注册到consul

在前面的微服务理论课程中,我们已经学习过consul。consul是服务注册与发现的组件,因此,如果我们本地系统已经安装了consul环境,我们可以选择将我们的服务注册到consul中。指定注册到consul时,需要先将consul进行启动。

启动consul
启动命令如下:

consul agent -dev

通过上述命令,我们可以在终端中启动consul。

通过实例化一个registry注册到consul

consulRegistry := consul.NewRegistry(registry.Addrs("localhost:8500"))

service := micro.NewService(micro.Name("student_service"), micro.Version("1.0.0"), micro.Registry(consulRegistry))

完整代码

server.go

package main

import (
	"GoCode/example/goMicroDemo/message"
	"context"
	"errors"
	"fmt"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-plugins/registry/consul"
	"log"
)

//学生服务管理实现
type StudentManager struct {
}

func (s StudentManager) GetStudent(c context.Context, request *message.StudentRequest, student *message.Student) error {
	studentMap := map[string]message.Student{
		"davie":  {Name: "davie", Classes: "软件工程专业", Grade: 80},
		"steven": {Name: "steven", Classes: "计算机科学与技术", Grade: 90},
		"tony":   {Name: "tony", Classes: "计算机网络工程", Grade: 85},
		"jack":   {Name: "jack", Classes: "工商管理", Grade: 96},
	}

	if request.Name == "" {
		return errors.New(" 请求参数错误,请重新请求。")
	}

	fmt.Println(request.Name)
	studentInfo := studentMap[request.Name]

	fmt.Println(studentInfo)

	if studentInfo.Name != "" {
		*student = studentInfo
		return nil
	}
	return errors.New(" 未查询当相关学生信息 ")
}


func main() {
	consulRegistry := consul.NewRegistry(registry.Addrs("localhost:8500"))

	service := micro.NewService(micro.Name("student_service"), micro.Version("1.0.0"), micro.Registry(consulRegistry))

	// 初始化
	service.Init()

	// 注册服务
	message.RegisterStudentServiceHandler(service.Server(), new(StudentManager))

	// 启动
	err := service.Run()
	if err != nil {
		log.Fatal(err)
	}
}

client.go

package main

import (
	"GoCode/example/goMicroDemo/message"
	"context"
	"fmt"
	"github.com/micro/go-micro"
)

func main() {

	service := micro.NewService(
		micro.Name("student_client"),
	)
	service.Init()

	studentService := message.NewStudentServiceClient("student_service", service.Client())

	res, err := studentService.GetStudent(context.TODO(), &message.StudentRequest{Name: "davie"})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(res.Name)
	fmt.Println(res.Classes)
	fmt.Println(res.Grade)
}

参考链接

Golang - 100天从新手到大师

源码地址

源码地址