java生成数据插入hbase_java hbase api 批量高效写入数据(线程池方式)

本文介绍了如何通过Java利用HBase API和线程池实现批量高效地写入HBase数据,测试显示能达到每秒百万级别的写入速度。文章提供了一段示例代码,包括配置、创建表、写入数据等关键步骤,适用于大数据场景下提高HBase写入性能。
摘要由CSDN通过智能技术生成

java hbase api 批量高效写入数据(线程池方式)

2018-09-16

笔者尝试各种方法写入Hbase,其中使用线程池方式写入数据最为快速。

测试环境:hbase 1.2.4,Hadoop 2.7 , 单条数据大小1kb,7台服务器24核48g内存千兆网卡,测试1000w数据写入

测试结果能达到百万级每秒写入速度。

下面上代码。

pom.xml

[html]

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

hcg

hcg

1.0-SNAPSHOT

Akka repository

http://repo.akka.io/releases

jboss

http://repository.jboss.org/nexus/content/groups/public-jboss

Sonatype snapshots

http://oss.sonatype.org/content/repositories/snapshots/

cloudera

https://repository.cloudera.com/artifactory/cloudera-repos/

central

http://repo.maven.apache.org/maven2

src/

src/test/

org.apache.maven.plugins

maven-shade-plugin

2.2

package

shade

*:*

META-INF/*.SF

META-INF/*.DSA

META-INF/*.RSA

implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">

reference.conf

implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">

org.apache.maven.plugins

maven-compiler-plugin

1.7

1.7

1.2.4

I:/java/jdk1.7

org.apache.hbase

hbase-client

${hbase.version}

org.apache.hbase

hbase-common

${hbase.version}

org.apache.hbase

hbase-server

${hbase.version}

HBaseUtil.Java

[java]

package cn.ngsoc.hbase.util;

import org.apache.commons.lang.StringUtils;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.hbase.*;

import org.apache.hadoop.hbase.client.*;

import org.apache.hadoop.hbase.filter.FilterList;

import org.apache.hadoop.hbase.filter.PageFilter;

import org.apache.hadoop.hbase.io.compress.Compression;

import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;

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

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.io.IOException;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Iterator;

import java.util.List;

/**

* HBase 工具类

* Created by babylon on 2016/11/29.

*/

public class HBaseUtil {

private static final Logger logger = LoggerFactory.getLogger(HBaseUtil.class);

private static Configuration conf;

private static Connection conn;

//  static {

//      try {

//          if (conf == null) {

//              conf = HBaseConfiguration.create();

                conf.set("hbase.zookeeper.property.clientPort", ConfigUtil.getInstance().getConfigVal("zkport", ConstantProperties.COMMON_PROP));

//                conf.set("hbase.zookeeper.quorum", ConfigUtil.getInstance().getConfigVal("zkhost", ConstantProperties.COMMON_PROP));

//                conf.set("zookeeper.znode.parent", "/hbase");

//          }

//      } catch (Exception e) {

//          logger.error("HBase Configuration Initialization failure !");

//          throw new RuntimeException(e) ;

//      }

//  }

public static void init(String zkHost){

try {

if (conf == null) {

conf = HBaseConfiguration.create();

//                conf.set("hbase.zookeeper.property.clientPort", ConfigUtil.getInstance().getConfigVal("zkport", ConstantProperties.COMMON_PROP));

conf.set("hbase.zookeeper.quorum", zkHost);

conf.set("zookeeper.znode.parent", "/hbase");

}

} catch (Exception e) {

logger.error("HBase Configuration Initialization failure !");

throw new RuntimeException(e) ;

}

}

/**

* 获得链接

* @return

*/

public static synchronized Connection getConnection() {

try {

if(conn == null || conn.isClosed()){

conn = ConnectionFactory.createConnection(conf);

}

//         System.out.println("---------- " + conn.hashCode());

} catch (IOException e) {

logger.error("HBase 建立链接失败 ", e);

}

return conn;

}

/**

* 创建表

* @param tableName

* @throws Exception

*/

public static void createTable(String tableName, String[] columnFamilies, boolean preBuildRegion) throws Exception {

if(preBuildRegion){

String[] s = new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };

int partition = 16;

byte[][] splitKeys = new byte[partition - 1][];

for (int i = 1; i 

splitKeys[i - 1] = Bytes.toBytes(s[i - 1]);

}

createTable(tableName, columnFamilies, splitKeys);

} else {

createTable(tableName, columnFamilies);

}

}

