使用 Google App Engine 实现基于云计算的小型 Java 数据服务应用

Google App Engine for Java 是 2009 年 Google 推出的云计算服务。与 Amazon 的 EC2 比较起来,Google App Engine 提供了一个免费的初级版本,这对于广大的 Java 网站开发人员来说是很有吸引力。关于如何开始使用 App Engine,developerWorks 上已经提供了 Google App Engine for Java 系列文章,因此本文重点介绍 App Engine 上的开发数据应用所需的常见技术。Google App Engine 的数据存储是基于 Google 特有的 BigTable 技术,与常见的关系型数据库有所不同,因此使用 Google App Engine 也有别于常。考虑到 Google 提供的免费数据空间对于小型应用来说,还是绰绰有余的(目前是根据 API 的调用次数或数据查询来统计的,而免费的额度很高,一天的免费查询上限为 417,311,168 次,免费存储空间有 1G),所以还是值得开发人员来花一定的时间来学习掌握的。

使用 JDO/JPA 实现数据对象持久化

面向数据服务的应用关键在于一致、可靠的数据储存模型,在传统计算模型下,基于关系数据库的数据对象存储与查询一直是主流,而 App Engine 则采用基于 datastore 的数据对象存储与查询模型,来实现分布式架构下海量数据的管理及扩展。尽管 datastore 不是一个关系型数据库,但仍具有极强的一致性,使得 datastore 的接口具有与传统数据库一样的特征,从而在延续传统编程接口规范性和便利性的同时,获得了云端存储的高扩展性。

在 App Engine 提供的 Java SDK 中包含了 JDO 和 JPA 两种对象建模与持久化的 API 实现。JDO 是一个用于存取某种数据仓库中的对象的标准化 API,提供透明的对象存储;JPA 通过 Java annotation 或 XML 描述对象 - 关系表的映射关系(O/R Mapping),并将运行的实体对象持久化到数据库中。JDO 与 JPA 同时作为数据对象持久化 API,尽管在关系数据库的支持上功能有所重叠,但针对的领域是不同的,JDO 更注重提供通用底层数据格式的存储功能,如文件、XML、关系数据库甚至对象数据库。因此,本文将重点阐述 App Engine 的 JDO API 的使用方法。

设置 JDO

(1)拷贝 appengine-java-sdk/lib/user/orm/ 目录中的 JAR 包到应用程序的 war/WEB-INF/lib/ 目录,并确保 appengine-api.jar 也在该目录中。

(2)在应用程序的 war/WEB-INF/classes/META-INF/ 目录创建 jdoconfig.xml 配置文件,其内容如下:

<?xml version="1.0" encoding="utf-8"?> 
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig"> 

  <persistence-manager-factory name="transactions-optional"> 
    <property name="javax.jdo.PersistenceManagerFactoryClass"
    value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/> 
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/> 
        <property name="javax.jdo.option.NontransactionalRead" value="true"/> 
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/> 
        <property name="javax.jdo.option.RetainValues" value="true"/> 
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/> 
  </persistence-manager-factory> 
</jdoconfig>             
            

(3)设置数据读取策略和 datastore 调用期限。App Engine datastore 会复制所有的数据到多个存储位置,用以保证当其中一个出现故障时,datastore 能迅速的切换到其他位置并正常的访问数据。为了保证多个数据节点的一致性,应用程序会使用其中一个位置作为主位置(primary location),当主位置的数据更新时,其它位置的数据将被并行地改变。在许多情况下,短时间内返回一些数据格式比等待返回当前数据更为重要,因此当主位置出现故障时,您可以让 datastore 从其它位置读取数据(可能数据不是最新的),这时就需要设置读取策略(read policy)。App engine 提供两种策略:strong consistency 和 eventual consistency,其默认策略是 strong consistency。strong consistency 表示任何情况下都从主存储位置读取数据,而 eventual consistency 则表示当主位置不可用时,可从其它存储位置读取数据。其相应配置如下:

<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" /> 
<property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />

在默认情况下,对 datastore 的调用直到调用成功才会返回。如果调用请求的期限到了,运行环境将会终止此次调用请求。应用程序可以指定调用期限的具体时间,对于批量更新的请求,如果在提交过程中,限定的时间已到,那么到期之前的更新将会被持久化,而之后的更新将不会发生;对于一个事务处理,如果在事务提交过程中期限已到,那么整个事务将会回滚。其相应的配置如下:

<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

增强数据类

JDO 使用一种后置编译处理的增强步骤来实现数据类与 JDO 实现之间的关联。如果您使用 Eclipse,Google Plugin for Eclipse 工具会自动完成这个步骤;如果您使用 Apache Ant,可以使用如下命令对已经编译出来的 class 文件进行增强处理。

