最近一直有粉丝来信咨询,面试中项目经历怎么写?感觉自己平时参与的项目比较少,没有拿得出手的,怎么丰富自己的项目经验?有没有什么办法?
当然有!有一台能上网的电脑就可以动手肝--动手自己从0实现一些开源的框架&轮子!可能有人就要问了,都有那么成熟的框架了,为啥自己还要去实现?自己实现的又不能在生产环境去使用。
个人从事软件开发这么多年,总结的一条非常重要的经验就是:学习一个框架最好的方法就是去动手实现它!实现一个麻雀虽小五脏俱全的版本,自己写出其核心逻辑,能够将自己学到的知识串起来。在实现的过程中会遇到很多问题,以及技术方案的选型,各种优化操作,debug,能够极大的加深自己对知识的理解。
我把自己最近一段时间动手实现的轮子&框架的文档解析、全部源码全部放在了github,方便大家参考,也欢迎大家帮我点个小星星STAR: github.com/xiajunhust/…
本文从0实现一个简易的KV数据库,代码行数不多,核心代码不超过200行。
设计思路
- 查询语法:支持String类型的Key和Value,暂不支持其他复杂类型,不支持SQL语法解析和执行计划优化。
- 存储引擎:基于顺序日志进行写入,每条执行命令的数据信息按行存在文本日志文件里。暂不支持类似hbase中的合并等复杂的逻辑。
代码模块
代码模块比较简单,主要是:
(1)API定义,定义核心的接口。
(2)核心实现,本文实现的是基于顺序日志的KV数据库。
(3)枚举类和领域模型。
下面给大家介绍核心代码解析。
核心API定义:
基于顺序日志的实现:
/**
* 基于日志顺序写入的KV数据库实现
*
* @author summer
* @version : LogBasedKV.java, v 0.1 2022年05月05日 4:06 PM summer Exp $
*/
@Log
public class LogBasedKV implements SimpleKvClient {
/**
* 日志文件
*/
private File logFile;
/**
* 构造函数
*
* @param fileName 文件名
*/
public LogBasedKV(String fileName) {
logFile = new File(fileName);
}
@Override
public void put(String key, String value) {
BufferedWriter bufferedWriter = null;
try {
FileWriter fileWriter = new FileWriter(logFile, true);
bufferedWriter = new BufferedWriter(fileWriter);
//日志写入内容构造
CommandRequestModel commandRequestModel = new CommandRequestModel();
commandRequestModel.setCommandTypeEnum(CommandTypeEnum.SET);
commandRequestModel.setKey(key);
commandRequestModel.setValue(value);
//往日志文件中写入内容
bufferedWriter.write(JSON.toJSONString(commandRequestModel));
bufferedWriter.newLine();
} catch (Exception e) {
log.warning("put exception,[" + key + "," + value + "]");
} finally {
try {
bufferedWriter.close();
} catch (Exception e) {
log.warning("bufferedWriter close exception");
}
}
}
@Override
public String get(String key) {
try {
FileReader fileReader = new FileReader(logFile);
BufferedReader bufferedReader = new BufferedReader(fileReader);
//按行读取日志文件的内容,查找到最后一条修改类操作命令
CommandRequestModel lastUpdateCommand = null;
String line = bufferedReader.readLine();
while (line != null) {
CommandRequestModel commandRequestModel = JSON.parseObject(line, CommandRequestModel.class);
if ((CommandTypeEnum.SET == commandRequestModel.getCommandTypeEnum()
|| CommandTypeEnum.DEL == commandRequestModel.getCommandTypeEnum())
&& StringUtils.equals(key, commandRequestModel.getKey())) {
lastUpdateCommand = commandRequestModel;
}
line = bufferedReader.readLine();
}
if (lastUpdateCommand == null || CommandTypeEnum.DEL == lastUpdateCommand.getCommandTypeEnum()) {
return null;
}
return lastUpdateCommand.getValue();
} catch (Exception e) {
log.warning("get exception,[" + key + "]");
}
return null;
}
@Override
public void del(String key) {
BufferedWriter bufferedWriter = null;
try {
FileWriter fileWriter = new FileWriter(logFile, true);
bufferedWriter = new BufferedWriter(fileWriter);
//日志写入内容构造
CommandRequestModel commandRequestModel = new CommandRequestModel();
commandRequestModel.setCommandTypeEnum(CommandTypeEnum.DEL);
commandRequestModel.setKey(key);
//往日志文件中写入内容
bufferedWriter.write(JSON.toJSONString(commandRequestModel));
bufferedWriter.newLine();
} catch (Exception e) {
log.warning("del exception,[" + key + "]");
} finally {
try {
bufferedWriter.close();
} catch (Exception e) {
log.warning("bufferedWriter close exception");
}
}
}
}
性能分析
因为是追加写入,所以写入的性能非常快,比如Hbase就是采用的顺序写入的方式。关于读取,因为需要读取扫描整个文件来得到key对应的记录,因此性能较差,是O(N)。像实际生产环境使用,是会做一些优化工作的,比如把日志内容进行刷盘处理,同样的key的多条记录会进行合并处理,然后建立索引,查询性能会快很多。此处只是给大家做个demo展示,还有相当多的优化工作需要做。
工程测试
import com.summer.simplekv.api.SimpleKvClient;
import com.summer.simplekv.core.LogBasedKV;
import lombok.extern.java.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Log
@SpringBootApplication
public class SimplekvApplication {
public static void main(String[] args) {
String logFileName = "simplekv.log";
SimpleKvClient kvClient = new LogBasedKV(logFileName);
//写入数据
for (int index = 0; index < 5; ++index) {
kvClient.put("k-" + index, "v-" + index);
}
//查询数据
for (int index = 0; index < 5; ++index) {
String value = kvClient.get("k-" + index);
log.info("get [" + "k-" + index + " value is [" + value + "]");
}
//删除一行数据
kvClient.del("k-" + 3);
log.info("after del 3");
//再次查询已被删除的数据
String value = kvClient.get("k-" + 3);
log.info("get [" + "k-" + 3 + " value is [" + value + "]");
SpringApplication.run(SimplekvApplication.class, args);
}
}
测试比较简单,直接构建的实例,调用其查询、写入、删除方法即可。
运行日志:
五月 05, 2022 4:59:56 下午 com.summer.simplekv.SimplekvApplication main
信息: get [k-0 value is [v-0]
五月 05, 2022 4:59:56 下午 com.summer.simplekv.SimplekvApplication main
信息: get [k-1 value is [v-1]
五月 05, 2022 4:59:56 下午 com.summer.simplekv.SimplekvApplication main
信息: get [k-2 value is [v-2]
五月 05, 2022 4:59:56 下午 com.summer.simplekv.SimplekvApplication main
信息: get [k-3 value is [v-3]
五月 05, 2022 4:59:56 下午 com.summer.simplekv.SimplekvApplication main
信息: get [k-4 value is [v-4]
五月 05, 2022 4:59:56 下午 com.summer.simplekv.SimplekvApplication main
信息: after del 3
五月 05, 2022 4:59:56 下午 com.summer.simplekv.SimplekvApplication main
信息: get [k-3 value is [null]
还在等什么,赶紧把代码从github clone下来跑起来吧!传送门「觉得有用的话帮忙点个STAR,你们的点赞是我创作的动力!」.