深入浅出HBase:一文理解HBase基础概念(列存储、时间戳、key-value)、架构特点以及适合的使用场景

通过了解Hbase的基础概念、架构特点,底层原理等,可以了解HBase读写的特点。通过从底层理解这些特点,我们能够将HBase应用到合适的场景。
本文主要了解HBase的:

  1. hbase的概念、架构
  2. hbase的特点细节:存储拓展性、列存储、定义数据版本、查询快吗
  3. 应用场景、生产注意事项。

 

Apache HBase™ is the Hadoop database, a distributed, scalable, big data store.

Apache HBase 是 Hadoop 数据库,一个分布式、可伸缩的大数据存储。HBase是一个NoSQL数据库,它把数据存在HDFS上。
HBase在HDFS之上提供了高并发的随机写和支持实时查询,这是HDFS不具备的。

 

HBase特点:

  • 存储数据的”结构“可以地非常灵活。
  • HBase仅能通过主键(row key)和主键的range来检索数据,仅支持单行事务(可通过 hive
    支持来实现多表 join等复杂操作),主要用来存储结构化和半结构化的松散数据。
  • Hbase 目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。

 

一. HBase 数据模型

1. 行存储与列式存储

1.1. 行存储

行存储系统以行的方式来组织数据。行存储将数据一行一行的写入,写入一条数据记录时,只需要将数据追加到已有数据记录后面即可。

在这里插入图片描述

行模式存储适合 OLTP(Online Transaction Processing)系统。因为数据基于行存储,所以数据的写入会更快。对按记录查询数据也更简单。

场景:统计整个系统今天的博客点赞数。对于行存储系统,需要将所有行数据读入内存,然后对 like_num 列做 sum 操作,从而得到结果。
在这里插入图片描述

我们假设磁盘一次可以读取图中 3 个方框的数据,那么这个聚合计算需要 N(N/3=数据量)次磁盘访问。

 

1.2. 列存储

同样是上面的示例数据,我们来看列式存储是怎样组织数据的。
在这里插入图片描述

列式存储将每一列的数据组织在一起。
利于对于列的操作,如上面我们说到的统计所有 like_num 之和。其过程将如下:
在这里插入图片描述

依然假设磁盘一次可以读取 3 个方框的数据(实际按 byte 读取)。可以看出按列存储组织数据的方式,只需要 1 次磁盘操作就可以完成。

 
列式存储也有不利的一面。首先就表现在数据写入上。
在这里插入图片描述
HBASE是怎么存储的?我们下面讨论。

 

对比:
在这里插入图片描述

 

2. HBase 数据模型

2.1. 模型概览

概念解释
Name Space1. 命名空间,类似于关系型数据库的 DataBase 概念,每个命名空间下有多个表。
2. HBase有两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表,default 表是用户默认使用的命名空间。
Region1. 类似于关系型数据库的表概念。
2. 不同的是,HBase 定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往 HBase 写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase 能够轻松应对字段变更的场景。
Row1. HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成。
2. 数据是按照 RowKey的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要(热点数据,同类数据下的实时需求)。
rowKeyrowKey又叫行键,它是有序的(字典顺序)
Column1. HBase 中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限定,例如 info:name,info:age。
2. 建表时,只需指明列族,而列限定符无需预先定义。
Time Stamp用于标识数据的不同版本,每条数据写入(新增、更新、删除等操作都是写入)时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入 HBase 的时间。
Cell{rowkey, column Family:column Qualifier, time Stamp} 确定一条数据的单元。

 
接下来重点讲解几个和关系型数据库不同的概念。
 

2.2. 列与列族

HBase的列不是我们在关系型数据库所想象中的列。

HBase的列(Column)都得归属到列族(Column Family)中。在HBase中用列修饰符(Column Qualifier)来标识每个列。在HBase里,先有列族,后有列。

什么是列族?可以简单理解为:列的属性类别
什么是列修饰符?在列族下用列修饰符来标识一列。

 

看一个的例子:
在这里插入图片描述

HBase表的每一行中,列的组成都是灵活的,行与行之间的列不需要相同。换句话说:一个列族下可以任意添加列,不受任何限制

