Agile Web Development
Nginx使用Linux内存加速静态文件访问
by Elton on 二.05, 2012, under Linux, Web
Nginx是一个非常出色的静态资源web服务器。如果你嫌它还不够快,可以把放在磁盘中的文件,映射到内存中,减少高并发下的磁盘IO。
先做几个假设。nginx.conf中所配置站点的路径是/home/wwwroot/res,站点所对应文件原始存储路径:/opt/web/res
shell脚本非常简单,思路就是拷贝资源文件到内存中,然后在把网站的静态文件链接指向到内存中即可。具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #! /bin/bash res_path="/opt/web/res" mem_path="/dev/shm/res" lk_path="/home/wwwroot/res" if [ ! -d "$mem_path" ]; then cp -r "$res_path" "$mem_path" fi if [ ! -L "$lk_path" ]; then ln -s "$mem_path" "$lk_path" fi |
可伸缩性架构常用技术——之数据切分(Data Sharding/Partition)
by Elton on 一.29, 2012, under Database
1. 简介
我们知道,为了应对不断增长的数据,我们对数据进行切分,存储在不同的数据库里,本文提到的数据库在非特定指明的情况下,均指一个逻辑数据库(是一组数据库,比如Master-Slave),而非单一各个物理数据库。
其主要有两种方式:
垂直切分(Vertical Partition/Sharding):就是把不同格式的数据,存储到不同的数据库。
水平切分(Horizontal Partition/Sharding):就是把相同的数据格式的数据,存储到不同的数据库,本文将侧重这点进行讲述。
2. 垂直切分(Vertical Partition/Sharding)
对于垂直切分,其实应用非常广泛,主旨是把那些关系依赖非常紧密的数据保存到同一数据库,主要包含如下几种应用方式:
不同的应用使用不同数据库:这个非常容易理解,即对于一个企业来讲,往往有多个应用,甚至有些应用逐渐演变成两个或者多个应用,这其实就是一种垂直切分应用。
应用的不同模块使用不同的数据库:为同一应用的不同模块分别使用不同的数据库,之间提供低耦合的API进行访问。
同一应用相同模块使用不同数据库:在一些应用中,对于适合关系查询的数据,保存在关系数据库,而另外一些适合以NoSQL数据库保存的数据(例如key-value数据库),保存在NoSQL数据库中,方便数据扩展。这里给出一个例子,比如一个论坛应用,可以对个人用户信息保存在关系数据库,例如,其访问次数,个人信息等等,而对于发表的帖子和回帖,则可以保存在一个NoSQL里,方便扩展。注意,这里给出的例子并非真实例子,只是为了易于说明给出一个假设性的例子。
3. 水平切分(Horizontal Partition/Sharding)
水平切分相对比较复杂,我们还是从水平切分的策略谈起。
3.1 水平切分策略主要分为以下几种:
1. Round-Robin(轮询式)算法
顾名思义,就是把数据按照轮流的方式依次存放在的数据库节点上,比如,有2个节点,N0和N1,那么Data0放在N0节点上,Data1放在N1上,Data2放在N0上,依次类推……。
这种方式实现起来非常容易,对于数字键,我们有:n = key mod N。其中,key为数据的键,N为节点的数量,n为存放数据的节点编号;对于那些非数字键,我们可以让其转变为数字键,比如通过某些hash函数,让键值均匀分布,于是有:n = f (key) mod N。
这种方式有个缺点非常明显,不容易应对数据节点的变化,即不易进行二次切分。所谓二次分片是指,当数据的增长超过数据库容量时,需要增加数据库,或者系统故障导致某些数据库不能使用时,这时需要重新切分数据库。例如,有两个节点,N0和N1,现在需要增加一个节点N2,这时候,都需要吧N0上的数据和N1上的数据迁移到N2上,这个工作量是巨大的;并且可能导致上层应用对数据的改变,比如,之前数据Data5存储在N1上面,上层应用访问该数据时,根据key=5知道其存储在数据库N1里,那么便会在N1里查询数据,现在增加另外一个节点N2,那么这条数据被迁移到N2上了,上层应用就应该去N2上查询此数据了,这个看似简单,其实往往导致应用程序的复杂性很高。
2. 虚拟分片技术
为了保证二次分片时,避免对上层应用因为实际物理数据库发生改变而引起对数据访问逻辑的改变,中间加入了一个虚拟片段—物理片段映射表,数据对象存储在虚拟分片上,每个虚拟片段通过这个映射表找到相应的物理片段。这时间,上层应用依赖于虚拟分片,而非物理分片,只要保证虚拟片段足够多,就能避免上层应用的依赖。
3. 一致性Hash算法
为了避免数据库数量发生变化,引起大规模的数据迁移问题,而引入了一致性Hash算法。此算法由David Karger等人发表于1997年,论文题目为《Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web》,这里有一篇文章讲述Java语言简单实现一致性Hash算法http://weblogs.java.net/blog/2007/11/27/consistent-hashing。
一致性Hash算法的主要思想是不改变Hash函数本身,当减少节点时,临近的节点接手该节点,因此,消失节点上的数据迁移只迁移到临近节点上面;而增加节点时,只接手其临近的一个节点的部分数据,因此,只有一部分临近节点的数据被迁移至新加节点。
我们来详细了解一下具体实现:我们的Hash函数生成的数据都有一个值区间[min,max],我们把该区间用一个环来表示,每个节点的hash值都映射到这个环上,如下图所示:

假设我们的值区间是[1,12],我们有三个节点,1,4,9,数据的键也映射到这个环上,a的键值介于1~4之间,则存储在节点4上,即按照顺时针方向存储数据,同样b存储于节点9,而c存储于节点1。
假设节点4不可用时,那么数据a就会被迁移至节点9,其他节点的数据不发生迁移,如下图所示:

假设增加节点7,那么将把节点9上的部分数据迁移至节点7,其他节点数据不发生改变,如下图所示:

4. 按照数据的特点进行切分数据
最常见的就是按照地理位置切分数据,那么我们按照用户的注册信息或者用户数据提交的ip地址等来把它们放置于离它们地理位置最近的数据库中。
3.2 实际应用
在真实的应用中,往往会结合这些策略,甚至提供更为抽象的接口让开发人员实现适合自己的切分方法。我们这里讲述Mongodb和Hibernate Shards的分片方式。
3.2.1 Mongodb Sharding
Mongo db是基于文档的NoSQL数据库,查询方式和关系数据库非常接近。
Mongodb把数据存放在称为Chuncks数据结构上面,Chunck的默认大小是64M,每个Chunck上面存储一定切分范围的数据,当数据超过64M时,会自行分裂成两个Chunks,相当于一致性Hash算法添加了一个节点,只是这个节点不是DB。而每台物理db(称为Shard)上含有多个Chunks,为了达到更好的负载均衡,这些物理db上的Chunks会自动迁移,使得db上的Chunks发布均衡。
3.2.2 Hibernate Shards
Hibernate Shards是在Hibernate Core上做的一层扩展,目的是在关系数据库上封装和降低水平切分的复杂性。
Hibernate Shards为开发者提供了抽象接口,开发人员可以实现自己想要的切分策略,为了避免物理数据库发生改变引起应用程序的改变,其采用虚拟分片技术。
Hibernate Shards参考中文文档请参见:http://redhat.iteye.com/blog/328032
3.3 应注意的问题
水平切分数据库之后,会给查询造成一定的困难,特别是Aggregation查询。Mongodb采用Map/Reduce方式,能够比较高效进行Aggregation查询。
4 总结
对于大规模,可伸缩,海量数据的应用,数据切分是其架构必须考虑的一个重点内容,我们在进行数据切分时,往往采用先垂直,再水平方式对数据分片。
摘自:http://blog.csdn.net/co0der/article/details/7217974
使用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会发现并拒绝执行。
为自己的网站实现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,估计很难找到这么大内存,此时保证内存能装下热数据即可,至于热数据是多少,取决于具体的应用。如此一来内存大小就明确了:内存 > 索引 + 热数据。
原文引自:火丁笔记
通过JNI实现Java对C/C++的调用
by Elton on 七.29, 2011, under C/C++, Java, Linux
JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。
大致步骤
- 编写带有native声明的方法的java类
- 使用javac命令编译所编写的java类
- 使用javah命令生成扩展名为h的头文件
- 使用C/C++实现本地方法
- 将C/C++编写的文件生成动态连接库
1) 编写java程序:
1 2 3 4 5 6 7 8 9 10 11 12 | public class HelloNative{ public native void greeting();//所有native所修饰的都是本地方法 static{ System.loadLibrary("HelloNative");//载入本地库 } public static void main(String[] args){ new HelloNative().greeting(); // System.out.println(System.getProperty("java.library.path")); } } |
声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明该方法为native的,并且不能实现。其中方法的参数和返回值在后面讲述。 Load动态库:System.loadLibrary(“HelloNative”);加载动态库(我们可以这样理解:我们的方法 greeting()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是System.loadLibrary();的参数“HelloNative”是动态库的名字。
2) 编译
1 | javac HelloNative.java |
3) 生成扩展名为h的头文件
1 | javah HelloNative |
命令执行后会在当前目录下生产一个c的头文件,名字为HelloNative.h。内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloNative */ #ifndef _Included_HelloNative #define _Included_HelloNative #ifdef __cplusplus extern "C" { #endif /* * Class: HelloNative * Method: greeting * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloNative_greeting (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif |
这个h文件相当于我们在java里面的接口,这里声明了一个 Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写C/C++程序的时候所使用的方法名必须和这里的一致
4) 编写本地方法实现和由javah命令生成的头文件里面声明的方法名相同的方法。
1 2 3 4 5 6 7 8 9 | #include <stdio.h> #include <jni.h> #include "HelloNative.h" JNIEXPORT void JNICALL Java_HelloNative_greeting (JNIEnv * env, jobject obj) { printf("Hello, Native!\n"); } |
5) 生成动态库
1 | gcc -fPIC -I/home/elton/jdk/include -I/home/elton/jdk/include/linux -shared -o libHelloNative.so HelloNative.c |
注意,必须告知编译器jni.h所在的接口位置。linux的动态库都是以lib开头,以.so结尾的,要遵守这个命名规范。-fPIC是告诉编译器生成跟位置无关的动态链接库
命令执行后,会在当前目录生成一个libHelloNative.so的动态链接库文件
6) 运行程序
1 | java -Djava.library.path=. HelloNative |
必须指定java.library.path变量的内容,告诉java你的动态链接库的位置。
或者在命令行上输入
1 | export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH |
这样就不用每次调试的时候都输入-Djava.library.path=.这个参数了。
当你部署的时候,你通过System.out.println(System.getProperty(“java.library.path”));得到你系统的java.library.path位置,然后把你的动态链接库拷贝到这个目录中。我用的是ubuntu 11.04 64位版本,得到的结果是
1 | /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib |
这样你的动态链接库就永远都会被java访问到了,不用每次指定环境变量了。
让Redis使用TCMalloc,实现高性能NOSql服务器
by Elton on 七.28, 2011, under C/C++, Linux, NoSQL
TCMalloc(Thread-Caching Malloc)是google开发的开源工具──“google-perftools”中的成员。与标准的glibc库的malloc相比,TCMalloc在内存的分配上效率和速度要高得多,可以在很大程度上提高MySQL服务器在高并发情况下的性能,降低系统负载。
TCMalloc库的安装步骤(Linux环境):
Step 1. 64位操作系统请先安装libunwind库(32位操作系统不要安装)
libunwind库为基于64位CPU和操作系统的程序提供了基本的堆栈辗转开解功能,其中包括用于输出堆栈跟踪的API、用于以编程方式辗转开解堆栈的API以及支持C++异常处理机制的API。
1 2 3 4 5 6 | wget http://download.savannah.gnu.org/releases/libunwind/libunwind-0.99-alpha.tar.gz tar zxvf libunwind-0.99-alpha.tar.gz cd libunwind-0.99-alpha/ CFLAGS=-fPIC ./configure make CFLAGS=-fPIC make CFLAGS=-fPIC install |
Step 2、安装google-perftools:
1 2 3 4 5 6 7 8 | wget http://google-perftools.googlecode.com/files/google-perftools-1.8.1.tar.gz tar zxvf google-perftools-1.8.1.tar.gz cd google-perftools-1.8.1/ ./configure --disable-cpu-profiler --disable-heap-profiler --disable-heap-checker --disable-debugalloc --enable-minimal make && make install sudo echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf #如果没有这个文件,自己建一个 sudo /sbin/ldconfig |
Step 3. 安装Redis
1 2 3 4 5 | $ curl -O http://redis.googlecode.com/files/redis-2.2.12.tar.gz $ tar xzvf redis-2.2.12.tar.gz $ cd redis-2.2.12 $ make USE_TCMALLOC=yes $ sudo make install |
Step 4. 检查tcmalloc是否生效
1 2 | sudo lsof -n | grep tcmalloc redis-ser 31590 elton mem REG 8,3 1155539 4856411 /usr/local/lib/libtcmalloc_minimal.so.0.2.1 |
Step 5. 测试Redis
1 2 3 4 5 6 7 8 9 10 11 12 | # 修改配置文件: vim redis.conf # 找到 daemonize,将后面的no改为yes,让其可以以服务方式运行 # 然后启动 redis: $ ./redis-server ./redis.conf #连接数据库进行测试 $ src/redis-cli redis> set foo bar OK redis> get foo "bar" |
ubuntu 11.04安装OpenCV
by Elton on 七.27, 2011, under C/C++, Linux
Step 1:安装必要的依赖包
1 | sudo apt-get install build-essential libgtk2.0-dev libjpeg62-dev libtiff4-dev libjasper-dev libopenexr-dev cmake python-dev python-numpy libtbb-dev libeigen2-dev yasm libfaac-dev libopencore-amrnb-dev libopencore-amrwb-dev libtheora-dev libvorbis-dev libxvidcore-dev |
Step 2:安装ffmpeg
1 2 3 4 5 6 7 | cd ~ wget http://ffmpeg.org/releases/ffmpeg-0.7-rc1.tar.gz tar -xvzf ffmpeg-0.7-rc1.tar.gz cd ffmpeg-0.7-rc1 ./configure --enable-gpl --enable-version3 --enable-nonfree --enable-postproc --enable-libfaac --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libtheora --enable-libvorbis --enable-libxvid --enable-x11grab --enable-swscale --enable-shared make sudo make install |
Step 3:安装OpenCV 2.3
1 2 3 4 5 6 7 8 | cd ~ wget http://downloads.sourceforge.net/project/opencvlibrary/opencv-unix/2.3/OpenCV-2.3.0.tar.bz2 tar -xvf OpenCV-2.3.0.tar.bz2 cd OpenCV-2.3.0/ mkdir release cd releasecmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D BUILD_NEW_PYTHON_SUPPORT=ON -D WITH_TBB=ON -D WITH_V4L=OFF -D INSTALL_C_EXAMPLES=ON -D INSTALL_PYTHON_EXAMPLES=ON -D BUILD_EXAMPLES=ON .. make sudo make intall |
Step 4:添加库的路径
1 | sudo vim /etc/ld.so.conf.d/opencv.conf |
加入:
1 | /usr/local/lib |
更新系统库
1 | $sudo ldconfig |
设置环境变量
1 | $sudo vim /etc/environment |
加入
1 | PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig |
至此,OpenCV的设置都完成了。
Step 5:测试
进入OpenCV源代码目录中的sample
1 2 3 | cd ~/OpenCV-2.3.0/samples/c chmod +x build_all.sh ./build_all.sh |
然后执行
1 | ./facedetect --cascade="/usr/local/share/opencv/haarcascades/haarcascade_frontalface_alt.xml" --scale=1.5 lena.jpg |
你应该可以看到以下图片,说明OpenCV运行正常

