Tokyo Tyrant 与 Redis 的一些简单比较

之前简单的看了一下 Tokyo Tyrant(包括 Tokyo Cabint) 在 hash 存储上的一些实现,最近 Redis 又比较火热,因此,自己也尝试性的去了解了一下 Redis,并且结合 Tokyo Tyrant(以下简称 tt server),说说自己对这两种产品的看法。

服务端处理模型

在 tt server 中,是以多线程的方式向客户端提供服务的:一个主线程负责 accept 客户端的socket,一定数目的线程(可以指定)进行读写服务,同时,也有一定数目的timer线程,专门用来负责定时的任务,比如一些定时的 Lua 脚本,同时,如果是slaver,则会有专门一个timer线程,定时负责 do slave 的工作。
而在 Redis 中,采用的则是单线程的模型来处理所有的客户端请求。

应该说这两种模型,都有各自的优点和缺点。多线程可以利用多核CPU的计算能力,但因此也会增加CAS自旋或者是锁的一些消耗,同时,如果线程过多,那么线程之间上下文的切换,也是一种消耗。

而如果是单线程,则可以完全避免锁的消耗,同时,上下文切换消耗也不需要过多的考虑(但仍需要考虑系统上还有其他的进程),这会让单个CPU的利用率比较高。
但是,单线程服务,就意味着不能利用多核。同时,服务端对客户端过来的请求是串行执行和响应的,这也在一定程度上,会影响服务端的并发能力,特别是在有些请求执行比较耗时的情况下。想象一下,就这么一个线程,可能正在拼命的执行客户端A的一个请求,而此时客户端B,C,D的请求,还仍在等着线程执行完成之后再去搭理他们。

因此,像 redis 这种单线程的服务模型,如果对一些请求的处理相对较耗时,那其 TPS 也就相应的不能提高上去,也就是说其吞吐量会提不上去;但反过来想, redis 如果能控制每次请求在执行过程是简短并且快速的,那么也许使用单线程,反而会比多线程有更好的性能,毕竟单线程少了上下文切换,以及锁或者 cas 的开销。

而 tt server 则中规中矩:一个线程负责 accept ,一定数目的线程则进行请求的处理。因此,我们在设置 tt server 的时候,也应尽量考虑好工作线程的数目,尽量让CPU数目与工作线程数目一致或者略少。原则是最好的发挥多核CPU的作用,同时又不让工作线程之间去竞争CPU。当然,这是需要不停的去实验的。

所以,在使用 redis 的时候,应尽量不要去使用一些相对耗时的请求;同时,我想 redis 的作者,也应该会尽量优化每种请求的执行速度(至少是一些常用的请求)。

而在使用 tt server 的时候,需要仔细调整使用的工作线程数目,让每个CPU都物尽其用。

数据存储方式、持久化比较

tt server 的 hash 数据库,是使用文件的方式,然后利用 mmap 系统调用映射到内存中。
这样,就可以利用操作系统的机制,不定期地将数据 flush 到磁盘中。同时,tt server 也提供了 sync 命令,可以让客户端手动将数据 flush 到磁盘中(使用 msync 系统调用)。最后,在关闭 tt server 进程的时候,应该使用 kill -15(TERM信号),或者使用 ttserver 自带的命令:ttserver -kl pid 进行关闭。这样 ttserver 会先把数据 flush 到磁盘上,再退出进程。

同时, tt server 也提供了 ulog 的方式,对数据库的变更操作进行记录,同样,可以利用 ulog 对 ttserver 进行恢复,但 ulog 的主要目的,按照我的理解,应是用来实现 replication 的。

而 redis 则是将数据直接写在了内存中,然后利用 redis 的持久化机制,将数据写到磁盘中。

redis 提供了两种持久化机制,分别是 RDB (redis DB) 和 AOF (appending only file)。
RDB的过程是:redis 进程 fork 一个子进程,然后子进程对内存中的数据写到一个临时文件,这个时候,两个进程就利用了操作系统的 copy on write 机制,共享一份内存数据,只有当父进程(也就是 redis 进程)对原有的数据进行修改或者删除之后,操作系统才为 redis 进程重新开辟新的内存空间(以页为单位)。Redis 本身也提供了 bgsave(background save) 命令支持手动将数据持久化( save 命令是同步的,而 redis 只有一个线程在服务,结果就是影响 redis 的性能,特别是在大数据量的情况下)。

