HBASE2生产实践&性能优化

HBASE

参考资料:HBASE权威指南

HBASE架构设计图

在这里插入图片描述

HMaster:
HMaster节点负责协调管理所有的regionServer,将region数据块分配给regionServer,恢复regionServer的故障;通常HMaster的负载都比较轻;

RegionServer:

  • 直接处理客户端的数据读写请求;
  • 管理 HMaster 为其分配的 Region;
  • 负责 Region 过大以后的拆分,以及 StoreFile 的合并工作;
  • 负责与底层的 HDFS 交互,存储数据到 HDFS;

RegionServer 与 HMaster 的协同: 当某个 RegionServer 宕机之后,ZK 会通知 Master 进行失效备援。下线的 RegionServer 所负责的 Region 暂时停止对外提供服务,Master 会将该 RegionServer 所负责的 Region 转移到其他 RegionServer 上,并且会对所下线的 RegionServer 上存在 MemStore 中还未持久化到磁盘中的数据由 WAL 重播进行恢复。

Region:
Region是hbase中扩展和负载均衡的基本单元,HRegion本质上是以行键排序的连续储存空间,HBase 的 Table 通过rowkey range 的范围被水平切分成多个Region,一个Region包含了所有的,在Region开始键和结束之内的行。Region如果超出设置大小就会进行拆分,相反如果太小就会进行合并以减少储存文件数量。每一个Region只能有一台RegionServer服务器加载,一台RegionServer服务器可以同时加载多个Region;

图示为hbase table的rowkey数据跟region数据存储的关系:
在这里插入图片描述

Store:
一个 Region 由多个 Store 组成,每个 Store 都对应一个 Column Family,Store 包含 MemStore 和 StoreFile。

MemStore:
Hbase的数据写入操作会先把数据写入到MemStore内存中,当MemStore内存大小达到 hbase.hregion.memstore.flush.size 配置的值,就会把内存中的数据刷新到磁盘中,生成一个StoreFile文件;

StoreFile & HFile:
HFile 和 StoreFile 其实是同一个数据文件,对 HDFS 来说这个文件叫 HFile,对Hbase来说这个文件叫 StoreFile。

zookeeper:
hbase依赖zookeeper来管理集群,zookeeper负责管理hbase:meta目录表的位置以及当前集群主控机的位置等重要信息。如果region分配期间有服务器奔溃,可以通过zookeeper进行协调分配;

hbase数据结构

在这里插入图片描述
hbase数据存储模式:
(Table, Rowkey, Family, Colunm, Timestamp) -> Value

hbase建表属性详解

建表示例:

create 'table_name', {
   NAME => 'cf1', BLOOMFILTER => 'ROW', VERSIONS => '1', COMPRESSION => 'SNAPPY', TTL => '2147483647', KEEP_DELETED_CELLS => 'false', BLOCKSIZE =>'65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'},{
   SPLITS=>['1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']}

参数详解:

  • NAME:指定列族名称;
  • BLOOMFILTER:布隆过滤器,优化HBase的随机读取性能,可选值NONE|ROW|ROWCOL,默认为ROW,该参数可以单独对某个列簇启用。启用过滤器,对于get操作以及部分scan操作可以剔除掉不会用到的存储文件,减少实际IO次数,提高随机读性能;
  • VERSIONS:数据版本数,一个cell可以保存带有不同时间戳的多版本数据,默认只保留一份版本;
  • COMPRESSION:数据压缩,目前HBase支持的压缩算法有GZip | LZO | Snappy ,Snappy的压缩率最低,编解码速率最高,hbase官方也推荐使用snappy算法;
  • TTL:数据有效期,默认值是Integer.MAX_VALUE,即2^31-1=2147483647 秒,大约68年;
  • KEEP_DELETED_CELLS:是否保存已删除的cell数据,默认为false;
  • BLOCKSIZE:HFile数据块大小(默认64kb);
  • IN_MEMORY:频繁查询的数据是否要放入缓存,默认为false。HBase Meta元数据信息存储在这块区域,如果业务数据设置为true而且太大会导致Meta数据被置换出去,导致整个集群性能降低;
  • BLOCKCACHE:是否开启数据块缓存,默认为true;
  • SPLITS:region预分区设置,默认建表只分配一个region,会导致数据集中只写入一个region中,可通过在rowkey前面加上4位MD5 hash值,然后再按照首字母0-9a-f分为16份,就可以预分配16个region;

HBASE rowkey设计

Rowkey唯一原则:HBASE是根据key-value格式储存数据的,同样的rowkey数据会相互覆盖
Rowkey长度原则:rowkey尽量越短越好,持久化文件HFile中是按照KeyValue存储的,rowkey过长会影响检索效率
Rowkey散列原则:设计的Rowkey应均匀的分布在各个HBase节点上,可在rowkey高位加上几位随机数,避免所有数据都集中在某几台服务器上,导致数据存储热点问题

数据存储热点问题

rowkey散列算法如下:

import org.apache.hadoop.hbase.util.MD5Hash;

    /**
     * Hbase rowkey生成,返回高位增加四位散列值的rowkey,散列值跟rowkey用下划线分割
     * 根据原始的rowkey信息可以重新生成同样的散列值,方便HBASE的检索
     *
     * @param originRowkey,主键,唯一
     * @return 
     */
    public static String getRowkey(String originRowkey) {
   
        try {
   
            String keyToHash = MD5Hash.getMD5AsHex(originRowkey.getBytes(Constants.UTF8_CHARSET)).substring(0, 4);
            return keyToHash + "_" + originRowkey;
        } catch (UnsupportedEncodingException e) {
   
            throw new BizException(ExceptionEnum.SYSTEM_ERROR);
        }
    }

检索效率

在这里插入图片描述
图中的从左到右查询性能主键变差,因此设计时通常将查询关键信息包含在rowkey中,只需要设置一个列族(官方文档推荐列族的数目在三个以内),这种方式的数据筛选效率是最高的。

性能调优

  • hbase.regionserver.handler.count:
    regionserver上用于等待响应用户表级请求的线程数,CDH安装时的默认值是30。这个值的大小取决于每次读写的数据量大小,当请求内容很大(上MB,比如大的put、使用缓存的scans)的时候,该值设置过大则会占用过多的内存,导致频繁的GC,或者出现OutOfMemory,因此该值需要根据实际业务作判断。

  • hbase.client.write.buffer:
    写入缓冲区大小(以字节为单位),默认值为2M。较大缓冲区需要客户端和服务器中有较大内存,因为服务器将实例化已通过的写入缓冲区并进行处理,这会降低远程过程调用 (RPC) 的数量。为了估计服务器已使用内存的数量,请用值“hbase.client.write.buffer”乘以“hbase.regionserver.handler.count”。

大量数据写入阻塞相关优化

磁盘IO写入性能优化参数:

  • hbase.hregion.max.filesize:
    HStoreFile 最大大小。如果列组的任意一个 HStoreFile 超过此值,则托管 HRegion 将分割成两个,默认大小是10G。region的大小与集群支持的总数据量有关系,如果总数据量小,则单个region太大,不利于并行的数据处理,如果集群需支持的总数据量比较大,region太小,则会导致region的个数过多,导致region的管理等成本过高。
    如果一个Region Server配置的磁盘时12TX3=36T,3台Region Server服务器,数据备份3份,则每台Region Server最多存储12T的数据,HStoreFile 最大大小为10G,每台Region Server最多管理1200个region。通常region的数量不要超过1000个,过多的region。如果Region Server配置的磁盘比36T更大,可以考虑调大HStoreFile 的最大大小。

  • hbase.hstore.compactionThreshold:
    HStore 压缩阈值,默认值为3。如在任意一个 HStore 中有超过此数量的 HStoreFiles,则将运行压缩以将所有 HStoreFiles 文件作为一个 HStoreFile 重新写入。(每次 memstore 刷新写入一个 HStoreFile)您可通过指定更大数量延长压缩,但压缩将运行更长时间。在压缩期间,更新无法刷新到磁盘。长时间压缩需要足够的内存,以在压缩的持续时间内记录所有更新。否则压缩期间客户端会超时。

  • hbase.hstore.blockingStoreFiles:
    HStore 阻塞存储文件数,默认值16。如在任意 HStore 中有超过此数量的 HStoreFiles,则会阻止对此 HRegion 的数据写入更新操作,直到完成压缩或直到超过为 ‘hbase.hstore.blockingWaitTime’ 指定的值。可以调大该值,建议设置到32,避免当写入大量数据时,memstore不能及时flush,触发memstore的block,从而阻塞写操作。

