课程笔记

今日内容:

    1. hbase的原理及其相关的工作机制 – 理解
    1. hbase的Bulk Load 的批量加载操作 – 操作
    1. hbase的协处理器 – 了解
    1. hbase的调优 – 了解
    1. 扩展 : hbase的数据结构 – 了解

1. apache HBase的原理

1.1 读取数据的流程

1) 由客户端发起读取数据的请求, 首先先连接 zookeeper , 从zookeeper获取hbase:meta表被哪个regionServer所管理meta表中主要记录了 hbase中各个表有那些region, 以及每个region被哪个regionServer所管理hbase中非常特殊的元数据存储表, 此表只会有一个region
2) 连接meta对应的regionServer, 从meta中获取要查询数据的在那些region中, 并将其对应regionServer地址返回给客户端3) 开始并行的连接这些regionServer, 从这些regionServer中获取数据先从 memStore  --> blockCache ---> storeFile  ---> 大的Hfile
4) 各个regionserver将读取到数据返回给client , client根据需要过滤出需要的数据, 最后展示给调用者

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZC0gFYf-1613913289238)(day18_课堂笔记.assets/image-20210118175811026.png)]

1.2 数据的写入流程

客户端的同步流程:
1) 客户端发起写入数据的请求, 首先会先连接zookeeper, 从zookeeper获取hbase:meta表所在的regionServer的地址
2) 连接meta表对应的regionServer, 从meta表获取目标表对应要写入数据的region的地址(基于region的startkey和endKey来确定)
3) 连接对应region的regionServer的地址, 开始进行数据的写入
4) 首先先将数据写入到这个regionServer的Hlog日志中, 然后在将数据写入到 对应的region中store模块的memStore中, 当这个两个地方都写入完成后, 客户端就会认为数据写入完成了异步服务器端执行过程:
5) 客户端不断的进行数据的写入工作, memStore数据也会不断的增多, 当memStore中数据达到一定的阈值(128M|1小时)后, 内部最终启动一个flush线程, 将数据刷新到HDFS上, 形成一个storeFile文件
6) 随着memStore不断刷新数据到HDFS中, storeFile文件也会越来越多, 当storeFile的文件达到一定的阈值后(3个及以上), 启动compact线程, 将多个文件合并最终合并为一个大文件(Hfile)
7) 随着不断的合并, 这个大的Hfile文件也会越来越大, 当这个大的Hfile达到一定的阈值(最终10GB)后, 启动split机制, 将大的Hfile一分为二的操作, 此时region也会进行分割操作, 变成两个新的region, 每个region管理每个分割后新的Hfile文件, 原有就得region就会被下线
8) 随着不断的进行split, 表的region的数量也会越来越多的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QFebVQWI-1613913289245)(day18_课堂笔记.assets/image-20210118181348381.png)]

2. HBase的原理及其相关的工作机制

2.1 HBase的flush刷新机制(溢写合并机制)

hbase2.0: flush溢写的流程说明

flush溢写流程:   hbase 2.0版本后的流程随着客户端不断写入数据到达memStore中, memStore内存就会被写满(128M), 当memStore内存达到一定的阈值后,
此时就会触发flush刷新线程, 将数据最终写入HDFS上, 形成一个StoreFile文件1) 当memStore的内存写满后, 首先将这个内存空间关闭, 然后开启一个新的memStore, 将这个写满内存空间的数据存储到一个pipeline的管道(队列)中 (只能读, 不能改)2) 在Hbase的2.0版本后, 这个管道中数据, 会尽可能晚刷新到磁盘中, 一直存储在内存中,  随着memStore不断的溢写, 管道中数据也会不断的变多3) 当管道中数据, 达到一定的阈值后, hbase就会启动一个flush的刷新线程, 对pipeline管道中数据一次性全部刷新到磁盘上,而且在刷新的过程中, 对管道中数据进行排序合并压缩操作, 在HDFS上形成一个合并后的storeFile文件1.0版本中: 区别在于 : 不存在 尽可能晚的刷新 , 也不存在合并溢写操作
注意: 虽然说在2.0时代加入这个内存合并方案, 但是默认情况下是不开启的

2.0中内存合并的策略:

