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

系列目录

分布式容器集群探索—grpc服务框架envoy-grpc-web
分布式容器集群探索—Peer Discovery RabbitMQ(编写中...)
微服务容器集群探索—Consul服务注册与发现(规划中...)
服务网格探索—Envoy的xDS与ServiceMesh(规划中...)


前言

分布式,用于解决大规模计算分流问题,高峰值压力问题,服务复用问题等等。然而,在解决问题的同时,分布式更跟随带来了更多的问题,如服务管理,服务解耦,服务发现等等

所以,优秀的分布式服务框架一直是大规模后端的第一需求。当然,面向服务的框架非常多,而且大部分集中在Java领域。但是服务架构与语言分离是大方向。后端革命的浪潮中,rest和rpc是两股中坚的力量

关于rest的方案之前的文章中已经有提过不少,这一次来探索目前rpc框架中十分优秀和简洁的框架——grpc,本文结合了docker容器集群部署,给出了一套精简小巧的分布式容器集群方案x-grpc
*这一个系列的文章,全部会基于容器技术,在个人能力范围内尽可能的展示各种各样的集群解决方案,预计路径规划如下:

简单分布式集群=>
复杂分布式集群=>
混合微服务集群=>
服务网格*=>
...

当然,文章内容更多的还是展示集群雏形,在实际生产需求中,更是需要添砖加瓦,落笔记录下来自己的思考和实现过程,更主要的目的还是能够抛砖引玉,在服务架构领域,能有效参考的资料实在太少,相互交流学习是最重要的


规划

分布式容器集群探索—面向服务框架envoy-grpc-web_第1张图片
分布式集群简化架构图.png

思路

  1. 【接口定义】grpc通过proto文件定义函数接口和消息结构
  2. 【服务实现】服务端选择NodeJS,通过grpc官方所建议的npm包进行proto文件加载和函数序列化
  3. 【服务调用】服务通过proto的加载启动后,客户端也同样根据相同的proto接口调用,实现不同主机甚至是网络节点之间的远程函数调用
  4. 【WEB调用】在实现了服务主机之间的相互服务调用之后,进一步的,人们自然会寄希望直接通过浏览器进行rpc服务调用,因为这可以更进一步的推进前后端的融合与标准化的工程开发。rest之所以发光发热,很大程度上也是因为其通过标准规范的推进了前后端的分离与交互。为了实现这一目标,grpc-web发布了,实现了通过envoy代理的rpc调用,虽然还不是最终完美的解决方案,但是伴随着HTTP2的推进,浏览器的更新迭代,一定在不久后的某一天,可以不需要任何代理而完成web rpc
  5. 【服务集群化】以上,就可以完成所有客户端服务,客户端浏览器发起rpc远程调用服务,很自然地,最后一步,服务集群化后,就可以满足超大规模rpc调用需求,自动扩容,自动负载,自动容灾等我们的核心需求。传统,完成这样一套集群,需要大规模的机房和资深的运维工程师。但是在技术领域蓬勃发展的今天,生产力极大的提高,在云服务的支撑下,利用容器技术,可以不那么费力的实现这一件目标。在这一步的思路,docker swarm是我们的主要工具

接口定义

  • 登录接口
    • 请求消息(帐号,密码)
    • 返回消息(状态码,结果)
  • 登出接口
    • 请求消息(帐号)
    • 返回消息(状态码,结果)

接口是解耦的核心,软件工程化的基石,有了接口,系统的实现就已经完成一半:)

服务实现

  • 登录服务
    • 接收帐号密码后,鉴别身份,返回登录结果
  • 登出服务
    • 接收帐号后,鉴别身份,返回登出结果

服务的实现不依赖语言,任何语言实现皆可

服务调用

  • 通过rpc.invoke()调用函数

rpc的优雅之处在于使得集群主机的服务调用开发,和单节点的本地服务调用开发几乎没有任何区别,开发层无感知

WEB调用

  • 通过envoy代理浏览器的rpc请求,打通浏览器与服务接口,浏览器使用grpc-web生成的代码进行请求

使用rpc模式的接口调用,前端人员不需要再关心网络,像调用本地函数一样调用后端接口,同时接口标准全部都以proto文件为准,不再需要原来第三方的接口管理工具例如postman等

服务集群化

  • 使用docker swarm自动组网,使用overlay负载网络,全自动的容灾和部署rpc集群服务

docker swarm使得集群部署变得容易,极大的降低了集群部署门槛,虽然网络上有很多针对swarm和kubernetes的比较,国内环境很多文章会一边倒的倾向极为复杂的kubernetes,这一类的文章大多是复制转载,有些会浅浅的写一些分析,但是大部分并没有表现出对于两者的深入理解(简单的东西,就一定比不上复杂的东西?)

