论坛首页 Java企业应用论坛

Tokyo Tyrant 与 Redis 的一些简单比较

浏览 7445 次
精华帖 (16) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-11-17   最后修改:2011-11-17

 

之前简单的看了一下 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 的性能,应该对这些耗时的命令进行一定的监控。

 

 

 

==================

 

ps: diecui1202 指出:

 写道
redis 则是每次 slave 重新连接到 master 时,master 会将数据进行全量的复制给 slave,而不是增量式的。

Redis复制与可扩展集群搭建这篇文章里提到一种“增量复制”的方式,大家可以参考一下:

1、 Master写AOF文件;

2、 在业务低峰期将进行内存快照,同时将AOF文件位置地一同写到快照文件中;

3、 当Slave来同步时,Master则是将上一次的内存快照sync给Slave;

4、 Slave根据快照文件构建内存,同时根据快照文件中的AOF文件位置来完成与Master的增量同步;

 

这个想法一方面可以提高Slave重建的速度,另一方面也可以降低Slave重建时对Master的压力;


 

 

   发表时间:2011-11-17  
写的不错,支持一个。
0 请登录后投票
   发表时间:2011-11-17  
要控制数据丢失问题,对于关键数据必须有提交点的概念,并且提交点必须写物理盘,而且在涉及主备备份上,没有提交点,在同步过程主备切换数据丢失则是必然,这个最后又成了事务
1秒的间隔如果是发生在全速运行的异步队列,基本上代表至少丢失了w级的记录或者是几十m的同步日志
0 请登录后投票
   发表时间:2011-11-17  
我想知道,楼主有没有对两个服务的性能做过测试。或者能测试出他们适合的场景。
0 请登录后投票
   发表时间:2011-11-18  
引用
要控制数据丢失问题,对于关键数据必须有提交点的概念,并且提交点必须写物理盘,而且在涉及主备备份上,没有提交点,在同步过程主备切换数据丢失则是必然,这个最后又成了事务
1秒的间隔如果是发生在全速运行的异步队列,基本上代表至少丢失了w级的记录或者是几十m的同步日志

redis 本身是每次命令都有调用 write 操作的,支持以每隔一秒的间隔进行fsync,将 page buffer 的数据刷进磁盘,即使进程出现问题,操作系统仍然能写回磁盘的。

如果数据正在复制的时候,进程挂了,那么之前 write 的数据,都可以保存下来了,只要操作系统不挂。而正在进行的命令,虽然没有进行write,但也会因为进程挂了,没有返回正确的响应给客户端,那这个正在执行的命令是不用担心的,因为客户端已经认为它是没有执行成功的了。

同时,如果在复制过程中挂了,那么备份端很有可能只接收到一部分数据,就会造成另一部分数据被丢失。
如果在很短的时间内,master 起来之后,能够支持断点复制,那么数据就可以很快同步到位;如果不行,被备份端丢失的数据,就会比较久才能同步了。很不幸,redis 目前还不支持断点复制。不过新浪对redis的这个缺点,有了另外一个解决方案,但需要改造 redis(见原文最后一段)。

我觉得,就是看要把 redis 当成是 store 还是 cache,如果当成 cache,那么这个缺点就不是太突出。

这是我的理解,如果有问题,请帮忙指正
0 请登录后投票
   发表时间:2011-11-18  
nklizhongnan 写道
我想知道,楼主有没有对两个服务的性能做过测试。或者能测试出他们适合的场景。

很惭愧,这个倒还没有测试过,如果有机会去实现一次对比测试的话,也是很不错的。
不过原文的文章也写了 新浪的 tim yang 曾经做过一次测试,得出的结论是 redis 对 5,000,000 量的小数据的读写性能非常不错,对 5,000,000 量的大数据的读写性能也不错,但没有小数据的性能好;而 tt server 则是对大数据的写性能比较好。因此,对于中小量的小数据,可能 redis 的读写性能更高;而对于中小量的大数据,可能 tt server 会更加合适。但这次测试至今也算比较久的了,如果有更新的测试,那就更好了。

不过我觉得 redis 更吸引人的,是它能支持较多的数据结构,这是 redis 很独特的优势。我想应该有很多场景,都可以从这方面考虑去使用 redis。
0 请登录后投票
   发表时间:2011-11-18  
agapple 写道
写的不错,支持一个。

多谢你的指导啊,哈
0 请登录后投票
   发表时间:2011-11-21  
两机或者多机的active-standby结构都是主要为了防止主机级的故障,尤其是类似单板掉电或者重启,这个时候buffer是来不及刷进实际物理文件系统的,所以此时的commit的物理写对于保护数据就尤其重要了,不管是单机还是多机active-standby这种主备同步,如果没有类似commit这种机制,那么client即使得到响应但实际仍然可能会响应后同步前因为主机故障导致内存中的数据没有刷向物理文件。以为提交成功实际数据并没最终成功写盘

而如果是active-standby这种主备同步,如果没有数据流中commit标识,在同步过程中主机当机,结果备机拉起来这种情况更加复杂,你甚至都很难判断到底丢了多少数据

如果是要保护数据,最好的方式还是双写,或者主备同步前阻塞client的write返回


redis 新浪的演讲听过,感觉其实主要还是当成比更快缓存在用,当一次机应该会丢不少数据

0 请登录后投票
   发表时间:2011-11-21  
ppgunjack 写道
两机或者多机的active-standby结构都是主要为了防止主机级的故障,尤其是类似单板掉电或者重启,这个时候buffer是来不及刷进实际物理文件系统的,所以此时的commit的物理写对于保护数据就尤其重要了,不管是单机还是多机active-standby这种主备同步,如果没有类似commit这种机制,那么client即使得到响应但实际仍然可能会响应后同步前因为主机故障导致内存中的数据没有刷向物理文件。以为提交成功实际数据并没最终成功写盘

而如果是active-standby这种主备同步,如果没有数据流中commit标识,在同步过程中主机当机,结果备机拉起来这种情况更加复杂,你甚至都很难判断到底丢了多少数据

如果是要保护数据,最好的方式还是双写,或者主备同步前阻塞client的write返回


redis 新浪的演讲听过,感觉其实主要还是当成比更快缓存在用,当一次机应该会丢不少数据


嗯,说得有道理。

数据的安全性和性能的确是一个值得平衡的问题。
如果采用双写或者多写,那么对数据的一致性会有一个要求,如果同步write,又会影响性能;而如果要求性能,那么数据安全性又会下降。

我想不同的场景对数据安全的要求也不同。不管是金钱交易数据,还是缓存数据,对数据安全的要求也不同。

对于数据安全性要求较高的场景,或者是适合使用双写的场景,进行双写时,会带来哪些问题(比如数据一致性)?又有哪些相应的解决方法,能否详细讲讲?
0 请登录后投票
   发表时间:2011-11-22   最后修改:2011-11-22

双写其实很容易,直接在客户端或者通过写请求往中央节点发送,中央节点负责双写
双写理论上不太会带来太大性能负担,因为可以不阻塞执行,但是client得到的响应会稍微慢些,是两节点里面最长的写入时间。一个后果是无法避免的,就是server端维护client的结构消耗的内存相对之前会高不少,消耗数目会和client写的平均时间成正比,如果是简单多线处理,堆积的线程数也会和client写的平均时间至少成正比
双写最大的问题还是解决一个当机了怎么办的问题,在我们系统我们为下级的网元做同步主备集群,方式就是双写失败采取将失败节点操作在正常节点上序列化,一旦失败节点恢复,则序列化的没同步的操作会在该节点全部执行。
其实可以看到这就是双写+主备的变种方案,这种方案实现不难,相对效果也还行但实际上也有局限性:
1.备用节点恢复后,追上主节点可能要花很长时间,这段时间如果发生二次故障比如主节点失败则备用节点此时到底是切换成主节点还是停止服务。
2.如果数据不断往系统灌,主节点还原成双写状态需要在某个接近完全同步的时间点阻塞client的请求处理等备节点,可能瞬间造成大量请求堆积


双写本身比较简单。但是要注意的是如果要绝对保证数据一致性,采取双写策略最安全的方式只有双写不成功立即停止业务,解除故障后才能继续,即单点故障会导致服务中断,这可以保证主备节点数据的绝对可靠性,但代价就是有服务中断的时间窗口,这实际是牺牲了可用性换取了数据的可靠性和容灾,它是将可靠的可恢复放在第一位,而不是快速可恢复。
数据主备同步也是一样的,不过如果依赖数据库主备同步并阻塞client的write返回来保证数据一致性,则明显问题也还有两个:
1.性能,客户端的响应速度比双写会慢很多,这导致响应时间从max(n1,n2)变成了n1+n2
2.备当机了,则服务同样中断,还是失去了可用性

如果同时要保证服务高可用、高性能和数据可靠,可能1对主控节点+3存储节点处理灾难会容易些,空想中的一个结构:
client--主控-----a
           |   |
             c     |__b

主控对a,b双写,c与a或b组成主备的异步同步,一旦a当机则b阻塞请求处理,等待c完全同步后b、c重新构成双写,b当机情况类似,c当机则a,b同时保存当机时间窗口的本地log历史操作,使得c节点恢复后能追上a,b,主控则可以采取双击同步主备维护a,b,c状态

即使有更经济的方案,但低成本(低节点数)、高可用(多点复制快速切换)、高可靠(一致性)难以兼得这是一定的
没有人为干预能够保一次性单点故障的方案不难,但是要设计无过多人为干预的能自动处理连续单点故障的系统就比较头疼
最简单的系统就是无阻塞的主备异步log同步,但是结果就是结合auto fail over来提供高可用则必然导致的数据丢失,区区几秒的异步数据丢失在满载的高性能系统中损失的可能至少是好几w的记录

 

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics