什么是微服务(microservice)?这是企业界正在向计算界提出的问题。一个产品的可持续性取决于它的可修改程度。
大型产品如果不能正常维护,就需要在某个时间点停机维护。而微服务架构用细化的服务取代了传统的单体服务,这些服务定义了明确的 RPC 或消息驱动的 API 边界。
微服务架构有别于更为传统的单体式方案,可将应用拆分成多个核心功能。每个功能都被称为一项服务,可以单独构建和部署,这意味着各项服务在工作(和出现故障)时不会相互影响。
微服务带来了以下好处:
- 每个服务都可以由专注于此服务的团队独立开发。小团队可以通过在一组小的功能上工作来进行并行迭代。
- 开发人员可以自由选择开发技术,对新的开发人员来说,可扩展性很强。
- 微服务架构可以使每个微服务独立部署。对系统的单个组件支持持续集成(CI)和持续交付(CD)。
- 微服务架构使得每个服务都可独立扩展。利用松耦合的架构提供更轻松的软件替换。
- 微服务架构不与特定的技术相联系。
在谈论微服务时,编排和服务发现是微服务中非常重要的部分。像 Kubernetes 这样的工具可以用来编排和协调 Docker 容器。一般来说,微服务的最佳实践就是每个微服务有一个 Docker 容器。
服务发现是对微服务实例的 IP 地址的自动检测。这种方式消除了硬编码 IP 地址的潜在威胁,硬编码会导致服务之间缺乏联系。
下图描绘了单体架构和微服务架构的结构图。
图的左边就是单体架构的示意图,如图所示:单体架构将所有的功能(如 UI、日志、数据层、系统逻辑、数据库等)都集成在一个系统中,像是一个紧耦合的架构。
相反,微服务是独立的实体,每个功能都是单独的服务,如日志服务、文件服务、系统逻辑服务等,更易于修改和替换,每个服务都可以通过各种远程传输机制进行沟通,如 HTTP、REST 或者 RPC。服务之间的交换的数据格式可以是 JSON 或者 Protocol buffers, 微服务还可以处理各种请求点,如 UI 和 API 客户端。
微服务可以被任何语言实现(Java、Go、Python、 Rust、 NodeJS 等),因为其有着松耦合的性质,每个独立的服务还可以今后被任何其他新技术或业务所需要的技术所替换。
关于微服务的相关知识就简单介绍到这,感兴趣的同学可以看看文末的推荐阅读部分,都是非常好的微服务学习资料。
优秀微服务框架一览
Java 社区中有着非常著名的框架用于构建微服务系统。如:
- Spring: Spring Boot 是用于编写微服务的流行 Java 框架。
- Spring Cloud:基于 Spring Boot,为微服务体系开发中的架构问题,提供了一整套的解决方案——服务注册与发现,服务消费,服务保护与熔断,网关,分布式调用追踪,分布式配置管理等。
- Dropwizard:一个开源的 RESTful 快速开发框架,对微服务的开发也极其友好,而且性能很强
- Micronaut:是一个现代的、基于 JVM 的全栈微服务框架,旨在构建模块化、易于测试的微服务应用程序
- Apache Dubbo:由阿里巴巴开源的分布式服务化治理框架,是一款微服务框架,为大规模微服务实践提供高性能 RPC 通信、流量治理、可观测性等解决方案,涵盖 Java、Golang 等多种语言 SDK 实现。
以上都是非常有名的微服务框架,在 Go 语言中,也有很多著名的框架(go-kit、go-kratos、go-zero 等), Go Micro 也是其中之一,截止发文 Github Star 数量达到了 19.6k。
Go Micro 是一个基于 RPC 的可插拔库,它提供了在 Go 中编写微服务的基本构建块。它使用 consul 实现服务发现,但可以换成 etcd、zookeeper 或任何能够满足该接口的其他实现。通过 http 或使用 proto-rpc 或 json-rpc 进行通信,
Go Micro 解决了构建可扩展系统的关键要求。它采用微服务架构模式并将其转换为一组充当平台构建块的工具。Micro 处理分布式系统的复杂性,并提供开发人员已经理解的简单抽象。
Go Micro 提供了 RPC 实现和事件驱动架构(EDAs),可以向其添加任何外部功能。如果你想换掉底层技术,代码重写率为零。
Go Micro 特点
Go Micro 的主要特点有:
- RPC Client/Server:基于 RPC 的请求/响应,支持双向流。为同步通信提供了一个抽象层,向一个服务提出的请求将被自动处理、负载均衡、拨号和流化。
- 服务发现: 自动服务注册和名称解析。服务发现是微服务开发的核心。当服务 A 需要与服务 B 对话时,它需要该服务的位置。默认的发现机制(zeroconf 系统)是多播 DNS(mdns)机制。
- 负载均衡:客户端负载均衡建立在服务发现的基础上。一旦我们有了一个服务的任何数量的实例的地址,我们现在需要一种方法来决定哪个节点的路由。我们使用随机散列的负载均衡来提供跨服务的均匀分布,并在出现问题时重试一个不同的节点。
- 信息编码:基于内容类型的动态信息编码。客户端和服务器将与内容类型一起使用编解码器,为你无缝编码和解码 Go 类型。任何种类的消息都可以被编码并从不同的客户端发送。客户端和服务器默认会处理这个问题。这包括默认的 protobuf 和 json 格式。
- 信息同步:发布/订阅(PubSub) 是作为异步通信和事件驱动架构的第一类公民而建立的。事件通知是微服务开发的一个核心模式。默认的消息传递系统是一个 HTTP 事件消息代理。
- 事件流: PubSub 对于异步通知来说是很好的,但对于更高级的用例,事件流是首选。提供持久性存储,从网络中的 offset(片偏移量) 和 acking(确认字符) 中进行消费。 Go Micro 包括对NATS Jetstream和 Redis 流的支持。
- 同步化:分布式系统通常以最终一致的方式构建。对分布式锁和领导节点的支持是作为 Sync 接口建立的。当使用最终一致的数据库或调度时,使用 Sync 接口。
- 可插拔接口:Go Micro 对每个分布式系统的抽象都使用了 Go 接口。正因为如此,这些接口是可插拔的,使 Go Micro 与运行时间无关。您可以在底层使用任何可用技术。 例如用于翻译的编解码器,用于存储系统的 brokers。
- 插件地址:https://github.com/go-micro/plugins
接下来,让我们动手写一个服务。
顶层的服务接口是构建服务的主要组件。它把 Go Micro 的所有底层包都包装成一个方便的接口。
type Service interface {
Init(...Option)
Options() Options
Client() client.Client
Server() server.Server
Run() error
String() string
}
go-micro 安装
使用下面的命令安装最新的 go-micro v4.9
go install go-micro.dev/v4@latest
本文在 Windows 11 安装成功如下:
使用 micro.NewService
创建一个新服务:
package main
import (
"go-micro.dev/v4"
)
// 创建新服务
service := micro.NewService(
micro.Name("HelloWorld")
)
// 初始化 flags
service.Init()
// 启动服务
service.Run()
其他选项可以在创建时传入:
service := micro.NewService(
micro.Name("greeter"),
micro.Version("latest"),
)
Options 全部参数如下图:
接下来,我们将以循序渐进的方式建立一个简易的提供加解密服务的 Go 微服务项目。首先为了创建微服务,需要前期设计几个实体:
- 定义服务的 RPC 方法的 protocol buffer 文件
- 具体方法实现的 handler 文件
- 一个公开 RPC 方法的服务器 server
- 一个可以发出 RPC 请求并获得响应结果的客户端 client
首先,为了将 protocol buffer 文件编译为 Go 包,需要先安装 protoc
,下载点此处,选择你对应的系统。
本文是以 Win 进行的示例开发,下载的是 protoc-21.9-win32.zip
,解压完后添加到系统环境变量,如图所示:
然后安装 proto-gen-micro
,使用如下命令:
go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest
接下来,创建我们的项目目录 encryptService
文件夹,然后在其中创建一个 proto
目录,新建一个 encryption.proto
文件,写入如下内容:
syntax = "proto3";
package main;
option go_package="./proto";
service Encrypter {
rpc Encrypt(Request) returns (Response) {}
rpc Decrypt(Request) returns (Response) {}
}
message Request {
string message = 1;
string key = 2;
}
message Response {
string result = 2;
}
上面的文件命名了一个 Encrypter
的服务,有着 Request
和 Response
两条消息。这两条信息是用来请求加密和解密的。
- 首行前置的文件语法是
proto3
- 请求消息
Request
有两个字段,分别为:message
(需要加密的信息)和key
(密钥)。客户端使用这些字段来发送一个plaintext/ciphertext
消息 - 响应消息
Response
只有一个字段result
:它是加密/解密过程的结果。加密Encypter
服务有两个 RPC 方法:Encrypt
和Decypt
,两者都是接收一个请求,然后返回一个响应。
接着我们可以通过编译 .proto
文件来生成 Go 文件,执行如下命令:
protoc --proto_path=. --micro_out=. --go_out=. proto/encryption.proto
执行成功后会在我们的项目 encryptService/proto
目录下自动生成两个文件:
- encryption.pb.go
- encryption.pb.micro.go
文件成功生成后如图:
这些自动生成的文件不需要我们手动进行修改。
encryptService
微服务端新建 utils.go 文件
接下来,我们新建一个 utils.go
文件,定义字符串 AES 加解密的方法,如下:
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
)
var initVector = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05}
// 字符串加密函数
func EncryptString(key, text string) string {
block, err := aes.NewCipher([]byte(key))
if err != nil {
panic(err)
}
plaintext := []byte(text)
cfb := cipher.NewCFBEncrypter(block, initVector)
cipertext := make([]byte, len(plaintext))
cfb.XORKeyStream(cipertext, plaintext)
return base64.StdEncoding.EncodeToString(cipertext)
}
// 解密函数
func DecryptString(key, text string) string {
block, err := aes.NewCipher([]byte(key))
if err != nil {
panic(err)
}
cipertext, _ := base64.StdEncoding.DecodeString(text)
cfb := cipher.NewCFBEncrypter(block, initVector)
plaintext := make([]byte, len(cipertext))
cfb.XORKeyStream(plaintext, cipertext)
return string(plaintext)
}
新建 handler.go 文件
接着新建一个 handler.go
文件,在这个文件内为我们的服务定义业务逻辑:
- 首先定义一个
Encrypt
结构体 - 增加两个方法
Encrypt
和Decrypt
处理 RPC 请求
package main
import (
"context"
"encryptService/proto"
)
type Encrypter struct{}
// 将消息加密后发送请求
func (g *Encrypter) Encrypt(ctx context.Context, req *proto.Request, rsp *proto.Response) error {
rsp.Result = EncryptString(req.Key, req.Message)
return nil
}
// 将密文解密后返回相应
func (g *Encrypter) Decrypt(ctx context.Context, req *proto.Request, rsp *proto.Response) error {
rsp.Result = DecryptString(req.Key, req.Message)
return nil
}
如上的代码,在 Encrypter
结构体中的两个方法 Encrypt
和 Decrypt
,
func (g *Encrypter) Encrypt(ctx context.Context, req *proto.Request, rsp *proto.Response)
func (g *Encrypter) Decrypt(ctx context.Context, req *proto.Request, rsp *proto.Response)
两个方法都是接收一个 context
对象、一个 RPC 请求对象、和一个 RPC 响应对象。每个方法所做的工作是调用各自的实用函数,并将响应对象返回为一个结果 rsp.Result
。
值得一提的是,Encrypt
加密和 Decrypt
解密会被映射到 protocol buffer 文件中的 RPC 方法中,如下方法:
rpc Encrypt(Request) returns (Response) {}
rpc Decrypt(Request) returns (Response) {}
新建 main.go 文件
紧接着,我们在 encryptService
根目录下,新建一个 main.go
文件,根据上一篇文章中对 go-micro 框架中创建微服务实例的方法,我们写入如下内容:
package main
import (
"encryptService/proto"
"fmt"
"go-micro.dev/v4"
)
func main() {
// 创建一个新服务
service := micro.NewService(
micro.Name("encrypter"),
)
// 初始化
service.Init()
proto.RegisterEncrypterHandler(service.Server(),
new(Encrypter))
// 启动服务
if err := service.Run(); err != nil {
fmt.Println(err)
}
}
micro.NewService
用于新建一个微服务,然后一个service
对象- 运行
service.Init()
收集命令行参数 - 通过
proto.RegisterEncrypterHandler
方法注册服务,这个方法是由 protocol buffer 编译器动态生成的。 - 最后,
service.Run
启动服务
运行 encryptService 服务
我们来看一下如何正常启动整个微服务实例:
- 执行
go mod init encrypService
在执行这一步出现问题,比如遇到如下错误:
go: encryptClient/proto imports
go-micro.dev/v4/api: go-micro.dev/v4/api@v1.18.0: parsing go.mod:
module declares its path as: github.com/micro/go-micro
but was required as: go-micro.dev/v4/api
使用如下命令进行解决,得到 v4 版:
go get go-micro.dev/v4
成功截图如下:
再来执行 go mod tidy
,执行成功如下图,最后会自动生成 go.mod
和 go.sum
文件。:
使用 go build .
编译整个项目,编译成功后在 Win 下会生成一个 .exe
的可执行文件。
编译完整个项目后的目录结构如下:
最后,运行我们的 encrypService 服务,通过使用 ./encryptService.exe
命令进行启动,成功如下:
正如你从服务器终端看到的那样,go-micro 利用一个 info Transport
和一个消息代理 info Broker
成功启动了一个微服务。此时,我们可以通过在浏览器中访问 http://127.0.0.1:58184/
查看相关信息:
现在,客户端可以向这些端口发送请求,但目前这些服务并不那么有用,因为我们还没有编写客户端来消费这些 API,接下来尝试建立一个 encryptClient
客户端,学习如何连接到这个服务器。
encryptClient
客户端同理,通过 Go Micro 框架构建客户端,通过 RPC 调用上面的服务端,接下来就是按步骤编写客户端的方法。新建一个 encryptClient
目录,然后在这个目录下建立一个 proto
文件夹。客户端项目结构图如下:
编写 proto 文件
首先,我们需要知道服务器和客户端应该同意使用相同的 protocol buffers (协议缓冲区)。同样地,Go Micro 希望服务器和客户端使用相同的 .proto
文件,在上面的的例子是 encryption.proto
文件。
在 encryptClient/proto
下创建一个和服务端相同的 encryption.proto
文件:
syntax = "proto3";
package main;
option go_package="./proto";
service Encrypter {
rpc Encrypt(Request) returns (Response) {}
rpc Decrypt(Request) returns (Response) {}
}
message Request {
string message = 1;
string key = 2;
}
message Response {
string result = 2;
}
类似地,使用 protoc -I=. --micro_out=. --go_out=. proto/encryption.proto
命令执行生成 Go 文件,如图:
编写 main.go
package main
import (
"context"
"encryptClient/proto"
"fmt"
"go-micro.dev/v4"
)
func main() {
// 创建新服务
service := micro.NewService(micro.Name("encrypter.client"))
// 初始化客户端,解析命令行参数
service.Init()
// 创建新的加密服务实例
encrypter := proto.NewEncrypterService("encrypter", service.Client())
// 调用 encrypter 加密服务
rsp, err := encrypter.Encrypt(context.TODO(), &proto.Request{
Message: "Hello world",
Key: "111023043350789514532147",
})
if err != nil {
fmt.Println(err)
}
// 打印响应
fmt.Println(rsp.Result)
// 调用解密 decrypter 服务
rsp, err = encrypter.Decrypt(context.TODO(), &proto.Request{
Message: rsp.Result,
Key: "111023043350789514532147",
})
if err != nil {
fmt.Println(err)
}
// 打印解密结果
fmt.Println(rsp.Result)
}
service := micro.NewService(micro.Name("encrypter.client")
新建服务实例- 调用加密服务时,传入
"Hello world"
文本和一个密钥"111023043350789514532147"
fmt.Println(rsp.Result)
,最后在终端打印rsp.Result
- 调用解密服务时,传入加密的结果
rsp.Result
和同一个密钥"111023043350789514532147"
- 然后打印解密结果
fmt.Println(rsp.Result)
编写完成后,执行 go mod init encryptClient
,如图:
接着,使用 go mod tidy
,自动生成 go.sum
文件。
然后执行编译 go build .
,生成 encryptClient.exe
文件。
最后执行客户端打印,终端输出 Hello world 的 AES 加密文本 8rqECLu6rQTfkCM=
和解密后的明文 Hello world
:
$ ./encryptClient.exe
8rqECLu6rQTfkCM=
Hello world
执行过程,如图所示:
这个过程证明了我们的加解密微服务的 RPC 调用时成功的,不过也能看到通过使用 Go Micro 框架,我们能通过几行代码,就创建了微服务和客户端。
本文通过实现加解密操作展示了一个微服务应用的开发过程。通过编写服务端,成功运行了一个微服务实例,该服务能够通过加密请求得到一个加密后的密文,通过解密请求将消息进行解密,并返回明文结果。然后通过编写客户端向服务端进行 RPC 调用,成功将 Hello world 字符串进行加密并打印出密文和明文的结果。
这个过程充分展示了 Go Micro 框架的便利性,至于 Go Mirco 框架还有更多的知识等着大家学习。希望本文能起到抛砖引玉的效果,让更多看到文章的人加入学习和微服务的开发当中。
这里是宇宙之一粟,下一篇文章见!
宇宙古今无有穷期,一生不过须臾,当思奋争。
希望本文能对你有所帮助,如果喜欢本文,可以点个赞或关注。
推荐阅读: