一、一般来说nginx 配置文件中对优化比较有作用的为以下几项:

1.  worker_processes 8;

nginx 进程数,建议按照cpu 数目来指定,一般为它的倍数 (如,2个四核的cpu计为8)。

2.  worker_cpu_affinity 00000001 0000001000000100 00001000 00010000 00100000 01000000 10000000;

为每个进程分配cpu,上例中将8 个进程分配到8 个cpu,当然可以写多个,或者将一
个进程分配到多个cpu。

3.  worker_rlimit_nofile65535;

这个指令是指当一个nginx 进程打开的最多文件描述符数目,理论值应该是最多打开文
件数(ulimit -n)与nginx 进程数相除,但是nginx 分配请求并不是那么均匀,所以最好与ulimit -n的值保持一致。

现在在linux2.6内核下开启文件打开数为65535,worker_rlimit_nofile就相应应该填写65535。

这是因为nginx调度时分配请求到进程并不是那么的均衡,所以假如填写10240,总并发量达到3-4万时就有进程可能超过10240了,这时会返回502错误。

查看linux系统文件描述符的方法:

[root@web001 ~]# sysctl -a | grep fs.file

fs.file-max = 789972

fs.file-nr = 510 0 789972

4.  useepoll;

使用epoll 的I/O 模型

(

补充说明:

与apache相类,nginx针对不同的操作系统,有不同的事件模型

     A)标准事件模型
     Select、poll属于标准事件模型,如果当前系统不存在更有效的方法,nginx会选择select或poll
     B)高效事件模型  
Kqueue:使用于 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD2.0 和 MacOS X. 使用双处理器的MacOS X系统使用kqueue可能会造成内核崩溃。
Epoll: 使用于Linux内核2.6版本及以后的系统。

/dev/poll:使用于 Solaris 7 11/99+, HP/UX 11.22+(eventport), IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+。

       Eventport:使用于 Solaris 10. 为了防止出现内核崩溃的问题,有必要安装安全补丁。

)

5.  worker_connections65535;

每个进程允许的最多连接数, 理论上每台nginx服务器的最大连接数为worker_processes*worker_connections。

6.  keepalive_timeout60;

keepalive 超时时间。

7.  client_header_buffer_size4k;

客户端请求头部的缓冲区大小,这个可以根据你的系统分页大小来设置,一般一个请求头的大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。 

分页大小可以用命令getconf PAGESIZE 取得。

[root@web001 ~]# getconf PAGESIZE 

4096

但也有client_header_buffer_size超过4k的情况,但是client_header_buffer_size该值必须设置为“系统分页大小”的整倍数。

8.  open_file_cachemax=65535 inactive=60s;

这个将为打开文件指定缓存,默认是没有启用的,max 指定缓存数量,建议和打开文件数一致,inactive是指经过多长时间文件没被请求后删除缓存。

9.  open_file_cache_valid80s;

这个是指多长时间检查一次缓存的有效信息。

10.  open_file_cache_min_uses1;

open_file_cache 指令中的inactive参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive时间内一次没被使用,它将被移除。


二、关于内核参数的优化:

net.ipv4.tcp_max_tw_buckets = 6000

timewait 的数量,默认是180000。

net.ipv4.ip_local_port_range = 1024 65000

允许系统打开的端口范围。

net.ipv4.tcp_tw_recycle = 1

启用timewait 快速回收。

net.ipv4.tcp_tw_reuse = 1

开启重用。允许将TIME-WAIT sockets 重新用于新的TCP 连接。

net.ipv4.tcp_syncookies = 1

开启SYN Cookies,当出现SYN 等待队列溢出时,启用cookies 来处理。

net.core.somaxconn = 262144

web 应用中listen 函数的backlog 默认会给我们内核参数的net.core.somaxconn限制到128,而nginx 定义的NGX_LISTEN_BACKLOG 默认为511,所以有必要调整这个值。

net.core.netdev_max_backlog = 262144

每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。

net.ipv4.tcp_max_orphans = 262144

系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。如果超过这个数字,孤儿连接将即刻被复位并打印出警告信息。这个限制仅仅是为了防止简单的DoS攻击,不能过分依靠它或者人为地减小这个值,更应该增加这个值(如果增加了内存之后)。

net.ipv4.tcp_max_syn_backlog = 262144

记录的那些尚未收到客户端确认信息的连接请求的最大值。对于有128M内存的系统而言,缺省值是1024,小内存的系统则是128。

net.ipv4.tcp_timestamps = 0

时间戳可以避免序列号的卷绕。一个1Gbps的链路肯定会遇到以前用过的序列号。时间戳能够让内核接受这种“异常”的数据包。这里需要将其关掉。

net.ipv4.tcp_synack_retries = 1

为了打开对端的连接,内核需要发送一个SYN 并附带一个回应前面一个SYN的ACK。也就是所谓三次握手中的第二次握手。这个设置决定了内核放弃连接之前发送SYN+ACK 包的数量。

net.ipv4.tcp_syn_retries = 1

在内核放弃建立连接之前发送SYN 包的数量。

net.ipv4.tcp_fin_timeout = 1

如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60 秒。2.2 内核的通常值是180秒,3你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB 服务器,也有因为大量的死套接字而内存溢出的风险,FIN-WAIT-2 的危险性比FIN-WAIT-1 要小,因为它最多只能吃掉1.5K 内存,但是它们的生存期长些。

net.ipv4.tcp_keepalive_time = 30

当keepalive 起用的时候,TCP 发送keepalive 消息的频度。缺省是2 小时。


三、下面贴一个完整的内核优化设置:

vi /etc/sysctl.conf CentOS5.5中可以将所有内容清空直接替换为如下内容:

net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.ipv4.tcp_max_tw_buckets = 6000
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_wmem = 4096 16384 4194304
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 262144
net.core.somaxconn = 262144
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_keepalive_time = 30
net.ipv4.ip_local_port_range = 1024 65000

使配置立即生效可使用如下命令:
/sbin/sysctl -p

四、下面是关于系统连接数的优化

linux 默认值 open files 和 max user processes 为1024

#ulimit -n

1024

#ulimit –u

1024

问题描述: 说明 server 只允许同时打开 1024 个文件,处理 1024个用户进程

使用ulimit -a 可以查看当前系统的所有限制值,使用ulimit -n 可以查看当前的最大打开文件数。

新装的linux 默认只有1024 ,当作负载较大的服务器时,很容易遇到error: too many open files。因此,需要将其改大。

 

解决方法:

使用 ulimit –n 65535 可即时修改,但重启后就无效了。(注ulimit -SHn 65535 等效 ulimit-n 65535 ,-S 指soft ,-H 指hard)

有如下三种修改方式:

1. 在/etc/rc.local 中增加一行 ulimit -SHn 65535
2. 在/etc/profile 中增加一行 ulimit -SHn 65535
3. /etc/security/limits.conf最后增加:

* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535

具体使用哪种,在 CentOS 中使用第1 种方式无效果,使用第3 种方式有效果,而在Debian 中使用第2种有效果

 # ulimit -n

65535

# ulimit -u

65535

 

备注:ulimit 命令本身就有分软硬设置,加-H 就是硬,加-S 就是软默认显示的是软限制

soft 限制指的是当前系统生效的设置值。 hard 限制值可以被普通用户降低。但是不能增加。 soft 限制不能设置的比hard 限制更高。 只有 root 用户才能够增加 hard 限制值。


五、下面是一个简单的nginx 配置文件:

