2010年6月19日星期六

2010/6/20 一种 Database+程序 的ID生成策略

特点:持久化、通用性、性能好、多进程共用。
这种实现保证了id递增的唯一性,并且多个VM同时使用同一个Database也没问题,vm突然强制关闭也没问题。

方法概述:采用 Database+VM(表示一个程序)结合的策略生成ID,生成的ID为递增的整数。用Database做id生成的持久层,采用vm进行分段递增。由程序负责一个段内的递增,vm每次开始分配id前先向数据库申请一段可用id。数据库记录上次分配出去的id最大值。

具体方法:数据库中建一张表,表中有一个长整数的Id字段,负责记录已分配出的ID最大值。然后程序在开始生成id前,先向数据库申请一段id,获得这段id的起始值和最大值,然后程序自己就可以在这段范围内采用原子递增方式分配id了。id段用完了就再次向数据库申请。申请的id段的大小由程序自己指定,大小适中即可。

申请的过程就是get and set,这个过程要保证数据一致。不一定采用事物的方式来保证,因为每个程序可以分配一段,所以冲突的可能性小,属于乐观的情况,所以可以采用非事物的方式如果不一致重试几次即可。

所谓ID,即唯一标识符,所以ID生成器的实现目标就是要保证每次分配的id唯一。
既然id生成过程是在程序中完成的,如果程序退出或强制关闭了,那么下次程序启动时会再次向数据库申请新的id段,因而id唯一性是有保证的,但不保证id一定是连续的,程序退出时可能会浪费掉少量段内还没分配完的id。

特点:
  1. 持久化:使用数据库记录上次分配出的最大id。一般而言,如果仅使用程序自身生成id,只能保证程序一次性运行中生成的id可靠,下次再运行就不成了。除了UUID等特殊算法外,一般ID生成策略是需要持久化一些数据的,而持久化的方法无疑最好是使用数据库。
  2. 通用性:只是自己在数据库新建一个简单的表,其它工作都有程序完成,因而不依赖于特定数据库的主键或Sequence生成方式,具有很好的可移植性。(不依赖于特定数据库带来的好处是可移植性,坏处就是这部分工作转给了程序自己实现)
  3. 性能好:因为不是每生成一个id都要更新数据库,而是在程序中完成的,所以效率高,性能好。
  4. 多进程共用:多个程序同时使用一张数据表也没问题,只要保证vm向数据库申请id段的一致性即可,由数据库做中介。

上面描述的方法只是利用了数据库表中的一条记录维护一个递增的ID,可以使用多条记录维护多种不相关的ID,供系统中的多处使用。

这个实现方法是在 Openfire 中使用的 ID 生成方法,具体实现见org.jivesoftware.database.SequenceManager。
表结构:
CREATE TABLE ofID (
  idType                INTEGER         NOT NULL,
  id                    BIGINT          NOT NULL,
  PRIMARY KEY (idType)
);

2010/6/9 Ajax的同步调用场景——使用同步Ajax在 onunload 事件时通知服务器

在一种场景下,server端维护了浏览器客户端的状态信息,当浏览器关闭时需要立刻通知server用户已经离开了,server端好清理状态。这种情况的典型案例就是:浏览器客户端的即时通信,即web IM。因为用户关闭了窗口就表明用户已经不在线了,所有对话都结束了,server端和对话的另一方需要立刻知道这个状态变化。而一般情况下,server端并不需要立刻通知的情况,那就无所谓了,只要过期超时即可。

要想在用户关闭浏览器后,server端得到通知,一般有两种做法:
方法一:通过 javascript 轮询的方式向 server 发送心跳(使用异步ajax),server端记下上次心跳时间,server端Timer定期检查会话的心跳时间是否超时来判断用户是否已经离开了。这种方法的缺点是,要想即时通知就得把心跳间隔时间设得短,server端检查的时间也要短,这样会导致server端的负担大大增加,所能承载的用户就会较少。
方法二:不采用发送心跳的方式,而是在用户关闭浏览器时,通知server即可。这样server端的负担就小了。
window.onunload = function(){
ajax.callServer();
}

重点说一下方法二的实现。
首先,页面上注册 onunload 事件。如 body元素的 onunload 属性,或直接设置 window.onunload 。
注意是 onunload 而不是 onbeforeunload 事件,因为用户可以在 onbeforeunload 事件时选择“取消”从而不关闭窗口,而onunload 事件意味着窗口肯定是要被关闭的。

