深入分析HBase-RPC(Protobuf)实现机制

Hbase集群调优 一:hbase现有硬件资源的理论性能 1.集群容量规划公式: 优化调整,发挥硬件的最大优势; Disk Size / Java Heap=RegionSize / MemstoreSize * ReplicationFactor * HeapFractionForMemstore *2 按照默认配置, RegionSize = 10G,对应参数为hbase.hregion.max.filesi... 阅读详情

深入分析HBase-RPC(Protobuf)实现机制

问题导读
1、 HBase-RPC实现机制是什么?
2、如何理解从功能上讲,RpcServer上包含了三个模块?
3、Protobuf内置编码与传统压缩技术是否可以配合使用?





背景
在HMaster、RegionServer内部,创建了RpcServer实例,并与Client三者之间实现了Rpc调用,HBase0.95内部引入了Google-Protobuf作为中间数据组织方式,并在Protobuf提供的Rpc接口之上,实现了基于服务的Rpc实现,本文详细阐述了HBase-Rpc实现细节。

HBase的RPC Protocol
在HMaster、RegionServer内部,实现了rpc 多个protocol来完成管理和应用逻辑,具体如下protocol如下:
HMaster支持的Rpc协议: 
MasterMonitorProtocol,Client与Master之间的通信,Master是RpcServer端,主要实现HBase集群监控的目的。
MasterAdminProtocol,Client与Master之间的通信,Master是RpcServer端,主要实现HBase表格的管理。例如TableSchema的更改,Table-Region的迁移、合并、下线(Offline)、上线(Online)以及负载平衡,以及Table的删除、快照等相关功能。
RegionServerStatusProtoco,RegionServer与Master之间的通信,Master是RpcServer端,负责提供RegionServer向HMaster状态汇报的服务。
RegionServer支持的Rpc协议:
ClientProtocol,Client与RegionServer之间的通信,RegionServer是RpcServer端,主要实现用户的读写请求。例如get、multiGet、mutate、scan、bulkLoadHFile、执行Coprocessor等。
AdminProtocols,Client与RegionServer之间的通信,RegionServer是RpcServer端,主要实现Region、服务、文件的管理。例如storefile信息、Region的操作、WAL操作、Server的开关等。
(备注:以上提到的Client可以是用户Api、也可以是RegionServer或者HMaster)
 


HBase-RPC实现机制分析
RpcServer配置三个队列:

1)普通队列callQueue,绝大部分Call请求存在该队列中:callQueue上maxQueueLength为${ipc.server.max.callqueue.length},默认是${hbase.master.handler.count}*DEFAULT_MAX_CALLQUEUE_LENGTH_PER_HANDLER,目前0.95.1中,每个Handler上CallQueue的最大个数默认值(DEFAULT_MAX_CALLQUEUE_LENGTH_PER_HANDLER)为10。

2)优先级队列: PriorityQueue。如果设置priorityHandlerCount的个数,会创建与callQueue相当容量的queue存储Call,该优先级队列对应的Handler的个数由rpcServer实例化时传入。

3)拷贝队列:replicationQueue。由于RpcServer由HMaster和RegionServer共用,该功能仅为RegionServer提供,queue的大小为${ipc.server.max.callqueue.size}指定,默认为1024*1024*1024,handler的个数为hbase.regionserver.replication.handler.count。

RpcServer由三个模块组成:

Listener ===Queue=== Responder
 


这里以HBaseAdmin.listTables为例,分析一个Rpc请求的函数调用过程:

1) RpcClient创建一个BlockingRpcChannel。

2)以channel为参数创建执行RPC请求需要的stub,此时的stub已经被封装在具体Service下,stub下定义了可执行的rpc接口。

3)stub调用对应的接口,实际内部channel调用callBlockingMethod方法。

RpcClient内实现了protobuf提供的BlockingRpcChannel接口方法callBlockingMethod,

  1.   @Override
  2. public Message callBlockingMethod(MethodDescriptor md, RpcController controller,
  3. Message param, Message returnType)
  4. throws ServiceException {
  5. return this.rpcClient.callBlockingMethod(md, controller, param, returnType, this.ticket,
  6. this.isa, this.rpcTimeout);
  7. }
复制代码


通过以上的实现细节,最终转换成rpcClient的调用,使用MethodDescriptor封装了不同rpc函数,使用Message基类可以接收基于Message的不同的Request和Response对象。

4)RpcClient创建Call对象,查找或者创建合适的Connection,并唤醒Connection。

5)Connection等待Call的Response,同时rpcClient调用函数中,会使用connection.writeRequest(Call call)将请求写入到RpcServer网络流中。

6)等待Call的Response,然后层层返回给更上层接口,从而完成此次RPC调用。

RPCServer收到的Rpc报文的内部组织如下:
  
Magic
  
(4Byte)
  
Version
  
(1Byte)
AuthMethod
  
(1Byte)
Connection
  
HeaderLength
  
(4Byte)
ConnectionHeader
Request
“HBas”
 
 
 
验证RpcServer 的CURRENT_VERSION
  
与RPC 报文一致
目前支持三类:
  
AuthMethod.SIMPLE
  
AuthMethod.KERBEROS
  
AuthMethod.DIGEST
  
RPC.proto 定义 
  RPCProtos.ConnectionHeader 
  message ConnectionHeader { 
  optional UserInformation userInfo = 1; 
  optional string serviceName = 2; 
  // Cell block codec we will use sending over optional cell blocks.   Server throws exception 
  // if cannot deal. 
  optional string cellBlockCodecClass = 3 [default =  "org.apache.hadoop.hbase.codec.KeyValueCodec"];
  // Compressor we will use if cell block is compressed.  Server will  throw exception if not supported. 
  // Class must implement hadoop’s CompressionCodec Interface 
  optional string cellBlockCompressorClass = 4; 
  } 
   序列化之后

整个Request存储是经过编码之后的byte数组,包括如下几个部分:
 
  
RequestHeaderLength(RawVarint32)
  
  
RequestHeader
  
  
ParamSize(RawVarint32)
  
  
Param
  
  
CellScanner
  
    
RPC.proto 定义: 
  message RequestHeader { 
  // Monotonically increasing callId to keep track of RPC requests and their  response 
  optional uint32 callId = 1; 
  optional RPCTInfo traceInfo = 2; 
  optional string methodName = 3;
  // If true, then a pb Message param follows. 
  optional bool requestParam = 4; 
  // If present, then an encoded data block follows. 
  optional CellBlockMeta cellBlockMeta = 5; 
  // TODO: Have client specify priority 
  } 
   序列化之后的数据 
   并从Header 中确认是否存在Param 和CellScanner ,如果确认存在的情况下,会继续访问。
  
    
Protobuf 的基本类型Message ,
  Request 的Param 继承了Message ,
   这个需要获取的Method 类型决定。
  
 

从功能上讲,RpcServer上包含了三个模块,

1)Listener。包含了多个Reader线程,通过Selector获取ServerSocketChannel接收来自RpcClient发送来的Connection,并从中重构Call实例,添加到CallQueue队列中。
  1. ”IPC Server listener on 60021″ daemon prio=10 tid=0x00007f7210a97800 nid=0x14c6 runnable [0x00007f720e8d0000] 
  2. java.lang.Thread.State: RUNNABLE 
  3. at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) 
  4. at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210) 
  5. at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65) 
  6. at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69) 
  7. - locked <0x00000000c43cae68> (a sun.nio.ch.Util$2) 
  8. - locked <0x00000000c43cae50> (a java.util.Collections$UnmodifiableSet) 
  9. - locked <0x00000000c4322ca8> (a sun.nio.ch.EPollSelectorImpl) 
  10. at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80) 
  11. at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:84) 
  12. at org.apache.hadoop.hbase.ipc.RpcServer$Listener.run(RpcServer.java:646)
复制代码


