rnacos——用 rust 重新现实的 nacos 服务,新版本实现 raft 和类 distro 协议,支持集群部署

2023-09-18 07:50:24 +08:00
 heqingpan

1. rnacos 简介

rnacos是一个用 rust 实现的 nacos 服务。

rnacos 是一个轻量、 快速、稳定、高性能的服务;包含注册中心、配置中心、web 管理控制台功能,支持单机、集群部署。

rnacos 设计上完全兼容最新版本 nacos 面向 client sdk 的协议(包含 1.x 的 http OpenApi ,和 2.x 的 grpc 协议), 支持使用 nacos 服务的应用平迁到 rnacos 。

rnacos 相较于 java nacos 来说,是一个提供相同功能,启动更快、占用系统资源更小、性能更高、运行更稳定的服务。

2. rnacos 支持集群部署

rnacos 之前只支持单机部署,不能水平扩容,同时存在单点稳定性问题,不太合适用于生产环境。所以 rnacos 一直有计划开发支持集群部署的功能。

目前 rnacos 0.3.1 版本已支持集群部署。其中配置中心通过 raft 协议支持集群部署,注册中心通过类 distro 协议支持集群部署。

rnacos 主要功能模块:

2.1 为什么在同一个应用中,配置中心、注册中心需要实现两个不同的协议支持集群部署?

主要因为配置中心和注册中心的特点不一样。

配置中心的数据需要持久化,在多个服务节点中的数据需要强一致,raft 是一个逻辑完备的分页式共识协议。实现 raft 协议只要大于半数的节点正常,就可以正常提供服务。 同时实例 raft 协议就相当于实现一个分页式存储,配置中心可以不需要额外依赖 mysql 等外部数据库,部署依赖更简单。 所以配置中心选择通过 raft 协议支持集群部署。

注册中心的数据主要是临时的服务实例数据,这类数据不需要持久化,不追求多个服务节点中的数据强一致。同时注册中心更关注在部分节点异常时能提供完整的服务,更观注集群的读写性能。所以注册中心不选择 raft 协议,而是通过类 distro 协议支持集群部署。

现在模块协议的对比:

模块 协议 写性能 读性能 数据一致性 容错率
配置中心 raft 一般(只有主节点可写) 高,每个节点都可读 强一致 一般,大于半数节点正常则可以正常提供服务
注册中心 raft 高(每个节点都可写) 高,每个节点都可读 一般 高,一个节点能不依赖其它依赖提供服务

2.2 配置中心 raft 协议

raft 协议的主要逻辑:

  1. 节点区分角色:leader(主节点),follower(从节点),candidate(选举节点);
  2. 稳定状态是一个主节点,多个从节点;
  3. 主节点负责写入,写入时需要先把写入日志同步到其它节点,超过半数节点写入日志成功后才能提交日志到状态机。
  4. 主节点需要定时发心跳到从节点,从节点如果超时未收到心跳,则会发起选举。选举时收到超过半数节点的同意,就可以切换成主节点。

具体协议可以参考 raft 协议论文

rnacos 接入 raft 的主要逻辑:

  1. 基于 async-raft 库实现 raft 协议,主要实现网络层和存储层。在 rnacos 中存储层的状态机就是配置中心。
  2. 配置中心接入 raft 协议的状态机,由 raft 状态机驱动更新配置中心的内容。

rnacos 一个三节点的配置中心请求处理示例:

写入:

  1. 客户端随机向一个节点发起一个更新配置请求
  2. 在请求入口层加一个 raft 路由判断,如果本节点是主节点则处理,否则路由到指定主节点处理
  3. 主节点写入请求到 raft 日志
  4. 将请求同步到其它从节点
  5. 如果超过半数节点写入日志成功(包含自身),则提交请求日志到状态机中,配置写入配置中心。(其它从节点的提交在下次日志同步或心跳时提交)
  6. 返回处理结果

请求:

  1. 客户端随机向一个节点发起一个查询配置请求
  2. 收到请求的节点和单机处理一样,直接查询本节点配置中心数据返回。

2.3 注册中心类 distro 协议

