HBASE的优化

有关HBASE的优化应该有许多方面,但是重点应该还在这三个部分,一个是预分区,一个是Rowkey的设计原则,一个是BulkLoading直接将数据导入到HFile中;这里面的重中之重又是Rowkey的设计原则,因为HBASE查询数据之快一定和Rowkey分不开

预分区

默认情况下,创建HBASE表的时候会自动创建一个region分区,当导入数据的时候,所有的HBASE客户端都会向这一个region中写入数据,直到这个region足够大了之后才会进行切分。
如果提前知道了hbase中数据表的key的分布情况,就可以在创建表的时候对hbase进行region预分区,这样可以防止大量数据插入的热点问题,提高数据的插入效率

划分hbase预分区

首先要知道数据的key是如何分布的,然后规划一下要分成多少个region,每个region的startkey和endkey是多少,之后将规划好的key写入到一个文件中
假设我们将一个文件分成十个预分区
那我们可以这样写:

0001|
0002|
0003|
0004|
0005|
0006|
0007|
0008|
0009|

创建的时候直接指定位置即可
在这里插入图片描述
可以看到这里分了十个区
在这里插入图片描述
在这里插入图片描述

Rowkey设计

HBase是三维有序存储的,通过rowkey(行键),column key(column family和qualifier)和TimeStamp(时间戳)这个三个维度可以对HBase中的数据进行快速定位。
HBase中rowkey可以唯一标识一行记录,在HBase查询的时候,有三种方式:

  1. 通过get方式,指定rowkey获取唯一一条记录
  2. 通过scan方式,设置startRow和stopRow参数进行范围匹配
  3. 全表扫描,即直接扫描整张表中所有行记录

rowkey长度原则

rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以 byte[] 形式保存,一般设计成定长。

建议越短越好,不要超过16个字节,原因如下:

数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;

MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。

rowkey散列原则

下面的高位、低位指的是一串数字的前几位和后几位
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以提高实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。

rowkey唯一原则

必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。

热点问题

HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。 设计良好的数据访问模式以使集群被充分,均衡的利用。

为了避免写热点,设计rowkey使得不同行在同一个region,但是在更多数据情况下,数据应该被写入集群的多个region,而不是一个。

下面是一些常见的避免热点的方法以及它们的优缺点:

加盐

这个操作,只能使用scan扫描某一个rowkey范围内的数据,不能准确的get到某一行的数据,因为这里你加的随机数是在前面,没办法get到准确的范值

这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。

哈希

哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据

反转

这里的反转实际上也和时间戳有关,假设我们一个时间戳是1634557248,我们就可以取一个比它更大一些的数,比如取3000000000,两个数做一个相减,得到1365442752,以这个数字作为rowkey来进行存储,这样做的好处就是将数据可以按照升序来进行排列,直接将时间戳反转,最终的结果还是按照降序进行排列

第三种防止热点的方法是反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。

反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题

时间戳反转

