Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

skyisbule/Graduation_Design

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

80 Commits

Repository files navigation

关于本项目

如你所见,它是我的本科毕业设计。
之前写的时候觉得还挺不错的,但经历了半年的阿里生活后,现在回过头来觉得代码真的就是一坨屎hhhhh,看以后能不能给他重构下。

关于项目背景

如果一个没有大数据背景的人去处理大数据,那么他会怎么做?
大概率会把数据导入MySql,然后用蹩脚的SQL去尝试把数据统计出来,这是比较理想的情况。
那么万一数据量很大MySql响应非常慢呢?万一统计很复杂SQL根本无法实现呢?万一无脑selsect直接把内存撑爆了呢?数据迭代怎么做?
虽然Hadoop、Spark、Pandas之类的计算引擎可以很好地解决这个问题,但他们对于新手别说学习了,恐怕搭建个环境都非常困难,如果只是为了分析点简单数据就被迫让自己变成"大数据专家"并且以后都不会再用的话,是一种非常得不偿失的行为。 于是很多新手在MySql无法实现时,不会选择去学习Spark,而是决定自己写程序,从文件中把数据读出来,然后自己实现算法去处理和统计数据,虽然基本能够达到目标,但这个过程对新手非常非常不友好,因为他需要关心文件读取、内存容量、迭代及保存效率等非常复杂的东西,这些通常不是新手能处理好的,于是很多人也从就这个过程中放弃了学习大数据。

解决方案

于是针对这个问题,本系统应运而生,底层存储拥有和MySql类似的库表架构,顶层运算支持和Spark相似迭代运算,让新手也可以很方便的对数据进行读写、运算, 而不用关心诸如文件操作、内存溢出、迭代运算、数据缓存、数据媒介等,只需要完成数据处理的逻辑即可,其他的操作都会由引擎层完成。
ps:其实也不仅仅是大数据处理,包括爬虫之类的,凡是涉及存储+计算的,都可以使用本引擎。

如何使用

由于服务是以jar包的形式透出去的,所以你可以先把代码拉下来,直接运行example包下的代码进行初步了解,在熟悉了操作之后直接mvn package打个包, 在其他项目中引入即可,非常简单。

核心概念及使用

那么直奔主题,介绍一下本引擎的核心设计思路及使用方法。

数据模型

本引擎定义了一种叫做"dataObject"的东西,你可以暂时把它理解为数据集,每一个"dataObject"就像MySql中的一张表一样,它是本引擎中的一等公民, 你对数据的一切操作都基于它所提供的API。
在你创建或获取一个数据集后,你就可以随意的向它扔数据,取数据,而不用担心诸如内存和效率等问题,引擎会在背后自动帮你选择性地构建合适的缓存,选择合适IO实现,在适合的时候自动异步刷盘,底层数据自动分库分表,自动构建索引。
同样的,你也可以通过实现compute接口并将其传入dataObject来对数据集的数据进行运算,同样的,诸如数据读写、中间数据刷盘、联动等都会由引擎负责,开发者只需要关心拿到数据后怎么处理即可。

那么如何获取一个"dataObject"呢?它的唯一入口是通过引擎实例的create及衍生方法,如下面的示例代码。

 //我们首先定义一个普通的JavaBean
 public class User {
 public User(){
 }
 public User(Integer uid, String name, Integer age) {
 this.uid = uid;
 this.name = name;
 this.age = age;
 }
 public Integer uid;
 public String name;
 public Integer age;
 }
 //然后写主函数
 public static void main(String[] args) {
 //创建SkyDB入口
 SkyDB db = SkyDB.getInstance();
 //创建一个数据集,指定版本为1.0
 DataObject dataObject = db.create(User.class, "1.0");
 }

至此我们就新建并拿到了一个数据集,即dataObject,此时你可能会好奇版本1.0是什么意思,但请不要着急,后文会详细介绍它的意义及使用。

简单的内存插入

上文也说过,引擎中所有对数据的操作都是针对dataObject的,那么在创建dataObject后如何插入数据呢?插入完后,怎么显示插入的数据呢? 直接上代码。

 public static void main(String[] args) {
 //创建SkyDB入口
 SkyDB db = SkyDB.getInstance();
 //创建一个数据集,指定版本为1.0
 DataObject dataObject = db.create(User.class, "1.0");
 //演示一下逐个插入
 for (int i = 0; i < 10; i++) {
 User user = new User(i, "sky", 18 + i);
 dataObject.doInsert(user);
 }
 //获取第一个页表的数据
 List<Object> objects = dataObject.getPage(1);
 //你可以自己打印信息
 for (Object object : objects) {
 User temp = (User)object;
 System.out.println(temp.uid + "_" + temp.name + "_" + temp.age);
 }
 //你也可以用内置的util来打印出漂亮的表格,更方便查看。
 ConsoleUtil.show(objects);
 //最后再打印一下数据集的基础信息
 dataObject.showInfo();
 }
输出:
[skyDB info] db starting...
[skyDB info] db start success!
[skyDB] create success: example.po.User_1.0
0_sky_18
1_sky_19
2_sky_20
3_sky_21
4_sky_22
5_sky_23
6_sky_24
7_sky_25
8_sky_26
9_sky_27
----------
|0|sky|18|
|1|sky|19|
|2|sky|20|
|3|sky|21|
|4|sky|22|
|5|sky|23|
|6|sky|24|
|7|sky|25|
|8|sky|26|
|9|sky|27|
----------
{"tableName":"1.0","pageNum":1,"recordNum":10,"types":["INT","STRING","INT"],"columnNames":["uid","name","age"]}

何为页表