java -cp classpath com.google.appengine.tools.enhancer.Enhance class-files

需要注意的是,Classpath 必须包含 appengine-tools-api.jar 这个 JAR 包和所有的数据类文件。

获取 PersistenceManager 实例

应用程序与 JDO 进行交互是通过 PersistenceManager 类来实现的,您可以通过 PersistenceManagerFactory 来获取该类的实例。由于 PersistenceManagerFactory 是根据 JDO 配置来构造的,较为耗时,因此,一个应用应该共享一个单一的实例。我们可采用一个简单的单例模式类来对其进行封装,例如:

 import javax.jdo.JDOHelper; 
 import javax.jdo.PersistenceManagerFactory; 

 public final class PMF{ 
   private static final PersistenceManagerFactory pmfInstance= 
       JDOHelper.getPersistenceManagerFactory("transactions-optional"); 
   private PMF() {} 
   public static PersistenceManagerFactory get() { 
       return pmfInstance; 
   } 
 }                         

通过该工厂实例,就可以为每次的 datastore 访问请求创建 PersistenceManager 实例了,例如:

 import javax.jdo.PersistenceManager; 
 import javax.jdo.PersistenceManagerFactory; 
 import PMF; 
 // ... 
   PersistenceManager pm= PMF.get().getPersistenceManager(); 
   try { 
       // ... do stuff with pm ... 
   } finally { 
        pm.close(); 
   }                         

需要注意的是,在使用完 PersistenceManager 实例后,您需要调用 close() 方法,以确保不会在其它地方调用。

基于 JDO API 的 CRUD

(1)要将简单数据对象存储到数据存储区中,可以调用 PersistenceManager 的 makePersistent() 方法,将实例传递给该方法。对 makePersistent() 的调用时同步的,直到保存对象并更新索引后才会返回。要在 JDO 中保存多个对象,可以使用 makePersistentAll(...) 方法。

(2)要获取对象,最简单、直接的方法就是通过键(key),利用 PersistenceManager 的 getObjectById() 方法来获取。更加复杂的查询可通过 JDO 规范中的 JDOQL 来完成,例如通过 lastName 来查询 Employee 信息的查询可通过以下 JDOQL 完成。

 Query query = pm.newQuery("select from Employee " + 
"where lastName == lastNameParam " + 
"parameters String lastNameParam " + 
"order by hireDate desc"); 
 List<Employee> results = (List<Employee>) query.execute("Smith");

(3)要将对象从数据存储区中删除,可以使用 PersistenceManager 的 deletePersistent() 方法。

(4)使用 JDO 更新对象的一种方式是抓取对象,然后在返回该对象的 PersistenceManager 仍然处于打开状态的情况下对该对象进行修改。当关闭 PersistenceManager 时,修改部分会自动保留。

基于 YAML 描述文件以及 bulkloader 实现大批量结构化数据导入 / 导出

大批量数据导入,导出是常见的数据服务操作。目前,对于 AppEngine for Java 的数据上传,还不是非常简便,需要以下四个步骤: RemoteApiSelvlet 的 web.xml 的例子。

(1)即使是 AppEngine for Java,在批量数据下载和上传方面,还是依赖于 AppEngine for Python SDK 的若干 Python 脚本,如 bulkloader.py 或是 appcfg.py 等等。因而,用户还是需要安装 AppEngine for Python SDK 在开发机上。

(2)在 AppEngine for Java 的 google 服务器端,需要部署 com.google.apphosting.utils.remorteapi.RemorteApiServlet,该 Servlet 会与安装在 AppEgnine for Python SDK 的开发机交互,来实现数据从开发机到服务器端的数据传输。下面是一个部署 RemoteApiSelvlet 的 web.xml 的例子。

 <servlet> 
      <servlet-name>remoteapi</servlet-name> 
      <servlet-class> 
        com.google.apphosting.utils.remoteapi.RemoteApiServlet 
      </servlet-class> 
 </servlet> 
 <servlet-mapping> 
      <servlet-name>remoteapi</servlet-name> 
      <url-pattern>/remote_api</url-pattern> 
 </servlet-mapping> 
 <security-constraint> 
      <web-resource-collection> 
            <web-resource-name>remoteapi</web-resource-name> 
            <url-pattern>/remote_api</url-pattern> 
       </web-resource-collection> 
       <auth-constraint> 
              <role-name>admin</role-name> 
       </auth-constraint> 
 </security-constraint>

(3)获取当前的 AppEngine for Java 服务器所用的数据表的 Schema,写成 yaml 格式。以下面的命令为例,该命令将 <my_app_id> 所使用的 DB schema 全部下载到 bulkloader.yaml 内。用户可以编辑该描述文件,上传后以实现对 DB schema 的修改。

 appcfg.py create_bulkloader_config --filename=bulkloader.yaml 
  --url http://<my_app_id>.appspot.com/remote_api

