MongoDB MapReduce

NoSQL No Comments »

MapReduce
MapReduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。这样做的好处是可以在任务被分解后,可以通过大量机器进行并行计算,减少整个操作的时间。

对科班出生的程序员来说,最好的例子莫过于归并排序的例子,没错,归并排序流程就可以看作是一个MapReduce,只是我们在学校写过的归并排序程序可能还没有涉及到并行计算罢了。

上面是MapReduce的理论部分,下面说实际的应用,下面以MongoDB MapReduce为例说明。

下面是MongoDB官方的一个例子:

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
$ ./mongo
> db.things.insert( { _id : 1, tags : ['dog', 'cat'] } );
> db.things.insert( { _id : 2, tags : ['cat'] } );
> db.things.insert( { _id : 3, tags : ['mouse', 'cat', 'dog'] } );
> db.things.insert( { _id : 4, tags : []  } );
 
> // map function
> m = function(){
...    this.tags.forEach(
...        function(z){
...            emit( z , { count : 1 } );
...        }
...    );
...};
 
> // reduce function
> r = function( key , values ){
...    var total = 0;
...    for ( var i=0; i<values.length; i++ )
...        total += values[i].count;
...    return { count : total };
...};
 
> res = db.things.mapReduce(m,r);
> res
{"timeMillis.emit" : 9 , "result" : "mr.things.1254430454.3" ,
 "numObjects" : 4 , "timeMillis" : 9 , "errmsg" : "" , "ok" : 0}
 
> db[res.result].find()
{"_id" : "cat" , "value" : {"count" : 3}}
{"_id" : "dog" , "value" : {"count" : 2}}
{"_id" : "mouse" , "value" : {"count" : 1}}
 
> db[res.result].drop()

例子很简单,计算一个标签系统中每个标签出现的次数。

这里面,除了emit函数之外,所有都是标准的js语法,当然你也可以使用你所知道的所有标准js函数。而这个emit函数是非常重要的,他的作用是将一条数据放入数据分组集合,这个分组是以emit的第一个参数为key的。你可以这样理解,当你在所有需要计算的行执行完了map函数,你就得到了一组key-values对。基本key是emit中的key,values是每次emit函数的第二个参数组成的集合。

现在我们的任务就是将这一个key-values变在key-value,也就是把这一个集合变成一个单一的值。这个操作就是Reduce。

好像这里和我们前面的理论是完全一样的,其实不然。当我们的key-values中的values集合过大,会被再切分成很多个小的key-values块,然后分别执行Reduce函数,再将多个块的结果组合成一个新的集合,作为Reduce函数的第二个参数,继续Reducer操作。可以预见,如果我们初始的values非常大,可能还会对第一次分块计算后组成的集合再次Reduce。这就类似于多阶的归并排序了。具体会有多少重,就看数据量了。

上面这一内部机制,我们不必非常了解,但我们必须了解这一机制会要求我们遵守的原则,那就是当我们书写Map函数时,emit的第二个参数形式是我们的Reduce函数的第二个参数,而Reduce函数的返回值,可能会作为新的输入参数再次执行Reduce操作,所以Reduce函数的返回值也需要和Reduce函数的第二个参数结构一致。

作为结束,下面照本宣科说一下MongoDB MapReduce调用参数和返回结果。

