系列目錄
分布式容器集群探索—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ī)劃
思路
- 【接口定義】grpc通過proto文件定義函數(shù)接口和消息結構
- 【服務實現(xiàn)】服務端選擇NodeJS册踩,通過grpc官方所建議的npm包進行proto文件加載和函數(shù)序列化
- 【服務調(diào)用】服務通過proto的加載啟動后,客戶端也同樣根據(jù)相同的proto接口調(diào)用效拭,實現(xiàn)不同主機甚至是網(wǎng)絡節(jié)點之間的遠程函數(shù)調(diào)用
- 【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
- 【服務集群化】以上,就可以完成所有客戶端服務擂红,客戶端瀏覽器發(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服務
- 執(zhí)行
npm install x-grpc
安裝服務加載框架 - 編寫
config/default.json
文件呜笑,指定服務端口以及接口文件和實現(xiàn)文件的根目錄
{
"grpc": {
"port": 50051,
"protosDir": "/src/protos/",
"implsDir": "/src/impls/",
"serverAddress": "localhost"
}
}
- 啟動
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' })
WEB調(diào)用
- 通過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文件
- 引入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())
})
- 訪問靜態(tài)資源服務
http://{staticserver}/x-grpc/web/index.html
剪验,瀏覽器控制臺可查看服務返回結果
服務集群化
- ssh m1 && docker swarm init --advertise-addr [m1主機的IP]
- ssh w1 && docker swarm join --token [m1的token]
- 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ù)量,自動擴容他爸,我們可以透過一下鏈接訪問我們所部署的服務:
訪問演示
-
http://{m1domain}:9090
-
http://{m1domain}:9091
對于我們真正業(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