0%

go-micro

简述

项目地址:go-micro

go-micro提供的接口功能

服务发现支持服务注册与发现,底层支持etcd、consul、k8s
负载均衡rpc服务间的请求调度均衡策略
同步通信基于RPC通信,支持单向、双向流通道模式
一步通信提供pub、sub通信模型的接口
高级接口比如服务发现,提供调用的接口是一致的

broker异步通信
codec消息编码,可以动态编码
registry服务注册与发现
selector负载均衡
transport 基于RPC的通信模块接口

账号系统微服务

新建一个文件XXX.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
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
syntax = "proto3";

package go.micro.service.user;

service UserService {
// 用户注册
rpc Signup(ReqSignup) returns (RespSignup) {}
// 用户登录
rpc Signin(ReqSignin) returns (RespSignin) {}
// 获取用户信息
rpc UserInfo(ReqUserInfo) returns (RespUserInfo) {}
// 获取用户文件
rpc UserFiles(ReqUserFile) returns (RespUserFile) {}
// 获取用户文件
rpc UserFileRename(ReqUserFileRename) returns (RespUserFileRename) {}
}

message ReqSignup {
string username = 1;
string password = 2;
}

message RespSignup {
int32 code = 1;
string message = 2;
}

message ReqSignin {
string username = 1;
string password = 2;
}

message RespSignin {
int32 code = 1;
string token = 2;
string message = 3;
}

message ReqUserInfo {
string username = 1;
}

message RespUserInfo {
int32 code = 1;
string message =2;
string username =3;
string email = 4;
string phone = 5;
string signupAt = 6;
string lastActiveAt = 7;
int32 status = 8;
}

message ReqUserFile {
string username = 1;
int32 limit = 2;
}

message RespUserFile {
int32 code = 1;
string message =2;
bytes fileData = 3;
}

message ReqUserFileRename {
string username = 1;
string filehash = 2;
string newFileName = 3;
}

message RespUserFileRename {
int32 code = 1;
string message =2;
bytes fileData = 3;
}

执行指令

1
2

protoc --proto_path=service/account/proto --go_out=service/account/proto --micro_out=service/account/proto service/account/proto/user.proto

这时候会生成两个文件
XXX.micro.go以及XXX.pb.go

handler调用

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package handler

import (
"context"
"fmt"
"time"

"filestore-server/common"
"filestore-server/config"
cfg "filestore-server/config"
proto "filestore-server/service/account/proto"
dbcli "filestore-server/service/dbproxy/client"
"filestore-server/util"
)

// User : 用于实现UserServiceHandler接口的对象
type User struct{}

// GenToken : 生成token
func GenToken(username string) string {
// 40位字符:md5(username+timestamp+token_salt)+timestamp[:8]
ts := fmt.Sprintf("%x", time.Now().Unix())
tokenPrefix := util.MD5([]byte(username + ts + "_tokensalt"))
return tokenPrefix + ts[:8]
}

// Signup : 处理用户注册请求
func (u *User) Signup(ctx context.Context, req *proto.ReqSignup, res *proto.RespSignup) error {
username := req.Username
passwd := req.Password

// 参数简单校验
if len(username) < 3 || len(passwd) < 5 {
res.Code = common.StatusParamInvalid
res.Message = "注册参数无效"
return nil
}

// 对密码进行加盐及取Sha1值加密
encPasswd := util.Sha1([]byte(passwd + cfg.PasswordSalt))
// 将用户信息注册到用户表中
dbResp, err := dbcli.UserSignup(username, encPasswd)
if err == nil && dbResp.Suc {
res.Code = common.StatusOK
res.Message = "注册成功"
} else {
res.Code = common.StatusRegisterFailed
res.Message = "注册失败"
}
return nil
}

// Signin : 处理用户登录请求
func (u *User) Signin(ctx context.Context, req *proto.ReqSignin, res *proto.RespSignin) error {
username := req.Username
password := req.Password

encPasswd := util.Sha1([]byte(password + config.PasswordSalt))

// 1. 校验用户名及密码
dbResp, err := dbcli.UserSignin(username, encPasswd)
if err != nil || !dbResp.Suc {
res.Code = common.StatusLoginFailed
return nil
}

// 2. 生成访问凭证(token)
token := GenToken(username)
upRes, err := dbcli.UpdateToken(username, token)
if err != nil || !upRes.Suc {
res.Code = common.StatusServerError
return nil
}

// 3. 登录成功, 返回token
res.Code = common.StatusOK
res.Token = token
return nil
}

