OMToolkit介绍(4) :Object-Oriented Database 实现

[align=center][size=large][b]OMToolkit介绍(4) :Object-Oriented Database 实现[/b][/size][/align]
[align=center][size=medium][b]1. 概述[/b][/size][/align]
  OMToolkit中数据存储的实现主要位于com.omc.data中,说是Object-Oriented Database可能有点夸大了,实际上是采用文本存储Entity的方式,实现方式比较初级。
  存储文件有两个,分别是data/meta和data/data。程序启动时将加载meta文件的内容。meta存储了Entity的id,Entity数据在data文件中的位置,以及Entity的类型。读取一个对象时,先从meata中读取数据所在的位置,再到data文件中获取Entity的数据(各属性的值)。
  程序启动时会加载全部的meta,数据量较大时将占用较大内存;目前的一个思路是可以建立“meta的meata”,从而形成多级的索引。这将在后续版本中实现。
  数据的更新和删除采用“无修改”的方式,及无论是对数据进行更新还是删除,都会在meta文件和data文件的末尾追加值,而不会修改原来的数据。这样一来,这了两个文件就会越来越大了。后续版本将会提供数据清理的工具。
  另一个需要解决的问题是更新锁。即以更新为目的获取对象时,将加锁以防止其他处理过程对数据的更新。这是一个潜在的性能瓶颈。后续的版本将以一定的策略对多个更新进行合并,以避免加锁的操作。

[align=center][size=medium][b]2. DataUtil:数据操作类[/b][/size][/align]
  DataUtil类是数据处理的主要类。负责数据的读取、更新和删除。

  根据id获取数据get(...)方法的实现如下:

public static Entity get(long id, boolean forUpdate) throws Exception {
if (forUpdate) {
while (locks.contains(id)) {}
locks.add(id);
}

Entity entity = cache.get(id);
return entity == null ? loadEntity(id) : entity;
}
  首先,如果是以更新为目的获取数据,那么需要检查更新锁;然后尝试从cache中获取Entity;如果cache中没有数据,则从数据文件中读取。
  读取Entity数据的loadEntity(...)方法的实现如下:

private static Entity loadEntity(long id) throws Exception {
Meta meta = Meta.get(id);
Entity entity = meta.getEntity();

loadFields(entity, meta);

entity.setId(id);
cache.add(entity);

return FieldUtil.clone(entity);
}
  先获取Entity的meta(这些meta以HashMap的形式加载到内存),然后创建Entity加载Entity的属性值,设置Entity的id,保存到cache中,clone一份后返回。之所以要进行clone,是为了保证不同的处理过程中对Entity的修改互不影响。
  加载Entity属性的loadFields(...)方法的实现如下:

private static void loadFields(Entity entity, Meta meta) throws Exception {
long position = meta.getPosition();
int size = meta.getSize();
String content = FileUtil.read(DATA_FILE, position, size);
parseFields(entity, content);
}
  获取数据所在的位置和size,从data文件中读取存储的数据的内容,然后进行解析并为Entity的属性赋值。parseFields(...)是FieldUtil中的一个方法,是通过import static方法导入的。

  保存对象的实现如下:

public static synchronized void save(Entity entity) throws Exception {
File dataFile = new File(DATA_FILE);
long position = dataFile.length();
saveData(entity);
int size = (int) (dataFile.length() - position);

Meta.saveMeta(entity, position, size);
cache.add(entity);
}
  这个方法是synchronized的,因此同时更新多个对象并保存时需要等待。逻辑是将Entity以一定的规则序列化后保存到data文件中,同时把起始位置和长度保存在meta中,并将entity放入cache。

  删除对象的实现如下:

public static synchronized void delete(long id) throws Exception {
Meta.delete(id);
cache.delete(id);
}
  该方法也是synchronized的。仅需要id作为参数,向meta中写入一个删除记录,并从cache中将该Entity移除。

[align=center][size=medium][b]3. FieldVisitor:属性访问类[/b][/size][/align]
  FieldVistor类仅有两个名称都为eachField的方法,同时还包含了两个接口:

public static interface Getable {
public Object get(Field f) throws Exception;
}

public static interface Operator {
public void operate(Field f) throws Exception;
}
  接口Getable用于封装获取Field值的方法,Operator用于封装对Field进行操作的方法。

  由于第一个eachField(...)方法调用了第二个eachField(...)方法,我们就先看看的二个eachField(...)方法的实现:

public static void eachField(Class<?> clz, Class<?> upper, Operator operator)
throws Exception {
do {
for (Field f : clz.getDeclaredFields()) {
f.setAccessible(true);
operator.operate(f);
}
clz = clz.getSuperclass();
} while (!clz.equals(upper));
}
  该方法将遍历指定的clz的所有属性,并递归遍历父类属性,直到指定的“上界”为止;遍历的过程中,可以对每个属性进行操作;操作的过程以接口Operator进行封装。
  这是一个对指定类的所有属性进行操作的通用方法。

  第一个eachField(...)的实现如下:

public static boolean eachField(final Entity entity, Class<?> upper,
final Getable getable) throws Exception {
final Wrapper<Boolean> warapper = new Wrapper<Boolean>(false);

Operator operator = new Operator() {
public void operate(Field f) throws Exception {
Object obj = getable.get(f);
if (obj == null) {
return;
}

if (obj instanceof OMField<?>) {
OMField<?> omField = (OMField<?>) obj;
omField.setEntity(entity);
warapper.set(true);
}

f.set(entity, obj);
}
};

eachField(entity.getClass(), upper, operator);

return warapper.get();
}
  该方法在第二个eachField(...)方法的基础上,实现对属性的赋值,其逻辑为:先利用传入的Getable对象获取属性的值;如果值不为空,检查它是非为OMField,如果为OMField则设置其enttiy,并记录更新;然后对属性进行赋值。
  之所以要记录更新,是因为一旦更新了对象,在事务提交时就可能需要将更新过的对象重新保存到数据文件中。
  该方法是一个为指定类的属性进行赋值的通用方法。

  那么,这些方法是如何被运用的?这就需要看看属性操作类FieldUtil的实现了。

[align=center][size=medium][b]4. FieldUtil:属性操作类[/b][/size][/align]
  FieldUtil提供对Entity的属性进行操作的方法,包括新创建的Entity的属性的初始化、从Map中解析属性并为Entity的属性赋值、充数据文件保存的数据中解析属性值、获取排序后的属性名称、将指定Entity的属性写入指定的writer中、对Entity进行clone等。这里只举两个例子来说明FieldUtil的实现方式。

  首先,是获取排序后的属性值的getSortedFields(...)方法:

final Queue<String> fields = new PriorityQueue<String>();

Operator operator = new Operator() {
public void operate(Field f) {
if (ReflectUtil.isSubclass(f.getType(), OMField.class)) {
fields.offer(f.getName());
}
}
};

eachField(clz, Entity.class, operator);

return fields;
  这个方法利用了FieldVisitor的eachField(...)方法,将获取属性值并添加到返回的Queue中的操作封装在Operator中传入,从而实现了对指定类的属性的遍历。排序是通过PriorityQueue本身的特性实现的,同时也限定了被添加的属性的类型必须为OMField的子类。

  然后,看看从数据文件中读取属性的parseFields(...)方法:

public static void parseFields(Entity entity, String content)
throws Exception {
Queue<String> fields = getSortedFields(entity.getClass());
for (String value : StringUtil.split(content, '\0')) {
loadField(entity, fields.poll(), value);
}
}

private static void loadField(Entity entity, String field, String value)
throws Exception {
Field f = entity.getClass().getDeclaredField(field);
f.setAccessible(true);

OMField<?> omField = (OMField<?>) parseField(f.getType(), value);
omField.setEntity(entity);
f.set(entity, omField);
}

private static Object parseField(Class<?> type, String value)
throws Exception {
if (ReflectUtil.isSubclass(type, OMField.class)) {
if (value.isEmpty()) {
return type.getConstructor().newInstance();
} else {
return type.getConstructor(String.class).newInstance(value);
}
} else {
return ReflectUtil.parseField(type, value);
}
}
  首先,将数据内容按“\0”进行分隔,“\0”这个字符因其特殊性被作为各属性值的分隔符;然后,对于每个属性值,则根据值是否为空,选择调用不同的构造函数实例化(见parseField(Class<?> type, String value));最后,将实例化的Field设置到Entity中。

[align=center][size=medium][b]5. Cache:缓存[/b][/size][/align]
  Cache的实现比较简单,它用ConcurrentHashMap<Long, Entity>保存了一个Entity的集合,包含add()、delete()等充缓存中增删Entity的方法,以及获取Enity的get(long id)方法。
  此外Cache有继承了OMThread,将在程序启动的同时启动一个清理Entity的线程:

protected void doRun() throws Exception {
Thread.sleep(Cfg.cache());
while (true) {
Thread.sleep(Cfg.cache());
clear();
}
}

private void clear() {
long deadLine = System.currentTimeMillis() - Cfg.cache();

Iterator<Entity> it = map.values().iterator();
while (it.hasNext()) {
if (it.next().touched() < deadLine) {
it.remove();
}
}
}
  清理的间隔时间可以在Cfg.cfg中进行配置。

[align=center][size=medium][b]6. Meta:元数据/数据索引[/b][/size][/align]
  Meta类保存了Entity在数据文件中的位置、长度和类型名称,并封装了一些操作meta据文件的方法。
  加载meta文件到内存的方法如下:

public static void readMeta() throws Exception {
long max = 1;

BufferedReader reader = FileUtil.reader(META_FILE);
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",", 2);
long id = Long.parseLong(parts[0]);
max = Math.max(max, id);

if (parts.length == 1) {
metaMap.remove(id);
} else {
metaMap.put(id, new Meta(parts[1]));
}
}

idCounter.set(max);
}
  meta数据时分行存储的,每行按“,”分隔,分别为id、position、size和className,内容大致如下:

1,0,1,Database
2,1,25,User
1,26,2,Database
2,28,26,User
1,54,3,Database
3,57,128,Text
4,185,367,Text
5,552,39,Article
1,591,5,Database
7,596,397,Text

  通过使用Meta,可以起到延迟加载Entity的目的,但目前的做法是程序一启动就加载所有的Meta,这可能仍然要占用较多空间,目前的思路是采用“多级Meta”或按一定策略延迟加载Meta;如何做到初始加载的数据量最小,同时又能保证读取时的效率,仍是一个难题。

[align=center][size=medium][b]7. 总结[/b][/size][/align]
  OMToolkit中的数据存储方式还比较初级,在后续版本中还需要持续修改。
  如何控制启动时加载的数据量?如何避免数据文件大小的膨胀?如何避免更新锁成为性能瓶颈?除此之外,Database不仅仅是读写数据而已,如何使得排序、筛选的功能更加高效便捷?这些都是需要考虑的。
  但是,直接在数据文件中读写Entity,而不是进行SQL解析,这个思路是不会改变的。这一切,都是为了是数据操作的部分与面向对象编程更加有效地结合。

  数据存储部分的讲解就到此为止了,接下来的一篇文章将对前面提到的Web Server、Web Framework、Database部分做一个总结;不会有太多的代码,而是计划以图形和原理的讲解为主。
  谢谢。(附件是目前最新的 OMSimpleBlog 和 OMToolkit 源码)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值