.Net Redis实战——事务和数据持久化

Redis事务

Redis事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令,和关系数据库那种可以在执行的过程中进行回滚(rollback)的事务不同,在Redis里面,被MULTI命令和EXEC命令包围的所有命令会一个接一个地执行,直到所有命令都执行完毕为止。当一个事务执行完毕之后,Redis才会处理其他客户端的命令。

当Redis从一个客户端那里接收到MULTI命令时,Redis会将这个客户端之后发送的所有命令都放入到一个队列里面,直到这个客户端发送EXEC命令为止,Redis才会在不被打断的情况下,一个接一个地执行存储在队列里面的命令。

有时候还会用到UNWATCHDISCARD命令。在用户使用WATCH命令对键进行监视之后,直到用户执行EXEC命令的这段时间里面,如果有其他客户端抢先对任何被监视的键进行了更新或删除等操作,那么用户尝试执行EXEC命令时,事务将失败并返回一个错误(之后用户可以选择重试事务或者放弃事务)。通过使用WATCHMULTI/EXEC、 UNWATCH/DISCARD等命令,程序可以在执行某些重要操作的时候,通过确保自己正在使用的数据没有发生变化避免数据出错。 

因为加锁有可能会造成长时间的等待,所以Redis为了尽可能地减少客户端的等待时间,并不会在执行WATCH命令时对数据进行加锁。Redis只会在数据被其他客户端抢先修改的情况下,通知执行了WATCH命令的客户端,这种做法被称为乐观锁(optimistic locking),而关系数据库实际执行的加锁操作则被称为悲观锁(pessimistic locking)。乐观锁在实际使用中同样非常有效,因为客户端永远不必花时间去等待第一个取得锁的客户端——它们只需要在自己的事务执行失败时进行重试就可以了。

UNWATCH命令可以在WATCH命令执行之后、MULTI命令执行之前对连接进行重置(reset);同样地,DISCARD命令也可以在MULTI命令执行之后、EXEC命令执行之前对连接进行重置。这也就是说,用户在使用WATCH监视一个或多个键,接着使用MULTI开始一个新的事务,并将多个命令入队到事务队列之后,仍然可以通过发送DISCARD命令来取消WATCH命令并清空所有已入队命令。

StackExchange.Redis 事务示例代码

using (var redis = ConnectionMultiplexer.Connect(redisConnectionStr))
            {
                var db = redis.GetDatabase();
                var tran = db.CreateTransaction();//MULTI
                tran.AddCondition(Condition.HashNotExists("key", "UniqueID")); //WATCH
                //tran.AddCondition(Condition.StringEqual("name", name));
                bool committed = tran.Execute();    //EXEC
            }

Redis实战一书中有提到非事物流水总线,即命令并不需要放在事务里面执行,通过一次发送所有命令来减少通信次数并降低延迟值。StackExchange.Redis将这部分封装为Batch。

Batch和Pipelining

Batch会把所需要执行的命令打包成一条请求发到Redis,然后一起等待返回结果。

using (var redis = ConnectionMultiplexer.Connect(redisConnectionStr))
            {
                var db = redis.GetDatabase();
                var batch = db.CreateBatch();
                Task t1 = batch.StringSetAsync("name", "bob");
                Task t2 = batch.StringSetAsync("age", 100);
                batch.Execute();
                Task.WaitAll(t1, t2);
            }

流水线(Pipelining)可以让我们同时发送多个请求,从而减轻延迟。

using (var redis = ConnectionMultiplexer.Connect(redisConnectionStr))
            {
                var db = redis.GetDatabase();
                var aPending = db.StringSetAsync("a","a");
                var bPending = db.StringSetAsync("b","b");
                var a = db.Wait(aPending);
                var b = db.Wait(bPending);
                //同样可以用aPending.Wait() 或 Task.WaitAll(aPending, bPending)代替
            }

关于Batch和Pipelining的区别:Batch模式不会与同一个Multiplexing内的竞争从而交叉操作(个人理解)。

