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中没有数据,则从数据文件中读取。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的修改互不影响。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移除。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进行操作的方法。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进行封装。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,并记录更新;然后对属性进行赋值。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的子类。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中。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中进行配置。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,Database2,1,25,User1,26,2,Database2,28,26,User1,54,3,Database3,57,128,Text4,185,367,Text5,552,39,Article1,591,5,Database7,596,397,Text