private static void createTable(String tableName, int pNum, boolean only) throws Exception {

String[] s = RandCodeEnum.HBASE_CHAR.getHbaseKeys(pNum,2,only);

byte[][] splitKeys = new byte[pNum][];

for (int i = 1; i <= pNum; i++) {

splitKeys[i - 1] = Bytes.toBytes(s[i - 1]);

}

createTable(tableName, new String[] { "events" }, splitKeys);

}

/**

* 建表

* @param tableName

* @param cfs

* @throws IOException

*/

private static void createTable(String tableName, String[] cfs, byte[][] splitKeys) throws Exception {

Connection conn = getConnection();

HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();

try {

if (admin.tableExists(tableName)) {

logger.warn("Table: {} is exists!", tableName);

return;

}

HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName));

for (int i = 0; i 

HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(cfs[i]);

hColumnDescriptor.setCompressionType(Compression.Algorithm.SNAPPY);

hColumnDescriptor.setMaxVersions(1);

tableDesc.addFamily(hColumnDescriptor);

}

admin.createTable(tableDesc, splitKeys);

logger.info("Table: {} create success!", tableName);

} finally {

admin.close();

closeConnect(conn);

}

}

/**

* 建表

* @param tableName

* @param cfs

* @throws IOException

*/

private static void createTable(String tableName, String[] cfs) throws Exception {

Connection conn = getConnection();

HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();

try {

if (admin.tableExists(tableName)) {

logger.warn("Table: {} is exists!", tableName);

return;

}

HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName));

for (int i = 0; i 

HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(cfs[i]);

hColumnDescriptor.setCompressionType(Compression.Algorithm.SNAPPY);

hColumnDescriptor.setMaxVersions(1);

tableDesc.addFamily(hColumnDescriptor);

}

admin.createTable(tableDesc);

logger.info("Table: {} create success!", tableName);

} finally {

admin.close();

closeConnect(conn);

}

}

/**

* 删除表

* @param tablename

* @throws IOException

*/

public static void deleteTable(String tablename) throws IOException {

Connection conn = getConnection();

HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();

try {

if (!admin.tableExists(tablename)) {

logger.warn("Table: {} is not exists!", tablename);

return;

}

admin.disableTable(tablename);

admin.deleteTable(tablename);

logger.info("Table: {} delete success!", tablename);

} finally {

admin.close();

closeConnect(conn);

}

}

/**

* 获取  Table

* @param tableName 表名

* @return

* @throws IOException

*/

public static Table getTable(String tableName){

try {

return getConnection().getTable(TableName.valueOf(tableName));

} catch (Exception e) {

logger.error("Obtain Table failure !", e);

}

return null;

}

/**

* 给 table 创建 snapshot

* @param snapshotName 快照名称

* @param tableName 表名

* @return

* @throws IOException

*/

public static void snapshot(String snapshotName, TableName tableName){

try {

Admin admin = getConnection().getAdmin();

admin.snapshot(snapshotName, tableName);

} catch (Exception e) {

logger.error("Snapshot " + snapshotName + " create failed !", e);

}

}

/**

* 获得现已有的快照

* @param snapshotNameRegex 正则过滤表达式

* @return

* @throws IOException

*/

public static List listSnapshots(String snapshotNameRegex){

try {

Admin admin = getConnection().getAdmin();

if(StringUtils.isNotBlank(snapshotNameRegex))

return admin.listSnapshots(snapshotNameRegex);

else

return admin.listSnapshots();

} catch (Exception e) {

logger.error("Snapshot " + snapshotNameRegex + " get failed !", e);

}

return null;

}