在这里插入图片描述

 

2.3. 时间戳:定义数据版本

数据写到HBase的时候都会被记录一个时间戳,这个时间戳被我们当做一个版本。比如说,我们修改或者删除某一条的时候,本质上是往里边新增一条数据,新增了记录的版本。

在这里插入图片描述

  • 比如现在修改phone的值,实际上就是多添加一条记录,在读的时候按照时间戳读最新的记录。在外界「看起来」就是把这条记录改了。
  • 删除一条数据实际上也是增加一条记录,只不过我们在KeyType里边设置为“Delete”就可以了。

 

2.4. HBase的Key-Value

HBase本质上其实就是Key-Value的数据库,那在HBase里边,Key是什么?Value是什么?
在这里插入图片描述

每个KeyValue都由4个部分构成,分别为key length,value length,key和value。其中

  1. key length和value length是两个固定长度的数值
  2. key是一个复杂的结构,

rowkey长度、rowkey
ColumnFamily长度、ColumnFamily、ColumnQualifier
时间戳
KeyType(keytype有四种类型,分别是Put、Delete、 DeleteColumn和DeleteFamily)

  1. value就是一串纯粹的二进制数据。

准确定位一条数据,那就需要知道:RowKey+Column+时间戳。

 

二. HBase架构

在这里插入图片描述

1. HBase读写流程简述

1、Client客户端,它提供了访问HBase的接口,并且维护了对应的cache来加速HBase的访问。
2、zookeeper不存储meta表,meta表还是放到regionserver里的region存储的,和其他表没啥太大区别,相当于其他表的索引,而zookeeper放的是meta表的索引,相当于索引的索引。

查找快的原因:

  1. 从Zookeeper里拿到meta元数据告诉给客户端去哪台机器读写数据,这也是hbase能支持海量数据快速查找的一个原因。
  2. 数据按rowkey字典序排序了,既然是排好序的数据,那二分法查找,时间复杂度是logN。

3、HRegionServer它是处理客户端的读写请求,负责与HDFS底层交互,是真正干活的节点。

 

总结大致的流程就是:

  1. client请求到Zookeeper,Zookeeper返回HRegionServer地址给client
  2. client得到Zookeeper返回的地址去请求HRegionServer,HRegionServer读写数据后返回给client。

 

2. HRegionServer内部

先来思考一个问题:

HBase一张表的数据会分到多台机器上的。那HBase是怎么切割一张表的数据的呢?用的就是RowKey来切分,其实就是表的横向切割。

在这里插入图片描述
 

在这里插入图片描述

 

内部数据流转:HRegion <- Store(HFile) <- MemStore <- HLog

名字说明
Region一个Region存储HBase表的一部分数据,多个Store组成一个Region。
StoreFile(HFile)1. 物理结构:保存实际数据的物理文件,StoreFile 以 HFile 的形式存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的。
2. 用于存储列族:一个列族的数据是存储在一起的,即存储到一个HFile中。所以我们可以认为HBase是基于列族存储的。
3. HFileKeyValue形式存储数据。
MemStore写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 MemStore 中,排好序后,等到达刷写时机才会刷写到 HFile,每次刷写都会形成一个新的 HFile。
HLog1. 为了防止数据丢失:由于数据要经 MemStore 排序后才能刷写到 HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写到HLog中,然后再写入 MemStore 中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
2. 顺序写:HLog是顺序写到磁盘的,所以速度还是挺快的(类似于kafka的感觉)

小结:

  1. HRegionServer是真正干活的机器(用于与hdfs交互),我们HBase表用RowKey来横向切分表
  2. HRegion里边会有多个Store,每个Store其实就是一个列族的数据(即HBase基于列族存储的)
  3. Store里边有Men Store和StoreFile(HFile),其实就是先走一层内存,然后再刷到磁盘的结构

 

3. HMaster

上面我们知道RegionServer管理所有Region,负责DML操作,接下来我们看HMaster。