协议主要逻辑:

  1. 每个节点有全量的数据,都可提供注册信息查询服务。
  2. 注册中心每个节点平等,按 hash 划分每个节点负责的内容;节点对负责的服务可写,否则转发到对应负责的节点处理。
  3. 通过 grpc 协议注册的服务,接收的节点直接处理。
  4. 一个节点更新服务实例信息后再同步给其它节点。

具体协议可以参考 java nacos 的 distro 协议实现 。 rnacos 和 java 版主体逻辑相同,但实现的细节有些区别。

rnacos 一个三节点的注册中心请求处理示例:

http 写入:

  1. 客户端随机向一个节点发起一个注册服务实例请求
  2. 请求跳过服务路由判断,如果服务路由的节点是本节点则处理,否则路由到指定的其它节点处理
  3. 收到本节点负责的服务实例请求,把请求注册到注册中心中
  4. 返回处理结果
  5. 异步同步更新的数据到其它节点

grpc 写入(不路由,本节点直接处理):

  1. 客户端随机向一个节点发起 grpc 长链接
  2. 客户端发起一个注册服务实例请求
  3. 像单机一样,把请求注册到注册中心中
  4. 返回处理结果
  5. 异步同步更新的数据到其它节点

查询:

  1. 客户端随机向一个节点发起一个查询服务信息请求
  2. 收到请求的节点和单机处理一样,直接查询本节点注册中心数据返回。

为什么 http 的写入与 grpc 写入的路由逻辑不同?

因为 grpc 的心跳是按长链接来处理,一个客户端的链接段开,则这个链接的所用请求都失效。 [高效] 然后 http 的实例注册是无状态的,只能通过定时器按注册时间更新实例的状态;同时注册中心中实例是按服务分类维护的,所以 http 注册的实例需要按服务做路由,这样才能支持不同的节点负责不同范围的服务。 [低效]

所以在注册中心使用 grpc 协议的性能会比 http 协议性能好很多。

3. 性能与容量

rnacos 支持集群后其性能与容量的水位是怎样的呢?

下面给出一组在我台式电脑(8 核 16 线程+16 内存)的压测性能对比数据.

主要使用 goose 压测,具体可以参考项目中的子压测工程 loadtest

性能压测结果

模块 场景 单节点 qps 集群 qps 总结
配置中心 配置写入,单机模式 1.5 万 1.5 万
配置中心 配置写入,集群模式 1.8 千 1.5 千 接入 raft 后没有充分优化,待优化,理论上可接近单机模式
配置中心 配置查询 8 万 n*8 万 集群的查询总 qps 是节点的倍数
注册中心 服务实例注册,http 协议 1.2 万 1.0 万 注册中心单机模式与集群模式写入的性能一致
注册中心 服务实例注册,grpc 协议 1.2 万 1.2 万 grpc 协议压测工具没有支持,目前没有实际压测,理论不会比 http 协议低
注册中心 服务实例心跳,http 协议 1.2 万 1.0 万 心跳是按实例计算和服务实例注册一致共享 qps
注册中心 服务实例心跳,grpc 协议 8 万以上 n*8 万 心跳是按请求链接计算,且不过注册中心处理线程,每个节点只需管理当前节点的心跳,集群总心跳 qps 是节点的倍数
注册中心 查询服务实例 3 万 n*3 万 集群的查询总 qps 是节点的倍数

注: 具体结果和压测环境有关

压测记录

注册中心查询(单机 3 万 qps):

配置中心查询,两个进程分别限流 4 万 qps 同时压测(共 8 万 qps),其中一个的压测记录:

容量分析

配置中心

  1. 配置中心的单机查询 8 万 qps ,很高,又支持水平扩容;集群基本没有查询瓶颈。
  2. 配置中心所占用的内存和配置内存有关,在内存没有满前,基本没有瓶颈。
  3. 配置中心集群写入时统一在主节点写入,写入可能有瓶颈;目前 1.5 千 tps,后面优化后应该能到 1 万 tps 以上。

注册中心

  1. 注册中心的单机查询 3 万 qps ,比较高,又支持水平扩容;集群基本没有查询瓶颈。
  2. 注册中心所占用的内存和配置内存有关,在内存没有满前,基本没有瓶颈。
  3. 注册中心集群写入时每个节点都要写一遍,整体集群的写入性能 tps 和单机理论上相当。
  4. http 协议(v1.x 版本)和 grpc 协议(v2.x)的心跳维护机制不同; http 心跳是按实例计算和服务实例注册一致共享 qps, grpc 的心跳是按请求链接计算且不过注册中心处理线程。所有这类协议理论支持的容量差别很大。

注册中心集群注册容量推理

  1. http 协议注册+心跳 qps 是 1 万,每个实例 5 秒钟一次心跳;理论上只能支持 5 万服务实例左右。
  2. grpc 协议,注册 qps 假设也是 1 万,心跳 qps 单实例 8 万,3 节点集群总心跳 24 万;如果平均一个应用实例 1 小时重连一次;支持注册的服务实例总数为:606010000 = 3600 万,心跳支持的链接实例总数为:5*24 万=120 万个链接实例(和集群节点有关)。

结论: 如果使用 v1.0x http 协议,支持的实例在 5 万个左右。 如果使用 v2.0x grpc 协议,理论上能到达千万实例,基本没有瓶颈。

4. rnacos 集群部署

4.1 获取 rnacos 应用包

方式 1:从 github release 下载对应系统的应用包,解压后即可运行。

linux 或 mac

# 解压
tar -xvf rnacos-x86_64-apple-darwin.tar.gz
# 运行
./rnacos -e envfine

windows 解压后直接运行 rnacos.exe 即可。

方式 2: 通过 docker 运行

#stable 是最新正式版本号,也可以指定镜像版本号,如:qingpan/rnacos:v0.3.0
docker pull qingpan/rnacos:stable  
# 在/path/rnacos/.env 配置文件中配置好运行参数
docker run --name mynacos -p 8848:8848 -p 9848:9848 -d -v /path/rnacos:/io qingpan/rnacos:stable

docker 的容器运行目录是 /io ,会从这个目录读写配置文件

方式 3:通过 cargo 编译安装

# 安装
cargo install rnacos
# 运行
rnacos -e envfile

方式 4: 下载源码编译运行

git clone https://github.com/heqingpan/rnacos.git
cd rnacos
cargo build --release
cargo run --release -- -e envfile

测试、试用推荐使用第 1 、第 2 种方式,直接下载就可以使用。

在 linux 下第 1 、第 2 种方式默认是 musl 版本(性能比 gnu 版本差一些),在生产服务对性能有要求的可以考虑使用第 3 、第 4 种在对应环境编译 gnu 版本部署。

4.2 运行参数说明

同一个应用包需要支持不同场景,就需要支持设置自定义参数。

rnacos 运行参数支持通过环境变量,或指定配置文件方式设置。 如果不设置则按默认参数运行。

例子

# 从 0.3.0 版本开始支持 -e env_file 运行参数
./rnacos -e env_file

如果不指定文件时也会尝试从当前目录下.env 文件加载配置参数

env_file 内容的格式是

KEY1=VALUE1
KEY2=VALUE2
KEY3=VALUE3

运行参数:

参数 KEY 内容描述 默认值 示例 开始支持的版本
RNACOS_HTTP_PORT rnacos 监听 http 端口 8848 8848 0.1.x
RNACOS_GRPC_PORT rnacos 监听 grpc 端口 默认是 HTTP 端口+1000 9848 0.1.x
RNACOS_HTTP_WORKERS http 工作线程数 cpu 核数 8 0.1.x
RNACOS_CONFIG_DB_FILE 配置中心的本地数据库文件地址 [ 0.2.x 后不在使用] config.db config.db 0.1.x
RNACOS_CONFIG_DB_DIR 配置中心的本地数据库 sled 文件夹, 会在系统运行时自动创建 nacos_db nacos_db 0.2.x
RNACOS_RAFT_NODE_ID 节点 id 1 1 0.3.0
RNACOS_RAFT_NODE_ADDR 节点地址 Ip:GrpcPort,单节点运行时每次启动都会生效;多节点集群部署时,只取加入集群时配置的值 127.0.0.1:GrpcPort 127.0.0.1:9848 0.3.0
RNACOS_RAFT_AUTO_INIT 是否当做主节点初始化,(只在每一次启动时生效) 节点 1 时默认为 true,节点非 1 时为 false true 0.3.0
RNACOS_RAFT_JOIN_ADDR 是否当做节点加入对应的主节点,LeaderIp:GrpcPort ;只在第一次启动时生效 127.0.0.1:9848 0.3.0
RUST_LOG 日志等级:debug,info,warn,error;所有 http,grpc 请求都会打 info 日志,如果不观注可以设置为 error 减少日志量 info error 0.3.0

