HBase-RegionServer架构

RegionServer的整体结构

一个region server包含了五部分功能:

1.和zookeeper相关的线程

    MasterAddressTracker负责捕获master节点的状态

    ClusterStatusTracker追踪hbase集群的状态

    CatalogTracker跟踪root表meta表和region的状态

    SplitlogWorker竞争获取znode上的splitlog,并切分HLog按照region分组,放到相应region

        的recovered.edits目录下

2.region相关的线程

    regionserver包含了一个region的集合,每个具体的操作会分到一个指定的region去处理

    CompactionChecker用于周期性的检查是否需要compact,如需要交给CompactSplitThread处理

    CompactSplitThread用于合并和切分处理的线程

    MemStoreFlusher如果memstore满了则flush到HDFS中

3.WAL相关

    HLog按照hbase的架构,一个regionserver只有一个hlog,多个region是共享的

    LogRoller用于日志回滚

4.和客户端通讯

    RPC server模块,这里包含了很多线程,listener,select,handler线程

    Leases 用于租借时间检查

5.和master及监控相关

    HMasterRegionInterface用户管理hbase

    HServerLoad检查hbase负载,并和master通讯

    HealthCheckChore服务的监控检查

    RegionServerMetrics 获取metrics相关的数据

    web server,启用一个jettyserver,可以监控region相关的信息

 

 

 

 

 

RegionServer的相关配置

参数名称默认值含义
hbase.client.retries.number10客户端的重试次数
hbase.regionserver.msginterval3000未知
hbase.regionserver.checksum.verifyfalse

是否启用hbase的

checksum

hbase.server.thread.wakefrequency10秒检查线程的频率
hbase.regionserver.numregionstoreport10未知
hbase.regionserver.handler.count10

处理用户表的工作

线程数量

hbase.regionserver.metahandler.count10

处理meta和root表

的工作线程数量

hbase.rpc.verbosefalse未知
hbase.regionserver.nbreservationblocksfalse未知

hbase.regionserver.compactionChecker.

majorCompactPriority

max int未知
hbase.regionserver.executor.openregion.threads3

开打用户表region

的线程数量

hbase.regionserver.executor.openroot.threads1

打开root表region

的线程数量

hbase.regionserver.executor.openmeta.threads1

打开meta表region

的线程数量

hbase.regionserver.executor.closeregion.threads3

关闭用户表region

的线程数量

hbase.regionserver.executor.closeroot.threads1

关闭root表region

的线程数量

hbase.regionserver.executor.closemeta.threads1

关闭meta表region

的线程数量

 

 

 

 

 

HRegionServer的启动入口类

org.apache.hadoop.hbase.regionserver.HRegionServer

hbase-site.xml中可以配置参数 hbase.regionserver.impl来自定自己的实现,但必须继承HRegionServer

 

之后调用HRegionServerCommandLine (这个类继承自ServerCommandLine,所以master也有一个实现)

HRegionServerCommandLine使用hadoop提供的ToolRunner去运行

 

ToolRunner#run(Configuration,Tool,String[])

ToolRunner会调用GenericOptionsParser,解析一些固定的参数,如-conf,-D,-fs,-files 这样的参数

解析好之后,配置configuration对象,然后将启动参数传给Tool接口的实现

所以ToolRunner 就是一个启动参数解析,配置configuration对象的工具类,然后将这些信息交给Tool实现类

 

 

 

 

 

初始化-调用HRgionServer构造函数

HRegionServerCommandLine反射创建HRegionServer(或其自定义子类)

1.这里对客户端连接配置做了一些初始化工作

2.配置host,DNS相关

3.HRegionServer 调用HBaseRPC,创建一个RpcEngine实现,这里是WritableRpcEngine

hbase-site.xml中可以配置参数 hbase.rpc.engine来自定自己的实现,但必须继承RpcEngine接口

HBaseRPC调用 getServer()获得一个具体的RpcServer实现,即通过RpcEngine --> RpcServer