AOF的过程是:在执行每次命令之后,或者每隔1秒钟之后,Redis会有一个线程将命令以 redis 协议的格式 append 到文件中,这也就是AOF名字的由来,这些命令当然是非只读的,只读不更改数据库,没有必要记录下来。
这里会有两个问题:
1、每次命令之后写文件,还是隔1秒之后写文件,影响会有哪些?
2、这些文件总会不断的膨胀,如何对文件进行压缩呢?

对于第一个问题,也是一个权衡的问题,如果每次命令之后都进行一次写磁盘操作,那么IO的程度可想而知,肯定会影响服务器性能(使用 write 系统调用,会因为文件系统而进入 page buffer,并非立刻写磁盘,而调用 fsync ,则会将 page buffer 中的数据写入磁盘,进行 IO 操作)。而如果每隔1秒进行一次 fsync,那么在这一秒和上一秒之间,如果服务器突然断电,那很有可能这些数据就会丢失。对于这个问题,redis 默认给出的方案是每隔1秒进行一次write。对于1秒的给定,我想,也是基于性能和数据安全的权衡,在性能和数据安全方面都可以让人接受。

对于第二个问题,redis 提供了 rewrite 的机制:当 aof 过大的时候,redis可以自动的进行 rewrite (从 redis 2.4 开始)。rewrite 的过程也是 fork 一个子进程;然后打开一个临时文件,将内存中的数据写入到文件中;在此期间,主进程继续将数据写入老的 aof 文件,同时也会将数据写入到一个内存缓存中;等子进程完成之后,主进程会将缓存中的数据写入到临时文件,再将临时文件进行rename,替换掉原来的文件。这样,就实现了写 aof 过程中的rewrite。

从数据的存储方式来说,尽管 tt server 和 redis 都是在内存上面进行数据的读写,我但认为两个产品对数据存储方式的观点是不一样的。
tt server 是将磁盘上的文件当作主要的存储方式,然后使用 mmap 将文件映射到内存中。本质上,这是数据应该存储在磁盘中的观点。
而 redis ,一开始就是将数据直接存储在内存中,在之后的持久化过程中,可以理解成只是将数据的日志写入到磁盘中。本质上,这是把数据应该存储在内存中的观点。

可见,由于作者的观点不一样,也就造成了两种实现方式不一样的产品,这还是比较有意思的。
从这个层面上来讲,我更加喜欢 redis 作者的思路,很可能作者就是受到 内存是新的磁盘,磁盘是新的磁带 的启发。

redis自带实现的VM将在以后不再使用(2.4将是最后一个自带vm功能的版本),作者认为数据就应该是放在物理内存中的,没有必要要将数据交换到磁盘中,磁盘只是作为日志的一种存储方式。这也是“内存是新的硬盘”思路的体现。

复制方式比较

tt server 和 redis 都支持 master-slave 方式的通信复制。
tt server 使用了 ulog,并且 slaver 使用了 rts(replication time-stamp) 文件,对上一次的复制时间戳进行保存,实现了复制的续传。

而 redis 则是每次 slave 重新连接到 master 时,master 会将数据进行全量的复制给 slave,而不是增量式的。redis 复制的方式与使用 RDB 持久化方式原理基本相同,也是使用子进程进行内存的dump,在此期间,父进程收集改变数据库的命令,等把子进程收集的数据传输给 slave 之后,再将此期间收集到的数据也传输给 slave。

如果从 slave 数据重建的角度来看,tt server 支持断点复制的实现,应该说是比 redis 先进了一步。

性能方面比较

新浪的 Tim Yang 做了 memcacheDB、Redis、tt server 的性能测试。这是比较早期的测试,相信随着版本的升级,两者的性能都会有所提升。不过按照这个测试的结果来看,redis 在数据量不多(500W)并且value 较小的时候,性能表现是很优越的;而对于稍大一些的 value ,tt 则在写方面表现很出色,但写的性能,相对较差。相比之下,redis的读写性能,倒是比较平衡。
但觉得随着时间的迁移,这个测试的参考性可能会打折扣,如果有可能的话,希望能看到更多的测试结果。

总结

