Archive for the ‘ GAE ’ Category


在GAE/J中使用memcache

memcache其实也不算是一个很新的概念了,在各种大型网站中是运用的非常的多的。著名的Twitter的白鲸事件,也和memcache有着巨大的关系。想了解其背景概念的和八卦的,不妨Google一下。简而言之,memcache就是将那些经常需要查询的数据或者经常需要调用的外部文件,存在缓存之中,来减少数据库查询或者远程网络调用的浪费。这个概念在数据库的缓存中已经十分的悠久了,但是由于涉及到太多极低层次的代码编写,一般的程序员是不会接触到这的。现在有了memcache的帮助,能够很好的将这一思想移植到应用层面来,对大型项目的开发有着很大的帮助。

Google App Engine很早就有了memcache的支持,算是其天生的一个优势,在这里我简单的介绍一下如何在GAE/J中使用memcache。对Python感兴趣的同学就十分抱歉了。

首先我们假象一个例子,在GAE/J中有一个学校的学生系统,在一个有着4-5万人的大学里,如果该系统包括了所有的诸如选课,学习,注册,论坛等等各种信息,对于数据库的查询需求是非常的大的。不过,这种网虫又往往局限在部分学生之中,就比如学校邮箱,喜欢经常去check的和很少去check的都会存在。于是怎么能让那些经常使用的数据能够得到更好的利用呢?我们假定数据库中存有一个表,是Student表专门存放学生信息,学生无论是登录,还是做什么也好,都需要查询其Student信息。于是,我们来看memcache如何实现对Student的缓存。

首先我们生成一个JDO的POJO类,来存放学生信息,在这里简单开来:
[java]@PersistenceCapable(identityType = IdentityType.APPLICATION)

@Inheritance(customStrategy = “complete-table”)

