rnacos实现raft和类distro协议,支持集群部署
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 | 一般(只有主节点可写) | 高,每个节点都可读 | 强一致 | 一般,大于半数节点正常则可以正常提供服务 |
注册中心 | distro | 高(每个节点都可写) | 高,每个节点都可读 | 一般 | 高,一个节点能不依赖其它依赖提供服务 |
2.2 配置中心raft协议
raft协议的主要逻辑:
- 节点区分角色:leader(主节点),follower(从节点),candidate(选举节点);
- 稳定状态是一个主节点,多个从节点;
- 主节点负责写入,写入时需要先把写入日志同步到其它节点,超过半数节点写入日志成功后才能提交日志到状态机。
- 主节点需要定时发心跳到从节点,从节点如果超时未收到心跳,则会发起选举。选举时收到超过半数节点的同意,就可以切换成主节点。
具体协议可以参考 raft协议论文
rnacos 接入 raft的主要逻辑:
- 基于 async-raft 库实现raft协议,主要实现网络层和存储层。在 rnacos中存储层的状态机就是配置中心。
- 配置中心接入raft 协议的状态机,由 raft 状态机驱动更新配置中心的内容。
rnacos一个三节点的配置中心请求处理示例:
写入:
- 客户端随机向一个节点发起一个更新配置请求
- 在请求入口层加一个raft路由判断,如果本节点是主节点则处理,否则路由到指定主节点处理
- 主节点写入请求到raft日志
- 将请求同步到其它从节点
- 如果超过半数节点写入日志成功(包含自身),则提交请求日志到状态机中,配置写入配置中心。(其它从节点的提交在下次日志同步或心跳时提交)
- 返回处理结果
请求:
- 客户端随机向一个节点发起一个查询配置请求
- 收到请求的节点和单机处理一样,直接查询本节点配置中心数据返回。
2.3 注册中心类distro协议
协议主要逻辑:
- 每个节点有全量的数据,都可提供注册信息查询服务。
- 注册中心每个节点平等,按hash划分每个节点负责的内容;节点对负责的服务可写,否则转发到对应负责的节点处理。
- 通过 grpc协议注册的服务,接收的节点直接处理。
- 一个节点更新服务实例信息后再同步给其它节点。
具体协议可以参考java nacos 的distro协议实现 。
rnacos 和 java版主体逻辑相同,但实现的细节有些区别。
rnacos一个三节点的注册中心请求处理示例:
http 写入:
- 客户端随机向一个节点发起一个注册服务实例请求
- 请求跳过服务路由判断,如果服务路由的节点是本节点则处理,否则路由到指定的其它节点处理
- 收到本节点负责的服务实例请求,把请求注册到注册中心中
- 返回处理结果
- 异步同步更新的数据到其它节点
grpc 写入(不路由,本节点直接处理):
- 客户端随机向一个节点发起grpc长链接
- 客户端发起一个注册服务实例请求
- 像单机一样,把请求注册到注册中心中
- 返回处理结果
- 异步同步更新的数据到其它节点
查询:
- 客户端随机向一个节点发起一个查询服务信息请求
- 收到请求的节点和单机处理一样,直接查询本节点注册中心数据返回。
为什么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),其中一个的压测记录:
容量分析
配置中心
- 配置中心的单机查询8万qps,很高,又支持水平扩容;集群基本没有查询瓶颈。
- 配置中心所占用的内存和配置内存有关,在内存没有满前,基本没有瓶颈。
- 配置中心集群写入时统一在主节点写入,写入可能有瓶颈;目前1.5千tps,后面优化后应该能到1万 tps以上。
注册中心
- 注册中心的单机查询3万qps,比较高,又支持水平扩容;集群基本没有查询瓶颈。
- 注册中心所占用的内存和配置内存有关,在内存没有满前,基本没有瓶颈。
- 注册中心集群写入时每个节点都要写一遍,整体集群的写入性能tps和单机理论上相当。
- http协议(v1.x版本)和grpc协议(v2.x)的心跳维护机制不同;http心跳是按实例计算和服务实例注册一致共享qps, grpc的心跳是按请求链接计算且不过注册中心处理线程。所有这类协议理论支持的容量差别很大。
注册中心集群注册容量推理
- http协议注册+心跳qps是1万,每个实例5秒钟一次心跳;理论上只能支持5万服务实例左右。
- grpc协议,注册qps假设也是1万,心跳qps单实例8万,3节点集群总心跳24万;如果平均一个应用实例1小时重连一次;支持注册的服务实例总数为:
60*60*10000
= 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开始,默认参数启动的节点会被当做只有一个节点,当前节点是主节点的集群部署。支持其它新增的从节点加入。
配置集群规则
- 所有的集群节点都需要设置RNACOS_RAFT_NODE_ID,RNACOS_RAFT_NODE_ADDR ,其中不同节点的node_id和 node_addr不能相同;node_id为一个正整数,node_addr为
ip:grpc_port
- 集群主节点: 初始设置RNACOS_RAFT_AUTO_INIT为true (如果节点为1,默认是 true,不用额外设置)。
- 集群从节点: 初始设置RNACOS_RAFT_AUTO_INIT为false (节点非1,默认就是false,不用额外设置);另外需要设置RNACOS_RAFT_JOIN_ADDR为当前主节点的地址,以方便启动时自动加入集群中。
- 第2、3点只是为了初始化组建集群。集群运行起来之后,后继启动配置从raft db中加载。
- 集群节点数量不要求,可以是1、2、3、4、... ; 不过raft协议只支持小于集群半数节点异常后继续提供写入服务(查询不影响)。例如:3个节点集群支持1个节点异常后提供写入服务,2个节点集群可以正常运行,不支持节点异常后提供服务。
- 从节点可以在使用过程中按需加入。比如原来3个节点,可能在使用一段时间后增加2个节点扩容。
4.3 集群实例
按上面的配置规则,下面我们配置一个3节点集群例子。
初始化节信息
- 主节点id为1,地址为127.0.0.1:9848
- 第一个从节点id为2,地址为127.0.0.1:9849
- 第二个从节点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提issues反馈。
如果对你有帮助就给个star鼓励鼓励 😃
对rnacos开发感兴趣的同学也欢迎到github提rp共建。 rnacos发布后已有一位同学参于共建,非常感谢一起共建的同学。