必須要掌握的開源Web應用伺服器—Tomcat多例項和負載均衡
RPC 框架原理
RPC 框架的目標就是讓遠端服務呼叫更加簡單、透明,RPC 框架負責遮蔽底層的傳輸方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二進位制)和通訊細節。服務呼叫者可以像呼叫本地介面一樣呼叫遠端的服務提供者,而不需要關心底層通訊細節和呼叫過程。
業界主流的 RPC 框架整體上分為三類:
- 支援多語言的 RPC 框架,比較成熟的有 Google 的 gRPC、facebook的Apache、Thrift;
- 只支援特定語言的 RPC 框架,例如新浪微博的 Motan;
- 支援服務治理等服務化特性的分散式服務框架,其底層核心仍然是 RPC 框架, 例如阿里的 Dubbo。
gRPC是什麼
gRPC 是一個高效能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支援 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C## 支援.
gRPC 基於 HTTP/2 標準設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連線上的多複用請求等特。這些特性使得其在移動裝置上表現更好,更省電和節省空間佔用。
grc優點
- 多語言:語言中立,支援多種語言。
- 輕量級、高效能:序列化支援 PB(Protocol Buffer)和 JSON,PB 是一種語言無關的高效能序列化框架。
可插拔 - IDL:基於檔案定義服務,通過 proto3 工具生成指定語言的資料結構、服務端介面以及客戶端 Stub。
- 移動端:基於標準的 HTTP2 設計,支援雙向流、訊息頭壓縮、單 TCP 的多路複用、服務端推送等特性,這些特性使得 gRPC 在移動端裝置上更加省電和節省網路流量。
安全
HTTP2 規範當使用 TLS 時強制使用 TLS 1.2 及以上的版本,並且在部署上對允許的密碼施加一些額外的限制以避免已知的比如需要 SNI 支援的問題。並且期待 HTTP2 與專有的傳輸安全機制相結合,這些傳輸機制的規格說明不能提供有意義的建議。
gRPC使用
使用gRPC, 我們可以一次性的在一個.proto檔案中定義服務並使用任何支援它的語言去實現客戶端和服務端,反過來,它們可以應用在各種場景中,從Google的伺服器到你自己的平板電腦—— gRPC幫你解決了不同語言及環境間通訊的複雜性。使用protocol buffers還能獲得其他好處,包括高效的序列號,簡單的IDL以及容易進行介面更新。總之一句話,使用gRPC能讓我們更容易編寫跨語言的分散式程式碼。
- 通過一個 protocol buffers 模式,定義一個簡單的帶有 Hello World 方法的 RPC 服務。
- 用你最喜歡的語言(如果可用的話)來建立一個實現了這個介面的服務端。
- 用你最喜歡的(或者其他你願意的)語言來訪問你的服務端。
什麼用grpc
- 服務而非物件、訊息而非引用:促進微服務的系統間粗粒度訊息互動設計理念。
- 負載無關的:不同的服務需要使用不同的訊息型別和編碼,例如 protocol buffers、JSON、XML 和 Thrift。
- 流:Streaming API。
- 阻塞式和非阻塞式:支援非同步和同步處理在客戶端和服務端間互動的訊息序列。
- 元資料交換:常見的橫切關注點,如認證或跟蹤,依賴資料交換。
- 標準化狀態碼:客戶端通常以有限的方式響應 API 呼叫返回的錯誤。
HealthCheck
gRPC 有一個標準的健康檢測協議,在 gRPC 的所有語言實現中基本都提供了生成程式碼和用於設定執行狀態的功能。
主動健康檢查 health check,可以在服務提供者服務不穩定時,被消費者所感知,臨時從負載均衡中摘除,減少錯誤請求。當服務提供者重新穩定後,health check 成功,重新加入到消費者的負載均衡,恢復請求。health check,同樣也被用於外掛方式的容器健康檢測,或者流量檢測(k8s liveness & readiness)。
protubuf檔案編寫
syntax = "proto3";
package hello;
// option go_package = "hello";
option go_package = "/hello";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
golang建立grpc server
安裝工具包:
- 下載protoc 連結
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
執行:
protoc --go_out=./ --go-grpc_out=./ hello.proto
- --proto_path: 指定了要去哪個目錄中搜索import中匯入的和要編譯為.go的proto檔案 (在這沒有使用,需要的話可以加上)
- --go_out:指定了生成的go檔案的目錄,我在這裡把go檔案放到本目錄中
- --go-grpc_out: 指定了生成的go grpc檔案的目錄,我在這裡把go grpc檔案放到本目錄中
- hello.proto, 定義了我要編譯的檔案是哪個檔案。
go server程式碼
package main
import (
"context"
"errors"
"fmt"
"github.com/zhaohaiyu1996/akit/example/grpc/hello"
"google.golang.org/grpc"
"net"
)
type Server struct {
hello.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *Server) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloReply, error) {
if in.Name == "error" {
return nil, errors.New("123")
}
if in.Name == "panic" {
panic("grpc panic")
}
return &hello.HelloReply{Message: fmt.Sprintf("Hello %+v", in.Name)}, nil
}
type Ss struct {
*grpc.Server
}
func main() {
// 監聽本地的8848埠
s := Ss{grpc.NewServer()}
hello.RegisterGreeterServer(s, &Server{}) // 在gRPC服務端註冊服務
lis, err := net.Listen("tcp", "127.0.0.1:8808")
if err != nil {
fmt.Printf("listen failed: %v", err)
return
}
//reflection.Register(s.Server) //在給定的gRPC伺服器上註冊伺服器反射服務
// Serve方法在lis上接受傳入連線,為每個連線建立一個ServerTransport和server的goroutine。
// 該goroutine讀取gRPC請求,然後呼叫已註冊的處理程式來響應它們。
err = s.Serve(lis)
if err != nil {
fmt.Printf("failed to serve: %v", err)
return
}
}
golang建立grpc client
執行:
protoc --go_out=./ --go-grpc_out=./ hello.proto
go client程式碼
package main
import (
"context"
"fmt"
"github.com/zhaohaiyu1996/akit/example/grpc/hello"
"google.golang.org/grpc"
)
func main() {
// 連線伺服器
conn, err := grpc.Dial("localhost:8808", grpc.WithInsecure())
if err != nil {
fmt.Printf("connect faild: %v", err)
}
defer conn.Close()
c := hello.NewGreeterClient(conn)
// 呼叫SayHello
r, err := c.SayHello(context.Background(), &hello.HelloRequest{Name: "zhaohaiyu"})
if err != nil {
fmt.Printf("sayHello failed: %v", err)
}
fmt.Println(r)
}
結果:
SayHello: hello ---> zhaohaiyu
python建立grpc client
使用python客戶端呼叫golang服務端的方法
下載依賴:
pip install grpcio
pip install protobuf
pip install grpcio_tools
執行:
python -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=./ hello.proto
python client程式碼
import grpc
import hello_pb2
import hello_pb2_grpc
def run():
with grpc.insecure_channel('localhost:8848') as channel:
stub = hello_pb2_grpc.HelloStub(channel)
res = stub.SayHello(hello_pb2.HelloRequest(name="趙海宇"))
print(res.message)
if __name__ == '__main__':
run()
結果:
python ./main.go
hello ---> 趙海宇
gprc的haeder
- grpc是基於http2.0的rpc框架 -
- grpc對於http頭部傳遞資料進行了封裝 metadata,單獨抽象了一個包google.golang.org/grpc/metadata-
- type ***p[string][]string其實就是一個map
客戶端傳送方式一:
// 建立md 並加入ctx
md := metadata.Pairs("key1","value1","key2","value2")
ctx := metadata.NewOutgoingContext(context.Background(),md)
// 從ctx中拿出md
md,_ = metadata.FromOutgoingContext(ctx)
newMd := metadata.Pairs("key3","value3")
ctx = metadata.NewOutgoingContext(ctx,metadata.Join(md,newMd))
客戶端傳送方式二:
ctx := context.Background()
ctx = metadata.AppendToOutgoingContext(ctx,"key1","value1","key2","value2")
ctx = metadata.AppendToOutgoingContext(ctx,"key3","value3")
服務端接收:
md,ok := metadata.FromIncomingContext(ctx)
例項:
- server:
package main
import (
"fmt"
"net"
pb "test/demo13/server/hello"
"github.com/grpc-ecosystem/grpc-gateway/examples/clients/responsebody"
"github.com/uber/jaeger-client-go/crossdock/client"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
)
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
md,ok := metadata.FromIncomingContext(ctx)
if ok {
fmt.Println(md)
}
return &pb.HelloResponse{Message: "hello ---> " + in.Name}, nil
}
func main() {
// 監聽本地的8848埠
lis, err := net.Listen("tcp", "localhost:8848")
if err != nil {
fmt.Printf("listen failed: %v", err)
return
}
s := grpc.NewServer() // 建立gRPC伺服器
pb.RegisterHelloServer(s, &server{}) // 在gRPC服務端註冊服務
reflection.Register(s) //在給定的gRPC伺服器上註冊伺服器反射服務
// Serve方法在lis上接受傳入連線,為每個連線建立一個ServerTransport和server的goroutine。
// 該goroutine讀取gRPC請求,然後呼叫已註冊的處理程式來響應它們。
err = s.Serve(lis)
if err != nil {
fmt.Printf("failed to serve: %v", err)
return
}
}
- client
package main
import (
"context"
"fmt"
pb "test/demo13/client/hello"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func main() {
// 連線伺服器
conn, err := grpc.Dial("localhost:8848", grpc.WithInsecure())
if err != nil {
fmt.Printf("faild to connect: %v", err)
}
defer conn.Close()
c := pb.NewHelloClient(conn)
// 呼叫服務端的SayHello
ctx := context.Background()
ctx = metadata.AppendToOutgoingContext(ctx,"zhyyz","961119")
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "zhaohaiyu"})
if err != nil {
fmt.Printf("sayHello failed: %v", err)
}
fmt.Printf("SayHello: %s \n", r.Message)
}