user www www;
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 0001000000100000
01000000;
error_log /www/log/nginx_error.log crit;
pid /usr/local/nginx/nginx.pid;
worker_rlimit_nofile 204800;
events
{
use epoll;
worker_connections 204800;
}
http
{
include mime.types;
default_type application/octet-stream;
charset utf-8;
server_names_hash_bucket_size 128;
client_header_buffer_size 2k;
large_client_header_buffers 4 4k;
client_max_body_size 8m;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
fastcgi_cache_path /usr/local/nginx/fastcgi_cache levels=1:2
keys_zone=TEST:10m
inactive=5m;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 4k;
fastcgi_buffers 8 4k;
fastcgi_busy_buffers_size 8k;
fastcgi_temp_file_write_size 8k;
fastcgi_cache TEST;
fastcgi_cache_valid 200 302 1h;
fastcgi_cache_valid 301 1d;
fastcgi_cache_valid any 1m;
fastcgi_cache_min_uses 1;
fastcgi_cache_use_stale error timeout invalid_headerhttp_500;
open_file_cache max=204800 inactive=20s;
open_file_cache_min_uses 1;
open_file_cache_valid 30s;
tcp_nodelay on;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/cssapplication/xml;
gzip_vary on;
server
{
listen 8080;
server_name backup.aiju.com;
index index.php index.htm;
root /www/html/;
location /status
{
stub_status on;
}
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|js|css)$
{
expires 30d;
}
log_format access '$remote_addr -- $remote_user [$time_local]"$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
access_log /www/log/access.log access;
}
}

六、关于FastCGI 的几个指令:

fastcgi_cache_path /usr/local/nginx/fastcgi_cache levels=1:2keys_zone=TEST:10minactive=5m;

这个指令为FastCGI 缓存指定一个路径,目录结构等级,关键字区域存储时间和非活动删除时间。

fastcgi_connect_timeout 300;

指定连接到后端FastCGI 的超时时间。

fastcgi_send_timeout 300;

向FastCGI 传送请求的超时时间,这个值是指已经完成两次握手后向FastCGI 传送请求的超时时间。

fastcgi_read_timeout 300;

接收FastCGI 应答的超时时间,这个值是指已经完成两次握手后接收FastCGI 应答的超时时间。

fastcgi_buffer_size 4k;

指定读取FastCGI应答第一部分需要用多大的缓冲区,一般第一部分应答不会超过1k,由于页面大小为4k,所以这里设置为4k。

fastcgi_buffers 8 4k;

指定本地需要用多少和多大的缓冲区来缓冲FastCGI 的应答。

fastcgi_busy_buffers_size 8k;

这个指令我也不知道是做什么用,只知道默认值是fastcgi_buffers 的两倍。

fastcgi_temp_file_write_size 8k;

在写入fastcgi_temp_path 时将用多大的数据块,默认值是fastcgi_buffers 的两倍。

fastcgi_cache TEST

开启FastCGI 缓存并且为其制定一个名称。个人感觉开启缓存非常有用,可以有效降低CPU 负载,并且防止502 错误。

fastcgi_cache_valid 200 302 1h;
fastcgi_cache_valid 301 1d;
fastcgi_cache_valid any 1m;

为指定的应答代码指定缓存时间,如上例中将200,302 应答缓存一小时,301 应答缓存1 天,其他为1 分钟。

fastcgi_cache_min_uses 1;

缓存在fastcgi_cache_path 指令inactive 参数值时间内的最少使用次数,如上例,如果在5 分钟内某文件1次也没有被使用,那么这个文件将被移除。

fastcgi_cache_use_stale error timeout invalid_headerhttp_500;

不知道这个参数的作用,猜想应该是让nginx 知道哪些类型的缓存是没用的。以上为nginx 中FastCGI相关参数,另外,FastCGI 自身也有一些配置需要进行优化,如果你使用php-fpm来管理FastCGI,可以修改配置文件中的以下值:

<valuename="max_children">60</value>

同时处理的并发请求数,即它将开启最多60 个子线程来处理并发连接。

<valuename="rlimit_files">102400</value>

最多打开文件数。

<valuename="max_requests">204800</value>

每个进程在重置之前能够执行的最多请求数。

posted @ 2013-02-01 10:44 小马歌 阅读(4457) | 评论 (0)编辑 收藏
 
     摘要: 前端时间对公司已有项目JavaScript代码进行优化,本文的是对优化工作的一个总结,拿出来与大家分享。当然我的优化方式可能并不是最优的,或者说有些不对的地方,请指教。  JavaScript优化总结分为以下几点优化前后对比优化前优化后代码混乱,同样功能的函数重复出现在多个地方。如果需要修改实现,需要找到所有的地方。牵一发而动全身模块化,提取公共接口组织为库、结构清晰、方便代码重用、并且能够游戏防...  阅读全文
posted @ 2013-01-24 14:17 小马歌 阅读(395) | 评论 (1)编辑 收藏
 
     摘要: from:http://developer.yahoo.com/performance/rules.htmlMinimize HTTP Requeststag: content80% of the end-user response time is spent on the front-end. Most of this time is tied up in downloading all the...  阅读全文
posted @ 2013-01-24 14:16 小马歌 阅读(253) | 评论 (0)编辑 收藏
 

在JavaScript开发中,被人问到:null与undefined到底有啥区别?

    一时间不好回答,特别是undefined,因为这涉及到undefined的实现原理。于是,细想之后,写下本文,请各位大侠拍砖。

    总所周知:null == undefined

    但是:null !== undefined

    那么这两者到底有啥区别呢?

    请听俺娓娓道来...

null

    这是一个对象,但是为空。因为是对象,所以 typeof null  返回 'object' 。

    null 是 JavaScript 保留关键字。

    null 参与数值运算时其值会自动转换为 0 ,因此,下列表达式计算后会得到正确的数值:

    表达式:123 + null    结果值:123

    表达式:123 * null    结果值:0

undefined

  undefined是全局对象(window)的一个特殊属性,其值是未定义的。但 typeof undefined 返回 'undefined' 。

      虽然undefined是有特殊含义的,但它确实是一个属性,而且是全局对象(window)的属性。请看下面的代码:

    alert('undefined' in window);   //输出:true

     var anObj = {};
     alert('undefined' in anObj);    //输出:false

 
从中可以看出,undefined是window对象的一个属性,但却不是anObj对象的一个属性。

  注意:尽管undefined是有特殊含义的属性,但却不是JavaScript的保留关键字。

  undefined参与任何数值计算时,其结果一定是NaN。

  随便说一下,NaN是全局对象(window)的另一个特殊属性,Infinity也是。这些特殊属性都不是JavaScript的保留关键字!

提高undefined性能

  当我们在程序中使用undefined值时,实际上使用的是window对象的undefined属性。

  同样,当我们定义一个变量但未赋予其初始值,例如:

    var aValue;

  这时,JavaScript在所谓的预编译时会将其初始值设置为对window.undefined属性的引用,

  于是,当我们将一个变量或值与undefined比较时,实际上是与window对象的undefined属性比较。这个比较过程中,JavaScript会搜索window对象名叫‘undefined'的属性,然后再比较两个操作数的引用指针是否相同。

  由于window对象的属性值是非常多的,在每一次与undefined的比较中,搜索window对象的undefined属性都会花费时 间。在需要频繁与undefined进行比较的函数中,这可能会是一个性能问题点。因此,在这种情况下,我们可以自行定义一个局部的undefined变 量,来加快对undefined的比较速度。例如:

    function anyFunc()
    {
        var undefined;          //自定义局部undefined变量
        
        if(x == undefined)      //作用域上的引用比较
        
        
        while(y != undefined)   //作用域上的引用比较
        
    };
    其中,定义undefined局部变量时,其初始值会是对window.undefined属性值的引用。新定义的局部undefined变量存在与 该函数的作用域上。在随后的比较操作中,JavaScript代码的书写方式没有任何的改变,但比较速度却很快。因为作用域上的变量数量会远远少于 window对象的属性,搜索变量的速度会极大提高。

  这就是许多前端JS框架为什么常常要自己定义一个局部undefined变量的原因!

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/leadzen/archive/2009/02/17/3899392.aspx

 