参考连接:https://stackoverflow.com/questions/27796054/pipelining-vs-batching-in-stackexchange-redis

Redis数据持久化

Redis提供了两种不同的持久化方法。一种方法叫快照(snapshotting),它可以将某一时刻的所有数据都写入硬盘里面。另一种方法叫只追加文件(append-only file,AOF),它会在执行写命令时,将被执行的写命令复制到硬盘里面。这两种持久化方法既可单独使用又可以同时使用。

快照持久化

Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。

redis.conf配置文件中

################################ SNAPSHOTTING  ################################

部分有做说明

save <seconds> <changes> :配置快照条件

save 900 1   表示900秒内至少发生过一次更改就会自动触发BGSAVE命令

如果设置了多个save配置选项,那么当任意一个save所设置的条件被满足时,Redis就会触发一次BGSAVE命令。

save ""  会删除之前配置的所有保存条件

stop-writes-on-bgsave-error yes

快照发生错误是否停止写数据。

By default Redis will stop accepting writes if RDB snapshots are enabled (at least one save point) and the latest background save failed.

rdbcompression yes

是否对快照文件进行压缩

rdbchecksum yes

是否对快照文件进行校验

dbfilename dump.rdb

指定存储的文件名

dir /var/lib/redis

指定存储路径

AOF持久化

 AOF持久化将被执行的写命令写到AOF文件的末尾,以此来记录数据发生的变化。Redis只要从头到尾重新执行一次AOF文件包含的所有写命令,就可以恢复AOF文件所记录的数据。

AOF持久化非常灵活地提供了多种不同的选项来满足不同应用程序对数据安全的不同要求,但AOF持久化也有缺陷——那就是AOF文件的体积过大。Redis会不断地将执行的命令记录到AOF文件里面,所以随着Redis运行,AOF文件的体积也会不断增长。

 

AOF工作流程

  1. 所有的写入命令会追加到aof_buf(缓冲区)中。

  2. AOF缓冲区根据对应的策略向硬盘做同步操作。

  3. 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。

  4. 当Redis服务器重启时,可以加载AOF文件进行数据恢复。

 

redis.conf配置文件中

############################## APPEND ONLY MODE ###############################

部分有做说明

appendonly no

是否开启AOF持久化

appendfilename "appendonly.aof"

设置AOF文件名,默认文件名是appendonly.aof

appendfsync always/everysec/no

设置同步频率

 If unsure, use "everysec".

no-appendfsync-on-rewrite no

主进程在写AOF文件采用always或者everysec配置,和子进程在重写AOF文件的时候,都会产生大量的I/O操作。可能会使fsync阻塞很长时间,

如果开启该参数,表示在bgsave和bgrewriteaof的过程中,主线程写入AOF不会调用fsync(),相当于配置appendfsync no。这样有可能会导致redis的修改命令丢失,Linux默认配置下,最多丢失30秒的数据。

如果关闭该参数,表示在bgsave和bgrewriteaof的过程中,主线程写入AOF会调用fsync(),并且被阻塞,这样是最安全的,不会丢失数据。

auto-aof-rewrite-percentage 100

AOF文件的体积比上一次重写之后的体积大了至少一倍(100%)的时执行BGREWRITEAOF命令重写

BGREWRITEAOF的工作原理和BGSAVE创建快照的工作原理非常相似:Redis会创建一个子进程,然后由子进程负责对AOF文件进行重写。

指定零百分比可以禁用自动AOF重写功能。

auto-aof-rewrite-min-size 64mb

当AOF文件的体积大于64MB时执行BGREWRITEAOF命令重写

aof-load-truncated yes

如果该配置启用,在加载时发现AOF尾部不正确,会向客户端写入一个log,但是会继续执行,如果设置为 no ,发现错误就会停止,必须修复后才能重新加载。

aof-use-rdb-preamble yes

RDB AOF 混合开启。Redis5.0新增功能,AOF重写及恢复可以使用RDB文件及AOF文件,速度更快,默认yes

验证快照文件和AOF文件