basic(基础型):说明: 仅做作为基本的合并, 不会对过期数据进行清除操作优点: 效率高 ,适合于这种有大量写的模式弊端: 如果数据中大多数都是已经过期的时候, 此时做了许多无用功, 对磁盘IO也会比较大
eager(饥渴型):说明: 在合并的过程中, 尽可能的去除过期的无用的数据, 保证合并后数据在当下都是可用的优点: 合并后的文件会较少, 对磁盘IO比较低, 适用于数据过期比较快的场景(比如 购物车数据)弊端: 由于合并需要多干活,会资源使用也会更多,  导致合并效率降低, 虽然IO减少, 但是依然效率是比较低下的
adaptive(适应型):说明: 在合并的过程中, 会根据数据的重复情况来决定是否需要采用哪种方案, 当重复数据过多, 就会采用eager型, 否则使用basic(基础型)优点:  更智能化, 自动切换弊端: 如果重复数据比较多 但是写入也比较频繁, 此时采用eager, 会导致资源被eager占用较大, 从而影响写入的效率

如何配置内存合并策略:

  • 方案一: 全局配置, 所有表都生效

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z803JVKI-1613913289249)(day18_课堂笔记.assets/image-20210119095808448.png)]

  • 方案二: 针对某个表来设置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdL6mULO-1613913289252)(day18_课堂笔记.assets/image-20210119095842281.png)]

2.2 HBase的storeFile的合并机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uld7lPbV-1613913289258)(day18_课堂笔记.assets/image-20210119102815674.png)]

触发时机:

minor: 触发时机 storeFile文件达到3及以上的时候 | 刚刚启动Hbase集群的时候
major: 默认触发时间: 7天  | 刚刚启动Hbase集群的时候
  • hbase矛盾点: HBase支持随机读写功能, HBase基于HDFS, 而HDFS不支持随机读写, 如何解决呢?
1) 在Hbase中, 所有的数据随机操作,都是对内存中数据进行处理, 如果是添加, 在内存中加入数据, 如果修改, 同样也是添加操作(时间戳记录版本),  如果删除,本应该是直接到磁盘中将数据删除, 但是HDFS不支持, 在内存中记录好这个标记,不显示给用户看即可2) 在进行storeFile的major合并操作的时候, 此时将HDFS的数据读取出来到内存中, 边读取边处理, 边将数据追加到HDFS中

2.3 Hbase的split机制(region分裂)

  • split在最终达到10GB时候, 就会执行split分裂, 分裂之后, 就会形成两个新的Region, 原有Region就会被下线, 新的region会分别各种切分后Hfile文件

  • 注意: split的 最终10Gb 指的是当Hbase中Region数量达到9个及以上的时候, 采用按照10GB进行分裂,而什么分裂取决于以下这个公式:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RElr1zJV-1613913289260)(day18_课堂笔记.assets/image-20210119105200036.png)]

R: 表的Region的数量
flush.size: 默认值为 128M
max.Filesize: 默认值 10GB

思考: 如果现在我希望, Region在5个时候, 最好就可以按照10GB分裂, 如何解决呢? 调整flush.size的大小

2.4 region server的上线流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FakseSpF-1613913289263)(day18_课堂笔记.assets/image-20210119111906090.png)]

2.5 regionServer的下线流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yNhu6ANS-1613913289265)(day18_课堂笔记.assets/image-20210119112108603.png)]

2.6 master的上线和下线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dlRVSOwg-1613913289267)(day18_课堂笔记.assets/image-20210119113135847.png)]

注意:master下线短期对hbase没有太大的影响, 因为master不会参与数据IO操作, 数据读写操作不会使用mastermaster下线主要是影响了对表的一些 以及对region的操作

3. HBase的Bulk Load 批量加载操作

原有的数据写入操作大致流转流程:

正常写入数据的流程: 数据写入到Hlog --> memStore --> storeFile --> Hfile

​ 如果以及有一批数据, 需要写入到Hbase中某个表中, 传统做法, 按照上述流程, 一步步将数据最终写入Hfile中, 此时整个region集群会经历大量的写入请求操作, hbase集群需要调度大量资源来满足本次的数据写入工作,如果这个时候, 又出现大量的读取数据请求也去访问这个表, 会发生什么问题呢? 读取性能有可能回受到影响 甚至出现卡顿现象

思考如何解决呢?

hbase的Bulk Load 说明: 对一批数据, 提前按照HBase的Hfile文件格式存储好, 然后将Hfile文件格式数据直接放置到Hbase对应数据目录下, 让Hbase直接加载, 此时不需要Hbase提供大量的写入资源, 即可完成全部数据写入操作总结: 第一个步骤: 将数据文件转换为HFile文件格式   -- MapReduce第二个步骤: 将Hfile文件格式数据加载到Hbase中

这里是引用