2)Handler。负责执行Call,调用Service的方法,然后返回Pair<Message,CellScanner>
  1. “IPC Server handler 0 on 60021″ daemon prio=10 tid=0x00007f7210eab000 nid=0x14c7 waiting on condition [0x00007f720e7cf000] 
  2. java.lang.Thread.State: WAITING (parking) 
  3. at sun.misc.Unsafe.park(Native Method) 
  4. - parking to wait for  <0x00000000c43cad90> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) 
  5. at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156) 
  6. at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987) 
  7. at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:399) 
  8. at org.apache.hadoop.hbase.ipc.RpcServer$Handler.run(RpcServer.java:1804)
复制代码


3) Responder。负责把Call的结果返回给RpcClient。
  1. ”IPC Server Responder” daemon prio=10 tid=0x00007f7210a97000 nid=0x14c5 runnable [0x00007f720e9d1000] 
  2. java.lang.Thread.State: RUNNABLE 
  3. at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) 
  4. at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210) 
  5. at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65) 
  6. at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69) 
  7. - locked <0x00000000c4407078> (a sun.nio.ch.Util$2) 
  8. - locked <0x00000000c4407060> (a java.util.Collections$UnmodifiableSet) 
  9. - locked <0x00000000c4345b68> (a sun.nio.ch.EPollSelectorImpl) 
  10. at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80) 
  11. at org.apache.hadoop.hbase.ipc.RpcServer$Responder.doRunLoop(RpcServer.java:833) 
  12. at org.apache.hadoop.hbase.ipc.RpcServer$Responder.run(RpcServer.java:816)
复制代码


RpcClient为Rpc请求建立Connection,通过Connection将Call发送RpcServer,然后RpcClient等待结果的返回。

思考
1)为什么HBase新版本使用了Protobuf,并实现RPC接口?

HBase是Hadoop生态系统内重要的分布式数据库,Hadoop2.0广泛采用Protobuf作为中间数据组织方式,整个系统内Wire-Compatible的统一需求。

2)HBase内部实现的Rpc框架对于服务性能的影响?

目前使用Protobuf作为用户请求和内部数据交换的数据格式,采用更为紧缩编码格式,能够提高传输数据的效率。但是,有些优化仍然可以在该框架内探索:

实现多个Request复用Connection(把多个短连接合并成一个长连接);
在RpcServer内创建多个CallQueue,分别处理不同的Service,分离管理逻辑与应用逻辑的队列,保证互不干扰;
Responder单线程的模式,是否高并发应用的瓶颈所在?
是否可以分离Read/Write请求占用的队列,以及处理的handler,从而使得读写性能能够更加平衡?
针对读写应用的特点,在RpcServer层次内对应用进行分级,建立不同优先级的CallQueue,按照Hadoop-FairScheduler的模式,然后配置中心调度(类似OMega或者Spallow轻量化调度方案),保证实时应用的低延迟和非实时应用的高吞吐。优先级更好的Call会优先被调度给Handler,而非实时应用可以实现多个Call的合并操作,从而提高吞吐。
3)Protobuf内置编码与传统压缩技术是否可以配合使用?

使用tcpdump获取了一段HMaster得到的RegionServer上报来的信息:
 

以上的信息几乎是明文出现在tcp-ip连接中,因此,是否在Protobuf-RPC数据格式采取一定的压缩策略,会给scan、multiGet等数据交互较为密集的应用提供一种优化的思路。

参考文献:
[1] HBase Rpc Protocols:    http://blog.zahoor.in/2012/08/protocol-buffers-in-hbase/

Hbase-H protobuf、优化、MR整合 大数据第24天-Hbase-protobuf、优化、MR整合 一、Hbase_protobufuffer Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。 protobuffer优化存储步骤: 1 创建文件 ​ vi phone.proto 2 在文件中写相关数据 ​ —java项目包名、本类名、列的v顺序 ​ —整合相同字段/去重 ​... 阅读详情

相关推荐

RPC通信功能实现