无论是快照持久化还是AOF持久化,都提供了在遇到系统故障时进行数据恢复的工具。Redis提供了两个命令行程序redis-check-aof和redis-check-rdb (redis-check-dump in 3.0 and below),它们可以在系统故障发生之后,检查AOF文件和快照文件的状态,并在有需要的情况下对文件进行修复。

程序修复AOF文件的方法非常简单:它会扫描指定的AOF文件,当发现第一个出错命令的时候,程序会删除出错的命令以及位于出错命令之后的所有命令。在大多数情况下,被删除的都是AOF文件末尾的不完整的写命令。

Redis数据复制

扩展平台以适应更高负载,复制(replication)是不可或缺的。Redis需要扩展读请求或者在需要写入临时数据的时,用户可以通过设置额外的Redis从服务器来保存数据的副本。接收到主服务器发送的数据初始副本(initial copy of the data)之后,客户端每次向主服务器写入数据时,从服务器都会实时得到更新。部署好主从服务器之后,客户端就可以向任意一个从服务器发送读请求,而不必把每个读请求都发送给主服务器。

Redis复制相关配置

replicaof <masterip> <masterport>

将服务器设置为从服务器

从 5.0.0 版本开始,Redis 正式将 SLAVEOF 命令改名成了 REPLICAOF 命令并逐渐废弃原来的 SLAVEOF 命令

对于一个正在运行的Redis服务器,用户可以通过发送REPLICAOF no one命令来让服务器终止复制操作,不再接受主服务器的数据更新,服务器在停止复制之后不会清空数据库,而是会继续保留复制产生的所有数据;也可以通过发送REPLICAOF host port命令来让服务器开始复制一个新的主服务器。

masterauth <master-password>

指定主服务器的连接密码

replica-serve-stale-data yes

当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:

1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。

2) 如果slave-serve-stale-data设置为no, INFO,replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,COMMAND, POST, HOST: and LATENCY命令之外的任何请求 都会返回一个错误”SYNC with master in progress”。

replica-read-only yes

作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议) 

repl-diskless-sync no

是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。

2种方式区别:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式。

repl-diskless-sync-delay 5

启用无磁盘复制时,可以配置延迟时间。如果设置为0,代表一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好设置一个等待时间,等更多的slave连上来

repl-ping-replica-period 10

根据指定的时间间隔向服务器发送ping请求

repl-timeout 60

复制连接超时时间。主从服务器都有超时时间的设置。需要注意的是repl-timeout需要设置为一个比repl-ping-slave-period更大的值,否则会经常检测到超时

repl-disable-tcp-nodelay no

是否禁用TCP_NODELAY。

如果你选择‘yes’,Redis将使用较少数量的TCP数据包和很少的带宽向副本发送数据。 但是这可能会增加数据在副本上出现的延迟,使用默认配置的Linux内核,最多可延迟40毫秒。

如果选择‘no’,则数据出现在副本上的延迟将会减少,但这将使用更多的带宽来进行复制。

在非常高流量的条件下推荐使用yes。

repl-backlog-size 1mb

复制缓冲区大小,缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m

repl-backlog-ttl 3600

设置复制缓冲区的内存释放的时间,单位为秒。

replica-priority 100

当master不可用,会根据slave的优先级选举一个master。最低优先级的slave,当选master。配置成0,永远不会被选举

min-replicas-to-write 3

表示连接到master的最少slave数量

min-replicas-max-lag 10

延迟小于min-replicas-max-lag秒的slave才认为是健康的slave

replica-announce-ip 5.5.5.5

向其主服务器报告特定的IP

replica-announce-port 1234

向其主服务器报告特定的端口

主从链

创建多个从服务器可能会造成网络不可用——当复制需要通过互联网进行或者在不同数据中心之间进行时,尤为如此。Redis的主服务器和从服务器并没有特别不同的地方,所以从服务器也可以拥有自己的从服务器,并由此形成主从链(master/slave chaining)。

posted @ 2021-01-28 20:52  Stacking  阅读(573)  评论(0编辑  收藏  举报