/**

* 批量删除Snapshot

* @param snapshotNameRegex 正则过滤表达式

* @return

* @throws IOException

*/

public static void deleteSnapshots(String snapshotNameRegex){

try {

Admin admin = getConnection().getAdmin();

if(StringUtils.isNotBlank(snapshotNameRegex))

admin.deleteSnapshots(snapshotNameRegex);

else

logger.error("SnapshotNameRegex can"t be null !");

} catch (Exception e) {

logger.error("Snapshots " + snapshotNameRegex + " del failed !", e);

}

}

/**

* 单个删除Snapshot

* @param snapshotName 正则过滤表达式

* @return

* @throws IOException

*/

public static void deleteSnapshot(String snapshotName){

try {

Admin admin = getConnection().getAdmin();

if(StringUtils.isNotBlank(snapshotName))

admin.deleteSnapshot(snapshotName);

else

logger.error("SnapshotName can"t be null !");

} catch (Exception e) {

logger.error("Snapshot " + snapshotName + " del failed !", e);

}

}

/**

* 分页检索表数据。

* (如果在创建表时为此表指定了非默认的命名空间,则需拼写上命名空间名称,格式为【namespace:tablename】)。

* @param tableName 表名称(*)。

* @param startRowKey 起始行键(可以为空,如果为空,则从表中第一行开始检索)。

* @param endRowKey 结束行键(可以为空)。

* @param filterList 检索条件过滤器集合(不包含分页过滤器;可以为空)。

* @param maxVersions 指定最大版本数【如果为最大整数值,则检索所有版本;如果为最小整数值,则检索最新版本;否则只检索指定的版本数】。

* @param pageModel 分页模型(*)。

* @return 返回HBasePageModel分页对象。

*/

public static HBasePageModel scanResultByPageFilter(String tableName, byte[] startRowKey, byte[] endRowKey, FilterList filterList, int maxVersions, HBasePageModel pageModel) {

if(pageModel == null) {

pageModel = new HBasePageModel(10);

}

if(maxVersions <= 0 ) {

//默认只检索数据的最新版本

maxVersions = Integer.MIN_VALUE;

}

pageModel.initStartTime();

pageModel.initEndTime();

if(StringUtils.isBlank(tableName)) {

return pageModel;

}

Table table = null;

try {

table = getTable(tableName);

int tempPageSize = pageModel.getPageSize();

boolean isEmptyStartRowKey = false;

if(startRowKey == null) {

//则读取表的第一行记录

Result firstResult = selectFirstResultRow(tableName, filterList);

if(firstResult.isEmpty()) {

return pageModel;

}

startRowKey = firstResult.getRow();

}

if(pageModel.getPageStartRowKey() == null) {

isEmptyStartRowKey = true;

pageModel.setPageStartRowKey(startRowKey);

} else {

if(pageModel.getPageEndRowKey() != null) {

pageModel.setPageStartRowKey(pageModel.getPageEndRowKey());

}

//从第二页开始,每次都多取一条记录,因为第一条记录是要删除的。

tempPageSize += 1;

}

Scan scan = new Scan();

scan.setStartRow(pageModel.getPageStartRowKey());

if(endRowKey != null) {

scan.setStopRow(endRowKey);

}

PageFilter pageFilter = new PageFilter(pageModel.getPageSize() + 1);

if(filterList != null) {

filterList.addFilter(pageFilter);

scan.setFilter(filterList);

} else {

scan.setFilter(pageFilter);

}

if(maxVersions == Integer.MAX_VALUE) {

scan.setMaxVersions();

} else if(maxVersions == Integer.MIN_VALUE) {

} else {

scan.setMaxVersions(maxVersions);

}

ResultScanner scanner = table.getScanner(scan);

List resultList = new ArrayList();

int index = 0;

for(Result rs : scanner.next(tempPageSize)) {

if(isEmptyStartRowKey == false && index == 0) {

index += 1;

continue;

}

if(!rs.isEmpty()) {

resultList.add(rs);

}

index += 1;

}

scanner.close();

pageModel.setResultList(resultList);

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

table.close();

} catch (IOException e) {

e.printStackTrace();

}

}

int pageIndex = pageModel.getPageIndex() + 1;

pageModel.setPageIndex(pageIndex);

if(pageModel.getResultList().size() > 0) {

//获取本次分页数据首行和末行的行键信息

byte[] pageStartRowKey = pageModel.getResultList().get(0).getRow();

byte[] pageEndRowKey = pageModel.getResultList().get(pageModel.getResultList().size() - 1).getRow();

pageModel.setPageStartRowKey(pageStartRowKey);

pageModel.setPageEndRowKey(pageEndRowKey);

}

int queryTotalCount = pageModel.getQueryTotalCount() + pageModel.getResultList().size();

pageModel.setQueryTotalCount(queryTotalCount);

pageModel.initEndTime();

pageModel.printTimeInfo();

return pageModel;

}

/**

* 检索指定表的第一行记录。

* (如果在创建表时为此表指定了非默认的命名空间,则需拼写上命名空间名称,格式为【namespace:tablename】)。

* @param tableName 表名称(*)。

* @param filterList 过滤器集合,可以为null。

* @return

*/

public static Result selectFirstResultRow(String tableName,FilterList filterList) {

if(StringUtils.isBlank(tableName)) return null;

Table table = null;

try {

table = getTable(tableName);

Scan scan = new Scan();

if(filterList != null) {

scan.setFilter(filterList);

}

ResultScanner scanner = table.getScanner(scan);

Iterator iterator = scanner.iterator();

int index = 0;

while(iterator.hasNext()) {

Result rs = iterator.next();

if(index == 0) {

scanner.close();

return rs;

}

}

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

table.close();

} catch (IOException e) {

e.printStackTrace();

}

}

return null;

}