这里获取的是WritableRpcEngine的内部类WritableRpcEngine$Server,它继承了HBaserServer

4.创建metrics线程(for JVM),LRU检查线程

5.连接zookeeper,做一些验证工作(kerbose)

 

 

 

 

 

启动,HRegionServer#run (在新线程中启动)

之后就开始启动server了,启动是从HRegionServer#run()开始的(新启动的线程)

1.创建zookeeper监听线程

2.创建和master通讯的线程

3.创建WAL相关的线程

4.创建metrics线程(for hbase)

5.创建日志回滚线程、cache flush线程、compact线程、心跳检查线程、租借检查线程

6.创建jetty线程

7.创建response线程,listener线程,handle线程,高优先级handle(处理meta表)线程,复制handle线程

8.创建日志切分线程

 

这里还定义了线程池,以后会通过处理请求的时候,可能会开启这些线程:(在第五步的时候定义的)

hbase.regionserver.executor.openregion.threads3(默认)

hbase.regionserver.executor.openroot.threads1

hbase.regionserver.executor.openmeta.threads1

hbase.regionserver.executor.closeregion.threads3

hbase.regionserver.executor.closeroot.threads1

hbase.regionserver.executor.closemeta.threads1

 

日志切分线程在启动的时候可能会有很多事情要做

之后整个region server就启动完成了

 

 

 

 

 

HRegionServer包含一些功能

HRegion集合

Leases(租借时间检查)

HMasterRegionInterface(管理hbase)

HServerLoad(hbase负载)

CompactSplitThread(用于合并处理)

CompactionChecker(周期性的检查是否需要compact,如需要交给CompactSplitThread处理)

MemStoreFlusher(用于刷新memstore)

HLog(WAL相关)

LogRoller(日志回滚)

ZooKeeperWatcher(zk监听)

SplitLogWorker(用于切分日志)

ExecutorService(用户启动open,close HRegion的线程池)

ReplicationSourceService和ReplicationSinkService(replication相关)

HealthCheckChore(健康检查)

RegionServerMetrics(监控)

 

一些监听类

MasterAddressTracker

CatalogTracker

ClusterStatusTracker

 

postOpenDeployTasks 用于更新root表或meta表

各种CURD,scanner,increment操作

multi操作(对于delete和put)

对HRegion的flush,close,open(提交到线程池去做)

split,compact操作,这些最终由一个具体的HRegion去完成

 

 

 

 

 

RegionServer的线程

用于小合并的

Daemon Thread [regionserver60020-smallCompactions-1392958977368] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

PriorityBlockingQueue<E>.take() line: 220

ThreadPoolExecutor.getTask() line: 957

ThreadPoolExecutor$Worker.run() line: 917

Thread.run() line: 662

 

打开用户表region的线程

Thread [RS_OPEN_REGION-myhost,60020,1392868973177-0] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

LinkedBlockingQueue<E>.take() line: 399

ExecutorService$TrackingThreadPoolExecutor(ThreadPoolExecutor).getTask() line: 957

ThreadPoolExecutor$Worker.run() line: 917

Thread.run() line: 662

 

这个是跟zookeeper通讯的线程

Daemon Thread [PostOpenDeployTasks:1028785192-EventThread] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

LinkedBlockingQueue<E>.take() line: 399

ClientCnxn$EventThread.run() line: 491

 

跟zookeeper通讯的线程

Daemon Thread [PostOpenDeployTasks:1028785192-SendThread(myhost:2181)] (Suspended)

EPollArrayWrapper.epollWait(long, int, long, int) line: not available [native method]

EPollArrayWrapper.poll(long) line: 210

EPollSelectorImpl.doSelect(long) line: 65

EPollSelectorImpl(SelectorImpl).lockAndDoSelect(long) line: 69

EPollSelectorImpl(SelectorImpl).select(long) line: 80

ClientCnxnSocketNIO.doTransport(int, List<Packet>, LinkedList<Packet>, ClientCnxn) line: 338

