用mongodb构建延时队列

延时队列(DelayQueue)的使用场景有很多,比如订单类的系统,用户创建订单后一段时间内如果没有付款,那么要把用户的这个订单关闭掉,同时把库存还原回去。解决的方案有很多,一种是用定时任务,定时去扫描符合条件的数据出来进行出来,还有一种就是把这个丢到延时队列里面,等时间到了自动出列之后处理。

由于我们业务场景需要实时的延时队列,也就是必须准时处理,如果通过定时扫描的话,如果时间间隔短,会任务太多处理不过来,如果时间间隔长,会导致中间的有一些延时出列了。之前有调研过一些已有的产品,像twitter的beanstalkd,就是用来处理延时的任务的。不过当时由于在测试环境跑起来了,但是在线上却一直不可用,另外也没有人熟悉这个产品,怕后期带来运维的问题,所以也没有用起来,于是决定自己开发了。google一把,很多人在吹嘘用mongodb替换消息队列(MQ),看起来也很简单,于是我们准备试一下,用来做延时队列。这一试,就定下来了,然后在线上跑了1年多了。

先说一下我们部署mongodb的机器配置,4核CPU,8G内存的VM。你没看错,我们所有数据库都部署在淘宝聚石塔的虚拟机上面。这里不是做广告,如果你要做应用,建议不要用聚石塔,哈哈。我们是做淘宝业务,有“安全性”的限制,所以才用。

之所以用mongodb做“队列”,是因为mongodb有一个叫findAndModify的操作,这个操作是原子性的,也就是你可以修改一条记录的同时把老的记录返回。基于这个操作,我们可以在把一条记录标记为处理中的同时获取到这条数据,这样别人如果同时也是进行这样的处理,由于这个操作具备原子性,你们处理的任务不会重复,所以简单实现了出列的问题。所以简单的, 我们可以这样设计我们的延时队列里面的字段设计如下:

timestamp:出列的时间戳,这样我们可以根据系统当前的时间判断该记录要不要出列

status:状态,用来标记该记录目前是可以出列还是已经出列。0、1就可以搞定了

data:数据,表示你要保存到队列中的数据,出列后根据这个数据来处理任务。

定义好了,出列的操作就是:


//获取当前时间戳
long current = getCurrentTimeMillis();
//0是初始化状态,表示可以出列, 1表示已经出列过了.
DBObject obj = collection.findAndModify({status:0, timestamp:{$lte: current}},{$set:{status: 1}});
//没有命中, 说明当前不需要出列
if(obj == null){
return null;
}
//获取数据, 序列化为二进制的话, 可以兼容各种各样的数据格式
byte[] data = obj.get("data");
//反序列化
return SerializableUtil.deserial(data);

上面是伪代码, 既不是java的也不是javascript的.

是不是很简单,我们的队列搞定了。
如果故事到这里就结束了,那我就不写这篇文章了,呵呵呵呵呵。

由于业务的增长,我们在延时队列里的数据越来越多,处理任务的机器不够用了,不得不剥离成多台机来处理这些任务,这时候作为一个“队列”的挑战才刚刚开始。

由于多台机,你必须要让每台机处理的数据没有重复。上面的findAndModify已经很好的满足需求了是不是?是的,确实满足了。但是findAndModify有个致命的弱点,就是不支持批量findAndModify。由于每天要处理上千万的延时数据,如果全部请求都压到mongodb上,我们的mongodb吃不消,关键我们也不想多加机器。所以问题来了,我们要批量的findAndModify。于是乎,我们顺延这findAndModify的特性,自己弄了一套批量操作的findAndModify,代价是引入分布式锁,在批量find和批量modify的这段时间加分布式锁,这样能保证数据不会被重复处理。由于分布式锁我们用了zookeeper来实现,zookeeper的链路稳定性是我见过最差了,没有之一。时不时客户端就会断开连接,时不时客户端就会session expired。而面对这些问题,zookeeper是没有直接提供接口给你解决的,因为他的接口实在是太底层了,估计写那套client的人以前是搞底层系统开发的,所有问题都要调用方来处理,真不是一般的难用。zookeeper的问题始终是可以解决的,但接下来一个更大的问题是队列的吞吐量(qps)。由于改成了批量处理之后,mongodb平时的负载确实变低了好多。而平时一次出列1000条数据,不会带来拥堵的问题,直到那一天。

没错,那一天就是2013年的天猫双十一,我们系统之前为此做了扩容,机器数量从平时的20台加到了50+台。我们预估当天的数据量会达到平日的6倍。但实际上那天的量,远超我们想象。订单量达到了平时的10倍以上,在双十一前10分钟,我们的延时队列就被冲垮了。我着急了,一看,堵了100+W的数据了。过了一个小时之后,这个堵的数据量达到了800+W。眼看着这么多订单,白花花的就溜走了。直到第二天10点,我们终于解决了这个问题。