参数表如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.runCommand(
 { mapreduce : <collection>,
   map : <mapfunction>,
   reduce : <reducefunction>
   [, query : <query filter object>]
   [, sort : <sort the query.  useful for optimization>]
   [, limit : <number of objects to return from collection>]
   [, out : <output-collection name>]
   [, keeptemp: <true|false>]
   [, finalize : <finalizefunction>]
   [, scope : <object where fields go into javascript global scope >]
   [, verbose : true]
 }
);
  • mapreduce:指定要进行mapreduce处理的collection
  • map:map函数
  • reduce:reduce函数
  • query:一个筛选条件,只有满足条件的行才会加入mapreduce集合,而这个筛选过程是先于整个mapreduce流程而执行的
  • sort:和query结合的sort排序参数,这是唯一可以优化分组机制的地方
  • limit:同上
  • out:结果输出的collection的名字,不指定会默认创建一个随机名字的collection
  • keytemp:true或false,表明结果输出到的collection是否是临时的,如果为true,则会在客户端连接中断后自动删除,如果你用的是MongoDB的mongo客户端连接,那必须exit后才会删除。如果是脚本执行,脚本退出或调用close会自动删除结果collection
  • finalize:和map,reduce一样是一个函数,它可以在reduce得出一个结果后再对key和value进行一次计算并返回一个最终结果
  • scope:设置参数值,在这里设置的值在map,reduce,finalize函数中可见
  • verbose:在执行过程中打印调试信息。

返回结果结构如下

1
2
3
4
5
6
7
8
9
10
{ result : <collection_name>,
  counts : {
       input :  <number of objects scanned>,
       emit  : <number of times emit was called>,
       output : <number of items in output collection>
  } ,
  timeMillis : <job_time>,
  ok : <1_if_ok>,
  [, err : <errmsg_if_error>]
}
  • result:储存结果的collection的名字
  • input:满足条件的数据行数
  • emit:emit调用次数,也就是所有集合中的数据总量
  • ouput:返回结果条数
  • timeMillis:执行时间,毫秒为单位
  • ok:是否成功,成功为1
  • err:如果失败,这里可以有失败原因,不过从经验上来看,原因比较模糊,作用不大
标签:,

Redis入门教程

redis 2 Comments »

本文包括如下内容:

  1. Redis简介
  2. Redis的性能
  3. 安装Redis、Redis启动参数介绍
  4. 应用实例:利用Redis构建简单的微博系统(官方例子链接)

1.Redis简介

Redis是一个key-value存储系统。和Memcached类似,但是解决了断电后数据完全丢失的情况,而且她支持更多无化的value类型,除了和string外,还支持lists(链表)、sets(集合)和zsets(有序集合)几种数据类型。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。

2.Redis的性能

下面是官方的bench-mark数据:

  • The test was done with 50 simultaneous clients performing 100000 requests.
  • The value SET and GET is a 256 bytes string.
  • The Linux box is running Linux 2.6, it’s Xeon X3320 2.5Ghz.
  • Text executed using the loopback interface (127.0.0.1).
  • Results: about 110000 SETs per second, about 81000 GETs per second.