第二篇 区别Javascript中的Null与Undefined

在JavaScript中存在这样两种原始类型:Null与Undefined。这两种类型常常会使JavaScript的开发人员产生疑惑,在什么时候是Null,什么时候又是Undefined?

Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。

Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。

js 代码


var oValue; 
alert(oValue == undefined); //output "true"

这段代码显示为true,代表oVlaue的值即为undefined,因为我们没有初始化它。

js 代码


alert(null == document.getElementById('notExistElement'));

当页面上不存在id为”notExistElement”的DOM节点时,这段代码显示为”true”,因为我们尝试获取一个不存在的对象。

js 代码


alert(typeof undefined); //output "undefined" 
alert(typeof null); //output "object"

第一行代码很容易理解,undefined的类型为Undefined;第二行代码却让人疑惑,为什么null的类型又是Object了呢?其实这 是 JavaScript最初实现的一个错误,后来被ECMAScript沿用下来。在今天我们可以解释为,null即是一个不存在的对象的占位符,但是在实 际编码时还是要注意这一特性。

js 代码


alert(null == undefined); //output "true"

ECMAScript认为undefined是从null派生出来的,所以把它们定义为相等的。但是,如果在一些情况下,我们一定要区分这两个值,那应该怎么办呢?可以使用下面的两种方法。

js 代码


alert(null === undefined); //output "false" 
alert(typeof null == typeof undefined); //output "false"

使用typeof方法在前面已经讲过,null与undefined的类型是不一样的,所以输出”false”。而===代表绝对等于,在这里null === undefined输出false。

以上是转载内容:

document.getElementById('不存在的元素')返回的是null

document.getElementById('不存在的元素').value返回的是undefined

jqury-->ajax(post,getJSON)函数中如果{id:undefined}的话,则此参数id不会提交到服务器中,{id:null}会提交到服务器中,如果是将参数直接写到url中则也会提交到服务器上

分类: JS
posted @ 2013-01-24 13:02 小马歌 阅读(446) | 评论 (1)编辑 收藏
 
因为linux系统里都是以文件来表示的,所以在做搞并发的web系统时,修改文件句柄限制那是必须的。 
 
查看某个进程使用了的文件句柄数 
# sudo ps -ef | grep java 
root      1663     1  0 02:37 ?        00:00:43 /opt/bitnami/java/bin/java 
从结果中可得知进程的pid,然后根据pid查看该进程当前使用的文件句柄数 
#sudo ls /proc/1663/fd | wc -l 
1022 
发现句柄数一直就停留在1022,同时top查看发现load持续增高。于是先查看了下当前进程的句柄数限制。 
#sudo cat /proc/1663/limits | grep "files" 
Max open files            1024                1024                files 
发现限制卡在这了,于是修改/etc/sysctl.conf文件,添加一行如下: 
fs.file-max=30720 
保存。并执行 
#sudo sysctl -p 
然后再查看就发现已经改过来了。 
Google发现linux系统里有两种文件句柄限制,一种是系统级的,一种是用户级的。 
修改系统级的:  www.2cto.com
#echo "30720" > /proc/sys/fs/file-max 
修改用户级的: 
#sudo vi /etc/security/limits.conf 
增加如下行: 
* soft nofile 2048 
* hard nofile 32768 
星号表示任何用户,soft/hard表示软限制、硬限制。修改/etc/pam.d/login文件指定使用PAM 
session required /lib/security/pam_limits.so 
不过需要注意的是系统是32位还是64位,若是64位的那就是/lib64/security/pam_limits.so 
上面的修改后,reboot,再次查看 
#ulimit -n 
30720 
修改成功。
posted @ 2013-01-10 13:58 小马歌 阅读(771) | 评论 (0)编辑 收藏
 

from:http://blog.jobbole.com/1194/ 
        淘宝网拥有国内最具商业价值的海量数据。截至当前,每天有超过30亿的店铺、商品浏览记录,10亿在线商品数,上千万的成交、收藏和评价数据。如何从这些数据中挖掘出真正的商业价值,进而帮助淘宝、商家进行企业的数据化运营,帮助消费者进行理性的购物决策,是淘宝数据平台与产品部的使命。

为此,我们进行了一系列数据产品的研发,比如为大家所熟知的量子统计、数据魔方和淘宝指数等。尽管从业务层面来讲,数据产品的研发难度并不高;但在 “海量”的限定下,数据产品的计算、存储和检索难度陡然上升。本文将以数据魔方为例,向大家介绍淘宝在海量数据产品技术架构方面的探索。

淘宝海量数据产品技术架构

数据产品的一个最大特点是数据的非实时写入,正因为如此,我们可以认为,在一定的时间段内,整个系统的数据是只读的。这为我们设计缓存奠定了非常重要的基础。

淘宝海量数据产品技术架构
图1 淘宝海量数据产品技术架构

 

按照数据的流向来划分,我们把淘宝数据产品的技术架构分为五层(如图1所示),分别是数据源、计算层、存储层、查询层和产品层。位于架构顶端的是我们的数据来源层,这里有淘宝主站的用户、店铺、商品和交易等数据库,还有用户的浏览、搜索等行为日志等。这一系列的数据是数据产品最原始的生命力所在。

在数据源层实时产生的数据,通过淘宝自主研发的数据传输组件DataX、DbSync和Timetunnel准实时地传输到一个有1500个节点的 Hadoop集群上,这个集群我们称之为“云梯”,是计算层的主要组成部分。在“云梯”上,我们每天有大约40000个作业对1.5PB的原始数据按照产品需求进行不同的MapReduce计算。这一计算过程通常都能在凌晨两点之前完成。相对于前端产品看到的数据,这里的计算结果很可能是一个处于中间状态的结果,这往往是在数据冗余与前端计算之间做了适当平衡的结果。

不得不提的是,一些对实效性要求很高的数据,例如针对搜索词的统计数据,我们希望能尽快推送到数据产品前端。这种需求再采用“云梯”来计算效率将是比较低的,为此我们做了流式数据的实时计算平台,称之为“银河”。“银河”也是一个分布式系统,它接收来自TimeTunnel的实时消息,在内存中做实时计算,并把计算结果在尽可能短的时间内刷新到NoSQL存储设备中,供前端产品调用。

容易理解,“云梯”或者“银河”并不适合直接向产品提供实时的数据查询服务。这是因为,对于“云梯”来说,它的定位只是做离线计算的,无法支持较高的性能和并发需求;而对于“银河”而言,尽管所有的代码都掌握在我们手中,但要完整地将数据接收、实时计算、存储和查询等功能集成在一个分布式系统中,避免不了分层,最终仍然落到了目前的架构上。

为此,我们针对前端产品设计了专门的存储层。在这一层,我们有基于MySQL的分布式关系型数据库集群MyFOX和基于HBase的NoSQL存储集群Prom,在后面的文字中,我将重点介绍这两个集群的实现原理。除此之外,其他第三方的模块也被我们纳入存储层的范畴。