解决方案很简单粗暴,就是把批量findAndModify这套方案直接干掉,由于总共有6台任务处理机器,我们直接使用单机的模式,一台机器使用一个队列,把分布式锁抛弃掉。也是用批量的方式,批量出列,处理数据,然后批量删除。效率极高,在后面的几次高峰期都顶住了压力,“顺利”度过了双十一。这种模式也带来一个问题,如果处理数据的机器下线了怎么办?这涉及到了数据迁移的问题,好解决,我们写了个小工具,可以很快的把遗留的数据迁移到另外的机器上。

另外的解决方案是把架构搞成C/S的,做成一个单独的server来访问mongodb,任务处理系统都来订阅这个server。这样也不会有锁的问题,只不过当业务量继续增加,需要扩展服务器的时候,同样的问题还是会来临。上面的单机解决方案看起来是很简单,但实际上效果也是很不错的。当你真正贴近业务的时候,你会发现不是要做一个架构多么牛逼的系统,而是应该做一个能简单维护却能很好解决问题的系统。

如果你想设计延时队列,可以先参考java的延时队列的实现:

java.util.concurrent.DelayQueue

如果可以直接在上面加一层持久层,就可以简单实现延时队列了。

一些总结:能不用锁的地方一定不要用锁,锁会把你的性能耗尽。能不用分布式锁的地方一定不要用,如果你的系统需要使用分布式锁,可以想想有没有其他的简单粗暴的方案,也许会有更好的搜获。分布式系统的一个特点是高并发,如果架上了分布式锁,很可能让你的系统变成了串行处理的系统了,这样就违背分布式系统的初衷了。

附 beanstalkd 项目地址:http://kr.github.io/beanstalkd/

 

关于用户研究

昨天晚上,黄猿师兄(@小猪和蜜桃的故事)过来给我们分享了关于用户研究的一些心得,感觉很不错,所以也分享一下。

1. 用户研究是隶属于UED的,在产品的各个阶段都需要用到他。

在产品未出来的时候,用户研究需要先行,进行用户调研,市场调研,竞品分析等等。在产品出来之后需要进行收集用户反馈,可用性测试,用户数据分析来改善产品的用户体验,最后当积累了一定量的大数据的时候,可以通过挖掘用户数据来推断出接下来做什么产品会受用户欢迎。大数据不仅仅是属于后端同学使用的,实际上数据的分析在改善产品方面有很大的作用。

2. 用户体验的五大层面。

从上到下是表现成,框架层,结构层,功能层和战略层。最顶层是用户直接能感受到的东西,而最底层是用户感知不到的,看起来很虚,但却很影响深远的一层。产品的形成,往往是由战略层开始,通过市场调研,发现做某类产品有市场,于是交给下游的产品经理做功能的需求设计,产品经理最后把功能和结构层框架层跟交互设计师一起确定,把产品的交互稿定下来,接下来就是UI表现层了。所以一个产品的产生往往是由战略层来决定的,他一开始影响深远,但随着时间的推移,他的影响会越来越小,直到战略方向要大调整的时候, 整个产品也即将面临大改版了。如下图

用户体验5要素的影响和时间的关系
用户体验5要素的影响和时间的关系

也可以看出,如果一个产品一开始的战略定位不对,后面调整起来,就是全盘都要调整了。(大公司的表现是,当产品换了一个新的老大,往往一个新的老大会想表现自己的‘杰出才能’,调整产品的战略,导致之做的很多工作要重新调整,最终反馈到用户的是:“哇塞,你们又升级啦!”“尼玛!这个按钮怎么没了?”“卧槽,排序算法又变了?” 等等)

3. 用户研究的手段

分类起来可以归结为两种:一种是面向人的定性研究,另一种是面向数据的定量研究

在做面向人的定性研究的时候,必须要把人物角色和使用场景都考虑到:

人物角色:好比说一个产品的使用者,是一个什么样的人?一个淘宝卖家客服,还是白领消费者,还是公司CEO,这些都必须要归类出来。然后对这些人分别进行定性研究。“你不能拿着你的社交APP去问一个还不回走路的小孩他的感觉是什么样的。” 也就是说你要先定位你要分析的人群, 然后再给这些人群归类,通过提取共性,抽象出人物角色。再进行分析。

使用场景:这是一个非常重要的因素却往往容易被忽视。比如你办公司的宽带是20M的,你不能把你的用户请过来你的办公司,让他体验你的产品顺不顺畅,而是应该走到你的用户所在的环境中去对用户进行调研。还有一些土豪公司,在自己的办公楼搞了一个XX产品用户体验式,里面是豪华的设备装修,8G16核的机器,然后邀请一批用户到你这里,给他们冲上一杯香浓的咖啡,然后让他们来体验产品,这种做法是跟用户的真正使用场景完全脱节的,这时候用户给你的反馈,一点用处都没有。

另外必须要注意的是,用户真正的需求,往往不是他们说出来的,而是通过观察感受出来的。人往往会有虚伪的一面,你问他,直接的反馈不一定是真实的反馈。(比如你要做一个男性成人用品的产品,需要知道用户的JJ长度分布情况,然后你调查的结果肯定会比大多数人实际长度长一些,无法做到客观。)