3.1 需求说明

  • 需求: 需要将每一天的银行转账记录的数据 存储到HBase中 , 数据量比较的庞大

    • 数据所在位置: HDFS中,
    • 数据格式为: CSV 分割符号为逗号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xIivGWWg-1613913289270)(day18_课堂笔记.assets/image-20210119145033844.png)]

3.2 准备工作

    1. 在hbase中创建名称空间, 并创建hbase的表
# 创建名称空间:
create_namespace 'ITCAST_BANK'
# 在名称空间下, 创建目标表:
create 'ITCAST_BANK:TRANSFER_RECORD' ,{NAME=>'C1',COMPRESSION=>'GZ'},{NUMREGIONS=>6,SPLITALGO=>'HexStringSplit'}
    1. 创建 maven项目 加载相关的pom 依赖

    说明: 如果将此全部导入到本项目中, 出现全部爆红错误, 可以将此内容放置到父工程的pom依赖中

    此时 有可能导致其他某个项目爆红(不用管), 只需要保证当前自己的项目没有任何问题即可

    <repositories><repository><id>aliyun</id><url>http://maven.aliyun.com/nexus/content/groups/public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled><updatePolicy>never</updatePolicy></snapshots></repository></repositories><dependencies><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-client</artifactId><version>2.1.0</version></dependency><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-mapreduce</artifactId><version>2.1.0</version></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-mapreduce-client-jobclient</artifactId><version>2.7.5</version></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-common</artifactId><version>2.7.5</version></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-mapreduce-client-core</artifactId><version>2.7.5</version></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-auth</artifactId><version>2.7.5</version></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-hdfs</artifactId><version>2.7.5</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><target>1.8</target><source>1.8</source></configuration></plugin></plugins></build>
    1. 在项目中创建包 和 添加配置文件log4j.properties

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iNWtGvDU-1613913289272)(day18_课堂笔记.assets/image-20210119151046606.png)]

    1. 将转换csv数据上传到HDFS中: 数据在资料中
hdfs dfs -mkdir -p /bulkLoad/outputhdfs dfs -put bank_record.csv /bulkLoad/output

3.3 将CSV数据转换为HFile文件格式数据

  • map 程序的代码
package com.itheima.bulkload.mr;import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;import java.io.IOException;public class BulkLoadMapper extends Mapper<LongWritable,Text,ImmutableBytesWritable,Put> {private  ImmutableBytesWritable k2 = new ImmutableBytesWritable();@Overrideprotected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {//1. 获取一行数据String line = value.toString();if(line != null && !"".equalsIgnoreCase(line)){//2. 对数据进行切割操作String[] fields = line.split(",");//3. 封装k2 和 v2的数据//封装 k2String rowkeyStr = fields[0];k2.set(rowkeyStr.getBytes());// 封装v2Put v2 = new Put(rowkeyStr.getBytes());v2.addColumn("C1".getBytes(),"code".getBytes(),fields[1].getBytes());v2.addColumn("C1".getBytes(),"rec_account".getBytes(),fields[2].getBytes());v2.addColumn("C1".getBytes(),"rec_bank_name".getBytes(),fields[3].getBytes());v2.addColumn("C1".getBytes(),"rec_name".getBytes(),fields[4].getBytes());v2.addColumn("C1".getBytes(),"pay_account".getBytes(),fields[5].getBytes());v2.addColumn("C1".getBytes(),"pay_name".getBytes(),fields[6].getBytes());v2.addColumn("C1".getBytes(),"pay_comments".getBytes(),fields[7].getBytes());v2.addColumn("C1".getBytes(),"pay_channel".getBytes(),fields[8].getBytes());v2.addColumn("C1".getBytes(),"pay_way".getBytes(),fields[9].getBytes());v2.addColumn("C1".getBytes(),"status".getBytes(),fields[10].getBytes());v2.addColumn("C1".getBytes(),"timestamp".getBytes(),fields[11].getBytes());v2.addColumn("C1".getBytes(),"money".getBytes(),fields[12].getBytes());//4. 输出context.write(k2,v2);}}
}
  • 驱动类的代码
package com.itheima.bulkload.mr;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
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.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;public class BulkLoadDriver {public static void main(String[] args) throws Exception {//1. 获取job对象//Configuration conf = new Configuration();Configuration conf = HBaseConfiguration.create();conf.set("hbase.zookeeper.quorum","node1:2181,node2:2181,node3:2181");Job job = Job.getInstance(conf, "BulkLoadDriver");//2. 配置集群运行的必备项job.setJarByClass(BulkLoadDriver.class);//3. 配置 MR的天龙八部//3.1: 指定输入类, 及其输入的路径job.setInputFormatClass(TextInputFormat.class);TextInputFormat.addInputPath(job,new Path("hdfs://node1:8020/bulkLoad/input/bank_record.csv"));//3.2: 指定map类 及其输出的k2和v2的类型job.setMapperClass(BulkLoadMapper.class);job.setMapOutputKeyClass(ImmutableBytesWritable.class);job.setMapOutputValueClass(Put.class);//3.3 : 指定 shuffle操作:  分区 排序 规约 分组  默认即可//3.7: 指定reduce类, 及其输出 k3 和 v3的类型job.setNumReduceTasks(0);job.setOutputKeyClass(ImmutableBytesWritable.class);job.setOutputValueClass(Put.class);//3.8: 设置输出类, 及其输出的路径: HFile文件格式job.setOutputFormatClass(HFileOutputFormat2.class);HFileOutputFormat2.setOutputPath(job,new Path("hdfs://node1:8020/bulkLoad/output"));Connection conn = ConnectionFactory.createConnection(conf);Table table = conn.getTable(TableName.valueOf("ITCAST_BANK:TRANSFER_RECORD"));HFileOutputFormat2.configureIncrementalLoad(job,table,conn.getRegionLocator(TableName.valueOf("ITCAST_BANK:TRANSFER_RECORD")));//4. 提交任務boolean flag = job.waitForCompletion(true);//5. 退出程序System.exit(flag ? 0 : 1);}}

3.4 将Hfile文件格式数据加载HBase中

语法说明:

hbase org.apache.hadoop.hbase.tool.LoadIncrementalHFiles  数据路径 Hbase表名

案例

hbase org.apache.hadoop.hbase.tool.LoadIncrementalHFiles  hdfs://node1.itcast.cn:8020/bulkLoad/output/  ITCAST_BANK:TRANSFER_RECORD

导入时 发现报错了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MliMn0E9-1613913289274)(day18_课堂笔记.assets/image-20210119161040733.png)]

此时可以通过查看当前执行对应regionServer的日志文件:

tail -100f hbase-root-regionserver-node2.out

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XaS7etY-1613913289277)(day18_课堂笔记.assets/image-20210119161358682.png)]