ubuntu 11.04安装gcc 4.6.1
by Elton on 七.27, 2011, under C/C++, Linux
首先下载相应的源代码:
ftp://ftp.dti.ad.jp/pub/lang/gcc/releases/gcc-4.6.1/
#下载 gcc-4.6.1.tar.bz2
ftp://ftp.dti.ad.jp/pub/lang/gcc/infrastructure/
#下载 gmp-4.3.2.tar.bz2 mpfr-2.4.2.tar.bz2 mpc-0.8.1.tar.gz
Step 0:
1 2 3 | $sudo apt-get install build-essential $sudo apt-get install zlibc $sudo apt-get install zlib1g-dev |
Step 1: 安装 gmp-4.3.2
1 2 3 4 | #cd to src_dir $./configure --prefix=/usr/local/gmp-4.3.2 $make $sudo make install |
Step 2: 安装 mpfr-2.4.2
1 2 3 4 | #cd to src_dir $./configure --prefix=/usr/local/mpfr-2.4.2 --with-gmp=/usr/local/gmp-4.3.2 $make $sudo make install |
Step 3: 安装 mpc-0.8.1
1 2 3 4 | #cd to src_dir $./configure --prefix=/usr/local/mpc-0.8.1 --with-gmp=/usr/local/gmp-4.3.2 --with-mpfr=/usr/local/mpfr-2.4.2 $make $sudo make install |
Step 4: 安装 gcc-4.6.0
1 2 3 4 5 | $export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/mpc-0.8.1/lib:/usr/local/gmp-4.3.2/lib:/usr/local/mpfr-2.4.2/lib #cd to src_dir $./configure --prefix=/usr/local/gcc-4.6.1 --enable-threads=posix --disable-checking --disable-multilib --enable-languages=c,c++ --with-gmp=/usr/local/gmp-4.3.2 --with-mpfr=/usr/local/mpfr-2.4.2 --with-mpc=/usr/local/mpc-0.8.1 $make $sudo make install |
Step 5: 多版本支持
为了让ubuntu支持多个gcc版本,需要做以下设置:
1 2 3 4 | $ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.5 40 $ sudo update-alternatives --install /usr/bin/gcc gcc /usr/local/gcc-4.6.1/bin/gcc 60 #选择你需要的版本 $sudo update-alternatives --config gcc |
Step 6:添加新版共享库
为了在编译软件时候,可以使用到最新的共享库
1 | $sudo vim /etc/ld.so.conf.d/x86_64-linux-gnu.conf |
添加下面内容
/usr/local/gcc-4.6.1/lib64/
保存后执行,更新共享库
1 | $sudo ldconfig |