HMaster负责DDL操作、管理所有RegionServer:

  • HMaster会处理元数据的变更;
  • 监控RegionServer的状态:在RegionServer出现意外宕机时,Master会负责该RegionServer上的Region进行迁移;
  • 处理 HRegion 的分配或转移:如果HRegion的数据量太大的话,HMaster会对拆分后的Region 重新分配RegionServer

 

三. 特性讨论

1. 大数据存储与拓展

  1. 海量存储:Hbase 适合存储 PB 级别的海量数据,在 PB 级别的数据以及采用廉价 PC 存储的情况下,能在几十到百毫秒内返回数据。
  2. 极易扩展: a. 基于存储的扩展(HDFS)。b. 通过横向添加 RegionSever 的机器,进行水平扩展,提升 Hbase 上层的处理能力。
  3. 基于Hbase的设计理念与存储原理,Hbase单表可以有百亿行、百万列,在横向和纵向两个维度所支持的数据量级都非常巨大,在列上其实并没有数量的限制。

所以根据业务需求,当表需要非常大时,可考虑选型Hbase。 如果最多只是上亿条记录并且列不是特别大的话,没有必要放入hbase中,ES和mongodb都能轻松搞定。

  1. 数据高可靠性:Hbase本身高可靠性特性以及HDFS的高可靠性。

 

2. HBase速度真的很快?

2.1. 为何HBase速度很快?

前面说过HBase会将数据保存到内存中,在内存中的数据按照key字典顺序排序,如果内存空间满了,会刷写到HFile中,所以HFile的内容也是有序的。当数据写入HFile后,内存中的数据会被丢弃。

HBase能提供实时计算服务主要原因是由其架构和底层的数据结构决定的, 即由

  • HTable(region分区)
  • Cache
  • LSM-Tree(Log-Structured Merge-Tree)
2.1.1. 写入快的原因

HBase的写入速度快是因为

  1. 它其实并不是真的立即写入文件中,而是先写入内存,随后异步刷入HFile。所以在客户端看来,写入速度很快。
  2. 另外,得益于LSM-Tree数据结构,写入时候将随机写入转换成顺序写,数据写入速度也很稳定。

 

2.1.2.查询快的原因
a. Region定位

通过RowKey定位数据所在Region

  1. 客户端先通过ZooKeeper的/hbase/meta-region-server节点查询到哪台RegionServer上有hbase:meta表。
  2. 客户端连接含有hbase:meta表的RegionServer。hbase:meta表存储了所有Region的行键范围信息,通过这个表就可以查询出你要存取的rowkey属于哪个Region的范围里面,以及这个Region又是属于哪个RegionServer。
  3. 获取这些信息后,客户端就可以直连其中一台拥有你要存取的rowkey的RegionServer,并直接对其操作。

注意:客户端会把查询到的meta信息缓存起来,下次操作就不需要进行以上加载hbase:meta的步骤了。

 

b. LSM树型结构

读取速度快是因为它使用了 LSM树型结构(ing),而不是B或B+树。磁盘的顺序读取速度要比寻找磁道速度快很多。

HBase的存储结构使得磁盘寻道时间在可预测范围内,并且rowkey任意连续数量的记录都不会引发额外的寻道开销。

比如有5个存储文件,那么最多需要5次磁盘寻道就可以。而关系型数据库,即使有索引,也无法确定磁盘寻道次数。

 

c. LRU Cache算法 + MemStore内存

HBase首先会在 缓存(BlockCache)中查找,它采用了 LRU(最近最少使用算法),如果缓存中没找到,会从内存中的MemStore中查找,只有这两个地方都找不到时,才会加载HFile中的内容,而上文也提到了读取HFile速度也会很快,因为节省了寻道开销。

 
小结:

数据定位: 客户端定位数据所在HRegion server,接着在服务器的一个region上查找要匹配的数据,通过内存(LRU Cache算法 + MemStore内存)或访问磁盘(LSM)查找到数据。

 

2.1.3. 举例说明

