hadoop连接mysql数据库执行数据读写数据库操作



目录(?)[+]

    为了方便 MapReduce 直接访问关系型数据库(Mysql,Oracle),Hadoop提供了DBInputFormat和DBOutputFormat两个类。通过DBInputFormat类把数据库表数据读入到HDFS,根据DBOutputFormat类把MapReduce产生的结果集导入到数据库表中。
    运行MapReduce时候报错:java.io.IOException: com.mysql.jdbc.Driver,一般是由于程序找不到mysql驱动包。解决方法是让每个tasktracker运行MapReduce程序时都可以找到该驱动包。

添加包有两种方式:

(1)在每个节点下的${HADOOP_HOME}/lib下添加该包。重启集群,一般是比较原始的方法。

(2)a)把包传到集群上: Hadoop fs -put MySQL-connector-Java-5.1.0- bin.jar /hdfsPath/

       b)在mr程序提交job前,添加语句:DistributedCache.addFileToClassPath(new Path(“/hdfsPath/mysql- connector-java-5.1.0-bin.jar”),conf);

mysql数据库存储到hadoop hdfs

mysql表创建和数据初始化

[sql]  view plain  copy
 print ?
  1. DROP TABLE IF EXISTS `wu_testhadoop`;  
  2. CREATE TABLE `wu_testhadoop` (  
  3.   `id` int(11) NOT NULL AUTO_INCREMENT,  
  4.   `title` varchar(255) DEFAULT NULL,  
  5.   `content` varchar(255) DEFAULT NULL,  
  6.   PRIMARY KEY (`id`)  
  7. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;  
  8.   
  9. -- ----------------------------  
  10. -- Records of wu_testhadoop  
  11. -- ----------------------------  
  12. INSERT INTO `wu_testhadoop` VALUES ('1''123''122312');  
  13. INSERT INTO `wu_testhadoop` VALUES ('2''123''123456');  

定义hadoop数据访问

mysql表创建完毕后,我们需要定义hadoop访问mysql的规则;

hadoop提供了org.apache.hadoop.io.Writable接口来实现简单的高效的可序列化的协议,该类基于DataInput和DataOutput来实现相关的功能。

hadoop对数据库访问也提供了org.apache.hadoop.mapred.lib.db.DBWritable接口,其中write方法用于对PreparedStatement对象设定值,readFields方法用于对从数据库读取出来的对象进行列的值绑定;

以上两个接口的使用如下(内容是从源码得来)

writable
[java]  view plain  copy
 print ?
  1. public class MyWritable implements Writable {  
  2.       // Some data       
  3.       private int counter;  
  4.       private long timestamp;  
  5.         
  6.       public void write(DataOutput out) throws IOException {  
  7.         out.writeInt(counter);  
  8.         out.writeLong(timestamp);  
  9.       }  
  10.         
  11.       public void readFields(DataInput in) throws IOException {  
  12.         counter = in.readInt();  
  13.         timestamp = in.readLong();  
  14.       }  
  15.         
  16.       public static MyWritable read(DataInput in) throws IOException {  
  17.         MyWritable w = new MyWritable();  
  18.         w.readFields(in);  
  19.         return w;  
  20.       }  
  21.     }  

DBWritable
[java]  view plain  copy
 print ?
  1. public class MyWritable implements Writable, DBWritable {  
  2.    // Some data       
  3.    private int counter;  
  4.    private long timestamp;  
  5.          
  6.    //Writable#write() implementation  
  7.    public void write(DataOutput out) throws IOException {  
  8.      out.writeInt(counter);  
  9.      out.writeLong(timestamp);  
  10.    }  
  11.          
  12.    //Writable#readFields() implementation  
  13.    public void readFields(DataInput in) throws IOException {  
  14.      counter = in.readInt();  
  15.      timestamp = in.readLong();  
  16.    }  
  17.          
  18.    public void write(PreparedStatement statement) throws SQLException {  
  19.      statement.setInt(1, counter);  
  20.      statement.setLong(2, timestamp);  
  21.    }  
  22.          
  23.    public void readFields(ResultSet resultSet) throws SQLException {  
  24.      counter = resultSet.getInt(1);  
  25.      timestamp = resultSet.getLong(2);  
  26.    }   
  27.  }  
数据库对应的实现
[java]  view plain  copy
 print ?
  1. package com.wyg.hadoop.mysql.bean;  
  2.   
  3. import java.io.DataInput;  
  4. import java.io.DataOutput;  
  5. import java.io.IOException;  
  6. import java.sql.PreparedStatement;  
  7. import java.sql.ResultSet;  
  8. import java.sql.SQLException;  
  9.   
  10. import org.apache.hadoop.io.Text;  
  11. import org.apache.hadoop.io.Writable;  
  12. import org.apache.hadoop.mapred.lib.db.DBWritable;  
  13.   
  14. public class DBRecord implements Writable, DBWritable{  
  15.     private int id;  
  16.     private String title;  
  17.     private String content;  
  18.     public int getId() {  
  19.         return id;  
  20.     }  
  21.   
  22.     public void setId(int id) {  
  23.         this.id = id;  
  24.     }  
  25.   
  26.     public String getTitle() {  
  27.         return title;  
  28.     }  
  29.   
  30.     public void setTitle(String title) {  
  31.         this.title = title;  
  32.     }  
  33.   
  34.     public String getContent() {  
  35.         return content;  
  36.     }  
  37.   
  38.     public void setContent(String content) {  
  39.         this.content = content;  
  40.     }  
  41.   
  42.     @Override  
  43.     public void readFields(ResultSet set) throws SQLException {  
  44.         this.id = set.getInt("id");  
  45.         this.title = set.getString("title");  
  46.         this.content = set.getString("content");  
  47.     }  
  48.   
  49.     @Override  
  50.     public void write(PreparedStatement pst) throws SQLException {  
  51.         pst.setInt(1, id);  
  52.         pst.setString(2, title);  
  53.         pst.setString(3, content);  
  54.     }  
  55.   
  56.     @Override  
  57.     public void readFields(DataInput in) throws IOException {  
  58.         this.id = in.readInt();  
  59.         this.title = Text.readString(in);  
  60.         this.content = Text.readString(in);  
  61.     }  
  62.   
  63.     @Override  
  64.     public void write(DataOutput out) throws IOException {  
  65.         out.writeInt(this.id);  
  66.         Text.writeString(out, this.title);  
  67.         Text.writeString(out, this.content);  
  68.     }  
  69.   
  70.     @Override  
  71.     public String toString() {  
  72.          return this.id + " " + this.title + " " + this.content;    
  73.     }  
  74. }  


实现Map/Reduce

[java]  view plain  copy
 print ?
  1. package com.wyg.hadoop.mysql.mapper;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.LongWritable;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapred.MapReduceBase;  
  8. import org.apache.hadoop.mapred.Mapper;  
  9. import org.apache.hadoop.mapred.OutputCollector;  
  10. import org.apache.hadoop.mapred.Reporter;  
  11.   
  12. import com.wyg.hadoop.mysql.bean.DBRecord;  
  13.   
  14. @SuppressWarnings("deprecation")  
  15. public class DBRecordMapper extends MapReduceBase implements Mapper<LongWritable, DBRecord, LongWritable, Text>{  
  16.   
  17.     @Override  
  18.     public void map(LongWritable key, DBRecord value,  
  19.             OutputCollector<LongWritable, Text> collector, Reporter reporter)  
  20.             throws IOException {  
  21.         collector.collect(new LongWritable(value.getId()), new Text(value.toString()));    
  22.     }  
  23.       
  24. }  

测试hadoop连接mysql并将数据存储到hdfs

[java]  view plain  copy
 print ?
  1. package com.wyg.hadoop.mysql.db;  
  2. import java.io.IOException;  
  3.   
  4. import org.apache.hadoop.fs.Path;  
  5. import org.apache.hadoop.io.LongWritable;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapred.FileOutputFormat;  
  8. import org.apache.hadoop.mapred.JobClient;  
  9. import org.apache.hadoop.mapred.JobConf;  
  10. import org.apache.hadoop.mapred.lib.IdentityReducer;  
  11. import org.apache.hadoop.mapred.lib.db.DBConfiguration;  
  12. import org.apache.hadoop.mapred.lib.db.DBInputFormat;  
  13.   
  14. import com.wyg.hadoop.mysql.bean.DBRecord;  
  15. import com.wyg.hadoop.mysql.mapper.DBRecordMapper;  
  16.   
  17. public class DBAccess {  
  18.       public static void main(String[] args) throws IOException {  
  19.              JobConf conf = new JobConf(DBAccess.class);  
  20.              conf.setOutputKeyClass(LongWritable.class);  
  21.              conf.setOutputValueClass(Text.class);  
  22.              conf.setInputFormat(DBInputFormat.class);  
  23.              Path path = new Path("hdfs://192.168.44.129:9000/user/root/dbout");  
  24.              FileOutputFormat.setOutputPath(conf, path);  
  25.              DBConfiguration.configureDB(conf,"com.mysql.jdbc.Driver""jdbc:mysql://你的ip:3306/数据库名","用户名","密码");  
  26.              String [] fields = {"id""title""content"};  
  27.              DBInputFormat.setInput(conf, DBRecord.class"wu_testhadoop",  
  28.                         null"id", fields);  
  29.              conf.setMapperClass(DBRecordMapper.class);  
  30.              conf.setReducerClass(IdentityReducer.class);  
  31.              JobClient.runJob(conf);  
  32.       }  
  33. }  

执行程序,结果如下:

[java]  view plain  copy
 print ?
  1. 15/08/11 16:46:18 INFO jvm.JvmMetrics: Initializing JVM Metrics with processName=JobTracker, sessionId=  
  2. 15/08/11 16:46:18 WARN mapred.JobClient: Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.  
  3. 15/08/11 16:46:18 WARN mapred.JobClient: No job jar file set.  User classes may not be found. See JobConf(Class) or JobConf#setJar(String).  
  4. 15/08/11 16:46:19 INFO mapred.JobClient: Running job: job_local_0001  
  5. 15/08/11 16:46:19 INFO mapred.MapTask: numReduceTasks: 1  
  6. 15/08/11 16:46:19 INFO mapred.MapTask: io.sort.mb = 100  
  7. 15/08/11 16:46:19 INFO mapred.MapTask: data buffer = 79691776/99614720  
  8. 15/08/11 16:46:19 INFO mapred.MapTask: record buffer = 262144/327680  
  9. 15/08/11 16:46:19 INFO mapred.MapTask: Starting flush of map output  
  10. 15/08/11 16:46:19 INFO mapred.MapTask: Finished spill 0  
  11. 15/08/11 16:46:19 INFO mapred.TaskRunner: Task:attempt_local_0001_m_000000_0 is done. And is in the process of commiting  
  12. 15/08/11 16:46:19 INFO mapred.LocalJobRunner:   
  13. 15/08/11 16:46:19 INFO mapred.TaskRunner: Task 'attempt_local_0001_m_000000_0' done.  
  14. 15/08/11 16:46:19 INFO mapred.LocalJobRunner:   
  15. 15/08/11 16:46:19 INFO mapred.Merger: Merging 1 sorted segments  
  16. 15/08/11 16:46:19 INFO mapred.Merger: Down to the last merge-pass, with 1 segments left of total size: 48 bytes  
  17. 15/08/11 16:46:19 INFO mapred.LocalJobRunner:   
  18. 15/08/11 16:46:19 INFO mapred.TaskRunner: Task:attempt_local_0001_r_000000_0 is done. And is in the process of commiting  
  19. 15/08/11 16:46:19 INFO mapred.LocalJobRunner:   
  20. 15/08/11 16:46:19 INFO mapred.TaskRunner: Task attempt_local_0001_r_000000_0 is allowed to commit now  
  21. 15/08/11 16:46:19 INFO mapred.FileOutputCommitter: Saved output of task 'attempt_local_0001_r_000000_0' to hdfs://192.168.44.129:9000/user/root/dbout  
  22. 15/08/11 16:46:19 INFO mapred.LocalJobRunner: reduce > reduce  
  23. 15/08/11 16:46:19 INFO mapred.TaskRunner: Task 'attempt_local_0001_r_000000_0' done.  
  24. 15/08/11 16:46:20 INFO mapred.JobClient:  map 100% reduce 100%  
  25. 15/08/11 16:46:20 INFO mapred.JobClient: Job complete: job_local_0001  
  26. 15/08/11 16:46:20 INFO mapred.JobClient: Counters: 14  
  27. 15/08/11 16:46:20 INFO mapred.JobClient:   FileSystemCounters  
  28. 15/08/11 16:46:20 INFO mapred.JobClient:     FILE_BYTES_READ=34606  
  29. 15/08/11 16:46:20 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=69844  
  30. 15/08/11 16:46:20 INFO mapred.JobClient:     HDFS_BYTES_WRITTEN=30  
  31. 15/08/11 16:46:20 INFO mapred.JobClient:   Map-Reduce Framework  
  32. 15/08/11 16:46:20 INFO mapred.JobClient:     Reduce input groups=2  
  33. 15/08/11 16:46:20 INFO mapred.JobClient:     Combine output records=0  
  34. 15/08/11 16:46:20 INFO mapred.JobClient:     Map input records=2  
  35. 15/08/11 16:46:20 INFO mapred.JobClient:     Reduce shuffle bytes=0  
  36. 15/08/11 16:46:20 INFO mapred.JobClient:     Reduce output records=2  
  37. 15/08/11 16:46:20 INFO mapred.JobClient:     Spilled Records=4  
  38. 15/08/11 16:46:20 INFO mapred.JobClient:     Map output bytes=42  
  39. 15/08/11 16:46:20 INFO mapred.JobClient:     Map input bytes=2  
  40. 15/08/11 16:46:20 INFO mapred.JobClient:     Combine input records=0  
  41. 15/08/11 16:46:20 INFO mapred.JobClient:     Map output records=2  
  42. 15/08/11 16:46:20 INFO mapred.JobClient:     Reduce input records=2  


同时可以看到hdfs文件系统多了一个dbout的目录,里边的文件保存了数据库对应的数据,内容保存如下

[java]  view plain  copy
 print ?
  1. 1   1 123 122312  
  2. 2   2 123 123456  


hdfs数据导入到mysql

    hdfs文件存储到mysql,也需要上边的DBRecord类作为辅助,因为数据库的操作都是通过DBInput和DBOutput来进行的;

    首先需要定义map和reduce的实现(map用以对hdfs的文档进行解析,reduce解析map的输出并输出)

[java]  view plain  copy
 print ?
  1. package com.wyg.hadoop.mysql.mapper;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.DataInput;  
  5. import java.io.DataOutput;  
  6. import java.sql.PreparedStatement;  
  7. import java.sql.ResultSet;  
  8. import java.sql.SQLException;  
  9. import java.util.Iterator;  
  10.   
  11. import org.apache.hadoop.filecache.DistributedCache;  
  12. import org.apache.hadoop.fs.Path;  
  13. import org.apache.hadoop.io.IntWritable;  
  14. import org.apache.hadoop.io.Text;  
  15. import org.apache.hadoop.io.Writable;  
  16. import org.apache.hadoop.mapred.JobClient;  
  17. import org.apache.hadoop.mapred.MapReduceBase;  
  18. import org.apache.hadoop.mapred.Mapper;  
  19. import org.apache.hadoop.mapred.OutputCollector;  
  20. import org.apache.hadoop.mapred.Reducer;  
  21. import org.apache.hadoop.mapred.Reporter;  
  22. import com.wyg.hadoop.mysql.bean.DBRecord;  
  23.   
  24. public class WriteDB {  
  25.     // Map处理过程  
  26.     public static class Map extends MapReduceBase implements  
  27.   
  28.             Mapper<Object, Text, Text, DBRecord> {  
  29.         private final static DBRecord one = new DBRecord();  
  30.   
  31.         private Text word = new Text();  
  32.   
  33.         @Override  
  34.   
  35.         public void map(Object key, Text value,  
  36.   
  37.             OutputCollector<Text, DBRecord> output, Reporter reporter)  
  38.   
  39.                 throws IOException {  
  40.   
  41.             String line = value.toString();  
  42.             String[] infos = line.split(" ");  
  43.             String id = infos[0].split("    ")[1];  
  44.             one.setId(new Integer(id));  
  45.             one.setTitle(infos[1]);  
  46.             one.setContent(infos[2]);  
  47.             word.set(id);  
  48.             output.collect(word, one);  
  49.         }  
  50.   
  51.     }  
  52.   
  53.     public static class Reduce extends MapReduceBase implements  
  54.             Reducer<Text, DBRecord, DBRecord, Text> {  
  55.         @Override  
  56.         public void reduce(Text key, Iterator<DBRecord> values,  
  57.                 OutputCollector<DBRecord, Text> collector, Reporter reporter)  
  58.                 throws IOException {  
  59.             DBRecord record = values.next();  
  60.             collector.collect(record, new Text());  
  61.         }  
  62.     }  
  63. }  
测试hdfs导入数据到数据库

[java]  view plain  copy
 print ?
  1. package com.wyg.hadoop.mysql.db;  
  2.   
  3. import org.apache.hadoop.fs.Path;  
  4. import org.apache.hadoop.io.IntWritable;  
  5. import org.apache.hadoop.io.LongWritable;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapred.FileInputFormat;  
  8. import org.apache.hadoop.mapred.JobClient;  
  9. import org.apache.hadoop.mapred.JobConf;  
  10. import org.apache.hadoop.mapred.TextInputFormat;  
  11. import org.apache.hadoop.mapred.lib.db.DBConfiguration;  
  12. import org.apache.hadoop.mapred.lib.db.DBInputFormat;  
  13. import org.apache.hadoop.mapred.lib.db.DBOutputFormat;  
  14.   
  15. import com.wyg.hadoop.mysql.bean.DBRecord;  
  16. import com.wyg.hadoop.mysql.mapper.WriteDB;  
  17.   
  18. public class DBInsert {  
  19.     public static void main(String[] args) throws Exception {  
  20.   
  21.            
  22.   
  23.         JobConf conf = new JobConf(WriteDB.class);  
  24.         // 设置输入输出类型  
  25.   
  26.         conf.setInputFormat(TextInputFormat.class);  
  27.         conf.setOutputFormat(DBOutputFormat.class);  
  28.   
  29.         // 不加这两句,通不过,但是网上给的例子没有这两句。  
  30.         //Text, DBRecord  
  31.         conf.setMapOutputKeyClass(Text.class);  
  32.         conf.setMapOutputValueClass(DBRecord.class);  
  33.         conf.setOutputKeyClass(Text.class);  
  34.         conf.setOutputValueClass(DBRecord.class);  
  35.         // 设置Map和Reduce类  
  36.         conf.setMapperClass(WriteDB.Map.class);  
  37.         conf.setReducerClass(WriteDB.Reduce.class);  
  38.         // 设置输如目录  
  39.         FileInputFormat.setInputPaths(conf, new Path("hdfs://192.168.44.129:9000/user/root/dbout"));  
  40.         // 建立数据库连接  
  41.         DBConfiguration.configureDB(conf,"com.mysql.jdbc.Driver""jdbc:mysql://数据库ip:3306/数据库名称","用户名","密码");  
  42.         String[] fields = {"id","title","content" };  
  43.         DBOutputFormat.setOutput(conf, "wu_testhadoop", fields);  
  44.         JobClient.runJob(conf);  
  45.     }  
  46.   
  47. }  

测试结果如下

[java]  view plain  copy
 print ?
  1. 15/08/11 18:10:15 INFO jvm.JvmMetrics: Initializing JVM Metrics with processName=JobTracker, sessionId=  
  2. 15/08/11 18:10:15 WARN mapred.JobClient: Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.  
  3. 15/08/11 18:10:15 WARN mapred.JobClient: No job jar file set.  User classes may not be found. See JobConf(Class) or JobConf#setJar(String).  
  4. 15/08/11 18:10:15 INFO mapred.FileInputFormat: Total input paths to process : 1  
  5. 15/08/11 18:10:15 INFO mapred.JobClient: Running job: job_local_0001  
  6. 15/08/11 18:10:15 INFO mapred.FileInputFormat: Total input paths to process : 1  
  7. 15/08/11 18:10:15 INFO mapred.MapTask: numReduceTasks: 1  
  8. 15/08/11 18:10:15 INFO mapred.MapTask: io.sort.mb = 100  
  9. 15/08/11 18:10:15 INFO mapred.MapTask: data buffer = 79691776/99614720  
  10. 15/08/11 18:10:15 INFO mapred.MapTask: record buffer = 262144/327680  
  11. 15/08/11 18:10:15 INFO mapred.MapTask: Starting flush of map output  
  12. 15/08/11 18:10:16 INFO mapred.MapTask: Finished spill 0  
  13. 15/08/11 18:10:16 INFO mapred.TaskRunner: Task:attempt_local_0001_m_000000_0 is done. And is in the process of commiting  
  14. 15/08/11 18:10:16 INFO mapred.LocalJobRunner: hdfs://192.168.44.129:9000/user/root/dbout/part-00000:0+30  
  15. 15/08/11 18:10:16 INFO mapred.TaskRunner: Task 'attempt_local_0001_m_000000_0' done.  
  16. 15/08/11 18:10:16 INFO mapred.LocalJobRunner:   
  17. 15/08/11 18:10:16 INFO mapred.Merger: Merging 1 sorted segments  
  18. 15/08/11 18:10:16 INFO mapred.Merger: Down to the last merge-pass, with 1 segments left of total size: 40 bytes  
  19. 15/08/11 18:10:16 INFO mapred.LocalJobRunner:   
  20. 15/08/11 18:10:16 INFO mapred.TaskRunner: Task:attempt_local_0001_r_000000_0 is done. And is in the process of commiting  
  21. 15/08/11 18:10:16 INFO mapred.LocalJobRunner: reduce > reduce  
  22. 15/08/11 18:10:16 INFO mapred.TaskRunner: Task 'attempt_local_0001_r_000000_0' done.  
  23. 15/08/11 18:10:16 INFO mapred.JobClient:  map 100% reduce 100%  
  24. 15/08/11 18:10:16 INFO mapred.JobClient: Job complete: job_local_0001  
  25. 15/08/11 18:10:16 INFO mapred.JobClient: Counters: 14  
  26. 15/08/11 18:10:16 INFO mapred.JobClient:   FileSystemCounters  
  27. 15/08/11 18:10:16 INFO mapred.JobClient:     FILE_BYTES_READ=34932  
  28. 15/08/11 18:10:16 INFO mapred.JobClient:     HDFS_BYTES_READ=60  
  29. 15/08/11 18:10:16 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=70694  
  30. 15/08/11 18:10:16 INFO mapred.JobClient:   Map-Reduce Framework  
  31. 15/08/11 18:10:16 INFO mapred.JobClient:     Reduce input groups=2  
  32. 15/08/11 18:10:16 INFO mapred.JobClient:     Combine output records=0  
  33. 15/08/11 18:10:16 INFO mapred.JobClient:     Map input records=2  
  34. 15/08/11 18:10:16 INFO mapred.JobClient:     Reduce shuffle bytes=0  
  35. 15/08/11 18:10:16 INFO mapred.JobClient:     Reduce output records=2  
  36. 15/08/11 18:10:16 INFO mapred.JobClient:     Spilled Records=4  
  37. 15/08/11 18:10:16 INFO mapred.JobClient:     Map output bytes=34  
  38. 15/08/11 18:10:16 INFO mapred.JobClient:     Map input bytes=30  
  39. 15/08/11 18:10:16 INFO mapred.JobClient:     Combine input records=0  
  40. 15/08/11 18:10:16 INFO mapred.JobClient:     Map output records=2  
  41. 15/08/11 18:10:16 INFO mapred.JobClient:     Reduce input records=2  

测试之前我对原有表进行了清空处理,可以看到执行后数据库里边添加了两条内容;

下次在执行的时候会报错,属于正常情况,原因在于我们导入数据的时候对id进行赋值了,如果忽略id,是可以一直添加的;

源码下载地址

源码已上传,下载地址为download.csdn.net/detail/wuyinggui10000/8974585

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值