// UserInfo : 查询用户信息
func (u *User) UserInfo(ctx context.Context, req *proto.ReqUserInfo, res *proto.RespUserInfo) error {
// 查询用户信息
dbResp, err := dbcli.GetUserInfo(req.Username)
if err != nil {
res.Code = common.StatusServerError
res.Message = "服务错误"
return nil
}
// 查不到对应的用户信息
if !dbResp.Suc {
res.Code = common.StatusUserNotExists
res.Message = "用户不存在"
return nil
}

user := dbcli.ToTableUser(dbResp.Data)

// 3. 组装并且响应用户数据
res.Code = common.StatusOK
res.Username = user.Username
res.SignupAt = user.SignupAt
res.LastActiveAt = user.LastActiveAt
res.Status = int32(user.Status)
// TODO: 需增加接口支持完善用户信息(email/phone等)
res.Email = user.Email
res.Phone = user.Phone
return nil
}

创建一个main.go作为单独的微服务启动

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
package main

import (
"log"
"time"

"github.com/micro/go-micro"

"filestore-server/service/account/handler"
proto "filestore-server/service/account/proto"

func main() {
//创建一个service
service := micro.NewService(
// service := k8s.NewService(
micro.Name("go.micro.service.user"),
)

// 初始化service, 解析命令行参数等
service.Init()

// 初始化dbproxy client
dbproxy.Init(service)

proto.RegisterUserServiceHandler(service.Server(), new(handler.User))
if err := service.Run(); err != nil {
log.Println(err)
}
}

运行go run service/account/main.go --registry=consul指定注册中心为consul

打开浏览器,访问consul的web界面:
localhost:8500/ui/dcl/services

网关微服务

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package handler

import (
"context"
"filestore-server/common"
"filestore-server/util"
"log"
"net/http"

"github.com/gin-gonic/gin"
micro "github.com/micro/go-micro"

cmn "filestore-server/common"
userProto "filestore-server/service/account/proto"
dlProto "filestore-server/service/download/proto"
upProto "filestore-server/service/upload/proto"
)

var (
userCli userProto.UserService
upCli upProto.UploadService
dlCli dlProto.DownloadService
)

func init() {
service := micro.NewService()
// 初始化, 解析命令行参数等
service.Init()

// 初始化一个account服务的客户端
userCli = userProto.NewUserService("go.micro.service.user", service.Client())
// 初始化一个upload服务的客户端
upCli = upProto.NewUploadService("go.micro.service.upload", service.Client())
// 初始化一个download服务的客户端
dlCli = dlProto.NewDownloadService("go.micro.service.download", service.Client())
}

// SignupHandler : 响应注册页面
func SignupHandler(c *gin.Context) {
c.Redirect(http.StatusFound, "/static/view/signup.html")
}

// DoSignupHandler : 处理注册post请求
func DoSignupHandler(c *gin.Context) {
username := c.Request.FormValue("username")
passwd := c.Request.FormValue("password")

resp, err := userCli.Signup(context.TODO(), &userProto.ReqSignup{
Username: username,
Password: passwd,
})

if err != nil {
log.Println(err.Error())
c.Status(http.StatusInternalServerError)
return
}

c.JSON(http.StatusOK, gin.H{
"code": resp.Code,
"msg": resp.Message,
})
}

// SigninHandler : 响应登录页面
func SigninHandler(c *gin.Context) {
c.Redirect(http.StatusFound, "/static/view/signin.html")
}

// DoSigninHandler : 处理登录post请求
func DoSigninHandler(c *gin.Context) {
username := c.Request.FormValue("username")
password := c.Request.FormValue("password")

rpcResp, err := userCli.Signin(context.TODO(), &userProto.ReqSignin{
Username: username,
Password: password,
})

if err != nil {
log.Println(err.Error())
c.Status(http.StatusInternalServerError)
return
}

if rpcResp.Code != cmn.StatusOK {
c.JSON(200, gin.H{
"msg": "登录失败",
"code": rpcResp.Code,
})
return
}

// 动态获取上传入口地址
upEntryResp, err := upCli.UploadEntry(context.TODO(), &upProto.ReqEntry{})
if err != nil {
log.Println(err.Error())
} else if upEntryResp.Code != cmn.StatusOK {
log.Println(upEntryResp.Message)
}

// 动态获取下载入口地址
dlEntryResp, err := dlCli.DownloadEntry(context.TODO(), &dlProto.ReqEntry{})
if err != nil {
log.Println(err.Error())
} else if dlEntryResp.Code != cmn.StatusOK {
log.Println(dlEntryResp.Message)
}

// 登录成功,返回用户信息
cliResp := util.RespMsg{
Code: int(common.StatusOK),
Msg: "登录成功",
Data: struct {
Location string
Username string
Token string
UploadEntry string
DownloadEntry string
}{
Location: "/static/view/home.html",
Username: username,
Token: rpcResp.Token,
UploadEntry: upEntryResp.Entry,
DownloadEntry: dlEntryResp.Entry,
},
}
c.Data(http.StatusOK, "application/json", cliResp.JSONBytes())
}

// UserInfoHandler : 查询用户信息
func UserInfoHandler(c *gin.Context) {
// 1. 解析请求参数
username := c.Request.FormValue("username")

resp, err := userCli.UserInfo(context.TODO(), &userProto.ReqUserInfo{
Username: username,
})

if err != nil {
log.Println(err.Error())
c.Status(http.StatusInternalServerError)
return
}

// 3. 组装并且响应用户数据
cliResp := util.RespMsg{
Code: 0,
Msg: "OK",
Data: gin.H{
"Username": username,
"SignupAt": resp.SignupAt,
// TODO: 完善其他字段信息
"LastActive": resp.LastActiveAt,
},
}
c.Data(http.StatusOK, "application/json", cliResp.JSONBytes())
}

路由

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
package route

import (
"filestore-server/service/apigw/handler"

"github.com/gin-gonic/gin"
)

// Router : 网关api路由
func Router() *gin.Engine {
router := gin.Default()

router.Static("/static/", "./static")

// 注册
router.GET("/user/signup", handler.SignupHandler)
router.POST("/user/signup", handler.DoSignupHandler)
// 登录
router.GET("/user/signin", handler.SigninHandler)
router.POST("/user/signin", handler.DoSigninHandler)
// 用户查询
router.POST("/user/info", handler.UserInfoHandler)

// 用户文件查询
router.POST("/file/query", handler.FileQueryHandler)
// 用户文件修改(重命名)
router.POST("/file/update", handler.FileMetaUpdateHandler)

return router
}

main.go

1
2
3
4
5
6
7
8
9
10
package main

import (
"filestore-server/service/apigw/route"
)

func main() {
r := route.Router()
r.Run(":8080")
}

运行go run service/apigw/main.go --registry=consul指定注册中心为consul

文件上传微服务

文件上传需要用到RPC微服务间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package rpc

import (
"context"
cfg "filestore-server/service/upload/config"
upProto "filestore-server/service/upload/proto"
)

// Upload : upload结构体
type Upload struct{}

// UploadEntry : 获取上传入口
func (u *Upload) UploadEntry(
ctx context.Context,
req *upProto.ReqEntry,
res *upProto.RespEntry) error {

res.Entry = cfg.UploadEntry
return nil
}
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
package main

import (
"fmt"
"time"

micro "github.com/micro/go-micro"

cfg "filestore-server/service/upload/config"
upProto "filestore-server/service/upload/proto"
"filestore-server/service/upload/route"
upRpc "filestore-server/service/upload/rpc"
)

func startRpcService() {
service := micro.NewService(
micro.Name("go.micro.service.upload"), // 服务名称
micro.RegisterTTL(time.Second*10), // TTL指定从上一次心跳间隔起,超过这个时间服务会被服务发现移除
micro.RegisterInterval(time.Second*5), // 让服务在指定时间内重新注册,保持TTL获取的注册时间有效
)
service.Init()

upProto.RegisterUploadServiceHandler(service.Server(), new(upRpc.Upload))
if err := service.Run(); err != nil {
fmt.Println(err)
}
}

func startApiService() {
router := route.Router()
router.Run(cfg.UploadServiceHost)
}

func main() {
// api 服务
go startApiService()

// rpc 服务
startRpcService()
}

通过shell脚本启动整个项目的微服务./service/start-all.sh(根据实际情况修改所需要的文件目录)

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
61
62
63
#!/bin/bash

# 检查service进程
check_process() {
sleep 1
res=`ps aux | grep -v grep | grep "service/bin" | grep $1`
if [[ $res != '' ]]; then
echo -e "\033[32m 已启动 \033[0m" "$1"
return 1
else
echo -e "\033[31m 启动失败 \033[0m" "$1"
return 0
fi
}

# 编译service可执行文件
build_service() {
go build -o service/bin/$1 service/$1/main.go
resbin=`ls service/bin/ | grep $1`
echo -e "\033[32m 编译完成: \033[0m service/bin/$resbin"
}

# 启动service
run_service() {
nohup ./service/bin/$1 --registry=consul >> $logpath/$1.log 2>&1 &
sleep 1
check_process $1
}

# 创建运行日志目录
logpath=/Users/samtake/Documents/GitHub/www/data/log/filestore-server #/data/log/filestore-server

mkdir -p $logpath

# 切换到工程根目录
cd $GOPATH/filestore-server
#cd /data/go/work/src/filestore-server

# 微服务可以用supervisor做进程管理工具;
# 或者也可以通过docker/k8s进行部署

services="
dbproxy
upload
download
transfer
account
apigw
"

# 执行编译service
for service in $services
do
build_service $service
done

# 执行启动service
for service in $services
do
run_service $service
done

echo '微服务启动完毕.'

go-micro学习资料

Go-Micro 基础篇 【1】Hello World:视频 & 文档

#72 Go-Micro 编写微服务实战: 视频地址

Micro 中国站

#62 Go-Micro 微服务框架介绍(一) by 舒先 【 Go 夜读 】

#72 Go-Micro 编写微服务实战 【 Go 夜读 】

#79 Go-Micro 运行时工具集 【 Go 夜读 】