Google I/O大会GAE方面的笔记
2009 11 26 05:10 PM 2829次查看
分类:Google App Engine 标签:Google App Engine
无奈之下只好看以前下载的视频了,没想到基本都听懂了,所以记录一些笔记。还有些下载不了,只能看PPT了。
Building Scalable Web Applications with Google App Engine
- 写是很昂贵的:磁盘寻道时间是10ms,因此每秒只能有100次寻道。总时间取决于实体大小和形状,以及是否使用批处理。
读是很便宜的:读取一次后就会在内存中缓存,而内存读取1MB数据只要250微秒,每秒可读4GB;对于1MB的实体,每秒可进行4000次查询。 - Bigtable的访问就像是一个分布式的散列表。(就是key-value对)
Key的长度最大为500字节。(之前没注意到,原来和字符串一样) - ACID事务:Atomicity(原子性)、Consistency(一致性)、Isolation(孤立性)和Durability(持久性)。
- 在事务处理时,对实体组的一个实体加锁,会锁住整个实体组。对实体组的写操作将被串行化。(这就意味着不能并行改写实体组中的多个实体,根据我以前的理解,就是把一个实体组当成一个整体来处理。)
而(不在事务中)查询实体组中的实体,并不会串行化访问。 - 实体组过大会导致冲突率过高(竞争),比较好的做法是按用户来分组,因为一个用户不太可能在同一时间里进行多个事务。具体实现可以看blog的例子。
Under the Covers of the Google App Engine Datastore
- Bigtable的基本构成是:行、行名和列。
- 实体表是主表,保存了所有应用的所有实体。行名是key,而唯一的列是序列化的实体。
- 实体键基于父实体:父实体、子实体、孙实体…
例如:Grandparent:Ethel/Parent:Jane/Child:William。其中Grandparent、Parent和Child是类名,而实体的名字是key name。 - 索引将属性值映射到实体。
每行包括索引数据和实体键。(Key最大为500字节,难怪索引可能会比数据本身还大。) - 类型(kind)索引:用于查询一个指定的类型,索引数据是kind。
单属性(single-property)索引:用于对一个属性进行查询,索引数据是kind、property name(属性名)和property value。
复合(composite index)索引:用于对多个属性或祖先进行查询,索引数据是kind、ancestor(祖先)和property values。查询时先将kind加入前缀,接着是祖先,再是等于操作符,如果有不等于操作符,则转成范围扫描,最后加上排序。
合并集(merge join):这个和复合索引很相似,以至于我以为多个属性就是复合索引。实际上如果只有等于或祖先操作符,即使不用复合索引也能完成查询。只需分别对每个属性进行查询,再用key来求交集即可。 - 每个实体都有一个提交时间戳来标记最后一次成功提交的时间,用于事务处理。
实体组由根实体定义,由根实体和其后代组成。
实体组的所有实体共用根实体的提交时间戳(这就是一个整体的原因啊)。改写时先更改数据,再是时间戳,最后是索引。
Best Practices - Building a Production Quality Application on Google App Engine
- 捕捉运行时异常。(这个谁都知道,但很少人去做,例如执行超时、超过配额)
- 用log来进行审计和追踪。
- 改写handle_exception方法,将捕捉到的异常邮件发送给管理员。
Building Scalable, Complex Apps on App Engine(这个非常不错,建议看看)
- 一对多关系可以用list属性来实现,只需简单地将其放在list中,即可避免JOIN操作。
- List属性的性能:
索引的写操作是并行执行的,因此可以同时更新包含上千个条目的列表。
随条目数线性增长。
每个实体最多有5000个被索引的属性。
写时必须将其打包成一个序列化的protocol buffer,但写操作毕竟较少。
而查询必须解包所有的结果实体,如果条目数大于100,读操作将会非常昂贵。 - 要解决这个读的性能问题,在关系数据库中可以选择抓取的列,从而避免获取无用数据。
而Bigtable是做不到的,于是可以将这个实体分成2个实体,其中列表放在单独的实体里,并将2个实体放在同一个实体组,以保证其事务性。
查询时可以对列表进行key only查询,然后获取结果的父实体键集合,再用db.get(keys)来获得所有的父实体。
此外,如果列表条目太多,导致索引不够用,可以将列表实体继续分裂为多个,以满足增长需求。 - 为什么使用合并集(merge join)操作:无需额外索引,降低了写操作的代价。
- 合并集操作在扫描各个属性的索引时是并行执行的(使用zig-zag算法)。
查询时间随过滤器数量和结果集大小而增长。因此建议使用在查询少量结果集上(不超过100个)。 - 合并集的缺点:
重叠部分过多的话,匹配过程比较久。
不能使用复合索引,意味着不能排序,只能在内存中排序。因此不适合大结果集,但等于限制较多时,一般不会有太大的结果集。 - filter的顺序影响合并集的性能:
所以如果事先知道结果集的大概大小的话,先将结果集较少的等于操作符放前面比较好。(例如有10个叫ooxx的用户,有1万个男性用户,那么将用户名放在前比较合适。)
A Design for a Distributed Transaction Layer for Google App Engine
- 保证事务的正确性:
状态是持久的、原子性的和隔离的。
当需要改变时,事务将状态标记为“transaction”,改完后恢复“good”。
程序始终保持在“good”机器状态,只允许从“good”跳转到“good”。 - 事务算法:
服务器读取和记录版本号,缓存shadow对象的写操作。(这个shadow对象可以理解为一个本地镜像;而版本号是个重点,也是实现get_or_insert这种无对象而无法加锁的操作所必须的。)
以key顺序获取被改写对象的写锁。
检查读取对象的版本号,并检查它们是否被写锁定。
将shadow复制到本地事务中的用户对象,更新对象版本号,解除写锁和shadow。
如果遇到突发故障,由于写锁未解除,所以其他事务是无法读取该实体的;而回滚操作可以使该实体组状态恢复正常。 - 死锁检测:持有锁会创建一个等待图,如果这个图连成了一个圈,则表示没有进程可以继续。
死锁避免:按照与一个共享的总顺序(shared total order)一致的顺序来锁对象。(Google用的是对象的key的字符串顺序。)一个DT(distributed transaction的缩写,LT则表示local transaction)只会等待顺序中,比已持有的锁更靠后的锁。 - 前滚(roll forward):这个虽然不是从视频中学来的,但是提及到了,所以去查了下。
它和数据库的实现有关。事务会记录重做(redo)和撤销(undo)日志文件。
当改写日志文件时,数据文件可能没有立即被写入(一般会放在缓存中),此时如果数据库崩溃或掉电,数据文件就会不正确。恢复时就需要进行前滚操作,将重做日志文件中标记为已提交的部分写入数据文件。
前滚结束后,数据库就处于open状态了。此时可以正常访问已提交的数据,但未恢复的事务和未提交的数据仍然会被加锁而无法访问。若有进程访问这些加锁的数据,数据库就执行回滚(roll back)操作,从数据文件中撤销没有提交却被写入数据文件的数据。
我感觉这里是指事务提交失败了,事务会自动重新提交,这个过程称为前滚。 - 事务中不能多次读一个数据。因为写操作是被缓存的,只有提交后才能保证已写入。而多次写操作虽然也曾经是不允许的,但在最近的版本中已允许。
- 查询是返回索引中的数据,而索引不一定实时更新,因此不能用于事务;但Google仍将尽力让事务中可以使用查询。
Transactions Across Datacenters
- Bigtable的分布式应用使用了Paxos算法,有兴趣的可以去读读这篇文章。
虽然在延迟方面不甚完美,但可以保证一致性和不丢失数据。此外,Google也期待有人能给出更好的解决办法。
向下滚动可载入更多评论,或者点这里禁止自动加载。