ClientCnxn$SendThread.run() line: 1068

 

专门处理META表的线程

Thread [RS_OPEN_META-myhost,60020,1392868973177-0] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

LinkedBlockingQueue<E>.take() line: 399

ExecutorService$TrackingThreadPoolExecutor(ThreadPoolExecutor).getTask() line: 957

ThreadPoolExecutor$Worker.run() line: 917

Thread.run() line: 662

 

专门处理ROOT表的线程

Thread [RS_OPEN_ROOT-myhost,60020,1392868973177-0] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

LinkedBlockingQueue<E>.take() line: 399

ExecutorService$TrackingThreadPoolExecutor(ThreadPoolExecutor).getTask() line: 957

ThreadPoolExecutor$Worker.run() line: 917

Thread.run() line: 662

 

日志拆分线程

Thread [SplitLogWorker-myhost,60020,1392868973177] (Suspended)

Object.wait(long) line: not available [native method]

Object.wait() line: 485

SplitLogWorker.taskLoop() line: 219

SplitLogWorker.run() line: 179

Thread.run() line: 662

 

jetty的扫描线程

Daemon Thread [Timer-0] (Suspended)

Object.wait(long) line: not available [native method]

TimerThread.mainLoop() line: 509

TimerThread.run() line: 462

 

jetty的接收线程

Thread [1142818380@qtp-1463348369-1 - Acceptor0 SelectChannelConnector@0.0.0.0:60030] (Suspended)

EPollArrayWrapper.epollWait(long, int, long, int) line: not available [native method]

EPollArrayWrapper.poll(long) line: 210

EPollSelectorImpl.doSelect(long) line: 65

EPollSelectorImpl(SelectorImpl).lockAndDoSelect(long) line: 69

EPollSelectorImpl(SelectorImpl).select(long) line: 80

SelectorManager$SelectSet.doSelect() line: 498

SelectChannelConnector$1(SelectorManager).doSelect(int) line: 192

SelectChannelConnector.accept(int) line: 124

AbstractConnector$Acceptor.run() line: 708

QueuedThreadPool$PoolThread.run() line: 582

 

jetty的工作线程

Thread [869247333@qtp-1463348369-0] (Suspended)

Object.wait(long) line: not available [native method]

QueuedThreadPool$PoolThread.run() line: 626

 

租借检查相关线程

Daemon Thread [regionserver60020.leaseChecker] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.parkNanos(Object, long) line: 196

AbstractQueuedSynchronizer$ConditionObject.awaitNanos(long) line: 2025

DelayQueue<E>.poll(long, TimeUnit) line: 201

Leases.run() line: 83

Thread.run() line: 662

 

检查压缩的线程

Daemon Thread [regionserver60020.compactionChecker] (Suspended)

Object.wait(long) line: not available [native method]

Sleeper.sleep(long) line: 91

HRegionServer$CompactionChecker(Chore).run() line: 75

Thread.run() line: 662

 

检查memstore flush的线程

Daemon Thread [regionserver60020.cacheFlusher] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.parkNanos(Object, long) line: 196

AbstractQueuedSynchronizer$ConditionObject.awaitNanos(long) line: 2025

DelayQueue<E>.poll(long, TimeUnit) line: 201

DelayQueue<E>.poll(long, TimeUnit) line: 39

MemStoreFlusher.run() line: 220

Thread.run() line: 662

 

日志回滚的线程

Daemon Thread [regionserver60020.logRoller] (Suspended)

Object.wait(long) line: not available [native method]

LogRoller.run() line: 77

Thread.run() line: 662

 

metrics线程

Daemon Thread [Timer thread for monitoring jvm] (Suspended)

Object.wait(long) line: not available [native method]

TimerThread.mainLoop() line: 509

TimerThread.run() line: 462

 

日志同步线程

Daemon Thread [regionserver60020.logSyncer] (Suspended)

Object.wait(long) line: not available [native method]

HLog$LogSyncer.run() line: 1265

Thread.run() line: 662

 

