mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
745 lines
31 KiB
HTML
745 lines
31 KiB
HTML
<!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>04 数据复制:如何保证数据在分布式场景下的高可用?.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="/专栏/24讲吃透分布式数据库-完/00 开篇词 吃透分布式数据库,提升职场竞争力.md.html">00 开篇词 吃透分布式数据库,提升职场竞争力.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/01 导论:什么是分布式数据库?聊聊它的前世今生.md.html">01 导论:什么是分布式数据库?聊聊它的前世今生.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/02 SQL vs NoSQL:一次搞清楚五花八门的“SQL”.md.html">02 SQL vs NoSQL:一次搞清楚五花八门的“SQL”.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/03 数据分片:如何存储超大规模的数据?.md.html">03 数据分片:如何存储超大规模的数据?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a class="current-tab" href="/专栏/24讲吃透分布式数据库-完/04 数据复制:如何保证数据在分布式场景下的高可用?.md.html">04 数据复制:如何保证数据在分布式场景下的高可用?.md.html</a>
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/05 一致性与 CAP 模型:为什么需要分布式一致性?.md.html">05 一致性与 CAP 模型:为什么需要分布式一致性?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/06 实践:设计一个最简单的分布式数据库.md.html">06 实践:设计一个最简单的分布式数据库.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/07 概要:什么是存储引擎,为什么需要了解它?.md.html">07 概要:什么是存储引擎,为什么需要了解它?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/08 分布式索引:如何在集群中快速定位数据?.md.html">08 分布式索引:如何在集群中快速定位数据?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/09 日志型存储:为什么选择它作为底层存储?.md.html">09 日志型存储:为什么选择它作为底层存储?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/10 事务处理与恢复(上):数据库崩溃后如何保证数据不丢失?.md.html">10 事务处理与恢复(上):数据库崩溃后如何保证数据不丢失?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/11 事务处理与恢复(下):如何控制并发事务?.md.html">11 事务处理与恢复(下):如何控制并发事务?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/12 引擎拓展:解读当前流行的分布式存储引擎.md.html">12 引擎拓展:解读当前流行的分布式存储引擎.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/13 概要:分布式系统都要解决哪些问题?.md.html">13 概要:分布式系统都要解决哪些问题?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/14 错误侦测:如何保证分布式系统稳定?.md.html">14 错误侦测:如何保证分布式系统稳定?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/15 领导选举:如何在分布式系统内安全地协调操作?.md.html">15 领导选举:如何在分布式系统内安全地协调操作?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/16 再谈一致性:除了 CAP 之外的一致性模型还有哪些?.md.html">16 再谈一致性:除了 CAP 之外的一致性模型还有哪些?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/17 数据可靠传播:反熵理论如何帮助数据库可靠工作?.md.html">17 数据可靠传播:反熵理论如何帮助数据库可靠工作?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/18 分布式事务(上):除了 XA,还有哪些原子提交算法吗?.md.html">18 分布式事务(上):除了 XA,还有哪些原子提交算法吗?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/19 分布式事务(下):Spanner 与 Calvin 的巅峰对决.md.html">19 分布式事务(下):Spanner 与 Calvin 的巅峰对决.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/20 共识算法:一次性说清楚 Paxos、Raft 等算法的区别.md.html">20 共识算法:一次性说清楚 Paxos、Raft 等算法的区别.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/21 知识串讲:如何取得性能和可扩展性的平衡?.md.html">21 知识串讲:如何取得性能和可扩展性的平衡?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/22 发展与局限:传统数据库在分布式领域的探索.md.html">22 发展与局限:传统数据库在分布式领域的探索.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/23 数据库中间件:传统数据库向分布式数据库的过渡.md.html">23 数据库中间件:传统数据库向分布式数据库的过渡.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/24 现状解读:分布式数据库的最新发展情况.md.html">24 现状解读:分布式数据库的最新发展情况.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/加餐1 概念解析:云原生、HTAP、图与内存数据库.md.html">加餐1 概念解析:云原生、HTAP、图与内存数据库.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/加餐2 数据库选型:我们该用什么分布式数据库?.md.html">加餐2 数据库选型:我们该用什么分布式数据库?.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>04 数据复制:如何保证数据在分布式场景下的高可用?</h1>
|
||
|
||
<p>我们上一讲介绍了分片技术,它主要的目的是提高数据容量和性能。这一讲,我们将介绍分布式数据库另外一个重要根基:复制。</p>
|
||
|
||
<p>复制的主要目的是在几个不同的数据库节点上保留相同数据的副本,从而提供一种数据冗余。这份冗余的数据可以提高数据查询性能,而更重要的是保证数据库的可用性。</p>
|
||
|
||
<p>本讲主要介绍两种复制模式:单主复制与多主复制,并通过 MySQL 复制技术的演化来进行相应的展示。</p>
|
||
|
||
<p>现在让我们开始学习单主复制,其中不仅介绍了该技术本身,也涉及了一些复制领域的话题,如复制延迟、高可用和复制方式等。</p>
|
||
|
||
<h3>单主复制</h3>
|
||
|
||
<p>单主复制,也称主从复制。写入主节点的数据都需要复制到从节点,即存储数据库副本的节点。当客户要写入数据库时,他们必须将请求发送给主节点,而后主节点将这些数据转换为复制日志或修改数据流发送给其所有从节点。从使用者的角度来看,从节点都是只读的。下图就是经典的主从复制架构。</p>
|
||
|
||
<p><img src="assets/Ciqc1GAJV6SADprzAACli5qqAMo678.png" alt="Drawing 0.png" /></p>
|
||
|
||
<p>这种模式是最早发展起来的复制模式,不仅被广泛应用在传统数据库中,如 PostgreSQL、MySQL、Oracle、SQL Server;它也被广泛应用在一些分布式数据库中,如 MongoDB、RethinkDB 和 Redis 等。</p>
|
||
|
||
<p>那么接下来,我们就从复制同步模式、复制延迟、复制与高可用性以及复制方式几个方面来具体说说这个概念。</p>
|
||
|
||
<h4>复制同步模式</h4>
|
||
|
||
<p>复制是一个非常耗费时间而且很难预测完成情况的操作。虽然其受影响的因素众多,但一个复制操作是同步发生还是异步发生,被认为是极为重要的影响因素,可以从以下三点来分析。</p>
|
||
|
||
<ol>
|
||
|
||
<li>同步复制:如果由于从库已崩溃,存在网络故障或其他原因而没有响应,则主库也无法写入该数据。</li>
|
||
|
||
<li>半同步复制:其中部分从库进行同步复制,而其他从库进行异步复制。也就是,如果其中一个从库同步确认,主库可以写入该数据。</li>
|
||
|
||
<li>异步复制:不管从库的复制情况如何,主库可以写入该数据。而此时,如果主库失效,那么还未同步到从库的数据就会丢失。</li>
|
||
|
||
</ol>
|
||
|
||
<p>可以看到不同的同步模式是在性能和一致性上做平衡,三种模式对应不同场景,并没有好坏差异。用户需要根据自己的业务场景来设置不同的同步模式。</p>
|
||
|
||
<h4>复制延迟</h4>
|
||
|
||
<p>如果我们想提高数据库的查询能力,最简便的方式是向数据库集群内添加足够多的从节点。这些从节点都是只读节点,故查询请求可以很好地在这些节点分散开。</p>
|
||
|
||
<p>但是如果使用同步复制,每次写入都需要同步所有从节点,会造成一部分从节点已经有数据,但是主节点还没写入数据。而异步复制的问题是从节点的数据可能不是最新的。</p>
|
||
|
||
<p>以上这些问题被称为“复制延迟”,在一般的材料中,我们会听到诸如“写后读”“读单增”等名词来解决复制延迟。但是这些概念其实是数据一致性模型的范畴。我将会在下一讲中深入介绍它们。</p>
|
||
|
||
<h4>复制与高可用性</h4>
|
||
|
||
<p>高可用(High availablity)是一个 IT 术语,指系统无中断地执行其功能的能力。系统中的任何节点都可能由于各种出其不意的故障而造成计划外停机;同时为了要维护系统,我们也需要一些计划内的停机。采用主从模式的数据库,可以防止单一节点挂起导致的可用性降低的问题。</p>
|
||
|
||
<p>系统可用程度一般使用小数点后面多个 9 的形式,如下表所示。</p>
|
||
|
||
<table>
|
||
|
||
<thead>
|
||
|
||
<tr>
|
||
|
||
<th><strong>可用性</strong></th>
|
||
|
||
<th><strong>年故障时间</strong></th>
|
||
|
||
</tr>
|
||
|
||
</thead>
|
||
|
||
<tbody>
|
||
|
||
<tr>
|
||
|
||
<td>99.9999%</td>
|
||
|
||
<td>32秒</td>
|
||
|
||
</tr>
|
||
|
||
<tr>
|
||
|
||
<td>99.999%</td>
|
||
|
||
<td>5分15秒</td>
|
||
|
||
</tr>
|
||
|
||
<tr>
|
||
|
||
<td>99.99%</td>
|
||
|
||
<td>52分34秒</td>
|
||
|
||
</tr>
|
||
|
||
<tr>
|
||
|
||
<td>99.9%</td>
|
||
|
||
<td>8小时46分</td>
|
||
|
||
</tr>
|
||
|
||
<tr>
|
||
|
||
<td>99%</td>
|
||
|
||
<td>3天15小时36分</td>
|
||
|
||
</tr>
|
||
|
||
</tbody>
|
||
|
||
</table>
|
||
|
||
<p>一般的生产系统都会至少有两个 9 的保证,追求三个 9。想要做到 4 个 9 是非常最具有挑战的。</p>
|
||
|
||
<p>在主从模式下,为了支撑高可用,就需要进行故障处理。我这里总结了两种可能的故障及其处理方案。</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>从节点故障</strong>。由于每个节点都复制了从主库那里收到的数据更改日志,因此它知道在发生故障之前已处理的最后一个事务,由此可以凭借此信息从主节点或其他从节点那里恢复自己的数据。</li>
|
||
|
||
<li><strong>主节点故障</strong>。在这种情况下,需要在从节点中选择一个成为新的主节点,此过程称为故障转移,可以手动或自动触发。其典型过程为:第一步根据超时时间确定主节点离线;第二步选择新的主节点,这里注意<strong>新的主节点通常应该与旧的主节点数据最为接近</strong>;第三步是重置系统,让它成为新的主节点。</li>
|
||
|
||
</ol>
|
||
|
||
<h4>复制方式</h4>
|
||
|
||
<p>为了灵活并高效地复制数据,下面我介绍几种常用的复制方式。</p>
|
||
|
||
<p><strong>1. 基于语句的复制</strong></p>
|
||
|
||
<p>主库记录它所执行的每个写请求(一般以 SQL 语句形式保存),每个从库解析并执行该语句,就像从客户端收到该语句一样。但这种复制会有一些潜在问题,如语句使用了获取当前时间的函数,复制后会在不同数据节点上产生不同的值。</p>
|
||
|
||
<p>另外如自增列、触发器、存储过程和函数都可能在复制后产生意想不到的问题。但可以通过预处理规避这些问题。使用该复制方式的分布式数据库有 VoltDB、Calvin。</p>
|
||
|
||
<p><strong>2. 日志(WAL)同步</strong></p>
|
||
|
||
<p>WAL 是一组字节序列,其中包含对数据库的所有写操作。它的内容是一组低级操作,如向磁盘的某个页面的某个数据块写入一段二进制数据,主库通过网络将这样的数据发送给从库。</p>
|
||
|
||
<p>这种方法避免了上面提到的语句中部分操作复制后产生的一些副作用,但要求主从的数据库引擎完全一致,最好版本也要一致。如果要升级从库版本,那么就需要计划外停机。PostgreSQL 和 Oracle 中使用了此方法。</p>
|
||
|
||
<p><strong>3. 行复制</strong></p>
|
||
|
||
<p>它由一系列记录组成,这些记录描述了以行的粒度对数据库表进行的写操作。它与特定存储引擎解耦,并且第三方应用可以很容易解析其数据格式。</p>
|
||
|
||
<p><strong>4. ETL 工具</strong></p>
|
||
|
||
<p>该功能一般是最灵活的方式。用户可以根据自己的业务来设计复制的范围和机制,同时在复制过程中还可以进行如过滤、转换和压缩等操作。但性能一般较低,故适合处理子数据集的场景。</p>
|
||
|
||
<p>关于单主复制就介绍到这里,下面我们再来说说多主复制。</p>
|
||
|
||
<h3>多主复制</h3>
|
||
|
||
<p>也称为主主复制。数据库集群内存在多个对等的主节点,它们可以同时接受写入。每个主节点同时充当主节点的从节点。</p>
|
||
|
||
<p>多主节点的架构模式最早来源于 DistributedSQL 这一类多数据中心,跨地域的分布式数据库。在这样的物理空间相距甚远,有多个数据中心参与的集群中,每个数据中心内都有一个主节点。而在每个数据中心的内部,却是采用常规的单主复制模式。</p>
|
||
|
||
<p>这么设计该类系统的目的在于以下几点。</p>
|
||
|
||
<ol>
|
||
|
||
<li>获得更好的写入性能:使数据可以就近写入。</li>
|
||
|
||
<li>数据中心级别的高可用:每个数据中心可以独立于其他数据中心继续运行。</li>
|
||
|
||
<li>更好的数据访问性能:用户可以访问到距离他最近的数据中心。</li>
|
||
|
||
</ol>
|
||
|
||
<p>但是,此方法的最大缺点是,存在一种可能性,即两个不同的主节点同时修改相同的数据。这其实是非常危险的操作,应尽可能避免。这就需要下一讲要介绍的一致性模型,配合冲突解决机制来规避。</p>
|
||
|
||
<p>还有一种情况是处理客户端离线操作的一致性问题。为了提高性能,数据库客户端往往会缓存一定的写入操作,而后批量发送给服务端。这种情况非常类似于大家使用协作办公文档工具的场景。在这种情况下,每个客户端都可以被看作是具有主节点属性的本地数据库,并且多个客户端之间存在一种异步的多主节点复制的过程。这就需要数据库可以协调写操作,并处理可能的数据冲突。</p>
|
||
|
||
<p>典型的多主复制产品有 MySQL 的 Tungsten Replicator、PostgreSQL 的 BDR 和 Oracle 的 GoldenGate。</p>
|
||
|
||
<p>目前,大部分 NewSQL、DistributedSQL 的分布式数据库都支持多主复制,但是大部分是用 Paxos 或 Raft 等协议来构建复制组,保证写入线性一致或顺序一致性;同时传统数据库如 MySQL 的 MGR 方案也是使用类似的方式,可以看到<strong>该方案是多主复制的发展方向</strong>。关于一致性协议的内容我们将在后续课程中详细介绍。</p>
|
||
|
||
<p>历史的发展潮流是从单主复制向多主复制演变的,以上我们抽象地总结了复制的发展模式和需要关注的技术点。下面我将通过 MySQL 高可用技术的发展路径,向你直观地展示数据库复制技术的发展脉络。</p>
|
||
|
||
<h3>MySQL 复制技术的发展</h3>
|
||
|
||
<p>MySQL 由于其单机机能的限制,很早就发展了数据复制技术以提高性能。同时依赖该技术,MySQL 可用性也得到了长足的发展。</p>
|
||
|
||
<p>截止到现在,该技术经历了四代的发展。第一代为传统复制,使用 MHA(Master High Available)架构;第二代是基于 GTID 的复制,即 GTID+Binlog server 的模式;第三代为增强半同步复制,GTID+增强半同步复制;第四代为 MySQL 原生高可用,即 MySQL InnoDB Cluster。</p>
|
||
|
||
<p>数据库的复制技术需要考虑两个因素:数据一致 RPO 和业务连续性 RTO。所以,就像前面的内容所强调的,复制与一致性是一对如影随形的概念,本讲内容聚焦于复制,但是会提到关于一致性相关的概念。</p>
|
||
|
||
<p>下面我就从第一代复制技术开始说起。</p>
|
||
|
||
<h4>MHA 复制控制</h4>
|
||
|
||
<p>下图是 MHA 架构图。</p>
|
||
|
||
<p><img src="assets/Cip5yGAJV9qAVnjXAAC85xLxhaU613.png" alt="Drawing 1.png" /></p>
|
||
|
||
<p>MHA 作为第一代复制架构,有如下适用场景:</p>
|
||
|
||
<ol>
|
||
|
||
<li>MySQL 的版本≤5.5,这一点说明它很古老;</li>
|
||
|
||
<li>只用于异步复制且一主多从环境;</li>
|
||
|
||
<li>基于传统复制的高可用。</li>
|
||
|
||
</ol>
|
||
|
||
<p>MHA 尽最大能力做数据补偿,但并不保证一定可以成功;它也尽最大努力在实现 RPO,有 RTO 概念支持。可以看到它只是一个辅助工具,本身的架构与机制对 RPO 和 RTO 没有任何保障。</p>
|
||
|
||
<p>那么由此可知,它会存在如下几个问题:</p>
|
||
|
||
<ol>
|
||
|
||
<li>它的 GTID 模型强依赖 binlog server,但是对于 5.7 后的 binlog 却不能识别,同时对并行复制支持不好;</li>
|
||
|
||
<li>服务 IP 切换依赖自行编写的脚本,也可以与 DNS 结合,其运维效果取决于运维人员的经验;</li>
|
||
|
||
<li>运维上需要做 SSH 信任、切换判断等人工操作,总体上处于“刀耕火种”的状态,自动化程度较低,维护难度高;</li>
|
||
|
||
<li>现在项目基本无维护。</li>
|
||
|
||
</ol>
|
||
|
||
<p>从上述问题中可以看到,MHA 作为第一代复制架构,功能相对原始,但已经为复制技术的发展开辟了道路,特别是对 GTID 和 binlog 的应用。但如果不是维护比较古老的 MySQL 集群,目前已经不推荐采用它了。</p>
|
||
|
||
<h4>半同步复制</h4>
|
||
|
||
<p>这是第二代复制技术,它与第一代技术的差别表现在以下几点。</p>
|
||
|
||
<ol>
|
||
|
||
<li>binlog 使用半同步,而第一代是异步同步。它保障了数据安全,一般至少要同步两个节点,保证数据的 RPO。</li>
|
||
|
||
<li>同时保留异步复制,保障了复制性能。并通过监控复制的延迟,保证了 RTO。</li>
|
||
|
||
<li>引入配置中心,如 consul。对外提供健康的 MySQL 服务。</li>
|
||
|
||
<li>这一代开始需要支持跨 IDC 复制。需要引入监控 Monitor,配合 consul 注册中心。多个 IDC 中 Monitor 组成分布式监控,把健康的 MySQL 注册到 consul 中,同时将从库复制延迟情况也同步到 consul 中。</li>
|
||
|
||
</ol>
|
||
|
||
<p>下图就是带有 consul 注册中心与监控模块的半同步复制架构图。</p>
|
||
|
||
<p><img src="assets/Cip5yGAJV-KAAg5HAAF8syZ9vQM483.png" alt="Drawing 2.png" /></p>
|
||
|
||
<p>第二代复制技术也有自身的一些缺陷。</p>
|
||
|
||
<ol>
|
||
|
||
<li>存在幻读的情况。当事务同步到从库但没有 ACK 时,主库发生宕机;此时主库没有该事务,而从库有。</li>
|
||
|
||
<li>MySQL 5.6 本身半同步 ACK 确认在 dump_thread 中,dump_thread 存在 IO 瓶颈问题。</li>
|
||
|
||
</ol>
|
||
|
||
<p>基于此,第三代复制技术诞生。</p>
|
||
|
||
<h4>增强半同步复制</h4>
|
||
|
||
<p>这一代需要 MySQL 是 5.7 以后的版本。有一些典型的框架来支持该技术,如 MySQL Replication Manager、GitHub-orchestrator 和国内青云开源的 Xenon 等。</p>
|
||
|
||
<p>这一代复制技术采用的是增强半同步。首先主从的复制都是用独立的线程来运行;其次主库采用 binlog group commit,也就是组提交来提供数据库的写入性能;而从库采用并行复制,它是基于事务的,通过数据参数调整线程数量来提高性能。这样主库可以并行,从库也可以并行。</p>
|
||
|
||
<p>这一代技术体系强依赖于增强半同步,利用半同步保证 RPO,对于 RTO,则取决于复制延迟。</p>
|
||
|
||
<p>下面我们用 Xenon 来举例说明,请看下图(图片来自官网)。</p>
|
||
|
||
<p><img src="assets/CgpVE2AJV-mAE6vWAAB_JZptW8Y497.png" alt="Drawing 3.png" /></p>
|
||
|
||
<p>从图中可以看到。每个节点上都有一个独立的 agent,这些 agent 利用 raft 构建一致性集群,利用 GTID 做索引选举主节点;而后主节点对外提供写服务,从节点提供读服务。</p>
|
||
|
||
<p>当主节点发生故障后,agent 会通过 ping 发现该故障。由于 GTID 和增强半同步的加持,从节点与主节点数据是一致的,因此很容易将从节点提升为主节点。</p>
|
||
|
||
<p>第三代技术也有自身的缺点,如增强半同步中存在幽灵事务。这是由于数据写入 binlog 后,主库掉电。由于故障恢复流程需要从 binlog 中恢复,那么这份数据就在主库。但是如果它没有被同步到从库,就会造成从库不能切换为主库,只能去尝试恢复原崩溃的主库。</p>
|
||
|
||
<h4>MySQL 组复制</h4>
|
||
|
||
<p>组复制是 MySQL 提供的新一代高可用技术的重要组成。其搭配 MySQL Router 或 Proxy,可以实现原生的高可用。</p>
|
||
|
||
<p>从这一代开始,MySQL 支持多主复制,同时保留单主复制的功能。其单主高可用的原理与第三代技术类似,这里我们不做过多分析了。</p>
|
||
|
||
<p>现在说一下它的多主模式,原理是使用 MySQL Router 作为数据路由层,来控制读写分离。而后组内部使用 Paxos 算法构建一致性写入。</p>
|
||
|
||
<p>它与第三代复制技术中使用的一致性算法的作用不同。三代中我们只使用该算法来进行选主操作,数据的写入并不包含在其中;而组复制的多主技术需要 Paxos 算法深度参与,并去决定每一次数据的写入,解决写入冲突。</p>
|
||
|
||
<p>组复制有如下几个优点。</p>
|
||
|
||
<ul>
|
||
|
||
<li>高可用分片:数据库节点动态添加和移除。分片实现写扩展,每个分片是一个复制组。可以结合上一讲中对于 TiDB 的介绍,原理类似。</li>
|
||
|
||
<li>自动化故障检测与容错:如果一个节点无法响应,组内大多数成员认为该节点已不正常,则自动隔离。</li>
|
||
|
||
<li>方案完整:前面介绍的方案都需要 MySQL 去搭配一系列第三方解决方案;而组复制是原生的完整方案,不需要第三方组件接入。</li>
|
||
|
||
</ul>
|
||
|
||
<p>当然,组复制同样也有一些限制。主要集中在需要使用较新的特性,一些功能在多组复制中不支持,还有运维人员经验缺乏等。</p>
|
||
|
||
<p>相信随着 MySQL 的发展,将会有越来越多的系统迁移到组复制中,多主模式也会逐步去替代单主模式。</p>
|
||
|
||
<h3>总结</h3>
|
||
|
||
<p>这一讲内容就介绍到这里了。我们深入介绍了复制技术在分布式数据库中的作用;探讨了单主和多主两种复制技术;而后通过 MySQL 复制技术的发展路径来介绍了复制技术的应用案例。</p>
|
||
|
||
<p>如我在上面所描述的,复制往往需要与一致性放在一起讨论。本讲聚焦于复制,下一讲我们将详细探讨一致性问题,包括 CAP 理论与一致性模型,并带你研究它与复制的结合。</p>
|
||
|
||
<h3>教学相长</h3>
|
||
|
||
<p>这里给你留一个思考题:我们常听到一种叫作“无主复制”的技术,它与我们这一讲介绍的两种复制技术有什么异同?</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/03 数据分片:如何存储超大规模的数据?.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/05 一致性与 CAP 模型:为什么需要分布式一致性?.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":"70996e00cbfe3d60","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>
|
||
|