一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到key的末尾,例如[key][reverse_timestamp] , [key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。

比如需要保存一个用户的操作记录,按照操作时间倒序排序,在设计rowkey的时候,可以这样设计

[userId反转][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow是[userId反转][000000000000],stopRow是[userId反转][Long.Max_Value - timestamp]

如果需要查询某段时间的操作记录,startRow是[user反转][Long.Max_Value - 起始时间],stopRow是[userId反转][Long.Max_Value - 结束时间]

其他一些建议

尽量减少rowkey和列的大小,当具体的值在系统间传输时,它的rowkey,列簇、列名,时间戳也会一起传输。如果你的rowkey、列簇名、列名很大,甚至可以和具体的值相比较,那么将会造成大量的冗余,不利于数据的储存与传输

列族尽可能越短越好,最好是一个字符

列名也尽可能越短越好,冗长的列名虽然可读性好,但是更短的列名存储在HBase中会更好

BulkLoading

直接将数据导入到HFile,不经过regionserver,之前使用的JavaAPI导入数据到HBASE中,需要经过region server,会有许多负载,触发许多次写请求。

如果我们一次性入库hbase巨量数据,处理速度慢不说,还特别占用Region资源, 一个比较高效便捷的方法就是使用 “Bulk Loading”方法,即HBase提供的HFileOutputFormat类。

它是利用hbase的数据信息按照特定格式存储在hdfs内这一原理,直接生成这种hdfs内存储的数据格式文件,然后上传至合适位置,即完成巨量数据快速入库的办法。配合mapreduce完成,高效便捷,而且不占用region资源,增添负载。

这种方式也有很大的限制:
仅适合初次数据导入,即表内数据为空,或者每次入库表内都无数据的情况。
HBase集群与Hadoop集群为同一集群,即HBase所基于的HDFS为生成HFile的MR的集群

BulkLoading导入数据:
数据以HFile的形式存在HDFS,但是在HBASE的目录下查看不到

package com.shujia;//BulkLoading用来导入数据,只有map阶段,没有reduce阶段

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2;
import org.apache.hadoop.hbase.mapreduce.KeyValueSortReducer;
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
import org.apache.hadoop.hbase.mapreduce.SimpleTotalOrderPartitioner;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class Demo8BulkLoading {

    public  static class BulkMapper extends Mapper<LongWritable,Text,ImmutableBytesWritable,KeyValue>{
        @Override
        protected void map(LongWritable key, Text value, Mapper<LongWritable,Text,ImmutableBytesWritable,KeyValue>.Context context) throws IOException, InterruptedException {
            String[] split = value.toString().split(",");
            String mdn = split[0];
            String start_time = split[1];
            String lg = split[4];
            String lat = split[5];

            String rowkey = mdn+"_"+start_time;

            //keyvalue对象相当于一个cell
            KeyValue lgValue = new KeyValue(rowkey.getBytes(), "info".getBytes(), "lg".getBytes(), lg.getBytes());
            KeyValue latValue = new KeyValue(rowkey.getBytes(), "info".getBytes(), "lat".getBytes(), lat.getBytes());

            context.write(new ImmutableBytesWritable(rowkey.getBytes()),lgValue);
            context.write(new ImmutableBytesWritable(rowkey.getBytes()),latValue);
        }
    }


    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum","master,node1,node2");
        Job job = Job.getInstance(conf);
        job.setJarByClass(Demo8BulkLoading.class);
        job.setJobName("Demo8BulkLoading.java");

        //配置map端
        job.setMapperClass(BulkMapper.class);
        job.setMapOutputKeyClass(ImmutableBytesWritable.class);
        job.setMapOutputValueClass(KeyValue.class);

        //保证每一个reduce是有序的
        job.setPartitionerClass(SimpleTotalOrderPartitioner.class);
        //配置reduce端
        //KeyValueSortReducer 只能保证每个reduce里处理的数据有序
        job.setReducerClass(KeyValueSortReducer.class);

        //reduce的数量由表的region数量决定
//        job.setNumReduceTasks(5); //该配置在这里是无效的

        //配置输入输出的路径
        FileInputFormat.addInputPath(job,new Path("/datas/DIANXIN.csv/"));
        FileSystem fs = FileSystem.get(conf);
        if (fs.exists(new Path("/bulk/"))){
            fs.delete(new Path("/bulk/"),true);
        }
        FileOutputFormat.setOutputPath(job,new Path("/bulk/"));

        Connection conn = ConnectionFactory.createConnection(conf);
        Admin admin = conn.getAdmin();
        Table table = conn.getTable(TableName.valueOf("SPLIT_DIANXIN"));
        //用于获取表的region信息
        RegionLocator regionLocator = conn.getRegionLocator(TableName.valueOf("SPLIT_DIANXIN"));

        //将表的信息格式化成HFile,以待进行bulkLoading
        HFileOutputFormat2.configureIncrementalLoad(job,table,regionLocator);


        //等待job任务运行完成
        boolean flag = job.waitForCompletion(true);

        if(flag){
            System.out.println("mr任务运行成功");
            //使用LoadIncrementalHFiles将生成的HFile加载到HBASE对应的表中
//            LoadIncrementalHFiles loadIncrementalHFiles = new LoadIncrementalHFiles(conf);
//            loadIncrementalHFiles.doBulkLoad(new Path("/bulk"),admin,table,regionLocator);
        }else {
            System.out.println("运行失败");
        }

        //等到job任务运行完成,才会生成hfile文件
//        job.waitForCompletion(true);
//        LoadIncrementalHFiles loadIncrementalHFiles = new LoadIncrementalHFiles(conf);
//        loadIncrementalHFiles.doBulkLoad(new Path("/bulk/"),admin,table,regionLocator);

    }

}

/*
com.shujia.Demo8BulkLoading.java
hadoop jar HBase-1.0-SNAPSHOT.jar com.shujia.Demo8BulkLoading
 */

数据以HFile的形式存在HDFS,在HBASE的目录下可以查看到

package com.shujia;//BulkLoading用来导入数据,只有map阶段,没有reduce阶段

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2;
import org.apache.hadoop.hbase.mapreduce.KeyValueSortReducer;
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
import org.apache.hadoop.hbase.mapreduce.SimpleTotalOrderPartitioner;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class Demo8BulkLoading {

    public  static class BulkMapper extends Mapper<LongWritable,Text,ImmutableBytesWritable,KeyValue>{
        @Override
        protected void map(LongWritable key, Text value, Mapper<LongWritable,Text,ImmutableBytesWritable,KeyValue>.Context context) throws IOException, InterruptedException {
            String[] split = value.toString().split(",");
            String mdn = split[0];
            String start_time = split[1];
            String lg = split[4];
            String lat = split[5];

            String rowkey = mdn+"_"+start_time;

            //keyvalue对象相当于一个cell
            KeyValue lgValue = new KeyValue(rowkey.getBytes(), "info".getBytes(), "lg".getBytes(), lg.getBytes());
            KeyValue latValue = new KeyValue(rowkey.getBytes(), "info".getBytes(), "lat".getBytes(), lat.getBytes());

            context.write(new ImmutableBytesWritable(rowkey.getBytes()),lgValue);
            context.write(new ImmutableBytesWritable(rowkey.getBytes()),latValue);
        }
    }


    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum","master,node1,node2");
        Job job = Job.getInstance(conf);
        job.setJarByClass(Demo8BulkLoading.class);
        job.setJobName("Demo8BulkLoading.java");

        //配置map端
        job.setMapperClass(BulkMapper.class);
        job.setMapOutputKeyClass(ImmutableBytesWritable.class);
        job.setMapOutputValueClass(KeyValue.class);

        //保证每一个reduce是有序的
        job.setPartitionerClass(SimpleTotalOrderPartitioner.class);
        //配置reduce端
        //KeyValueSortReducer 只能保证每个reduce里处理的数据有序
        job.setReducerClass(KeyValueSortReducer.class);

        //reduce的数量由表的region数量决定
//        job.setNumReduceTasks(5); //该配置在这里是无效的

        //配置输入输出的路径
        FileInputFormat.addInputPath(job,new Path("/datas/DIANXIN.csv/"));
        FileSystem fs = FileSystem.get(conf);
        if (fs.exists(new Path("/bulk/"))){
            fs.delete(new Path("/bulk/"),true);
        }
        FileOutputFormat.setOutputPath(job,new Path("/bulk/"));

        Connection conn = ConnectionFactory.createConnection(conf);
        Admin admin = conn.getAdmin();
        Table table = conn.getTable(TableName.valueOf("SPLIT_DIANXIN"));
        //用于获取表的region信息
        RegionLocator regionLocator = conn.getRegionLocator(TableName.valueOf("SPLIT_DIANXIN"));

        //将表的信息格式化成HFile,以待进行bulkLoading
        HFileOutputFormat2.configureIncrementalLoad(job,table,regionLocator);


        //等待job任务运行完成
        boolean flag = job.waitForCompletion(true);

        if(flag){
            System.out.println("mr任务运行成功");
            //使用LoadIncrementalHFiles将生成的HFile加载到HBASE对应的表中
            LoadIncrementalHFiles loadIncrementalHFiles = new LoadIncrementalHFiles(conf);
            loadIncrementalHFiles.doBulkLoad(new Path("/bulk"),admin,table,regionLocator);
        }else {
            System.out.println("运行失败");
        }

        //等到job任务运行完成,才会生成hfile文件
//        job.waitForCompletion(true); //所以这条语句的执行顺序要在加载HFile到对应表中这个操作前面
//        LoadIncrementalHFiles loadIncrementalHFiles = new LoadIncrementalHFiles(conf);
//        loadIncrementalHFiles.doBulkLoad(new Path("/bulk/"),admin,table,regionLocator);

    }

}

