Redis事物、持久化、复制

258 阅读1分钟

Redis的事务只能保证不被打断,不能保证回滚。

multi

op

op

op

exec

multi和exec的主要作用是移除竞争条件,并且可以减少客户端和服务端之间的通信次数,提升性能。

$ setkey value

$ expire key 100

$ ttl key

Redis提供两种数据持久化的方式,快照RDB和只追加文件AOF。这两种持久化的方式可以选择性的使用。

save 60 1000

rdbcompression yes

dbfilename dump.

rdb

appendonly no

appendfsync everysec

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

dir ./

以上就是一些简单的持久化配置。对于快照文件持久化,可能会丢失最近一次创建快照之后写入的所有数据,快照文件可以通过以下方式触发。

1 客户端向Redis发送BGSAVE-(background save)命令来创建快照。

2 客户端向Redis发送SAVE命令来创建一个快照,Redis服务端在执行。

3 如果配置了save 60 10000表示60秒内完成10000此写入就会触发BGSAVE。

4 当Redis通过SHUTDOWN命令接收关闭服务器的请求时,或者接收到TERM信号时,会执行一个SAVE命令。

5 当一个Redis服务器连接另外一个Redis服务器,并向对方发送SYNC命令来开始一次复制操作时,如果主服务器没有在执行BGSAVE操作,或者主服务器并非刚刚执行完BGSAVE操作,那么主服务器就会执行BGSAVE操作。

使用快照可能丢失数据,使用需谨慎,最好确定能够丢失多长时间的数据,来设置Save操作,快照不能太频繁,也不能太稀少。比如在我们处理日志文件的时候,可以没1000行将处理的进度存入Redis,即使程序崩溃,我们也可以继续执行执行之前未完成的处理工作,我们还可以使用事务流水线来将进度和处理结果同时记录到文件里。

当Redis存储的数据只有几GB的时候,使用快照来保存数据是没有问题的。可能比我们读这句话的时间还要短。由于BGSAVE会开启一个子进程去进行快照写,因此需要剩余内存比较多,因此当Redis存储了数十G的数据后,剩余内存不足够的情况下,BGSAVE可能会导致系统长时间停顿,也可能导致大量的使用虚拟内存,导致Redis的性能降低到无法使用的程度,停顿的原因和Redis内存中存储数据的多少成正比。在一台68GB的Xen虚拟机上,对一个占用50GB内存的Redis服务器执行GBSAVE命令的话,光是创建子进程就需要花费15秒以上,而生成快照文件更是需要15-20分钟,这时候我们可以考虑使用SAVE命令,花费3-5分钟就可以完成快照的生成工作,对于那些对快照次数不那么频繁的程序,我们可以在不繁忙的情况下执行脚本来调用SAVE命令,然后备份生成的快照,然后继续为客户端继续执行操作。

还是那句话,如果你允许数据的丢失,那么使用快照是一个不错的选择,否则可以使用AOF来将存储在内存里面的数据尽快地保存到硬盘里。

文件写入=写入缓冲区+通知操作系统尽快写入硬盘+写入硬盘。

使用Redis建议设置为每秒同步,这和不使用持久化时的性能相差无几,这样能保证即使程序崩溃也只丢失一秒的数据。但是AOF的文件体积是一个问题,首先可能撑爆磁盘,也使得故障重启时非常慢。这时候我们可以向Redis发送BGREWRITEAOF来移除AOF文件中的冗余命令,这也会开启一个后台进程去执行重写,我们也可以设置auto-aof-rewrite-percentage和auto-aof-rewrite-min-size来自动执行BGREWRITEAOF。

持久化是必须的,但是生成的持久化文件我们必须进行备份,最好备份到多个不同的地方,这样才能避免数据丢失,最好是将快照文件和aof文件备份到不同的服务器上面

除了持久化,复制也是Redis的一大特性。和关系型数据库一样,Redis也主服务端向从服务端发送更新,并使用从服务端来处理所有读请求。虽然,Redis性能特别优秀,但是也会遇上无法快速处理的情况,特别是在集合和有序集合进行操作的时候,涉及的元素可能会有成千上万个甚至百万个,在这种情况下,执行操作所花费的时间可能需要以秒来进行计算。一般情况下,一个正常的2.4GHZ的2处理器的服务器上对两个10000元素的集合进行SUNIONSTORE会花费7-8毫秒。

我们可以使用SLAVEOF host port来让slave接受主服务器的数据更新。从服务器连接主服务器之后会发送SYNC命令,主服务器执行BGSAVE并将所有的写操作写入缓冲区,发送BGSAVE,然后发送缓冲区的写命令,缓冲区写发送完毕之后,从现在开始每执行一个写命令,都会向从服务器发送相同的写命令。缓冲区需要内存,因此建议让主服务器只使用50%-65%的内存,留下的内存用于执行BGSAVE和创建记录写命令的缓冲区。

使用SLAVEOF host port或使用SALVEOF命令都可以配置主从关系。如果是前者,那么Redis会在启动时载入任何可用的快照或AOF文件,然后执行复制过程。Redis不支持主主复制。当从服务器又有从服务器时,在发送BGSAVE文件时,会导致从服务器的从服务器断连重连。

当读请求的重要性明显高于写,我们可以创建一颗多叉树结构的主从链,来实现复制,通过使用AOF和复制,我们可以在丢失最少的情况下,将数据同步到多台服务器。