存储层异构模块的增多,对前端产品的使用带来了挑战。为此,我们设计了通用的数据中间层——glider——来屏蔽这个影响。glider以HTTP协议对外提供restful方式的接口。数据产品可以通过一个唯一的URL获取到它想要的数据。

以上是淘宝海量数据产品在技术架构方面的一个概括性的介绍,接下来我将重点从四个方面阐述数据魔方设计上的特点。

关系型数据库仍然是王道

关系型数据库(RDBMS)自20世纪70年代提出以来,在工业生产中得到了广泛的使用。经过三十多年的长足发展,诞生了一批优秀的数据库软件,例如Oracle、MySQL、DB2、Sybase和SQL Server等。

图2 MyFOX中的数据增长曲线
图2 MyFOX中的数据增长曲线

 

尽管相对于非关系型数据库而言,关系型数据库在分区容忍性(Tolerance to Network Partitions)方面存在劣势,但由于它强大的语义表达能力以及数据之间的关系表达能力,在数据产品中仍然占据着不可替代的作用。

淘宝数据产品选择MySQL的MyISAM引擎作为底层的数据存储引擎。在此基础上,为了应对海量数据,我们设计了分布式MySQL集群的查询代理层——MyFOX,使得分区对前端应用透明。

图3 MyFOX的数据查询过程
图3 MyFOX的数据查询过程

 

目前,存储在MyFOX中的统计结果数据已经达到10TB,占据着数据魔方总数据量的95%以上,并且正在以每天超过6亿的增量增长着(如图2所示)。这些数据被我们近似均匀地分布到20个MySQL节点上,在查询时,经由MyFOX透明地对外服务(如图3所示)。

图4 MyFOX节点结构
图4 MyFOX节点结构

 

值得一提的是,在MyFOX现有的20个节点中,并不是所有节点都是“平等”的。一般而言,数据产品的用户更多地只关心“最近几天”的数据,越早的数据,越容易被冷落。为此,出于硬件成本考虑,我们在这20个节点中分出了“热节点”和“冷节点”(如图4所示)。

顾名思义,“热节点”存放最新的、被访问频率较高的数据。对于这部分数据,我们希望能给用户提供尽可能快的查询速度,所以在硬盘方面,我们选择了每分钟15000转的SAS硬盘,按照一个节点两台机器来计算,单位数据的存储成本约为4.5W/TB。相对应地,“冷数据”我们选择了每分钟7500转的 SATA硬盘,单碟上能够存放更多的数据,存储成本约为1.6W/TB。

将冷热数据进行分离的另外一个好处是可以有效降低内存磁盘比。从图4可以看出,“热节点”上单机只有24GB内存,而磁盘装满大约有 1.8TB(300 * 12 * 0.5 / 1024),内存磁盘比约为4:300,远远低于MySQL服务器的一个合理值。内存磁盘比过低导致的后果是,总有一天,即使所有内存用完也存不下数据的索引了——这个时候,大量的查询请求都需要从磁盘中读取索引,效率大打折扣。
NoSQL是SQL的有益补充

在MyFOX出现之后,一切都看起来那么完美,开发人员甚至不会意识到MyFOX的存在,一条不用任何特殊修饰的SQL语句就可以满足需求。这个状态持续了很长一段时间,直到有一天,我们碰到了传统的关系型数据库无法解决的问题——全属性选择器(如图5所示)。

图5 全属性选择器
图5 全属性选择器

 

这是一个非常典型的例子。为了说明问题,我们仍然以关系型数据库的思路来描述。对于笔记本电脑这个类目,用户某一次查询所选择的过滤条件可能包括 “笔记本尺寸”、“笔记本定位”、“硬盘容量”等一系列属性(字段),并且在每个可能用在过滤条件的属性上,属性值的分布是极不均匀的。在图5中我们可以看到,笔记本电脑的尺寸这一属性有着10个枚举值,而“蓝牙功能”这个属性值是个布尔值,数据的筛选性非常差。

在用户所选择的过滤条件不确定的情况下,解决全属性问题的思路有两个:一个是穷举所有可能的过滤条件组合,在“云梯”上进行预先计算,存入数据库供查询;另一个是存储原始数据,在用户查询时根据过滤条件筛选出相应的记录进行现场计算。很明显,由于过滤条件的排列组合几乎是无法穷举的,第一种方案在现实中是不可取的;而第二种方案中,原始数据存储在什么地方?如果仍然用关系型数据库,那么你打算怎样为这个表建立索引?

这一系列问题把我们引到了“创建定制化的存储、现场计算并提供查询服务的引擎”的思路上来,这就是Prometheus(如图6所示)。

图6 Prom的存储结构
图6 Prom的存储结构

 

从图6可以看出,我们选择了HBase作为Prom的底层存储引擎。之所以选择HBase,主要是因为它是建立在HDFS之上的,并且对于 MapReduce有良好的编程接口。尽管Prom是一个通用的、解决共性问题的服务框架,但在这里,我们仍然以全属性选择为例,来说明Prom的工作原理。这里的原始数据是前一天在淘宝上的交易明细,在HBase集群中,我们以属性对(属性与属性值的组合)作为row-key进行存储。而row-key 对应的值,我们设计了两个column-family,即存放交易ID列表的index字段和原始交易明细的data字段。在存储的时候,我们有意识地让每个字段中的每一个元素都是定长的,这是为了支持通过偏移量快速地找到相应记录,避免复杂的查找算法和磁盘的大量随机读取请求。

图7 Prom查询过程
图7 Prom查询过程

 

图7用一个典型的例子描述的Prom在提供查询服务时的工作原理,限于篇幅,这里不做详细描述。值得一提的是,Prom支持的计算并不仅限于求和 SUM运算,统计意义上的常用计算都是支持的。在现场计算方面,我们对Hbase进行了扩展,Prom要求每个节点返回的数据是已经经过“本地计算”的局部最优解,最终的全局最优解只是各个节点返回的局部最优解的一个简单汇总。很显然,这样的设计思路是要充分利用各个节点的并行计算能力,并且避免大量明细数据的网络传输开销。

用中间层隔离前后端

上文提到过,MyFOX和Prom为数据产品的不同需求提供了数据存储和底层查询的解决方案,但随之而来的问题是,各种异构的存储模块给前端产品的使用带来了很大的挑战。并且,前端产品的一个请求所需要的数据往往不可能只从一个模块获取。

举个例子,我们要在数据魔方中看昨天做热销的商品,首先从MyFOX中拿到一个热销排行榜的数据,但这里的“商品”只是一个ID,并没有ID所对应的商品描述、图片等数据。这个时候我们要从淘宝主站提供的接口中去获取这些数据,然后一一对应到热销排行榜中,最终呈现给用户。

图8 glider的技术架构
图8 glider的技术架构

 

有经验的读者一定可以想到,从本质上来讲,这就是广义上的异构“表”之间的JOIN操作。那么,谁来负责这个事情呢?很容易想到,在存储层与前端产品之间增加一个中间层,它负责各个异构“表”之间的数据JOIN和UNION等计算,并且隔离前端产品和后端存储,提供统一的数据查询服务。这个中间层就是glider(如图8所示)。

缓存是系统化的工程

除了起到隔离前后端以及异构“表”之间的数据整合的作用之外,glider的另外一个不容忽视的作用便是缓存管理。上文提到过,在特定的时间段内,我们认为数据产品中的数据是只读的,这是利用缓存来提高性能的理论基础。

