原文以下:html
rpcx是一個相似阿里巴巴 Dubbo 和微博 Motan 的分佈式的RPC服務框架,基於Golang net/rpc實現。git
談起分佈式的RPC框架,比較出名的是阿里巴巴的dubbo,包括由噹噹網維護的dubbox。
不知道dubbo在阿里的內部競爭中敗給了HSF,仍是阿里有意將其閉源了,官方的代碼使用的spring還停留在2.5.6.SEC03的版本,dubbox的spring也只升級到3.2.9.RELEASE。
無論怎樣,dubbo仍是在電商企業獲得普遍的應用,京東也有部分在使用dubbo開發。程序員
DUBBO是一個分佈式服務框架,致力於提供高性能和透明化的RPC遠程服務調用方案,是阿里巴巴SOA服務化治理方案的核心框架,天天爲2,000+個服務提供3,000,000,000+次訪問量支持,並被普遍應用於阿里巴巴集團的各成員站點。微博的RPC框架 Motan 也正式開源了,如張雷所說:github
2013 年微博 RPC 框架 Motan 在前輩大師們(福林、fishermen、小麥、王喆等)的精心設計和辛勤工做中誕生,向各位大師們致敬,也獲得了微博各個技術團隊的鼎力支持及不斷完善,現在 Motan 在微博平臺中已經普遍應用,天天爲數百個服務完成近千億次的調用。golang
這兩個個優秀的框架都是使用Java開發的,國外的互聯網企業也有很是出名的的RPC框架如 thrift 、 finagle 。算法
本項目 rpcx 的目標就是實現一個Go生態圈的Dubbo,爲Go生態圈提供一個分佈式的、多插件的、帶有服務治理功能的產品級的RPC框架。spring
Go生態圈已經有一些RPC庫,如官方的 net/rpc 、 grpc-go 、 gorilla-rpc 等,爲何還要開發 rpcx 呢?apache
緣由在於儘管這些框架都是爲Go實現的RPC庫,可是它們的功能比較單一,只是實現了點對點(End-to-End)的通信框架。缺少服務治理的功能,好比服務註冊和發現、負載均衡、容災、服務監控等功能。所以我基於Go net/rpc框架實現了一個相似Dubbo的分佈式框架。編程
和rpcx比較相似的Go RPC框架是 go-micro ,可是rpcx提供了更豐富的功能,基於TCP的通信協議性能更好。json
遠程過程調用(英語:Remote Procedure Call,縮寫爲 RPC)是一個計算機通訊協議。該協議容許運行於一臺計算機的程序調用另外一臺計算機的子程序,而程序員無需額外地爲這個交互做用編程。若是涉及的軟件採用面向對象編程,那麼遠程過程調用亦可稱做遠程調用或遠程方法調用,例:Java RMI。簡單地說就是能使應用像調用本地方法同樣的調用遠程的過程或服務。很顯然,這是一種client-server的交互形式,調用者(caller)是client,執行者(executor)是server。典型的實現方式就是request–response通信機制。
RPC 是進程之間的通信方式(inter-process communication, IPC), 不一樣的進程有不一樣的地址空間。
若是client和server在同一臺機器上,儘管物理地址空間是相同的,可是虛擬地址空間不一樣。
若是它們在不一樣的主機上,物理地址空間也不一樣。
RPC的實現的技術各不相同,也不必定兼容。
一個正常的RPC過程能夠分紅下面幾步:
RPC只是描繪了 Client 與 Server 之間的點對點調用流程,包括 stub、通訊、RPC 消息解析等部分,在實際應用中,還須要考慮服務的高可用、負載均衡等問題,因此產品級的 RPC 框架除了點對點的 RPC 協議的具體實現外,還應包括服務的發現與註銷、提供服務的多臺 Server 的負載均衡、服務的高可用等更多的功能。目前的 RPC 框架大體有兩種不一樣的側重方向,一種偏重於服務治理,另外一種偏重於跨語言調用。
服務治理型的 RPC 框架有 Dubbo、DubboX、Motan 等,這類的 RPC 框架的特色是功能豐富,提供高性能的遠程調用以及服務發現及治理功能,適用於大型服務的微服務化拆分以及管理,對於特定語言(Java)的項目能夠十分友好的透明化接入。但缺點是語言耦合度較高,跨語言支持難度較大。
跨語言調用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等,這一類的 RPC 框架重點關注於服務的跨語言調用,可以支持大部分的語言進行語言無關的調用,很是適合於爲不一樣語言提供通用遠程服務的場景。但這類框架沒有服務發現相關機制,實際使用時通常須要代理層進行請求轉發和負載均衡策略控制。
本項目 rpcx 屬於服務治理類型,是一個基於 Go 開發的高性能的輕量級 RPC 框架,Motan 提供了實用的服務治理功能和基於插件的擴展能力。
rpcx使用Go實現,適合使用Go語言實現RPC的功能。
rpcx目標是輕量級的,小而簡單,可是指望全部的功能均可以經過插件的方式搭積木的方式完成。
rpcx中有服務提供者 RPC Server,服務調用者 RPC Client 和服務註冊中心 Registry 三個角色。
當前rpcx支持zookeeper, etcd等註冊中心,Consul註冊中心正在開發中。
rpcx基於Go net/rpc的底層實現, Client和Server之間通信是經過TCP進行通信的,它們之間經過Client發送Request,Server返回Response實現。
Request和Response消息的格式都是 Header+Body
的格式。Header和Body具體的格式根據編碼方式的不一樣而不一樣,能夠是二進制,也能夠是結構化數據如JSON。
rpcx擁有衆多特性。
rpcx當前支持多種序列化/反序列化的方式,能夠根據需求選擇合適的編碼庫。
特性 | 功能描述 |
---|---|
gob | 官方提供的序列化方式,基於一個包含元數據的流 |
jsonrpc | 也是官方提供的編碼庫,以JSON格式傳輸 |
msgp | 相似json格式的編碼,可是更小更快,能夠直接編碼struct |
gencode | 一個超級快的序列化庫,須要定義schema,可是定義方式和struct相似 |
protobuf | Google推出的廣受關注的序列化庫,推薦使用 gogo-protobuf ,能夠得到更高的性能 |
在數據結構簡單的狀況下,這幾種庫均可以知足需求,參照本文中的benchmark測試。可是若是追求性能,建議採用後面三種序列化庫。
序列化庫的選擇對於RPC服務的影響是巨大的,我建立了另一個項目專門比較各序列化庫的性能: gosercomp 。
新的序列化庫的實現也很是簡單,只需實現下面兩個方法便可:
funcNewXXXXXServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec { …… } funcNewXXXXXClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec { …… }
編碼庫負責marshal/unmarshal Reuqest/Response, 包括消息中的Header和Body。若是你想,你也能夠對Header和Body實現不一樣的編碼。
目前提供了兩種註冊中心:
註冊中心的配置只需在服務器初始化的時候增長如下代碼,服務的實現無需作任何的改動,也不須要額外的配置。
plugin := &ZooKeeperRegisterPlugin{ ServiceAddress: "tcp@127.0.0.1:1234", ZooKeeperServers: []string{"127.0.0.1:2181"}, BasePath: "/betterrpc", metrics: metrics.NewRegistry(), Services: make([]string,1), updateInterval: time.Minute, } server.PluginContainer.Add(plugin)
其中ServiceAddress爲本機(Server)要暴露給Client地址。由於ZooKeeper的節點名不支持"/",因此此處用"@"代替"://"。
ZooKeeperServers爲ZK集羣的地址。
BasePath爲一個服務組,此組下的服務對於Client都是可見的。
etcd能夠經過TTL判斷服務器的存活,另外此插件也會定時把調用次數定時更新到etcd。
此插件可使用下面的代碼配置:
plugin := &EtcdRegisterPlugin{ ServiceAddress: "tcp@127.0.0.1:1234", EtcdServers: []string{"http://127.0.0.1:2379"}, BasePath: "/betterrpc", metrics: metrics.NewRegistry(), Services: make([]string,1), updateInterval: time.Minute, } server.PluginContainer.Add(plugin)
注意註冊中心插件必須在配置服務以前設置,不然註冊中心沒法獲取要註冊的服務信息。
當前rpcx爲server提供瞭如下擴展點:
你能夠根據這些擴展點編寫本身的插件,只需實現相應的接口便可。定義的接口你能夠看godoc的IXXXXXXPlugin的定義。
上面介紹的註冊中心就是經過插件的方式實現。同時rpcx還實現了其它的插件,以下面的介紹。
負載均衡是經過不一樣的ClientSelector來實現的。
負載均衡器 | 功能描述 |
---|---|
DirectClientSelector | 點對點的直連,客戶端直接鏈接一個服務器 |
MultiClientSelector | 多對多的直連,一個客戶端能夠從一組固定的服務器中選擇一個直連,無需註冊中心 |
ZooKeeperClientSelector | 從ZK註冊中心選擇一個服務器鏈接 |
EtcdClientSelector | 從Etcd註冊中心選擇一個服務器鏈接 |
一個Selector須要實現ClientSelector接口:
typeClientSelectorinterface{ Select(clientCodecFunc ClientCodecFunc) (*rpc.Client, error) }
Client的序列化方式必須和服務器的序列化方式保持一致。
Client提供了兩種容錯方式: Failfast
、 Failover
、 Failtry
:
對於多個服務器,重選發送支持:
Client的擴展點以下:
點對點的實現和Go net/rpc的使用基本一致。
packagemain import"github.com/smallnest/rpcx" typeArgsstruct{ A int`msg:"a"` B int`msg:"b"` } typeReplystruct{ C int`msg:"c"` } typeArithint func(t *Arith) Mul(args *Args, reply *Reply) error { reply.C = args.A * args.B returnnil } func(t *Arith) Error(args *Args, reply *Reply) error { panic("ERROR") } funcmain() { server := rpcx.NewServer() server.RegisterName("Arith",new(Arith)) server.Serve("tcp","127.0.0.1:8972") }
同步方式:
packagemain import( "fmt" "time" "github.com/smallnest/rpcx" ) typeArgsstruct{ A int`msg:"a"` B int`msg:"b"` } typeReplystruct{ C int`msg:"c"` } funcmain() { s := &rpcx.DirectClientSelector{Network: "tcp", Address:"127.0.0.1:8972", Timeout:10* time.Second} client := rpcx.NewClient(s) args := &Args{7,8} varreply Reply err := client.Call("Arith.Mul", args, &reply) iferr !=nil{ fmt.Printf("error for Arith: %d*%d, %v \n", args.A, args.B, err) } else{ fmt.Printf("Arith: %d*%d=%d \n", args.A, args.B, reply.C) } client.Close() }
異步方式(經過Channel得到執行結果):
packagemain import( "fmt" "time" "github.com/smallnest/rpcx" ) typeArgsstruct{ A int`msg:"a"` B int`msg:"b"` } typeReplystruct{ C int`msg:"c"` } funcmain() { s := &rpcx.DirectClientSelector{Network: "tcp", Address:"127.0.0.1:8972", Timeout:10* time.Second} client := rpcx.NewClient(s) args := &Args{7,8} varreply Reply divCall := client.Go("Arith.Mul", args, &reply,nil) replyCall := <-divCall.Done // will be equal to divCall ifreplyCall.Error !=nil{ fmt.Printf("error for Arith: %d*%d, %v \n", args.A, args.B, replyCall.Error) } else{ fmt.Printf("Arith: %d*%d=%d \n", args.A, args.B, reply.C) } client.Close() }
這裏例子啓動了兩個服務器,其中一個服務器故意將 7 * 8
計算成 560
,以便和另一個服務器進行區分,咱們能夠觀察計算結果。
packagemain import"github.com/smallnest/rpcx" typeArgsstruct{ A int`msg:"a"` B int`msg:"b"` } typeReplystruct{ C int`msg:"c"` } typeArithint func(t *Arith) Mul(args *Args, reply *Reply) error { reply.C = args.A * args.B returnnil } func(t *Arith) Error(args *Args, reply *Reply) error { panic("ERROR") } typeArith2int func(t *Arith2) Mul(args *Args, reply *Reply) error { reply.C = args.A * args.B *10 returnnil } func(t *Arith2) Error(args *Args, reply *Reply) error { panic("ERROR") } funcmain() { server1 := rpcx.NewServer() server1.RegisterName("Arith",new(Arith)) server1.Start("tcp","127.0.0.1:8972") server2 := rpcx.NewServer() server2.RegisterName("Arith",new(Arith2)) server2.Serve("tcp","127.0.0.1:8973") }
隨機選取服務器的例子:
packagemain import( "fmt" "time" "github.com/smallnest/rpcx" "github.com/smallnest/rpcx/clientselector" ) typeArgsstruct{ A int`msg:"a"` B int`msg:"b"` } typeReplystruct{ C int`msg:"c"` } funcmain() { server1 := clientselector.ServerPair{Network: "tcp", Address:"127.0.0.1:8972"} server2 := clientselector.ServerPair{Network: "tcp", Address:"127.0.0.1:8973"} servers := []clientselector.ServerPair{server1, server2} s := clientselector.NewMultiClientSelector(servers, rpcx.RandomSelect,10*time.Second) fori :=0; i <10; i++ { callServer(s) } } funccallServer(s rpcx.ClientSelector) { client := rpcx.NewClient(s) args := &Args{7,8} varreply Reply err := client.Call("Arith.Mul", args, &reply) iferr !=nil{ fmt.Printf("error for Arith: %d*%d, %v \n", args.A, args.B, err) } else{ fmt.Printf("Arith: %d*%d=%d \n", args.A, args.B, reply.C) } client.Close() }
RoundRobin選取服務器的例子
packagemain import( "fmt" "time" "github.com/smallnest/rpcx" "github.com/smallnest/rpcx/clientselector" ) typeArgsstruct{ A int`msg:"a"` B int`msg:"b"` } typeReplystruct{ C int`msg:"c"` } funcmain() { server1 := clientselector.ServerPair{Network: "tcp", Address:"127.0.0.1:8972"} server2 := clientselector.ServerPair{Network: "tcp", Address:"127.0.0.1:8973"} servers := []clientselector.ServerPair{server1, server2} s := clientselector.NewMultiClientSelector(servers, rpcx.RoundRobin,10*time.Second) fori :=0; i <10; i++ { callServer(s) } } funccallServer(s rpcx.ClientSelector) { client := rpcx.NewClient(s) args := &Args{7,8} varreply Reply err := client.Call("Arith.Mul", args, &reply) iferr !=nil{ fmt.Printf("error for Arith: %d*%d, %v \n", args.A, args.B, err) } else{ fmt.Printf("Arith: %d*%d=%d \n", args.A, args.B, reply.C) } client.Close() }
Failover
packagemain import( "fmt" "time" "github.com/smallnest/rpcx" "github.com/smallnest/rpcx/clientselector" ) typeArgsstruct{ A int`msg:"a"` B int`msg:"b"` } typeReplystruct{ C int`msg:"c"` } funcmain() { server1 := clientselector.ServerPair{Network: "tcp", Address:"127.0.0.1:8972"} server2 := clientselector.ServerPair{Network: "tcp", Address:"127.0.0.1:8973"} server3 := clientselector.ServerPair{Network: "tcp", Address:"127.0.0.1:8974"} servers := []clientselector.ServerPair{server1, server2, server3} s := clientselector.NewMultiClientSelector(servers, rpcx.RoundRobin,10*time.Second) fori :=0; i <10; i++ { callServer(s) } } funccallServer(s rpcx.ClientSelector) { client := rpcx.NewClient(s) client.FailMode = rpcx.Failover args := &Args{7,8} varreply Reply err := client.Call("Arith.Mul", args, &reply) iferr !=nil{ fmt.Printf("error for Arith: %d*%d, %v \n", args.A, args.B, err) } else{ fmt.Printf("Arith: %d*%d=%d \n", args.A, args.B, reply.C) } client.Close() }
rpcx基於Go net/rpc框架實現,它的插件機制並不會帶來多少性能的損失,以下面的測試,rpcx性能和官方的Go net/rpc持平。
[root@localhostrpcx]# go test -bench . -test.benchmem PASS BenchmarkNetRPC_gob-1610000018742ns/op321B/op9allocs/op BenchmarkNetRPC_jsonrpc-1610000021360ns/op1170B/op31allocs/op BenchmarkNetRPC_msgp-1610000018617ns/op776B/op35allocs/op BenchmarkRPCX_gob-1610000018718ns/op320B/op9allocs/op BenchmarkRPCX_json-1610000021238ns/op1170B/op31allocs/op BenchmarkRPCX_msgp-1610000018635ns/op776B/op35allocs/op BenchmarkRPCX_gencodec-1610000018454ns/op4485B/op17allocs/op BenchmarkRPCX_protobuf-1610000017234ns/op733B/op13allocs/op
參考資料:
RPCX: http://www.tuicool.com/m/articles/vYB3euv
go-micro: https://github.com/micro/go-micro
https://blog.micro.mu/2016/03/28/go-micro.html