更多详细数据请见官方bench-mark page(http://code.google.com/p/redis/wiki/Benchmarks

3.安装Redis、Redis启动参数介绍

3.1.安装Redis

Redis的代码遵循ANSI-C编写,可以在所有POSIX系统(如Linux, *BSD, Mac OS X, Solaris等)上安装运行。而且Redis并不依赖任何非标准库,也没有编译参数必需添加。编译安装Redis,我们唯一需要的就是make,下面是安装过程,使用的是目前的稳定版本1.2.6版,2.0版本目前尚处于开发状态。

3.1.1.获取源码、解压、进入源码目录:

1
2
3
wget http://redis.googlecode.com/files/redis-1.2.6.tar.gz
tar xzf redis-1.2.6.tar.gz
cd redis-1.2.6

3.1.2.编译生成可执行文件:

由于makefile文件已经写好,我们只需要直接在源码目录执行make命令进行编译即可:

1
make

make命令执行完成后,会在当前目录下生成本个可执行文件,分别是redis-server、redis-cli、redis-benchmark、redis-stat,它们的作用如下:

  • redis-server:Redis服务器的daemon启动程序
  • redis-cli:Redis命令行操作工具。当然,你也可以用telnet根据其纯文本协议来操作
  • redis-benchmark:Redis性能测试工具,测试Redis在你的系统及你的配置下的读写性能
  • redis-stat:Redis状态检测工具,可以检测Redis当前状态参数及延迟状况

3.1.3.建立Redis目录(非必须)

这个过程不是必须的,只是为了将Redis相关的资源统一管理而进行的操作。

执行以下命令建立相关目录并拷贝相关文件至目录中:

1
2
3
4
5
6
sudo -s
mkdir -p /usr/local/redis/bin
mkdir -p /usr/local/redis/etc
mkdir -p /usr/local/redis/var
cp redis-server redis-cli redis-benchmark redis-stat /usr/local/redis/bin/
cp redis.conf /usr/local/redis/etc/

3.2.Redis配置参数详解

在我们成功安装Redis后,我们直接执行redis-server即可运行Redis,此时它是按照默认配置来运行的(默认配置甚至不是后台运行)。我们希望Redis按我们的要求运行,则我们需要修改配置文件,Redis的配置文件就是我们上面第二个cp操作的redis.conf文件,目前它被我们拷贝到了/usr/local/redis/etc/目录下。修改它就可以配置我们的server了。如何修改?下面是redis.conf的主要配置参数的意义:

  • daemonize:是否以后台daemon方式运行
  • pidfile:pid文件位置
  • port:监听的端口号
  • timeout:请求超时时间
  • loglevel:log信息级别
  • logfile:log文件位置
  • databases:开启数据库的数量
  • save * *:保存快照的频率,第一个*表示多长时间,第三个*表示执行多少次写操作。在一定时间内执行一定数量的写操作时,自动保存快照。可设置多个条件。
  • rdbcompression:是否使用压缩
  • dbfilename:数据快照文件名(只是文件名,不包括目录)
  • dir:数据快照的保存目录(这个是目录)
  • appendonly:是否开启appendonlylog,开启的话每次写操作会记一条log,这会提高数据抗风险能力,但影响效率。
  • appendfsync:appendonlylog如何同步到磁盘(三个选项,分别是每次写都强制调用fsync、每秒启用一次fsync、不调用fsync等待系统自己同步)

下面是一个略做修改后的配置文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
daemonize yes
pidfile /usr/local/redis/var/redis.pid
port 6379
timeout 300
loglevel debug
logfile /usr/local/redis/var/redis.log
databases 16
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /usr/local/redis/var/
appendonly no
appendfsync always
glueoutputbuf yes
shareobjects no
shareobjectspoolsize 1024

将上面内容写为redis.conf并保存到/usr/local/redis/etc/目录下

然后在命令行执行:

1
/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

即可在后台启动redis服务,这时你通过

1
telnet 127.0.0.1 6379

即可连接到你的redis服务。

4.利用Redis构建简单的微博系统

具体例子见官方文档:
A case study: Design and implementation of a simple Twitter clone using only the Redis key-value store as database and PHP

标签:,

Mongo Database Profiler

技术相关 1 Comment »

在MySQL中,慢查询日志是经常作为我们优化数据库的依据,那在MongoDB中是否有类似的功能呢?答案是肯定的,那就是Mongo Database Profiler.不仅有,而且还有一些比MySQL的Slow Query Log更详细的信息。它就是我们这篇文章的主题。

开启 Profiling 功能

有两种方式可以控制 Profiling 的开关和级别,第一种是直接在启动参数里直接进行设置。

启动MongoDB时加上–profile=级别 即可。

也可以在客户端调用db.setProfilingLevel(级别) 命令来实时配置。可以通过db.getProfilingLevel()命令来获取当前的Profile级别。

1
2
3
> db.setProfilingLevel(2);
{"was" : 0 , "ok" : 1}
> db.getProfilingLevel()

上面斜体的级别可以取0,1,2 三个值,他们表示的意义如下:

0 – 不开启

1 – 记录慢命令 (默认为>100ms)

2 – 记录所有命令

Profile 记录在级别1时会记录慢命令,那么这个慢的定义是什么?上面我们说到其默认为100ms,当然有默认就有设置,其设置方法和级别一样有两种,一种是通过添加–slowms启动参数配置。第二种是调用db.setProfilingLevel时加上第二个参数:

1
2
db.setProfilingLevel( level , slowms )
db.setProfilingLevel( 1 , 10 );

查询 Profiling 记录

与MySQL的慢查询日志不同,Mongo Profile 记录是直接存在系统db里的,记录位置 system.profile ,所以,我们只要查询这个Collection的记录就可以获取到我们的 Profile 记录了。

1
2
3
4
5
6
7
8
9
10
11
> db.system.profile.find()
{"ts" : "Thu Jan 29 2009 15:19:32 GMT-0500 (EST)" , "info" : "query test.$cmd ntoreturn:1 reslen:66 nscanned:0
query: { profile: 2 }  nreturned:1 bytes:50" , "millis" : 0}
db.system.profile.find( { info: /test.foo/ } )
{"ts" : "Thu Jan 29 2009 15:19:40 GMT-0500 (EST)" , "info" : "insert test.foo" , "millis" : 0}
{"ts" : "Thu Jan 29 2009 15:19:42 GMT-0500 (EST)" , "info" : "insert test.foo" , "millis" : 0}
{"ts" : "Thu Jan 29 2009 15:19:45 GMT-0500 (EST)" , "info" : "query test.foo ntoreturn:0 reslen:102 nscanned:2
query: {}  nreturned:2 bytes:86" , "millis" : 0}
{"ts" : "Thu Jan 29 2009 15:21:17 GMT-0500 (EST)" , "info" : "query test.foo ntoreturn:0 reslen:36 nscanned:2
query: { $not: { x: 2 } }  nreturned:0 bytes:20" , "millis" : 0}
{"ts" : "Thu Jan 29 2009 15:21:27 GMT-0500 (EST)" , "info" : "query test.foo ntoreturn:0 exception  bytes:53" , "millis" : 88}

列出执行时间长于某一限度(5ms)的 Profile 记录:

1
2
> db.system.profile.find( { millis : { $gt : 5 } } )
{"ts" : "Thu Jan 29 2009 15:21:27 GMT-0500 (EST)" , "info" : "query test.foo ntoreturn:0 exception  bytes:53" , "millis" : 88}

查看最新的 Profile 记录:

1
db.system.profile.find().sort({$natural:-1})

Mongo Shell 还提供了一个比较简洁的命令show profile,可列出最近5条执行时间超过1ms的 Profile 记录。

Profile 信息内容详解:
ts-该命令在何时执行.
millis Time-该命令执行耗时,以毫秒记.
info-本命令的详细信息.
query-表明这是一个query查询操作.

ntoreturn-本次查询客户端要求返回的记录数.比如, findOne()命令执行时 ntoreturn 为 1.有limit(n) 条件时ntoreturn为n.
query-具体的查询条件(如x>3).
nscanned-本次查询扫描的记录数.
reslen-返回结果集的大小.
nreturned-本次查询实际返回的结果集.

update-表明这是一个update更新操作.

fastmod-Indicates a fast modify operation. See Updates. These operations are normally quite fast.
fastmodinsert – indicates a fast modify operation that performed an upsert.
upsert-表明update的upsert参数为true.此参数的功能是如果update的记录不存在,则用update的条件insert一条记录.
moved-表明本次update是否移动了硬盘上的数据,如果新记录比原记录短,通常不会移动当前记录,如果新记录比原记录长,那么可能会移动记录到其它位置,这时候会导致相关索引的更新.磁盘操作更多,加上索引更新,会使得这样的操作比较慢.

insert-这是一个insert插入操作.
getmore-这是一个getmore 操作,getmore通常发生在结果集比较大的查询时,第一个query返回了部分结果,后续的结果是通过getmore来获取的。

MongoDB 查询优化

如果nscanned(扫描的记录数)远大于nreturned(返回结果的记录数)的话,那么我们就要考虑通过加索引来优化记录定位了。

reslen 如果过大,那么说明我们返回的结果集太大了,这时请查看find函数的第二个参数是否只写上了你需要的属性名。(类似 于MySQL中不要总是select *)

对于创建索引的建议是:如果很少读,那么尽量不要添加索引,因为索引越多,写操作会越慢。如果读量很大,那么创建索引还是比较划算的。(和RDBMS一样,貌似是废话 -_-!!)

MongoDB 更新优化

如果写查询量或者update量过大的话,多加索引是会有好处的。以及~~~~(省略N字,和RDBMS差不多的道理)

Use fast modify operations when possible (and usually with these, an index). See Updates.

Profiler 的效率

Profiling 功能肯定是会影响效率的,但是不太严重,原因是他使用的是system.profile 来记录,而system.profile 是一个capped collection 这种collection 在操作上有一些限制和特点,但是效率更高。

标签:, , , , , ,

libevent应用杂谈

Unix C编程 3 Comments »

libevent是一个优秀的跨平台异步事件驱动库.当然,使用libevent的所有主品中,最有代表性的就是Memcached了.

本文主要讲是libevent的在应用上的一些基础知识,大湿们莫笑.

最简情形

最简单的libevent示例在libevent官方首页可下载.

地址:http://www.monkey.org/~provos/libevent/event-test.c

本示例中使用的是UNIX 管道fifo.具体流程是创建一个fifo文件,然后用libevent监听文件EV_READ事件,这里的EV_READ他哥是EV_WRITE,他们不是文件被读或者被写时的事件,而是一个可读或者可写的状态,说白了其实应该叫readable或者writeable.创建时管道是空的,当随便用shell 的echo 命令向里面写点东西时,libevent就会检测到这个状态,然后调用注册的回调函数处理这个事件.

整个代码也很简单,略去出错检验部分,对libevent的应用代码大致如下:

1
2
3
4
5
struct event evfifo;
event_init();
event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo);
event_add(&evfifo, NULL);
event_dispatch();

下面说一下上面的流程:

  • 首先是申明一个event结构体对象.
  • 然后调用event_init()初始化全局的event_base.
  • 再调用event_set初始化第一步申明的evfifo对象.这个函数的第一个参数相当于函数返回值的功能了,C语言经常这么用.把evfifo的地址传进去,然后在函数返回时他就会被初始化成我们想要的类型.后面都是初始化它的参数.第二个socket当然是对哪一个socket上的事件进行监测,第三个是上面说过的事件类型,是一个short类型,第四个是回调函数名,当fifo可读时就调用这个函数,最后一个是传给这个函数的第三个参数(为什么是第三个,下面我们看了这个函数的实现就明白了).
  • 然后调用event_add将初始化完成的evfifo放入事件队列.后面的NULL参数是一个time_out值.是libevent作为定时器时的设置,我们这里不需要,所以设置为NULL.
  • 最后调用event_dispatch()驱动事件监测器就完成了.

这时候,当这个fifo文件有可读信息时,fifo_read函数就会被调用.而上面这个通过event_add加进去的事件会从集合中删除,以免重复监测到EV_READ状态.当然会有某些时候你不希望事件被通知一次就删除,你可以设置数据类型为EV_READ|EV_PERSIST.后一个参数可以使回调函数被调用时保持原事件不删除,依然留在事件队列中.

下面我们看一看fifo_read相关的实现.

1
2
3
4
5
6
fifo_read(int fd, short event, void *arg)
{
	struct event *ev = arg;
	event_add(ev, NULL);
	//do something....;
}

首先是函数的参数,这个参数不是自己可以控制的,由于它是event的回调函数,前两个参数是默认的,而第三个参数就是我们上面的event_set函数的最后一个参数.我们可以看到这个参数传过来有什么用,他的用处就是让我们再将这个事件添加到全局的事件监测队列中去(第4行).

最简单的示例就讲完了.还有更复杂的吗?当然有,可以说上面的示例其实过于简单了,以至于我们随便个程序都能办到,而不必非得用libevnt.杀鸡用牛刀,是为了给没用过刀的人演示刀是怎么用的.

libevent结合socket编程

稍微复杂一点的例子是将管道换成socket连接,其实这里面大同小异,不过可能层次上要多一层,且听细细道来.

我们都非常清楚socket连接中的socket(),bind(),listen(),accept(),recv(),send(),close()这个最规范的流程,好,我们看一下其中哪些在正常情况下会导致同步地阻塞.listen在没有连接过来的时候,是不会向下调用accept的,这是第一个,accept后如果socket中没有可读数据,调用recv是会阻塞的,这是第二个.好了,就他们俩.

所以这时候不能向上面那样一个main函数一个fifo_read就搞定了.这时候通常需要三个函数.

main()

sock_accept();

sock_read();

示例:

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
void sock_read(int fd, short event, void *arg){
    char buf[1024];
    int len;
    struct event *ev = arg;
    if((len = recv(fd, buf, sizeof(buf)-1,0)) == -1)&
    send(fd, buf, strlen(buf)+1, 0);
    event_add(ev, NULL);
}
 
static void sock_accept(int fd, short event, void *arg){
    struct event *ev = arg;
    struct sockaddr addr;
    int s;
    socklen_t len = sizeof(addr);
    struct event *rev = (struct event *)malloc(sizeof(*rev));
 
    if((s = accept(fd, &addr, &len)) == -1){
        fprintf(stderr, "Sock Accept Failed!\n");
        exit(0);
    }
    event_set(rev, s, EV_READ, sock_read, rev);
    event_add(rev, NULL);
    event_add(ev, NULL);
}
 
int main(){
    struct event ev;
    int fd;
    struct sockaddr_in addr;
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        fprintf(stderr, "Sock Create Failed!\n");
        exit(1);
    }
 
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234);
    addr.sin_addr.s_addr = 0;
    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
    {
        fprintf(stderr, "Sock Bind Failed!\n");
        exit(1);
    }
 
    if (listen(fd, 10) == -1)
    {
        fprintf(stderr,"Sock Listen Failed!\n");
        exit(1);
    }
 
    event_init();
    event_set(&ev, fd, EV_READ, sock_accept, &ev);
    event_add(&ev, NULL);
    event_dispatch();
}

main用来做socket初始化的工作,一直执行到listen()函数,这时候将sock的fd加入到event事件队列中,设置回调函数sock_accept()

当有连接到来时,再调用sock_accept();sock_accept()执行accept函数产生一个新的socket fd,构造一个新的event对象,其fd为accept函数返回的fd,其监测状态为EV_READ,其回调函数为sock_read,将这个新event对象加入到event事件队列中.同时将main函数中的主event事件加入到event事件队列中,让我们设置的端口依然可以响应外部连接.

当通过accept新创建的这个 socket fd状态为可读时,sock_read函数被调用,他用函数recv从socket中读取信息,处理完业务逻辑后再调用send()返回结果给客户端..由于客户端通常是采用connect一次而请求多次的形式,所以在处理完成后,不能关闭这个socket,而且还要将在sock_accept中创建的这个监测是否有可读信息的event对象再将添加到event事件队列中.当然,你可以自己实现一个当recv到的数据是exit或者quit时就关闭连接的逻辑.

到此完成了整个调用.

上面有一个细节,就是在main函数中我们对event_set的调用是取了ev的地址,而ev的类型也不是一个指针,而在sock_accept里是申明了一个指针,然后用malloc来初始化的.当然,对evnet_set函数来说,只要是一个传入的地址就可以,我们这么做的原因是,在main函数中申明的ev会随着main的结束而被释放.当然main结束就是程序终止,释放是应该的,而我们不能让sock_accept中的对象在sock_accept执行完后就释放,因为sock_read还得用它呢.所以我们用了malloc.

实际情形

好了,上面其实也很简单.甚至可以说在真实环境下根本不够用,下面我们谈一下真正高并发请求下如何使用libevent编程.

首先考虑多线程的情况,多线程与libevent相结合的方式已经成为很多产品的选择,但是在libevent官方文档中指出,event_add函数是非线程安全的,如果我们使用多线程编程,必须做到的一点就是保证event_add函数不能出现在多个线程的执行流程中.

要实现这一点有两个方法:

  1. 后端应用逻辑的处理与libevent网络层处理分别用不同的线程.也就是网络层和具体的逻辑层分开,网络层将接收到的请求送入一个任务池就算完成任务,比如上面的只要accept创建了新的socket并将其加入任务池中就可以将主event重新添加到事件队列中.而后端启用一个独立的线程从任务池中取任务单元独立完成,这时所有任务是串行的,其实这根本就不是多线程处理.当然,这里说的是采用两个线程,网上也有人把网络层和逻辑层分布在不同机器通过网络通信实现.当然也是一种实现方式.
  2. 另一个方法比较直接.不是说event_add函数并不保证线程安全吗,那好,我把这个函数的处理放到一个池子里.通过单一的串行处理来不就行了.这时候我们的逻辑层完全可以是真正的多线程,每一个任务执行完后要将原事件再添加到event队列中时,不再调用event_add函数,而是将其放到一个池子中,由一单一线程将其一个个取出并串行的调用event_add添加到event队列中.

当然,还有人用每一个线程一个event_base的方法来实现,我个人是不太能接受这种山寨的方式.而且实现也不难想象,这里就不说了.

本人水平有限,如有错误,欢迎指正.

相关链接:

http://www.monkey.org/~provos/libevent/

http://hi.chinaunix.net/?20660017/viewspace-41444

标签:, ,

Redis数据库?-Redis的Virtual Memory介绍

redis 2 Comments »

众所周知,Redis是一个内存数据库,和Memcached类似,所有数据存在内存中,当然,Redis有rdb和appendonlyfile两个落地文件,可以对断电停机等故障下的数据恢复做一些保证.但是到2.0版本之前,Redis的所有数据在运行时都完全是内存读写.

然而在可见的未来,Redis的官网上已经有关于2.0最重要功能的预告,那就是Redis Virtual Memory.

在2.0的测试版本中已经包含VM的测试版代码,但实际上此功能已经比较成熟.使用方法是将配置文件中的vm-enabled参数设置为yes

简单来说,Redis Virtual Memory就是Redis将支持一些选项,通过配置,可以让用户设置最大使用内存,当超出这个内存的时候,通过LRU(Least Recently Used)类似算法,将一部分数据存入文件中,在内存中只保存使用频率高的数据.

这个方法和TokyoCabinet的xmsiz参数非常相似.在Redis里,这个配置名叫vm-max-memory

和TC一样,Redis并不会真的将所有大于vm-max-memory的数据存入内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘.

Redis官方文档对VM的使用提出了一些建议:

  • 当你的key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大.
  • 当你的key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如你可以考虑将key,value组合成一个新的value.
  • 最好使用linux ext3 等对稀疏文件支持比较好的文件系统保存你的swap文件.
  • vm-max-threads这个参数,可以设置访问swap文件的线程数,设置最好不要超过机器的核数.如果设置为0,那么所有对swap文件的操作都是串行的.可能会造成比较长时间的延迟,但是对数据完整性有很好的保证.

有了VM功能,Redis终于摆脱了受内存容量限制的噩梦了,似乎我们可以称其为Redis数据库了,我们还可以想象又有多少新的用法可以产生.当然,希望这一功能不会对Redis原有的非常牛B的内存存储性能有所影响.

其它还有一些知识点,可以参看官方文档:http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide

标签:, ,