涂鸦智能dubbo-go亿级流量的实践与探索
云棲號資訊:【點擊查看更多行業資訊】
在這里您可以找到不同行業的第一手的上云資訊,還在等什么,快來!
dubbo 是一個基于 Java 開發的高性能的輕量級 RPC 框架,dubbo 提供了豐富的服務治理功能和優秀的擴展能力。而 dubbo-go 在 java 與 golang 之間提供統一的服務化能力與標準,是涂鴉智能目前最需要解決的主要問題。本文分為實踐和快速接入兩部分,分享在涂鴉智能的 dubbo-go 實戰經驗,意在幫助用戶快速接入 dubbo-go RPC 框架,希望能讓大家少走些彎路。另外,文中的測試代碼基于 dubbo-go版本 v1.4.0。
dubbo-go 網關實踐
?
dubbo-go 在涂鴉智能的使用情況如上圖,接下來會為大家詳細介紹落地細節,希望這些在生產環境中總結的經驗能夠幫助到大家。
1. 背景
在涂鴉智能,dubbo-go 已經作為了 golang 服務與原有 dubbo 集群打通的首選 RPC 框架。其中比較有代表性的 open-gateway 網關系統(下文統一稱 gateway,開源版本見?https://github.com/dubbogo/dubbo-go-proxy)。該 gateway 動態加載內部 dubbo 接口信息,以HTTP API 的形式對外暴露。該網關意在解決上一代網關的以下痛點。
- 通過頁面配置 dubbo 接口開放規則,步驟繁瑣,權限難以把控;
- 接口非 RESTful 風格,對外部開發者不友好;
- 依賴繁重,升級風險大;
- 并發性能問題。
2. 架構設計
針對如上痛點,隨即著手準備設計新的 gateway 架構。首先就是語言選型,golang 的協程調用模型使得 golang 非常適合構建 IO 密集型的應用,且應用部署上也較 java 簡單。
經過調研后我們敲定使用 golang 作為 proxy 的編碼語言,并使用 dubbo-go 用于連接 dubbo provider 集群。provider 端的業務應用通過使用 java 的插件,以注解形式配置 API 配置信息,該插件會將配置信息和 dubbo 接口元數據更新到元數據注冊中心(下圖中的 redis )。這樣一來,配置從管理后臺頁面轉移到了程序代碼中。開發人員在編碼時,非常方便地看到 dubbo 接口對外的 API 描述,無需從另外一個管理后臺配置 API 的使用方式。
?
3. 實踐
從上圖可以看到,網關能動態加載 dubbo 接口信息,調用 dubbo 接口是基于 dubbo 泛化調用。泛化調用使 client 不需要構建 provider 的 interface 代碼,在 dubbo-go 中表現為無需調用 config.SetConsumerService 和 hessian.RegisterPOJO 方法,而是將請求模型純參數完成,這使得 client 動態新增、修改接口成為可能。在 apache / dubbo-sample / golang / generic / go-client 中的有泛化調用的演示代碼。
func test() { var appName = "UserProviderGer" var referenceConfig = config.ReferenceConfig{ InterfaceName: "com.ikurento.user.UserProvider", Cluster: "failover", Registry: "hangzhouzk", Protocol: dubbo.DUBBO, Generic: true, } referenceConfig.GenericLoad(appName) // appName is the unique identification of RPCService time.Sleep(3 * time.Second) resp, err := referenceConfig.GetRPCService().(*config.GenericService). Invoke([]interface{}{"GetUser", []string{"java.lang.String"}, []interface{}{"A003"}}) if err != nil { panic(err) } }泛化調用的實現其實相當簡單。其功能作用在 dubbo 的 Filter 層中。Generic Filter 已經作為默認開啟的 Filter 加入到 dubbo Filter 鏈中。其核心邏輯如下:
func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 { oldArguments := invocation.Arguments() if oldParams, ok := oldArguments[2].([]interface{}); ok { newParams := make([]hessian.Object, 0, len(oldParams)) for i := range oldParams { newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i]))) } newArguments := []interface{}{ oldArguments[0], oldArguments[1], newParams, } newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments()) newInvocation.SetReply(invocation.Reply()) return invoker.Invoke(ctx, newInvocation) } } return invoker.Invoke(ctx, invocation) }Generic Filter 將用戶請求的結構體參數轉化為統一格式的 map(代碼中的 struct2MapAll ),將類( golang 中為 struct )的正反序列化操作變成 map 的正反序列化操作。這使得無需 POJO 描述通過硬編碼注入 hessain 庫。
從上面代碼可以看到,泛化調用實際需要動態構建的內容有 4 個,ReferenceConfig 中需要的 InterfaceName、參數中的 method、ParameterTypes、實際入參 requestParams。
那么這些參數是如何從 HTTP API 匹配獲取到的呢?
這里就會用到上文提到的 provider 用于收集元數據的插件。引入插件后,應用在啟動時會掃描需要暴露的 dubbo 接口,將 dubbo 元數據和 HTTP API 關聯。插件使用方法大致如下,這里調了幾個簡單的配置作為示例,實際生產時注解內容會更多。
?
最終獲得的 dubbo 元數據如下:
{ "key": "POST:/hello/{uid}/add", "interfaceName": "com.tuya.hello.service.template.IUserServer", "methodName": "addUser", "parameterTypes": ["com.tuya.gateway.Context", "java.lang.String", "com.tuya.hello.User"], "parameterNames": ["context", "uid", "userInfo"], "updateTimestamp": "1234567890", "permissionDO":{}, "voMap": { "userInfo": { "name": "java.lang.String", "sex": "java.lang.String", "age": "java.lang.Integer" } }, "parameterNameHumpToLine": true, "resultFiledHumpToLine": false, "protocolName": "dubbo", ....... }Gateway 從元數據配置中心訂閱到以上信息,就能把一個 API 請求匹配到一個 dubbo 接口。再從 API 請求中抓取參數作為入參。這樣功能就完成了流量閉環。
以上內容,大家應該對此 gateway 的項目拓撲結構有了清晰的認知。我接著分享項目在使用 dubbo-go 過程中遇到的問題和調優經驗。19 年初,當時的 dubbo-go 項目還只是構建初期,沒有什么用戶落地的經驗。我也是一邊參與社區開發,一邊編碼公司內部網關項目。在解決了一堆 hessain 序列化和 zookeeper 注冊中心的問題后,項目最終跑通了閉環。但是,作為一個核心應用,跑通閉環離上生產環境還有很長的路要走,特別是使用了當時穩定性待測試的新框架。整個測試加上功能補全,整整花費了一個季度的時間,直到項目趨于穩定,壓測效果也良好。單臺網關機器( 2C 8G )全鏈路模擬真實環境壓測達到 2000 QPS。由于引入了比較重的業務邏輯(單個請求平均調用 3 個 dubbo 接口),對于這個壓測結果,是符合甚至超出預期的。
總結了一些 dubbo-go 參數配置調優的經驗,主要是一些網絡相關配置。
大家在跑 demo 時,應該會看到配置文件最后有一堆配置,但如果對 dubbo-go 底層網絡模型不熟悉,就很難理解這些配置的含義。目前 dubbo-go 網絡層以 getty 為底層框架,實現讀寫分離和協程池管理。getty 對外暴露 session 的概念,session 提供一系列網絡層方法注入的實現,因為本文不是源碼解析文檔,在這里不過多論述。讀者可以簡單的認為 dubbo-go 維護了一個 getty session池,session 又維護了一個 TCP 連接池。對于每個連接,getty 會有讀協程和寫協程伴生,做到讀寫分離。這里我盡量用通俗的注釋幫大家梳理下對性能影響較大的幾個配置含義:
protocol_conf: # 這里是協議獨立的配置,在dubbo協議下,大多數配置即為getty session相關的配置。 dubbo: # 一個session會始終保證connection_number個tcp連接個數,默認是16, # 但這里建議大家配置相對小的值,一般系統不需要如此多的連接個數。 # 每隔reconnect_interval時間,檢查連接個數,如果小于connection_number, # 就建立連接。填0或不填都為默認值300ms reconnect_interval: 0 connection_number: 2 # 客戶端發送心跳的間隔 heartbeat_period: "30s" # OnCron時session的超時時間,超過session_timeout無返回就關閉session session_timeout: "30s" # 每一個dubbo interface的客戶端,會維護一個最大值為pool_size大小的session池。 # 每次請求從session池中select一個。所以真實的tcp數量是session數量*connection_number, # 而pool_size是session數量的最大值。測試總結下來一般程序4個tcp連接足以。 pool_size: 4 # session保活超時時間,也就是超過session_timeout時間沒有使用該session,就會關閉該session pool_ttl: 600 # 處理返回值的協程池大小 gr_pool_size: 1200 # 讀數據和協程池中的緩沖隊列長度,目前已經廢棄。不使用緩沖隊列 queue_len: 64 queue_number: 60 getty_session_param: compress_encoding: false tcp_no_delay: true tcp_keep_alive: true keep_alive_period: "120s" tcp_r_buf_size: 262144 tcp_w_buf_size: 65536 pkg_wq_size: 512 tcp_read_timeout: "1s" # 每次讀包的超時時間 tcp_write_timeout: "5s" # 每次寫包的超時時間 wait_timeout: "1s" max_msg_len: 102400 # 最大數據傳輸長度 session_name: "client"dubbo-go 快速接入
前文已經展示過 dubbo-go 在涂鴉智能的實踐成果,接下來介紹快速接入 dubbo-go 的方式。
第一步:hello world
dubbo-go 使用范例目前和 dubbo 一致,放置在 apache/dubbo-samples 項目中。在 dubbo-sample/golang 目錄下,用戶可以選擇自己感興趣的 feature 目錄,快速測試代碼效果。
tree dubbo-samples/golang -L 1 dubbo-samples/golang ├── README.md ├── async ├── ci.sh ├── configcenter ├── direct ├── filter ├── general ├── generic ├── go.mod ├── go.sum ├── helloworld ├── multi_registry └── registry我們以 hello world 為例,按照 dubbo-samples/golang/README.md 中的步驟,分別啟動 server 和 client 。可以嘗試 golang 調用 java 、 java 調用 golang 、golang 調用 golang 、java 調用 java。dubbo-go 在協議上支持和 dubbo 互通。
我們以啟動 go-server 為例,注冊中心默認使用 zookeeper 。首先確認本地的 zookeeper 是否運行正常。然后執行以下命令,緊接著你就可以看到你的服務正常啟動的日志了。
export ARCH=mac export ENV=dev cd dubbo-samples/golang/helloworld/dubbo/go-server sh ./assembly/$ARCH/$ENV.sh cd ./target/darwin/user_info_server-2.6.0-20200608-1056-dev/ sh ./bin/load.sh start第二步:在項目中使用 dubbo-go
上面,我們通過社區維護的測試代碼和啟動腳本將用例跑了起來。接下來,我們需要在自己的代碼中嵌入 dubbo-go 框架。很多朋友往往是在這一步遇到問題,這里我整理的一些常見問題,希望能幫到大家。
1)環境變量
目前 dubbo-go 有 3 個環境變量需要配置:
- CONF_CONSUMER_FILE_PATH:Consumer 端配置文件路徑,使用 consumer 時必需;
- CONF_PROVIDER_FILE_PATH:Provider 端配置文件路徑,使用 provider 時必需;
- APP_LOG_CONF_FILE:Log 日志文件路徑,必需;
- CONF_ROUTER_FILE_PATH:File Router 規則配置文件路徑,使用 File Router 時需要。
2)代碼注意點
注入服務 : 檢查是否執行以下代碼
客戶端 func init() { config.SetConsumerService(userProvider) } 服務端 func init() { config.SetProviderService(new(UserProvider)) }注入序列化描述 :檢查是否執行以下代碼
hessian.RegisterJavaEnum(Gender(MAN)) hessian.RegisterJavaEnum(Gender(WOMAN)) hessian.RegisterPOJO(&User{})3)正確理解配置文件
references / services 下的 key ,如下面例子的 "UserProvider" 需要和服務 Reference() 返回值保持一致,此為標識改接口的 key。
references: "UserProvider": registry: "hangzhouzk" protocol : "dubbo" interface : "com.ikurento.user.UserProvider" cluster: "failover" methods : name: "GetUser" retries: 3注冊中心如果只有一個注冊中心集群,只需配置一個。多個 IP 用逗號隔開,如下:
registries : "hangzhouzk": protocol: "zookeeper" timeout : "3s" address: "172.16.120.181:2181,172.16.120.182:2181" username: "" password: ""4)java 和 go 的問題
go 和 java 交互的大小寫 :golang 為了適配 java 的駝峰格式,在調用 java 服務時,會自動將 method 和屬性首字母變成小寫。很多同學故意將 java 代碼寫成適配 golang 的參數定義,將首字母大寫,最后反而無法序列化匹配。
第三步:拓展功能
dubbo-go 和 dubbo 都提供了非常豐富的拓展機制。可以實現自定義模塊代替 dubbo-go 默認模塊,或者新增某些功能。比如實現 Cluster、Filter 、Router 等來適配業務的需求。這些注入方法暴露在 dubbo-go/common/extension 中,允許用戶調用及配置。
【云棲號在線課堂】每天都有產品技術專家分享!
課程地址:https://yqh.aliyun.com/live
立即加入社群,與專家面對面,及時了解課程最新動態!
【云棲號在線課堂 社群】https://c.tb.cn/F3.Z8gvnK
原文發布時間:2020-06-15
本文作者:阿里巴巴云原生
本文來自:“dockone”,了解相關信息可以關注“dockone”
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的涂鸦智能dubbo-go亿级流量的实践与探索的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 发光的二次元克拉克拉 满足年轻用户个性化
- 下一篇: CDN百科第三讲|如果用了云服务器,还需