Redis持久化-主从同步和安全性-压测

持久化方式

1、快照方式(RDB 默认)

这种方式就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

客户端也可以使用 e save 或者 e bgsave 命令通知 redis 做一次快照持久化。save 操作是在主线程中保存快照的,由于 redis 是用一个主线程来处理所有客户端的请求,这种方式会阻塞所有客户端请求。所以不推荐使用。

另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步增量数据。如果数据量大的话,写操作会比较多,必然会引起大量的磁盘 IO 操作,可能会严重影响性能。

注意:由于快照方式是在一定间隔时间做一次的,所以如果 redis 意外当机的话,就会丢失最后一次快照后的所有数据修改。

2、日志追加方式(AOF)

这种方式 redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认appendonly.aof)。当 redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

当然由于操作系统会在内核中缓存 write 做的修改,所以可能不是立即写到磁盘上。这样的持久化还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis 我们想要通过 fsync 函数强制操作系统写入到磁盘的时机。

有三种方式如下(默认是 :每秒 fsync 一次)
appendonly yes #启用日志追加持久化方式
#appendfsync always #每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
#appendfsync no //完全依赖操作系统,性能最好,持久化没保证

日志追加方式同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用 incrtest 命令 100 次,文件中必须保存全部 100 条命令,其实有 99 条都是多余的。因为要恢复数据库状态其实文件中保存一条 set test 100 就够了。

为了压缩这种持久化方式的日志文件 ,redis 提供了 bgrewriteaof 命令。收到此命令 redis 将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的持久化日志文件。

虚拟内存

redis 没有使用操作系统提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制

对于redis 这样的内存数据库,内存总是不够用的。使用虚拟内存技术把那些不经常访问的数据交换到磁盘上。

vm-enabled yes #开启虚拟内存功能
vm-swap-file /tmp/redis.swap #交换出来 value 保存的文件路径/tmp/redis.swap
vm-max-memory 268435456 #redis 使用的最大内存上限(256MB),超过上限后redis 开始交换 value 到磁盘 swap 文件中。建议设置为系统空闲内存的 60%-80%
vm-page-size 32 #每个 redis 页的大小 32 个字节
vm-pages 134217728 #最多在文件中使用多少个页,交换文件的大小 =(vm-page-size * vm-pages)4GB
vm-max-threads 8 #用于执行 value 对象换入换出的工作线程数量。0表示不使用工作线程

redis 的虚拟内存在设计上为了保证 key 的查询速度,只会将 value 交换到 swap 文件中。 如果是由于太多 key 很小的 value 造成的内存问题,那么 redis 的虚拟内存并不能解决问题 。

和操作系统一样 redis 也是按页来交换对象的。redis 规定同一个页只能保存一个对象。但是一个对象可以保存在多个页中。在 redis 使用的内存没超过 vm-max-memory 之前是不会交换任何 value 的。当超过最大内存限制后,redis 会选择把较老的对象交换到 swap文件中去。如果两个对象一样老会优先交换比较大的对象,精确的交换计算公 式swappability = age*log(size_in_memory)。

redis 虚拟内存工作方式

当 vm-max-threads 设为 0 时(阻塞方式)

换出:主线程定期检查发现内存超出最大上限后,会直接以阻塞的方式,将选中的对象保存到 swap文件中,并释放对象占用的内存空间,此过程会一直重复直到下面条件满足

1.内存使用降到最大限制以下
2.swap 文件满了。
3.几乎全部的对象都被交换到磁盘了

换入:当有客户端请求已经被换出的 value 时,主线程会以阻塞的方式从 swap 文件中加载对应的value 对象,加载时此时会阻塞所有客户端。然后处理该客户端的请求

当 vm-max-threads 大于 0 时( ( 工作线程方式) )

换出:当主线程检测到使用内存超过最大上限,会将选中要交换的对象信息放到一个队列中交给工作线程后台处理,主线程会继续处理客户端请求。

换入:如果有客户端请求的 key 已经被换出了,主线程会先阻塞发出命令的客户端,然后将加载对象的信息放到一个队列中,让工作线程去加载。加载完毕后工作线程通知主线程。主线程再执行客户端的命令。这种方式只阻塞请求的 value 是已经被换出 key 的客户端。总的来说阻塞方式的性能会好一些,因为不需要线程同步、创建线程和恢复被阻塞的客户端等开销。但是也相应的牺牲了响应性。工作线程方式主线程不会阻塞在磁盘 IO 上,所以响应性更好。如果我们的应用不太经常发生换入换出,而且也不太在意有点延迟的话推荐使用阻塞方式。

主从主要是为了实现读写分离

Redis 支持将数据同步到多台从库上,这种特性对提高 读取性能非常有益。

1) master 可以有多个 slave。
2) 除了多个 slave 连到相同的 master 外,slave 也可以连接其它 slave 形成图状结构。
3) 主从复制不会阻塞 master。也就是说当一个或多个 slave 与 master 进行初次同步数据时,master 可以继续处理客户端发来的请求。相反 slave 在初次同步数据时则会阻塞不能处理客户端的请求。
4) 主从复制可以用来提高系统的可伸缩性,我们可以用多个 slave 专门用于客户端的读请求,比如 sort 操作可以使用 slave 来处理。也可以用来做简单的数据冗余。
5) 可以在 master 禁用数据持久化,只需要注释掉 master 配置文件中的所有 save 配置,然后只在 slave 上配置数据持久化。

Redis 主从复制的过程

当设置好 slave 服务器后,slave 会建立和 master 的连接,然后发送 sync 命令。无论第一次同步建立的连接还是连接断开后的重新连接,master 都会启动一个后台进程,将数据库快照保存到文件中,同时 master 主进程会开始收集新的写命令并缓存起来。

后台进程完成写文件后,master 就发送文件给 slave,slave 将文件保存到磁盘上,然后加载到内存恢复数据库快照到 slave 上。接着 master 就会把缓存的命令转发给 slave。而且后续 master 收到的写命令都会通过开始建立的连接发送给slave。

从master到slave的同步数据的命令和从 客户端发送的命令使用相同的协议格式。当 master 和 slave 的连接断开时 slave 可以自动重新建立连接。如果 master 同时收到多个 slave 发来的同步连接命令,只会启动一个进程来写数据库镜像,然后发送给所有 slave。
配置 slave 服务器很简单,只需要在配置文件中加入如下配置。

1
2
3
slaveof 192.168.1.1 6379 #指定 master 的 ip 和端口
slaveof <materip><materport>
materauth <master-password>

安全性

Redis设置密码:
配置文件中:
#requirepass foobared
requirepass *****
重启,再次进入:keys *
    提示没有权限
输入:auth ***
重新进入,这样每次都有可以重新输入密码
可以在登录客户端的时候登录授权:
redis-cli -a ***

发布和订阅:
使用subscribe进行订阅监听
使用publish惊醒发布消息广播。

压力测试

Redis 自带了一个叫redis-benchmark的工具来模拟 N 个客户端同时发出 M 个请求。

使用./redis-benchmark –help查看参数

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 运行默认配置下的测试
$ redis-benchmark

# 指定并发数20,总请求数为10W,redis server主机IP为192.168.1.1
$ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20

# 测试SET随机数性能
$ redis-benchmark -t set -n 1000000 -r 100000000

# 测试结果输出到csv
$ redis-benchmark -t ping,set,get -n 100000 --csv

# 执行特定命令下的测试
$ redis-benchmark -r 10000 -n 10000 eval 'return redis.call("ping")' 0

# 测试list入队的速度
$ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__
-t 选择你想测试的命令,比如redis-benchmark -t set
-p 指定port redis-benchmark -p 6379
-l 一直循环
-c 指定客户端数量
-n 指定request数量
-q 运行在安静模式中
-r 随机  -比如想设置 10 万随机 key 连续 SET 1 万次  redis-benchmark -t set -r 100000 -n 10000

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost bin]# ./redis-benchmark -h 192.168.1.105 -p 6001 -t set -n 100000 -r 1000000
====== SET ======
100000 requests completed in 1.58 seconds
50 parallel clients
3 bytes payload
keep alive: 1

90.70% <= 1 milliseconds
99.50% <= 2 milliseconds
99.91% <= 3 milliseconds
99.94% <= 4 milliseconds
99.95% <= 62 milliseconds
100.00% <= 63 milliseconds
100.00% <= 63 milliseconds
63492.06 requests per second