redis数据类型及操作(七)高级实用特性

Redis高级实用特性

  • redis可以设置密码,因为redis速度太快。所以通常使用过程中需要设置密码,防治暴力破解。
  • 主从复制

1.master可以拥有多个slave。
2.多个slave可以连接同一个master,还可以连接到其他slave。
3.主从复制不会阻塞master,在进行数据同步时,master可以继续处理请求。
4.提高系统的伸缩性。

  • 主从复制过程
    当配置好slave后,slave与master建立连接,然后发送sync命令。无论是第一次连接还是重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存。后台进程完成写文件后,master就发送文件给slave,slave将文件保存到硬盘上,再加载到内存中接着master就会把缓存的命令发送给slave,后续master将收到的写命令发送给slave。如果master同时收到多个slave发送过来的同步连接命令,master只会启动一个进程来写数据库镜像,然后发送给所有的slave。

  • 配置
    配置slave在配置文件中加入如下配置

slaveof 192.168.1.1 6379 #指定master 的ip 和端口

下边进行演示:

# slaveof <masterip> <masterport>
slaveof localhost 6379

我们再一台机器上启动主库(6379),从库(6378)

主库控制台启动日志 如下:

[root@localhost redis-2.2.12]# src/redis-server redis.conf
[7064] 09 Aug 20:13:12 * Server started, Redis version 2.2.12
[7064] 09 Aug 20:13:12 # WARNING overcommit_memory is set to 0! Background save may fail under
low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and
then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[7064] 09 Aug 20:13:12 * The server is now ready to accept connections on port 6379
[7064] 09 Aug 20:13:13 - 0 clients connected (0 slaves), 539512 bytes in use
[7064] 09 Aug 20:13:18 - 0 clients connected (0 slaves), 539512 bytes in use
[7064] 09 Aug 20:13:20 - Accepted 127.0.0.1:37789
[7064] 09 Aug 20:13:20 * Slave ask for synchronization
[7064] 09 Aug 20:13:20 * Starting BGSAVE for SYNC
[7064] 09 Aug 20:13:20 * Background saving started by pid 7067
[7067] 09 Aug 20:13:20 * DB saved on disk
[7064] 09 Aug 20:13:20 * Background saving terminated with success
[7064] 09 Aug 20:13:20 * Synchronization with slave succeeded
[7064] 09 Aug 20:13:23 - 0 clients connected (1 slaves), 547380 bytes in use

从库启动后日志如下