其次,在onunload事件中调用同步的ajax请求通知server,而非默认的异步ajax请求。因为调用异步的ajax请求在一些浏览器下无法真正使server得到通知,实际上根本无法保障!在发出了这个异步请求之后,浏览器就要关闭窗口并清理所有相关资源,因为这是异步请求,所以浏览器有理由认为无需等待该请求完成即可开始关闭窗口,这样该请求就可能失败(IE上就是这样,用异步请求无法通知到server)。这也解释了为什么异步callServer()调用后面再加个alert语句就没问题了,因为alert语句在后面挡着,需要人来点确认,所以可以完成对server的请求。而用同步的ajax请求则浏览器会等待该请求完成后才关闭窗口清理资源,因而就不会有这个问题。

这就是一种使用同步ajax的场景,ajax中第一个a指的是 asynchronous 异步,当初引入ajax的主要目的就是引入一种异步执行的方法,绝大部分情况下应该使用异步方式执行ajax。同步ajax也许应该叫做 sjax ( s 是 synchronously)。
XMLHttpRequest 的 open 方法中的第三参数表示是同步还是异步,xmlHttp.open("GET", url, true); 各种支持Ajax的框架,如prototype、ext、dwr等肯定都有选项设置是否为异步模式。

最后,实际情况的做法是要结合方法一和方法二。方法二由于各种原因(比如用户电脑突然停电、浏览器崩溃等,这你总没办法吧),总是难免浏览器在关闭时没有成功的通知server。尽管是少数情况,但server端是要保证长期运行稳定的,要避免资源泄漏,所以必然server端还是需要定期检查会话是否过期的。但因为已经保证了大部分情况下server得到了通知,所以server端的定期检查可以时间长一些,心跳间隔也可以时间长一些(但要小于server检查的时间间隔),因而对server也不造成什么负担。

相关参考资料:
Making AJAX calls onUnload: http://www.livelearncode.com/archives/11

2010/5/25 Eclipse 中 drop to frame 的调试技巧

前些天和同事交流调试技巧时,知道了 Eclipse debug 时有个 drop to frame 的技巧。这是我以前不知道的,自己又查了一下这个功能的含义。官方的解释是:

Select the Drop to Frame command [ Drop to Frame ] to re-enter the selected stack frame in the Debug View.

Note this command is only available if the current VM supports drop to frame and the selected stackframe is not the top frame or a frame in a native method.

就是说,这个功能可以重新跳到当前方法的开始处重新执行,并且所有上下文变量的值也回到那个时候。不一定是当前方法,可以点击当前调用栈中的任何一个frame跳到那里(除了最开始的那个frame)。主要用途是所有变量状态快速恢复到方法开始时候的样子重新执行一遍,即可以一遍又一遍地在那个你关注的上下文中进行多次调试(结合改变变量值等其它功能),而不用重来一遍调试到哪里了。当然,原来执行过程中产生的副作用是不可逆的(比如你往数据库中插入了一条记录)。
其实这个功能早就有了,就是一般人不知道或很少使用。

2010/5/24 收到联通寄来的0元发票

你们见过面额为0元的发票吗?反正我以前没见过,但上周真的见到了,中国联通开的。

上周帮人在联通网上营业厅买了个3g手机号,只买号不要钱,并且免费寄送给你。在下单的时候,可以填写发票抬头,我一看金额为0,觉得很有意思,没花钱也给开发票?结果在收到东西的时候还真有这个0元的发票。

根据自己的个人经验,开发票一般就有点麻烦,更何况是没花钱还要让人给你开发票。我猜可能联通规定,只要办了业务就开对应的发票,不管金额多少。

2010/5/20 抱着做产品的心态做开发

我这半年主要在负责做一个产品。
有几个不错的同类产品,同事发了那个产品的反馈意见的论坛链接。里面有很多人提出的建议。

我在邮件的回复了下面这些内容:
只有当你的产品对别人有用,并且有很多人用的时候,你才会收到很多别人给你的建议。所以先参考一下这个比较实际一些。

我浏览了一下第一页的标题,从别人的建议中,我发现其实都不是什么技术上的难题,我相信他们和我们还有其它同类产品都可以解决这些问题。从技术人的角度看,往往喜欢分析技术本身是不是够难,说这个我们能做、那个也可以做、这个地方只要如何如何就实现了。其实,我想对开发人员说,技术上一旦确定了架构,能力就已经确定了,这个架构提供了基本模型,并且子模块都是可扩展的,满足各种未来的扩展需要。所以,我明确告诉大家技术上的问题都可以解决。现在是能不能做出一个好产品的问题。这要求大家非常注重产品的每一个细节,精益求精,多从使用者的角度出发理解各种业务功能而不是从技术实现的角度,提升产品的品质和价值。从根上说,就是大家要抱着一种做产品的心态来开发,你敢自豪地向别人说这个产品是我做的。内核虽是以开源项目为基础的,但是产品是我们自己做的!任何人都可以拿这个核来做产品,而大家工作的真正意义在于所有人都拿这个核做产品的情况下我们做出来的产品是最好的,这是我们存在的意义。比较直观的一个类比是,很多浏览器都以IE或Webkit为内核的,比如chrome、傲游、搜狗等浏览器,浏览器才是最终用户使用的产品,内核只有那么少数几个,但浏览器确非常多。你想想既然傲游是以IE为内核的,但为什么在国内用傲游的甚至比用IE的人还多?

2010/5/13 Javascript实现输入内容变化的监控通知

目前做个即时通讯的web客户端,类似于Web QQ。我们使用qq、msn时,经常会看到“对方正在打字”的提示,这样就知道对方正在输入一些东西。实现就是当用户做出一些改变的时候,向对方发送一个通知信息。至于如何定义什么时候发这个通知,则由客户端实现自己决定。一般就是监测用户的键盘事件或输入内容的变化。

实现目标:当用户在输入框中输入或改变了一些内容时,发送一个通知。
起初的简单实现:监听浏览器中输入框的 keypress 事件。因为不应该每一个改变都应该立刻发送通知,那样过于频繁了,所以设置一个计数器counter,counter大于一个值(比如3)才是发送通知,再把counter设为0。

除了常规输入(输入字符和数字)外,用户按下Backspace和Delete键时会怎样?自己测试时发现一些问题:
keypress 事件在按下非字符键时,有些键会触发,有些键不会触发,而且具体随浏览器还不同。比如在ie中,仅字符键会触发keypress,那就是说你在IE上按Backspace删除了很多内容时,也不会发通知,因为没有触发keypress事件,而我的目标是应该发送通知。但在 Firefox 上就会触发keypress事件,也就是会发送通知。
一种解决方式是,你需要在specialKey中单独监控Backspace键,就可以在不同浏览器上都监控好。
但是我发现同样的方法监控不到Delete键。

后来又发现中文输入法也会干扰正常的事件,影响我目标的实现。
在输入中文时,在输入法窗口中显示了很多字,但只会产生一个keypress事件,这是正常的。但这样我原来的简单实现就没有很好地达到我的目的。而这时不应该仅靠事件触发的次数,还应该看每次事件时,字数变化的多少。

而且不同的输入法的兼容性和不同的浏览器实现都会有影响。
比如在IE上,使用谷歌拼音输入法,按数字键或输入中文,虽然都正常输入了,但都不会产生事件,而用智能abc输入中文没有事件,输入数字有事件;
而在Firefox上,使用谷歌拼音输入法,事件都是正常的。

小结:不同浏览器的键盘事件的处理有差异,同时中文输入法事件也有影响,并且还是二者交错的,比较复杂。
可以采用另一种方法绕开浏览器事件差异、中文输入和输入法兼容的影响,而实现我们原本的目的:
在浏览器上采用 timer 的方式,比如每隔0.5秒检查一下输入框中的内容变化,只根据内容的变化情况决定是否发送通知(据说Google搜索的自动补全就是这样做的)。
比如记录上次发送通知时输入框的内容(lastInput),每隔0.5秒查看当前内容(currentInput)与 lastInput 的长度之差的绝对值是否大于3。

参考资料:
JavaScript Madness: Keyboard Events:http://unixpapa.com/js/key.html
从谷歌的一个Bug说起,谈谈键盘事件的兼容性:http://www.javaeye.com/topic/233038

2010/4/15 XMPP服务器的SRV DNS配置

对XMPP客户端而言,首先查询 DNS 中的 SRV 记录如:_xmpp-client._TCP.example.com 。从而获得 xmpp
 server 的 hostname 和 port,然后使用这个进行连接。如果没有找到 SRV 记录,则会直接使用该 domain 和 默认端口进行连接。所以需要在DNS中设置 SRV 记录。

比如在Windows上可以使用 nslookup 命令查询 srv 的配置(linux就用 dig 命令):
nslookup -qt=SRV _xmpp-client._TCP.gmail.com
得到的是 Google Talk 服务的SRV配置,Google Talk 使用的xmpp hostname 是 talk.google.com。 

参考资料:
XMPP RFC 3920 中的关于 SRV 的说明 http://xmpp.org/rfcs/rfc3920.html#rfc.section.14.3
如果用 Smack api 作为xmpp client api,smack 中使用的是 org.jivesoftware.smack.util.DNSUtil 的方法按照上述原则执行查找的。

XMPP服务器的SRV DNS配置 - hgk - 韩国恺的博客