从零开始SpringCloud Alibaba实战(94)——nacos从1.x升级到2.x需要注意的地方

Nacos 2.X 版本迎来了首秀,在 1.X 的架构基础上 新增了对长连接模型的支持。通信层目前通过 grpc 实现了长连接 RPC 调用和推送能力,使用长链接的好处大幅度减少了 1.x 轮询心跳频繁导致 JVM Full GC。

nacos 1

1.X架构存在的问题:心跳多,无效查询多,心跳续约感知变化慢,连接消耗大,资源空耗严重。

心跳数量多,导致 TPS 居高不下

通过心跳续约,当服务规模上升时,特别是类似 Dubbo 的接口级服务较多时,心跳及配置元数据的轮询数量众多,导致集群 TPS 很高,系统资源高度空耗。

通过心跳续约感知服务变化,时延长

心跳续约需要达到超时时间才会移除并通知订阅者,默认为 15 s,时延较长,时效性差。若改短超时时间,当网络抖动时,会频繁触发变更推送,对客户端服务端都有更大损耗。

UDP 推送不可靠,导致 QPS 居高不下

由于 UDP 不可靠,因此客户端测需要每隔一段时间进行对账查询,保证客户端缓存的服务列表的状态正确,当订阅客户端规模上升时,集群 QPS 很高,但大多数服务列表其实不会频繁改变,造成无效查询,从而存在资源空耗。

基于 HTTP 短连接模型,TIME_WAIT 状态连接过多

HTTP 短连接模型,每次客户端请求都会创建和销毁 TCP 链接,TCP 协议销毁的链接状态是 WAIT_TIME,完全释放还需要一定时间,当 TPS 和 QPS 较高时,服务端和客户端可能有大量的 WAIT_TIME 状态链接,从而会导致 connect time out 错误或者 Cannot assign requested address 的问题。

配置模块的 30 秒长轮询引起的频繁 GC

配置模块使用 HTTP 短连接阻塞模型来模拟长连接通信,但是由于并非真实的长连接模型,因此每 30 秒需要进行一次请求和数据的上下文切换,每一次切换都有引起造成一次内存浪费,从而导致服务端频繁 GC。

nacos2

优点
客户端不再需要定时发送实例心跳,只需要有一个维持连接可用 keepalive 消息即可。重复 TPS 可以大幅降低。

TCP 连接断开可以被快速感知到,提升反应速度。

长连接的流式推送,比 UDP 更加可靠;nio 的机制具有更高的吞吐量,而且由于可靠推送,可以加长客户端用于对账服务列表的时间,甚至删除相关的请求。重复的无效 QPS 可以大幅降低。

长连接避免频繁连接开销,可以大幅缓解 TIME_ WAIT 问题。

真实的长连接,解决配置模块 GC 问题。

更细粒度的同步内容,减少服务节点间的通信压力。

缺点
没有银弹的方案,新架构也会引入一些新问题

内部结构复杂度上升,管理连接状态,连接的负载均衡需要管理。

数据由原来的无状态,变为与连接绑定的有状态数据,流程链路更长。

RPC 协议的观测性不如 HTTP。即使 gRPC 基于 HTTP2.0 Stream 实现,仍然不如直接使用 HTTP 协议来的直观。

性能提升
Nacos 2.x 服务发现性能测试都是针对重点功能,通过对 3 节点规模集群进行压测,可以看到接口性能负载和容量,以及对比相同/类似场景下 Nacos1.X 版本的提升。

压测时服务及实例容量达到百万级,集群运行持续稳定,达到预期;(该场景没有计算频繁变更导致的频繁推送内容,仅单纯计算容量上线,附带推送的真实场景将在下轮压测报告中给出)

注册/注销实例 TPS 达到 26000 以上,总体较 Nacos1.X 提升至少 2 倍,接口达到预期;

查询实例 TPS 能够达到 30000 以上,总体较 Nacos1.X 提升 3 倍左右,接口达到预期;

兼容性
配置中心
完全兼容 1.X 客户端所有 API 接口方法

完全实现 2.X 客户端所有 API 接口方法

完全兼容所有配置中心相关 openAPI

服务发现
由于服务发现的数据模型发生了比较重大的改变,因此以下功能暂时未支持。

查看当前集群 leader(将废弃)

批量更新实例元数据(Beta,不支持)

批量删除实例元数据(Beta,不支持)

控制台
完全兼容配置中心相关页面及功能

完全兼容权限控制相关页面及功能

完全兼容命名空间相关页面及功能

完全兼容集群管理相关页面及功能

完全兼容服务发现相关页面及功能

Spring Cloud Alibaba 适配
由于目前 Spring cloud alibaba 2.2.5 版本内置的 nacos-client 为 1.4.1,可通过指定 nacos-client 方式,提前使用 Nacos2.0 长连接功能。

com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2.2.5.RELEASE com.alibaba.nacos nacos-client com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config 2.2.5.RELEASE com.alibaba.nacos nacos-client com.alibaba.nacos nacos-client 2.0.0

nacos2.x与nacos1.x在性能上有了蛮大提升,作为配置中心使用时nacos2.x支持通过长连接的方式工作,当配置更改后将新的配置推送到nacos config客户端(springboot应用等)。

    nacos作为配置中心的使用方法可以参考nacos官网:Nacos Spring Cloud 快速开始,这里就不再赘述使用方法,仅说明下nacos2.x在作为配置中心时需要注意的地方,,

Nacos2.0.x版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成

端口 与主端口的偏移量 描述
9848 1000 客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求
9849 1001 服务端gRPC请求服务端端口,用于服务间同步等

所以如果使用nacos-client 2.0.1版本,必须保证nacos server对应的9849端口开发,在使用docker或者k8s部署nacos-server时需要将对应的端口暴漏出来,特别需要注意的是k8s环境,k8s默认端口开放范围为30000-32768,使用kubesphere部署时对外暴露的NodePort端口是随机生成的,这时我们就需要对其进行修改,保证对外暴露端口相差1000

这部分可以在

com.alibaba.nacos.common.remote.client.grpc.GrpcClient的connectToServer方法中找到:

    @Override
    public Connection connectToServer(ServerInfo serverInfo) {
        try {
            if (grpcExecutor == null) {
                int threadNumber = ThreadUtils.getSuitableThreadCount(8);
                grpcExecutor = new ThreadPoolExecutor(threadNumber, threadNumber, 10L, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(10000),
                        new ThreadFactoryBuilder().setDaemon(true).setNameFormat("nacos-grpc-client-executor-%d")
                                .build());
                grpcExecutor.allowCoreThreadTimeOut(true);
                
            }
            // 创建与nacos-server的grpc连接,端口号为8848(docker环境暴露则为对外暴露端口,比如本文中的31648端口)+端口偏移(1000)
            RequestGrpc.RequestFutureStub newChannelStubTemp = createNewChannelStub(serverInfo.getServerIp(),
                    serverInfo.getServerPort() + rpcPortOffset());
            if (newChannelStubTemp != null) {
                
                Response response = serverCheck(newChannelStubTemp);
                if (response == null || !(response instanceof ServerCheckResponse)) {
                    shuntDownChannel((ManagedChannel) newChannelStubTemp.getChannel());
                    return null;
                }
                
                BiRequestStreamGrpc.BiRequestStreamStub biRequestStreamStub = BiRequestStreamGrpc
                        .newStub(newChannelStubTemp.getChannel());
                GrpcConnection grpcConn = new GrpcConnection(serverInfo, grpcExecutor);
                grpcConn.setConnectionId(((ServerCheckResponse) response).getConnectionId());
                
                //create stream request and bind connection event to this connection.
                StreamObserver<Payload> payloadStreamObserver = bindRequestStream(biRequestStreamStub, grpcConn);
                
                // stream observer to send response to server
                grpcConn.setPayloadStreamObserver(payloadStreamObserver);
                grpcConn.setGrpcFutureServiceStub(newChannelStubTemp);
                grpcConn.setChannel((ManagedChannel) newChannelStubTemp.getChannel());
                //send a  setup request.
                ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest();
                conSetupRequest.setClientVersion(VersionUtils.getFullClientVersion());
                conSetupRequest.setLabels(super.getLabels());
                conSetupRequest.setAbilities(super.clientAbilities);
                conSetupRequest.setTenant(super.getTenant());
                grpcConn.sendRequest(conSetupRequest);
                //wait to register connection setup
                Thread.sleep(100L);
                return grpcConn;
            }
            return null;
        } catch (Exception e) {
            LOGGER.error("[{}]Fail to connect to server!,error={}", GrpcClient.this.getName(), e);
        }
        return null;
    }

所以要想通过nacos-client2.0.1与nacos-server通信,需要对外暴露8848以及9848端口

注意:
一定要保证docker容器对外暴露端口满足1000,1001的关系

兼容性简单概括:1.x版本nacos-client能访问2.x版本nacos-server,但是2.x版本nacos-client不能访问1.x nacos-server

Nacos 2.0.0 部署及升级文档

本文档包含两个部分:Nacos2.0.0的部署,以及如何从Nacos1.x平滑升级至Nacos2.0.0。

部署部分,适用于直接部署Nacos2.0.0以上版本的用户。

升级部分,适用于从Nacos1.X版本平滑升级到Nacos2.0.0版本(以及2.0.0-BETA版本)的用户。Nacos2.0.0-ALPHA版本无法进行平滑升级,请勿参照本文档进行升级。

由于Nacos1.X和Nacos2.0的数据结构发生了变化,为了能够完成平滑升降级,需要将数据进行双写,分别生成Nacos1和Nacos2的数据结构进行存储。因此会对性能有一定影响。当集群升级并稳定运行后,可以关闭双写,关闭双写后将会失去平滑降级的功能。

部署步骤
本部分,适用于直接部署Nacos2.0.0以上版本的用户。

1.预备环境准备
Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用:

64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。
64 bit JDK 1.8+;下载 & 配置。
Maven 3.2.x+;下载 & 配置。
2.下载源码或者安装包
你可以通过源码和发行包两种方式来获取 Nacos。

从 Github 上下载源码方式
git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
ls -al distribution/target/

// change the v e r s i o n t o y o u r a c t u a l p a t h c d d i s t r i b u t i o n / t a r g e t / n a c o s − s e r v e r − version to your actual path cd distribution/target/nacos-server- versiontoyouractualpathcddistribution/target/nacosserverversion/nacos/bin

下载编译后压缩包方式
您可以从 最新稳定版本 下载 nacos-server-$version.zip 包。

unzip nacos-server- v e r s i o n . z i p 或 者 t a r − x v f n a c o s − s e r v e r − version.zip 或者 tar -xvf nacos-server- version.ziptarxvfnacosserverversion.tar.gz
cd nacos/bin
3.启动服务器
Linux/Unix/Mac
单机启动命令(standalone代表着单机模式运行):

sh startup.sh -m standalone

如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:

bash startup.sh -m standalone

单机启动,使用内置数据库(注:使用内置Derby数据库需要保证~/nacos/data/derby-data文件夹下无残留数据):

bash startup.sh -p embedded

集群启动(使用内嵌数据库):

bash startup.sh -p embedded

集群启动(使用外置数据库):

bash startup.sh

4.启动后自检
集群中所有机器部署为2.0.X版本并启动时,应当进行启动之后的检查。

当集群中所有节点logs/naming-server.log日志中观察到upgrade check result true及Upgrade to 2.0.X,便判定为集群准备完毕时,此时才可以使用Nacos2.0。

5.关闭双写
为了节省性能开销,当集群部署完成后,可以先观察一段时间运行情况,当确认无误后,可以关闭双写,从而释放性能,具体的关闭方式是通过API进行:

curl -X PUT ‘localhost:8848/nacos/v1/ns/operator/switches?entry=doubleWriteEnabled&value=false’

关闭后可以从logs/naming-server.log日志中观察到Disable Double write, stop and clean v1.x cache and features字样。说明关闭双写。

注意,关闭双写后无法在进行平滑降级,请先确认关闭前集群正确运行。

6.关闭服务器
Linux/Unix/Mac
sh shutdown.sh

Windows
cmd shutdown.cmd

或者双击shutdown.cmd运行文件。

升级步骤
以linux系统为例。window系统请自行替换sh脚本为cmd脚本。

  1. 停止旧节点
    选择集群中一台Nacos1.X节点,使用Nacos目录下nacos/bin/shutdown.sh进行停止。

  2. 替换文件
    下载并解压缩nacos-server-2.0.2.tar.gz,将其下的bin,conf,target目录覆盖原Nacos1.X的安装目录下。

  3. 修改配置
    自行修改nacos/bin/startup.sh中的JVM参数,conf/cluster.conf中的集群列表以及conf/application.prpperties中数据库或其他相关参数。

  4. 启动Nacos2.0
    使用nacos目录下nacos/bin/startup.sh启动nacos2.0,其他更多启动指令请查看Nacos部署环境 。

  5. 观察是否启动成功
    首先查看nacos目录下 logs/start.out或logs/nacos.log 观察到nacos启动成功的日志,如 Nacos started successfully in cluster mode. use xxx storage 说明程序已启动成功。

之后在观察 logs/naming-server.log 中,可以看到有upgrade check result false 以及 Check whether close double write等日志信息。

属于正常现象。

  1. 升级其他节点
    待该节点的服务及实例信息已经同步完毕后(可从控制台进行确认)。重复1~5步骤,将其他的nacos节点也进行升级。

  2. 确认升级完成
    当集群中最后一个节点也升级到2.0.X版本时,集群会开始进行升级检测。每个节点会对该节点的服务信息和实例信息进行校验,并检测是否还有未完成的双写任务。

当该节点的服务信息和实例信息已经核对成功,并且没有双写任务存在时,该节点会判定自己已经做好升级准备,并修改自己的状态且通知其他Nacos节点。每台节点是否完成升级准备可以从控制台的集群管理中元数据信息中看到"readyToUpgrade": false/true。

当集群中所有节点均判定为准备完毕时。Nacos集群中的节点会进行升级切换,自动升级到Nacos2.0的处理逻辑。

可以从logs/naming-server.log日志中观察到upgrade check result true及Upgrade to 2.0.X。

8.1 关闭双写
当集群升级完成后,可以先观察一段时间运行情况,当确认无误后,可以关闭双写,从而释放性能,具体的关闭方式是通过API进行:

curl -X PUT ‘localhost:8848/nacos/v1/ns/operator/switches?entry=doubleWriteEnabled&value=false’

关闭后可以从logs/naming-server.log日志中观察到Disable Double write, stop and clean v1.x cache and features字样。说明关闭双写。

注意,关闭双写后无法在进行平滑降级,请先确认关闭前集群正确运行。

8.2 降级
集群升级完毕后,依旧会进行双写,当升级后发现Nacos2.0存在问题时,可以快速进行降级,降级流程为重复步骤1~6,只是将版本改为对应的1.X版本。

当第一台降级完成后,集群即可观察到logs/naming-server.log 中的upgrade check result false ,且控制台集群管理中,所有新版本"readyToUpgrade": false。

升级相关的openAPI
在2.0.2版本中,nacos-server提供了一些方便查看升级状态及不同版本中的数据区别,方便用户排查升级中的问题。

查看统计
描述
查看当前升级状态

请求类型
GET

请求URL
/nacos/v1/ns/upgrade/ops/metrics

请求参数

返回参数
参数类型 描述
string 升级状态
示例
upgraded = true
isAll20XVersion = true
isDoubleWriteEnabled = false
doubleWriteDelayTaskCount = 0
serviceCountV1 = 0
instanceCountV1 = 0
serviceCountV2 = 0
instanceCountV2 = 0
subscribeCountV2 = 0
responsibleServiceCountV1 = 0
responsibleInstanceCountV1 = 0
ephemeralServiceCountV2 = 0
persistentServiceCountV2 = 0
ephemeralInstanceCountV2 = 0
persistentInstanceCountV2 = 0
service.V1.not.in.V2 =
service.V2.not.in.V1 =
查询服务
描述
查询对应Nacos版本中一个服务内容

请求类型
GET

请求路径
/nacos/v1/ns/upgrade/ops/service
请求参数
名称 类型 是否必选 描述
serviceName 字符串 是 服务名
groupName 字符串 否 分组名
namespaceId 字符串 否 命名空间ID
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X GET ‘127.0.0.1:8848/nacos/v1/ns/upgrade/ops/service?serviceName=nacos.test.2’
示例返回
{
metadata: { },
groupName: “DEFAULT_GROUP”,
namespaceId: “public”,
name: “nacos.test.2”,
selector: {
type: “none”
},
protectThreshold: 0,
clusters: [
{
healthChecker: {
type: “TCP”
},
metadata: { },
name: “c1”
}
]
}
查询服务列表
描述
查询对应Nacos版本的服务列表

请求类型
GET

请求路径
/nacos/v1/ns/upgrade/ops/service/list
请求参数
名称 类型 是否必选 描述
pageNo int 是 当前页码
pageSize int 是 分页大小
groupName 字符串 否 分组名
namespaceId 字符串 否 命名空间ID
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X GET ‘127.0.0.1:8848/nacos/v1/ns/upgrade/ops/service/list?pageNo=1&pageSize=2’
示例返回
{
“count”:148,
“doms”: [
“nacos.test.1”,
“nacos.test.2”
]
}
查询实例列表
描述
查询对应Nacos版本中某个服务下的实例列表

请求类型
GET

请求路径
/nacos/v1/ns/upgrade/ops/instance/list
请求参数
名称 类型 是否必选 描述
serviceName 字符串 是 服务名
groupName 字符串 否 分组名
namespaceId 字符串 否 命名空间ID
clusters 字符串,多个集群用逗号分隔 否 集群名称
healthyOnly boolean 否,默认为false 是否只返回健康实例
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X GET ‘127.0.0.1:8848/nacos/v1/ns/upgrade/ops/instance/list?serviceName=nacos.test.1’
示例返回
{
“dom”: “nacos.test.1”,
“cacheMillis”: 1000,
“useSpecifiedURL”: false,
“hosts”: [{
“valid”: true,
“marked”: false,
“instanceId”: “10.10.10.10-8888-DEFAULT-nacos.test.1”,
“port”: 8888,
“ip”: “10.10.10.10”,
“weight”: 1.0,
“metadata”: {}
}],
“checksum”: “3bbcf6dd1175203a8afdade0e77a27cd1528787794594”,
“lastRefTime”: 1528787794594,
“env”: “”,
“clusters”: “”
}
查询实例详情
描述
查询一个对应Nacos版本中某个服务下个某个实例详情。

请求类型
GET

请求路径
/nacos/v1/ns/upgrade/ops/instance
请求参数
名称 类型 是否必选 描述
serviceName 字符串 是 服务名
groupName 字符串 否 分组名
ip 字符串 是 实例IP
port 字符串 是 实例端口
namespaceId 字符串 否 命名空间ID
cluster 字符串 否 集群名称
healthyOnly boolean 否,默认为false 是否只返回健康实例
ephemeral boolean 否 是否临时实例
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X GET ‘127.0.0.1:8848/nacos/v1/ns/upgrade/ops/instance?serviceName=nacos.test.2&ip=10.10.10.10&port=8888&cluster=DEFAULT’
示例返回
{
“metadata”: {},
“instanceId”: “10.10.10.10-8888-DEFAULT-nacos.test.2”,
“port”: 8888,
“service”: “nacos.test.2”,
“healthy”: false,
“ip”: “10.10.10.10”,
“clusterName”: “DEFAULT”,
“weight”: 1.0
}
添加服务
描述
补充添加一个服务到对应Nacos版本下

请求类型
POST

请求路径
/nacos/v1/ns/upgrade/ops/service
请求参数
名称 类型 是否必选 描述
serviceName 字符串 是 服务名
groupName 字符串 否 分组名
namespaceId 字符串 否 命名空间ID
protectThreshold 浮点数 否 保护阈值,取值0到1,默认0
metadata 字符串 否 元数据
selector JSON格式字符串 否 访问策略
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X POST ‘127.0.0.1:8848/nacos/v1/ns/service?serviceName=nacos.test.2&metadata=k1%3dv1’
示例返回
ok
删除服务
描述
从对应Nacos版本中删除一个服务,如果删除v2服务,只有当服务下实例数为0时允许删除。

请求类型
DELETE

请求路径
/nacos/v1/ns/upgrade/ops/service
请求参数
名称 类型 是否必选 描述
serviceName 字符串 是 服务名
groupName 字符串 否 分组名
namespaceId 字符串 否 命名空间ID
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X DELETE ‘127.0.0.1:8848/nacos/v1/ns/service?serviceName=nacos.test.2’
示例返回
ok
注册实例
描述
注册一个实例到对应Nacos版本的服务下。

请求类型
POST

请求路径
/nacos/v1/ns/upgrade/ops/instance
请求参数
名称 类型 是否必选 描述
ip 字符串 是 服务实例IP
port int 是 服务实例port
namespaceId 字符串 否 命名空间ID
weight double 否 权重
enabled boolean 否 是否上线
healthy boolean 否 是否健康
metadata 字符串 否 扩展信息
clusterName 字符串 否 集群名
serviceName 字符串 是 服务名
groupName 字符串 否 分组名
ephemeral boolean 否 是否临时实例
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X POST ‘http://127.0.0.1:8848/nacos/v1/ns/instance?port=8848&healthy=true&ip=11.11.11.11&weight=1.0&serviceName=nacos.test.3&encoding=GBK&namespaceId=n1’
示例返回
ok

注销实例
描述
删除对应Nacos版本服务下的一个实例。

请求类型
DELETE

请求路径
/nacos/v1/ns/upgrade/ops/instance
请求参数
名称 类型 是否必选 描述
serviceName 字符串 是 服务名
groupName 字符串 否 分组名
ip 字符串 是 服务实例IP
port int 是 服务实例port
clusterName 字符串 否 集群名称
namespaceId 字符串 否 命名空间ID
ephemeral boolean 否 是否临时实例
ver 字符串 否 版本 v1 或者 v2, 默认v2
示例请求
curl -X DELETE ‘127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.test.1&ip=1.1.1.1&port=8888&clusterName=TEST1’
示例返回
ok

升级过程中可能遇到的问题

  1. 最后一台节点升级完成时,注册的服务出现波动(变成不健康或暂时被摘除)
    升级过程中,为了节约性能,双写的内容仅是内容发生变更时的状态,心跳等内容不会被双写,因此切换版本时,可能有部分实例的心跳过久而健康检查又刚好开始执行,从而被标记非健康或摘除。

后续心跳处理将会把数据补充回来,最终会一致。

  1. 升级完成后,升级的最后一台服务端报错Server is DOWN
    这可能是因为Raft选主失败导致的,解决方法是重启最后一台升级的服务端。或先将最后一台服务端降级,之后再重新进行一次升级即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值