错误原因: 是由于在安装软件过程中, 采用不同域名来操作, 导致Hbase无法识别 从而导致数据加载失败了

如何解决呢? 建议选择 HDFS的修改

修改 node1的hdfs的core-site.xml
cd /export/server/hadoop-2.7.5/etc/hadoop
vim core-site.xml
# 修改一下内容: <property><name>fs.defaultFS</name><value>hdfs://node1.itcast.cn:8020</value></property>
修改完成后, 将core-site.xml 发送给node2和node3
scp -r core-site.xml node2:$PWD
scp -r core-site.xml node3:$PWD还需要修改: slaves文件:
vim slaves
# 修改为以下内容:
node1.itcast.cn
node2.itcast.cn
node3.itcast.cn# 将此文件 发生给 node2 和 node3
scp -r slaves node2:$PWD
scp -r slaves node3:$PWD

4. HBase的协处理器

Hbase的协处理器主要有二大类: observer 和 endpoint

  • observer: 可以将其看做是拦截器(过滤器 触发器), 可以基于这种协处理器对Hbase相关操作进行监控(钩子 Hook)

    • 例如: 监控用户插入到某个表操作, 插入之前要打印一句话
    • ObServer所提供一些类, 这些类可以监控到HBase中各种操作: 对数据的CURD 对表的CURD 对region的操作 对日志操作
    • Observer能做什么事情?
        1. 记录操作日志
        1. 权限的管理
  • endpoint: 可以看做数据库中存储过程,也可以看做在java代码中封装一个方法(功能), 将这个方法放置服务端, 让服务器进行执行操作, 客户端只需要拿到服务端执行结果即可

    • 作用: 执行一些聚合操作: 求和 求差 求最大 …