/**

* 异步往指定表添加数据

* @param tablename     表名

* @param puts              需要添加的数据

* @return long             返回执行时间

* @throws IOException

*/

public static long put(String tablename, List puts) throws Exception {

long currentTime = System.currentTimeMillis();

Connection conn = getConnection();

final BufferedMutator.ExceptionListener listener = new BufferedMutator.ExceptionListener() {

@Override

public void onException(RetriesExhaustedWithDetailsException e, BufferedMutator mutator) {

for (int i = 0; i 

System.out.println("Failed to sent put " + e.getRow(i) + ".");

logger.error("Failed to sent put " + e.getRow(i) + ".");

}

}

};

BufferedMutatorParams params = new BufferedMutatorParams(TableName.valueOf(tablename))

.listener(listener);

params.writeBufferSize(5 * 1024 * 1024);

final BufferedMutator mutator = conn.getBufferedMutator(params);

try {

mutator.mutate(puts);

mutator.flush();

} finally {

mutator.close();

closeConnect(conn);

}

return System.currentTimeMillis() - currentTime;

}

/**

* 异步往指定表添加数据

* @param tablename     表名

* @param put               需要添加的数据

* @return long             返回执行时间

* @throws IOException

*/

public static long put(String tablename, SocPut put) throws Exception {

return put(tablename, Arrays.asList(put));

}

/**

* 往指定表添加数据

* @param tablename     表名

* @param puts              需要添加的数据

* @return long             返回执行时间

* @throws IOException

*/

public static long putByHTable(String tablename, List> puts) throws Exception {

long currentTime = System.currentTimeMillis();

Connection conn = getConnection();

HTable htable = (HTable) conn.getTable(TableName.valueOf(tablename));

htable.setAutoFlushTo(false);

htable.setWriteBufferSize(5 * 1024 * 1024);

try {

htable.put((List)puts);

htable.flushCommits();

} finally {

htable.close();

closeConnect(conn);

}

return System.currentTimeMillis() - currentTime;

}

/**

* 删除单条数据

* @param tablename

* @param row

* @throws IOException

*/

public static void delete(String tablename, String row) throws IOException {

Table table = getTable(tablename);

if(table!=null){

try {

Delete d = new Delete(row.getBytes());

table.delete(d);

} finally {

table.close();

}

}

}

/**

* 删除多行数据

* @param tablename

* @param rows

* @throws IOException

*/

public static void delete(String tablename, String[] rows) throws IOException {

Table table = getTable(tablename);

if (table != null) {

try {

List list = new ArrayList();

for (String row : rows) {

Delete d = new Delete(row.getBytes());

list.add(d);

}

if (list.size() > 0) {

table.delete(list);

}

} finally {

table.close();

}

}

}

/**

* 关闭连接

* @throws IOException

*/

public static void closeConnect(Connection conn){

if(null != conn){

try {

//              conn.close();

} catch (Exception e) {

logger.error("closeConnect failure !", e);

}

}

}

/**

* 获取单条数据

* @param tablename

* @param row

* @return

* @throws IOException

*/

public static Result getRow(String tablename, byte[] row) {

Table table = getTable(tablename);

Result rs = null;

if(table!=null){

try{

Get g = new Get(row);

rs = table.get(g);

} catch (IOException e) {

logger.error("getRow failure !", e);

} finally{

try {

table.close();

} catch (IOException e) {

logger.error("getRow failure !", e);

}

}

}

return rs;

}

/**

* 获取多行数据

* @param tablename

* @param rows

* @return

* @throws Exception

*/

public static  Result[] getRows(String tablename, List rows) {

Table table = getTable(tablename);

List gets = null;

Result[] results = null;

try {

if (table != null) {

gets = new ArrayList();

for (T row : rows) {

if(row!=null){

gets.add(new Get(Bytes.toBytes(String.valueOf(row))));

}else{

throw new RuntimeException("hbase have no data");

}

}

}

if (gets.size() > 0) {

results = table.get(gets);

}

} catch (IOException e) {

logger.error("getRows failure !", e);

} finally {

try {

table.close();

} catch (IOException e) {

logger.error("table.close() failure !", e);

}

}

return results;

}

/**

* 扫描整张表,注意使用完要释放。

* @param tablename

* @return

* @throws IOException

*/

public static ResultScanner get(String tablename) {

Table table = getTable(tablename);

ResultScanner results = null;

if (table != null) {

try {

Scan scan = new Scan();

scan.setCaching(1000);

results = table.getScanner(scan);

} catch (IOException e) {

logger.error("getResultScanner failure !", e);

} finally {

try {

table.close();

} catch (IOException e) {

logger.error("table.close() failure !", e);

}

}

}

return results;

}

/**

* 格式化输出结果

*/

public static void formatRow(KeyValue[] rs){

for(KeyValue kv : rs){

System.out.println(" column family  :  " + Bytes.toString(kv.getFamily()));

System.out.println(" column   :  " + Bytes.toString(kv.getQualifier()));

System.out.println(" value   :  " + Bytes.toString(kv.getValue()));

System.out.println(" timestamp   :  " + String.valueOf(kv.getTimestamp()));

System.out.println("--------------------");

}

}

/**

* byte[] 类型的长整形数字转换成 long 类型

* @param byteNum

* @return

*/

public static long bytes2Long(byte[] byteNum) {

long num = 0;

for (int ix = 0; ix 

num <<= 8;

num |= (byteNum[ix] & 0xff);

}

return num;

}

}

HBaseService 实现类 HBaseServiceImpl.java

[java]

package cn.ngsoc.hbase;

import cn.ngsoc.hbase.util.HBaseUtil;

import cn.ngsoc.hbase.util.ThreadPoolUtil;

import org.apache.hadoop.hbase.client.Put;

import org.apache.hadoop.hbase.client.Result;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.util.Arrays;

import java.util.List;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.Future;

/**

* HBaseService  实现类

* Created by babylon on 2016/12/5.

*/

