精华帖 (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 的工作。 应该说这两种模型,都有各自的优点和缺点。多线程可以利用多核CPU的计算能力,但因此也会增加CAS自旋或者是锁的一些消耗,同时,如果线程过多,那么线程之间上下文的切换,也是一种消耗。
而如果是单线程,则可以完全避免锁的消耗,同时,上下文切换消耗也不需要过多的考虑(但仍需要考虑系统上还有其他的进程),这会让单个CPU的利用率比较高。
因此,像 redis 这种单线程的服务模型,如果对一些请求的处理相对较耗时,那其 TPS 也就相应的不能提高上去,也就是说其吞吐量会提不上去;但反过来想, redis 如果能控制每次请求在执行过程是简短并且快速的,那么也许使用单线程,反而会比多线程有更好的性能,毕竟单线程少了上下文切换,以及锁或者 cas 的开销。 而 tt server 则中规中矩:一个线程负责 accept ,一定数目的线程则进行请求的处理。因此,我们在设置 tt server 的时候,也应尽量考虑好工作线程的数目,尽量让CPU数目与工作线程数目一致或者略少。原则是最好的发挥多核CPU的作用,同时又不让工作线程之间去竞争 CPU。当然,这是需要不停的去实验的。 所以,在使用 redis 的时候,应尽量不要去使用一些相对耗时的请求;同时,我想 redis 的作者,也应该会尽量优化每种请求的执行速度(至少是一些常用的请求)。 而在使用 tt server 的时候,需要仔细调整使用的工作线程数目,让每个CPU都物尽其用。
数据存储方式、持久化比较tt server 的 hash 数据库,是使用文件的方式,然后利用 mmap 系统调用映射到内存中。
同时, tt server 也提供了 ulog 的方式,对数据库的变更操作进行记录,同样,可以利用 ulog 对 ttserver 进行恢复,但 ulog 的主要目的,按照我的理解,应是用来实现 replication 的。
而 redis 则是将数据直接写在了内存中,然后利用 redis 的持久化机制,将数据写到磁盘中。 redis 提供了两种持久化机制,分别是 RDB (redis DB) 和 AOF (appending only file)。
AOF的过程是:在执行每次命令之后,或者每隔1秒钟之后,Redis会有一个线程将命令以 redis 协议的格式 append 到文件中,这也就是AOF名字的由来,这些命令当然是非只读的,只读不更改数据库,没有必要记录下来。
对于第一个问题,也是一个权衡的问题,如果每次命令之后都进行一次写磁盘操作,那么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 都是在内存上面进行数据的读写,我但认为两个产品对数据存储方式的观点是不一样的。
可见,由于作者的观点不一样,也就造成了两种实现方式不一样的产品,这还是比较有意思的。
redis自带实现的VM将在以后不再使用(2.4将是最后一个自带vm功能的版本),作者认为数据就应该是放在物理内存中的,没有必要要将数据交换到磁盘中,磁盘只是作为日志的一种存储方式。这也是“内存是新的硬盘”思路的体现。
复制方式比较tt server 和 redis 都支持 master-slave 方式的通信复制。 而 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,能存的数据大小,只能限制在物理内存的范围以内。
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的压力;
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-11-17
写的不错,支持一个。
|
|
返回顶楼 | |
发表时间:2011-11-17
要控制数据丢失问题,对于关键数据必须有提交点的概念,并且提交点必须写物理盘,而且在涉及主备备份上,没有提交点,在同步过程主备切换数据丢失则是必然,这个最后又成了事务
1秒的间隔如果是发生在全速运行的异步队列,基本上代表至少丢失了w级的记录或者是几十m的同步日志 |
|
返回顶楼 | |
发表时间:2011-11-17
我想知道,楼主有没有对两个服务的性能做过测试。或者能测试出他们适合的场景。
|
|
返回顶楼 | |
发表时间:2011-11-18
引用 要控制数据丢失问题,对于关键数据必须有提交点的概念,并且提交点必须写物理盘,而且在涉及主备备份上,没有提交点,在同步过程主备切换数据丢失则是必然,这个最后又成了事务
1秒的间隔如果是发生在全速运行的异步队列,基本上代表至少丢失了w级的记录或者是几十m的同步日志 redis 本身是每次命令都有调用 write 操作的,支持以每隔一秒的间隔进行fsync,将 page buffer 的数据刷进磁盘,即使进程出现问题,操作系统仍然能写回磁盘的。 如果数据正在复制的时候,进程挂了,那么之前 write 的数据,都可以保存下来了,只要操作系统不挂。而正在进行的命令,虽然没有进行write,但也会因为进程挂了,没有返回正确的响应给客户端,那这个正在执行的命令是不用担心的,因为客户端已经认为它是没有执行成功的了。 同时,如果在复制过程中挂了,那么备份端很有可能只接收到一部分数据,就会造成另一部分数据被丢失。 如果在很短的时间内,master 起来之后,能够支持断点复制,那么数据就可以很快同步到位;如果不行,被备份端丢失的数据,就会比较久才能同步了。很不幸,redis 目前还不支持断点复制。不过新浪对redis的这个缺点,有了另外一个解决方案,但需要改造 redis(见原文最后一段)。 我觉得,就是看要把 redis 当成是 store 还是 cache,如果当成 cache,那么这个缺点就不是太突出。 这是我的理解,如果有问题,请帮忙指正 |
|
返回顶楼 | |
发表时间:2011-11-18
nklizhongnan 写道 我想知道,楼主有没有对两个服务的性能做过测试。或者能测试出他们适合的场景。
很惭愧,这个倒还没有测试过,如果有机会去实现一次对比测试的话,也是很不错的。 不过原文的文章也写了 新浪的 tim yang 曾经做过一次测试,得出的结论是 redis 对 5,000,000 量的小数据的读写性能非常不错,对 5,000,000 量的大数据的读写性能也不错,但没有小数据的性能好;而 tt server 则是对大数据的写性能比较好。因此,对于中小量的小数据,可能 redis 的读写性能更高;而对于中小量的大数据,可能 tt server 会更加合适。但这次测试至今也算比较久的了,如果有更新的测试,那就更好了。 不过我觉得 redis 更吸引人的,是它能支持较多的数据结构,这是 redis 很独特的优势。我想应该有很多场景,都可以从这方面考虑去使用 redis。 |
|
返回顶楼 | |
发表时间:2011-11-18
agapple 写道 写的不错,支持一个。
多谢你的指导啊,哈 |
|
返回顶楼 | |
发表时间:2011-11-21
两机或者多机的active-standby结构都是主要为了防止主机级的故障,尤其是类似单板掉电或者重启,这个时候buffer是来不及刷进实际物理文件系统的,所以此时的commit的物理写对于保护数据就尤其重要了,不管是单机还是多机active-standby这种主备同步,如果没有类似commit这种机制,那么client即使得到响应但实际仍然可能会响应后同步前因为主机故障导致内存中的数据没有刷向物理文件。以为提交成功实际数据并没最终成功写盘
而如果是active-standby这种主备同步,如果没有数据流中commit标识,在同步过程中主机当机,结果备机拉起来这种情况更加复杂,你甚至都很难判断到底丢了多少数据 如果是要保护数据,最好的方式还是双写,或者主备同步前阻塞client的write返回 redis 新浪的演讲听过,感觉其实主要还是当成比更快缓存在用,当一次机应该会丢不少数据 |
|
返回顶楼 | |
发表时间:2011-11-21
ppgunjack 写道 两机或者多机的active-standby结构都是主要为了防止主机级的故障,尤其是类似单板掉电或者重启,这个时候buffer是来不及刷进实际物理文件系统的,所以此时的commit的物理写对于保护数据就尤其重要了,不管是单机还是多机active-standby这种主备同步,如果没有类似commit这种机制,那么client即使得到响应但实际仍然可能会响应后同步前因为主机故障导致内存中的数据没有刷向物理文件。以为提交成功实际数据并没最终成功写盘
而如果是active-standby这种主备同步,如果没有数据流中commit标识,在同步过程中主机当机,结果备机拉起来这种情况更加复杂,你甚至都很难判断到底丢了多少数据 如果是要保护数据,最好的方式还是双写,或者主备同步前阻塞client的write返回 redis 新浪的演讲听过,感觉其实主要还是当成比更快缓存在用,当一次机应该会丢不少数据 嗯,说得有道理。 数据的安全性和性能的确是一个值得平衡的问题。 如果采用双写或者多写,那么对数据的一致性会有一个要求,如果同步write,又会影响性能;而如果要求性能,那么数据安全性又会下降。 我想不同的场景对数据安全的要求也不同。不管是金钱交易数据,还是缓存数据,对数据安全的要求也不同。 对于数据安全性要求较高的场景,或者是适合使用双写的场景,进行双写时,会带来哪些问题(比如数据一致性)?又有哪些相应的解决方法,能否详细讲讲? |
|
返回顶楼 | |
发表时间:2011-11-22
最后修改:2011-11-22
双写其实很容易,直接在客户端或者通过写请求往中央节点发送,中央节点负责双写
|
|
返回顶楼 | |