在演示简单插入数据的示例中,示例程序从库里取出了"第一个页表",并打印出了页表里的数据,那么何为页表?
简单点说,页表就是引擎层读写数据的最小的长度单元,为16KB,这点在设计上和MySql等主流数据库是一致的,只不过相对于它们,本引擎将这种针对 页表的读写能力直接暴露给开发者,让开发者可以基于此实现一些很骚的操作,而不像SQL的语法那样限制特别多。

文件读取、批量插入

如果说前边的都是些开胃菜,那么从这里开始就是一个开发者非常关心的东西了,如何从文件中读取数据?如何在内存中,通过代码执行高效的批量数据插入? 同样的,我们直接看代码。

 public static void main(String[] args) {
 //创建SkyDB入口
 SkyDB db = SkyDB.getInstance();
 //创建一个数据集,指定版本为1.1,不和1.0冲突
 DataObject dataObject = db.create(User.class, "1.1");
 //指定文件,并使用默认的解析器
 dataObject.fromFile("/Users/hqt/Desktop/data.text", new SimpleParser(" "));
 //打印一下数据
 ConsoleUtil.show(dataObject.getPage(1));
 }
 /**
 * 内置的一个简单的文件解析器
 */
 public class SimpleParser implements Parser {
 public SimpleParser(String separator) {
 this.separator = separator;
 }
 /**
 * 代表 line 分隔符格式的结构 比如:
 * id name age
 * -----------以下为文本内的数据,则在构造parser的时候传入" "即可
 * 1 sky 23
 * 2 sky 23
 */
 private String separator;
 @Override
 public List<String> doParser(String line, ParseContext context) {
 return Arrays.asList(line.split(separator));
 }
 }

代码并不复杂,我就不多解释了,如果你的文件格式是其他的的类型,那么只需要实现Parser接口,重写doParser方法即可。
同样的,批量插入的代码也不就解释了,很容易理解。

 public static void main(String[] args) {
 //创建db实例
 SkyDB skyDB = SkyDB.getInstance();
 skyDB.dropIfExit(User.class, "1.0");
 //由于 simpleInsertExample中已经创建了这个数据集 所以这里直接get即可
 //如果你把数据文件删除了 或执行了drop 则在这里你需要使用create
 DataObject dataObject = skyDB.getOrCreate(User.class, "1.0");
 //准备数据
 List<Object> data = new LinkedList<>();
 for (int i = 0; i < 10000; i++) {
 User user = new User(i, "sky", 18 + i);
 data.add(user);
 }
 long beginTime = System.currentTimeMillis();
 //执行批量插入
 dataObject.bathInsert(data);
 long endTime = System.currentTimeMillis();
 //看一下插入所花费的时间
 System.out.println("插入10000条数据总共花费:" + (endTime - beginTime) + "ms");
 //打印一下此时的数据集信息
 dataObject.showInfo();
 //获取第一个页表的数据
 List<Object> objects = dataObject.getPage(1);
 //你也可以用内置的util来打印出漂亮的表格,更方便查看。
 ConsoleUtil.show(objects);
 }

如何对数据进行运算

通过上文提到的功能,相信你已经可以创建数据集并把自己需要的数据写入引擎了,那么接下来就开始进行数据的运算,废话不多说先看代码。

 public static void main(String[] args) throws IllegalAccessException, InstantiationException {
 //创建SkyDB入口
 SkyDB db = SkyDB.getInstance();
 //创建一个数据集,指定版本为1.0
 DataObject dataObject = db.getOrCreate(User.class, "1.0");
 //开始对任务进行计算,如果计算比较复杂的话,推荐把computer实现另写一个类,而不是像下面这样只适合简单的情况。
 dataObject.doCompute(new Computer<User>() {
 @Override
 public void init(ComputeContext computeContext) {
 System.out.println("开始计算前,你可以在这里做一些诸如初始化之类的操作。");
 }
 @Override
 public void doCompute(User user, ComputeContext context) {
 System.out.println("db里的每一条数据都会被传进这个函数,这里是你处理数据的最核心的方法");
 //这里只是简单演示一下计算
 if (user.age % 2 == 0){
 //把它扔到上下文中
 context.getResultList().add(user);
 }
 }
 @Override
 public void afterOnePage(ComputeContext context) {
 System.out.println("已经计算完了一页的数据");
 }
 @Override
 public void afterAll(ComputeContext context) {
 System.out.println("所有的数据都已经处理完了,打印一下结果:");
 ConsoleUtil.show(context.getResultList());
 }
 });
 }

可以看到,计算数据的核心为dataObject的doCompute方法,而开发者也仅仅需要实现Computer接口的一系列方法,引擎就会自动帮我们执行计算了。
在这背后,引擎会将数据从文件中读出来,将每条数据转成user对象,传入并调用doCompute方法,即开发者实现的方法,供开发者对数据进行处理。
这样,开发者就不需要关心那么多关于文件操作、数据操作之类的问题了,只需要关心自己拿到了一条数据后应该怎么办即可。
那么你可能会问,我处理了单条数据后是需要和其他数据做聚合的,是有关联的,怎么做?很简单,看到ComputeContext了吗?塞进去即可!

关于版本号

在看完计算示例后,你应该差不多可以理解它存在的意义了,没错!你可以随时随意地创建不同的dataObject,并在任何时机,包括doCompute方法里,对数据进行 任何操作,比如上文的计算示例中,直接将age为偶数的插入上下文,最终打印,而你完全可以把它改成插入一个新的版本为1.1的dataObject,它就会立刻同步进文件, 并且之后你也可以基于它再做任何操作,可见,自由度是非常之高的。

一个真实的项目

可以看看example包下边的paperAnalysis的那个示例,很完整了。

About

我的毕设

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /