2009年5月10日星期日

Google Web 工具包工作原理(copy from google)

产品概述

如今,编写网络应用程序是一个单调乏味且易于出错的过程。开发人员可能要花费 90% 的时间来处理浏览器行话。此外,构建、重复使用以及维护大量 JavaScript 代码库和 AJAX 组件可能困难且不可靠。Google Web 工具包 (GWT) 通过允许开发人员用 Java 编程语言快速构建和维护复杂但高性能的 JavaScript 前端应用程序来减轻该负担。

Google Web 工具包工作原理

有了 Google Web 工具包 (GWT),可以使用 Java 编程语言编写 AJAX 前端,然后 GWT 会交叉编译到优化的 JavaScript 中,而 JavaScript 可以自动在所有主要浏览器上运行。在开发过程中,您可以用 JavaScript 按习惯的相同“编辑 - 刷新 - 查看”循环快速反复,还有另一个好处就是能够调试和逐行单步调试 Java 代码。准备好进行部署后,GWT 会将 Java 源代码编译到优化且独立的 JavaScript 文件中。使用 Google Web 工具包可以轻松地为现有网页或整个应用程序构建一个 Widget。

使用 Java 语言编写 AJAX 应用程序,然后编译为优化的 JavaScript

与仅在文本级别运行的 JavaScript Minifier 不同,GWT 编译器会在整个 GWT 数据库中执行综合性静态分析和优化,通常生成的 JavaScript 加载和执行均比等效手写的 JavaScript 更快。例如,GWT 编译器可以安全地消除无用代码 -- 极大的减少不使用的类别、方法、字段甚至方法参数 -- 以确保您编译的脚本尽可能最小。另一个示例:GWT 编译器选择性地内联方法,消除方法调用的性能开销。

交叉编译提供了开发所需的可维护的提取和模块性,而不会导致运行时性能损失。了解详情

开发工作流程

编辑 Java 代码,然后立即查看更改而无需重新编译

在开发过程中,使用 GWT 的托管模式浏览器可以立即查看代码更改。无需汇编译为 JavaScript 或部署到服务器。只需进行更改,然后在托管模式浏览器中单击“刷新”。

使用 Java 调试器单步调试当前 AJAX 代码

在生产过程中,可以将代码编译为纯 JavaScript,但是在开发阶段,代码将在 Java 虚拟机作为字节码运行。这意味着,当代码执行处理鼠标事件等操作时,将获得功能完整的 Java 调试。Java 调试器可以执行的任何操作也应用于 GWT 代码,所以也可以执行断点和单步调试等自然操作。了解详情

编译和部署优化的、跨浏览器的 JavaScript

准备好进行部署后,GWT 会将 Java 代码编译成独立的纯 JavaScript 文件,任何网络服务器都支持该文件。此外,GWT 应用程序可自动支持 IE、Firefox、Mozilla、Safari 和 Opera,而无需在代码中进行浏览器检测或特殊封装。编写相同的代码后,GWT 会根据每个用户的特殊浏览器将其转换为最有效的 JavaScript。了解详情

功能

通过非常简单的 RPC 与服务器通信

GWT 支持一组开放的传输协议,例如 JSON 和 XML,但 GWT RPC 使所有 Java 通信都特别轻松且有效。类似于传统 Java RMI,只需创建一个用于指定您要调用的远程方法的接口。从浏览器调用远程方法时,GWT RPC 将自动串行化参数,并调用服务器上的适当方法,然后反串行化客户端代码的返回值。GWT RPC 也将非常成熟,其可以处理多态类层次结构、对象图循环,甚至可以跨网抛出异常。了解详情

根据用户个人资料优化 JavaScript 脚本下载

延时绑定是 GWT 的一种功能,可以生成许多版本的编译代码,而在运行时自引导期间仅其中一个版本需要由特殊客户端载入。每个版本均以浏览器为基础生成,并带有应用程序定义 或使用的任何其他轴。例如,如果要使用 GWT 的国际化模块来国际化应用程序,GWT 编译器可能会根据每个浏览器环境生成各个版本的应用程序,例如“英文版 Firefox”、“法文版 Firefox”、“英文版 Internet Explorer”等,因此,部署的 JavaScript 代码非常紧凑并且下载比在 JavaScript 中编码然后声明更快。了解详情

跨项目重复使用 UI 组件

通过合成其他 Widget 来创建可重复使用的 Widget,然后轻松地在面板中自动对他们进行布局。GWT 展示应用程序可以提供 GWT 中各种 UI 功能的概述。要在其他项目中重复使用 Widget 吗?只需将其打包以便他人在 JAR 文件中使用。了解详情

使用其他 JavaScript 库和本机 JavaScript 代码

如果 GWT 的类库不能满足您的需要,则可以使用 JavaScript 本地接口 (JSNI) 在 Java 源代码中加入手写的 JavaScript。使用 GWT 1.5,现在就可以为 GWT JavaScriptObject (JSO) 类创建子类以将 Java“类覆盖”创建到任意 JavaScript 对象上。因此,可以获得将 JS 对象比拟为适当的 Java 类型(例如代码完成、重构、内联)而无需另外占用内存或速度的好处。此功能可以优化使用 JSON 结构。了解详情

轻松支持浏览器的后退按钮和历史记录

不,AJAX 应用程序无需破坏浏览器的后退按钮。使用 GWT,您可以通过轻松地为浏览器的后退按钮历史记录添加状态,来使您的站点更加有用。了解详情

有效的本地化应用程序

使用 GWT 功能强大的延时绑定技术来轻松创建有效的国际化应用程序和库。此外,从 1.5 版起,标准 GWT Widget 开始支持双向性。了解详情

使用选择的开发工具提高生产力

由于 GWT 使用 Java,您可以使用所有喜欢的 Java 开发工具(EclipseIntelliJJProfilerJUnit) 来进行 AJAX 开发。这使网络开发人员可以控制自动化 Java 重构和代码提示/完成的生产效率。此外,Java 语言的静态类型检查使开发人员可以在编写代码时而非运行时找出一类 JavaScript 错误(输入错误、类型不匹配),在减少错误的同时提高生产率。没有临时变量发现的更多用户。最后,则可以利用基于 Java 的 OO 设计模式和提取,由于编译器优化,模式和提取易于理解和维护而无需用户承担任何运行时性能损失。

使用 JUnit 测试代码

GWT 与 JUnit 直接集成,使您可以在调试器和浏览器中进行单元测试,并且您甚至可以对异步 RPC 进行单元测试。了解详情

扩展或投稿 - Google Web 工具包是一种开源软件

使用 Apache 2.0 许可,可获取所有 GWT 代码。如果您对投稿感兴趣,请访问使 GWT 变得更好

2009年5月7日星期四

缓存,缓存算法,缓存框架


(原文:http://javalandscape.blogspot.com/2009/01/cachingcaching-algorithms-and-caching.html)


 缓存,缓存算法,缓存框架 第一部分 

介绍:

很多人听说过"缓存“这个词,当你问他们什么是缓存的时候,他们能给你一个完美的答案,但是他们并不知道”缓存"是怎么构建的,也不知道应该以哪种标准去进行缓存框架的选择。在这篇文章里,我们要说说缓存,缓存算法和缓存框架,并且比较一下哪个更好一点。


面试:


“缓存是一个临时存放数据的地方(这些数据我经常用到),因为获取原始的数据代价很高,所以缓存起来可以更快的获取数据。


这是面试中某程序员的回答。(一个月之前,他发了一封简历给一家公司,这家公司需要一个有丰富缓存、缓存框架、大规模数据处理经验的java程序员)


这个程序员以前用hashtable实现了一个缓存,hashtable是他唯一知道的缓存的知识,而他的hashtable存放着150条数据,这被他认为是大规模的数据:)

(caching == hashtable,把要查询的数据放在hashtable里面,这样就万无一失了)。下面来看看这个面试时怎么进行的。


面试官:很好,你依据什么标准来选择缓存系统。


某程序员:哈,(想了5分钟),嗯。。依据,依据数据吧。(咳嗽。。。)


面试官:什么!你刚才说什么?


某程序员:数据?!


面试官:哦,明白了。好吧,列出几个缓存算法,然后告诉我都是什么原理。


某程序员:无辜的望着面试官,脸上露出一些奇怪的表情,一些没人知道的非人类的表情。


面试官:好吧。我换种方式问。如果达到缓存容量上限,那么缓存系统该怎么做?


某程序员:上限?嗯。(想。。。hashtable对容量没有限制啊,我可以往里面随意添加数据,hashtable会自动扩展容量的)(我想这应该是那个程序员的没说出来的想法)

面试官对那个程序员说了声谢谢(这次面试持续了仅10分钟),然后一个女人过来说,哦,谢谢您大老远跑来,您回去等消息吧,我们会再联系您。慢走。

这是那个程序员最糟糕的一场面试。(他没有看到那个职位要求描述,要求上写着:要求求职者必须要用很强的缓存知识背景,而实际上他仅仅看到了那段“待遇优厚”的描述:))


说到做到

当那个程序员离开后,他想去了解面试者到底说写啥,那些问题该怎么回到。所以他去上网。那程序员根本就知道啥缓存知识,想着:当我要缓存的时候,我用hashtable。

当用最喜欢的搜索引擎搜索后,他找到了一篇非常好的解释缓存的文章,开始读了起来。


为什么我们需要缓存?

在没缓存概念之前的很长一段时间,用户去请求一个对象,这个对象从存储系统里取出来。随着对象越来越大(多),用户需要花费越来越多的时间。

这使存储系统负担加重,因为他必须满负荷运转。而用户和数据库都相当的恼火,有两种可能的结果:

1--用户受不了了,抱怨甚至不再使用这个软件(很常见的情况)

2--存储系统受不了。down了。这是一个相当大的问题(没地方存数据了)(发生几率比较小)


缓存系统是上帝的馈赠:

在60年代,IBM的研究者经过多年的研究,引入了一个新的概念,命名为"Cache"


什么是缓存?

“缓存是一个临时存放数据的一个地方(这些数据我经常需要),因为获取原始的数据代价很高,所以缓存起来我可以更快的获取数据。
缓存有一个记录池,这些记录是存放在存储系统(比如数据库)中的真实数据的拷贝。
缓存中的数据会被打个标签(关键字),以便获取。
好,这些程序员1已经知道了。但是他还不知道下面列出来缓存术语:


缓存命中率:

当客户端调用一个请求(我们假设他想看看产品信息),我们的程序响应了这个请求,程序需要访问存储系统(数据库)中的产品信息,首先它会去缓存检查

如果找到了一条与标签(比如说产品id)与所请求的数据匹配的记录,这条数据将被使用。这就是所说的缓存命中(缓存命中是衡量一个缓存系统有效性的主要指标,稍后我们会再谈谈)

缓存命中数占全部缓存请求的百分比,我们称之为命中率或者命中比。


缓存未命中:

相反,如果标签在缓存中没有找到(没有匹配的数据),这被叫做缓存没命中。这时候将向后端存储系统发送请求,取到数据后,它将被放在缓存中,将来的缓存请求可以找到,,并可以命中。

如果我们遇到一次缓存没命中,那么将可能出现以下两种情景:

情景1:缓存中有多余空间(缓存没有达到它的上限有多余空间),这种情况下,没有命中缓存的那个对象将从存储系统的取回来,并插入到缓存中。

情景2:缓存已经没有剩余空间了(缓存已经达到容量上限了),这种情况下,没有命中缓存的那个对象将从存储系统的取回来,然后我们将决定缓存中哪一个记录需要被删掉,以便有空间容纳我们刚创建的那个对象(就是我们刚获取的),这个是由替换策略(缓存算法)来做的,它决定哪个记录被删掉以便腾出空间来。下面我们还会谈谈这个。


存储成本

当缓存没有命中时,从后端存储系统中获取的数据将被加载放入缓存中。但是这些我们刚获取的数据在缓存中需要多大空间呢?这就是所谓的存储成本了。


检索成本:

当我们需要去加载数据的时候,我们必须知道取这些数据要多长时间。这就是所谓的检索成本。

失效:

当缓存中的对象被更新了,比如说在后端存储系统中。那么这个对象需要被更新,让缓存保持最新就是所谓的失效。

缓存中的数据失效后,再次从后端存储系统从获取,这样就可以得到一个更新了的版本了。


替换策略:

当缓存没命中时,缓存排除一些对象以便为之前没有缓存的数据腾出空间(当没有足够空间的情况下),用来选择去除记录的启发式方法我们称之为替换策略。


最佳替换策略:

理论上的最佳页面替换策略(也叫做OPT或者比雷迪最佳替换策略)是一个算法,它试图达到以下目标:当一个可缓存的对象需要放到缓存中,缓存算法应该替换那个

将在最长时间之后才会被用到的缓存记录。

举个例子:一个10秒钟内不会被用到缓存记录将被一个在2秒内会用到的记录所替代。想想,对于最佳替换策略,我们可以说是不可能达到的,但是一些算法依据启发,能够做到接近最优化策略,那么所有的算法都基于启发式知识,那么是什么让一种算法优于另一种算法呢?他们又是怎么利用他们的启发式知识的呢?


java街上的噩梦:

当程序员1读这篇文章的时候,他睡着了,并且做了个噩梦(从来没有过的噩梦)

程序员1:哇哈哈,我要让你失效。(发疯似的说)

缓存对象:不,请不要让我离开,他们还需要我,我还有孩子。

程序员1:所有的缓存记录在他们要被失效的时候都这么说哦,那你什么时候有的孩子?不管了,现在就给我永远的消失吧。

霍哈哈,程序员1狂笑着,沉默了几分钟后,一个警察突然打破了平静,警察逮住了程序员1,他被逮捕了,因为他让一个还在被客户端使用的缓存记录失效了。他被送进了监狱。

程序员1醒了,他非常害怕,他看看周围,意识到这不过是一场梦,他继续读着“缓存”,试图排除恐惧。



缓存算法:

没人能说自己的缓存算法比别人的都好。


最不常使用 (LFU):

我是“最不常使用”;我通过增加一个计数器来统计缓存记录的使用频率。

我把用的最少的记录删掉,首先我不是很快,在自适应行为(意思是使用模型或者换句话说请求模型来保留那些真正需要的记录,而抛弃那些最久之后才用到的记录)中表现也不是很好。


最近最少使用 (LRU):

我是“最近最少使用”。

我是最近最少使用缓存算法;我会首先把那些最近用得最少的数据抛弃掉,也就是那个最长时间里没用到的记录。

我需要记录下什么缓存项什么时候被用到了,这个代价很高,如果它想确保我总是抛弃那些最近最少使用的缓存项

浏览器把我用来做缓存。新记录被放到缓存最上面。当缓存到达它的上限时,我将被那些最下面抛弃掉。方法就是:只有是一个缓存项被访问了,我就把它放在最上面。

所以呢,最常用到的那些缓存项始终放在缓存里。有两种方式可以实现我,数组或者链表(这个把最少使用的放在尾,最常用的放在了头。)

我很快,而且我能自适应,换句话说我能针对根据数据访问模型进行调整,我有一个很大的家族,他们跟我竞争,他们甚至比我表现更好(有时候我很嫉妒,但是还好),他们中包括(LRU2和2Q)(他们都是用来改进LRU缓存算法的)


最近最少使用2(LRU2):

我是最近最少使用2,一些人叫我两次最近最少使用,我更喜欢这个叫法,在对象被第二次访问的时候,我会把它放到缓存中(对象需要被请求两次以便把它放到缓存中);当缓存满了的时候,我移除那些两次最近最少使用的记录。因为需要记录2次最近访问,访问的开销随着缓存的大小而增长。如果我被用在一个大容量的缓存中,这将会是一个问题,也会成为缺点。此外,我还必须记录那些没在缓存中的记录(他们还没被访问两次)。我比最近最少使用要好,我也能适用访问模型。

-Two Queues:

I
am Two Queues; I add entries to an LRU cache as they are accessed. If
an entry is accessed again, I move them to second, larger, LRU cache.

I
remove entries a so as to keep the first cache at about 1/3 the size of
the second. I provide the advantages of LRU2 while keeping cache access
overhead constant, rather than having it increase with cache size.
Which makes me better than LRU2 and I am also like my family, am
adaptive to access patterns.

Adaptive Replacement Cache (ARC):

I
am Adaptive Replacement Cache; some people say that I balance between
LRU and LFU, to improve combined result, well that’s not 100% true
actually I am made from 2 LRU lists, One list, say L1, contains entries
that have been seen only once “recently”, while the other list, say L2,
contains entries that have been seen at least twice “recently”.

The
items that have been seen twice within a short time have a low
inter-arrival rate, and, hence, are thought of as “high-frequency”.
Hence, we think of L1as capturing “recency” while L2 as capturing
“frequency” so most of people think I am a balance between LRU and LFU
but that is ok I am not angry form that.

I am considered one of
the best performance replacement algorithms, Self tuning algorithm and
low overhead replacement cache I also keep history of entries equal to
the size of the cache location; this is to remember the entries that
were removed and it allows me to see if a removed entry should have
stayed and we should have chosen another one to remove.(I really have
bad memory)And yes I am fast and adaptive.

Most Recently Used (MRU):

I
am most recently used, in contrast to LRU; I remove the most recently
used items first. You will ask me why for sure, well let me tell you
something when access is unpredictable, and determining the least most
recently used entry in the cache system is a high time complexity
operation, I am the best choice that’s why.

I am so common in
the database memory caches, whenever a cached record is used; I replace
it to the top of stack. And when there is no room the entry on the top
of the stack, guess what? I will replace the top most entry with the
new entry.

First in First out (FIFO):

I
am first in first out; I am a low-overhead algorithm I require little
effort for managing the cache entries. The idea is that I keep track of
all the cache entries in a queue, with the most recent entry at the
back, and the earliest entry in the front. When there e is no place and
an entry needs to be replaced, I will remove the entry at the front of
the queue (the oldest entry) and replaced with the current fetched
entry. I am fast but I am not adaptive


先进先出 (FIFO):

我是先进先出,我是个低消耗算法,只需要做一点点工作就管理好缓存记录。想法就是把缓存记录存在一个队列中。把最近使用的放在后面,把最久未使用的放在前面,当缓存没空间,记录需要被替换的时候,我就把队列前面的(最老的那个记录)删掉,并用刚获取的数据替换,我很快,但是不能自适应。


译者注:下面几种算法用的比较少,不翻译了。


-Second Chance:

Hello
I am second change I am a modified form of the FIFO replacement
algorithm, known as the Second chance replacement algorithm, I am
better than FIFO at little cost for the improvement. I work by looking
at the front of the queue as FIFO does, but instead of immediately
replacing the cache entry (the oldest one), i check to see if its
referenced bit is set(I use a bit that is used to tell me if this entry
is being used or requested before or no). If it is not set, I will
replace this entry. Otherwise, I will clear the referenced bit, and
then insert this entry at the back of the queue (as if it were a new
entry) I keep repeating this process. You can think of this as a
circular queue. Second time I encounter the same entry I cleared its
bit before, I will replace it as it now has its referenced bit cleared.
am better than FIFO in speed

-Clock:

I am
Clock and I am a more efficient version of FIFO than Second chance
because I don’t push the cached entries to the back of the list like
Second change do, but I perform the same general function as
Second-Chance.

I keep a circular list of the cached entries in
memory, with the "hand" (something like iterator) pointing to the
oldest entry in the list. When cache miss occurs and no empty place
exists, then I consult the R (referenced) bit at the hand's location to
know what I should do. If R is 0, then I will place the new entry at
the "hand" position, otherwise I will clear the R bit. Then, I will
increment the hand (iterator) and repeat the process until an entry is
replaced.I am faster even than second chance.

Simple time-based:

I
am simple time-based caching; I invalidate entries in the cache based
on absolute time periods. I add Items to the cache, and they remain in
the cache for a specific amount of time. I am fast but not adaptive for
access patterns

Extended time-based expiration:

I
am extended time based expiration cache, I invalidate the items in the
cache is based on relative time periods. I add Items the cache and they
remain in the cache until I invalidate them at certain points in time,
such as every five minutes, each day at 12.00.

Sliding time-based expiration:

I
am Sliding time-base expiration, I invalidate entries a in the cache by
specifying the amount of time the item is allowed to be idle in the
cache after last access time. after that time I will invalidate it . I
am fast but not adaptive for access patterns

Ok after we listened
to some replacement algorithms (famous ones) talking about themselves,
some other replacement algorithms take into consideration some other
criteria like:

Cost: if items have different costs, keep those items that are expensive to obtain, e.g. those that take a long time to get.

Size: If items have different sizes, the cache may want to discard a large item to store several smaller ones.

Time:
Some caches keep information that expires (e.g. a news cache, a DNS
cache, or a web browser cache). The computer may discard items because
they are expired. Depending on the size of the cache no further caching
algorithm to discard items may be necessary.

The E-mail!

After
programmer 1 did read the article he thought for a while and decided to
send a mail to the author of this caching article, he felt like he
heard the author name before but he couldn’t remember who this person
was but anyway he sent him mail asking him about what if he has a
distributed environment? How will the cache behave?

The author of
the caching article got his mail and ironically it was the man who
interviewed programmer 1 :D, The author replied and said :


分布式缓存系统:

*缓存的数据能存放在一个独立的缓存空间里,跟缓存目录(用来处理缓存记录。。)分开,能通过网络访问。.比如说磁盘。

*缓存分布允许增加缓存的大小。.

*这种情况下(译者注:增加缓存大小)获取数据的成本会增加,网络请求时间也会增加获取数据的成本。.

*这样也会使缓存命中率增加,因为有了更大的缓存。


但是分布式缓存怎么工作的呢?


假设我们有3台服务器,其中的两台被用来处理分布式缓存,第三台处理所有的请求(访问缓存的请求)。

Step
1:程序请求关键字entry1,entry2,entry3。在分析这些记录的hash值依据这些hash值,将决定把请求发往合适的缓存服务器。

Step 2: 主节点发送并行请求给所有相关的服务器(他们有我们要找的缓存记录)

Step 3: 缓存服务器返回结果给主节点(在第一个地方发送缓存请求的那个)

Step 4: 主节点发送响应给应用程序(缓存客户端)。

*如果缓存没有找到 (记录的hash值将被重定向发给服务器1或者2。这种情况下,我们要的记录在服务器1上找不到,那么它将从数据库中获取,然后加到服务器1的缓存列表中。




衡量缓存:

大部分的缓存依据命中率和与理论上的最佳算法来评价,这个通常通过产生一个没有真实数据的缓存关键字列表来做到。但是这里命中率的评价都假设所有的记录都有相同的检索成本,这往往不是实际情况,比如在Web缓存中,缓存能缓存的数据量比缓存命中率重要(比如,我能用10个小的记录来替换一个大的记录,在Web上,这往往更有效率)



结论:

我们已经看了缓存系统中一些流行的缓存算法,有一些基于时间,缓存对象大小。有一些基于使用频率。下一节我们将讲讲缓存框架,看看他们是怎么使用缓存算法的。敬请期待:)

相关文章:

Part 2 (Algorithm Implementation)

Part 3 (Algorithm Implementation)

Part 4 (Frameworks Comparison)

Part 5 (Frameworks Comparison)