[root@localhost redis-2.2.12]# src/redis-server redis.slave
[7066] 09 Aug 20:13:20 * Server started, Redis version 2.2.12
[7066] 09 Aug 20:13:20 # WARNING overcommit_memory is set to 0! Background save may fail under
low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and
then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
[7066] 09 Aug 20:13:20 * The server is now ready to accept connections on port 6378
[7066] 09 Aug 20:13:20 - 0 clients connected (0 slaves), 539548 bytes in use
[7066] 09 Aug 20:13:20 * Connecting to MASTER...
[7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync started: SYNC sent
[7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync: receiving 10 bytes from master
[7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync: Loading DB in memory
[7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync: Finished with success
[7068] 09 Aug 20:13:20 * SYNC append only file rewrite performed
[7066] 09 Aug 20:13:20 * Background append only file rewriting started by pid 7068
[7066] 09 Aug 20:13:21 * Background append only file rewriting terminated with success
[7066] 09 Aug 20:13:21 * Parent diff flushed into the new append log file with success (0 bytes)
[7066] 09 Aug 20:13:21 * Append only file successfully rewritten.
[7066] 09 Aug 20:13:21 * The new append only file was selected for future appends.
[7066] 09 Aug 20:13:25 - 1 clients connected (0 slaves), 547396 bytes in use

在主库上进行存储;

redis 127.0.0.1:6379> set name HongWan
OK
redis 127.0.0.1:6379>

在从库上进行读取:

redis 127.0.0.1:6378> get name
"HongWan"
redis 127.0.0.1:6378>

说明主从同步正确

我们想要知道哪个是主库哪个是从库。只需要调用info这个命令就可以得到主从的信息了。

redis 127.0.0.1:6378> info
.
.
.
role:slave   #这里代表 角色标志。
master_host:localhost
master_port:6379
master_link_status:up #up代表同步正常。down代表异常
master_last_io_seconds_ago:10
master_sync_in_progress:0
db0:keys=1,expires=0   #数据库有几个key,以及过期key的数量。
redis 127.0.0.1:6378>
  • 事务控制
    redis 对事务的支持目前还比较简单。redis 只能保证一个client 发起的事务中的命令可以连续的执行,而中间不会插入其他client 的命令。 由于redis 是单线程来处理所有client 的请求的所以做到这点是很容易的。一般情况下redis 在接受到一个client 发来的命令后会立即处理并 返回处理结果,但是当一个client 在一个连接中发出multi 命令有,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接受到exec 命令后,redis 会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给client.然后此连接就 结束事务上下文。

  • 简单事务控制

redis 127.0.0.1:6379> get age
"33"
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set age 10
QUEUED
redis 127.0.0.1:6379> set age 20
QUEUED
redis 127.0.0.1:6379> exec
1) OK
2) OK
redis 127.0.0.1:6379> get age
"20"
redis 127.0.0.1:6379>

从这个例子我们可以看到2 个set age 命令发出后并没执行而是被放到了队列中。调用exec后2 个命令才被连续的执行,最后返回的是两条命令执行后的结果。

  • 如何取消一个事务
    我们可以调用discard 命令来取消一个事务,让事务回滚。接着上面例子
redis 127.0.0.1:6379> get age
"20"
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set age 30
QUEUED
redis 127.0.0.1:6379> set age 40
QUEUED
redis 127.0.0.1:6379> discard
OK
redis 127.0.0.1:6379> get age
"20"
redis 127.0.0.1:6379>

可以发现这次2 个set age 命令都没被执行。discard 命令其实就是清空事务的命令队列并退
出事务上下文,也就是我们常说的事务回滚。

  • 乐观锁复杂事务控制
    乐观锁:大多数是基于数据版本(version)的记录机制实现的。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个“version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
    乐观锁实例:假设数据库中帐户信息表中有一个version 字段,当前值为1;而当前帐户余
    额字段(balance)为$100。下面我们将用时序表的方式来为大家演示乐观锁的实现原理:
操作员A操作员B
(1)、操作员A 此时将用户信息读出(此时version=1),并准备从其帐户余额中扣除$50($100-$50)(2)、在操作员A 操作的过程中,操作员B 也读入此用户信息(此时version=1),并准备从其帐户余额中扣除$20($100-$20)
(3)、操作员A 完成了修改工作,将数据版本号加1(此时version=2),连同帐户扣除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录version更新为2
(4)、操作员B 完成了操作,也将版本号加1( version=2 ) 并试图向数据库提交数据(balance=$80),但此时比对数据库记录版本时发现,操作员B 提交的数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记录当前版本才能执行更新”的乐观锁策略,因此,操作员B 的提交被驳回

这样,就避免了操作员B 用基于version=1 的旧数据修改的结果来覆盖操作员A 的操作结果的可能.

即然乐观锁比悲观锁要好很多,redis 是否也支持呢?答案是支持, redis 从2.1.0 开始就支持乐观锁了,可以显式的使用watch 对某个key 进行加锁,避免悲观锁带来的一系列问题。Redis 乐观锁实例:假设有一个age 的key,我们开2 个session 来对age 进行赋值操作,我们来看一下结果如何。

在这里插入图片描述
从以上实例可以看到在
第一步,Session 1 还没有来得及对age 的值进行修改
第二步,Session 2 已经将age 的值设为30
第三步,Session 1 希望将age 的值设为20,但结果一执行返回是nil,说明执行失败,之后我们再取一下age 的值是30,这是由于Session 1 中对age 加了乐观锁导致的。

watch 命令会监视给定的key,当exec 时候如果监视的key 从调用watch 后发生过变化,则整个事务会失败。也可以调用watch 多次监视多个key.这 样就可以对指定的key 加乐观锁了。注意watch 的key 是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当exec,discard,unwatch 命令都会清除连接中的所有监视。

redis 的事务实现是如此简单,当然会存在一些问题。第一个问题是redis 只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。下面将以一个实例的例子来说明这个问题:

redis 127.0.0.1:6379> get age
"30"
redis 127.0.0.1:6379> get name
"HongWan"
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr age
QUEUED
redis 127.0.0.1:6379> incr name
QUEUED
redis 127.0.0.1:6379> exec
1) (integer) 31
2) (error) ERR value is not an integer or out of range
redis 127.0.0.1:6379> get age
"31"
redis 127.0.0.1:6379> get name
"HongWan"
redis 127.0.0.1:6379>

从这个例子中可以看到,age 由于是个数字,那么它可以有自增运算,但是name 是个字符串,无法对其进行自增运算,所以会报错,如果按传统关系型数据库的思路来讲,整个事务都会回滚,但是我们看到redis 却是将可以执行的命令提交了,所以这个现象对于习惯于关系型数据库操作的朋友来说是很别扭的,这一点也是redis 今天需要改进的地方。

  • 持久化机制
    redis 是一个支持持久化的内存数据库,也就是说redis 需要经常将内存中的数据同步到磁盘来保证持久化。redis 支持两种持久化方式,一种是Snapshotting(快照)也是默认方式,另一种是Append-only file(缩写aof)的方式。下面分别介绍:

  • snapshotting
    快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n 秒内如果超过m 个key 被修改就自动做快照,下面是默认的快照保存配置