注:从 v0.3.0 开始,默认参数启动的节点会被当做只有一个节点,当前节点是主节点的集群部署。支持其它新增的从节点加入。

配置集群规则

  1. 所有的集群节点都需要设置 RNACOS_RAFT_NODE_ID,RNACOS_RAFT_NODE_ADDR ,其中不同节点的 node_id 和 node_addr 不能相同; node_id 为一个正整数,node_addr 为ip:grpc_port
  2. 集群主节点: 初始设置 RNACOS_RAFT_AUTO_INIT 为 true (如果节点为 1 ,默认是 true,不用额外设置)。
  3. 集群从节点: 初始设置 RNACOS_RAFT_AUTO_INIT 为 false (节点非 1,默认就是 false,不用额外设置);另外需要设置 RNACOS_RAFT_JOIN_ADDR 为当前主节点的地址,以方便启动时自动加入集群中。
  4. 第 2 、3 点只是为了初始化组建集群。集群运行起来之后,后继启动配置从 raft db 中加载。
  5. 集群节点数量不要求,可以是 1 、2 、3 、4 、... ; 不过 raft 协议只支持小于集群半数节点异常后继续提供写入服务(查询不影响)。例如:3 个节点集群支持 1 个节点异常后提供写入服务,2 个节点集群可以正常运行,不支持节点异常后提供服务。
  6. 从节点可以在使用过程中按需加入。比如原来 3 个节点,可能在使用一段时间后增加 2 个节点扩容。

4.3 集群实例

按上面的配置规则,下面我们配置一个 3 节点集群例子。

初始化节信息

  1. 主节点 id 为 1 ,地址为 127.0.0.1:9848
  2. 第一个从节点 id 为 2 ,地址为 127.0.0.1:9849
  3. 第二个从节点 id 为 3 ,地址为 127.0.0.1:9849

正式集群部署的 log 等级建议设置为warn,不打正常的请求日志,只打报警或异常日志,减少日志量。

配置信息如下

env01

#file:env01 , Initialize with the leader node role
RUST_LOG=warn
RNACOS_HTTP_PORT=8848
RNACOS_RAFT_NODE_ADDR=127.0.0.1:9848
RNACOS_CONFIG_DB_DIR=db01
RNACOS_RAFT_NODE_ID=1
RNACOS_RAFT_AUTO_INIT=true

env02:

#file:env02 , Initialize with the follower node role
RUST_LOG=warn
RNACOS_HTTP_PORT=8849
RNACOS_RAFT_NODE_ADDR=127.0.0.1:9849
RNACOS_CONFIG_DB_DIR=db02
RNACOS_RAFT_NODE_ID=2
RNACOS_RAFT_JOIN_ADDR=127.0.0.1:9848

env03:

#file:env03 , Initialize with the follower node role
RUST_LOG=warn
RNACOS_HTTP_PORT=8850
RNACOS_RAFT_NODE_ADDR=127.0.0.1:9850
RNACOS_CONFIG_DB_DIR=db03
RNACOS_RAFT_NODE_ID=3
RNACOS_RAFT_JOIN_ADDR=127.0.0.1:9848

注: 上面的地址是本机运行多实例的地址,实际使用时换成具体的服务 ip 和 port 即可。

分别运行三个节点,需要先运行主节点成功后再运行

先运行主节点

nohup ./rnacos -e env01 > n01.log &

主节点功能启动后,再运行从节点

nohup ./rnacos -e env02 > n02.log &
nohup ./rnacos -e env03 > n03.log &

实例过程中不同的节点需要在不同的服务器运行服务。

4.4 运行应用使用集群

集群服务启动后,即可运行原有的 nacos 应用。

配置中心 http api 例子

echo "\npublish config t001:contentTest to node 1"
curl -X POST 'http://127.0.0.1:8848/nacos/v1/cs/configs' -d 'dataId=t001&group=foo&content=contentTest'
sleep 1

echo "\nget config info t001 from node 1, value:"
curl 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=t001&group=foo'

echo "\nget config info t001 from node 2, value:"
curl 'http://127.0.0.1:8849/nacos/v1/cs/configs?dataId=t001&group=foo'

