05 Feb 2015
为什么我们不使用Redis做存储数据库?
Redis 以其强劲的性能、完善的数据结构、丰富的数据操作,再加上数据持久化、数据同步,以碾压 memcached 的姿态迅速在我们的项目中普及。但是在我们大部分的应用,Redis 承担的角色都是缓存数据库,要么后端还有其他数据库做永久存储使用,要么存储的数据是临时的,允许一定程度的数据丢失。
这次我们基于 Cotton 在上层搭建一个类 filepicker 的服务,需要在数据库中存储文件的元信息。基于 Cotton 的经验,400万文件占用的所有 Redis 内存是1.5G,还不说这个400万个文件都有冗余备份。基于这样的场景类比,觉得使用 Redis 作为存储数据库,内存占用不是什么问题。再加上 Redis 其他数据库无法比拟的性能,想想都醉了。所以就开始了使用 Redis 做存储数据库的思考。
细想之下要想 Redis 作为存储数据库, Redis 需要满足3点:
- 数据 实时 持久化。
- 副本集。
- 扩容。
数据实时持久化
Redis 提供了两种 持久化存储 的方式:
- RDB 持久化。可以在指定的时间间隔内生成数据集的时间点快照。优点是:文件紧凑,占用空间小,用 RDB 文件恢复速度快,适合作为备份及灾难恢复。但是单次 RDB 开销比较大,只能设置间隔一段时间执行。如果两次 RDB 的间隔,Redis 挂掉,这间隔的数据更新就丢失了。
-
AOF 持久化。记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。因此,AOF 文件必然会比 RDB 文件占用的空间大,恢复数据时挨个执行一遍写命令,恢复速度自然比不上 RDB,在开启
fsync
的情况下,速度会比 RDB 慢。但是最大的优点是:可以 准实时 持久化,通过配置fsync
策略,最大可能减少数据丢失的风险。- fsync 配置的策略是每秒 append 下 Redis 写命令,最多只会丢失这一秒的数据。Redis 默认的 fsync 策略。
- fsync 配置的策略是同步 append Redis 写命令,这样不会丢数据,但是性能就没有那么快了。
聪明的你肯定会发现,能不能这样干呢: 同时使用 AOF 和 RDB 持久化,平时 append 写命令到文件,定时 RDB 持久化同时清空 AOF 文件,从 RDB 持久化后的时间点重新 AOF,恢复的时候先从 RDB 恢复再从 AOF 恢复。看起来不错哦,只是目前 Redis 不支持,让我们憧憬下美好的未来。
Note: for all these reasons we’ll likely end up unifying AOF and RDB into a single persistence model in the future (long term plan).
在目前阶段,使用 AOF 也不错。
- 占用空间大点,不就是耗费点磁盘空间。
- 恢复速度慢点,其实不算慢。顾爷在公司分配的虚拟机上,400万写命令也就21秒。
- 如果 fsync 同步写,确实慢很多,但是改成每秒,也能接受。再说 filepicker 典型的写多读少,即使开启 fsync 同步写,也不为过。
副本集
前面讲到,使用 AOF 也不错,如果能包容 AOF 的缺点,AOF 能够提高实时持久化,其 AOF 文件也能拿来定时备份,以及容灾恢复,貌似很不错的选择。但是定时备份依然有丢数据的风险,比如定时备份的间隙磁盘挂了,不能读盘了。因此最好的方法就是 Replica Sets ,保存副本集,既能够实时备份,又能够快速容灾恢复。不用说,实现 Replica Sets 的方式,就是 Master-Slave 架构了。
+---------+ +---------+
| Master | <===== | Slave |
+---------+ +---------+
那么问题来了?使用 Master-Slave 架构后,Master-Slave 是否需要开启 AOF 持久化?有一种观点是: Master 不开持久化保证访问性能,Slave 开 AOF 持久化(哪怕 fsync alwarys 都可)保证数据不丢失 。貌似看起来 perfect 的方案!!有木有风险?
- Master 如果重启会发生什么?Master 重启后,由于没有开启持久化,不需要从 AOF 文件恢复数据,重启后,会是一个空的数据集,此时 Slave 监测到了 Master 起来了重新同步,从而也得到 一个空的数据集 。你没有看错,数据因此就丢了。因此如果 Master 没有开启持久化,那么 Master 就一定不能随便重启,包括随便自动重启、开机启动等。
- 如果保证 Master 不会自动重启、随机重启后,还有木有问题呢?我们想想这样一种场景,Master 挂了,也没有随机重启,Slave 上面的数据还没有丢,此时为了恢复服务,需要将 Slave 变成 Master,原来的 Master 恢复后变成 Slave 从新 Master 同步数据,角色互变后,此时的 Master 开启了持久化,Slave 关闭了持久化。为了维护我们初衷,需要手动关闭 Master 的持久化以及自动重启,手动开启 Slave 的持久化和自动重启。这对于运维来说,是个灾难。
因此从这里来说,还是 ** Master-Slave 都要开启持久化比较好** 。那如果 Master 挂了后,该如何处理呢?是否应该将 Slave 变成 Master 去承载服务呢?这样就涉及到了一个问题,Master 和 Slave 的数据谁更新?
- 如果 Master 开启了
fsync always
,由于 Slave 同步延迟的缘故,Master 的数据会更新。 - 如果 Master 开启了
fsync every second
,这个时候 有可能 数据已经同步给了 Slave ,但是还没有 fsync 到 Master 的 AOF 文件,所以有可能 Slave 数据更新。
头痛啊,到底哪个数据更新,该从哪个恢复呢?又给运维带给了困难。
扩容
由于 Redis 的数据都必须在内存中,内存资源有限,随着数据量上涨,单台服务器必然不能承载,因此需要横向扩容。很多童鞋觉得,Redis 扩容很方便,只要根据 KEY 进行哈希算法(比如一致性哈希)即可分布式。使用 Redis 作为数据缓存确实可以这种,当对 KEY 进行哈希算法寻找 Shard 节点,扩容后如果查询没有命中,从后端的数据存储数据库取数据,重放数据到该 Shard Redis 节点中。但是结合我们今天的主题,Redis 就是作为后端的数据存储数据库哦,扩容 Rebalance 的过程如何实现?或许有 Rebalance 的方法,但是是否成熟,是否有坑儿,项目是否敢试用?
结论
使用 Redis 作为存储数据库,我的结论是:
- 数据库持久化 Persistence 的方法比较成熟。
- 副本集 Replication 的运维有一定难度。
- 扩容 Rebalance 还不见官方方案。
所以要慎用哦。