1. 从服务器模型来说,tt server 使用 acceptor + workers 的方式提供服务,能够利用多核的性能,但随着而来的是一些同步、加锁的复杂和开销;而 redis 使用了单线程提供服务,利用不了多核,但如果能够将每次服务的速度控制下来,对单个CPU的利用率,反而可以提高。如果想利用机器的多核性能,也可以在一台机器上搭建多个 redis 实例,但可能更要考虑到机器的内存限制。

2. 从数据存储的方式来说,尽管 tt server 和 redis 都是将数据存储在内存中,但我认为两个产品对“数据是如何存储”的观点是有所不同的。tt server 认为数据是存储在文件中的,只是通过内存映射,将对文件的操作转化成对内存的操作;而 redis 是直接将数据存储到内存中,之后再通过持久化等机制,将数据备份到磁盘中。虽然之前 redis 自己实现了 vm 功能,但redis 后续会取消掉自己实现的 vm 功能,按照“内存是最新的磁盘”这种思路,也就不难理解了:除了增加复杂度之外,还有一个因素,那就是 redis 不需要 vm,能存的数据大小,只能限制在物理内存的范围以内。
从这个方面来将,redis 后续的版本可能就会限制用户使用的数据库大小是要小于物理内存的,而如果使用 tt server ,则用户须让使用数据文件小于物理内存,否则,发生内存交换,是非常损性能的。
总而言之,在使用内存数据库的时候,应该有意识的对数据进行容量规划,避免出现物理内存不够而引起的内存交换。

3. tt server 和 redis 的策略都是从 slaver 配置 master ,而不是从 master 配置 slaver 关系,这样就减轻了 master 的负担,同时,master 不必知道自己有多少个 slaver ,就可以横向的扩增 slaver 。但 tt server 支持所谓的断点复制。需要考虑到的是 redis 在做 replication 的时候,是 fork 一个子进程工作的,如果有多个 replicate 的请求,redis 依然还是一个子进程在工作。这样也会对多个 slaver 产生一定的复制延时。

4. redis 在工作方式上,会 fork 子进程,因此 redis 在容量规划上,需要考虑到 redis fork 出子进程所需要的内存和 CPU,在最差的情况下:bgsave时候,父子两个进程虽然可以使用 copy on write 的好处,但如果在此期间整个表记录都被修改了,那就足足需要一倍的内存,否则,此时父进程会进行 copy ,父进程很可能没有内存可用,就需要进行内存交换,由此所带来的性能代价也是非常高的;与此同时,子进程子在 bgsave 的时候,需要对数据进行压缩,压缩是计算密集型的,因此最好不要和父进程使用同一个CPU,因为父进程使用了单线程事件处理的模型,这种模型的优点是充分利用CPU的资源,如果出现子进程与父进程抢CPU,那就得不偿失了。

5. redis 支持较多的数据结构,但在使用 sort 等时间复杂性较多的命令时,也会稍微的降低 redis 的性能,应该对这些耗时的命令进行一定的监控。

Posted in Linux, nosql | Tagged , , | Leave a comment

内存学习——虚拟内存

接着上次学习虚拟内存的文章继续学习。

虚拟内存如何实现

虚拟内存是以虚拟页面的单位组成的。
虚拟页面使用磁盘来作为自己的存储,一些经常使用到的页面,才会被load到内存中,此时,就相当于把内存看成是磁盘的一个缓存。
因为每次都load到内存的是一个虚拟页面,所以对应的,内存就需要有一个物理页面能过装下这个虚拟页面。因此,物理内存也被分隔为一个一个的物理页面。虚拟页面和物理页面应该是相等的。
既然要把虚拟页面和物理页面关联起来,那么就需要维护虚拟页面和物理页面之间的关系。
因此,就又需要这样一张页面来维持这样的关系,通常,这张表是放在物理内存中,由操作系统来维护的,叫做页表。

基于这种实现,实际上虚拟内存就有能力解决之前所遇到的问题:每个进程都可以使用相同的虚拟地址,操作系统会将虚拟地址翻译成不同的物理地址,但对进程来说,是透明的;每个进程也都不会因为物理内存受限而不能加载运行,实际上虚拟内存是存在磁盘的,而通常磁盘要比内存大很多(虚拟页面一般放在磁盘的swap分区);同时,虚拟内存通过维护虚拟页面和物理页面的页面,可以实现权限控制,而这种权限控制,就是搭载在页面上面的,因此,一般的内存权限控制,都是以页为单位的。

虚拟内存的访问过程

当程序访问的虚拟地址在页表中找不到对应的物理页时,这时候,就会向磁盘读取此虚拟页面,并且将此虚拟页面与一个物理页面关联起来,并且在页面记录这种关系。当物理页面都已经被占满的时候,操作系统就会踢掉一个用的少的物理页面,从而让这个新的虚拟页面装入物理页面。 —— 这和我们使用缓存的逻辑没有什么不同 —— 操作系统将虚拟页面放入物理页面的动作,是由异常机制触发的。
而当程序中访问的虚拟地址已经在页表中有对应的物理页面时,这一切就变得相当轻松了。同时,内存权限的控制,也是通过在页表中的记录进行的。

总结

虚拟内存通过一个存储器层次的概念,将使用物理内存碰到的几个限制,就都统统解决了。我想,这和大学时候老师教我们在解决难题时候的思路一样,进行空间的坐标转换,换个角度,问题说不定就得以迅速解决,而且简单,高效。

Posted in Linux | Tagged , , , | Leave a comment

内存学习——为什么需要虚拟内存

关于虚拟内存,物理内存,我有蛮多概念都是很模糊的,今天下午看了一下虚拟内存,也算是有了一点小收获,本文就针对为什么需要有虚拟内存的理解写下来。

同时,我也希望自己能够陆续学习linux内存管理的知识,并且写出一些文章,来记录自己的一些理解。
如果您觉得有任何问题,可以留下评论,我们一起讨论,毕竟越辩越明。

为什么要使用虚拟内存

之前的计算机系统,是使用物理地址来使用内存的,这样,CPU就根据某个寄存器中相应的值,直接到物理内存去取值了。
这样的好处就是非常直接,非常容易理解。

而缺点是,我们需要知道物理地址的值,每次程序开始执行,也就是执行程序从磁盘被load到物理内存中之后,我们必须告诉CPU,程序是从哪一个地址开始执行的(即PC寄存器的值);还有一个致命的缺点是:程序使用的内存会被物理内存所限制,比如我们的机器上只有512M内存,那我们的程序就不能使用需占1G内存的程序了。这点或许是催生虚拟内存产生的最主要原因。

虚拟内存的概念

为了解决上面的的问题,就产生了虚拟内存的概念。那什么是虚拟内存呢?这里我先说说自己对存储器层次结构的理解。

计算机中有一种存储器层次的原则。我的理解是,CPU使用的数据都是基于寄存器的,如果我们的寄存器足够的大和多并且足够便宜,那么也就没有后来那么多的东西产生了。而问题就是寄存器足够快,但其造价却非常昂贵,因此,考虑到成本,就有了之后的CPU高速缓存,主存(就是我们常说的内存)和硬盘,甚至磁带等存储器了。

那么,计算机是如何让这些速度快慢不一、容量大小不一的存储器在一起工作的呢?一个方案就是存储器层次结构

对于这个存储器层次结构,我的理解是:CPU是和寄存器打交道的,但寄存器的容量毕竟有限,因此就需要高速缓存存储器来作为寄存器的缓存,当有些数据在寄存器中找不到时,CPU就可以去寻找高速缓存这个存储器中的内容,如果告诉缓存器还没有这个数据,那我就去主存中再去寻找这个数据,如果主存中也还没有,那就去磁盘中找吧。

这也是我对存储器层次结构的一个理解。

以上说了一通,但都没有提到虚拟内存,那虚拟内存究竟是什么?他在这个层次中处于哪个位置呢?

实际上,按照这种模型去思考的话,就可以这样理解:虚拟内存就是去解决主存到磁盘这个层次的方案

没错,我认为虚拟内存就是一种方案,而且他是非常重要的,为什么呢?众所周知,CPU的速度很快,内存就是作为匹配CPU和磁盘之间速度的一个中间层,高速缓存其实也是这样一个缓存的角色,但问题是,如果高速缓存失效,那么CPU会去访问内存,这样的速度只是降低了十倍的数量级;而如果是内存失效,让CPU去访问磁盘的话,这样的速度却是降低了十万倍到百万倍的数量级。

可见,在寄存器、高速缓存和主存之间缓存的失效,结果还是可以让人接受的(想想java的volatile关键字),但主存和磁盘之间缓存的失效,就会给程序造成比较大的性能影响了,所以我们应该努力避免主存的失效,这也是虚拟内存所必须要解决和面对的问题之一。