定量分析:

根据数据来做定量分析的结果,一般是比较客观的(数据足够多的时候)。但是数据只是反馈了客观的数据,你不能加上主观的猜测然后刚好符合数据的表现就说明是这样的。比如,你一个功能做上去好久发现没人用,然后数据分析的结果证实了这一点,如果这时候你说这个功能没用是不客观的,你要看这个功能有没有入口,然后在用户的使用场景下,找到这个入口困不困难等等。数据是客观的,但不能为了‘佐证’你某个观点而滥用数据。

数据分析一般是用来验证你产品新功能的决策正不正确。比如你通过ABtest发现新功能的用户活跃度和消费度更高了,说明这是一个好的功能可以上。反之,应该继续改进产品。

数据挖掘不同于数据分析,他问问可以挖出用户潜在的需求,这是用于帮助产品新功能或者说新产品的决策的。比如美国著名的视频网站netflix就是通过大数据分析他们的用户平时都喜欢看什么类型的电视剧,最后发现用户喜欢看剧情片,所以出钱投资拍摄了美剧《纸牌屋》,现在已经第二季了。当然数据挖掘的难度比数据分析更大,更有创造性。

题外话:netflix我用过他们的开源zookeeper客户端curator, 确实把zookeeper那些shi一样的接口包装得容易用了许多。(不过还是有少许bug, session expired后有些时候没有自动重连)如果你要用到zookeeper,可以试试他们的框架。

响应式交互网站

最近把博客升级了一下:

1. 主题换成了响应式(responsive)的主题

2. 评论系统使用disqus

响应式web设计
响应式web设计

为什么要这样呢?

随着移动互联网的崛起,越来越多的网站把他们的站点迁移到移动端,小公司(业务比较单一)的做法是直接做一个APP把web端的内容用app重新实现一把,而大公司一般(由于业务太多)是做一个移动平台的APP的皮,内嵌一个webview控件,里面跑的是H5的代码,可以保证各大智能手机平台(IOS, android)上访问到的内容是一模一样的,同时保证了开发的迭代速度。数据还是来自原来的数据,只不过是输出HTML5,同时做了响应式的交互罢了。这样同时可以保证APP不过于庞大的同时能提供跟web上一样的服务。在以前,wap流行的时候,wap浏览器渲染能力太弱,页面真心难看,所以大公司也不会太关注wap端的发展。但是智能操作系统的告诉发展为h5的流行带来了契机,移动端的体验容易上去了,各大小公司也就都来搞一把了。

响应式交互最早我care是谁提出来的,但是我最早知道的一个响应式交互这个词是来自于titter的bootstrap 的css框架,那时候(2011)还不懂什么是响应式交互,只不过感叹于这个框架的强大,所以用了起来。虽然他不兼容IE6,IE7两个原始浏览器,但是用来做后台系统的管理界面,那就甩出什么extjs,jquery-ui(我们都用过)好几条大街。如果你正好需要快速搭建后台系统,建议使用bootstrap。实际上现在好多创业型的小公司,为了快速开发同事保持界面的整洁,也采用了bootstrap框架来做开发(如果你团队缺乏专业的前端人才,正好拿来使用),兼容的事情,总会有别的框架帮你搞定。(我们的做法是只兼容到IE7,IE6直接提示用户去升级浏览器,否则是无法使用的。)

后来越来越多的地方有人提到了响应式交互,我的理解是,实际上就是自适应式的交互,页面的内容能够根据你浏览器当前的window size来调整内容(要么隐藏,要么换行)的显示,以期望你的网站在各种分辨率下都能正常访问。目前国内大多数网站的大多数页面都还是固定宽度的(900,950,1000等),很少有整体网站做了响应式交互的,因为实在不好实现。像国内UED团队比较活跃的淘宝网,也仅仅是首页和“我的淘宝”做了响应式的设计,除了这两个之外的其他页面都是固定宽度的(1000)同时主内容居中显示。

到了移动端,我们发现这个做响应式页面的难度降低了,因为目前主流的移动平台IOS和Android上的浏览器几乎都是全面支持H5和CSS3的,这样的话再移动端使用CSS3来写响应式的页面难度就大大降低了,这是移动互联网天然的优势,也是他之于pc互联网有大进步的地方。当然目前H5和CSS3在android平台上表现出来的性能还是不佳,导致一些使用app内容webview来加载h5内容的app我们经常要等好久(比如手机淘宝的app,用三星的s4,渲染一个页面还是太久,比iPhone5要慢个1两秒),另外是canvas的性能也不咋地。当然这主要是android系统性能不够优化的原因,但是我们看到android的进步很快,相信很快这两个系统直接的性能差距就会没有了,到时候正是H5和CSS3统一移动端的时候。

所以,你如果打算做一个新时代的网站,注意要多考虑响应式(responsive)交互以及移动互联网。

扩展阅读:

响应式网页设计  http://zh.wikipedia.org/zh-cn/%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1

响应式web设计 http://www.yixieshi.com/ucd/11828.html