public class HBaseServiceImpl extends AbstractHBaseService{

private static final Logger logger = LoggerFactory.getLogger(HBaseServiceImpl.class);

private ThreadPoolUtil threadPool= ThreadPoolUtil.init();       // 初始化线程池

@Override

public void put(String tableName, Put put, boolean waiting) {

batchPut(tableName, Arrays.asList(put), waiting);

}

/**

* 多线程同步提交

* @param tableName  表名称

* @param puts  待提交参数

* @param waiting  是否等待线程执行完成  true 可以及时看到结果, false 让线程继续执行,并跳出此方法返回调用方主程序

*/

@Override

public void batchPut(final String tableName, final List puts, boolean waiting) {

threadPool.execute(new Runnable() {

@Override

public void run() {

try {

HBaseUtil.put(tableName, puts);

} catch (Exception e) {

logger.error("batchPut failed . ", e);

}

}

});

if(waiting){

try {

threadPool.awaitTermination();

} catch (InterruptedException e) {

logger.error("HBase put job thread pool await termination time out.", e);

}

}

}

@Override

public  Result[] getRows(String tablename, List rows) {

return HBaseUtil.getRows(tablename, rows);

}

@Override

public Result getRow(String tablename, byte[] row) {

return HBaseUtil.getRow(tablename, row);

}

/**

* 多线程异步提交

* @param tableName  表名称

* @param puts  待提交参数

* @param waiting  是否等待线程执行完成  true 可以及时看到结果, false 让线程继续执行,并跳出此方法返回调用方主程序

*/

public void batchAsyncPut(final String tableName, final List puts, boolean waiting) {

Future f = threadPool.submit(new Runnable() {

@Override

public void run() {

try {

HBaseUtil.putByHTable(tableName, puts);

} catch (Exception e) {

logger.error("batchPut failed . ", e);

}

}

});

if(waiting){

try {

f.get();

} catch (InterruptedException e) {

logger.error("多线程异步提交返回数据执行失败.", e);

} catch (ExecutionException e) {

logger.error("多线程异步提交返回数据执行失败.", e);

}

}

}

/**

* 创建表

* @param tableName         表名称

* @param columnFamilies   列族名称数组

* @param preBuildRegion  是否预分配Region   true 是  , false 否  默认 16个region,rowkey生成的时候记得指定前缀

* @return  返回执行时间 (单位: 毫秒)

*/

public void createTable(String tableName, String[] columnFamilies, boolean preBuildRegion) throws Exception {

HBaseUtil.createTable(tableName, columnFamilies, preBuildRegion);

}

}

代码使用案例  HBasePutTest.java

[java]

import cn.ngsoc.hbase.HBase;

import cn.ngsoc.hbase.util.HBaseUtil;

import org.apache.hadoop.hbase.client.Put;

import org.apache.hadoop.hbase.client.Result;

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

import org.junit.Before;

import org.junit.Test;

import java.util.Arrays;

import java.util.Random;

/**

* HBase 测试类

* Created by babylon on 2016/11/29.

*/

public class HBasePutTest {

@Before

public void init(){

HBaseUtil.init("zkHost");

}

@Test

public void testPut()  {

Put put = new Put(Bytes.toBytes("rowKey"));

put.addColumn(Bytes.toBytes("events"), Bytes.toBytes("severity"), Bytes.toBytes(new Random().nextInt(10)+""));

HBase.put("logs", Arrays.asList(new Object[]{put}), true);

}

@Test

public void testGet()  {

Result result = HBaseUtil.getRow("logs", HBase.generateRowkey("rowKey"));

HBaseUtil.formatRow(result.raw());

}

@Test

public void testCreateTable()  {

try {

HBaseUtil.createTable("logs", new String[]{"events"}, true);

} catch (Exception e) {

e.printStackTrace();

}

}

}

完整代码见 Github

https://github.com/JasonBabylon/hbase-operations-with-java/tree/master

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。

http://www.pinlue.com/style/images/nopic.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值