HDFS客户端租借检查线程

Daemon Thread [LeaseChecker] (Suspended)

Thread.sleep(long) line: not available [native method]

DFSClient$LeaseChecker.run() line: 1379

Daemon(Thread).run() line: 662

 

未知

Thread [regionserver60020] (Suspended)

Object.wait(long) line: not available [native method]

Sleeper.sleep(long) line: 91

Sleeper.sleep() line: 55

HRegionServer.run() line: 787

Thread.run() line: 662

 

LRU相关的线程

Daemon Thread [LRU Statistics #0] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.parkNanos(Object, long) line: 196

AbstractQueuedSynchronizer$ConditionObject.awaitNanos(long) line: 2025

DelayQueue<E>.take() line: 164

ScheduledThreadPoolExecutor$DelayedWorkQueue.take() line: 609

ScheduledThreadPoolExecutor$DelayedWorkQueue.take() line: 602

ScheduledThreadPoolExecutor(ThreadPoolExecutor).getTask() line: 957

ThreadPoolExecutor$Worker.run() line: 917

Thread.run() line: 662

 

LRU缓存检查线程

Daemon Thread [main.LruBlockCache.EvictionThread] (Suspended)

Object.wait(long) line: not available [native method]

LruBlockCache$EvictionThread(Object).wait() line: 485

LruBlockCache$EvictionThread.run() line: 612

Thread.run() line: 662

 

RPC监控线程

Daemon Thread [Timer thread for monitoring rpc] (Suspended)

Object.wait(long) line: not available [native method]

TimerThread.mainLoop() line: 509

TimerThread.run() line: 462

 

reader线程

Daemon Thread [IPC Reader 0 on port 60020] (Suspended)

EPollArrayWrapper.epollWait(long, int, long, int) line: not available [native method]

EPollArrayWrapper.poll(long) line: 210

EPollSelectorImpl.doSelect(long) line: 65

EPollSelectorImpl(SelectorImpl).lockAndDoSelect(long) line: 69

EPollSelectorImpl(SelectorImpl).select(long) line: 80

EPollSelectorImpl(SelectorImpl).select() line: 84

HBaseServer$Listener$Reader.doRunLoop() line: 528

HBaseServer$Listener$Reader.run() line: 514

ThreadPoolExecutor$Worker.runTask(Runnable) line: 895

ThreadPoolExecutor$Worker.run() line: 918

Thread.run() line: 662

 

工作线程(可以配置多个)    REPL是用于复制的,PRI是用于处理META表的,IPC是普通的工作线程

Daemon Thread [REPL IPC Server handler 0 on 60020] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

LinkedBlockingQueue<E>.take() line: 399

HBaseServer$Handler.run() line: 1398

 

Daemon Thread [PRI IPC Server handler 0 on 60020] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

LinkedBlockingQueue<E>.take() line: 399

HBaseServer$Handler.run() line: 1398

 

Daemon Thread [IPC Server handler 1 on 60020] (Suspended)

Unsafe.park(boolean, long) line: not available [native method]

LockSupport.park(Object) line: 156

AbstractQueuedSynchronizer$ConditionObject.await() line: 1987

LinkedBlockingQueue<E>.take() line: 399

HBaseServer$Handler.run() line: 1398

 

select线程

Daemon Thread [IPC Server listener on 60020] (Suspended)

EPollArrayWrapper.epollWait(long, int, long, int) line: not available [native method]

EPollArrayWrapper.poll(long) line: 210

EPollSelectorImpl.doSelect(long) line: 65

EPollSelectorImpl(SelectorImpl).lockAndDoSelect(long) line: 69

EPollSelectorImpl(SelectorImpl).select(long) line: 80

EPollSelectorImpl(SelectorImpl).select() line: 84

HBaseServer$Listener.run() line: 636

 

响应线程

Daemon Thread [IPC Server Responder] (Suspended)

EPollArrayWrapper.epollWait(long, int, long, int) line: not available [native method]

EPollArrayWrapper.poll(long) line: 210

EPollSelectorImpl.doSelect(long) line: 65

EPollSelectorImpl(SelectorImpl).lockAndDoSelect(long) line: 69

EPollSelectorImpl(SelectorImpl).select(long) line: 80

HBaseServer$Responder.doRunLoop() line: 825

HBaseServer$Responder.run() line: 808

 

未知

Daemon Thread [hbase-tablepool-1-thread-1] (Suspended)

Object.wait(long) line: not available [native method]

HBaseClient$Call(Object).wait() line: 485

HBaseClient.call(Writable, InetSocketAddress, Class<VersionedProtocol>, User, int) line: 981

WritableRpcEngine$Invoker.invoke(Object, Method, Object[]) line: 86

$Proxy14.multi(MultiAction) line: not available

HConnectionManager$HConnectionImplementation$3$1.call() line: 1427

HConnectionManager$HConnectionImplementation$3$1.call() line: 1425

HConnectionManager$HConnectionImplementation$3$1(ServerCallable<T>).withoutRetries() line: 215

HConnectionManager$HConnectionImplementation$3.call() line: 1434

HConnectionManager$HConnectionImplementation$3.call() line: 1422

FutureTask$Sync.innerRun() line: 303

FutureTask<V>.run() line: 138

ThreadPoolExecutor$Worker.runTask(Runnable) line: 895

ThreadPoolExecutor$Worker.run() line: 918

Thread.run() line: 662

 

总体来说线程有这么一些

1.日志回滚,日志syn同步,日志切分

2.大合并,小合并线程

3.LRU缓存检查,memstore检查

4.HDFS客户端,HDFS客户端超时检查,zookeeper通讯

5.专门处理root表,专门处理meta表

6.jetty线程

7.listener线程,reader线程,handle线程,响应线程,用于处理META的handle线程,用于复制的handle线程

 

 

 

 

 

postOpenDeployTask线程(用于更新META表)

具体逻辑如下:

  1. //PostOpenDeployTask用于更新META表的线程  
  2. OpenRegionHandler$PostOpenDeployTasksThread#run() {  
  3.     HRegionServer#postOpenDeployTasks();      
  4. }  
  5.   
  6. //首先看是否需要刷新Store中的数据  
  7. //之后根据是ROOT表META表还是普通表再做更新  
  8. HRegionServer#postOpenDeployTasks() {  
  9.     for (Store s : r.getStores().values()) {  
  10.         if (s.hasReferences() || s.needsCompaction()) {  
  11.             getCompactionRequester().requestCompaction(r, s, "Opening Region"null);  
  12.         }  
  13.     }  
  14.     // Update ZK, ROOT or META  
  15.     if (r.getRegionInfo().isRootRegion()) {  
  16.         RootLocationEditor.setRootLocation(getZooKeeper(),this.serverNameFromMasterPOV);  
  17.     } else if (r.getRegionInfo().isMetaRegion()) {  
  18.         MetaEditor.updateMetaLocation(ct, r.getRegionInfo(),this.serverNameFromMasterPOV);  
  19.     } else {  
  20.         if (daughter) {  
  21.             // If daughter of a split, update whole row, not just location.  
  22.             MetaEditor.addDaughter(ct, r.getRegionInfo(),  
  23.             this.serverNameFromMasterPOV);  
  24.         } else {  
  25.             MetaEditor.updateRegionLocation(ct, r.getRegionInfo(),  
  26.             this.serverNameFromMasterPOV);  
  27.         }  
  28.     }     
  29. }  
  30.   
  31. //更新ROOT表在ZK中的信息  
  32. RootLocationEditor#setRootLocation() {  
  33.     ZKUtil.createAndWatch(zookeeper, zookeeper.rootServerZNode,  
  34.     Bytes.toBytes(location.toString()));      
  35. }  
  36.   
  37. //更新META表的内容,这里是创建了一个Put对象然后去更新  
  38. MetaEditor#updateLocation() {  
  39.     Put put = new Put(regionInfo.getRegionName());  
  40.     put.add("info""server",Bytes.toBytes(sn.getHostAndPort()));  
  41.     put.add("info""serverstartcode",Bytes.toBytes(sn.getStartcode()));      
  42.     HTable table = isRootTableRow(row)? getRootHTable(catalogTracker):  
  43.       getMetaHTable(catalogTracker);      
  44.     table.put(put);  
  45. }  
  46.   
  47. //如果是在做split,则更新这个row的所有KeyValue  
  48. //否则就更新server和serverstartcode两个KeyValue即可  
  49. MetaEditor#addDaughter() {  
  50.     Put put = new Put(regionInfo.getRegionName());  
  51.     p.add("info""regioninfo",Writables.getBytes(hri));  
  52.     if (ServerName != null) {  
  53.         put.add("info""server",Bytes.toBytes(sn.getHostAndPort()));  
  54.         put.add("info""serverstartcode",Bytes.toBytes(sn.getStartcode()));          
  55.     }  
  56.     putToMetaTable(catalogTracker, put);  
  57. }  

 

 

 

 

 