hbase是根据rowkey查询的,我们看下如何快速定位rowkey

  1. 快速找到(region)分区:假设表有10亿条记录,占空间1TB, 分列成了500个region, 通过zk+二分法找到region的时间复杂度为:O(log₂500)(9步)。

  2. 根据列存储缩小范围:是按列存储的,其实是列族,假设分为3个列族,每个列族就是666M, 设要查询的东西在其中1个列族上(包含一个或多个HFile、假设一个HFile是128M),该列族包含在5个HFile文件,剩下的在内存中。

  3. 根据字段排序再加速查询:查找的记录有可能在最前面或最后面,假设最坏情况在中间,我们只需遍历2.5个HStoreFile共300M

  4. 通过rowkey遍历一个个数据块中key的位置,并判断符合条件可以了。一般key是有限固定长度,假设key:value长度是1:19,最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。 加上块缓存机制(LRU原则),会取得更高的效率。

HBase适合存储PB级别的海量数据(百亿千亿量级条记录),如果根据记录主键Rowkey来查询,能在几十毫秒返回数据。

 

2.2. 查询效率什么情况下会降低

hbase适合大数据量的查询,但并不适合大范围的查询,本质上HBase是put、get kv对,海量数据下跑类似select一样的检索scan一遍全表,且因为分布式的原因查询效率会随着查询范围的增大导致查询效率降低。

 

3. 有限的查询方式

3.1.不适合复杂查询

HBase作为一种KV数据库,数据访问模式以主键为核心,当面对非主键查询时,其原生解决方案无法满足大多数联机应用的性能需求。

 

3.2. 不支持sql查询

与传统的关系型数据库不同,HBase不支持SQL查询,也没有标准的关系型数据库中所定义的表关系,也不支持多表联合查询。

其次,由于HBase是面向列(Column)的数据存储模型,它可以存储大量稀疏的数据,而这种数据在关系型数据库中一般是难以处理的,需要对数据进行多表关联才能实现。因此,在HBase中使用SQL查询的效率和灵活性都是相对较低的。

实际使用过程中,可以使用HBase提供的API或者其他工具来实现数据的读写操作。