的确,作为Google多年来的容器编排管理工具,复杂的kubernetes有其独特的优越性,但是也不可否认docker swarm的简洁优雅。技术并不分对错,只有人分。就好比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内部的混合语言数据标准,可实现跨服务,跨平台通信,因此基于grpc,分布式的所有计算节点,都只需要通过proto文件来定义接口

服务实现

user.js

// 服务实现
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的框架下,以上两个文件就是一个服务所必要的所有信息,理论上这两个文件就是集群服务单元:)

好吧,看到这会有很多困惑,这服务接口定义和服务编码实现都有了,可是要怎么调用呢,不同服务之间,甚至是浏览器,要怎么调用已经编码完成的user服务呢?所以我们需要一个小巧的grpc服务加载框架

服务加载

  • protobuf的服务加载,不同语言的官方包是不一样的,在github上,我开源了一个精简小巧的nodejs加载grpc服务的框架x-grpc,在这个加载服务框架中提供以上两个写好的接口和实现文件,便可加载grpc服务
  1. 执行npm install x-grpc安装服务加载框架
  2. 编写config/default.json文件,指定服务端口以及接口文件和实现文件的根目录
{
    "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的加载编码在此处粘贴出来没有太大的意义,而且会显得冗余冗长,但是也不用担心,个人追求的是最少的编码,源码非常简单,对实现有兴趣的可以前往github查看源码

服务调用

client.js是调用例子,只需要先连接服务后,调用函数请求即可完成远端服务调用

const RPCClient = require('x-grpc').RPCClient
const rpc = await new RPCClient(config.grpc).connect()
await rpc.invoke('demo.User.login', { username: 'cheney', password: '123456' })
分布式容器集群探索—面向服务框架envoy-grpc-web_第2张图片
image.png

WEB调用

  1. 通过protoc工具和protoc-gen-grpc-web插件,根据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,调用方法即可(可使用包括vue,react在内的多种前端框架)
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. 访问静态资源服务http://{staticserver}/x-grpc/web/index.html,浏览器控制台可查看服务返回结果
    分布式容器集群探索—面向服务框架envoy-grpc-web_第3张图片
    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:
#网络集合
networks:
  overlay:

只需要一份docker-compose.yml文件即可定义编排所需要的所有容器服务,在以上的compose文件中,所有服务都已经封装成为镜像

WEB代理—Envoy

通过官方的envoy,需要进行一些配置,根据官方文档得到的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,这是一个现代化的面向服务的代理,功能极为强大(彻底替代nginx),围绕着它为核心,拓展出了后来的Service Mesh(服务网格)istio。这是后话了,后续的文章中会逐渐说明

envoy的配置文件稍微有一点复杂,但是简单说主要目的就是10000端口网络请求转向50051端口,而50051端口正是grpc服务所监听的端口,所以envoy在这里起到了连接web浏览器与grpc服务的作用

当使用docker stack deploy命令一键部署完成后,我们的服务会全自动通过overlay网络,将所有镜像分发下载至所有主机,并且自动根据所配置的服务数量,自动扩容,我们可以透过一下链接访问我们所部署的服务:

访问演示

  1. http://{m1domain}:9090


    分布式容器集群探索—面向服务框架envoy-grpc-web_第4张图片
    Portainer集群管理服务.png
  2. http://{m1domain}:9091


    分布式容器集群探索—面向服务框架envoy-grpc-web_第5张图片
    Enovy管理服务.png
  3. 对于我们真正业务的grpc服务,我们便可以通过客户端远程调用,或web浏览器远程调用我们所部署的grpc服务集群,overlay网络会自动分流负载

后记

十分感谢您的阅读,集群系列的文章最近才开始写,中途遇到了许多困难,本文中用到的相关技术和实践,花了大约一周的时间才逐渐走通,grpc-web发布没有多久,处于实验性的完善阶段,但这是未来的一大方向。本文中所描述的分布式服务框架只是一个小小的引子,个人准备基于它拓展更多的分布式框架组件,包括服务发现集群,消息队列集群,日志处理集群,缓存集群,甚至是以后的分布式锁和分布式事务处理等等,会陆续发出文章:)

本文使用到相关技术栈官方资源集合
docker(image/compose/swarm/stack)
nodejs
envoy
grpc
grpc-web
protoc
protoc-gen-grpc-web

作者:CheneyXu
关于:Github项目源码:x-grpc

你可能感兴趣的:(分布式容器集群探索—面向服务框架envoy-grpc-web)