我们还要检查是否复制的内容是否已经写入硬盘。首先我们得使用master_link_status是否为up来判断从是否连接上,另外判断数据是否同步到了从服务器,一般我们在写入真正的数据之后往主服务器再写入一条唯一的虚构值,看这个虚构值能不能到达从服务器。如何确定数据是否被写入磁盘呢,这个就困难一点,使用INFO命令来查看aof_pending_bio_fsync属性是否为0,如果是0,那就表示服务器已经将已知的所有数据写入到了硬盘里。

查看服务端的综合状态可以使用Redis中的INFO命令来完成

数据安全&性能保障

如果我们要将Redis作为唯一的数据存储手段,我们就要确保Redis不会丢失任何数据。Redis没有像传统关系型数据库那样提供ACID保证可靠的数据事务,Redis只是一个软件,即使硬件和软件设计的完美无暇,也可能出现停电等突发情况。

当出现系统故障时,我们应该收下检查aof文件和dumpfile目录,并且通过在redis-check-aof后面加上–fix参数,程序会对aof文件进行扫描,发现错误命令则会删除该命令以及之后的所有命令。但是快照文件无法修复,因此我们最好将快照文件进行多处备份。

有了备份之后,我们在遇到故障主服务器时我们需要进行更换。首先向从服务B器发起SAVE命令,生成一个新的快照文件,然后将快照文件发送给机器C,并在机器C上启动Redis,最后让B成为C的从服务器。另外一种方法就是将从服务器升级为主服务器,然后为新的主服务器创建从服务器。当然这些操作都是人为的,可能出错。我们可以使用Redis Sentinel监视指定的Redis主服务器以及其属下的从服务器,并且在主服务器下线时自动进行故障转移。

为了保证数据的正确性,我们需要使用Redis的事务。Redis的事务和传统关系型数据库并不相同,传统的关系型数据库支持回滚,但是Redis只能保证多个读写操作串行执行不被打断。下面我们通过一种交易来学习下Redis事务,假设我们有一个需求,这个需求就是容许游戏玩家来出售自己的道具,玩家将自己的游戏道具放入市场里,如果有人购买了,那么玩家的账户会响应的增加资金,购买的那个玩家则会减少资金。

user:8888 -> {id: 8888,name: Jerry,funds: 43,}

inventory

:8888 ->[

item

:1001,

item

:1002,

item

:1003

]

market

: ->[

item

:1004:8888 | 58

]

在上面的数据结构中,将商品的id和玩家的id进行拼接起来,并且按出售价格将市场中的道具进行了排序,非常适合分页和排序。在Redis的事务中,我们常常会用到如下的命令MULTI/EXEC、WATCH、UNWATCH/DISCARD等命令。UNWATCH可以在multi之前对将命令从事务队列中移除,DISCARD可以在multi和exec之间将命令从事务队列中移除。为了保证放入队列的操作的原子性,我们需要监视玩家的包裹中元素的变化,只有当道具在用户的包裹中时,后续才能将道具从玩家的包裹中移除到市场中,否则,这个将道具移到市场的操作就是无效的。

whilenotexpired

try

watch inventory

if notismember itemid of inventory

unwatch

return

multi

market: add item

inventory rem item

exec

returntrue

exception

passreturn false

同样的购买行为也需要监视market:和购买者的个人信息。如果用户的信息没有被修改,并且market:的内容也没有被修改,并且买家有足够多钱的情况下,那么才会执行购买逻辑。

while notexpired // 保证方法能够在一定时间后退出。try

watch market: buyer

if item price changed or buyer notable to buy

unwatch market: buyer

return

multi

//logic here

exec

returntrue

exception

passreturn false

监视market:是为了保证道具没有被其他买家买走,监视买家是为了看买家是否有足够的资金来购买道具,因为一个买家可能暂时没有足够的资金,但是自己在market:里的其他商品卖出后就可能有足够的资金进行购买了。

Watch的本质是一种乐观锁,和Java中的CAS有点像。和传统的关系型数据库的悲观锁不一样(有些的数据库搜索引擎也实现了乐观锁MVCC),乐观锁的思想就是先干,干不成功就不成功,可以重试,也可以不再干。当多个客户端同时对相同的数据进行操作时,正确的使用事务可以有效地防止数据错误发生

虽然multiexec很好用,但是它们也会消耗资源,可能导致其他重要的命令延迟执行,但是,我们可以使用非事务型流水线。使用pipeline(false)来开启非事务流水线。

如果你想测试Redis的性能,可以使用redis-benchmark工具。-c表示使用一个客户端进行测试,-q使用简化输出的结果格式。

$ redis-benchmark -c 1 -q

执行的结果如下所示,根据机器硬件配置会有所差异。



这种测试出来的性能可能不真实,因为redis-benchmark不会处理执行命令所获得的命令回复,节省了大量的语法分析时间。在不使用流水线的情况下,一般只能有上述50%-60%的性能。如果什么时候你的性能只有上面性能的25%左右,或者出现无法分配请求地址的情况,检查一下你是不是创建了过多的连接。

Redis除了可以用来作为系统的一部分,还可以用来作为系统的支撑部分。比如可以用Redis来存储日志和计数器来收集当前的状态信息,记录配置信息等等。

常见的记录日志的方法有2种。第一种就是将日志记录到文件里面。第二种就是使用syslog服务来接收外部程序发来的日志消息,并且支持路由日志存储和日志清理工作。我们往往会使用syslog服务来记录日志,这样会方便很多,让我们来使用Redis来保存最新的日志。

来源:https://developer.huawei.com/consumer/cn/forum/topicview?fid=23&tid=0201188042215490253

原作者: 稳哥很稳