learn.lianglianglee.com/专栏/Redis 核心原理与实战/36 实战:Redis 主从同步.md.html
2022-05-11 18:57:05 +08:00

1079 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>36 实战Redis 主从同步.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Redis 核心原理与实战/01 Redis 是如何执行的.md.html">01 Redis 是如何执行的.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/02 Redis 快速搭建与使用.md.html">02 Redis 快速搭建与使用.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/03 Redis 持久化——RDB.md.html">03 Redis 持久化——RDB.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/04 Redis 持久化——AOF.md.html">04 Redis 持久化——AOF.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/05 Redis 持久化——混合持久化.md.html">05 Redis 持久化——混合持久化.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/06 字符串使用与内部实现原理.md.html">06 字符串使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/07 附录:更多字符串操作命令.md.html">07 附录:更多字符串操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/08 字典使用与内部实现原理.md.html">08 字典使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/09 附录:更多字典操作命令.md.html">09 附录:更多字典操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/10 列表使用与内部实现原理.md.html">10 列表使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/11 附录:更多列表操作命令.md.html">11 附录:更多列表操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/12 集合使用与内部实现原理.md.html">12 集合使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/13 附录:更多集合操作命令.md.html">13 附录:更多集合操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/14 有序集合使用与内部实现原理.md.html">14 有序集合使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/15 附录:更多有序集合操作命令.md.html">15 附录:更多有序集合操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/16 Redis 事务深入解析.md.html">16 Redis 事务深入解析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/17 Redis 键值过期操作.md.html">17 Redis 键值过期操作.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/18 Redis 过期策略与源码分析.md.html">18 Redis 过期策略与源码分析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/19 Redis 管道技术——Pipeline.md.html">19 Redis 管道技术——Pipeline.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/20 查询附近的人——GEO.md.html">20 查询附近的人——GEO.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/21 游标迭代器过滤器——Scan.md.html">21 游标迭代器过滤器——Scan.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/22 优秀的基数统计算法——HyperLogLog.md.html">22 优秀的基数统计算法——HyperLogLog.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/23 内存淘汰机制与算法.md.html">23 内存淘汰机制与算法.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/24 消息队列——发布订阅模式.md.html">24 消息队列——发布订阅模式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/25 消息队列的其他实现方式.md.html">25 消息队列的其他实现方式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/26 消息队列终极解决方案——Stream.md.html">26 消息队列终极解决方案——Stream.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/27 消息队列终极解决方案——Stream.md.html">27 消息队列终极解决方案——Stream.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/28 实战:分布式锁详解与代码.md.html">28 实战:分布式锁详解与代码.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/29 实战:布隆过滤器安装与使用及原理分析.md.html">29 实战:布隆过滤器安装与使用及原理分析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/30 完整案例:实现延迟队列的两种方法.md.html">30 完整案例:实现延迟队列的两种方法.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/31 实战:定时任务案例.md.html">31 实战:定时任务案例.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/32 实战RediSearch 高性能的全文搜索引擎.md.html">32 实战RediSearch 高性能的全文搜索引擎.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/33 实战Redis 性能测试.md.html">33 实战Redis 性能测试.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/34 实战Redis 慢查询.md.html">34 实战Redis 慢查询.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/35 实战Redis 性能优化方案.md.html">35 实战Redis 性能优化方案.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Redis 核心原理与实战/36 实战Redis 主从同步.md.html">36 实战Redis 主从同步.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/37 实战Redis哨兵模式.md.html">37 实战Redis哨兵模式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/38 实战Redis 哨兵模式(下).md.html">38 实战Redis 哨兵模式(下).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/39 实战Redis 集群模式(上).md.html">39 实战Redis 集群模式(上).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/40 实战Redis 集群模式(下).md.html">40 实战Redis 集群模式(下).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/41 案例Redis 问题汇总和相关解决方案.md.html">41 案例Redis 问题汇总和相关解决方案.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/42 技能学习指南.md.html">42 技能学习指南.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/43 加餐Redis 的可视化管理工具.md.html">43 加餐Redis 的可视化管理工具.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>36 实战Redis 主从同步</h1>
<p>主从同步(主从复制)是 Redis 高可用服务的基石,也是多机运行中最基础的一个。我们把主要存储数据的节点叫做主节点 (master把其他通过复制主节点数据的副本节点叫做从节点 (slave如下图所示</p>
<p><img src="assets/29675e90-800a-11ea-8186-75c206477f1e" alt="主从同步.png" /></p>
<p>在 Redis 中一个主节点可以拥有多个从节点,一个从节点也可以是其他服务器的主节点,如下图所示:</p>
<p><img src="assets/369eda70-800a-11ea-b751-6ff511beda88" alt="主从同步-从从模式.png" /></p>
<h3>主从同步的优点</h3>
<p>主从同步具有以下三个优点:</p>
<ul>
<li>性能方面:有了主从同步之后,可以把查询任务分配给从服务器,用主服务器来执行写操作,这样极大的提高了程序运行的效率,把所有压力分摊到各个服务器了;</li>
<li>高可用:当有了主从同步之后,当主服务器节点宕机之后,可以很迅速的把从节点提升为主节点,为 Redis 服务器的宕机恢复节省了宝贵的时间;</li>
<li>防止数据丢失:当主服务器磁盘坏掉之后,其他从服务器还保留着相关的数据,不至于数据全部丢失。</li>
</ul>
<p>既然主从同步有这么多的优点,那接下来我们来看如何开启和使用主从同步功能。</p>
<h3>开启主从同步</h3>
<h4><strong>运行中设置从服务器</strong></h4>
<p>在 Redis 运行过程中,我们可以使用 <code>replicaof host port</code> 命令,把自己设置为目标 IP 的从服务器,执行命令如下:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; replicaof 127.0.0.1 6380
OK
</code></pre>
<p>如果主服务设置了密码,需要在从服务器输入主服务器的密码,使用 <code>config set masterauth 主服务密码</code> 命令的方式,例如:</p>
<pre><code class="language-shell">127.0.0.1:6377&gt; config set masterauth pwd654321
OK
</code></pre>
<p><strong>1. 执行流程</strong></p>
<p>在执行完 replicaof 命令之后,从服务器的数据会被清空,主服务会把它的数据副本同步给从服务器。</p>
<p><strong>2. 测试同步功能</strong></p>
<p>主从服务器设置完同步之后,我们来测试一下主从数据同步,首先我们先在主服务器上执行保存数据操作,再去从服务器查询。</p>
<p>主服务器执行命令:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; set lang redis
OK
</code></pre>
<p>从服务执行查询:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; get lang
&quot;redis&quot;
</code></pre>
<p>可以看出数据已经被正常同步过来了。</p>
<h4><strong>启动时设置从服务器</strong></h4>
<p>我们可以使用命令 <code>redis-server --port 6380 --replicaof 127.0.0.1 6379</code> 将自己设置成目标服务器的从服务器。</p>
<h3>数据同步</h3>
<h4><strong>完整数据同步</strong></h4>
<p>当有新的从服务器连接时,为了保障多个数据库的一致性,主服务器会执行一次 bgsave 命令生成一个 RDB 文件,然后再以 Socket 的方式发送给从服务器,从服务器收到 RDB 文件之后再把所有的数据加载到自己的程序中,就完成了一次全量的数据同步。</p>
<h4><strong>部分数据同步</strong></h4>
<p>在 Redis 2.8 之前每次从服务器离线再重新上线之前,主服务器会进行一次完整的数据同步,然后这种情况如果发生在离线时间比较短的情况下,只有少量的数据不同步却要同步所有的数据是非常笨拙和不划算的,在 Redis 2.8 这个功能得到了优化。</p>
<p>Redis 2.8 的优化方法是当从服务离线之后,主服务器会把离线之后的写入命令,存储在一个特定大小的队列中,队列是可以保证先进先出的执行顺序的,当从服务器重写恢复上线之后,主服务会判断离线这段时间内的命令是否还在队列中,如果在就直接把队列中的数据发送给从服务器,这样就避免了完整同步的资源浪费。</p>
<blockquote>
<p>小贴士:存储离线命令的队列大小默认是 1MB使用者可以自行修改队列大小的配置项 repl-backlog-size。</p>
</blockquote>
<h4><strong>无盘数据同步</strong></h4>
<p>从前面的内容我们可以得知,在第一次主从连接的时候,会先产生一个 RDB 文件,再把 RDB 文件发送给从服务器,如果主服务器是非固态硬盘的时候,系统的 I/O 操作是非常高的为了缓解这个问题Redis 2.8.18 新增了无盘复制功能,无盘复制功能不会在本地创建 RDB 文件,而是会派生出一个子进程,然后由子进程通过 Socket 的方式,直接将 RDB 文件写入到从服务器这样主服务器就可以在不创建RDB文件的情况下完成与从服务器的数据同步。</p>
<p>要使用无须复制功能,只需把配置项 repl-diskless-sync 的值设置为 yes 即可,它默认配置值为 no。</p>
<h3>查询服务器的角色</h3>
<p>我们使用 role 命令,来查询当前服务器的主从角色信息。</p>
<h4><strong>主服务查看</strong></h4>
<p>在主服务器上执行 role 结果如下:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; role
1) &quot;master&quot;
2) (integer) 546
3) 1) 1) &quot;172.17.0.1&quot;
2) &quot;6379&quot;
3) &quot;546&quot;
</code></pre>
<p>master 表示主服务器,底下是从服务器的 IP、端口和连接时间。</p>
<h4><strong>从服务器查看</strong></h4>
<p>在从服务器执行 role 命令,执行结果如下:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; role
1) &quot;slave&quot;
2) &quot;192.168.1.71&quot;
3) (integer) 6380
4) &quot;connected&quot;
5) (integer) 14
</code></pre>
<p>slave 表示从服务器,底下主服务器的 IP、端口和连接时间。</p>
<h3>关闭主从同步</h3>
<p>我们可以使用 <code>replicaof no one</code> 命令来停止从服务器的复制,操作命令如下:</p>
<pre><code class="language-shell">127.0.0.1:6379&gt; role #查询当前角色
1) &quot;slave&quot; #从服务器
2) &quot;192.168.1.71&quot;
3) (integer) 6380
4) &quot;connected&quot;
5) (integer) 14
127.0.0.1:6379&gt; replicaof no one #关闭同步
OK
127.0.0.1:6379&gt; role #查询当前角色
1) &quot;master&quot; #主服务器
2) (integer) 1097
3) (empty list or set)
</code></pre>
<p>可以看出执行了 <code>replicaof no one</code> 命令之后,自己就从服务器变成主服务器了。</p>
<blockquote>
<p>小贴士:服务器类型的转换并不会影响数据,这台服务器的数据将会被保留。</p>
</blockquote>
<h3>注意事项</h3>
<p>主从同步有一些需要注意的点,我们来看一下。</p>
<h4><strong>数据一致性问题</strong></h4>
<p>当从服务器已经完成和主服务的数据同步之后,再新增的命令会以异步的方式发送至从服务器,在这个过程中主从同步会有短暂的数据不一致,如在这个异步同步发生之前主服务器宕机了,会造成数据不一致。</p>
<h4><strong>从服务器只读性</strong></h4>
<p>默认在情况下,处于复制模式的主服务器既可以执行写操作也可以执行读操作,而从服务器则只能执行读操作。</p>
<p>可以在从服务器上执行 <code>config set replica-read-only no</code> 命令,使从服务器开启写模式,但需要注意以下几点:</p>
<ul>
<li>在从服务器上写的数据不会同步到主服务器;</li>
<li>当键值相同时主服务器上的数据可以覆盖从服务器;</li>
<li>在进行完整数据同步时,从服务器数据会被清空。</li>
</ul>
<h4><strong>复制命令的变化</strong></h4>
<p>Redis 5.0 之前使用的复制命令是 slaveof在 Redis 5.0 之后复制命令才被改为 replicaof在高版本Redis 5+)中我们应该尽量使用 replicaof因为 slaveof 命令可能会被随时废弃掉。</p>
<h3>小结</h3>
<p>本文我们了解了 Redis 多机运行的基础功能主从同步,主从同步可以通过 <code>replicaof host port</code> 命令开启,知道了同步的三种方式:完整数据同步(第一次全量 RDB 同步部分数据同步Redis 2.8 对于短时间离线的同步功能优化),无盘同步(非 RDB 生成的方式同步数据),我们也可以使用 <code>replicaof no one</code> 命令来停止从服务器的复制功能。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Redis 核心原理与实战/35 实战Redis 性能优化方案.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Redis 核心原理与实战/37 实战Redis哨兵模式.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709973ff3a6e3d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>