在图8中我们看到,glider中存在两层缓存,分别是基于各个异构“表”(datasource)的二级缓存和整合之后基于独立请求的一级缓存。除此之外,各个异构“表”内部可能还存在自己的缓存机制。细心的读者一定注意到了图3中MyFOX的缓存设计,我们没有选择对汇总计算后的最终结果进行缓存,而是针对每个分片进行缓存,其目的在于提高缓存的命中率,并且降低数据的冗余度。

大量使用缓存的最大问题就是数据一致性问题。如何保证底层数据的变化在尽可能短的时间内体现给最终用户呢?这一定是一个系统化的工程,尤其对于分层较多的系统来说。

图9 缓存控制体系
图9 缓存控制体系

 

图9向我们展示了数据魔方在缓存控制方面的设计思路。用户的请求中一定是带了缓存控制的“命令”的,这包括URL中的query string,和 HTTP头中的“If-None-Match”信息。并且,这个缓存控制“命令”一定会经过层层传递,最终传递到底层存储的异构“表”模块。各异构“表” 除了返回各自的数据之外,还会返回各自的数据缓存过期时间(ttl),而glider最终输出的过期时间是各个异构“表”过期时间的最小值。这一过期时间也一定是从底层存储层层传递,最终通过HTTP头返回给用户浏览器的。

缓存系统不得不考虑的另一个问题是缓存穿透与失效时的雪崩效应。缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。在数据魔方里,我们采用了一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存失效时的雪崩效应对底层系统的冲击非常可怕。遗憾的是,这个问题目前并没有很完美的解决方案。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。在数据魔方中,我们设计的缓存过期机制理论上能够将各个客户端的数据失效时间均匀地分布在时间轴上,一定程度上能够避免缓存同时失效带来的雪崩效应。

结束语

正是基于本文所描述的架构特点,数据魔方目前已经能够提供压缩前80TB的数据存储空间,数据中间层glider支持每天4000万的查询请求,平均响应时间在28毫秒(6月1日数据),足以满足未来一段时间内的业务增长需求。

尽管如此,整个系统中仍然存在很多不完善的地方。一个典型的例子莫过于各个分层之间使用短连接模式的HTTP协议进行通信。这样的策略直接导致在流量高峰期单机的TCP连接数非常高。所以说,一个良好的架构固然能够在很大程度上降低开发和维护的成本,但它自身一定是随着数据量和流量的变化而不断变化的。我相信,过不了几年,淘宝数据产品的技术架构一定会是另外的样子。

posted @ 2012-12-13 10:16 小马歌 阅读(332) | 评论 (0)编辑 收藏
 

Linux下GDB调试



本文写给主要工作在Windows操作系统下而又需要开发一些跨平台软件的程序员朋友,以及程序爱好者。

GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。

GDB中的命令固然很多,但我们只需掌握其中十个左右的命令,就大致可以完成日常的基本的程序调试工作。

命令解释示例
file <文件名>加载被调试的可执行程序文件。
因为一般都在被调试程序所在目录下执行GDB,因而文本名不需要带路径。
(gdb) file gdb-sample
rRun的简写,运行被调试的程序。
如果此前没有下过断点,则执行完整个程序;如果有断点,则程序暂停在第一个可用断点处。
(gdb) r
cContinue的简写,继续执行被调试程序,直至下一个断点或程序结束。(gdb) c
b <行号>
b <函数名称>
b *<函数名称>
b *<代码地址>

d [编号]

b: Breakpoint的简写,设置断点。两可以使用“行号”“函数名称”“执行地址”等方式指定断点位置。
其中在函数名称前面加“*”符号表示将断点设置在“由编译器生成的prolog代码处”。如果不了解汇编,可以不予理会此用法。

d: Delete breakpoint的简写,删除指定编号的某个断点,或删除所有断点。断点编号从1开始递增。

(gdb) b 8
(gdb) b main
(gdb) b *main
(gdb) b *0x804835c

(gdb) d

s, ns: 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数;
n: 执行一行源程序代码,此行代码中的函数调用也一并执行。

s 相当于其它调试器中的“Step Into (单步跟踪进入)”;
n 相当于其它调试器中的“Step Over (单步跟踪)”。

这两个命令必须在有源代码调试信息的情况下才可以使用(GCC编译时使用“-g”参数)。

(gdb) s
(gdb) n
si, nisi命令类似于s命令,ni命令类似于n命令。所不同的是,这两个命令(si/ni)所针对的是汇编指令,而s/n针对的是源代码。(gdb) si
(gdb) ni
p <变量名称>Print的简写,显示指定变量(临时变量或全局变量)的值。(gdb) p i
(gdb) p nGlobalVar
display ...

undisplay <编号>

display,设置程序中断后欲显示的数据及其格式。
例如,如果希望每次程序中断后可以看到即将被执行的下一条汇编指令,可以使用命令
“display /i $pc”
其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。

undispaly,取消先前的display设置,编号从1开始递增。

(gdb) display /i $pc

(gdb) undisplay 1

iInfo的简写,用于显示各类信息,详情请查阅“help i”。(gdb) i r
qQuit的简写,退出GDB调试环境。(gdb) q
help [命令名称]GDB帮助命令,提供对GDB名种命令的解释说明。
如果指定了“命令名称”参数,则显示该命令的详细说明;如果没有指定参数,则分类显示所有GDB命令,供用户进一步浏览和查询。
(gdb) help display

/add************************************/

j命令              回跳

ret               设置返回值        (例ret 0/ret -1)

/add************************************/

废话不多说,下面开始实践。gdb-sample.c

 

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

#include <</font>stdio.h>

int nGlobalVar = 0;

int tempFunction(int a, int b)
{
    printf("tempFunction is called, a = %d, b = %d \n", a, b);
    return (a + b);
}

int main()
{
    int n;
        n = 1;
        n++;
        n--;

        nGlobalVar += 100;
        nGlobalVar -= 12;

    printf("n = %d, nGlobalVar = %d \n", n, nGlobalVar);

        n = tempFunction(1, 2);
    printf("n = %d", n);

    return 0;
}

 

gcc gdb-sample.c -o gdb-sample -g

使用参数 -g 表示将源代码信息编译到可执行文件中。如果不使用参数 -g,会给后面的GDB调试造成不便。当然,如果我们没有程序的源代码,自然也无从使用 -g 参数,调试/跟踪时也只能是汇编代码级别的调试/跟踪。

下面“gdb”命令启动GDB,将首先显示GDB说明,不管它:

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
(gdb)

上面最后一行“(gdb) ”为GDB内部命令引导符,等待用户输入GDB命令。

下面使用“file”命令载入被调试程序 gdb-sample(这里的 gdb-sample 即前面 GCC 编译输出的可执行文件):

(gdb) file gdb-sample
Reading symbols from gdb-sample...done.

上面最后一行提示已经加载成功。

下面使用“r”命令执行(Run)被调试文件,因为尚未设置任何断点,将直接执行到程序结束:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample
n = 1, nGlobalVar = 88
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

下面使用“b”命令在 main 函数开头设置一个断点(Breakpoint):

(gdb) b main
Breakpoint 1 at 0x804835c: file gdb-sample.c, line 19.

上面最后一行提示已经成功设置断点,并给出了该断点信息:在源文件 gdb-sample.c 第19行处设置断点;这是本程序的第一个断点(序号为1);断点处的代码地址为 0x804835c(此值可能仅在本次调试过程中有效)。回过头去看源代码,第19行中的代码为“n = 1”,恰好是 main 函数中的第一个可执行语句(前面的“int n;”为变量定义语句,并非可执行语句)。