/*
com.shujia.Demo8BulkLoading.java
hadoop jar HBase-1.0-SNAPSHOT.jar com.shujia.Demo8BulkLoading
 */

感谢阅读,我是啊帅和和,一位大数据专业大四学生,祝你快乐。

<think>我们正在讨论HBase优化问题的解决方案。根据用户提供的引用和之前的讨论,我们需要整理HBase优化的关键点。用户引用了三处来源: 引用[1]:提到了RegionServer崩溃的问题(未完整给出)以及性能优化的一些方向。 引用[2]:详细说明了HBase性能优化的策略,包括硬件配置、GC优化、数据分布、分区设计,以及解决性能问题的方法(扩容、参数优化、负载均衡、JVM配置)。 引用[3]:展示了一个具体的JVM参数优化例子(GC日志打印和CMS垃圾回收器的设置)。 用户需求是:查找HBase优化问题的解决方案或信息。 因此,我们的回答应围绕HBase性能优化展开,涵盖以下几个方面: 1. 硬件和配置优化(内存、磁盘等) 2. JVM优化(GC设置) 3. 数据设计和负载均衡(RowKey设计、分区设计、避免热点) 4. 参数调优(memstore、块缓存、压缩等) 5. 集群扩展(扩容RegionServer) 6. 使用工具(如YARN) 同时,按照用户要求: - 回答结构清晰,逐步解决问题。 - 使用中文。 - 在引用处添加引用标识,如[^2]。 - 生成相关问题。 注意:引用[1]中关于RegionServer崩溃的问题描述不完整,但我们知道RegionServer崩溃是常见问题,通常与内存不足、GC问题等有关,所以我们在优化中会涵盖这些内容。 另外,用户提供的引用[3]是一个具体的JVM参数设置示例,我们可以在JVM优化部分引用。 回答结构建议: 1. 引言:简要说明HBase优化的重要性。 2. 分点列出优化策略,每个策略下给出具体方法和建议。 3. 每点中适当引用用户提供的引用内容。 4. 最后给出总结和相关问题。 由于引用内容有限,我们主要基于引用[2]展开,因为它提供了较全面的优化指南,并补充引用[3]的JVM配置实例。 具体优化点(根据引用[2]): 1. 硬件配置:内存、磁盘。 2. GC优化:调整GC参数,如使用CMS,设置合适的堆大小和GC间隔。 3. 数据分布:避免热点,设计良好的RowKey和分区。 4. 参数优化:memstore大小、块缓存大小、压缩算法等。 5. 负载均衡策略:如随机负载均衡。 6. 扩容:增加RegionServer节点。 我们还将引用[3]的具体JVM参数示例放在GC优化部分。 注意:引用[1]中提到了RegionServer崩溃,但是没有给出具体描述,我们可以将其与GC问题或硬件不足联系起来,在相应的优化部分提及。 最后,生成3-5个相关问题。 </think>### HBase优化问题全面解决方案 以下针对HBase常见性能瓶颈,结合官方最佳实践与生产经验,提供系统性优化方案: --- #### 一、硬件与配置优化 1. **内存管理** - **RegionServer堆内存**:推荐设置为集群总内存的70%-80%(如64GB物理内存配45GB堆)[^2] - **块缓存(BlockCache)**:占用堆内存40%,使用BucketCache off-heap模式避免Full GC ```xml <!-- hbase-site.xml --> <property> <name>hfile.block.cache.size</name> <value>0.4</value> <!-- 堆内缓存占比 --> </property> <property> <name>hbase.bucketcache.size</name> <value>40960</value> <!-- 40GB堆外缓存 --> </property> ``` 2. **GC调优** - 启用CMS+ParNew组合,避免STW停顿超过200ms[^3]: ```bash export HBASE_OPTS="$HBASE_OPTS -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70" ``` - 监控GC日志:关注`Full GC`频率,超过1次/小时需扩容内存[^2] --- #### 二、数据模型优化 1. **RowKey设计原则** - **散列热点**:对单调递增RowKey添加哈希前缀,如`MD5(user_id)_timestamp` - **长度控制**:不超过16字节,满足$$ \text{存储效率} \propto \frac{1}{\text{RowKey长度}} $$ 2. **分区均衡** - 预分区避免Region分裂延迟:创建表时指定`SPLITS` ```bash create 'orders', 'cf', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'} ``` - 监控`hbase:meta`表,确保Region大小均匀(推荐10-20GB/Region)[^2] --- #### 三、读写性能优化 | **参数** | **优化值** | **作用** | |--------------------|------------------|------------------------------| | hbase.hstore.blockingStoreFiles | 20 | 控制Compaction触发阈值 | | hbase.hregion.memstore.flush.size | 256MB | 降低MemStore刷写IO压力 | | hbase.regionserver.handler.count | 256 | 提升并发请求处理能力 | 1. **写入优化** - 批量写入:使用`Put.setDurability(Durability.SKIP_WAL)`跳过WAL(允许数据丢失场景) - 压缩算法:启用Snappy压缩$$ \text{压缩比} \approx 60\% \sim 80\% $$[^2] ```xml <property> <name>hbase.regionserver.codecs</name> <value>snappy</value> </property> ``` 2. **查询优化** - **布隆过滤器**:对随机读场景启用`ROW`模式 ```java HColumnDescriptor.setBloomFilterType(BloomType.ROW); ``` - **批量Scan**:设置`setCaching(1000)`减少RPC次数 --- #### 四、集群运维优化 1. **水平扩展** - RegionServer数量公式:$$ N = \frac{\text{总数据量}}{\text{单节点内存上限}} \times 1.2 $$ - 添加节点后执行`balance_switch true`触发负载均衡[^2] 2. **监控指标** | **关键指标** | **健康阈值** | **优化动作** | |----------------------------|--------------------|----------------------------| | CompactionQueueSize | < 20 | 增加`hbase.hstore.thread.compaction.large` | | MemStoreSize | < 80%堆内存 | 调低flush.size或扩容内存 | | BlockCacheHitRatio | > 95% | 增大缓存比例 | --- ### 总结优化路径 ```mermaid graph LR A[性能瓶颈分析] --> B{问题类型} B -->|写入慢| C[优化MemStore/WAL] B -->|查询慢| D[调整BlockCache/布隆过滤器] B -->|节点热点| E[RowKey散列+预分区] C --> F[参数调优] D --> G[查询模式改造] E --> H[负载均衡] ``` --- ### 相关问题 1. RegionServer频繁Full GC如何定位和解决? 2. HBase如何实现热点Region的动态分裂与合并? 3. 如何设计RowKey以同时支持范围扫描和高并发写入? 4. HBase与Spark集成时有哪些性能调优关键点? > 建议通过`hbase shell`执行`status 'detailed'`持续监控集群状态,并结合Grafana+Prometheus实现实时指标分析[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊帅和和。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值