public class HBaseManager {
    public static void main(String[] args) throws IOException {
        //创建HBase配置对象
        Configuration config = HBaseConfiguration.create();
        //设置Zookeeper地址,Zookeeper是HBase的元数据存储位置
        config.set("hbase.zookeeper.quorum", "localhost");
        //创建HBase连接
        Connection conn = ConnectionFactory.createConnection(config);
        //获取HBase表对象
        Table table = conn.getTable(TableName.valueOf("test_table"));
        //创建HBase行键对象
        byte[] rowKey = Bytes.toBytes("rowkey1");
        //创建HBase查询对象
        Get get = new Get(rowKey);
        //获取HBase行数据
        Result result = table.get(get);
        //读取HBase列数据
        byte[] value = result.getValue(Bytes.toBytes("cf"), Bytes.toBytes("col1"));

 

4. 列存储的特点

  1. 适合稀疏特性的数据进行存储
    为空的列并不占用存储空间,表可以设计的非常稀疏。不必像关系型数据库那样需要预先知道所有列名然后再进行null填充。
  2. 动态列拓展
    Hbase的数据在表中是按照列族进行存储的,可动态增加列,这样在只查询少数几个字段的时候,不需要全表扫描,能极大提高查询效率。

 

四、适合的业务场景

针对某些特点的数据可以使用 HBase 高效地解决,如以下的应用场景。

  • 数据库中的很多列都包含了很多空字段,在 HBase 中列存储使得空字段不会像在关系型数据库中占用空间。
  • 需要很高的吞吐量,瞬间写入量很大。
  • 数据有很多版本需要维护,HBase 利用时间戳来区分不同版本的数据。
  • 具有高可扩展性,能动态地扩展整个存储系统。
  • 通过rowkey查询数据很快

 

1. 写密集型但相对读数量较小的应用

京东用Hbase存储卖家操作日志,即几十万商家时时刻刻进行的各种操作。以便进行分析,并且可以保证商家可以精确查询自己的各种操作。卖家操作日志的特点是:数据量大、实时性强、增多查少。

因此,Hbase适合做海量数据(亿万条记录)的最底层数据源。

 

2. 搜索引擎

HBase 是 Google Bigtable 的开源实现,而 Google 公司开发 Bigtable 是为了它的搜索引擎应用。

 
这里举例:基于 Elasticsearch 存储的HBase二级索引方案

一级索引的限制

HBase里面只有rowkey作为一级索引, 如果要对库里的非rowkey字段进行数据检索和查询, 往往要通过MapReduce/Spark等分布式计算框架进行,硬件资源消耗和时间延迟都会比较高。

添加二级索引

采用Elasticsearch存储HBase的索引信息,以支持复杂高效的查询功能。

  • ES中存放用于检索条件的数据(用来检索的少数几个字段),并将row key也存储进去;
  • ES根据查询条件查询出row key集合
  • row key去HBase数据库查询对应的结果。

在这里插入图片描述
 
优点:各取所长,发挥两个组件各自的优势

  1. 发挥了ES的全文检索的优势,能够快速根据关键字检索出相关度最高的结果;
  2. 减少了ES的存储压力,这种场景下不需要存储检索无关的内容,节约存储空间,同时提升写入速度;
  3. 避免了ES大数据量下查询返回慢的问题,大数据量下Hbase的抽取速度明显优于Elasticsearch;

成效

  • 之前:1T 数据都放 es,会每次查询都是 5 ~ 10 秒,现在可能性能就会很高,每次查询就是 50ms。
  • 现在:从 es 检索可能就花费 20ms,然后再根据 es 返回的 rowkey 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms。

 

3. 车联网数据的收集

车联网系统是利用车载设备收集车辆运行时产生的各项数据,通过网络实时上传,在平台进行动态分析和利用。

在这里插入图片描述

车联网系统所面对的数据特点是
大量车辆终端高并发的不间断写入TB级甚至PB级的数据,而且对于实时分析来说,为了保证分析结果的时效性,又要求查询的低时延响应

HBase采用LSM存储模型,可以从容应对高并发写入的场景,同时也能保证读时延在可接受的范围内。同时HBase具有良好的水平扩展能力。通过增减RegionServer来实现对存储容量动态调整,满足对使用成本的要求。

 

4. 标签数据(稀疏矩阵)的存储

标签数据是稀疏矩阵的代表,描述了实体的各类属性,主要应用于智能推荐、商务智能或营销引擎等领域。

如下:三个不同的用户在同一公司旗下的不同APP中留下了大量的行为数据,这些数据中包含了直接填写的用户资料、使用APP的具体行为以及领域专家对某些现象的标记,通过后台的标签算法可以得到这样的数据:
在这里插入图片描述

可以看到,对用户行为采集存在局限性,因此所能得到的标签种类各不相同,表中大量的数据项只能被置空,也就是所谓的稀疏矩阵。而且随着用户更深度的使用APP,可以预见到,对用户感兴趣领域/不感兴趣领域会逐渐被发掘,那么表的列也会随之增加。

使用HBase存储时,未指定value的列不会占用任何的存储空间,且HBase对于Column的增删极为容易,有利于应对未来属性的扩张。

 

5. 用户交互数据

例如,Facebook 里的 Like 按钮,每次用户 Like —个特定主题,计数器增加一次。FaceBook 使用 HBase 的计数器来计量人们 Like 特定网页的次数。博主可以得到近乎实时的、用户 Like 他们网页的数据信息。他们可以据此更敏捷地判断应该提供什么内容。

 

6. 广告效果和点击流

  • 实时存储: 使用HBase实时存储点击行为数据(如点击广告的时间、位置)。
  • 实时推荐:当用户访问网站或APP时,系统可以实时地根据用户的历史点击行为从HBase中检索数据,并使用推荐算法(如协同过滤、内容推荐等)来推荐相关的广告给用户。
  • 反馈和优化: 收集用户对广告的点击反馈,用于优化推荐算法。
  • 扩展和分析: 利用HBase的分析工具对积累的数据进行批量处理,发现用户行为规律,进一步优化系统。

 

五. 用在生产环境前的注意事项

HBase从本身原理和特性上保证了其高可用、高可靠性,以及分布式全内存异步的高写入性能,那么最终用在生产前需要注意以下几个方面?

1. 查询条件

HBase查询条件简单,只支持基于主键rowkey索引,即只能通过rowkey进行查询,不能像其他数据库一样使用多条件复杂查询,不支持二级索引,因此选型前,需确认是否能满足业务需求。

 

2. rowkey设计要求较高

2.1. RowKey设计

设计RowKey时要保证RowKey是唯一的,毕竟它是行键,其次为了带来更好的满足业务需求,我们可以使用时间戳、自增数据、组合键(例如:userId_regionId_timestamp)等方式。

 

2.2. 根据RowKey查询

在HBase里边提供了:全局扫描、根据一个RowKey进行查询、根据RowKey过滤的范围查询等三种方式。

a. 根据RowKey进行单条查询

从0.96版本之后,Hbase为二层查询架构。

(1)客户端先通过ZooKeeper的/hbase/meta-region-server节点查询到哪台RegionServer上有hbase:meta表。
(2)客户端连接含有hbase:meta表的RegionServer。hbase:meta表存储了所有Region的行键范围信息,通过这个表就可以查询出你要存取的rowkey属于哪个Region的范围里面,以及这个Region又是属于哪个RegionServer。
(3)获取这些信息后,客户端就可以直连其中一台拥有你要存取的rowkey的RegionServer,并直接对其操作。
客户端会把meta信息缓存起来,下次操作就不需要进行以上加载hbase:meta的步骤了。

RowKey是会按字典序排序的,HBase表会用RowKey来横向切分表。

HRegion有start-keyend-key两个属性,用于标识RowKey的范围。当读写时,我们通过RowKey+[start-key,end-key)定位到HRegion,也就定位到HRegionServer。

 

b. 根据RowKey范围查询的场景

假设直播过程中,间隔几秒就采集直播间热度,之后业务方经常要把主播的一段时间内的热度给查询出来。
通过设计RowKey( 某直播平台_主播名ID_直播时间戳 ),将该主播的一段时间内的热度都写到同一个HRegion上,拉取的时候只要访问一个HRegionServer就可以得到全部我想要的数据了,那查询的速度就快很多。

 

2.3. 热点问题处理

由于我们的RowKey是以字典序排序的,如果我们对RowKey没有做任何处理,那就有可能存在热点数据(某类数据会出现很集中的现象)的问题。

  • 方式1: 自己指定RowKey的分割点来划分region个数。

比如有一组数据RowKey为[1,2,3,4,5,6,7],此时给定split。 RowKey是1,3,6,那么就会划分为[1,3),[3,6),[6,7)的三个region里。如果对于RowKey的组成及数据分布非常清楚的话,可以使用这种方式精确预分区。

  • 方式2 : 只知道RowKey的组成大致的范围时:

设定始末的RowKey,以及根据数据量给定大致的region数。一般建议region数最多不要超过集群的regionServer节点数,过多region数不但不能增加表访问性能,反而会增加master节点压力。如果给定始末RowKey范围与实际偏差较大的话,还是比较容易产生数据热点问题。

  • 方式3:生成RowKey时,进行加盐或者哈希的处理,可以缓解数据热点问题。

HBase是Key/vale数据库,也只能通过key(即rowkey)来查询数据,rowkey的设计非常重要,一个优秀的rowkey设计,即可以满足查询业务需求,同时也能让数据均衡分布在集群中的节点上。提升读写性能。

 

3. 不太适合大范围key查询

从HBase的存储原理可知,其根据rowkey字节范围进行分区分文件存储,大范围的数据查询会使查询落到多个不同的RegionServer上,所以大范围的rokey查询,查询效率会比较低下。

 

4. Hbase部署相对复杂,运维成本高

部署Hbase集群之前,首先要部署Hadoop集群,这包括HDFS、Yarn、Mapredue等一系列组件,其次还要部署Zookeeper集群。
在这两块的服务都正常部署启动后,才能部署HBase集群,此外还包括监控运维等服务组件。

 

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

roman_日积跬步-终至千里

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

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

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

打赏作者

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

抵扣说明:

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

余额充值