Table of Contents RPC通信功能实现 配置参数调用方法 RPC通信功能实现 HBase的RPC通信功能主要基于Protobuf和NIO这两个组件来实现,在通信管道上选择的是protobuf对外声明的BlockingRpcChannel(阻塞式),其callBlockingMethod方法决定了客户端与服务端的交互行为,比如采用什么样的方

JavaMan_chen的专栏 1万+

[Binospace] 深入分析HBase RPC(Protobuf)实现机制

背景 在HMaster、RegionServer内部,创建了RpcServer实例,并与Client三者之间实现了Rpc调用,HBase0.95内部引入了Google-Protobuf作为中间数据组织方式,并在Protobuf提供的Rpc接口之上,实现了基于服务的Rpc实现,本文详细阐述了HBase-Rpc实现细节。 HBase的RPC Protocol  在HMaster、Regi...

cloudera 604

硬件压力测试工具使用方法

B, --bind host# 绑定到主机的多个地址中的一个。当客户端到服务器端的测试结束时,服务器端通过-l选项指定的端口(或默认为客户端连接到服务器端的端口),反向连接至客户端。-c, --client host#如果Iperf运行在服务器模式,并且用-c参数指定一个主机,那么Iperf将只接受指定主机的连接。-C, --compatibility# 与低版本的Iperf使用时,可以使用兼容模式。-b, --bandwidth #[KM] UDP模式使用的带宽,单位bits/sec。

weixin_53303772的博客 8215

hbase之RPC详解

Hbase的RPC主要由HBaseRPC、RpcEngine、HBaseClient、HBaseServer、VersionedProtocol 5个概念组成。 1、HBaseRPC是hbase RPC的实现类,核心方法: 1)、RpcEngine getProtocolEngine():返回RpcEngine对象 2)、&lt;T extends VersionedProtocol&g...

wangneng_168的专栏 1723

CameraLink接口详解:从引脚定义到信号传输

本文详细解析了CameraLink接口的物理连接与信号传输机制。从MDR26连接器的引脚定义与镜像关系入手,深入讲解了BASE与MEDIUM模式下的信号映射、LVDS差分传输原理,并强调了屏蔽层处理与端接电阻等关键硬件设计细节。文章结合实战经验,提供了常见图像传输问题的排查思路,是工业视觉领域高速图像传输的实用指南。

weixin_28318011的博客 888

从原理到实践:MIPI D-PHY v1.2的HS-Deskew功能全解析(附时序图详解)

本文深入解析MIPI D-PHY v1.2的HS-Deskew功能,从技术背景到实现机制,详细介绍了其在2.5Gbps高速传输中的关键作用。通过时序图详解和实测数据,帮助工程师理解并应用这一技术,解决信号完整性问题,提升系统可靠性。

weixin_30300225的博客 307

Hadoop之HBase基本简介

目录 1.HBase的基本概念 2.HBase的工作流程 1.HBase的基本概念 1.1基本概念 HBase运行在HDFS上,所以HBase中的数据以多副本形式存放,数据也服从分布式存放,数据的恢复也可以得到保障。 HBase支持横向扩展,这就意味着如果现有服务器硬件性能出现瓶颈只需要在现有的正在运行的集群中添加新的机器节点即可。 HBase是面向列存储的非关系型数据库,每个列都单独存储,所以在HBase中列是连续存储的,而行不是,区别于关系型数据库。 HBase不支持事务,适合结构化数据

taka_is_beauty的博客 5212

FPGA以太网接口设计避坑指南:GMII与RGMII时序转换的三种实现方案对比

本文深入解析FPGA以太网接口设计中GMII与RGMII时序转换的三种实现方案,包括Xilinx官方IP核、手工原语逻辑和开源方案gmii_to_rgmii。通过对比分析各方案的优缺点和适用场景,为工程师提供技术选型指南,帮助避免常见设计陷阱,提升高速网络设备开发的效率和可靠性。

weixin_42570484的博客 380

hbase rpc这点事

