介绍
微服务和单体架构
单体架构弊端
单体应用程序中的服务模块是紧耦合的。业务逻辑纠缠不清,很难隔离应用程序,因此可扩展性成为一个挑战。
由于代码库非常庞大,这会延缓应用程序的开发和测试周期的速度。
微服务解决了单体架构的弊端
但也有新的缺点
RPC框架是什么
https://zhuanlan.zhihu.com/p/411315625
RPC 框架说白了就是让你可以像调用本地方法一样调用远程服务提供的方法,而不需要关心底层的通信细节。简单地说就让远程服务调用更加简单、透明。 RPC包含了客户端(Client)和服务端(Server)
GRPC
https://grpc.io/docs/what-is-grpc/introduction/
grpc默认使用proto buffers,是谷歌一套成熟的开源的结构数据序列化机制
Protobuf安装
安装protobuf
brew install protobuf protoc --version //验证
|
安装go语言的protoc生成器
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
Proto文件
syntax = "proto3";
option go_package = ".;service";
service SayHello { rpc SayHello(HelloRequest) returns (HelloResponse) {} }
message HelloRequest { string username = 1; int64 code = 2; }
message HelloResponse { string responseMsg = 1; }
|
利用命令生成
protoc --go_out=../rpc hello.proto protoc --go-grpc_out=../rpc hello.proto
|
服务端代码实现
package main
import ( service "LearnTest/rpc" "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "log" "net" "strconv" )
type server struct { service.UnimplementedSayHelloServer }
func (s *server) SayHello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) { fmt.Println("recv from " + req.Username) return &service.HelloResponse{ResponseMsg: "hello " + req.Username + "\nyour code is: " + strconv.Itoa(int(req.Code))}, nil }
func main() { lis, err := net.Listen("tcp", ":2333") if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() service.RegisterSayHelloServer(grpcServer, &server{}) reflection.Register(grpcServer) fmt.Println("Listening on port 2333...") grpcServer.Serve(lis)
}
|
客户端代码实现
package main
import ( service "LearnTest/rpc" "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" )
func main() { conn, err := grpc.Dial("127.0.0.1:2333", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return } defer conn.Close()
client := service.NewSayHelloClient(conn) resp, _ := client.SayHello(context.Background(), &service.HelloRequest{ Username: "qqw", Code: 3377, })
fmt.Println(resp.GetResponseMsg()) }
|
认证以及安全传输
刚才写的代码是不安全的,在客户端中,我们可以发现,只要我们知道目标服务器的ip和端口就能够对它进行连接传输,所以我们要采取相关认证和安全的传输方式
gRPC 内默认提供了两种 内置的认证方式:
- 基于 CA 证书的 SSL/TLS 认证方式;
- 基于 Token 的认证方式。
gRPC 中的连接类型一共有以下 3 种:
- insecure connection:不使用 TLS 加密;
- server-side TLS:仅服务端 TLS 加密;
- mutual TLS:客户端、服务端都使用 TLS 加密。
如之前的连接就是不安全的
conn, err := grpc.Dial("127.0.0.1:2333", grpc.WithTransportCredentials(insecure.NewCredentials()))
|
TLS认证实现
参考
https://www.cnblogs.com/rickiyang/p/14981374.html
客户端CA证书
openssl genrsa -out ca.key 2048
openssl req -new -key ca.key -out ca.csr
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
|
服务端证书
其中需要先复制本机的openssl.cnf文件到该目录,然后进行相关修改
openssl genrsa -out server.key 2048
openssl req -new -subj "/C=GB/L=Beijing/O=github/CN=qqw.com" \ -key server.key -out server.csr -config openssl.cnf
openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 \ -in server.csr -out server.crt -extensions v3_req -extfile openssl.cnf
|
修改相关代码
package main
import ( service "LearnTest/rpc" "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" "log" "net" "strconv" )
type server struct { service.UnimplementedSayHelloServer }
func (s *server) SayHello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) { fmt.Println("recv from " + req.Username) return &service.HelloResponse{ResponseMsg: "hello " + req.Username + "\nyour code is: " + strconv.Itoa(int(req.Code))}, nil }
func main() { cert, _ := credentials.NewServerTLSFromFile("绝对路径/server.crt", "绝对路径/server.key")
lis, err := net.Listen("tcp", ":2333") if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer(grpc.Creds(cert)) service.RegisterSayHelloServer(grpcServer, &server{}) reflection.Register(grpcServer) fmt.Println("Listening on port 2333...") grpcServer.Serve(lis)
}
|
package main
import ( service "LearnTest/rpc" "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" )
func main() { cert, _ := credentials.NewClientTLSFromFile("绝对路径/ca.crt", "kk.qqw.com")
conn, err := grpc.Dial("127.0.0.1:2333", grpc.WithTransportCredentials(cert)) if err != nil { return } defer conn.Close()
client := service.NewSayHelloClient(conn) resp, _ := client.SayHello(context.Background(), &service.HelloRequest{ Username: "qqw", Code: 3377, })
fmt.Println(resp.GetResponseMsg()) }
|
Token认证实现
这里再写一个token.proto
文件
syntax = "proto3";
option go_package = ".;service";
message LoginRequest { string username = 1; string password = 2; }
message LoginResp { string status = 1; string token = 2; }
message PingMessage { string greet = 1; }
service TokenService { rpc Login(LoginRequest) returns (LoginResp) {} rpc Greet(PingMessage) returns (PingMessage) {} }
|
protoc --go_out=../rpc token.proto protoc --go-grpc_out=../rpc token.proto
|
采用jwt-go做token的验证
package token
import ( "context" "fmt" "time"
"github.com/dgrijalva/jwt-go" "google.golang.org/grpc/metadata" )
func CreateToken(userName string) (tokenString string) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iss": "lora-app-server", "aud": "lora-app-server", "nbf": time.Now().Unix(), "exp": time.Now().Add(time.Hour).Unix(), "sub": "user", "username": userName, }) tokenString, err := token.SignedString([]byte("qqw-1-sec-37")) if err != nil { panic(err) } return tokenString }
type AuthToken struct { Token string }
func (c AuthToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "authorization": c.Token, }, nil }
func (c AuthToken) RequireTransportSecurity() bool { return false }
type Claims struct { jwt.StandardClaims
Username string `json:"username"` }
func getTokenFromContext(ctx context.Context) (string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return "", fmt.Errorf("ErrNoMetadataInContext") } token, ok := md["authorization"] if !ok || len(token) == 0 { return "", fmt.Errorf("ErrNoAuthorizationInMetadata") } return token[0], nil }
func CheckAuth(ctx context.Context) (username string) { tokenStr, err := getTokenFromContext(ctx) if err != nil { panic("get token from context error") } var clientClaims Claims token, err := jwt.ParseWithClaims(tokenStr, &clientClaims, func(token *jwt.Token) (interface{}, error) { if token.Header["alg"] != "HS256" { panic("ErrInvalidAlgorithm") } return []byte("qqw-1-sec-37"), nil }) if err != nil { panic("jwt parse error") }
if !token.Valid { panic("ErrInvalidToken") }
return clientClaims.Username }
|
然后编写服务端代码
注意这个地方token_grpc.pb.go
会自行编译出一个mustEmbedUnimplementedTokenServiceServer()
方法
参考
type TokenServiceServer interface { Login(context.Context, *LoginRequest) (*LoginResp, error) Greet(context.Context, *PingMessage) (*PingMessage, error) mustEmbedUnimplementedTokenServiceServer() }
|
可以在编译时加参数取消这个方法
protoc --go_out=../rpc --go-grpc_out=require_unimplemented_servers=false:../rpc token.proto
或者通过结构体嵌套方式默认实现这个方法,否则在gprc服务端注册服务的时候会报错
package main
import ( service "LearnTest/rpc" "LearnTest/token" "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/reflection" "net" )
func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { fmt.Printf("request gRPC method: %s, %v\n", info.FullMethod, req) resp, err := handler(ctx, req) fmt.Printf("response gRPC method: %s, %v\n", info.FullMethod, resp) return resp, err }
type login struct { service.UnimplementedTokenServiceServer }
func (login *login) Login(ctx context.Context, request *service.LoginRequest) (resp *service.LoginResp, err error) { if request.Username == "qqw" && request.Password == "qqw" { token := token.CreateToken(request.Username) return &service.LoginResp{Status: "200", Token: token}, nil } return &service.LoginResp{Status: "401", Token: ""}, nil }
func (login *login) Greet(ctx context.Context, request *service.PingMessage) (resp *service.PingMessage, err error) { auth := token.CheckAuth(ctx) return &service.PingMessage{Greet: auth}, nil }
func main() {
lis, err := net.Listen("tcp", ":2333") if err != nil { fmt.Printf("failed to listen: %v\n", err) return }
creds, err := credentials.NewServerTLSFromFile("/Users/liuke/GolandProjects/LearnTest/ssl/server.crt", "/Users/liuke/GolandProjects/LearnTest/ssl/server.key") if err != nil { grpclog.Fatalf("Failed to generate credentials %v", err) }
s := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(LoggingInterceptor)) service.RegisterTokenServiceServer(s, &login{})
reflection.Register(s) fmt.Println("Listening on port 2333...") err = s.Serve(lis)
}
|
接着编写客户端代码
package main
import ( service "LearnTest/rpc" Token "LearnTest/token" "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" )
func main() { var err error var opts []grpc.DialOption
creds, err := credentials.NewClientTLSFromFile("/Users/liuke/GolandProjects/LearnTest/ssl/ca.crt", "www.qqw.com") if err != nil { grpclog.Fatalf("Failed to create TLS credentials %v", err) return } opts = append(opts, grpc.WithTransportCredentials(creds))
conn, err := grpc.Dial(":2333", opts...) if err != nil { fmt.Printf("failed to connect: %v\n", err) return } defer conn.Close()
c := service.NewTokenServiceClient(conn) r, err := c.Login(context.Background(), &service.LoginRequest{Username: "qqw1", Password: "qqw"}) if err != nil { fmt.Printf("could not login: %v\n", err) return } requestToken := new(Token.AuthToken) requestToken.Token = r.Token
conn, err = grpc.Dial(":2333", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(requestToken)) if err != nil { fmt.Printf("failed to connect: %v\n", err) return } defer conn.Close() c = service.NewTokenServiceClient(conn) greet, err := c.Greet(context.Background(), &service.PingMessage{Greet: "调用greet"}) if err != nil { fmt.Printf("could not greet: %v\n", err) return }
fmt.Printf("Greeting: %s, %s\n", r.Token, greet) }
|
正常登录请求返回200,登录认证不通过返回401
参考
https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIzNDcwNjQxMg==&action=getalbum&album_id=1705166100159594498&scene=173&from_msgid=2247484803&from_itemidx=1&count=3&nolastread=1#wechat_redirect
https://www.bilibili.com/video/BV1S24y1U7Xp?vd_source=81993e830ebcf48a17d58eb0b7f3a7c2