Author Archive
分布式文件系统MFS、Ceph、GlusterFS、Lustre的比较
by Elton on 十二.29, 2011, under Linux
摘自存储实验室
基于位置的实时游戏MapAttack的技术实现
by Elton on 十.11, 2011, under NoSQL
MapAttack是一款实时的,基于地理位置的游戏,其通过Socket.io,node.js,和Redis实现了其实时性。下面文章对整个游戏及其技术实现进行了描述,推荐给大家。
这里我将向大家表述我们如何利用Socket.io、Redis、Node.js以及一路上我们所学的东西来规划、开发并测试一款实时的、基于位置的游戏。在过去的几个月中,我们将大部分的空闲时间都用在了开发一款实时游戏上,由于它是基于地图环境的,所以我们称之为“MapAttack!”,并把它作为我们LBS平台Geoloqi的一个测试程序。游戏中,两组队员互相竞争,尽可能多的攻占游戏界面上的小圆圈。而在这里,游戏界面其实就是城市中玩家周围的街道。

Geofence在这里指地图上带有数字的小圆圈,玩家进入这些小圆圈后,就会得到与圆圈中所标记数字相同的分数,它所在小组的总分数也会相应增加,同时圆圈的颜色会变成该玩家小组的颜色。
为什么要开发这么一款实时Geofencing游戏呢?
我们想要创造一款游戏,它能够让人们与真实世界物理交互,而不是像第一人称射击游戏或者即时战略游戏那样通过电脑控制台来交互。同时,我们也是受到了真人版吃豆人(PacManhattan)的启发。
技术挑战
处理用户进入区域的检测,同时留出200+的小圆圈。
处理一局游戏中所有手机位置信息的更新量(一局游戏有20或者更多用户)。
允许每台手机或者观看游戏的Web浏览器都能实时地看到玩家们的移动和圆圈颜色的改变。每台手机都会将它的位置发送给服务器,服务器会广播这些位置数据给其他手机以及观看游戏的浏览器。
处理GPS技术在不同智能手机模型间的错误和差异以保证游戏的公平体验。
GPS硬件的差异
众所周知,GPS信号反映了城市中高层建筑的情况。这就导致了在位置数据方面的不准确和不一致。这点在新手机上并不显著,但是在较老的手机上却十分明显。

MapAttack架构图

Socket.io
Socket.io是一个跨浏览器的Web套接字实现,它允许在浏览器上做实时数据更新,并且也支持老的浏览器。多亏了Socket.io我们可以利用最新的技术,同时不用要求所有我们的用户升级到最新的浏览器。这让我们实现了游戏中浏览器和手机间的即时更新。
Node.js
Node.js是谷歌浏览器的V8 Javascript引擎事件驱动的I/O实现,它由一个反应器实现,而这个反应器使得大量异步数据的传输得以实现。
当手机要发送数据时,我们用一个Node.js服务器将位置数据流从手机传输到Redis的发布频道或者订阅频道上。数据发布到Redis上,另一个Node服务器订阅该频道。我们的Node.js服务器接收手机通过一个类似Google’s Protocol Buffers的顾客协议发来的更新,实际上就是压缩的二进制的JSON。
当一个浏览器想要开始发送数据流时,它连接Socket.io服务器然后这个服务器订阅Redis的发布频道或订阅频道。这个Socket.io服务器通过Websockets向浏览器发送数据,如果Websockets不可用,闪回或者长轮询作为后备方式。
本质上讲,Socket.io允许我们使用Websockets规范,这是全新的,但同时也能工作在较老的浏览器上。
Redis
Redis是一个开源的、高级key-value存储系统,它支持消息队列使用发布/订阅模式。
从较高层次来说,Redis让我们所能做的事,是控制把数据实时发送到所有游戏中的手机和浏览器。游戏中的每一台手机把它的位置发送给服务器,服务器广播这些数据给其它手机和正在观看游戏的浏览器。
关于发布/订阅系统的一件吸引人的事:使用一个传统的系统你不得不维持许多连接,并且为了通过连接发送数据,你不得不重复发送,与发布/订阅系统不同,如果你有10,000个用户,你将不得不重复通过10,000个连接,发送回非常缓慢,并且容易出现死锁在套接字上的问题。
使用Redis的发布/订阅模式,就像启动一个广播电台。一旦把它打开了,人们(在这里指的是浏览器)就可以收听。这让我们能够把实时数据大规模地更新给客户端(浏览器和手机)。
Sinatra Synchrony
Sinatra::Synchrony是Sinatra的一个小扩展,它动态提升了Sinatra网络应用的并发性。由于EventMachine和EM-Synchrony的支持,当你有很多传输和低速IO请求时(如向外部APIs发送的HTTP请求),它增加了你的应用每个过程可服务的客户的数量。由于它内部使用Fibers处理堵塞的IO,所以没有回调的需要。这意味着我们可以像写一个正常的Sinatra网络应用那样进行开发。
Sinatra::Synchrony允许我们做异步程序,除了那些在Fibers中封装了回调操作的。这让我们能够实现同步程序的同时利用异步代码的优势。除了可以这样简单地变成,它也让我们根据需要可以转换一个不同的并发策略。
The MapAttack Game Server
最后,有一个MapAttack游戏的服务器,在这里,这个游戏服务器是一个简单的数据库,他负责存储玩家在地图上显示的所在点的数据,以及手机上玩家需要实时去抢夺的点的数据。
英文原文:blog.programmableweb.com
译文出处:www.lbsvision.com
大美长白
by Elton on 十.08, 2011, under Photography
十一假期抽空来了趟短途游,去了中朝边境的长白山。
长白山,位于吉林省延边州安图县和白山市抚松县境内,是中朝两国的界山、中华十大名山之一、国家5A级风景区、关东第一山。因其主峰多白色浮石与积雪而得名,素有“千年积雪为年松,直上人间第一峰”的美誉。中国境内的白云峰海拔高度2691米,是东北第一高峰,而长白山最高峰是位于朝鲜境内的将军峰。长白山是中国东北境内海拔最高、喷口最大的火山体。长白山还有一个美好的寓意“长相守、到白头”。
10月1日从北京出发,10月5日返回。历程2600多公里,跨越河北,天津,辽宁,吉林。中朝边境的S303省道是这次旅程中风景最好的一段线路。