内存写入性能优化参数:

  • HBase RegionServer 的 Java 堆栈大小:
    Region Server java堆大小会限制memstore内存的上限,内存充足的情况下,该值可以设置成6G或者以上。

  • hbase.regionserver.global.memstore.upperLimit:
    RS所有memstore占用内存在总内存中的上限比例,默认值0.4。当达到该值,会从整个RS中找出最需要flush的region进行flush,直到总内存比例降至该数限制以下,并且在降至限制比例以下前将阻塞所有的写memstore的操作。如果集群写入量比较大可以调高该参数。
    一个regionserver上有一个blockcache和N个memstore,它们的大小之和必须小于heapsize(HBase RegionServer 的 Java 堆栈大小)* 0.8,否则hbase不能启动,因为仍然要留有一些内存保证其它任务的执行。因此 hbase.regionserver.global.memstore.upperLimit + hfile.block.cache.size 之和必须小于0.8,因此需要根据业务偏向于读操作还是偏向于写操作调整两者的比例。兼顾读和写的场景可以两者都设置为0.4。

  • hfile.block.cache.size:
    Region Server中的block cache的内存大小限制,默认值0.4。在偏向读的业务中,可以适当调大该值。但是该值和hbase.regionserver.global.memstore.upperLimit之和不能超过0.8。

  • hbase.hregion.memstore.block.multiplier:
    HBase Memstore 块乘法器,默认值为4。如 memstore 的内存大小超过 ‘hbase.hregion.block.memstore’ 乘以 ‘hbase.hregion.memstore.flush.size’ 字节的值,则会阻塞该Menstore的写入操作,避免大量数据写入内存导致内存溢出。同时该内存大小也受到hbase.regionserver.global.memstore.upperLimit上限的限制。内存充足的情况下建议 hbase.hregion.block.memstore 设置为8,hbase.hregion.memstore.flush.size 设置为256M。

  • hbase.hregion.memstore.flush.size:
    HBase Memstore 刷新大小,默认值128M,如果 memstore 大小超过此值,Memstore 中的数据将写入到磁盘中。该值比较适中,一般不需要调整,Region Server的jvm内存比较充足(16G以上),可以调整为256M。

数据版本

HBASE能为一个单元格储存多个版本的数据,是通过每个版本使用一个时间戳并且按照降序存储来实现的。写入数据的时候可以显式提供一个时间戳或者忽略,忽略时间戳的话RegionServer在put数据的时候会取服务器的时间来填充该时间戳。因此要保证HBASE集群服务器的时间是同步的,不然数据版本可能就会发生问题。

hbase客户端

hbase客户端要连接三个不同的服务角色:
(1)Zookeeper:主要用于获得meta-region位置,集群Id、master等信息。
(2)HBase Master:主要用于执行HBaseAdmin接口的一些操作,例如建表等。
(3)HBase RegionServer:用于读、写数据。

Connection是线程安全的,而Table和Admin则不是线程安全的,因此hbase操作工具类里面可以定义一个全局Connection对象,Table对象可以根据 connection.getTable() 方法获取,方法的实现是new一个Table对象返回供当前线程使用。
Table table = connection.getTable(TableName.valueOf(tableName));

