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

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

1. 概述

  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文件的末尾追加值,而不会修改原来的数据。这样一来,这了两个文件就会越来越大了。后续版本将会提供数据清理的工具。
  另一个需要解决的问题是更新锁。即以更新为目的获取对象时,将加锁以防止其他处理过程对数据的更新。这是一个潜在的性能瓶颈。后续的版本将以一定的策略对多个更新进行合并,以避免加锁的操作。

2. DataUtil:数据操作类

  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移除。

3. FieldVisitor:属性访问类

  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的实现了。

4. FieldUtil:属性操作类

  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中。

5. Cache:缓存

  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中进行配置。

6. Meta:元数据/数据索引

  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;如何做到初始加载的数据量最小,同时又能保证读取时的效率,仍是一个难题。

7. 总结

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

  数据存储部分的讲解就到此为止了,接下来的一篇文章将对前面提到的Web Server、Web Framework、Database部分做一个总结;不会有太多的代码,而是计划以图形和原理的讲解为主。
  谢谢。(附件是目前最新的 OMSimpleBlog 和 OMToolkit 源码)

你可能感兴趣的:(java,sql,编程,cache,database)