年前的时候系统梳理了一下hbase rpc的实现,并且对组里的小伙伴做了一次分享。趁着热乎劲还没完全消失殆尽,准备赶紧记录下来。 hbase中rpc概况 作为一个分布式系统,hbase的设计是典型的master-salve架构。hbase中主要有master,regionserver,client这三个角色。这三个角色之间rpc的调用关系可以用下图来描述。 client client有...

weixin_30536513的博客 269

保姆级教程:用北醒TFmini-i-CAN雷达给PixHawk飞控加装‘眼睛’,实现自动避障与定高

经过多次测试,前向雷达的最佳安装位置是机架前部下方5-10cm处,倾斜角度20-30度。电源方面,建议为CAN设备单独供电,避免电机启动时的电压骤降导致数据异常。北醒TFmini-i-CAN雷达与PixHawk飞控的组合,为DIY爱好者打开了低成本实现智能避障的大门。实际部署时,需确保整个CAN网络的波特率一致(TFmini-i默认为250kbps),每个设备具有唯一ID。雷达的安装角度也直接影响检测效果,建议通过3D打印支架将雷达向前下方倾斜15-30度。当使用多个雷达时,需为每个设备创建独立的参数组。

weixin_30892889的博客 337

Vue+OpenLayers6一本通,OpenLayers6完整教程大全,OpenLayers6全知全解,OpenLayers6入门、实战、扩展进阶三合一专栏目录汇总

Gis开发入门,OpenLayers、Leaflet、Maplibre-gl和Cesiumjs地图引擎介绍以及几种地图服务vms、vmts介绍OpenLayers介绍,相比较其他地图引擎有什么优点OpenLayers7.x版本和6.x版本有什么区别,是否需要升级到OpenLayers7版本?OpenLayers6实战:OpenLayers创建自定义控件,以创建一个地图复位控件为例OpenLayers6实战,OpenLayers创建自定义鹰眼控件,自定义鹰眼控件样式,调整鹰眼控件位置、大小、文字和按钮等样式。

GIS技术方向,GIS地图技术分享(GIS入门、Openlayers教程、Leaflet教程),做最好的GIS地图开发教程。关注博主公众号:eguid 2199

HBase大批量写入操作ipc queue size持续上升问题记录及修复

由于需要将关系数据库数据通过VDataHub工具导入到HBase表,虽然在VDataHub中进行了流控,但是由于集群还有其他任务在跑,导致整个集群磁盘IO几乎跑满,很多put操作没法写入,在hbase服务端可以看到很多类似以下的异常日志: 2015-06-23 13:45:18,844 WARN [RpcServer.handler=71,port=60020] ipc.RpcServe

黄刚的技术博客 1万+

华为OD机试 - 文件存储系统的排序 - 深度优先搜索dfs(Java 双机位C卷 200分)

华为OD机试 双机位C卷题库疯狂收录中,刷题。

学Java,找哪吒 719

HBase-replication

replication相关znode结构replication相关参数1.replication.source.log.queue.warn ReplicationSource 中待处理的 HLog 数量达 到该参数值时,打印警告,默认值为 22.replication.source.size.capacity 每次向 sink 端同步的数据大小不能超过该阀 值,默认为 64M;3.replicat

鲸鱼写程序的专栏 1053

为何wait和notify方法必须加锁?

为何wait和notify方法必须加锁? 是为了避免Lost Wake Up问题,即丢失唤醒问题。 所谓Lost Wake Up,指的是:线程A调用wait()方法进入阻塞状态,接下来没有其他线程去唤醒线程A,或者其他线程唤醒的时机不对(早于线程A的wait()),导致线程A永远阻塞下去。 下面以一个生产者和消费者的案例来说明上述问题。 假如我们有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠,.

打酱油的葫芦娃 9155

Statistics and Machine Learning Toolbox

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

HBase之异步Replication机制