再次使用“r”命令执行(Run)被调试程序:

(gdb) r
Starting program: /home/liigo/temp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;

程序中断在gdb-sample.c第19行处,即main函数是第一个可执行语句处。

上面最后一行信息为:下一条将要执行的源代码为“n = 1;”,它是源代码文件gdb-sample.c中的第19行。

下面使用“s”命令(Step)执行下一行代码(即第19行“n = 1;”):

(gdb) s
20 n++;

上面的信息表示已经执行完“n = 1;”,并显示下一条要执行的代码为第20行的“n++;”。

既然已经执行了“n = 1;”,即给变量 n 赋值为 1,那我们用“p”命令(Print)看一下变量 n 的值是不是 1 :

(gdb) p n
$1 = 1

果然是 1。($1大致是表示这是第一次使用“p”命令——再次执行“p n”将显示“$2 = 1”——此信息应该没有什么用处。)

下面我们分别在第26行、tempFunction 函数开头各设置一个断点(分别使用命令“b 26”“b tempFunction”):

(gdb) b 26
Breakpoint 2 at 0x804837b: file gdb-sample.c, line 26.
(gdb) b tempFunction
Breakpoint 3 at 0x804832e: file gdb-sample.c, line 12.

使用“c”命令继续(Continue)执行被调试程序,程序将中断在第二 个断点(26行),此时全局变量 nGlobalVar 的值应该是 88;再一次执行“c”命令,程序将中断于第三个断点(12行,tempFunction 函数开头处),此时tempFunction 函数的两个参数 a、b 的值应分别是 1 和 2:

(gdb) c
Continuing.

Breakpoint 2, main () at gdb-sample.c:26
26 printf("n = %d, nGlobalVar = %d \n", n, nGlobalVar);
(gdb) p nGlobalVar
$2 = 88
(gdb) c
Continuing.
n = 1, nGlobalVar = 88

Breakpoint 3, tempFunction (a=1, b=2) at gdb-sample.c:12
12 printf("tempFunction is called, a = %d, b = %d \n", a, b);
(gdb) p a
$3 = 1
(gdb) p b
$4 = 2

上面反馈的信息一切都在我们预料之中,哈哈~~~

再一次执行“c”命令(Continue),因为后面再也没有其它断点,程序将一直执行到结束:

(gdb) c
Continuing.
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.

 

有时候需要看到编译器生成的汇编代码,以进行汇编级的调试或跟踪,又该如何操作呢?

这就要用到display命令“display /i $pc”了(此命令前面已有详细解释):

(gdb) display /i $pc
(gdb)

此后程序再中断时,就可以显示出汇编代码了:

(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;
1: x/i $pc 0x804835c : movl $0x1,0xfffffffc(�p)

看到了汇编代码,“n = 1;”对应的汇编代码是“movl $0x1,0xfffffffc(�p)”。

并且以后程序每次中断都将显示下一条汇编指定(“si”命令用于执行一条汇编代码——区别于“s”执行一行C代码):

(gdb) si
20 n++;
1: x/i $pc 0x8048363 : lea 0xfffffffc(�p),�x
(gdb) si
0x08048366 20 n++;
1: x/i $pc 0x8048366 : incl (�x)
(gdb) si
21 n--;
1: x/i $pc 0x8048368 : lea 0xfffffffc(�p),�x
(gdb) si
0x0804836b 21 n--;
1: x/i $pc 0x804836b : decl (�x)
(gdb) si
23 nGlobalVar += 100;
1: x/i $pc 0x804836d : addl $0x64,0x80494fc

 

接下来我们试一下命令“b *<函数名称>”。

为了更简明,有必要先删除目前所有断点(使用“d”命令——Delete breakpoint):

(gdb) d
Delete all breakpoints? (y or n) y
(gdb)

当被询问是否删除所有断点时,输入“y”并按回车键即可。

下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每个函数的开头和结尾自行插入的代码):

 

(gdb) b *main
Breakpoint 4 at 0x804834c: file gdb-sample.c, line 17.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample

Breakpoint 4, main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834c : push �p
(gdb) si
0x0804834d 17 {
1: x/i $pc 0x804834d : mov %esp,�p
(gdb) si
0x0804834f in main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834f : sub $0x8,%esp
(gdb) si
0x08048352 17 {
1: x/i $pc 0x8048352 : and $0xfffffff0,%esp
(gdb) si
0x08048355 17 {
1: x/i $pc 0x8048355 : mov $0x0,�x
(gdb) si
0x0804835a 17 {
1: x/i $pc 0x804835a : sub �x,%esp
(gdb) si
19 n = 1;
1: x/i $pc 0x804835c : movl $0x1,0xfffffffc(�p)

 

此时可以使用“i r”命令显示寄存器中的当前值———“i r”即“Infomation Register”:

 

(gdb) i r
eax 0xbffff6a4 -1073744220
ecx 0x42015554 1107383636
edx 0x40016bc8 1073834952
ebx 0x42130a14 1108544020
esp 0xbffff6a0 0xbffff6a0
ebp 0xbffff6a8 0xbffff6a8
esi 0x40015360 1073828704
edi 0x80483f0 134513648
eip 0x8048366 0x8048366
eflags 0x386 902
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x33 51

 

当然也可以显示任意一个指定的寄存器值:

 

(gdb) i r eax
eax 0xbffff6a4 -1073744220

 

最后一个要介绍的命令是“q”,退出(Quit)GDB调试环境:

posted @ 2012-12-12 12:24 小马歌 阅读(258) | 评论 (0)编辑 收藏
 
实战Makefile.am
Makefile.am是一种比Makefile更高层次的规则。只需指定要生成什么目标,它由什么源文件生成,要安装到什么目录等构成。
表一列出了可执行文件、静态库、头文件和数据文件,四种书写Makefile.am文件个一般格式。

表 1Makefile.am一般格式

对于可执行文件和静态库类型,如果只想编译,不想安装到系统中,可以用noinst_PROGRAMS代替bin_PROGRAMS,noinst_LIBRARIES代替lib_LIBRARIES。
Makefile.am还提供了一些全局变量供所有的目标体使用:

表 2 Makefile.am中可用的全局变量
在Makefile.am中尽量使用相对路径,系统预定义了两个基本路径:

表 3Makefile.am中可用的路径变量
在上文中我们提到过安装路径,automake设置了默认的安装路径:
1)标准安装路径
默认安装路径为:$(prefix) = /usr/local,可以通过./configure --prefix=的方法来覆盖。
其它的预定义目录还包括:bindir = $(prefix)/bin, libdir = $(prefix)/lib, datadir = $(prefix)/share, sysconfdir = $(prefix)/etc等等。
2) 定义一个新的安装路径
比如test, 可定义testdir = $(prefix)/test, 然后test_DATA =test1 test2,则test1,test2会作为数据文件安装到$(prefix)/ /test目录下。
我们首先需要在工程顶层目录下(即project/)创建一个Makefile.am来指明包含的子目录:
SUBDIRS=src/lib src/ModuleA/apple/shell src/ModuleA/apple/core 
CURRENTPATH=$(shell /bin/pwd)
INCLUDES=-I$(CURRENTPATH)/src/include -I$(CURRENTPATH)/src/ModuleA/apple/include 
export INCLUDES
由于每个源文件都会用到相同的头文件,所以我们在最顶层的Makefile.am中包含了编译源文件时所用到的头文件,并导出,见蓝色部分代码。
我们将lib目录下的swap.c文件编译成libswap.a文件,被apple/shell/apple.c文件调用,那么lib目录下的Makefile.am如下所示:
noinst_LIBRARIES=libswap.a
libswap_a_SOURCES=swap.c
INCLUDES=-I$(top_srcdir)/src/includ
细心的读者可能就会问:怎么表1中给出的是bin_LIBRARIES,而这里是noinst_LIBRARIES?这
是因为如果只想编译,而不想安装到系统中,就用noinst_LIBRARIES代替bin_LIBRARIES,对于可执行文件就用
noinst_PROGRAMS代替bin_PROGRAMS。对于安装的情况,库将会安装到$(prefix)/lib目录下,可执行文件将会安装
到${prefix}/bin。如果想安装该库,则Makefile.am示例如下:
bin_LIBRARIES=libswap.a
libswap_a_SOURCES=swap.c
INCLUDES=-I$(top_srcdir)/src/include
swapincludedir=$(includedir)/swap
swapinclude_HEADERS=$(top_srcdir)/src/include/swap.h
最后两行的意思是将swap.h安装到${prefix}/include /swap目录下。
接下来,对于可执行文件类型的情况,我们将讨论如何写Makefile.am?对于编译apple/core目录下的文件,我们写成的Makefile.am如下所示:
noinst_PROGRAMS=test
test_SOURCES=test.c 
test_LDADD=$(top_srcdir)/src/ModuleA/apple/shell/apple.o $(top_srcdir)/src/lib/libswap.a 
test_LDFLAGS=-D_GNU_SOURCE
DEFS+=-D_GNU_SOURCE
#LIBS=-lpthread
由于我们的test.c文件在链接时,需要apple.o和libswap.a文件,所以我们需要在
test_LDADD中包含这两个文件。对于Linux下的信号量/读写锁文件进行编译,需要在编译选项中指明-D_GNU_SOURCE。所以在
test_LDFLAGS中指明。而test_LDFLAGS只是链接时的选项,编译时同样需要指明该选项,所以需要DEFS来指明编译选项,由于
DEFS已经有初始值,所以这里用+=的形式指明。从这里可以看出,Makefile.am中的语法与Makefile的语法一致,也可以采用条件表达
式。如果你的程序还包含其他的库,除了用AC_CHECK_LIB宏来指明外,还可以用LIBS来指明。
如果你只想编译某一个文件,那么Makefile.am如何写呢?这个文件也很简单,写法跟可执行文件的差不多,如下例所示:
noinst_PROGRAMS=apple
apple_SOURCES=apple.c
DEFS+=-D_GNU_SOURCE
我们这里只是欺骗automake,假装要生成apple文件,让它为我们生成依赖关系和执行命令。所以当你运行完automake命令后,然后修改apple/shell/下的Makefile.in文件,直接将LINK语句删除,即:
…….
clean-noinstPROGRAMS:
    -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
apple$(EXEEXT): $(apple_OBJECTS) $(apple_DEPENDENCIES) 
    @rm -f apple$(EXEEXT)
#$(LINK) $(apple_LDFLAGS) $(apple_OBJECTS) $(apple_LDADD) $(LIBS)
                
                
                

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/61254/showart_1821022.html 
posted @ 2012-11-30 16:19 小马歌 阅读(2424) | 评论 (0)编辑 收藏
 

一、 概述

为了更好的制作configure与Makefile,我先把制作流程给写在这里,好让大伙都有个心理准备。这里只说流程,不做解释。(附图供参考)


1、autosan命令生成configure.scan文件,这只是个模板,我们重新编辑这个文件,并把它保存为configure.in文件。

2、aclocal命令生成aclocal.m4文件。

3、autoheader命令生成config.h.in。前提是aclocal.m4和configure.in必须生成。

4、autoconf命令生成configure文件(这个文件都很熟悉吧,嘎嘎)。

5、创建并编辑Makefile.am,这个文件在根目录与子目录都应该有的。

6、automake命令生成Makefile.in。

7、./configure命令,根据Makefile.in生成Makefile文件,这个再熟悉不过了吧。

相关阅读: 详解Linux下auto工具制作Makefile源码包(工具安装篇)http://www.linuxidc.com/Linux/2011-05/36616.htm

二、     制作

巧妇难做无米之炊,要想完成这次体验,我们还得按规矩一步一步来,源文件得有。首先建个目录Family用来放我们的东西,它下面的东西就多了。源代码什么阿c,阿h啊,都放在src下面,src这个文件夹命名一般都是约定俗成的,我们也不破坏。当然你也可以起其他名字,别搞忘就好。

<Family>

   |-configure.in

   |-Makefile.am 

   |-<src>

     |-wife.c

     |-daughter.c                                                                   |-main.c

     |-wife.h                                                                       |-daughter.h

     |-Makefile.am

※说明:

1. configure.in 这是最重要的文档,整个安装过程都靠它来主导。

2. Makefile.am automake会根据它来生成Makefile.in,再由./configure Makefile.in变成最终的Makefile,一般来说在顶级目录和各个子目录都应该有一个Makefile.am

3. wife.c daughter.c main.c wife.h daughter.h 这是我们的源程序。

不用细看,这些文件除了configure.in是用模板创建手动编辑外,其他都是手动创建并编辑的,如果你还木有,动手吧。其实这里边有个小小的经验,也不一定是对的,只是个人的经验,也就是后缀名为in的文件是生成的模板。

$ mkdir –p Family/src

$ touch Makefile.am

$ cd src

$ touch wife.c daughter.c main.c wife.h daughter.h Makefile.am

$ cd ..  进入刚创建的Family目录

$ autoscan 该命令产生 configure.scan 和 configure.log两个文件,然后

$ mv configure.scan configure.i n这样configure.in就创建成了。

※源代码内容:

main.c: 

#include <stdio.h>

#include "wife.h"

#include "daughter.h"

#ifdef HAVE_CONFIG_H

#include <config.h>

#endif

int main(void)

{

printf( "These are my girls\n");

daughter_say();

wife_say();

return 0;

}

daughter.c

#include <stdio.h>

#include "daughter.h"

#ifdef HAVE_CONFIG_H

#include <config.h>

#endif

void daughter_say(void)

{

printf("My Dad ,are you call me ?\n");

}

daughter.h

#ifndef _DAUGHTER_

#define _DAUGHTER_

void daughter_say(void);

#endif

wife.c

#include <stdio.h>

#include "wife.h"

#ifdef HAVE_CONFIG_H

#include <config.h>

#endif

void wife_say(void)

{

printf("My darling ,are you call me ?\n");

}

wife.h

#ifndef _WIFE_

#define _WIFE_

void wife_say(void);

#endif

※制作流程:

第1步:编辑configure.in文件。

上面用autoscan生成的scan后缀的文件改名而成的configure.in文件,我的用autoconf版本是2.66,貌似autoscan自动生成的模板2.61之前与之后的不大一样,这点可以自己参照自己的版本修改,2.61以后的版本这个文件更简单了。

打开看看:  #                                               -*- Autoconf -*-

# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.66])

AC_INIT([family], [1.0], [804927399@qq.com])

AM_INIT_AUTOMAKE(family,1.0)

AC_CONFIG_SRCDIR([src/wife.c])

AC_CONFIG_HEADERS([config.h])

# Checks for programs.

AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile

src/Makefile])

AC_OUTPUT

configure.in 未修改版,简单对上面进行说明:

AC_PREREQ([2.66])这个宏是用来检测autoconf的版本的。

