背景
在之前的文章《漫談微服務》我已經簡單的介紹過微服務,微服務特性是輕量級跨平台和跨語言的服務,也列舉了比較了集中微服務通信的手段的利弊,本文將通過RPC通信的方式實現一個增刪查Redis的輕量級微服務示例,大部分內容翻譯自文章《Microservice in golang, using Redis and gRPC》,中間加上自己的實踐和理解。
實驗環境
Mac OS
go version go1.12.4 darwin/amd64
Docker version 18.09.2, build 6247962
代碼倉庫
https://github.com/felipeinfantino/microservice-golang
微服務實踐
gRPC代碼生成
選用gRPC的原因是因為gRPC本身是一款開源且高性能的RPC框架,支持跨平台,支持golang,java,c,C++ 等10多種編程語言。因為我們要實現一個通過gRPC通信的基於Redis 數據庫的增刪改微服務,所以我們首先需要定義一個gRPC的通信描述文件server.proto:
syntax = "proto3"; package proto; // Server Requests message SetRequest{ string key = 1; string value = 2; } message GetRequest{ string key = 1; } message DeleteRequest{ string key = 1; } // Server Response message ServerResponse{ bool success = 1; string value = 2; string error = 3; } // Define service service BasicService{ rpc Set(SetRequest) returns (ServerResponse); rpc Get(GetRequest) returns (ServerResponse); rpc Delete(DeleteRequest) returns (ServerResponse); }
想要將上面的server.proto文件轉換為golang代碼需要安裝protocol buffer的編譯器:
1.下載https://github.com/protocolbuffers/protobuf/releases/tag/v3.11.0中的protoc-3.11.0-osx-x86_64.zip包。
2.解壓拷貝里面的二進制protoc及google子目錄到該示例工程目錄下。
3.通過上面定義的server.proto 生成golang代碼,可以看到proto目錄下生成了service.pb.go文件。
./protoc --proto_path=proto --proto_path=google --go_out=plugins=grpc:proto service.proto
如果還不行可以參考protocol buffer官方安裝詳細步驟 https://github.com/protocolbuffers/protobuf
工程代碼目錄結構如下:
. ├── Dockerfile ├── README.md ├── database │ ├── database.model.go │ ├── errors.go │ └── redis.go ├── docker-compose.yml ├── go.mod ├── go.sum ├── google │ └── protobuf │ ├── any.proto │ ├── api.proto │ ├── compiler │ │ └── plugin.proto │ ├── descriptor.proto │ ├── duration.proto │ ├── empty.proto │ ├── field_mask.proto │ ├── source_context.proto │ ├── struct.proto │ ├── timestamp.proto │ ├── type.proto │ └── wrappers.proto ├── main ├── main.go ├── proto │ ├── service.pb.go │ └── service.proto └── protoc
Redis服務
package database // Database abstraction type Database interface { Set(key string, value string) (string, error) Get(key string) (string, error) Delete(key string) (string, error) } // Factory looks up acording to the databaseName the database implementation func Factory(databaseName string) (Database, error) { switch databaseName { case "redis": return createRedisDatabase() default: return nil, &NotImplementedDatabaseError{databaseName} } }
定義了一個Database的接口,里面含有增刪查三種方法,只要實現了這三種方法的數據庫都可以作為該微服務的數據庫,所以提供一個工廠函數供用戶后續擴展,目前只實現了Redis一種存儲。Redis的實現直接引用了開源第三方Redis操作用github/go-redis/redis 然后封裝了上面增刪查三種方法。這里就不展開講redis實現了。
主程序
然后就是我們的golang主程序,程序邏輯為開啟gRPC服務端,提供增刪查三個接口及響應。代碼如下:
func main() { listener, err := net.Listen("tcp", ":4040") if err != nil { panic(err) // The port may be on use } srv := grpc.NewServer() databaseImplementation := os.Args[1] db, err = database.Factory(databaseImplementation) if err != nil { panic(err) } proto.RegisterBasicServiceServer(srv, &server{}) fmt.Println("Prepare to serve") if e := srv.Serve(listener); e != nil { panic(err) } } func (s *server) Set(ctx context.Context, in *proto.SetRequest) (*proto.ServerResponse, error) { value, err := db.Set(in.GetKey(), in.GetValue()) return generateResponse(value, err) } func (s *server) Get(ctx context.Context, in *proto.GetRequest) (*proto.ServerResponse, error) { value, err := db.Get(in.GetKey()) return generateResponse(value, err) } func (s *server) Delete(ctx context.Context, in *proto.DeleteRequest) (*proto.ServerResponse, error) { value, err := db.Delete(in.GetKey()) return generateResponse(value, err) } func generateResponse(value string, err error) (*proto.ServerResponse, error) { if err != nil { return &proto.ServerResponse{Success: false, Value: value, Error: err.Error()}, nil } return &proto.ServerResponse{Success: true, Value: value, Error: ""}, nil }
啟動服務
為了測試方便用docker-compose定義了我們的微服務,對docker-compose不太熟悉的朋友可以簡單的看下我之前寫的《利用Docker Compose快速搭建本地測試環境》這篇文章。該服務的docker-compose.yaml內容如下:
version: "3.7" services: server: build: . ports: - "4040:4040" depends_on: - redis redis: container_name: redis_container image: redis
可以看出我們通過暴露4040端口提供我們的服務,服務依賴於redis,所以redis服務會在我們服務之前以容器的方式被拉起來。微服務的啟動命令可以從Dockerfile中獲取:
FROM golang:latest RUN mkdir /app ADD . /app/ WORKDIR /app EXPOSE 4040 CMD ["go", "run", "main.go", "redis"]
拉起服務:
docker-compose up Starting redis_container ... done Starting microservice-golang_server_1 ... done Attaching to redis_container, microservice-golang_server_1 redis_container | 1:C 22 Dec 2019 13:11:10.761 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo redis_container | 1:C 22 Dec 2019 13:11:10.761 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=1, just started redis_container | 1:C 22 Dec 2019 13:11:10.761 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf redis_container | 1:M 22 Dec 2019 13:11:10.763 * Running mode=standalone, port=6379. redis_container | 1:M 22 Dec 2019 13:11:10.763 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. redis_container | 1:M 22 Dec 2019 13:11:10.763 # Server initialized redis_container | 1:M 22 Dec 2019 13:11:10.763 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. redis_container | 1:M 22 Dec 2019 13:11:10.764 * DB loaded from disk: 0.000 seconds redis_container | 1:M 22 Dec 2019 13:11:10.764 * Ready to accept connections server_1 | Prepare to serve
通過docker ps看到啟動了兩個容器,一個是redis,一個是我們的主程序:
測試
測試的客戶端用的gRPC的圖形化工具BloomRPC,安裝方法比較簡單:brew cask install bloomrpc
然后導入我們的gRPC定義文件server.proto就能點擊測試:
總結
本文從工程實踐的角度帶讀者實現了一個通過gRPC通信的增刪查Redis的微服務,希望對讀者有所啟發。