day18_课堂笔记相关推荐

  1. 管理系统中计算机应用第四章重点,管理系统中计算机应用课堂笔记第四章(4)...

    管理系统中计算机应用课堂笔记第四章(4) 分类:自考 | 更新时间:2016-07-08| 来源:转载 这个分析和抽象工作可分以下三步进行: 5.2.1数据流程图的绘制 数据流程图既是对原系统进行分析 ...

  2. AI公开课:19.04.10颜水成—360副总裁《人工智能:观察与实践》课堂笔记以及个人感悟—191017再次更新

    AI公开课:19.04.10颜水成-360副总裁<人工智能:观察与实践>课堂笔记以及个人感悟 导读       颜水成,新加坡国立大学副教授.360集团副总裁.人工智能研究院院长. 颜水成 ...

  3. AI公开课:19.05.16漆远-蚂蚁金服集团CF《金融智能的深度与温度》课堂笔记以及个人感悟—191017再次更新

    AI公开课:19.05.16漆远-蚂蚁金服集团CF<金融智能的深度与温度>课堂笔记以及个人感悟-191017再次更新 导读         漆远,麻省理工学院博士后,39岁被评为美国普渡大 ...

  4. AI英特尔杯公开课:2019.06.27在线直播《研究生人工智能创新大赛—AI赋能,创新引领》课堂笔记和感悟(二)

    AI英特尔杯公开课:2019.06.27在线直播<研究生人工智能创新大赛-AI赋能,创新引领>课堂笔记和感悟(二) 导读      讲解总体不错,知识点比较基础,适合入门,各种主流框架都有 ...

  5. AI英特尔杯公开课:2019.06.27在线直播《研究生人工智能创新大赛—AI赋能,创新引领》课堂笔记和感悟(一)

    AI英特尔杯公开课:2019.06.27在线直播<研究生人工智能创新大赛-AI赋能,创新引领>课堂笔记和感悟(一) 导读      讲解总体不错,知识点比较基础,适合入门,各种主流框架都有 ...

  6. AI公开课:19.05.29 浣军-百度大数据实验室主任《AutoDL 自动化深度学习建模的算法和应用》课堂笔记以及个人感悟

    AI公开课:19.05.29 浣军 百度大数据实验室主任<AutoDL 自动化深度学习建模的算法和应用>课堂笔记以及个人感悟 导读        浣军博士,汉族,1975年出生于江苏苏州, ...

  7. AI公开课:19.05.15施尧耘-达摩院量子实验室主任《量子计算:前景与挑战》课堂笔记以及个人感悟

    AI公开课:19.05.15施尧耘-达摩院量子实验室主任<量子计算:前景与挑战>课堂笔记以及个人感悟 导读         施尧耘1997年本科毕业于北京大学,后在普林斯顿大学取得计算机科 ...

  8. AI公开课:19.05.22 Aya Soffer—IBM AI Tech VP《Advanced, scalable, and trusted AI 》课堂笔记以及个人感悟

    AI公开课:19.05.22 Aya Soffer-IBM AI Tech VP<Advanced, scalable, and trusted AI >课堂笔记以及个人感悟 Introd ...

  9. AI公开课:19.04.18俞益洲—深睿医疗CS《计算机视觉的应用与落地》课堂笔记以及个人感悟

    AI公开课:19.04.18俞益洲-深睿医疗CS<计算机视觉的应用与落地>课堂笔记以及个人感悟 导读       俞益洲,现为深睿医疗联合创始人.首席科学家(Chief Scientist ...

最新文章

  1. 暴雨之后,评估与重建可以用这个数据集
  2. MAXIMO启动中心设置
  3. 百练OJ:2388:寻找中位数
  4. css元素居中方法归纳
  5. VTK:Utilities之DiscretizableColorTransferFunction
  6. VTK:图片之ImageOrder
  7. 【分享】WeX5的正确打开方式(1)
  8. java实现打印菱形
  9. 三星s9php禁用列表,ADB禁用列表
  10. logback配置文件模板
  11. 浏览器是如何工作的:Chrome V8让你更懂JavaScript
  12. 也谈USB重定向的方式
  13. An'droid TextView
  14. 安装到到setup support files时,SQL2008安装程序自动消失掉。
  15. 一篇文章带你读懂水晶易表的简介及其安装初识
  16. 【解决】UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xef in position 0: ordinal not in range(128
  17. 2014江西理工大学C语言程序竞赛高级组
  18. 计算机毕业设计ssm筋斗租车系统d0733系统+程序+源码+lw+远程部署
  19. Luogu 3934 Nephren Ruq Insania
  20. 荧光和明场图像融合 matlab,一种用于明场显微成像的多层图像融合算法

热门文章

  1. 肥宅快乐串-动态规划
  2. Ubuntu20.04搭建hadoop集群
  3. React中使用富文本编辑器Quill,支持粘贴图片
  4. 阿里体育法定代表人变更 由张大钟变更为戴玮
  5. grunt uglify
  6. OpenHardwareMonitor0.9.6汉化说明
  7. PHP时间日期操作增减(date strtotime) 加一天 加一月
  8. 不花一分钱,七个小招式简单搞定新车异味
  9. 微软OneDrive 免费云盘容量有多大?坑爹15GB 太小、太慢
  10. STM32学习|STM32最小系统介绍