分布式容器集群探索—面向服務框架envoy-grpc-web

系列目錄

分布式容器集群探索—grpc服務框架envoy-grpc-web
分布式容器集群探索—Peer Discovery RabbitMQ(編寫中...)
微服務容器集群探索—Consul服務注冊與發(fā)現(xiàn)(規(guī)劃中...)
服務網(wǎng)格探索—Envoy的xDS與ServiceMesh(規(guī)劃中...)


前言

分布式蛀骇,用于解決大規(guī)模計算分流問題融击,高峰值壓力問題投储,服務復用問題等等敢会。然而,在解決問題的同時锁保,分布式更跟隨帶來了更多的問題偏瓤,如服務管理,服務解耦排龄,服務發(fā)現(xiàn)等等

所以,優(yōu)秀的分布式服務框架一直是大規(guī)模后端的第一需求翎朱。當然橄维,面向服務的框架非常多,而且大部分集中在Java領域拴曲。但是服務架構與語言分離是大方向争舞。后端革命的浪潮中,rest和rpc是兩股中堅的力量

關于rest的方案之前的文章中已經(jīng)有提過不少澈灼,這一次來探索目前rpc框架中十分優(yōu)秀和簡潔的框架——grpc竞川,本文結合了docker容器集群部署店溢,給出了一套精簡小巧的分布式容器集群方案x-grpc
*這一個系列的文章,全部會基于容器技術委乌,在個人能力范圍內(nèi)盡可能的展示各種各樣的集群解決方案床牧,預計路徑規(guī)劃如下:

簡單分布式集群=>
復雜分布式集群=>
混合微服務集群=>
服務網(wǎng)格*=>
...

當然,文章內(nèi)容更多的還是展示集群雛形遭贸,在實際生產(chǎn)需求中叠赦,更是需要添磚加瓦,落筆記錄下來自己的思考和實現(xiàn)過程革砸,更主要的目的還是能夠拋磚引玉除秀,在服務架構領域,能有效參考的資料實在太少算利,相互交流學習是最重要的


規(guī)劃

分布式集群簡化架構圖.png

思路

  1. 【接口定義】grpc通過proto文件定義函數(shù)接口和消息結構
  2. 【服務實現(xiàn)】服務端選擇NodeJS册踩,通過grpc官方所建議的npm包進行proto文件加載和函數(shù)序列化
  3. 【服務調(diào)用】服務通過proto的加載啟動后,客戶端也同樣根據(jù)相同的proto接口調(diào)用效拭,實現(xiàn)不同主機甚至是網(wǎng)絡節(jié)點之間的遠程函數(shù)調(diào)用
  4. 【W(wǎng)EB調(diào)用】在實現(xiàn)了服務主機之間的相互服務調(diào)用之后暂吉,進一步的,人們自然會寄希望直接通過瀏覽器進行rpc服務調(diào)用缎患,因為這可以更進一步的推進前后端的融合與標準化的工程開發(fā)慕的。rest之所以發(fā)光發(fā)熱,很大程度上也是因為其通過標準規(guī)范的推進了前后端的分離與交互挤渔。為了實現(xiàn)這一目標肮街,grpc-web發(fā)布了,實現(xiàn)了通過envoy代理的rpc調(diào)用判导,雖然還不是最終完美的解決方案嫉父,但是伴隨著HTTP2的推進,瀏覽器的更新迭代眼刃,一定在不久后的某一天绕辖,可以不需要任何代理而完成web rpc
  5. 【服務集群化】以上,就可以完成所有客戶端服務擂红,客戶端瀏覽器發(fā)起rpc遠程調(diào)用服務仪际,很自然地,最后一步昵骤,服務集群化后树碱,就可以滿足超大規(guī)模rpc調(diào)用需求,自動擴容涉茧,自動負載赴恨,自動容災等我們的核心需求疹娶。傳統(tǒng)伴栓,完成這樣一套集群,需要大規(guī)模的機房和資深的運維工程師。但是在技術領域蓬勃發(fā)展的今天钳垮,生產(chǎn)力極大的提高惑淳,在云服務的支撐下,利用容器技術饺窿,可以不那么費力的實現(xiàn)這一件目標歧焦。在這一步的思路,docker swarm是我們的主要工具