HBase 提供了一种群集复制机制,允许使用源群集的预写日志(WAL)来传播更改,从而使一个群集的状态与另一个群集的状态保持同步。集群复制的一些用例包括:HBase中的Replication是基于WAL的,其在主集群的每一个RegionServer进程内部起了一个叫作ReplicationSource的线程来负责Replication,同时在备集群的每一个RegionServer内部起了一个ReplicationSink的线程来负责接收Replication数据。ReplicationSource记录须要同

数据源的港湾 2486

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

HBase 源码分析1 – RPC机制 : 基础

HBase采用了和Hadoop相同的RPC机制,作为它的主要通信手段.这是一个轻量的,不同于Java标准的RMI的一种方式.所以它的实现必须克服一些问题.如: 1) 如何分配RPC角色和通信信道,使得RPC通信可以实现. 2) 通信接口或协议的内容 3) 如何传输对象(Object),即序列化. 4) 传输,并发及会话控制 5) 其它的保障,如出错,重试等. 对于第一个问题,首先要确定...

云计算? 265

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

【Java】Hbase之protobuf的使用

开发环境的配置见前几篇文章, 软件下载可自行百度,或者关注。解压 protobuf-2.5.0。进入protobuf-2.5.0。如有问题欢迎评论区讨论。

微信公众号: 后端码匠 1310

HBase参数调优

内存大小:master默认为1G,可增加到2G,regionserver默认1G,可调大到10G,或者更大,zk并不耗资源,可以不用调整;HBase RegionServer 的 Java 堆栈大小(字节)HBase Master 的 Java 堆栈大小(字节)

pansiliang2009的博客 425

HBase(二)HBase的优化策略

HBase 数据表优化 HBase 是一个高可靠性、高性能、面向列、可伸缩的分布式数据库,但是当并发量过高或者已有数据量很大时,读写性能会下降。我们可以采用如下方式逐步提升 HBase 的检索速度。 预先分区 默认情况下,在创建 HBase 表的时候会自动创建一个 Region 分区,当导入数据的时候,所有的 HBase 客户端都向这一个 Region 写数据,直到这个 Region 足够大...

ThinkMore 317

HBase配置参数

原文链接:http://www.cnblogs.com/junrong624/p/3582610.html hbase.rootdir:这个目录是region  server的共享目录,用来持久化Hbase。URL需要是'完全正确'的,还要包含文件系统的scheme。例如,要表示hdfs中的 '/hbase'目录,namenode  运行在namenode.example.org的9

6720

HBase源码分析2 – RPC机制:客户端

先澄清一些本文中术语的涵意 客户端 – 指的是HBase client API.提供了从用户程序连接到HBase后台服务器即Master server及Region server的功能 服务端 – 即指的是HBase的Master server 及 Region server 用户端 – 指用户程序.即对HBase client API的调用方. 本篇的主要目的是说明RPC的客户端实现.解...

云计算? 191

Hbase(五) hbase内部原理

一、系统架构 客户端连接hbase依赖于zookeeper,hbase存储依赖于hadoop client: 1、包含访问 hbase 的接口, client 维护着一些 cache(缓存) 来加快对 hbase 的访问,比如 region 的位置信息。 (经常使用的表的位置信息) zookeeper: 1、保证任何时候,集群中只有一个 master2、存贮所有 ...

weixin_30443895的博客 233

HBase 客户端 RPC 重试如何配置?

在 HBase 客户端到服务端的通信过程中,可能会碰到各种各样的异常。例如有以下几种导致重试的常见异常:下面我们了解一下HBase常见的几个超时参数。假设某业务要求单次 HBase 的读请求延迟不超过 1 s,那么该如何设置上述 4 个超时参数呢?首先, 应该设成 1 s。其次,在 SSD 集群上,如果集群参数设置合适且集群服务正常,则基本可以保证 p99 延迟在 100ms 以内,因此 设成 100...

Shockang的博客 1338
上一篇: hbase表的缓存
下一篇: Protobuf语言指南——.proto文件语法详解
Next__One
博客等级 码龄9年 14粉丝 62原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值