说说Java for Google App Engine(1) – 关于 DataStore 的设计
Google App Engine 的 Java 版本出来已经有一个月了。之前一直没有机会深入的看下去,只是申请了一个帐号。今天抽空,把GAE的架构设计,整个看了一遍,觉得要完全适应GAE的开发模式,或者把复杂应用迁移到GAE上的成本还是不小的。但是作为一个很有想法的架构设计,有许多值得参考的地方。
在Java for GAE中介绍最详细的,也最重要的涉及部分,就是对于数据库的操作。Java for GAE使用的是JDO的标准,对数据库进行操作。可以从这个项目隐约看到的是,后台一个强大的底层数据库支持。因为是基于POJO封装的,所以基本上不支持利用数据做关联查询(注:在JPA的说明中,明确提到了不支持Join。也不支持Group by, Having, sum ,avg, max, min 这些常见的语言),或者更加复杂的存储过程、触发器等操作。正明确说明了Google对于DataStore的态度——数据库就是存储为最终目标,而所有的计算都应该让Java来实现。Java for GAE仅仅对于自增量的ID索引,提供了一个简单的解决方案:
@PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key;
而在JDO的定义中,项目之间的关联,也被清楚地设置成为 立即加载的1对1,立即加载的1对多,非立即加载的1对1,非立即加载的1对多,非包含关系的1对1,非包含关系的1对多。让跨表的查询和关联查询,尽可能地在Java的语言层面有一个解决方案,而不是利用数据库的模式来解决。
以这种模式来推断,我们其实可以把GAE后端的数据库引擎看做是独立的若干个数据库表+索引。索引本身只是通过配制文件,让查询更加有效。但并不作为搜索关键字的依据。把索引作为一个独立的配制文件列出来,是一个聪明的做法。而避免了类似于Hibernate一样的配制文件,来约定数据库表的结构。但是有一个比较神奇的地方,是让人想不明白的。
那就是,GAE的后端数据库是怎么维护数据存储的呢?如果我在第一个版本上传了一些POJO的封装,而下一次我更改了这些POJO的规则,那原来的旧数据会被如何处置?这是一个需要尝试的问题。看到GAE的设计指标里面,只对一个数据库表的条目有1M的限制,而不限制表的总长度和表的数量。凭借这个依据,也许可以猜测到GAE对数据的存储是基于完全hash的。顶多,会根据POJO多定义一个读取数据表的规范。
这个规范让我想起了ROR的动态语言对数据库封装的设计思路。也许就是这样的,对于不存在的数值或者多出来的数值,GAE自有它的一套机制来给出缺省值。这保证对于任何的一个POJO,只要原始的存储中存在这个 hash( 表名 + 主键 ) 的实例,就能读出来。而索引的建立,到底是降序还是升序,那些字段增加索引,则是通过配制文件,建立另外一个 hash(表名 + 字段名 ) 来实现的。这种模式,就是典型的搜索引擎的逆向表的设计方法,对于GOOGLE来说,简直就是小菜一碟了。
对于数据库的描述。JDO可以说是一种最清爽的模式,而配合了外部的一个PersistenceManager的封装。并且给PersistenceManager增加了Transaction的功能,这也是一个Java的实现策略,而不是数据库的实现方案。
综合上面这些对于DB的限制,我们基本上可以看成,GAE后端的DataStore,真的只是一个数据仓库,唯一比Mencached强大的在于,它总算还是提供了一个排序和筛选的功能,支持Range,支持 >=, <= ,也支持 order by。这对数据库来说已经够了。在合理地筛选出来必要地数据之后,深度计算地工作。应该交给Java来做。
当然这么做也是有代价的。如果要统计已经有了多少条记录,原来的sql语言,只要count一下就可以了。而对于GAE的操作,也许需要把整个存储空间都翻一遍才可以。而为了避免这种翻一遍的事情发生,就需要在改动数据的时候,建立一个常数表。这么在Java对数据操作上,操作就麻烦了许多。