1.本来一个人写一本书,模块划分之后,每个人都可以参与进去开发,而且每个人都是独立开发的,不受限与第三人,提高开发效率
2.每个人写怎么写都可以,你可以用手机,也可以用ipad ,可以根据不同的适用场景采用不同的技术,也就是混合技术栈。
3.三个人如果有一个人没写完,或者一个人写错了,并不影响另外两个人的进度,也就是故障隔离,降低了开发风险,提高了系统的高可用。
4.每个人都可以根据业务需求,进行功能的扩展和缩减,粒度灵活缩放
那么微服务只有好处吗?有没有带来哪些不便呢?
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
就像我们平时用到的json,xml 这种。用于数据传输的一种数据格式。但是既然有了json,为什么会再出来一种数据格式呢?google 官方测试报告显示,同一条消息数据,protobuf 序列化后占用空间是 json 的 1/10,xml 的 1/20,但性能却是几十倍。
又称PB编码,但其内部是纯二进制格式,比Json,XML等格式要更精炼,主要用于数据的序列化和反序列化,目前官方提供了JAVA、Python、C++等多种语言的实现。虽然基于文本的序列化程序(XML,JSON)和基于二进制的序列化程序(Protobuf)都可以快速高效(或缓慢)。但是二进制序列化程序具有其天生的优势。 这意味着“好的”二进制序列化程序通常会比“好的”基于文本的序列化程序更快。
我们上代码看一下,具体的下面会有操作
package main
import (
person "acurd.com/m/proto/gen/go"
"encoding/json"
"fmt"
"github.com/golang/protobuf/proto"
"log"
)
func main() {
var user person.User
user.Id=42
fmt.Println(&user)
b,err:=proto.Marshal(&user)//这个就是我们传输的内容,一个二进制流
if err!=nil {
log.Fatal(err)
}
fmt.Printf("%X\n",b)
//前面的值假设是我们服务端接到了,开始进行解码
var user2 person.User
err=proto.Unmarshal(b,&user2)
fmt.Println(&user2)
b,err=json.Marshal(&user2)//转化为json
fmt.Printf("%s\n",b)
}
执行结果
你也可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。
sudo mv protoc-3.20.0-rc-1-osx-x86_64 /usr/local/protobuf
export PROTOBUF=/usr/local/protobuf
export PATH=$PROTOBUF/bin:$PATH
cd ~
source .bash_profile
go install \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc
//我们定义一个人的数据结构,
syntax="proto3";
package person;
option go_package="/Users/zhangguofu/website/goproject/proto/gen/go;person";//生成的go文件存放目录在哪;包名叫什么
message Person{
// 1 和2 是说第一个字段是name 第二个字段是age,为了方便后面解析二进制流
string name=1;
int64 age=2;
}
zhangguofu@zhangguofudeMacBook-Pro proto (main) $ pwd
/Users/zhangguofu/website/goproject/proto
zhangguofu@zhangguofudeMacBook-Pro proto (main) $ mkdir -p gen/go
zhangguofu@zhangguofudeMacBook-Pro proto (main) $ protoc -I . --go_out=paths=source_relative:gen/go person.proto
zhangguofu@zhangguofudeMacBook-Pro proto (main) $
type Person struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 1 和2 是说第一个字段是name 第二个字段是age,为了方便后面解析二进制流
// 解释:bytes 指类型; 1指标识符; opt 可选 ;proto3指版本号 ;name是json字段的名字;omitempty意思是说如果字段值为空,该字段在json序列化时则省略
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Age int64 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
}
package main
import (
person "acurd.com/m/proto/gen/go"
"encoding/json"
"fmt"
"github.com/golang/protobuf/proto"
"log"
)
func main() {
var p1 person.Person
p1.Name="小名"
p1.Age=18
fmt.Println(&p1)
b,err:=proto.Marshal(&p1)//这个就是我们传输的内容,一个二进制流
if err!=nil {
log.Fatal(err)
}
fmt.Printf("%X\n",b)
//前面的值假设是我们服务端接到了,开始进行解码
var p2 person.Person
err=proto.Unmarshal(b,&p2)
fmt.Println(&p2)
b,err=json.Marshal(&p2)//转化为json
fmt.Printf("%s\n",b)
}
参考文章 那么我们具体来看一下proto的写法
//我们定义一个人的数据结构,
//protobuf 有2个版本,默认版本是 proto2,如果需要 proto3,则需要在非空非注释第一行使用 syntax = "proto3" 标明版本。
syntax = "proto3";
//package,即包名声明符是可选的,用来防止不同的消息类型有命名冲突。比如两个包有两个Person
package person;
//生成go文件的地址和包名
option go_package = "/Users/zhangguofu/website/goproject/proto/gen/go;person";
// 消息类型 使用 message 关键字定义 Person是类型名,name age 算是该类型的组成元素,
// 一个 .proto 文件中可以写多个消息类型,即对应多个结构体(struct)。
message Person{
//每个字符 =后面的数字称为标识符,每个字段都需要提供一个唯一的标识符。标识符用来在消息的二进制格式中识别各个字段,一旦使用就不能够再改变,标识符的取值范围为 [1, 2^29 - 1] 。注意,生成的struct和你定义的顺序是一致的,而不会的按 1 2 3 4 等标识符调整顺序
// 1 和2 是说第一个字段是name 第二个字段是age,为了方便后面解析二进制流
string name = 1;
int64 age = 2;
}
message User{
int64 id=1;
}
//我们定义一个人的数据结构,
//protobuf 有2个版本,默认版本是 proto2,如果需要 proto3,则需要在非空非注释第一行使用 syntax = "proto3" 标明版本。
syntax = "proto3";
package goods;
option go_package = "/Users/zhangguofu/website/goproject/proto/gen/go/goods/;goods";
service GoodsInfo{
//一个添加商品
rpc addGoods(Goods) returns(GoodsId);
//一个获取商品
rpc getGoods(GoodsId) returns(Goods);
}
//定义商品的消息类型
message Goods{
string id = 1;
string name = 2;
string desc = 3;
}
//定义商品id的消息类型
message GoodsId{
string value=1;
}
mkdir /Users/zhangguofu/website/goproject/proto/gen/go/goods
执行命令
protoc -I . --go_out=paths=source_relative:gen/go/goods/ --go-grpc_opt=require_unimplemented_servers=false --go-grpc_out==plugins=grpc,paths=source_relative:gen/go/goods/ goods.proto
CREATE TABLE `an_goods` (
`id` varchar(60) NOT NULL DEFAULT '' COMMENT '业务主键',
`name` varchar(20) NOT NULL COMMENT '商品名称',
`desc` varchar(32) NOT NULL DEFAULT '' COMMENT '商品描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表'
# golang.org/x/net/http2
../../../../go/pkg/mod/golang.org/x/net@v0.0.0-20220127200216-cd36cc0744dd/http2/transport.go:417:45: undefined: os.ErrDeadlineExceeded
note: module requires Go 1.17
package main
import (
"acurd.com/m/proto/gen/go/goods"
"context"
"database/sql"
_ "github.com/go-sql-driver/mysql"
uuid "github.com/satori/go.uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"log"
"net"
)
const (
Adress = "localhost:9988"
)
type Server struct {
goods.UnimplementedGoodsInfoServer
}
func (s Server) AddGoods(ctx context.Context, g *goods.Goods) (*goods.GoodsId, error) {
//参数校验忽略
db, err := sql.Open("mysql", "acurd:acurd@tcp(127.0.0.1:3306)/test")
if err != nil {
grpclog.Fatal(err)
}
defer db.Close()
getUUID := GetUUID()
name := g.Name
desc := g.Desc
sqlStr := "INSERT INTO an_goods (`id`,`name`,`desc`) VALUES (?,?,?)"
_, err = db.Exec(sqlStr, getUUID, name, desc)
if err != nil {
grpclog.Fatal(err)
}
goodsId := goods.GoodsId{}
goodsId.Value = getUUID
log.Printf("add success ,the id is %s", getUUID)
return &goodsId, nil
}
func (s Server) GetGoods(ctx context.Context, id *goods.GoodsId) (*goods.Goods, error) {
db, err := sql.Open("mysql", "acurd:acurd@tcp(127.0.0.1:3306)/test")
if err != nil {
grpclog.Fatal(err)
}
defer db.Close()
search_id := id.Value
g := goods.Goods{}
sqlStr := "SELECT `id`,`name`,`desc` FROM an_goods WHERE id=?"
err = db.QueryRow(sqlStr, search_id).Scan(&g.Id, &g.Name, &g.Desc)
return &g, nil
}
func GetUUID() string {
u2 := uuid.NewV4()
return u2.String()
}
var server Server
func main() {
//绑定监听端口
listener, err := net.Listen("tcp", Adress)
if err != nil {
log.Println("net listen err ", err)
return
}
//创建grpc服务
s := grpc.NewServer()
//开始grpc监听
goods.RegisterGoodsInfoServer(s, &server)
log.Println("start gRPC listen on Adress " + Adress)
if err := s.Serve(listener); err != nil {
log.Println("failed to serve...", err)
return
}
}
package main
import (
"acurd.com/m/proto/gen/go/goods"
"context"
uuid "github.com/satori/go.uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"log"
)
const (
Adress = "localhost:9988"
)
func GetUUID() string {
u2 := uuid.NewV4()
return u2.String()
}
func main() {
// 声明监听
conn, err := grpc.Dial(Adress, grpc.WithInsecure())
if err!=nil {
log.Fatal(err)
}
defer conn.Close()
//客户端绑定连接
client:=goods.NewGoodsInfoClient(conn)
goodsNew := goods.Goods{
Name: "apple",
Desc: "i am an apple",
}
//添加一个商品
res,errs:=client.AddGoods(context.Background(),&goodsNew)
if errs!=nil{
grpclog.Error(errs)
}
//返回结果
log.Printf("%+v",res)
//搜索一个商品
goodsIdNew:=goods.GoodsId{}
goodsIdNew.Value=res.Value
res2,errs2:=client.GetGoods(context.Background(),&goodsIdNew)
if errs2!=nil{
grpclog.Error(errs2)
}
//返回结果
log.Printf("%+v",res2)
}
client