echo "\nget config info t001 from node 3, value:"
curl 'http://127.0.0.1:8850/nacos/v1/cs/configs?dataId=t001&group=foo'
sleep 1

echo "\npublish config t002:contentTest02 to node 2"
curl -X POST 'http://127.0.0.1:8849/nacos/v1/cs/configs' -d 'dataId=t002&group=foo&content=contentTest02'
sleep 1

echo "\nget config info t002 from node 1, value:"
curl 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=t002&group=foo'

echo "\nget config info t002 from node 2, value:"
curl 'http://127.0.0.1:8849/nacos/v1/cs/configs?dataId=t002&group=foo'

echo "\nget config info t002 from node 3, value:"
curl 'http://127.0.0.1:8850/nacos/v1/cs/configs?dataId=t002&group=foo'

注册中心 http api 例子

echo "\nregister instance nacos.test.001 to node 1"
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' -d 'port=8000&healthy=true&ip=192.168.1.11&weight=1.0&serviceName=nacos.test.001&groupName=foo&metadata={"app":"foo","id":"001"}'
echo "\nregister instance nacos.test.001 to node 2"
curl -X POST 'http://127.0.0.1:8849/nacos/v1/ns/instance' -d 'port=8000&healthy=true&ip=192.168.1.12&weight=1.0&serviceName=nacos.test.001&groupName=foo&metadata={"app":"foo","id":"002"}'
echo "\nregister instance nacos.test.001 to node 3"
curl -X POST 'http://127.0.0.1:8850/nacos/v1/ns/instance' -d 'port=8000&healthy=true&ip=192.168.1.13&weight=1.0&serviceName=nacos.test.001&groupName=foo&metadata={"app":"foo","id":"003"}'
sleep 1
echo "\n\nquery service instance nacos.test.001 from node 1, value:"
curl "http://127.0.0.1:8848/nacos/v1/ns/instance/list?&namespaceId=public&serviceName=foo%40%40nacos.test.001&groupName=foo&clusters=&healthyOnly=true"
echo "\n\nquery service instance nacos.test.001 from node 2, value:"
curl "http://127.0.0.1:8849/nacos/v1/ns/instance/list?&namespaceId=public&serviceName=foo%40%40nacos.test.001&groupName=foo&clusters=&healthyOnly=true"
echo "\n\nquery service instance nacos.test.001 from node 3, value:"
curl "http://127.0.0.1:8850/nacos/v1/ns/instance/list?&namespaceId=public&serviceName=foo%40%40nacos.test.001&groupName=foo&clusters=&healthyOnly=true"
echo "\n"

详细使用说明参考rnacos book

5. 欢迎试用与共建

rnacos 单机版本发布已有 4 个月,期间有收到一些使用问题的反馈,目前主体功能已经算比较稳定,有使用 nacos 的同学欢迎试用。

项目已开源到 github gitee

使用过程中和什么问题或建议可以到 github 提 issues 反馈。

如果对你有帮助就给个 star 鼓励鼓励 :-)

对 rnacos 开发感兴趣的同学也欢迎到 github 提 rp 共建。rnacos 发布后已有一位同学参于共建,非常感谢一起共建的同学。

2081 次点击
所在节点    Rust
8 条回复
cheneydog
2023-09-18 08:33:26 +08:00
支持
heqingpan
2023-09-18 09:00:12 +08:00
感谢支持:-)
pannanxu
2023-09-18 09:30:28 +08:00
第一眼:nacos
第二眼:macos
第三眼:rnacos ???

我瞎了
heqingpan
2023-09-18 21:48:43 +08:00
这么看有点尴尬😓
nebkad
2023-09-19 15:42:40 +08:00
在 rustcc 上看了简介,虽然不懂什么叫 nacos ,但是关于 “配置中心、注册中心需要实现两个不同的协议支持集群部署” 的说明惊艳到了我~ 关注一下
heqingpan
2023-09-20 08:37:51 +08:00
欢迎关注:-)
heqingpan
2023-09-20 08:40:41 +08:00
有谁知道 V2EX 是不是不支持修改已发布的内容?

看到几处错别字,没找到修改的入口😓
imchl
2023-11-27 07:33:39 +08:00
支持

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/974680

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX