Apollo 中的 长轮询 定时机制

NacosConfig异常:create config service error! (NacosException: null) 可以看到是在创建NacosConfgiServer时报错 create config service error!properties=NacosConfigProperties{serverAddr='null', encode='null', group='DEFAULT_GROUP', sharedDataids='null', refreshableDataids='null', prefix='null', fileExtension='properties', timeout=3000, en 阅读详情

今天这篇文章来介绍一下Nacos配置中心的原理之一:长轮询机制的应用

为方便理解与表达,这里把 Nacos 控制台和 Nacos 注册中心称为 Nacos 服务器(就是 web 界面那个),我们编写的业务服务称为 Nacso 客户端;

Nacos 动态监听的长轮询机制原理图,本篇将围绕这张图剖析长轮询定时机制的原理:

ConfigService 是 Nacos 客户端提供的用于访问实现配置中心基本操作的类型,我们将从 ConfigService 的实例化开始长轮询定时机制的源码之旅;

1. 客户端的长轮询定时机制

我们从
NacosPropertySourceLocator.locate()开始【断点步入】:

1.1 利用反射机制实例化 NacosConfigService 对象

客户端的长轮询定时任务是在
NacosFactory.createConfigService() 方法中,构建 ConfigService 对象是实例时启动的,我们接着 1.1 处的源码;

进入
NacosFactory.createConfigService():

public static ConfigService createConfigService(Properties properties) throws NacosException {    //【断点步入】创建 ConfigService    return ConfigFactory.createConfigService(properties);}

进入
ConfigFactory.createConfigService(),发现其使用反射机制实例化 NacosConfigService 对象;

1.2 NacosConfigService 的构造方法里启动长轮询定时任务

进入
NacosConfigService.NacosConfigService() 构造方法,里面设置了一些跟远程任务相关的属性;

1.2.1 初始化 HttpAgent

MetricsHttpAgent 类的设计如下:

ServerHttpAgent 类的设计如下:

1.2.2 初始化 ClientWorker

进入 ClientWorker.ClientWorker() 构造方法,主要是创建了两个定时调度的线程池,并启动一个定时任务;

进入
ClientWorker.checkConfigInfo(),每隔 10s 检查一次配置是否发生变化;

  • cacheMap:是一个 AtomicReference<Map<String, CacheData>> 对象,用来存储监听变更的缓存集合,key 是根据 datalD/group/tenant(租户)拼接的值。Value 是对应的存储在 Nacos 服务器上的配置文件的内容;
  • 长轮询任务拆分:默认情况下,每个长轮询 LongPollingRunnable 任务处理3000个监听配置集。如果超过3000个,则需要启动多个 LongPollingRunnable 去执行;

1.3 检查配置变更,读取变更配置 LongPollingRunnable.run()

因为我们没有这么多配置项,debug 不进去,所以直接找到 LongPollingRunnable.run() 方法,该方法的主要逻辑是:

根据 taskld 对 cacheMap 进行数据分割;

再通过checkLocalConfig() 方法比较本地配置文件(在${user}\nacos\config\ 里)的数据是否存在变更,如果有变更则直接触发通知;

public void run() {    List<CacheData> cacheDatas = new ArrayList();    ArrayList inInitializingCacheList = new ArrayList();    try {        //遍历 CacheData,检查本地配置        Iterator var3 = ((Map)ClientWorker.this.cacheMap.get()).values().iterator();        while(var3.hasNext()) {            CacheData cacheData = (CacheData)var3.next();            if (cacheData.getTaskId() == this.taskId) {                cacheDatas.add(cacheData);                try {                    //检查本地配置                    ClientWorker.this.checkLocalConfig(cacheData);                    if (cacheData.isUseLocalConfigInfo()) {                        cacheData.checkListenerMd5();                    }                } catch (Exception var13) {                    ClientWorker.LOGGER.error("get local config info error", var13);                }            }        }        //【断点步入 1.3.1】通过长轮询请求检查服务端对应的配置是否发生变更        List<String> changedGroupKeys = ClientWorker.this.checkUpdateDataIds(cacheDatas, inInitializingCacheList);        //遍历存在变更的 groupKey,重新加载最新数据        Iterator var16 = changedGroupKeys.iterator();        while(var16.hasNext()) {            String groupKey = (String)var16.next();            String[] key = GroupKey.parseKey(groupKey);            String dataId = key[0];            String group = key[1];            String tenant = null;            if (key.length == 3) {                tenant = key[2];            }            try {                //【断点步入 1.3.2】读取变更配置,这里的 dataId、group 和 tenant 是【1.3.1】里获取的                String content = ClientWorker.this.getServerConfig(dataId, group, tenant, 3000L);                CacheData cache = (CacheData)((Map)ClientWorker.this.cacheMap.get()).get(GroupKey.getKeyTenant(dataId, group, tenant));                cache.setContent(content);                ClientWorker.LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}", new Object[]{ClientWorker.this.agent.getName(), dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(content)});            } catch (NacosException var12) {                String message = String.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s", ClientWorker.this.agent.getName(), dataId, group, tenant);                ClientWorker.LOGGER.error(message, var12);            }        }        //触发事件通知        var16 = cacheDatas.iterator();        while(true) {            CacheData cacheDatax;            do {                if (!var16.hasNext()) {                    inInitializingCacheList.clear();                    //继续定时执行当前线程                    ClientWorker.this.executorService.execute(this);                    return;                }                cacheDatax = (CacheData)var16.next();            } while(cacheDatax.isInitializing() && !inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheDatax.dataId, cacheDatax.group, cacheDatax.tenant)));            cacheDatax.checkListenerMd5();            cacheDatax.setInitializing(false);        }    } catch (Throwable var14) {        ClientWorker.LOGGER.error("longPolling error : ", var14);        ClientWorker.this.executorService.schedule(this, (long)ClientWorker.this.taskPenaltyTime, TimeUnit.MILLISECONDS);    }}

注意:这里的断点需要注意 Nacos 服务器上修改配置(间隔大于 30s),进入后才好理解;

1.3.1 检查配置变更 ClientWorker.checkUpdateDataIds()

我们点进
ClientWorker.checkUpdateDataIds()​ 方法,发现其最终调用的是 ClientWorker.checkUpdateConfigStr() 方法,其实现逻辑与源码如下:

  • 通过MetricsHttpAgent.httpPost()​ 方法(上面 1.2.1 有提到)调用/v1/cs/configs/listener 接口实现长轮询请求;
  • 轮询请求在实现层面只是设置了一个比较长的超时时间,默认是 30s;
  • 如果服务端的数据发生了变更,客户端会收到一个 HttpResult ,服务端返回的是存在数据变更的 Data ID、Group、Tenant;
  • 获得这些信息之后,在LongPollingRunnable.run() 方法中调用 getServerConfig() 去 Nacos 服务器上读取具体的配置内容;
List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws IOException {    List<String> params = Arrays.asList("Listening-Configs", probeUpdateString);    List<String> headers = new ArrayList(2);    headers.add("Long-Pulling-Timeout");    headers.add("" + this.timeout);    if (isInitializingCacheList) {        headers.add("Long-Pulling-Timeout-No-Hangup");        headers.add("true");    }    if (StringUtils.isBlank(probeUpdateString)) {        return Collections.emptyList();    } else {        try {            //调用 /v1/cs/configs/listener 接口实现长轮询请求,返回的 HttpResult 里包含存在数据变更的 Data ID、Group、Tenant            HttpResult result = this.agent.httpPost("/v1/cs/configs/listener", headers, params, this.agent.getEncode(), this.timeout);            if (200 == result.code) {                this.setHealthServer(true);                //                return this.parseUpdateDataIdResponse(result.content);            }            this.setHealthServer(false);            LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", this.agent.getName(), result.code);        } catch (IOException var6) {            this.setHealthServer(false);            LOGGER.error("[" + this.agent.getName() + "] [check-update] get changed dataId exception", var6);            throw var6;        }        return Collections.emptyList();    }}

1.3.2 读取变更配置 ClientWorker.getServerConfig()

进入
ClientWorker.getServerConfig()​ 方法;读取服务器上的变更配置;最终调用的是 MetricsHttpAgent.httpGet()​ 方法(上面 1.2.1 有提到),调用 /v1/cs/configs​ 接口获取配置;然后通过调用 LocalConfigInfoProcessor.saveSnapshot() 将变更的配置保存到本地;

2. 服务端的长轮询定时机制

2.1 服务器接收请求 ConfigController.listener()

Nacos客户端 通过 HTTP 协议与服务器通信,那么在服务器源码里必然有对应接口的实现;

在 nacos-config 模块下的 controller 包,提供了个 ConfigController 类来处理请求,其中有个 /listener 接口,是客户端发起数据监听的接口,其主要逻辑和源码如下:

  • 获取客户端需要监听的可能发生变化的配置,并计算 MD5 值;
  • ConfigServletInner.doPollingConfig() 开始执行长轮询请求;

2.2 执行长轮询请求 ConfigSer

vletInner.doPollingConfig()

进入
ConfigServletInner.doPollingConfig() 方法,该方法封装了长轮询的实现逻辑,同时兼容短轮询逻辑;

进入
LongPollingService.addLongPollingClient() 方法,里面是长轮询的核心处理逻辑,主要作用是把客户端的长轮询请求封装成 ClientPolling 交给 scheduler 执行;

2.3 创建线程执行定时任务 ClientLongPolling.run()

我们找到 ClientLongPolling.run() 方法,这里可以体现长轮询定时机制的核心原理,通俗来说,就是:

服务端收到请求之后,不立即返回,没有变更则在延后 (30-0.5)s 把请求结果返回给客户端;

这就使得客户端和服务端之间在 30s 之内数据没有发生变化的情况下一直处于连接状态;

2.4 监听配置变更事件

2.4.1 监听 LocalDataChangeEvent 事件的实现

当我们在 Nacos 服务器或通过 API 方式变更配置后,会发布一个 LocalDataChangeEvent 事件,该事件会被 LongPollingService 监听;

这里 LongPollingService 为什么具有监听功能在 1.3.1 版本后有些变化:

  • 1.3.1 前:LongPollingService.onEvent();
  • 1.3.1 后:Subscriber.onEvent();

在 Nacos 1.3.1 版本之前,通过 LongPollingService 继承 AbstractEventListener 实现监听,覆盖 onEvent() 方法;

而在 1.3.2 版本之后,通过构造订阅者实现

效果是一样的,实现了对 LocalDataChangeEvent 事件的监听,并通过通过线程池执行 DataChangeTask 任务;

2.4.2 监听事件后的处理逻辑 DataChangeTask.run()

我们找到 DataChangeTask.run() 方法,这个线程任务实现了

3. 源码结构图小结

3.1 客户端的长轮询定时机制

  • NacosPropertySourceLocator.locate() :初始化 ConfigService 对象,定位配置;
  • NacosConfigService.NacosConfigService():NacosConfigService 的构造方法;
  • Executors.newScheduledThreadPool():创建 executor 线程池;
  • Executors.newScheduledThreadPool():创建 executorService 线程池;
  • ClientWorker.checkConfigInfo():使用 executor 线程池检查配置是否发生变化;
  • ClientWorker.checkLocalConfig():检查本地配置;
  • ClientWorker.checkUpdateDataIds():检查服务端对应的配置是否发生变更;
  • ClientWorker.getServerConfig():读取变更配置
  • MetricsHttpAgent.httpPost():调用 /v1/cs/configs/listener 接口实现长轮询请求;
  • ClientWorker.checkUpdateConfigStr():检查服务端对应的配置是否发生变更;
  • MetricsHttpAgent.httpGet():调用 /v1/cs/configs 接口获取配置;
  • LongPollingRunnable.run():运行长轮询定时线程;
  • MetricsHttpAgent.MetricsHttpAgent():初始化 HttpAgent;
  • ClientWorker.ClientWorker():初始化 ClientWorker;
  • NacosFactory.createConfigService():创建配置服务器;
  • ConfigFactory.createConfigService():利用反射机制创建配置服务器;

3.2 服务端的长轮询定时机制

  • ConfigController.listener() :服务器接收请求;
  • LongPollingService.addLongPollingClient():长轮询的核心处理逻辑,提前 500ms 返回响应;
  • ClientLongPolling.run():长轮询定时机制的实现逻辑;
  • Map.put():将 ClientLongPolling 实例本身添加到 allSubs 队列中;
  • Queue.remove():把 ClientLongPolling 实例本身从 allSubs 队列中移除;
  • MD5Util.compareMd5():比较数据的 MD5 值;
  • LongPollingService.sendResponse():将变更的结果通过 response 返回给客户端;
  • ConfigExecutor.scheduleLongPolling():启动定时任务,延时时间为 29.5s;
  • HttpServletRequest.getHeader():获取客户端设置的请求超时时间;
  • MD5Util.compareMd5():和服务端的数据进行 MD5 对比;
  • ConfigExecutor.executeLongPolling():创建 ClientLongPolling 线程执行定时任务;
  • MD5Util.getClientMd5Map():计算 MD5 值;
  • ConfigServletInner.doPollingConfig():执行长轮询请求;

3.3 Nacos 服务器配置变更的事件监听

Nacos 服务器上的配置发生变更后,发布一个 LocalDataChangeEvent 事件;

Subscriber.onEvent() :监听 LocalDataChangeEvent 事件(1.3.2 版本后);

  • DataChangeTask.run():根据 groupKey 返回配置;
  • ConfigExecutor.executeLongPolling():通过线程池执行 DataChangeTask 任务;
Nacos 和 Apollo中的 长轮询 定时机制,太好用了~ 今天这篇文章来介绍一下Nacos配置中心的原理之一:长轮询机制的应用为方便理解与表达,这里把 Nacos 控制台和 Nacos 注册中心称为 Nacos 服务器(就是 web 界面那个),我们编写的业务服务称为 Nacso 客户端;Nacos 动态监听的长轮询机制原理图,本篇将围绕这张图剖析长轮询定时机制的原理:ConfigService 是 Nacos 客户端提供的用于访问实现配置中心基本操作的类,我们将从 ConfigService 的实例化开始长轮询定时机制的源码之旅;我们从NacosProperty 阅读详情

相关推荐

Nacos 和 Apollo中的 长轮询 定时机制,太好用了

今天这篇文章来介绍一下Nacos配置中心的原理之一:长轮询机制的应用为方便理解与表达,这里把 Nacos 控制台和 Nacos 注册中心称为 Nacos 服务器(就是 web 界面那个),我们编写的业务服务称为 Nacso 客户端;Nacos 动态监听的长轮询机制原理图,本篇将围绕这张图剖析长轮询定时机制的原理:ConfigService 是 Nacos 客户端提供的用于访问实现配置中心基本操作的类型,我们将从 ConfigService 的实例化开始长轮询定时机制的源码之旅;

Cr1556648487的博客 843

【Nacos源码分析】- 01 ConfigService创建流程

基本API/** *测试从NacosServer拉取配置内容 *@throwsNacosException */ @Test publicvoidgetConfig...

god_86的专栏 3231

揭秘 Apollo 配置更新奥秘:推拉结合,新鲜配置一手掌握!

上一篇文章中,我们聊到了SpringBoot项目在启动时,会通过@EnableApolloConfig注解启用Apollo客户端,在启动的过程中,会注册一个名为ApolloAnnotationProcessor的Bean,它会处理Apollo提供的相关注解,如@ApolloConfig, @ApolloJsonValue等,本节我们来探讨下Apollo配置的更新机制以及更新时机。

weixin_45593273的博客 1298

Nacos源码解析(三)-NacosConfigService创建分析

NacosConfigService创建分析

yizefeng7241的专栏 2523

apollo配置中心,实时感知配置

在apollo client 在启动的时候,spring 的beanPostPorcessor会扫描所有beande 方法,判断是不是有每个localRespository对应一个远程拉取,如下图,在远程拉取中有一个立即拉取,及定时“短轮询”&“定时长轮询”同步,重点关注下定时长轮询![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7bc3ed0b20b3434eb72142d61d203f52.png。

li1011612120的博客 286

PhoenixSuit全志刷机实战:从驱动安装到固件烧录的完整避坑指南

本文提供全志芯片设备使用PhoenixSuit进行线刷的完整实战指南。详细介绍了从驱动安装、双公头USB线连接、强制刷机模式进入,到固件烧录全过程的核心步骤与常见错误排查。重点解析了如何避免驱动安装失败、正确触发刷机模式以及应对烧录卡顿等关键问题,帮助用户顺利完成刷机或救砖操作。

algae的博客 1694

Nacos(配置中心)实践

一、安装Nacos Server 安装之前请确保Java环境和maven环境正常: 64 bit JDK 1.8+; Maven 3.2.x+; 1、下载源码或者安装包 我选择直接下载安装包:https://github.com/alibaba/nacos/releases下的nacos-server-1.1.3.zip 2、启动服务 双击startup.cmd运行文件 出现以下界面说明运行成功了 如果启动失败有可能是外部数据库没配置好,执行一下下面的sql就行: 可.

肖潇不写代码 4616

Windows7 64位系统PL2303驱动无法安装问题解决

问题描述:电脑安装的是Win7 64位的旗舰版,USB to RS232这种线很简单,对于笔记本如果要用串口,就不能少了它了。USB to RS232转换芯片种类繁多,常见的有ch340,0108,PL2303,FT232等,这里面FT232的应该价格是相对比较贵的,因为FTDI的芯片本身比较贵。由于买了一根PL2303芯片的USB to RS232转换线,于是插上后发现驱动无法安装,又是在设备管

suxiang198的博客 5810

Nacos 和 Apollo中的 长轮询 定时机制最好用的方法

今天来介绍一下Nacos配置中心的原理之一长轮询机制的应用为方便理解与表达,这里把Nacos控制台和Nacos注册中心称为Nacos服务器(就是web界面那个),我们编写的业务服务称为Nacso客户端;Nacos动态监听的长轮询机制原理图,本篇将围绕这张图剖析长轮询定时机制的原理ConfigService是Nacos客户端提供的用于访问实现配置中心基本操作的类型,我们将从ConfigService的实例化开始长轮询定时机制的源码之旅;...

weixin_62421895的博客 274

【OpenClaw 故障排查完全指南】

OpenClaw 故障排查指南摘要 本指南针对OpenClaw 2026.2.26版本常见问题提供解决方案,适用于Windows/Linux/macOS平台。 核心问题及修复方案: 飞书插件连接失败:删除冲突插件或重装OpenClaw Ollama认证错误:设置环境变量OLLAMA_API_KEY="ollama-local" 配置警告:可安全忽略的安全扫描提示 端口占用:查找并终止占用18789端口的进程 插件重复加载:清理重复插件目录 快速修复: 提供一键修复PowerShell脚本

weixin_37572294的博客 1万+

【GAN实战】基于pytorch-CycleGAN与pix2pix的自定义数据集训练全攻略

本文详细解析了基于PyTorch的CycleGAN与pix2pix模型在自定义数据集上的完整训练流程。核心在于根据数据是否成对来正确选择模型:pix2pix适用于严格配对的“孪生”数据,而CycleGAN则擅长处理非配对的“散装”数据集。文章从数据准备、配置训练到测试应用,提供了全链路实战指南与关键参数调优心得,帮助开发者高效实现图像到图像的风格转换。

x2y3z4a5b的博客 704

Apollo客户端 Client 轮询机制全解析

摘要: Apollo客户端采用长轮询+定时拉取的双重机制实现配置更新,核心设计包括:1)初始化时建立Local/Remote两级配置仓库;2)默认5分钟定时拉取作为保底,60秒长轮询实时监听变更;3)服务发现动态获取ConfigService地址并自动容错;4)内存+本地文件双缓存保障高可用。该机制融合了推拉结合、最终一致性和多层缓存设计思想,在实时性、可靠性和性能之间取得平衡,通过异步监听和自动降级确保分布式环境下的稳定运行。(149字)

油墨香^-^的博客 874

Python爬虫实战:利用Playwright和反反爬技术高效抓取Bloomberg金融数据

用户行为分析(鼠标移动、点击模式)请求头检测IP频率限制WebSocket通信验证Canvas指纹识别我们的爬虫需要模拟人类行为,使用代理IP池,并合理控制请求频率。

2201_76125261的博客 1020

Apollo 4 客户端 SDK 设计

前言 之前聊了客户端的一些功能,例如融入 Spring, @value 注解的自动刷新实现,长轮询等,这次从客户端的整体设计来聊聊。 设计 上图是 client 项目的包结构。 其中,核心包就是 internals 包,包含了客户端的主要功能逻辑。主要有以下功能: 获取 ConfigService 服务的远程配置。 长轮询/定时轮询 ConfigService。 监听机制——更新后,立即通知应...

weixin_34343689的博客 360

9篇3章7节:基于 R 语言的 FAERS 数据提取与合并

本文主要探讨了 FAERS 数据提取与合并的操作流程。首先,通过程序化方法从本地存储的 FAERS ASCII 文件中提取指定季度或范围内的单药报告数据,并可通过参数设置控制数据存储位置、处理方式、计算资源分配以及报告者职业类别等。随后,通过批量读取的方式对多个季度的 DRUG 文件进行循环处理,并将其合并为一个综合数据框。

MD Code 1267

第14.5章 高翔博士最新开源框架Lightning-LM源码解读(五):揭秘位姿图优化核心模块,从数学原理到代码实现

本文介绍了Lightning-LM框架中的位姿图优化(PGO)模块实现。该模块通过融合多源定位信息(激光定位、激光里程计、DR等),构建并优化位姿图来获得更精确的位姿估计。 主要内容包括: 模块概况与数据流程:初始化配置→添加PGO帧→分配位姿→优化求解→更新位姿→滑动窗口 16个核心函数详解:从构造函数到边缘化处理,涵盖完整的PGO工作流程 关键技术点:噪声参数设置、优化问题构建、约束添加、异常值处理等 关键数据结构:帧队列管理、顶点和边构建、优化统计信息收集 该PGO模块采用增量式优化方式,支持多种约束

行知SLAM 1216

Apollo源码解析——Client轮询拉取配置

文章目录一、概述二.代码流程 一、概述 Client的轮询包括两部分: RemoteConfigRepository,定时轮询Config Service的/configs/{appId}/{clusterName}/{namespace} 接口的配置读取 RemoteConfigLongPollService 长轮询 Config Service的配置变更通知 /notification/v2接口 二.代码流程 1. AbstractConfigRepository#trySync方法 客户

ZXMSH的博客 731

Modbus通信从入门到精通_2_Modbus TCP通信详解及仿真(搭建ModbusTCP仿真环境:创建虚拟PLC并进行ModbusTCP通讯;寄存器与PLC中映射关系;适合理解如何编写上位机)

LocalPort”是PLC的端口,最终的设置如下图所示:(相关的设置都可以查看帮助文档)P#DB1.DBX0.0 BYTE28是以指针形式表示的DB1的DBX0.0开始(在使用的西门子Modbus tcp的程序块中设置的“MB_HOLD_REG”输出寄存器),其有28个字节(28个字节对应14个寄存器),4001是第一个寄存器,往后数14个即可。在上面的基础上,增加一个“DB1”的“组对象”,同时将“MW0”的组对象禁止(双击组对象,“Active”取消勾选),启动之后发现无法连接。

分享学习成长历程,Winter is coming! 1万+

apollo配置变更原理

普通情况下,使用 实现apollo的监听器ConfigChangeListener,通过onChange方法来实时获取配置。 但是,如果对配置变化的时效性要求不高,只是想在用到配置的时候,使用新配置的话,可以不实现监听器,直接从environment里面获取配置。 文章目录apollo机制图解代码分析主要流程创建RemoteConfigRepository首次同步apollo开启定时刷新配置...

weixin_42260270的博客 4370

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

上一篇: RocketMQ 的用法,你懂吗?
下一篇: Https如何保证通信安全
Java_ttcd
博客等级 码龄4年 198粉丝 190原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值