总结

其实可以认为,程序使用的内存,都是用的虚拟内存,因此也就没有了物理内存的限制(但还是限制于计算机的寻址位数,比如32位和64位,因为虚拟内存系统需要使用到物理内存)。他可以把自己的一部分放在物理内存中,还有一部分当做缓存放到磁盘中。另外,虚拟内存有相应的虚拟地址,因此,他就可以做到对于每一个程序来说,使用的都是相同的虚拟地址,这些虚拟地址,则可以映射到不同的物理地址,也就是说每个程序都可以把自己想象成自己拥有整台机器的内存。

Posted in cache, Linux | Tagged , , , | 3 Comments

阿里巴巴B2B招聘高级java工程师

阿里巴巴B2B招聘高级java开发工程师

我不想列“精通xxx…熟悉xxx”,只要求,如果您:

有2年或以上java实际开发经验,或
1年以上java实际开发经验但技术能力较强
就能直接联系我: 直接在此帖留言或直接联系我

通过沟通我们可以谈简历的事情,走内部推荐,1.电面2.来杭面试,流程简单,全程报销路费;

P.S. 年初,各大公司招聘旺季,阿里巴巴这里呢,我不想说有多好,但也绝对不算差,最实际的,薪酬待遇,各大公司基本保密,但其实业内人士大多心里也有数,秘而不宣;所以,待遇方面不用过多担心,请诸君仔细斟酌,欢迎联系!

再P.S. 为什么我这招聘帖这么简单呢?其实你懂的,“精通xxx熟悉xxx”那只是吓唬小菜的,对“高级java开发工程师”而言没有意义,我们需要的只是充分沟通、im沟通+当面沟通。在这个有点糟糕的时代,我们人人都不仅需要money,也需要平台与机遇,更需要个人修为与成长!请给阿里和您自己一个机会,谢谢!

版权注明: 本文的招聘文字来自 @pf_miles ,详见:http://wenyue.me/blog/292

Posted in life | 6 Comments

让Ubuntu32位支持4G内存的方法

在Ubuntu中实现4G内存的方法很简单.

而且经过测试,完全符合我们一般的工作应用和开发应用.

当然,我们的方法不一定是最简单的,如果你有更好的方法,欢迎留下你的做法.

如果你实在发现在支持 pae 的内核下,你的系统不稳定,你可以做两件事情:
1. 把内核切换回原来的 generic 内核;
2. 去买张彩票 :)

下面是我们的实践方法:

  1. 安装 generic-pae 内核
  2. 重启,检查系统是否识别成功
  3. 如果发现有问题,切换回 generic 内核

安装generic-pae内核

针对 ubuntu, 可以使用如下命令:

$ sudo apt-get update
$ sudo apt-get install linux-headers-server linux-image-server linux-server

重启,检查系统是否识别成功

可以先用 uname -a 看看内核是否变成 generic pae 了? 如果还不是 pae, 可以用 grub 切换到 pae 内核. 切换方法与下方的<切换回generic内核>道理一致

使用以下命令:

free -m

在 total 一栏中,看有没有识别出4G内存.

如果发现有问题,切换回 generic 内核

如果你的系统正常,并且正确识别了4G内存,就可以不用往下看了.

如果你发现有问题,想切换回 generic 内核,就接着往下看吧.

切换回内核,这里我们使用 grub 去解决.

首先查看 grub 版本号: grub-install -v

如果是 1.9 或者以上,则表示你是 grub2 ,如果是以下,则表示你是 grub 1

针对 GRUB 1

直接修改 /boot/grub/menu.lst 文件, 把 default 的值设置成 需要启动内核版本 的序号.

针对 GRUB 2

我的做法是:

1. 查看 /boot/grub/grub/grub.cfg 文件,找到 menuentry 中所对应的内核,记住我想要启动内核的序号,从 0 开始算.

2. 修改 /etc/default/grub 下 GRUB_DEFAULT 的值,也就是启动项

3. 执行 sudo update-grub , 自动生成  grub.cfg

4. 重启即可.

5. uname -a 查看你现在的内核,如果你再不爽,可以把 generic pae 内核给删了.

Posted in Ubuntu | Tagged , , | Leave a comment