內容目錄如下:
- 什麼是微服務?
- RPC 協議
- ProtoBuf 使用 protobuf 簡單語法 Protobuf 高級用法
- GRPC 框架
- go-micro 框架發現服務 consul 使用 consul 和 grpc 結合使用 go-micro 使用 web 與服務端通信
分享時間:
2020.4.18日(週六) 21:00-22:00 (一次講不完,連載直播無回放)
地址:https://ke.qq.com/course/2088147?taid=8548389375433939&tuin=31589b0e
主講師:HZ
具有多年的軟件開發實戰經驗,精通 Golang、C/C++、Linux 系統編程。參與大型網絡遊戲研發,區塊鏈研發,具有豐富的項目經驗,授課認真負責,幽默風趣,深受學員好評。
什麼是微服務?
服務拆分原則:高內聚低耦合
在介紹微服務時,首先得先理解什麼是微服務,顧名思義,微服務得從兩個方面去理解,什麼是"微"、什麼是"服務"?
微(micro)狹義來講就是體積小,著名的"2 pizza 團隊"很好的詮釋了這一解釋(2 pizza 團隊最早是亞馬遜CEO Bezos提出來的,意思是說單個服務的設計,所有參與人從設計、開發、測試、運維所有人加起來 只需要2個披薩就夠了 )。
服務(service)一定要區別於系統,服務一個或者一組相對較小且獨立的功能單元,是用戶可以感知最小功能集。
那麼廣義上來講,微服務是一種分佈式系統解決方案,推動細粒度服務的使用,這些服務協同工作。
在這裡我們可能會混淆一個點,那就是微服務和微服務架構,這是兩個不同的概念,而我們平時說到的微服務已經包含了這兩個概念了,我們需要把它們說清楚以免學習中糾結。微服務架構是一種設計方法,而微服務這是指使用這種方法而設計的一個應用。所以我們必要對微服務的概念做出一個比較明確的定義。
微服務架構是將複雜的系統使用組件化的方式進行拆分,並使用輕量級通訊方式進行整合的一種設計方法。微服務是通過這種架構設計方法拆分出來的一個獨立的組件化的小應用。微服務架構定義的精髓,可以用一句話來描述,那就是“分而治之,合而用之”。將複雜的系統進行拆分 的方法,就是“分而治之”。分而治之,可以讓複雜的事情變的簡單,這很符合我們平時處理問題的方法。 使用輕量級通訊等方式進行整合的設計,就是“合而用之”的方法,合而用之可以讓微小的力量變動強大。RPC 協議封裝
上面的代碼服務名都是寫死的,不夠靈活(容易寫錯),這裡我們對RPC的服務端和客戶端再次進行一 次封裝,來屏蔽掉服務名,具體代碼如下
服務端封裝
<code>//抽離服務名稱
var serverName = "LoginService"
//定義一個父類
type RPCDesign interface {
Hello(string,*string)error
}
//實現工廠函數
func RegisterRPCServer(srv RPCDesign)error{
return rpc.RegisterName(serverName,srv)
}
/<code>
封裝之後的服務端實現如下:
<code>type RpcServer struct{}
//5 + 3i chan func complex
func (this *RpcServer) Hello(req string, resp *string) error {
*resp += req + "你好"
return nil
}
func main() {
//設置監聽
listener, err := net.Listen("tcp", ":8899")
if err != nil {
fmt.Println("設置監聽錯誤")
return
}
defer listener.Close()
fmt.Println("開始監聽....")
for {
//接收鏈接
conn, err := listener.Accept()
if err != nil {
fmt.Println("獲取連接失敗")
return
}
defer conn.Close()
fmt.Println(conn.RemoteAddr().String() + "連接成功")
//rpc表 註冊rpc服務
if err = RegisterRPCServer(new(RpcServer)); err != nil {
fmt.Println("註冊rpc服務失敗")
return
}
//把rpc服務和套接字綁定
//rpc.ServeConn(conn)
rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
/<code>
客戶端封裝
<code>type RPCClient struct {
rpcClient *rpc.Client
}
func NewRpcClient(addr string)(RPCClient){
conn,err := net.Dial("tcp",addr)
if err != nil {
fmt.Println("鏈接服務器失敗")
return RPCClient{}
}
defer conn.Close()
//套接字和rpc服務綁定
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
return RPCClient{rpcClient:client}
}
func (this*RPCClient)CallFunc(req string,resp*string)error{
return this.rpcClient.Call(serverName+".Hello",req,resp)
}
/<code>
封裝之後客戶端實現
<code>func main() {
//初始化對象 與服務名有關的內容完全封裝起來了
client := NewRpcClient("127.0.0.1:8899")
//調用成員函數
var temp string
client.CallFunc("xiaoming",&temp)
fmt.Println(temp) }
/<code>
protobuf 基本編譯
protobuf編譯是通過編譯器protoc進行的,通過這個編譯器,我們可以把.proto文件生成go,Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代碼,生成命令如下:
<code>protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto
/<code>
- proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路徑,如果忽略則默認當前目錄。如果有多個目錄則可以多次調用--proto_path,它們將會順序的被訪問並執行導 入。
- --go_out=DST_DIR, 指定了生成的go語言代碼文件放入的文件夾
- 允許使用 protoc --go_out=./ *.proto 的方式一次性編譯多個 .proto 文件
- go語言編譯時,protobuf 編譯器會把 .proto 文件編譯成 .pd.go 文件
一般在使用的時候我們都是使用下面這種簡單的命令:
<code>protoc --go_out=./ *.proto
/<code>
編譯當前文件夾下的所有.proto文件,並把生成的 go 文件放置在當前文件夾下。
我們先來編譯一個最簡單的的proto文件,編譯之後會得到一個如下一個go文件,如下:
message 被對應的編譯成 go 語言中的結構體,同時還為 String 類型自動生成了一組方法,其中 ProtoMessage 方法表示這是一個實現了 proto.Message 接口的方法。此外 Protobuf 還為每個成員生成了一個 Get 方法,Get 方法不僅可以處理空指針類型,而且可以和 Proto2 的方法保持一致.
然後我們給這個.proto 文件中添加一個 RPC 服務,再次進行編譯,發現生成的 go 文件沒有發生變化。這是因為世界上的 RPC 實現有很多種,protoc 編譯器並不知道該如何為 HelloService 服務生成代碼。不 過在 protoc-gen-go 內部已經集成了一個叫grpc 的插件,可以針對 grpc 生成代碼:
<code>protoc --go_out=plugins=grpc:. *.proto
/<code>
在生成的代碼中多了一些類似HelloServiceServer、HelloServiceClient的新類型。如下:
和我們優化之後的RPC服務有點相似,但是又不太一樣,接下來就讓我們來學習一下grpc框架吧。
GRPC 框架
GRPC 是 Google 公司基於 Protobuf 開發的跨語言的開源 RPC 框架。GRPC 基於HTTP/2 協議設計,可以 基於一個 HTTP/2 鏈接提供多個服務,對於移動設備更加友好。目前提供 C、Java 和 Go 語言版本,分別 是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持。
在 gRPC 裡客戶端應用可以像調用本地對象一樣直接調用另一臺不同的機器上服務端應用的方法,使得 您能夠更容易地創建分佈式應用和服務。與許多 RPC 系統類似, gRPC 也是基於以下理念:
- 定義一個服務,指定其能夠被遠程調用的方法(包含參數和返回類型)。
- 在服務端實現這個接口,並運行一個 gRPC 服務器來處理客戶端調用。
在客戶端擁有一個存根能夠像服務端一樣的方法。gRPC 客戶端和服務端可以在多種環境中運行和交互 - 從 google 內部的服務器到你自己的筆記本,並且可以用任何 gRPC支持的語言來編寫。
所以,你可以很容易地用 Java 創建一個 gRPC 服務端,用 Go、 Python、Ruby 來創建客戶端。此外, Google最新 API 將有 gRPC 版本的接口,使你很容易地將 Google 的功能集成到你的應用裡。
gRPC 官方文檔中文版:http://doc.oschina.net/grpc?t=60133
gRPC 官網:https://grpc.io
在詳細瞭解使用 GRPC 之前先來了解一下上面定義中的一些關鍵詞。首先我們來看一下 HTTP/2是什麼內容?
其實本質上就是 http2.0 版本,http 目前為止主要有四個版本,分別為 http1.0、http1.1、http2.0、 https。
http1.0是最原始的版本,不支持持久連接,效率也比較低
http1.1針對 http1.0 版本做了優化,可以連接一次,多次使用,效率比 http1.0高
http2.0實現了多路複用,對 http1.1 再次進行了優化。http2.0 也被稱為下一代 http 協議,是在 2013 年8 月進行首次測試,所以現在用的不是很廣。https 其實是在 http 協議上多加了一層SSL 協議,具體如下圖:
所以本質上,http1.0、http1.1、http2.0 都可以添加 SSL 協議。
GRPC 使用
如果從 Protobuf 的角度看,GRPC 只不過是一個針對 service 接口生成代碼的生成器。接著我們來學習一下 GRPC 的用法。這裡我們創建一個簡單的 proto 文件,定義一個 HelloService 接口:
<code>syntax = "proto3"; //指定版本信息,不指定會報錯
package pb; //後期生成go文件的包名
message Person{
// 名字
string name = 1;
// 年齡
int32 age = 2 ;
}
//定義RPC服務
service HelloService {
rpc Hello (Person)returns (Person);
}
/<code>
對 proto 文件進行編譯:
<code>$ protoc --go_out=plugins=grpc:. *.proto
/<code>
GRPC 插件會為服務端和客戶端生成不同的接口:
<code>//客戶端接口
type HelloServiceClient interface {
Hello(ctx context.Context, in *Person, opts ...grpc.CallOption)
(*Person, error)
}
//服務器接口
type HelloServiceServer interface {
Hello(context.Context, *Person) (*Person, error)
}
/<code>
我們接著可以基於他們給的服務端接口重新實現 HelloService 服務:
<code>type HelloService struct{}
func (this*HelloService)Hello(ctx context.Context, person *pb.Person)
(*pb.Person, error){
reply := &pb.Person{
Name:"zhangsan" + person.Name,
Age:18,
}
return reply,nil
}
/<code>
GRPC 的啟動流程和 RPC 的啟動流程類似,代碼如下:
<code>func main(){
//獲取grpc服務端對象
grpcServer := grpc.NewServer()
//註冊grpc服務
pb.RegisterHelloServiceServer(grpcServer,new(HelloService))
//設置服務端監聽
lis,err := net.Listen("tcp",":1234")
if err != nil {
panic(err)
}
//在指定端口上提供grpc服務
grpcServer.Serve(lis)
}
/<code>
然後我們就可以通過客戶端來連接 GRPC 服務了:
<code>func main(){
//和grpc服務建立連接
conn,err := grpc.Dial("localhost:1234",grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
client := pb.NewHelloServiceClient(conn)
reply,err := client.Hello(context.Background(),&pb.Person{Name:"lisi",Age:100})
if err != nil {
panic(err)
}
fmt.Println("reply,",reply)
}
/<code>
** go-micro 框架**
在瞭解 go-micro 之前,我們先來了解一下什麼是 micro。Micro 是一個專注於簡化分佈式系統開發的微服務生態系統。由開源庫和工具組成。主要包含以下幾種庫:
- go-micro:用於編寫微服務的可插入 Go-RPC 框架; 服務發現,客戶端/服務器 rpc,pub/sub 等, 是整個 Micro 的核心。**默認使用 mdns 做服務發現,可以在插件中替換成 consul,etcd,k8s 等 組播 廣播 **
- go-plugins:go-micro 的插件,包括 etcd,kubernetes(k8s),nats,rabbitmq,grpc 等
- micro:一個包含傳統入口點的微服務工具包; API 網關,CLI,Slack Bot,Sidecar 和 Web UI。
其他各種庫和服務可以在github.com/micro找到。我們主要使用的框架也是 go-micro,在使用之前我們先來了解一下服務發現是個什麼東西?有什麼作用?
** 服務發現**
我們在做微服務開發的時候,客戶端的一個接口可能需要調用 N 個服務,客戶端必須知道所有服務的網絡位置(ip+port),如下圖所示
以往的做法是把服務的地址放在配置文件活數據庫中,這樣就有以下幾個問題:(健壯性)
- 需要配置N個服務的網絡位置,加大配置的複雜性
- 服務的網絡位置變化,需要改變每個調用者的配置
- 集群的情況下,難以做負載(反向代理的方式除外)
總結起來一句話:服務多了,配置很麻煩,問題一大堆 所以現在就選擇服務發現來解決這些問題。我們來看一下,服務發現如何解決這個問題,具體設計如
與之前解決方法不同的是,加了個服務發現模塊。服務端把當前自己的網絡位置註冊到服務發現模塊 (這裡註冊的意思就是告訴),服務發現就以 K-V 的方式記錄下,K一般是服務名,V 就是 IP:PORT。服 務發現模塊定時的輪詢查看這些服務能不能訪問的了(這就是健康檢查)。客戶端在調用服務 A-N 的時 候,就跑去服務發現模塊問下它們的網絡位置,然後再調用它們的服務。這樣的方式是不是就可以解決 上面的問題了呢?客戶端完全不需要記錄這些服務的網絡位置,客戶端和服務端完全解耦!
常見的服務發現框架有:Etcd、Eureka、Consul、Zookeeper 這裡分享的時候我們選擇 go-micro 默認的服務發現框架 consul 來做一個詳細介紹。
對於服務發現框架 consul、 go-micro 框架的使用、代碼介紹直播的時候會進行詳細解說
......
瞭解一下
閱讀更多 Python學習 的文章