public class Student implements Serializable{

@PrimaryKey

@Persistent

private String uuid;

@Persistent

private String name;

@Persistent

private String email;

@Persistent

private String address;

public Student(){

this.uuid = UUID.randomUUID().toString();

//setter&getter

}[/java]
接着,我们需要创立一个Cache类,来负责缓存层的操作:

[java]

public class QueryCache {

private static final Logger log = Logger.getLogger(QueryCache.class

.getName());

private static QueryCache instance;

private Cache cache;

private QueryCache(){

try{

CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();

cache = cacheFactory.createCache(Collections.emptyMap());

}catch(CacheException e){

log.severe(”Error in creating the cache”);

}

}

public static synchronized QueryCache getInstance(){

if(instance==null){

instance = new QueryCache();

}

return instance;

}

public void putInCache(String address, String student){

cache.put(address, student);

}

public String findInCache(String address){

if(cache.containsKey(address)){

return (String)cache.get(address);

}else{

return null;

}

}

}

[/java]

在这个类中,其单例模式下的构造函数生成了一个新的Cache,它的内部结构是一个Map。同时我们定义了两个方法,一个是将学生信息放进cache,一个是从cache中取出学生信息。

最后,我们创建一个Servlet,来进行学生信息的查询:

public class QueryServlet extends HttpServlet{
private static final Logger log = Logger.getLogger(QueryServlet.class.getName());
@Override
protected void doGet(HttpServletRequest req, HttpServletResposne resp) throws ServletException, IOException{
log.info(”Now start……”);
QueryCache cache = QueryCache.getInstance();
String studentC = cache.findInCache(”Address7694″);
if(studentC!=null){
resp.getWriter().write(”Found the item in cache!”);
}else{
resp.getWriter().write(”No hit in cache!”);
PersistenceManager pm = PMF.get().getPersistenceManager();
Query query = pm.newQuery(Student.class);
query.setFilter(”address==’Address7694′”);
List students = List query.execute();
if(students.iterator().hasNext()){
log.info(”Found one:”+student.toString());
resp.getWriter().write(”Found one:”+student.toString());
cache.putInCache(”Address7694″, student.toString());
}else{
log.info(”None found!”);
resp.getWriter().write(”None Found!”);
}
}
}

这样一个很简单的例子,显示出了整个memcache在GAE/J中的运用方法。当然这只是最最基础的,还有许多的东西需要思考,比如该在什么样的地方使用memcache,memcache的有效时间是多少等等。

几个GAE/J中的Transaction的概念

Transaction是在计算机很多领域里都需要的,最基础的知识相信大家都懂,那就是一个或一组操作,要么都成功,一旦有任何一个失败则所有的都失败,要回滚到原始位置。这个概念在GAE/J中一样。在datastore中,所有的写操作都被认为是atomic的,任何尝试着创建,更新或者删除实体的操作要么成功,要么就失败。一个操作有可能因为很多原因而失败,比如当许多用户同时尝试去修改这个值,或者该操作超过了其允许的配额值,当然也有可能是datastore的内部错误造成。在任何情况下的出错,都会使得所有操作就像没有发生一样而回滚。

实体组在GAE/J中是一个十分重要的概念。最开始的时候,我也是什么都想着往关系数据库上套,结果发现一来出现很多外键上的问题,二来出现很多包含关系的问题。个人认为,对于datastore而言,实体组是必须十分熟悉的内容。实体组之间的关系是以父子来称呼的,比如一个实体是国家,另一个实体是省份,那么显然一个国家会有很多省份而一个省份属于一个国家,于是我们可以将国家视作一个父实体,则通过每一个子实体都可以得到父实体也就是这个国家。当实体之间有了父子关系的时候,我们可以将他们视为在一个实体组中。对于一个没有父实体的实体而言,它就是一个根实体。一个实体的父实体是在该实体创建时就设定好了而不可以更改的。 处在同一个根实体下的所有实体都可以看作属于同一个实体组,被存在同一个datastore节点上。

那么实体组和Transaction的关系在哪呢?一个单一的Transaction只能对一个实体组进行操作,或者将另一个实体加入到该实体组中去。无论是查询,更新还是删除实体,如果一旦涉及到多个实体组,就会报错。同样的道理,在一个单一的Transaction中,不能涉及到多个实体有多个根节点的,因为一个根节点就等于代表了一个实体组。

如果在任何的tx.begin()和tx.commit()之间,有多个进程同时调用同样的实体组,那么JDO就会抛出JDODataStoreException或者JDOException异常,这是由java.util.ConcurrentModificationException造成的。解决的办法可以通过建立一个循环,多次进行重试来保证每个进程都能够成功。如果在一个进程之间,更新多个实体组,则会报JDOFatalUserException的异常。

Datastore的隔离级别在Transaction之外是最接近于READ_COMMITTED,在Transaction之内是SERIALIZABLE,特别指出是一种Snapshot Isolation。

我们在更新一个实体的部分值时需要使用Transaction,原因是我们在修改的同时可能也会有其他的进程在修改。另外一个需要使用的地方实在更新或者创建Key的时候,道理与上者类似,以避免多进程的同时操作。最后一个需要使用的地方是多个读操作发生的时候,为了保持数据的一致性而使用Transaction。

一个Transaction应该很快的进行,来减少发生冲突的机会从而减少重试的次数。尽可能的在Transaction之外把数据准备好,然后在Transaction内部进行执行需要保持数据一致性的操作。应该在Transaction之内来准备Key,和使用Key来获得实体。将datanucleus.appengine.autoCreateDatastoreTxns设置为false,则所有的Transaction都被禁用了。该文件的位置位于war/WEB-INF/classes/META-INF。

如果你的代码是以前根据关系数据库编写的,里面的Transaction极有可能是基于全局的,于是在GAE/J下必然会报错。一个好的解决办法是,首先禁用掉GAE/J中的所有的Transaction,然后一步步的操作来解决这个Transaction的问题。

在GAE/J上玩Web服务

Web服务是GAE的主要交流工具,其中的核心部分就是RESTlet。但是目前来说,市场上还有很多很多的Web服务是只提供SOAP服务的,比如我现在正在解决的关于调用GoGrid CDN的API,在CDN这一块,他们只提供SOAP。但是想要在GAE/J上调用SOAP简直就是不可能。因为对很多类的限制,JAX-WS等一系列的传统方式,都无法在GAE/J上运行。

有一个force.com的网站,提供了一个婉转的办法。他们提供了一个WSC的类,可以将下载下来的WSDL文件,自动生成一个JAR包其中包含所需要链接的connector和类,然后在这个类的基础之上来操纵web服务。有兴趣的同志们可以直接去看看。

但是我的问题更严重,因为GoGrid CDN的SOAP API是基于.Net的,而我是用的Java,这又是一条横岗。在GAE的Issue里已经提交了这个问题,希望Google早日支持这个库,不然的话我可不想从JAXB开始手写调用Web服务。

先暂且把这个Task放一放,Azure这个服务商提供的API是支持REST的,这样总可以解决一些问题了。

GAE-Java一些小的注意的地方

1. GAE的Eclipse在引用本地的library的时候,不会自动将其复制到WEB-INF/lib下去,结果就是如果选择直接上传到SERVER,程序会报找不到类的错。目前的开发人员正在改正这个问题,可能会在下一版得到解决。目前的最好解决办法,是首先将需要的类复制到WEB-INF/lib下,然后再引用。

2. 如果希望在本地调试程序的时候,可以查看datastore和queuetask的信息,可以通过http://localhost:8080/_ah/admin进入。里面提供了手动调控task的功能。

GAE-Java中JDO部分的KEY

发现有些部分的文档,看一次是绝对不可能看懂的。必须要过两天回头再来看,才发现一些端倪。比如这里要说的是JDO中类的KEY。

习惯了RDBMS开发的同志们,都会想到这还不是一个很简单的问题,无非就是选择一个唯一的可以代表该实例的作为ID就可以了。特别是目前开发中,大多数人喜欢用系统自动生成的ID序列来构造key。然而在GAE所支持的JDO中,我们会发现文档中提供了四种方式来实现KEY,不同的方法适用于不同的场合。

1. Long变量
这就是上面所说的最常见的一种,由系统自动生成的长整型变量来表示KEY。注意,这种选择只适用于那些没有父类的类。什么叫没有父类的类呢?例如有两个类,People和ContactInfo,在People类中,有一个属性就是ContantInfo,那么在这种情况下,People类就是有父类的类,而ContactInfo就是没有父类的类。

2.Unencoded String
和上面的Long变量一样,这个KEY的实现同样只适用于那些没有父类的类。但是在这里,它的值是由程序指定的,在这里使用的是没有编码的String类型。

3.Key
Key类是GAE-Java中专门提供的作为KEY的类型,它包含了可能有的父类信息以及一个系统自动分配或者程序指定的ID。也就是说,它是在包括父类信息的前提之下,选择以上两种选择之一来共同完成Key的任务。用KeyFactory的createKey()方法即可完成这一功能。

4.Key作为编码String
这种类型和Key类型是很相似的,只是将Key转化为来String来进行操作,方便使用。Key和encoded String之间可以用KeyFactory的keyToString()和stringToKey()方法互相转换。因为是String类型,所以如果想要得到ID的其中的内容,可以在声明了Key之后,附加声明其String或者Long类型的ID。@Extension(vendorName=”datanucleus”, key=”gae.pk-name或者gae.pk-id”,value=”true”)。同样,name类型的值是可以更改的,但是id类型的是自动生成无法变化的。

如何生成Key?
1. 对于没有父类的类型,我们可以使用createKey()静态方法来实现。它的参数包括createKey(String kind, String name)。其中kind就是指的要创造的类的类型,一般用Class.class.getSimpleName()来实现。第二个参数取决于使用的是自动生成的Long变量还是程序指定的String变量而定。

2,对于有父类的类型,我们同样可以使用createKey()的另一个重载方法来实现createKey(Key parent, String kind, long id)。在这里,我们首先实现父类的Key,然后将其包括在方法参数内即可。
不过在教程上,提供了另外一种方法来是实现,使用的是KeyFactory.Builder类。在这里首先通过构造函数,实现一个Key,然后不断调用addChild()方法,把子类附加上,最后通过getKey()方法得到Key。这种方法的好处在于可以产生一个链状的Builder,更加方便使用。比如在我们上面的People和ContactInfo例子之中,首先我们生成ContactInfo的Key,然后下一步附加上People的Key即可。

如何通过Key得到对象
一个很简单的方法,就是pm.getObjectById(class, key/id);如果你有的是key,第二个参数就用key;如果有的是id,第二个参数就用id。同时,你可以选择使用非类里面声明的类型,来作为第二个参数。比如在类里面使用的是Key,你可以通过调用name的String来获得。唯一的例外是String和Long之间的互相调用是不允许的。

几个在GAE的datastore方面不太理解的问题

乍一看上去,觉得GAE的确是给很好的东西,理念十分先进,虽然datastore看上去和关系数据库很相似,但实际在底层的设计上却是有着决然的不同。今天看来一天,发现来几个问题还是无法理解的,先mark出,以免忘记。

1. datastore的数据是不是永远存在的,也就是说google会不会主动去删除。问题一在,如果数据越增越多,会不会影响效率,当然在小范围内我们可以预见到是没有问题的,可过给十年八年呢?问题二,是不是每建一给数据类型,就会有一个对应的Entity类型出现,那也就是说如果我把以前的数据类型的某一项属性稍微更改一下,那么这个旧数据就相当于垃圾一样存在来datastore中?

2. Key这个玩意,感觉上和主键很相像,但是却有很多不同之处。比如GAE规定,Key只能有四种形式,Long, Unencoded String, Key和Key as Encoded String。对于这之间的区别实在是十分模糊。比如在关系数据库中,我们常用的是设立一个自增的主键,而在这边呢,到底是用哪一种呢?而且Key的String值还是自动随机生成的。

3. 数据在google那边到底是如何存储的,是杂乱无章的,还是按照某种顺序?也许就是直接按照Hash来进行存储的,但在获取的时候,如何才能提高效率呢?特别是当数据量非常之大的时候?

思考一下,有结果了再来继续。

另外加些

1. GAE的一些本地内容是存储在\war\WEB-INF\appengine-generated下面的,有两个。分别是datastore-indexes-auto.xml负责存储index信息,和 local-db.bin存储的是本地的数据。

  • English Version

    • Cannot read Chinese? Please take a look at my English site, hope you can find more you need there!
  • 感谢支持

  • twitter

    facebook

    linkedin

    • You are currently browsing the archives for the GAE category.

  • Categories