接口定義

  • 登錄接口
    • 請求消息(帳號肚医,密碼)
    • 返回消息(狀態(tài)碼绢馍,結果)
  • 登出接口
    • 請求消息(帳號)
    • 返回消息(狀態(tài)碼,結果)

接口是解耦的核心肠套,軟件工程化的基石舰涌,有了接口,系統(tǒng)的實現(xiàn)就已經(jīng)完成一半:)

服務實現(xiàn)

  • 登錄服務
    • 接收帳號密碼后你稚,鑒別身份瓷耙,返回登錄結果
  • 登出服務
    • 接收帳號后,鑒別身份刁赖,返回登出結果

服務的實現(xiàn)不依賴語言搁痛,任何語言實現(xiàn)皆可

服務調(diào)用

  • 通過rpc.invoke()調(diào)用函數(shù)

rpc的優(yōu)雅之處在于使得集群主機的服務調(diào)用開發(fā),和單節(jié)點的本地服務調(diào)用開發(fā)幾乎沒有任何區(qū)別宇弛,開發(fā)層無感知

WEB調(diào)用

  • 通過envoy代理瀏覽器的rpc請求鸡典,打通瀏覽器與服務接口,瀏覽器使用grpc-web生成的代碼進行請求

使用rpc模式的接口調(diào)用枪芒,前端人員不需要再關心網(wǎng)絡轿钠,像調(diào)用本地函數(shù)一樣調(diào)用后端接口,同時接口標準全部都以proto文件為準病苗,不再需要原來第三方的接口管理工具例如postman等

服務集群化

  • 使用docker swarm自動組網(wǎng)疗垛,使用overlay負載網(wǎng)絡,全自動的容災和部署rpc集群服務

docker swarm使得集群部署變得容易硫朦,極大的降低了集群部署門檻贷腕,雖然網(wǎng)絡上有很多針對swarm和kubernetes的比較,國內(nèi)環(huán)境很多文章會一邊倒的傾向極為復雜的kubernetes咬展,這一類的文章大多是復制轉載泽裳,有些會淺淺的寫一些分析,但是大部分并沒有表現(xiàn)出對于兩者的深入理解(簡單的東西破婆,就一定比不上復雜的東西涮总?)

的確,作為Google多年來的容器編排管理工具祷舀,復雜的kubernetes有其獨特的優(yōu)越性瀑梗,但是也不可否認docker swarm的簡潔優(yōu)雅烹笔。技術并不分對錯,只有人分抛丽。就好比windows和linux這么多年的恩恩怨怨

方案

接口定義

user.proto

syntax = "proto3";
package demo;

// 服務定義
service User {
  rpc login (LoginReq) returns (LoginRes) {}
  rpc logout (LogoutReq) returns (LogoutRes) {}
}

// 請求返回定義
message LoginReq {
  string username = 1;
  string password = 2;
}

message LoginRes {
  string code = 1;
  string res = 2;
}

message LogoutReq {
  string username = 1;
}

message LogoutRes {
  string code = 1;
  string res = 2;
}

protobuf是Google內(nèi)部的混合語言數(shù)據(jù)標準谤职,可實現(xiàn)跨服務,跨平臺通信亿鲜,因此基于grpc允蜈,分布式的所有計算節(jié)點,都只需要通過proto文件來定義接口

服務實現(xiàn)

user.js

// 服務實現(xiàn)
module.exports = {
  login(call, cb) {
    console.log(`${Date.now()}${JSON.stringify(call.request)}`)
    cb(null, { res: `${call.request.username} 登錄成功` })
  },
  logout(call, cb) {
    console.log(`${Date.now()}${JSON.stringify(call.request)}`)
    cb(null, { res: `${call.request.username} 退出成功`, code: '0' })
  }
}

是的蒿柳,在grpc的框架下饶套,以上兩個文件就是一個服務所必要的所有信息,理論上這兩個文件就是集群服務單元:)

好吧垒探,看到這會有很多困惑凤跑,這服務接口定義和服務編碼實現(xiàn)都有了,可是要怎么調(diào)用呢叛复,不同服務之間仔引,甚至是瀏覽器,要怎么調(diào)用已經(jīng)編碼完成的user服務呢褐奥?所以我們需要一個小巧的grpc服務加載框架

服務加載

  • protobuf的服務加載咖耘,不同語言的官方包是不一樣的,在github上撬码,我開源了一個精簡小巧的nodejs加載grpc服務的框架x-grpc儿倒,在這個加載服務框架中提供以上兩個寫好的接口和實現(xiàn)文件,便可加載grpc服務
  1. 執(zhí)行npm install x-grpc安裝服務加載框架
  2. 編寫config/default.json文件呜笑,指定服務端口以及接口文件和實現(xiàn)文件的根目錄
{
    "grpc": {
        "port": 50051,
        "protosDir": "/src/protos/",
        "implsDir": "/src/impls/",
        "serverAddress": "localhost"
    }
}
  1. 啟動app.js
const RPCServer = require('x-grpc').RPCServer
new RPCServer(config.grpc).run()

x-grpc的加載編碼在此處粘貼出來沒有太大的意義夫否,而且會顯得冗余冗長,但是也不用擔心叫胁,個人追求的是最少的編碼凰慈,源碼非常簡單,對實現(xiàn)有興趣的可以前往github查看源碼

服務調(diào)用

client.js是調(diào)用例子驼鹅,只需要先連接服務后微谓,調(diào)用函數(shù)請求即可完成遠端服務調(diào)用

const RPCClient = require('x-grpc').RPCClient
const rpc = await new RPCClient(config.grpc).connect()
await rpc.invoke('demo.User.login', { username: 'cheney', password: '123456' })
image.png

WEB調(diào)用

  1. 通過protoc工具和protoc-gen-grpc-web插件,根據(jù)proto文件输钩,生成WEB客戶端pb代碼
  ./protoc  \
  --plugin=./protoc-gen-grpc-web  \
  -I=$package $file --js_out=import_style=commonjs:$package  \
  --grpc-web_out=import_style=commonjs,mode=grpcwebtext:$package

$package是proto的包目錄豺型,$file是proto文件

  1. 引入grpc-web生成的pb文件,實例化客戶端买乃,連接遠程服務envoy姻氨,調(diào)用方法即可(可使用包括vue,react在內(nèi)的多種前端框架)
const { LoginReq, LoginRes } = require('./grpc/demo/user_pb.js')
const { UserClient } = require('./grpc/demo/user_grpc_web_pb.js')

let userClient = new UserClient('http://localhost:10000')

let loginReq = new LoginReq()
loginReq.setUsername('cheney')
loginReq.setPassword('123456')
userClient.login(loginReq, {}, (err, res) => {
    console.error(err)
    console.log(res.getRes())
})
  1. 訪問靜態(tài)資源服務http://{staticserver}/x-grpc/web/index.html剪验,瀏覽器控制臺可查看服務返回結果
    image.png

服務集群化

  1. ssh m1 && docker swarm init --advertise-addr [m1主機的IP]
  2. ssh w1 && docker swarm join --token [m1的token]
  3. ssh m1 && docker stack deploy --prune -c docker-compose.yml [stack名稱]
version: '3.7'
#服務集合
services:
  #Docker集群可視化服務
  portainer:
    image: portainer/portainer:latest
    command: ["-H", "unix:///var/run/docker.sock", "--no-auth"]
    ports:
      - "9090:9000"
    networks:
      - overlay
    volumes:
      - portainer:/data
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      replicas: 1
      placement:
        constraints: [node.role == manager]
  #WEB服務代理
  envoy:
    image: cheney/envoy:latest
    ports:
      - '9091:9901'
      - '10000:10000'
    networks:
      - overlay
    deploy:
      replicas: 1
      placement:
        constraints: [node.role == manager]
  #自定義RPC服務
  x-grpc:
    image: cheney/x-grpc:latest
    ports:
      - "50051:50051"
    networks:
      - overlay
    deploy:
      replicas: 1
#卷集合
volumes:
  portainer:
#網(wǎng)絡集合
networks:
  overlay:

只需要一份docker-compose.yml文件即可定義編排所需要的所有容器服務肴焊,在以上的compose文件中前联,所有服務都已經(jīng)封裝成為鏡像

WEB代理—Envoy

通過官方的envoy,需要進行一些配置抖韩,根據(jù)官方文檔得到的envoy的Dockerfile如下

envoy/Dockerfile

FROM envoyproxy/envoy:latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml

envoy.yaml

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 10000 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: grpc_service
                  max_grpc_timeout: 0s
              cors:
                allow_origin:
                - "*"
                allow_methods: GET, PUT, DELETE, POST, OPTIONS
                allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
                max_age: "1728000"
                expose_headers: custom-header-1,grpc-status,grpc-message
                enabled: true
          http_filters:
          - name: envoy.grpc_web
          - name: envoy.cors
          - name: envoy.router
  clusters:
  - name: grpc_service
    connect_timeout: 0.25s
    type: logical_dns
    http2_protocol_options: {}
    lb_policy: round_robin
    hosts: [{ socket_address: { address: {stack名稱}_x-grpc, port_value: 50051 }}]

通過構建以上Dockerfile就可以得到comose文件中所引用的鏡像cheney/envoy

這里可以提一下envoy蛀恩,這是一個現(xiàn)代化的面向服務的代理疫铜,功能極為強大(徹底替代nginx)茂浮,圍繞著它為核心,拓展出了后來的Service Mesh(服務網(wǎng)格)istio壳咕。這是后話了席揽,后續(xù)的文章中會逐漸說明

envoy的配置文件稍微有一點復雜,但是簡單說主要目的就是10000端口網(wǎng)絡請求轉向50051端口谓厘,而50051端口正是grpc服務所監(jiān)聽的端口幌羞,所以envoy在這里起到了連接web瀏覽器與grpc服務的作用

當使用docker stack deploy命令一鍵部署完成后,我們的服務會全自動通過overlay網(wǎng)絡竟稳,將所有鏡像分發(fā)下載至所有主機属桦,并且自動根據(jù)所配置的服務數(shù)量,自動擴容他爸,我們可以透過一下鏈接訪問我們所部署的服務:

訪問演示

  1. http://{m1domain}:9090


    Portainer集群管理服務.png
  2. http://{m1domain}:9091


    Enovy管理服務.png
  3. 對于我們真正業(yè)務的grpc服務聂宾,我們便可以通過客戶端遠程調(diào)用,或web瀏覽器遠程調(diào)用我們所部署的grpc服務集群诊笤,overlay網(wǎng)絡會自動分流負載

后記

十分感謝您的閱讀系谐,集群系列的文章最近才開始寫,中途遇到了許多困難讨跟,本文中用到的相關技術和實踐纪他,花了大約一周的時間才逐漸走通,grpc-web發(fā)布沒有多久晾匠,處于實驗性的完善階段茶袒,但這是未來的一大方向。本文中所描述的分布式服務框架只是一個小小的引子凉馆,個人準備基于它拓展更多的分布式框架組件弹谁,包括服務發(fā)現(xiàn)集群,消息隊列集群句喜,日志處理集群预愤,緩存集群,甚至是以后的分布式鎖和分布式事務處理等等咳胃,會陸續(xù)發(fā)出文章:)

本文使用到相關技術棧官方資源集合
docker(image/compose/swarm/stack)
nodejs
envoy
grpc
grpc-web
protoc
protoc-gen-grpc-web

作者:CheneyXu
關于:Github項目源碼:x-grpc

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末植康,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子展懈,更是在濱河造成了極大的恐慌销睁,老刑警劉巖供璧,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異冻记,居然都是意外死亡睡毒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門冗栗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來演顾,“玉大人,你說我怎么就攤上這事隅居∧浦粒” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵胎源,是天一觀的道長棉钧。 經(jīng)常有香客問我,道長涕蚤,這世上最難降的妖魔是什么宪卿? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮万栅,結果婚禮上佑钾,老公的妹妹穿的比我還像新娘。我一直安慰自己申钩,他們只是感情好次绘,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撒遣,像睡著了一般邮偎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上义黎,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天禾进,我揣著相機與錄音,去河邊找鬼廉涕。 笑死泻云,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的狐蜕。 我是一名探鬼主播宠纯,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼层释!你這毒婦竟也來了婆瓜?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎廉白,沒想到半個月后个初,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡猴蹂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年院溺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磅轻。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡珍逸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓢省,到底是詐尸還是另有隱情弄息,我是刑警寧澤痊班,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布勤婚,位于F島的核電站,受9級特大地震影響涤伐,放射性物質(zhì)發(fā)生泄漏馒胆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一凝果、第九天 我趴在偏房一處隱蔽的房頂上張望祝迂。 院中可真熱鬧,春花似錦器净、人聲如沸型雳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纠俭。三九已至,卻和暖如春浪慌,著一層夾襖步出監(jiān)牢的瞬間冤荆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工权纤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钓简,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓汹想,卻偏偏與公主長得像外邓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子古掏,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353