(4)下面是一个 DB Schema 的实例,该实例的含义是,数据表名为 Person,主键是 email,还包含属性 name, surname,文件传输格式为 CSV。

 ------------------------------------------------------------ 
 transformers: 
 - kind: Person 
  connector: CSV 
  property_map: 
    - property: __key__ 
      external_name: email 
      export_transform: transform.key_id_or_name_as_string 
    - property: name 
      external_name: name 
    - property: surname 
      external_name: prenom 
 ------------------------------------------------------------

(5)上述步骤完成以后,即可使用 appcfg.py 来实现数据的上传下载。如下例所示,<filename> 是用于包含实际数据的 CSV 文件。

 appcfg.py download_data --config_file=bulkloader.yaml 
             --filename=<filename> 
             --kind=Person 
    --url http://<my_app_id>.appspot.com/remote_api 

 appcfg.py upload_data --config_file=bulkloader.yaml 
                      --filename=<filename> 
                      --kind=Person 
      --url http://supershajiawang.appspot.com/remote_api 
      --batch_size=200 
      --num_threads=5

使用 Blobstore 实现非结构化数据存储

基于 Web 的数据应用常会遇到非结构化数据存储的需求,如文件、图片或视频的管理。App engine 采用 Blobstore API 来管理这些不易被 datastore 管理的大尺寸(总配额为 2GB)数据对象(Blobs)。一般来说,应用不会直接创建 blobs 数据,而是由提交的 Web 表单或者 HTTP POST 请求间接创建的。

Blobs 在创建之后能够被删除,但不能被修改。每一个 blob 在 datastore 中都有相对应的记录信息,如创建时间、内容类型。可通过 blob key 来查询相关的记录和属性信息。Blobstore 的具体应用过程如下:

上传 Blob

(1)编写上传 JSP 页面,并且设置 blobstoreService.createUploadUrl() 作为表单的 action。例如:

 <body> 
     <form action="<%= blobstoreService.createUploadUrl("/upload") %>" 
                     method="post" enctype="multipart/form-data"> 
         <input type="file" name="myFile"> 
         <input type="submit" value="Submit"> 
     </form> 
 </body>         

(2)编写 Blob Handler Servlet,存储 Blob key。当用户提交表单并触发 Handler 被调用时,blob 就已被 App engine 保存并将相关信息添加 datastore。如果应用程序不想保存 blob,应立即删除,避免其成为不能被引用到的孤立数据,浪费应用空间。Handler 的主要处理逻辑如下:

 Map<String, BlobKey> blobs = blobstoreService.getUploadedBlobs(req); 
 BlobKey blobKey = blobs.get("myFile"); 
 if (blobKey == null) { 
     res.sendRedirect("/"); 
 } else { 
     res.sendRedirect("/serve?blob-key=" + blobKey.getKeyString()); 
 }             

如果不明确指定上传文件的 content type,Blobstore 将会从文件的后缀名推断出 content type。如果 content type 不能确定下来,新创建的 blob 将被赋予 application/octet-stream 类型。

下载 Blob

获取 blob key,编写 Download Handler Servlet。这个 Handler 必须将 blob key 传递给 blobstoreService.serve(blobKey, response) 函数。下面的例子中,blob key 是通过 URL 的参数来传递的。在实际应用中,Download Handler 可以以任何您需要的方式获取 blob key。不过每次从 Blobstore 读取的数据大小被被限制为 1MB。

 public void doGet(HttpServletRequest req, HttpServletResponse res) 
 throws IOException { 
     BlobKey blobKey = new BlobKey(req.getParameter("blob-key")); 
     blobstoreService.serve(blobKey, res); 
 }             

基于 JCache 提高数据存取性能

高性能、可扩展性是 Web 应用的普遍设计目标,而采用分布式内存数据缓存,并部署于健壮的数据持久层之上,是实现该目标的可靠方法。App Engine 也包含了内存缓存服务,并支持 JCache 缓存接口。

JCache 提供一个类似于 Map 的接口用于缓存数据,并通过键(keys)来存取键值,键和值可以是任何可序列化的数据类型和类。创建并使用 JCache API 的示例代码如下:

 import java.util.Collections; 
 import net.sf.jsr107.Cache; 
 import net.sf.jsr107.CacheException; 
 import net.sf.jsr107.CacheManager; 

 // ... 
 Cache cache; 

 try { 
     cache = CacheManager.getInstance() 
             .getCacheFactory().createCache(Collections.emptyMap()); 
 } catch (CacheException e) { 
 // ... 
 } 

 String key; // ... 
 byte[] value; // ... 
 // Put the value into the cache. 
 cache.put(key, value); 
 // Get the value from the cache. 
 value = (byte[]) cache.get(key);             

使用缓存的一个主要目的是为了提升 datastore 查询的速度。如果 Web 应用中的某些基础数据在整个程序的生命周期中变化不大且使用频繁的话,那么将其放入缓存中可大大提高数据访问速度。当应用请求一些会话数据或用户信息时,可先检查 memcache 中是否存在,如果不存在或者过期,再去执行 datastore 查询,可提高访问效率。需要注意的是,App Engine 中的缓存大小是有配额限制的,单个应用的最大限额为 1MB。

GQL 的使用及注意事项

GQL 就是 Google 开发的类 SQL。主要用于对 GAE 存储的数据进行查询和检索。和标准的 SQL 相比,GQL 实现的功能非常有限。其实只相当于 SQL 中 select 语句的部分功能。开发人员可以在 Python 程序中直接调用 GQL,或者在 GAE 数据存储管理界面里运行 GQL 命令行。下面,主要介绍一下 GQL 的语法,使用中的注意事项和一些典型的应用案例。用户也可以查看 GQL Reference Book来了解更多细节。

GQL 的语法可以表示为:

 SELECT * FROM <kind> 
  [WHERE <condition> [AND <condition> ...]]    
  [ORDER BY <property> [ASC | DESC] 
  [, <property> [ASC | DESC] ...]]    
  [LIMIT [<offset>,]<count>]    
  [OFFSET <offset>] 

 <condition> := <property> {< | <= | > | >= | = | != } <value> 

 <condition> := <property> IN <list> 

 <condition> := ANCESTOR IS <entity or key>

GQL 关键字 SELECT, FROM, 等不区分大小写。其它部分,如条件等,区分大小写。字符串应用单引号标出。和标准 SQL 类似 SELECT 关键字后面跟上选取字段,但是 GQL 只能是全部字 段(*)或者主键 (__key__)。FROM 后面用来指定查询的对象,即 model 里定义的类名字。WHERE 关键字用来给定查询的条件,只能通过 AND 来组合多个条件。排序语句用来指定返回结果的顺序。ASC 为升序,DESC 为降序。如果指定了多个属性用来排序,该对象必须创建相应的索引。索引的 定义必须和排序的属性及顺序一致。否则系统会抛出有关索引不存在的异常。增加索引十分简单,只需手工编辑 index.yml 文件,再上传即可。但是索引的 建立需要一定时间,一般几个小时之后就可以使用新的索引了。LIMIT 用来表明返回结果的个数,最多为 1000,所以指定超过 1000 的 LIMIT 是不起 作用的。OFFSET 用来指定返回结果开始的位移。LIMIT 和 OFFSET 的设计是标准 SQL 不具备的,它们更适用网站大量数据查询及分页处理的需求。

GQL 的查询条件也有很多限制。首先,GQL 的查询条件不能超过 30 个。对于 IN 和 !=,这点容易忽视。因为系统会将它们分解为多个单独的查询条件。例如 IN 后 面列表的每个元素都会被分裂为一个条件。这里还需要指出的一点是,对于列表性质的字段,例如 StringListProperty,判断一个值是否在列表 内,请用 =,而不是 IN 来判断。例如 Product.tag = ‘ tv ’表示查询包含 tv 的产品标签。GQL 对于查询条件的不等式运算也有限制。如果出现一个以上不等式条件,相关的属性只能是同一个,例如: age >= 10 AND age <=20,而不能是 age >=10 AND height >= 6。

GQL 语句除了在 Python 程序中使用外,还可以在应用控制台的数据查看页面中使用。进入应用控制台后,选取数据存储查看。然后选择要操作的对象,点上使用 GQL,就可以在下面的输入框中输入 GQL 查询了。以下是一些常用的查询语句:

  • 查询名字为 Lin 或者 lin 的用户:SELECT * FROM Customer where name in ('lin','Lin')
  • 按照键值查询:SELECT * FROM Customer WHERE __key__ = KEY('agthbGlzb2Z0ZGVtb3IPCxIIV2lzaExpc3QY6gcM')
  • 查询 abc 开头的 prop 纪录:db.GqlQuery("SELECT * FROM MyModel WHERE prop >= :1 AND prop < :2", "abc", u"abc" + u"\ufffd")

结论

综上所述,Google AppEngine 目前提供的数据服务,无论是经济性,可靠性都有着传统网络数据服务所不能比拟的优势。这是依赖于 Google 特有的 BigTable 的技术特点决定的。然而,从使用的便捷性来讲,AppEngine 还是有一些遗憾的。例如,用户很难实时获得数据库的具体大小信息,并且,目前批量的导入,导出操作也是比较不便的。而且,现有的服务并不太适用于非结构化数据,如影音资料的存储。这就有待于 Google Stroage Service 来解决这一问题了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值