1. 什么是微服务(what)
将一个大型的软件系统,按业务拆解成多个模块,各模块之间通过rpc通信调用。以一个网盘系统为例,可以分为以下几个模块,一个模块就可以单独部署一个微服务
2. 为什么要用微服务(why)
对于那些大型且复杂度极高的软件,微服务利用分而治之的方法,把难啃的大石块拆解成一些轻而易举的小石头,使用微服务有以下好处:
2.1 微服务的优点
-
模块间低耦合,模块内高内聚
-
各个模块可以独立开发、部署、维护
-
提高系统的整体可用性
-
每个服务都可独立扩展,集群部署
2.2 微服务的缺点
-
增加了程序间相互调用的复杂度(rpc)
-
增加了运维、部署的工作量和复杂度
-
日志难以统一收集处理
-
引入了较多的非模块代码
2.3 什么时候用微服务
不要为了微服务而微服务,如果系统使用微服务的架构带来的坏处比好处多的话,那么微服务也就没什么意义了
3. go-micro实现微服务(How)
以外卖平台的用户系统和订单系统的微服务为例,来说明如何通过go-micro来实现微服务。业务场景是根据订单ID生成订单信息,订单信息中包含有用户信息,而用户信息需要调用用户系统的微服务来获取用户的地址和电话号码等信息
3.1 项目目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
|____mircoservice
| |____order //订单微服务
| | |____proto
| | | |____order.pb.micro.go
| | | |____order.pb.go
| | | |____order.proto
| | |____orderservice.go
| |____go.mod
| |____user //用户微服务
| | |____proto
| | | |____user.pb.micro.go
| | | |____user.pb.go
| | | |____user.proto
| | |____userservice.go
| |____go.sum
|
3.2 用户微服务
3.2.1 proto 接口协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
syntax = "proto3";
// 定义包名
package proto;
// 定义user服务的接口
service UserSrv {
// 获取用户账号信息
rpc GetAccount(GetAccountRequest) returns (Account) {}
}
// 定义获取账号信息的请求消息
message GetAccountRequest {
int32 id = 1; // 用户id
}
// 定义用户账号消息
message Account {
int32 id = 1; // id
string username = 2; // 账号
string address = 3; // 地址
string phone =4; // 电话
}
|
3.2.2 生成go代码和微服务代码
1
|
protoc --micro_out=. --go_out=. *.proto
|
3.3.3 实现协议中定义的GetAccount方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package main
import (
"context"
micro "github.com/micro/go-micro"
"mircoservice/user/proto"
)
type UserService struct {
}
func (u *UserService)GetAccount(
ctx context.Context,
req *proto.GetAccountRequest,
resp *proto.Account)error{
id := int32(req.Id)
resp.Id = id
resp.Username = "小强"
resp.Address = "地球"
resp.Phone = "8848"
return nil
}
func main() {
service := micro.NewService(
micro.Name("go.micro.api.userservice"),
)
service.Init()
proto.RegisterUserSrvHandler(service.Server(),new(UserService))
if err := service.Run(); err !=nil{
panic(err)
}
}
|
3.3.4 调试微服务接口
go-micro
框架为我们提供了两种调试方式
- micro web web界面的方式
- Micro call 服务名 接口 参数
首先需要安装go-micro运行时组件
1
|
go get github.com/micro/micro
|
1
2
3
4
5
6
7
|
➜ ~ micro call go.micro.api.userservice UserSrv.GetAccount '{"id":188}'
{
"id": 188,
"username": "小强",
"address": "地球",
"phone": "8848"
}
|
3.3 订单服务
3.3.1 proto 接口协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
syntax = "proto3";
// 定义包名
package proto;
// 定义订单服务的接口
service OrderSrv {
// 获取订单信息
rpc GetOrder(GetOrderRequest) returns (Order) {}
}
// 定义获取订单的请求消息
message GetOrderRequest {
int32 id = 1; // 订单id
}
// 定义订单消息
message Order {
int32 id = 1; // 订单id
string name = 2; // 商品名
double price = 3; // 价格
string username = 4; //用户
string address = 5; // 用户地址
string phone =6; // 联系电话
string createTime = 7; //创建订单时间
}
|
3.3.2 接口实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
package main
import (
"context"
"github.com/micro/go-micro"
"mircoservice/order/proto"
userproto "mircoservice/user/proto"
)
var service micro.Service
type OrderService struct {
}
func (o *OrderService) GetOrder(
ctx context.Context,
req *proto.GetOrderRequest,
resp *proto.Order) error {
userid := 1234
userCli := userproto.NewUserSrvService("go.micro.api.userservice", service.Client())
user, err := userCli.GetAccount(context.TODO(), &userproto.GetAccountRequest{
Id: int32(userid),
})
if err == nil {
resp.Username = user.Username
resp.Phone = user.Phone
resp.Address = user.Address
}
resp.Id = req.Id
resp.Name = "小鸡炖蘑菇"
resp.CreateTime = "2020-09-29 19:37"
resp.Price = 30
return nil
}
func main() {
// 定义一个微服务
service = micro.NewService(
micro.Name("go.micro.api.orderservice"), // 定义订单服务的服务名
)
// 初始化
service.Init()
// 注册订单服务
proto.RegisterOrderSrvHandler(service.Server(), new(OrderService))
// 启动服务
if err := service.Run(); err != nil {
panic(err)
}
}
|
这里面比较关键的是如何在一个微服务中调用另一个微服务,实现方式是在调用方new一个被调用方的实例作为存根
,后面的操作只要拿着这个实例/存根
就能像调用本地方法一样远程调用了(rpc原理)
1
2
3
4
5
6
7
8
9
10
11
|
// new一个被调用方实例
userCli := userproto.NewUserSrvService("go.micro.api.userservice", service.Client())
user, err := userCli.GetAccount(context.TODO(), &userproto.GetAccountRequest{
Id: int32(userid),
})
if err == nil {
resp.Username = user.Username
resp.Phone = user.Phone
resp.Address = user.Address
}
|
3.3.3 web界面调试