最美的风景都在路上




最美的风景貌似都在朝鲜那边,栅栏对面就是朝鲜人民主义共和国

天池边远眺

最美天池

S303省道边的白桦林

下面这条小河就是著名的鸭绿江,河对面就是朝鲜。想偷渡到朝鲜的,趟河过去就好了

随着往下游走,河面渐宽



使用rvm在Mac中安装ruby和rails
by Elton on 九.09, 2011, under Mac, Rails
MacOS默认安装的是ruby 1.8.7,如果你想使用ruby 1.9.2的话,除了在官网下载源码编译安装外,可以使用rvm来协助安装。
STEP-1 安装RVM
在Terminal中输入以下命令即可安装
1 | bash < <(curl -s https://rvm.beginrescueend.com/install/rvm) |
为了可以在shell中使用,需要在.bash_profile中输入以下命令
1 2 3 4 5 | cd ~/ sudo vim .bash_profile #在.bash_profile中加入 [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" # This loads RVM into a shell session. |
之后退出Terminal,重启它。
STEP-2 安装Ruby
使用以下命令,可以看到rvm可以支持安装的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ rvm list known # MRI Rubies 1.8.6[-p420] 1.8.6-head 1.8.7[-p352] 1.8.7-head 1.9.1-p378 1.9.1[-p431] 1.9.1-head 1.9.2-p180 1.9.2[-p290] 1.9.2-head ruby-head ... |
使用下面的命令安装ruby 1.9.2
1 | rvm install 1.9.2 |
然后使用下面命令,让系统使用新的ruby
1 2 3 4 | $rvm use 1.9.2 Using /Users/elton/.rvm/gems/ruby-1.9.2-p290 $ruby -v ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin11.1.0] |
当你重启机器后,你会发现又回复成了1.8.7了,可以使用下面命令,让系统默认使用1.9.2
1 | rvm --default use 1.9.2 |
STEP-3 安装Rails
这步很简单
1 | gem install rails |
之后就可以使用最新的ruby和rails了。
Redis中7种集合类型应用场景
by Elton on 八.30, 2011, under Database
Strings
Strings 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字。使用Strings类型,你可以完全实现目前 Memcached 的功能,并且效率更高。还可以享受Redis的定时持久化,操作日志及 Replication等功能。除了提供与 Memcached 一样的get、set、incr、decr 等操作外,Redis还提供了下面一些操作:
- 获取字符串长度
- 往字符串append内容
- 设置和获取字符串的某一段内容
- 设置及获取字符串的某一位(bit)
- 批量设置一系列字符串的内容
Hashs
在Memcached中,我们经常将一些结构化的信息打包成hashmap,在客户端序列化后存储为一个字符串的值,比如用户的昵称、年龄、性别、积分等,这时候在需要修改其中某一项时,通常需要将所有值取出反序列化后,修改某一项的值,再序列化存储回去。这样不仅增大了开销,也不适用于一些可能并发操作的场合(比如两个并发的操作都需要修改积分)。而Redis的Hash结构可以使你像在数据库中Update一个属性一样只修改某一项属性值。
Lists
Lists 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。Lists的另一个应用就是消息队列,可以利用Lists的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作Lists中某一段的api,你可以直接查询,删除Lists中某一段的元素。
Sets
Sets 就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的Sets数据结构,可以存储一些集合性的数据,比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
Sorted Sets
和Sets相比,Sorted Sets增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的Sorted Sets,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。另外还可以用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
Pub/Sub
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
Transactions
谁说NoSQL都不支持事务,虽然Redis的Transactions提供的并不是严格的ACID的事务(比如一串用EXEC提交执行的命令,在执行中服务器宕机,那么会有一部分命令执行了,剩下的没执行),但是这个Transactions还是提供了基本的命令打包执行的功能(在服务器不出问题的情况下,可以保证一连串的命令是顺序在一起执行的,中间有会有其它客户端命令插进来执行)。Redis还提供了一个Watch功能,你可以对一个key进行Watch,然后再执行Transactions,在这过程中,如果这个Watched的值进行了修改,那么这个Transactions会发现并拒绝执行。
阻止iOS设备锁屏
by Elton on 八.22, 2011, under iPhone
默认,所有iOS设备在过了设定的休眠时间后,都会自动锁屏。 如果你的应用不希望iOS设备自动锁屏,可以使用以下方式来保持屏幕一直开着。
1 2 3 4 5 | // Disable the idle timer [[UIApplication sharedApplication] setIdleTimerDisabled: YES]; // Or for those who prefer dot syntax: [UIApplication sharedApplication].idleTimerDisabled = YES; |
为自己的网站实现Heatmap
by Elton on 八.20, 2011, under PHP, Web
Heatmap,已经有网站提供此类服务,如:clickdensity,clicktale,crazyegg等等,甚至还有类似clickheat项目提供源代码供你直接使用。
不过最灵活的方案莫过于自己搞定,下面大概说说Heatmap的实现:
捕捉点击
当然,这需要Javascript来实现。为了不陷入浏览器兼容的泥潭,我们选择JQuery:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <script> jQuery(document).ready(function() { $(document).mousedown(function(e) { if (e.clientX >= $(window).width() || e.clientY >= $(window).height()) { return; } $.get("/path/to/a/empty/html/file", { page_x : e.pageX, page_y : e.pageY, screen_width : screen.width, screen_height: screen.height }); }); }); </script> |
客户端使用Ajax通过GET方法触发一个空HTML页面,当然,还可以更简单点:
1 2 3 4 5 6 | <script> var image = new Image(); image.src = "..."; </script> |
之所以要记录屏幕分辨率是因为有的情况下需要修正点击坐标。比如说,一个居中显示的定宽的页面,其同一个位置在不同分辨率下的坐标是不同的,当渲染图片的时候,坐标需要以一个分辨率为准进行修正。
另外,如果用户正在拖动滚动条,是不应该记录的。
分析日志
客户端使用Ajax通过GET方法触发一个空HTML页面,如此就会在服务端留下日志:
1 | page_x=...&page_y=...&screen_width=...&screen_height=... |
不同的日志格式,结果会有所不同,这里仅仅以此为例来说明问题,本文采用AWK来解析日志,当然你也可以使用Perl或别的你熟悉的语言:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #!/usr/bin/awk -f BEGIN { FS="&"; } NF == 4 { param["page_x"] = "0"; param["page_y"] = "0"; param["screen_width"] = "0"; param["screen_height"] = "0"; split($0, query, "&"); for (key in query) { split(query[key], item, "="); if (item[1] in param) { param[item[1]] = item[2]; } } print "page_x:" , param["page_x"]; print "page_y:" , param["page_y"]; print "screen_width:" , param["screen_width"]; print "screen_height:", param["screen_height"]; print "\n"; } |
至于数据的持久化,是使用MongoDB或者别的,自己定夺,这里就不多说了。
渲染图片
出于演示方便的考虑,我使用了一些随机生成的数据,以Imagick为例,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <?php $coordinates = array(); for ($i = 0; $i < 1000; $i++) { $coordinates[] = array(rand($i, 1000), rand($i, 1000)); } $max_repeat = max( array_count_values( array_map(function($v) { return "{$v[0]}x{$v[1]}"; }, $coordinates) ) ); $opacity = 1 - 1 / $max_repeat; $heatmap_image = new Imagick(); $heatmap_image->newImage(1000, 1000, new ImagickPixel('white')); $heatmap_image->setImageFormat('png'); $plot_image = new Imagick('plot.png'); $iterator = $plot_image->getPixelIterator(); foreach($iterator as $row) { foreach ($row as $pixel) { $colors = $pixel->getColor(); foreach (array('r', 'g', 'b') as $channel) { $color = $colors[$channel]; if ($color !== 255) { $colors[$channel] = $color + ((255 - $color) * $opacity); } } $pixel->setColor("rgb({$colors['r']},{$colors['g']},{$colors['b']})"); } $iterator->syncIterator(); } $plot_size = $plot_image->getImageGeometry(); foreach ($coordinates as $pair) { $heatmap_image->compositeImage( $plot_image, Imagick::COMPOSITE_MULTIPLY, $pair[0] - $plot_size['width'] / 2, $pair[1] - $plot_size['height'] / 2 ); } $color_image = new Imagick('clut.png'); $heatmap_image->clutImage($color_image); $heatmap_image->writeImage('heatmap.png'); ?> |
代码虽然很多,但并不复杂,其中用到了两个图片,分别是:plot.png和clut.png。实际应用时,有时候点击量会非常大,此时没有必要把所有的点击都渲染出来,而应该采取随机取样的策略,如果采用MongoDB持久化的话,可以参考:The Random Attribute。
备注:代码参考image-tempest。
最终展示
形象一点来说,其实就是通过CSS+Javascript把生成的图片盖在网页上,并调节图片透明度来达到合二为一的效果,篇幅所限,具体代码留给大家自己实现,例子效果可参考下图:
BTW:热点可能会随着时间改变,为了能对照某个时间的网页,可以使用CutyCapt截屏。顺手再贴一个相关的项目:smt2(simple mouse tracking)。
有关Heatmap的详细介绍,还可以参考
收工!Heatmap虽然不是很复杂的技术,但涉及的方面却很繁杂,希望本文能帮到大家。
引自:火丁笔记
MongoDB与内存管理
by Elton on 八.20, 2011, under Database, Linux
但凡初次接触MongoDB的人,无不惊讶于它对内存的贪得无厌,至于个中缘由,我先讲讲Linux是如何管理内存的,再说说MongoDB是如何使用内存的,答案自然就清楚了。
据说带着问题学习更有效,那就先看一个MongoDB服务器的top命令结果:
1 2 3 4 5 6 | shell> top -p $(pidof mongod) Mem: 32872124k total, 30065320k used, 2806804k free, 245020k buffers Swap: 2097144k total, 100k used, 2097044k free, 26482048k cached VIRT RES SHR %MEM 1892g 21g 21g 69.6 |
这台MongoDB服务器有没有性能问题?大家可以一边思考一边继续阅读。
先讲讲Linux是如何管理内存的
在Linux里(别的系统也差不多),内存有物理内存和虚拟内存之说,物理内存是什么自然无需解释,虚拟内存实际是物理内存的抽象,多数情况下,出于方便性的考虑,程序访问的都是虚拟内存地址,然后操作系统会把它翻译成物理内存地址。
很多人会把虚拟内存和Swap混为一谈,实际上Swap只是虚拟内存引申出的一种技术而已:操作系统一旦物理内存不足,为了腾出内存空间存放新内容,就会把当前物理内存中的内容放到交换分区里,稍后用到的时候再取回来,需要注意的是,Swap的使用可能会带来性能问题,偶尔为之无需紧张,糟糕的是物理内存和交换分区频繁的发生数据交换,这被称之为Swap颠簸,一旦发生这种情况,先要明确是什么原因造成的,如果是内存不足就好办了,加内存就可以解决,不过有的时候即使内存充足也可能会出现这种问题,比如MySQL就有可能出现这样的情况,解决方法是限制使用Swap:
1 | shell> sysctl -w vm.swappiness=0 |
查看内存情况最常用的是free命令:
1 2 3 4 5 | shell> free -m total used free shared buffers cached Mem: 32101 29377 2723 0 239 25880 -/+ buffers/cache: 3258 28842 Swap: 2047 0 2047 |
新手看到used一栏数值偏大,free一栏数值偏小,往往会认为内存要用光了。其实并非如此,之所以这样是因为每当我们操作文件的时候,Linux都会尽可能的把文件缓存到内存里,这样下次访问的时候,就可以直接从内存中取结果,所以cached一栏的数值非常的大,不过不用担心,这部分内存是可回收的,操作系统会按照LRU算法淘汰冷数据。还有一个buffers,也是可回收的,它和cache的区别,可以参考维基百科。
知道了原理,我们就可以推算出系统可用的内存是free + buffers + cached:
1 2 | shell> echo "2723 + 239 + 25880" | bc -l 28842 |
至于系统实际使用的内存是used – buffers – cached:
1 2 | shell> echo "29377 - 239 - 25880" | bc -l 3258 |
除了free命令,还可以使用sar命令:
1 2 3 4 5 6 7 | shell> sar -r kbmemfree kbmemused %memused kbbuffers kbcached 3224392 29647732 90.19 246116 26070160 shell> sar -W pswpin/s pswpout/s 0.00 0.00 |
希望你没有被%memused吓到,如果不幸言中,重读本文。
再说说MongoDB是如何使用内存的
目前,MongoDB使用的是内存映射存储引擎,它会把磁盘IO操作转换成内存操作,如果是读操作,内存中的数据起到缓存的作用,如果是写操作,内存还可以把随机的写操作转换成顺序的写操作,总之可以大幅度提升性能。MongoDB并不干涉内存管理工作,而是把这些工作留给操作系统的虚拟内存管理器去处理,这样的好处是简化了MongoDB的工作,但坏处是你没有方法很方便的控制MongoDB占多大内存,事实上MongoDB会占用所有能用的内存,所以最好不要把别的服务和MongoDB放一起。
有时候,即便MongoDB使用的是64位操作系统,也可能会遭遇臭名昭著的OOM问题,出现这种情况,多半是因为限制了虚拟内存的大小所致,可以这样查看当前值:
1 | shell> ulimit -a | grep 'virtual' |
多数操作系统缺省都是把它设置成unlimited的,如果你的操作系统不是,可以这样修改:
1 | shell> ulimit -v unlimited |
不过要注意的是,ulimit的使用是有上下文的,最好放在MongoDB的启动脚本里。
有时候,出于某些原因,你可能想释放掉MongoDB占用的内存,不过前面说了,内存管理工作是由虚拟内存管理器控制的,所以通常你只能通过重启服务来释放内存,你一定不齿于这样的方法,幸好可以使用MongoDB内置的closeAllDatabases命令达到目的:
1 2 | mongo> use admin mongo> db.runCommand({closeAllDatabases:1}) |
另外,通过调整内核参数drop_caches也可以释放缓存:
1 | shell> sysctl -w vm.drop_caches=1 |
平时可以通过mongo命令行来监控MongoDB的内存使用情况,如下所示:
1 2 3 4 5 6 | mongo> db.serverStatus().mem: { "resident" : 22346, "virtual" : 1938524, "mapped" : 962283 } |
还可以通过mongostat命令来监控MongoDB的内存使用情况,如下所示:
1 2 3 | shell> mongostat mapped vsize res faults 940g 1893g 21.9g 0 |
其中内存相关字段的含义是:
- mapped:映射到内存的数据大小
- visze:占用的虚拟内存大小
- res:占用的驻留内存大小
注:如果操作不能在内存中完成,结果faults列的数值不会是0,视大小可能有性能问题。
在上面的结果中,vsize是mapped的两倍,而mapped等于数据文件的大小,所以说vsize是数据文件的两倍,之所以会这样,是因为本例中,MongoDB开启了journal,需要在内存里多映射一次数据文件,如果关闭journal,则vsize和mapped大致相当。
如果想验证这一点,可以在开启或关闭journal后,通过pmap命令来观察文件映射情况:
1 | shell> pmap $(pidof mongod) |
到底MongoDB配备多大内存合适?宽泛点来说,多多益善,如果要确切点来说,这实际取决于你的数据及索引的大小,内存如果能够装下全部数据加索引是最佳情况,不过很多时候,数据都会比内存大,比如本文所涉及的MongoDB实例:
1 2 3 4 5 | mongo> db.stats() { "dataSize" : 1004862191980, "indexSize" : 1335929664 } |
本例中索引只有1G多,内存完全能装下,而数据文件则达到了1T,估计很难找到这么大内存,此时保证内存能装下热数据即可,至于热数据是多少,取决于具体的应用。如此一来内存大小就明确了:内存 > 索引 + 热数据。
原文引自:火丁笔记