leaseChecker线程(执行超时后销毁这些操作)

这个类的作用是当某些执行超时,比如get,scan等,需要释放相应的scan或者行锁等

这里是在异步的线程中执行的

具体逻辑如下:

  1. //租借时间检查,当一些执行操作超时后  
  2. //需要释放这些操作  
  3. Leases#run() {  
  4.     Lease lease = leaseQueue.poll(leaseCheckFrequency, TimeUnit.MILLISECONDS);  
  5.     lease.getListener().leaseExpired();  
  6. }  
  7.   
  8. //行操作执行尝试则释放行锁  
  9. RowLockListener#leaseExpired() {  
  10.     Integer r = rowlocks.remove(this.lockName);  
  11.     if (r != null) {  
  12.         region.releaseRowLock(r);  
  13.     }     
  14. }  
  15.   
  16. //当scan执行超时就关闭这个scan  
  17. ScannerListener#leaseExpired() {  
  18.     RegionScanner s = scanners.remove(this.scannerName);  
  19.     HRegion region = getRegion(s.getRegionInfo().getRegionName());  
  20.     s.close();    
  21. }  

 

 

 

 

 

参考

HBase深入分析之RegionServer

Hbase系统架构及数据结构

HRegionServer启动过程 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HBase是一个基于Hadoop的分布式、面向列的数据库。它采用了一种分布式存储架构,可以支持海量数据的存储和高并发的访问。 HBase的分布式存储架构主要由HMaster、RegionServer和ZooKeeper组成。HMaster是HBase的主控节点,负责对整个集群进行管理和协调。它负责分配RegionServer和表的负载均衡,并监控RegionServer的状态。 RegionServer是实际存储数据的节点,每个RegionServer负责一定范围的数据区域(Region)。Region按照表的列族进行分割存储,并自动进行数据的划分和迁移,以实现数据的平衡和高可用性。每个RegionServer可以同时服务多个客户端请求,并对数据进行读写操作。 ZooKeeper是分布式协调服务,它主要用于管理和监控整个HBase集群的状态、配置信息和元数据。ZooKeeper负责维护HBase集群的一致性和可用性,通过选主机制确保HMaster的高可用性,同时协调各个RegionServer之间的通信和同步。 在HBase的分布式存储架构中,数据存储在HDFS(Hadoop分布式文件系统)上,并进行分散和冗余存储,以提高系统的可靠性和容错性。HBase采用了列存储的方式,可以支持多维度的扫描和快速的随机读写操作,适用于大数据量和高并发的场景。 总结来说,HBase的分布式存储架构是基于Hadoop的、面向列的数据库系统。它通过HMaster、RegionServer和ZooKeeper等组件,实现了数据的分布式存储、负载均衡和高可用性。这种架构可以支持海量数据的存储和高并发的访问,适用于大规模数据处理和分析的场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值