缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
-
缓存雪崩
我们可以简单的理解为:由于缓存挂掉或原有缓存失效,(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决办法:
Redis高可用;
限流降级: 通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待;
缓存预热: 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。 -
缓存穿透
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。 这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决办法;
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。 -
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据
库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
直接写个缓存刷新页面,上线时手工操作下;
数据量不大,可以在项目启动的时候自动进行加载;
定时刷新缓存。 -
缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂! -
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
Redis的过期策略以及内存淘汰机制
- Redis采用的是定期删除+惰性删除策略。
- 为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略。 - 定期删除+惰性删除是如何工作的呢?
定期删除,Redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,Redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。于是,惰性删除派上用场。也就是说在你获取某个key的时候,Redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。 - 采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,Redis的内存会越来越高。那么就应该采用内存淘汰机制。
在redis.conf中有一行配置:
maxmemory-policy volatile-lru
该配置就是配内存淘汰策略的。
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰;
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;
- no-enviction(驱逐):禁止驱逐数据,新写入操作会报错。
Redis持久化
-
Redis共有两种数据持久化类型:RDB和AOF。RDB可以看作是Redis在某一个时间点上的快照(snapshot),非常适合于备份和灾难恢复。AOF(append-only file)则是一种只记录Redis写入命令的追加式日志文件,将在服务器启动时被重放。
-
启用RDB
方式一:
在redis-cli使用命令 config set save x y
方式二:
修改配置文件,在配置文件中配置 save x y
-
禁用RDB
方式一:
在redis-cli使用命令 config set save “”
方式二:
修改配置文件,在配置文件中注释掉或删掉save
手动执行RDB快照:
使用命令save:会阻塞客户端请求;
使用命令bgsave:后台开启子线程进行转储,Redis的主线程将继续处理收到的命令,同时会通过fork()系统调用创建一个子进程将转储数据保存到一个名为temp-.rdb。当转储结束后,这个临时文件会被重命名为由参数dbfilename定义的名字并覆盖由参数dir指定的本地目录中的旧转储文件。 -
启用AOF
方式一:
在redis-cli使用命令 config set appendonly yes
方式二:
修改配置文件,在配置文件中加入 appendonly yes -
禁用AOF
方式一:
在redis-cli使用命令 config set appendonly no
方式二:
修改配置文件,在配置文件中加入 appendonly no
当AOF被启用时,Redis将会在数据目录中创建AOF文件。AOF文件的默认文件名为appendonly.aof,并通过配置文件中的appendfilename参数进行修改。Redis的命令首先会被写入到缓冲区,而缓冲区中的数据必须被刷新到磁盘中才能被永久保存,这个过程是通过Linux系统调用fsync()完成的。我们可以通过Redis的配置参数appendfsync来调整fsync()的频率,该参数共有3个选项:
always:对每个写入命令都调用fsync();
everysec:每秒调一次fsync();
no:永运不调用fsync()。采用该选项时将由操作系统决定何时将数据从缓冲区写入到磁盘。在大多数的Linux系统中,这个频率是每30秒。随着Redis将写入命令追加到AOF文件中,AOF文件的大小可能会显著增加。Redis提供了一种机制,即通过AOF重写来压缩AOF文件,可以使用bgrewriteaof命令来启动重写过程。Redis主进程会创建子进程来执行AOF重写,在子进程创建后,子进程不能访问在其被创建后的新产生的数据,Redis将创建子进程后的命令写入到一个名为aof_rewrite_buf_blocks的缓冲区,当子进程完成新AOF文件重写后,向主进程发送一个信号,主进程会把aof_rewrite_buf_blocks缓冲区的命令写入到新AOF文件中,然后用新AOF文件替换旧的AOF文件。
如果系统崩溃,那么AOF文件的最后可能会损坏或截断。Redis提供了一个工具redis-check-aof,用于修复损坏的文件。运行命令:
bin/redis-check-aof --fix appendonly.aof在同时启用了RDB和AOF后,将参数aof-use-rdb-preamble设置为yes,来启用从Redis4.x开始提供的新的混合持久化功能。当这个参数设置为yes后,在重写AOF文件时,Redis首先会把数据集以RDB的格式转储到内存中并作为AOF文件的开始部分。在重写AOF之后,Redis继续使用传统的AOF格式在AOF文件中记录写入命令。Redsi保证RDB和AOF重写不会同时进行。当Redis启动时,即便RDB和AOF持久化同时启用且AOF、RDB文件都存在,则Redis总是会先加载AOF文件。当加载AOF文件时,如果启用了混合持久化,那么Redis将首先检查AOF的前五个字符,如果这五个字符是REDIS,那么该AOF文件就是混合格式的,Redis服务器会先加载RDB部分再加载AOF部分。
-
Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。 -
RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)
AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
Redis备份与恢复
- SAVE命令用于创建当前数据库的备份,该命令将在redis安装目录中创建dump.rdb文件;
- 如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取redis目录可以使用CONFIG命令,如下所示:
- 创建 Redis 备份文件也可以使用命令BGSAVE,该命令在后台执行。
- Redis通过监听一个TCP端口或者Unix socket的方式来接收来自客户端的连接,当一个连接建立后,Redis内部会进行以下一些操作:
首先客户端socket会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型;
然后为这个socket设置 CP_NODELAY 属性,禁用 Nagle 算法;
然后创建一个可读的文件事件用于监听这个客户端socket的数据发送。
Redis事务
-
Redis事务可以一次执行多个命令,并且带有以下三个重要的保证:
批量操作在发送EXEC命令前被放入队列缓存;
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行;
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。一个事务从开始到执行会经历以下三个阶段:
开始事务;
命令入队;
执行事务。
以下是一个事务的例子,它先以 MULTI 开始一个事务,然后将多个命令入队到事务中,最后由EXEC命令触发事务,一并执行事务中的所有命令:
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name “Mastering C++ in 21 days”
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag “C++” “Programming” “Mastering Series”
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
- OK
- “Mastering C++ in 21 days”
- (integer) 3
-
- “Mastering Series”
- “C++”
- “Programming”
- 单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
序号 | 命令 | 描述 |
---|---|---|
1 | DISCARD | 取消事务,放弃执行事务块内的所有命令。 |
2 | EXEC | 执行所有事务块内的命令。 |
3 | MULTI | 标记一个事务块的开始。 |
4 | UNWATCH | 取消 WATCH 命令对所有 key 的监视。 |
5 | WATCH key [key …] | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
-
Redis事务与关系数据库事务的区别:
Redis事务没有回滚功能。一般来说在一个Redis事务中可能会出现两种类型的错误:
第一钟错误:命令有语法错误。在这种情况下,由于在命令入队时就能发现存在语法错误,所以整个事务会快速失败且事务中的所有命令都不会执行;
第二种错误:所有命令成功入队,在执行的过程中发生错误。在这种情况下,位于发生错误的命令之前的命令不会回滚,后面的命令都将继续执行。 -
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
1.Redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
2.如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
3.如果在一个事务中出现运行错误,那么正确的命令会被执行。
1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil。
3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
键管理
- dbsize获取Redis中键的个数;
- 获取Redis中的所有键;
方式一:keys *
方式二:scan;
- 删除键
方式一:del;
方式二:unlink(在Redis4.0以上版本引入,主要用于执行大key的异步删除);
- exists检查是否存在某个key;
- type获取键的数据类型;
- rename重命名键,会对已存在的键进行删除;
- expire设置键的过期时间,单元秒、pexpire设置键的过期时间,单元毫秒;
- ttl获取键存活时间,单元秒、pttl获取键的存活时间,单位毫秒;
如果键存在但未设置过期时间,ttl/pttl返回-1;如果键不存在,ttl/pttl返回-2。
- 当对Redis中的一个键设置过期时间时,键的过期时间会被存储为绝对的UNIX时间戳。这样做的目的在于,即使Redis服务器宕机一段时间,这个时间戳也会被持久化到RDB文件中。当Redis再次启动时,这个用来判断键是否过期的时间戳不会发生变化,一旦这个时间超过这个时间戳,键就过期了。在一个键 过期后,当客户端试图访问已过期键时,Redis会立即将其从内存中删除。Redis这种删除键的方式被称为被动过期。对于那些已经过期且永远不会被访问到的键。Redis还会定期运行一个基于概率的算法来进行主动删除。更具体的说,Redis会随机选择设置了过期时间的20个键。在这20个被选中的键中,已过期的键会立即删除;如果选中的键中有超过25%的键已经过期被删除,那么Redis会再次随机选择20个键并重复这个过程。默认情况下,上述过程每秒运行10次(可通过配置文件中hz的值进行设置)。
序号 | 命令 | 描述 |
---|---|---|
1 | DEL key | 该命令用于在 key 存在时删除 key。 |
2 | DUMP key | 序列化给定 key ,并返回被序列化的值。 |
3 | EXISTS key | 检查给定 key 是否存在。 |
4 | EXPIRE key seconds | 为给定 key 设置过期时间,以秒计。 |
5 | EXPIREAT key timestamp | EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
6 | PEXPIRE key milliseconds | 设置 key 的过期时间以毫秒计。 |
7 | PEXPIREAT key milliseconds-timestamp | 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
8 | KEYS pattern | 查找所有符合给定模式( pattern)的 key 。 |
9 | MOVE key db | 将当前数据库的 key 移动到给定的数据库 db 当中。 |
10 | PERSIST key | 移除 key 的过期时间,key 将持久保持。 |
11 | PTTL key | 以毫秒为单位返回 key 的剩余的过期时间。 |
12 | TTL key | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
13 | RANDOMKEY | 从当前数据库中随机返回一个 key 。 |
14 | RENAME key newkey | 修改 key 的名称 |
15 | RENAMENX key newkey | 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
16 | TYPE key | 返回 key 所储存的值的类型。 |
Redis管道技术
- Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:
客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应;服务端处理命令,并将结果返回给客户端。Redis管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。管道技术最显著的优势是提高了Redis服务的性能。
Redis发布订阅(Publish-Subscribe PubSub)
- Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
- PubSub相关的机制均不支持持久化,这意味着,消息、频道和PubSub的关系均不能保存到磁盘上。如果服务器出于某种原因退出,那么所有的这些对象都将丢失。此外,在消息投递和处理的场景中,如果频道没有订阅者,那么被发送到频道上的消息将被丢弃。换句话说,Redis并没有保证消息投递可靠性的机制。
序号 | 命令 | 描述 |
---|---|---|
1 | PSUBSCRIBE pattern [pattern …] | 订阅一个或多个符合给定模式的频道。 |
2 | PUBSUB subcommand [argument [argument …]] | 查看订阅与发布系统状态。 |
3 | PUBLISH channel message | 将信息发送到指定的频道。 |
4 | PUNSUBSCRIBE [pattern [pattern …]] | 退订所有给定模式的频道。 |
5 | SUBSCRIBE channel [channel …] | 订阅给定的一个或多个频道的信息。 |
6 | UNSUBSCRIBE [channel [channel …]] | 指退订给定的频道。 |
Redis脚本
- Lua是一种轻量级的脚本语言。Redis从2.6版本开始引入对Lua脚本的支持。Lua脚本是原子化执行的。 执行脚本的常用命令为 EVAL。
序号 | 命令 | 描述 |
---|---|---|
1 | EVAL script numkeys key [key …] arg [arg …] | 执行 Lua 脚本。 |
2 | EVALSHA sha1 numkeys key [key …] arg [arg …] | 执行 Lua 脚本。 |
3 | SCRIPT EXISTS script [script …] | 查看指定的脚本是否已经被保存在缓存当中。 |
4 | SCRIPT FLUSH | 从脚本缓存中移除所有脚本。 |
5 | SCRIPT KILL | 杀死当前正在运行的 Lua 脚本。 |
6 | SCRIPT LOAD script | 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。 |
Redis连接
序号 | 命令 | 描述 |
---|---|---|
1 | AUTH password | 验证密码是否正确 |
2 | ECHO message | 打印字符串 |
3 | PING | 查看服务是否运行 |
4 | QUIT | 关闭当前连接 |
5 | SELECT index | 切换到指定的数据库 |
Redis服务器命令
序号 | 命令 | 描述 |
---|---|---|
1 | BGREWRITEAOF | 异步执行一个 AOF(AppendOnly File) 文件重写操作 |
2 | BGSAVE | 在后台异步保存当前数据库的数据到磁盘 |
3 | CLIENT KILL [ip:port] [ID client-id] | 关闭客户端连接 |
4 | CLIENT LIST | 获取连接到服务器的客户端连接列表 |
5 | CLIENT GETNAME | 获取连接的名称 |
6 | CLIENT PAUSE timeout | 在指定时间内终止运行来自客户端的命令 |
7 | CLIENT SETNAME connection-name | 设置当前连接的名称 |
8 | CLUSTER SLOTS | 获取集群节点的映射数组 |
9 | COMMAND | 获取 Redis 命令详情数组 |
10 | COMMAND COUNT | 获取 Redis 命令总数 |
11 | COMMAND GETKEYS | 获取给定命令的所有键 |
12 | TIME | 返回当前服务器时间 |
13 | COMMAND INFO command-name [command-name …] | 获取指定 Redis 命令描述的数组 |
14 | CONFIG GET parameter | 获取指定配置参数的值 |
15 | CONFIG REWRITE | 对启动 Redis 服务器时所指定的 redis.conf 配置文件进行改写 |
16 | CONFIG SET parameter value | 修改 redis 配置参数,无需重启 |
17 | CONFIG RESETSTAT | 重置 INFO 命令中的某些统计数据 |
18 | DBSIZE | 返回当前数据库的 key 的数量 |
19 | DEBUG OBJECT key | 获取 key 的调试信息 |
20 | DEBUG SEGFAULT | 让 Redis 服务崩溃 |
21 | FLUSHALL | 删除所有数据库的所有key |
22 | FLUSHDB | 删除当前数据库的所有key |
23 | INFO [section] | 获取 Redis 服务器的各种信息和统计数值 |
24 | LASTSAVE | 返回最近一次 Redis 成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示 |
25 | MONITOR | 实时打印出 Redis 服务器接收到的命令,调试用 |
26 | ROLE | 返回主从实例所属的角色 |
27 | SAVE | 同步保存数据到硬盘 |
28 | SHUTDOWN [NOSAVE] [SAVE] | 异步保存数据到硬盘,并关闭服务器 |
29 | SLAVEOF host port | 将当前服务器转变为指定服务器的从属服务器(slave server) |
30 | SLOWLOG subcommand [argument] | 管理 redis 的慢日志 |
31 | SYNC | 用于复制功能(replication)的内部命令 |
为什么Redis的操作是原子性的,怎么保证原子性的
- 对于Redis而言,命令的原子性指的是:一个操作不可以再分,操作要么执行,要么不执行。
Redis的操作之所以是原子性的,是因为Redis是单线程的。
Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。 - 多个命令在并发中也是原子性的吗?
不一定,将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现。
Redis 常见性能问题和解决方案
(1) Master最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件;
(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次;
(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内;
(4) 尽量避免在压力很大的主库上增加从库;
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-Slave3…。