客户端核心参数配置

import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * @Auther: wangzhongxing
 * @Date: 2019/12/6 17:05
 * @Description:
 */
@Configuration
public class HbaseClusterConfig {
   

    private static final Logger LOGGER = LoggerFactory.getLogger(HbaseClusterConfig.class);

    /**
     * zook集群地址
     */
    @Value("${hbase.zookeeper.quorum}")
    private String HBASE_ZOOKEEPER_QUORUM;
    /**
     * zookeeper端口
     */
    @Value("${hbase.zookeeper.property.clientPort:2180}")
    private String HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT;
    /**
     * RPC请求的超时时间。如果某次RPC时间超过该值,客户端就会主动关闭socket
     */
    @Value("${hbase.rpc.timeout:6000}")
    private String HABSE_RPC_TIMEOUT;
    /**
     * 为一次操作总的时间(从开始调用到重试n次之后失败的总时间),有可能包含多个RPC请求,如果有一次操作大批量数据的场景,该参数的时间需要调大
     */
    @Value("${hbase.client.operation.timeout:30000}")
    private String HBASE_CLIENT_OPERATION_TIMEOUT;
    /**
     * HBase客户端发起一次scan操作的所有rpc调用至得到响应之间总的超时时间。
     * 一次scan操作hbase会根据scan查询条件的cacheing将scan操作会分成多次rpc操作。
     * 例如:满足scan条件的rowkey数量为10000个,scan查询的cacheing=200,则查询所有的结果需要执行的rpc调用次数为50个。而该值是指50个rpc调用的单个相应时间的最大值。
     */
    @Value("${hbase.client.scanner.timeout.period:6000}")
    private String HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD;
    /**
     * 失败重试次数,默认为31次
     */
    @Value("${hbase.client.retries.number:3}")
    private String HBASE_CLIENT_RETRIES_NUMBER;

    @Autowired
    private Connection connection;

    /**
     * Connection是线程安全的,不同线程可以共用一个Connection
     * @return
     */
    @Bean
    public Connection connection() {
   
        org.apache.hadoop.conf.Configuration conf = HBaseConfiguration.create();
        // zookeeper集群地址
        conf.set("hbase.zookeeper.quorum", HBASE_ZOOKEEPER_QUORUM);
        // zookeeper端口
        conf.set("hbase.zookeeper.property.clientPort", HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT);
        conf.set("hbase.rpc.timeout", HABSE_RPC_TIMEOUT);
        conf.set("hbase.client.operation.timeout", HBASE_CLIENT_OPERATION_TIMEOUT);
        conf.set("hbase.client.scanner.timeout.period", HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD);
        conf.set("hbase.client.retries.number", HBASE_CLIENT_RETRIES_NUMBER);

        Connection conn = null;
        try {
   
            conn = ConnectionFactory.createConnection(conf);
        } catch (Exception e) {
   
            LOGGER.error("init hbase connection error!HBASE_ZOOKEEPER_QUORUM={},HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT={}",
                    HBASE_ZOOKEEPER_QUORUM, HBASE_ZOOKEEPER_PROPERTY_CLIENTPORT, e);
        }

        return conn;
    }
}

hbase部署配置

hadoop部署注意点

namenode部署

namenode内存配置(hadoop-env.sh文件中的HADOOP_NAMENODE_OPTS属性):
参考hadoop权威指南->hadoop配置->环境配置:
在这里插入图片描述
namenode数据备份配置:
dfs.namenode.name.dir可以指定多个目录(不同目录需要分配在不同的磁盘中)并配置一个nfs远程文件系统,配置多个目录时namenode的元数据文件会同时冗余备份在所指定的目录中。这样即使当中的某一块磁盘发生故障,其余的磁盘都可以重新恢复元数据文件。

DataNode部署