save 900 1 #900 秒内如果超过1 个key 被修改,则发起快照保存
save 300 10 #300 秒内容如超过10 个key 被修改,则发起快照保存
save 60 10000

下面介绍详细的快照保存过程:
1.redis 调用fork,现在有了子进程和父进程。
2. 父进程继续处理client 请求,子进程负责将内存内容写入到临时文件。由于os 的实时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os 会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程地址空间内的数据是fork时刻整个数据库的一个快照。
3.当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

client 也可以使用save 或者bgsave 命令通知redis 做一次快照持久化。save 操作是在主线程中保存快照的,由于redis 是用一个主线程来处理所有client 的请求,这种方式会阻塞所有client 请求。所以不推荐使用。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步变更数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io 操作,可能会严重影响性能。

下面将演示各种场景的数据库持久化情况

redis 127.0.0.1:6379> set name HongWan
OK
redis 127.0.0.1:6379> get name
"HongWan"
redis 127.0.0.1:6379> shutdown
redis 127.0.0.1:6379> quit

我们先设置了一个name 的键值对,然后正常关闭了数据库实例,数据是否被保存到磁盘了呢?我们来看一下服务器端是否有消息被记录下来了:

[6563] 09 Aug 18:58:58 * The server is now ready to accept connections on port 6379
[6563] 09 Aug 18:58:58 - 0 clients connected (0 slaves), 539540 bytes in use
[6563] 09 Aug 18:59:02 - Accepted 127.0.0.1:58005
[6563] 09 Aug 18:59:03 - 1 clients connected (0 slaves), 547368 bytes in use
[6563] 09 Aug 18:59:08 - 1 clients connected (0 slaves), 547424 bytes in use
[6563] 09 Aug 18:59:12 # User requested shutdown...
[6563] 09 Aug 18:59:12 * Saving the final RDB snapshot before exiting.
[6563] 09 Aug 18:59:12 * DB saved on disk
[6563] 09 Aug 18:59:12 # Redis is now ready to exit, bye bye...
[root@localhost redis-2.2.12]#

从日志可以看出,数据库做了一个存盘的操作,将内存的数据写入磁盘了。正常的话,磁盘上会产生一个dump 文件,用于保存数据库快照,我们来验证一下:

[root@localhost redis-2.2.12]# ll
总计 188
-rw-rw-r-- 1 root root 9602 2011-07-22 00-RELEASENOTES
-rw-rw-r-- 1 root root 55 2011-07-22 BUGS
-rw-rw-r-- 1 root root 84050 2011-07-22 Changelog
drwxrwxr-x 2 root root 4096 2011-07-22 client-libraries
-rw-rw-r-- 1 root root 671 2011-07-22 CONTRIBUTING
-rw-rw-r-- 1 root root 1487 2011-07-22 COPYING
drwxrwxr-x 4 root root 4096 2011-07-22 deps
drwxrwxr-x 2 root root 4096 2011-07-22 design-documents
drwxrwxr-x 2 root root 12288 2011-07-22 doc
-rw-r--r-- 1 root root 26 08-09 18:59 dump.rdb
-rw-rw-r-- 1 root root 652 2011-07-22 INSTALL
-rw-rw-r-- 1 root root 337 2011-07-22 Makefile
-rw-rw-r-- 1 root root 1954 2011-07-22 README
-rw-rw-r-- 1 root root 19067 08-09 18:48 redis.conf
drwxrwxr-x 2 root root 4096 08-05 19:12 src
drwxrwxr-x 7 root root 4096 2011-07-22 tests
-rw-rw-r-- 1 root root 158 2011-07-22 TODO
drwxrwxr-x 2 root root 4096 2011-07-22 utils
[root@localhost redis-2.2.12]#

硬盘上已经产生了一个数据库快照了。这时侯我们再将redis 启动,看键值还是否真的持久化到硬盘了。

redis 127.0.0.1:6379> keys *
1) "name"
redis 127.0.0.1:6379> get name
"HongWan"
redis 127.0.0.1:6379>
  • aor 方式

另外由于快照方式是在一定间隔时间做一次的,所以如果redis 意外down 掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof 持久化方式。下面介绍Append-only file:
aof 比快照方式有更好的持久化性,是由于在使用aof 持久化方式时,redis 会将每一个收到的写命令都通过write 函数追加到文件中(默认appendonly.aof)。当redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于os 会在内核中缓存 write 做的修改,所以可能不是立即写到磁盘上。这样aof 方式的持久化也还是有可能会
丢失部分修改。不过我们可以通过配置文件告诉redis 我们想要通过fsync 函数强制os 写入到磁盘的时机。有三种方式如下(默认是:每秒fsync 一次)
appendonly yes //启用aof 持久化方式
#appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中# appendfsync no //完全依赖os,性能最好,持久化没保证

redis 127.0.0.1:6379> set name HongWan
OK
redis 127.0.0.1:6379> set age 20
OK
redis 127.0.0.1:6379> keys *
1) "age"
2) "name"
redis 127.0.0.1:6379> shutdown
redis 127.0.0.1:6379>

我们先设置2 个键值对,然后我们看一下系统中有没有产生appendonly.aof 文件

[root@localhost redis-2.2.12]# ll
总计 184
-rw-rw-r-- 1 root root 9602 2011-07-22 00-RELEASENOTES
-rw-r--r-- 1 root root 0 08-09 19:37 appendonly.aof
-rw-rw-r-- 1 root root 55 2011-07-22 BUGS
-rw-rw-r-- 1 root root 84050 2011-07-22 Changelog
drwxrwxr-x 2 root root 4096 2011-07-22 client-libraries
-rw-rw-r-- 1 root root 671 2011-07-22 CONTRIBUTING
-rw-rw-r-- 1 root root 1487 2011-07-22 COPYING
drwxrwxr-x 4 root root 4096 2011-07-22 deps
drwxrwxr-x 2 root root 4096 2011-07-22 design-documents
drwxrwxr-x 2 root root 12288 2011-07-22 doc
-rw-rw-r-- 1 root root 652 2011-07-22 INSTALL
-rw-rw-r-- 1 root root 337 2011-07-22 Makefile
-rw-rw-r-- 1 root root 1954 2011-07-22 README
-rw-rw-r-- 1 root root 19071 08-09 19:24 redis.conf
drwxrwxr-x 2 root root 4096 08-05 19:12 src
drwxrwxr-x 7 root root 4096 2011-07-22 tests
-rw-rw-r-- 1 root root 158 2011-07-22 TODO
drwxrwxr-x 2 root root 4096 2011-07-22 utils
[root@localhost redis-2.2.12]#

结果证明产生了,接着我们将redis 再次启动后来看一下数据是否还在

[root@localhost redis-2.2.12]# src/redis-cli
redis 127.0.0.1:6379> keys *
1) "age"
2) "name"
redis 127.0.0.1:6379>

数据还存在系统中,说明系统是在启动时执行了一下从磁盘到内存的load 数据的过程。
aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100 次,文件中必须保存全部的100 条命令,其实有99 条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100 就够了。为了压缩aof 的持久化文件。redis 提供了bgrewriteaof 命令。收到此命令redis 将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件。具体过程如下
1、redis 调用fork ,现在有父子两个进程
2、子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
3、父进程继续处理client 请求,除了把写命令写入到原来的aof 文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
4、当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
5、现在父进程可以使用临时文件替换老的aof 文件,并重命名,后面收到的写命令也开始往新的aof 文件中追加。

需要注意到是重写aof 文件的操作,并没有读取旧的aof 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof 文件,这点和快照有点类似。接来我们看一下实际的例子:
我们先调用5 次incr age 命令:

redis 127.0.0.1:6379> incr age
(integer) 21
redis 127.0.0.1:6379> incr age
(integer) 22
redis 127.0.0.1:6379> incr age
(integer) 23
redis 127.0.0.1:6379> incr age
(integer) 24
redis 127.0.0.1:6379> incr age
(integer) 25
redis 127.0.0.1:6379>

接下来我们看一下日志文件的大小

[root@localhost redis-2.2.12]# ll
总计 188
-rw-rw-r-- 1 root root 9602 2011-07-22 00-RELEASENOTES
-rw-r--r-- 1 root root 259 08-09 19:43 appendonly.aof
-rw-rw-r-- 1 root root 55 2011-07-22 BUGS
-rw-rw-r-- 1 root root 84050 2011-07-22 Changelog

大小为259 个字节,接下来我们调用一下bgrewriteaof 命令将内存中的数据重新刷到磁盘的日志文件中

redis 127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
redis 127.0.0.1:6379>

再看一下磁盘上的日志文件大小

[root@localhost redis-2.2.12]# ll
总计 188
-rw-rw-r-- 1 root root 9602 2011-07-22 00-RELEASENOTES
-rw-r--r-- 1 root root 127 08-09 19:45 appendonly.aof
-rw-rw-r-- 1 root root 55 2011-07-22 BUGS
-rw-rw-r-- 1 root root 84050 2011-07-22 Changelog

日志文件大小变为127 个字节了,说明原来日志中的重复记录已被刷新掉了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值