AC_INIT()是个初始化宏,括号中内容分别为:要生成的软件名称,版本号,bug报告邮箱

AM_INIT_AUTOMAKE(family,1.0)这个宏是新添加的,不过好像1.8的automake不用添加这个也可以,但是我没有添加的时候在后面make的时候会有“Makefile:15: *** 遗漏分隔符 。 停止。”的问题,而加上则有警告。现在还真有点晕呼呼滴。

AC_CONFIG_SRCDIR([src/wife.c])这个宏是用来检测源码目录的有效性,srcdir就可以看出来,括号里边的文件不一定非得是wife.c,也可以改成其他的。

AC_CONFIG_HEADERS([config.h])这个宏用来生成标准的config.h文件。

接下来的内容就是众多的check了

AC_PROG_CC这个是检测编译器的宏。

我们用的版本不用AC_OUTPUT输出了,还是比较省力的。

第2步:aclocal命令生成aclocal.m4文件

不知为何,我直接用命令:

$aclocal 命令总是出错:aclocal

aclocal: couldn't open directory `/usr/local/share/aclocal-': 没有那个文件或目录。

最后还是加上绝对路径/usr/bin/aclocal总算是没这个错误,生成aclocal.m4了。

第3步:autoconf命令生成 configure文件

这一步没什么难度,输入命令:

$ autoconf 就ok了,autoconf可以根据configure.in和aclocal.m4生成大名鼎鼎的configure,这时候已经可以运行它了,但是会报错,因为Makefile.in还没出现。

那么何为M4呢,M4的名称取自Macro(M后面跟4个字母…)。它和C预处理器里的宏是一个概念(其实,M4和C预处理器都K&R操刀设计的!!),用来处理文本替换。也就是说,M4是bash里的预处理器。

第4步:autoheader命令生成config.h.in

$ autoheader通过autoheader命令,我们就可以得到config.h.in这个东东了。有了它,./configure才会生成config.h这个东东,所以不可大意。

autoheader这个工具通常会从“acconfig.h”文件中复制用户附加的符号定义,因此此处没有附加符号定义,所以不需要创建“acconfig.h”文件。

第5步:编辑Makefile.am文件

我们再编译安装源码包的时候都知道./configure可以生成Makefile,殊不知要生成Makefile还全仰仗Makefile.in这个老东西,而这个东西是以in结尾的 ,也是个模板,是由Makefile.am生成的,好了,知道这个来龙去脉了,就着手编写Makefile.am,我们这里Family顶级目录与子目录各有一个,如果还有其他的子目录,理论上也是需要Makefile.am的。我们这就来编辑它:

Family/Makefile.am内容如下:

AUTOMAKE_OPTIONS = foreign

SUBDIRS = src

Family/src/Makefile.am内容如下:

AUTOMAKE_OPTIONS = foreign

bin_PROGRAMS = family

family_SOURCES = main.c wife.c daughter.c wife.h daughter.h

其中的AUTOMAKE_OPTIONS为设置automake的选项。由于GNU对自己发布的软件有严格的规范,比如必须附带许可证声明文件COPYING等,否则automake执行时会报错。automake提供了三种软件等级:foreign、gnu和gnits,让用 户选择采用,默认等级为gnu。在本例使用foreign等级,它只检测必须的文件,而不用检查README啊什么的。

bin_PROGRAMS定义要产生的执行文件名。如果要产生多个执行文件,每个文件名用空格隔开。

family_SOURCES定义“family”这个执行程序所需要的原始文件。如果”family”这个程序是由多个原始文件所产生的,则必须把它所用到的所有原 始文件都列出来,并用空格隔开。如果源文件名字太长,可以加上”\”行连接符换行书写。

第6步:automake --add-missing命令生成config.h.in

$ automake –a或者 automake -–add-missing,大功告成!

注意运行automake命令时一定要加参数,否则不会自动生成install.sh,missing等脚本,这样会出乱子滴。

这样,源码安装包就制作成了,用tar打包就可以了,使用的时候用三部曲./configure,make , make install安装即可,帅吧!

posted @ 2012-11-29 15:48 小马歌 阅读(542) | 评论 (0)编辑 收藏
 
翻译: bobning编译和安装ubuntu或debian下的安装非常简单
# apt-get install rabbitmq-server

默认的数据库内容
当第一次启动服务,检测数据库是否未初始化或者被删除,它会用下面的资源初始化一个新的数据库:

一个命名为 / 的虚拟宿主一个名为guest密码也为guest的用户,他拥有/虚拟宿主的所有权限如果你的中间件是公开访问的,最好修改guest用户的密码。管理概观rabbitmqctl 是RabbitMQ中间件的一个命令行管理工具。它通过连接一个中间件节点执行所有的动作。本地节点默认被命名为”rabbit”。可以通过这个命令前使 用”-n”标志明确的指定节点名称, 例如:# rabbitmqctl -n rabbit@shortstop add_user tonyg changeit
这个命令指示RabbitMQ中间件在rabbit@shortstop 节点创建一个tonyg/changeit的用户。
在一个名为”server.example.com”的主机,RabbitMQ Erlang节点的名称通常是rabbit@server(除非RABBITMQ_NODENAM在 中间件启动时候被设置)。hostnam -s 的输出通常是”@”符号正确的后缀。rabbitmqctl 默认产生详细输出。通过”-q”标示可选择安静模式。rabbitmqctl -q status应用和集群管理1.停止RabbitMQ应用,关闭节点
# rabbitmqctl stop
2.停止RabbitMQ应用
# rabbitmqctl stop_app
3.启动RabbitMQ应用
# rabbitmqctl start_app
4.显示RabbitMQ中间件各种信息
# rabbitmqctl status
5.重置RabbitMQ节点
# rabbitmqctl reset
# rabbitmqctl force_reset
从它属于的任何集群中移除,从管理数据库中移除所有数据,例如配置过的用户和虚拟宿主, 删除所有持久化的消息。
force_reset命令和reset的区别是无条件重置节点,不管当前管理数据库状态以及集群的配置。如果数据库或者集群配置发生错误才使用这个最后 的手段。
注意:只有在停止RabbitMQ应用后,reset和force_reset才能成功。
6.循环日志文件
# rabbitmqctl rotate_logs[suffix]
7.集群管理
# rabbitmqctl cluster clusternode

用户管理
1.添加用户
# rabbitmqctl add_user username password
2.删除用户
# rabbitmqctl delete_user username
3.修改密码
# rabbitmqctl change_password username newpassword
4.列出所有用户
# rabbitmqctl list_users

权限控制1.创建虚拟主机
# rabbitmqctl add_vhost vhostpath
2.删除虚拟主机
# rabbitmqctl delete_vhost vhostpath
3.列出所有虚拟主机
# rabbitmqctl list_vhosts
4.设置用户权限
# rabbitmqctl set_permissions [-p vhostpath] username regexp regexp regexp
5.清除用户权限
# rabbitmqctl clear_permissions [-p vhostpath] username
6.列出虚拟主机上的所有权限
# rabbitmqctl list_permissions [-p vhostpath]
7.列出用户权限

# rabbitmqctl list_user_permissions username

 

例子:

添加  rabbitmqctl add_vhost az

rabbitmqctl set_permissions -p az guest ".*" ".*" ".*" 
posted @ 2012-11-28 15:43 小马歌 阅读(4699) | 评论 (0)编辑 收藏
仅列出标题
共95页: First 上一页 30 31 32 33 34 35 36 37 38 下一页 Last