DataNode存储目录配置:
dfs.datanode.data.dir可以指定多个目录,其目的是使DataNode循环地在各个目录下写数据,因此为了提高性能,可以在每个本地磁盘指定一个存储目录,使数据块跨磁盘分布,针对不同数据块的读操作可以并发执行,提升读取性能。
小提示:DataNode磁盘挂载可以选用noatime选项挂载,读写文件时无需记录文件修改和访问时间,可以显著提升读写性能。

hbase集群管理

添加备用master服务器

  1. 修改集群中所有服务器的 backup-masters 文件,添加需要新增的备用master服务器节点;
  2. 将hbase集群服务器中的hbase安装文件夹拷贝到要添加的服务器节点上;
  3. 进入hbase安装目录的bin目录下,执行命令以下命令,如果当前已有master服务器在运行,该命令会启动一个新的备用master进程:
    -> ./hbase-daemon.sh start master
  4. 查看hbase监控web界面,是否增加了对应的Backup Masters服务器;

添加regionServer服务器

  1. 修改集群中所有服务器的 regionservers 文件,添加需要新增的regionServer服务器节点;
  2. 将hbase集群服务器中的hbase安装文件夹拷贝到要添加的服务器节点上;
  3. 进入hbase安装目录的bin目录下,执行命令以下命令,新启动的regionServer服务器就会加入到集群当中并被分配region;
    -> ./hbase-daemon.sh start regionserver

添加hadoop DataNode服务器

  1. 配置新增节点与集群其他节点之间的ssh免密登录,参考文档:HBASE2.2.2+hadoop3.1.3分布式集群搭建
  2. 修改hadoop集群中所有服务器的 $HADOOP_HOME/etc/hadoop下workers文件,添加新增的DataNode节点;
  3. 启动新曾DataNode节点上的DataNode和NodeManager进程,并重新数据块分布负载均衡:
    -> $HADOOP_HOME/bin/hdfs --daemon start datanode
    -> $HADOOP_HOME/bin/yarn --daemon start nodemanager
    -> start-balancer.sh

DataNode添加硬盘

  1. 修改$HADOOP_HOME/conf/hdfs-site.xml文件(注意如果hadoop安装目录所属的用户没有新增硬盘的读写权限,需要分配对应的硬盘权限);
    <!-- datanode存放数据的位置,默认值为:file://${hadoop.tmp.dir}/dfs/data
         配置多个磁盘时用英文逗号将多个磁盘路径隔开 -->
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>/home/hadoop/data,/ext1/hadoop/data</value>
    </property>
  1. 将修改后的hdfs-site.xml文件拷贝到hbase安装目录的conf/ 目录下替换原有的文件;
  2. 将修改后的hdfs-site.xml文件分发到所有集群服务器的hadoop安装目录下的 etc/hadoop/ 目录和hbase安装目录下的conf/ 目录。如果只是在单台DataNode服务器上添加硬盘则无需分发到其它的DataNode服务器;
  3. 配置完之后重启DataNode节点即可:
    -> $HADOOP_HOME/bin/hdfs --daemon start datanode
    -> $HADOOP_HOME/bin/yarn --daemon start nodemanager
    -> start-balancer.sh

hbase性能测试

PE(Performance Evaluation)

hbase自带性能评估工具PE,可输入不带参数的命令(hbase pe)查看参数详解:

-> hbase pe --oneCon=true --valueSize=1000 --compress=SNAPPY --rows=1500000 --autoFlush=true --presplit=64 randomWrite 2

核心参数解释:
–nomapred表示不使用MAPREDUCE框架
–oneCon=true 所有线程是否共享连接
–valueSize=100 一次写入所写入value的大小,默认:1000
–compress 压缩方式
–presplit=64 创建预分表(初始情况下将table分为多少个分区)
–autoFlush=true client在收到put请求时是否每次都发送到region server
–rows=150000 每个线程需要发送的数据量
–randomWrite 运行随机写测试

运行命令不需要输入表名,不需要设定列族,PE代码会自动生成一张TestTable的测试表并往测试表中插入数据,并在运行完命令后打印出测试结果,测试结果如下:
在这里插入图片描述

hbase数据跨集群迁移

使用export/import进行数据迁移:
使用HBase自带的Export/Import实现,这种方法可以支持网络不通的集群之间的数据迁移。迁移步骤是先利用Export将需要数据迁出的HBase表数据转换成文件存入HDFS中,然后通过get命令将HDFS上的文件下载到本地文件中,在将文件拷贝到需要数据迁入的集群上,通过put把拷贝的本地文件上传至目标集群的HDFS中,最后利用Import命令将目标集群HDFS中的数据导入到HBase表。以上过程需要MapReduce的支持,所以需要启动yarn。

# 使用export命令将源HBase集群中的表的数据以文件的形式存储到HDFS中
# 命令格式:hbase org.apache.hadoop.hbase.mapreduce.Export <tableName> <hdfsPath> 
# 参数详解:
# tableName: 需要导出的hbase表名
# hdfsPath: hbase表数据备份到hdfs上的文件路径
hbase org.apache.hadoop.hbase.mapreduce.Export task_label_icon /task_label_icon

# 使用get命令将上一步HDFS上的数据下载到本地(该命令会在本地文件夹中自动创建于HDFS同目录的表名)。
# 该步骤要注意本地文件夹读写权限问题,读写权限不足可能会导致导出数据到本地文件夹失败
# 命令格式:hadoop fs -get <hdfsPath> <localFilePath>
# 参数详解:
# hdfsPath: hdfs上需要导出数据的文件路径
# localFilePath: hdfs上的数据导出到服务器本地的文件路径
hadoop fs -get /task_label_icon /home/hadoop/exportdata/

#源HBase下载到服务器上的数据通过scp命令拷贝到目标服务器,在目标服务器上通过put命令将数据存储到HDFS对应目录中,如果HDFS的目录不存在需要先创建相关目录。步骤如下:
# 使用scp将源集群的数据文件传送到目标集群服务器上
scp -r <sourceFilePath> <destIp>:<destFilePath>
# 在目标hbase集群上创建对应的HDFS文件目录
hdfs dfs -mkdir <hdfsDir>
# 在目标hbase集群上,将源集群拷贝过来的本地数据文件上传到目标集群的HDFS指定目录上
# 命令格式:hadoop fs -put <localFilePath> <hdfsDir>
hadoop fs -put /home/hadoop/exportdata/task_label_icon/ /task_label_icon/
# 查看HDFS目录下的文件是否上传成功
# 命令格式:hadoop fs -ls <hdfsDir>
hdfs dfs -ls /task_label_icon

# 将HDFS上的数据导入到HBase对应的表中,前提是目标HBase集群已存在这张表。而且Import数据到HBase表的过程是append(累加)而不是overwrite(覆盖)。
# 将HDFS的数据导入到hbase对应的表中,要注意先查看数据put进HDFS中的实际文件目录,目录不一致会导致写入失败
# 命令格式:hbase org.apache.hadoop.hbase.mapreduce.Import <tableName> <hdfsPath>
hbase org.apache.hadoop.hbase.mapreduce.Import task_label_icon /task_label_icon

hbase重命名表名

# 先disable掉表<demo_table>
disable 'demo_table'
# 创建快照
snapshot 'demo_table', 'demo_table_snapshot'
# 克隆这个快照 赋给新的表名<demo_table_old>
clone_snapshot 'demo_table_snapshot', 'demo_table_old'
# 删除之前创建的快照
delete_snapshot 'demo_table_snapshot'
# 再删除掉之前的表<demo_table>
drop 'demo_table'

hbase java操作工具类

import com.insigma.traffic.road.detection.common.constants.Constants;
import com.insigma.traffic.road.detection.model.common.HbaseBatchInsertParam;
import com.insigma.traffic.road.detection.model.exception.BizSystemException;
import com.insigma.traffic.road.detection.model.exception.ExceptionEnum;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.MD5Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值