<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Galera</title>
	<atom:link href="https://blog.gmem.cc/tag/galera/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Wed, 29 Apr 2026 12:04:56 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Galera学习笔记</title>
		<link>https://blog.gmem.cc/galera-study-note</link>
		<comments>https://blog.gmem.cc/galera-study-note#comments</comments>
		<pubDate>Tue, 14 Jan 2020 11:24:25 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Galera]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=35345</guid>
		<description><![CDATA[<p>MariaDB对比MySQL MariaDB提供了兼容MySQL的数据库解决方案，它本身是MySQL的一个Fork。 复制 MySQL中复制（Replication）是异步的、单向的。其中一个服务器作为Master，其它的作为Slave。所谓主主模式，是两个服务器分别配置为对方的Slave。MariaDB提供主主复制、主从复制。 MySQL 的默认二进制日志格式是基于行的，而在 MariaDB 中，默认的二进制日志格式是混合式的。 MariaDB支持二进制日志压缩（log_bin_compress）。 集群 MySQL提供shared-nothing的集群支持，通过自动分区的方式将数据分发到不同节点。在内部MySQL使用同步的、两阶段的提交，确保数据被写入到多个分片。 MariaDB使用Galera Cluster实现多主，从10.1开始MariaDB内置Galera，只需要配置参数即可启用集群模式。 存储引擎 MariaDB支持更多的存储引擎：XtraDB（10.2-的默认引擎，InnoDB增强版）、InnoDB（10.2+默认引擎）、MariaDB ColumnStore、Aria、Archive、Blackhole、Cassandra Storage Engine、Connect、CSV、FederatedX、Memory、Merge、Mroonga、MyISAM、MyRocks、QQGraph、Sequence Storage Engine、SphinxSE、Spider、TokuDB 其它特性 不可见列 <a class="read-more" href="https://blog.gmem.cc/galera-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/galera-study-note">Galera学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">MariaDB对比MySQL</span></div>
<p>MariaDB提供了兼容MySQL的数据库解决方案，它本身是MySQL的一个Fork。</p>
<div class="blog_h2"><span class="graybg">复制</span></div>
<p>MySQL中<span style="background-color: #c0c0c0;">复制（Replication）是异步的、单向的</span>。其中一个服务器作为Master，其它的作为Slave。所谓主主模式，是两个服务器分别配置为对方的Slave。MariaDB提供主主复制、主从复制。</p>
<p>MySQL 的默认二进制日志格式是基于行的，而在 MariaDB 中，默认的二进制日志格式是混合式的。</p>
<p>MariaDB支持二进制日志压缩（log_bin_compress）。</p>
<div class="blog_h2"><span class="graybg">集群</span></div>
<p>MySQL提供shared-nothing的集群支持，通过自动分区的方式将数据分发到不同节点。在内部MySQL使用<span style="background-color: #c0c0c0;">同步的、两阶段的提交，确保数据被写入到多个分片</span>。</p>
<p>MariaDB使用Galera Cluster实现多主，从10.1开始MariaDB内置Galera，只需要配置参数即可启用集群模式。</p>
<div class="blog_h2"><span class="graybg">存储引擎</span></div>
<p>MariaDB支持更多的存储引擎：XtraDB（10.2-的默认引擎，InnoDB增强版）、InnoDB（10.2+默认引擎）、MariaDB ColumnStore、Aria、Archive、Blackhole、Cassandra Storage Engine、Connect、CSV、FederatedX、Memory、Merge、Mroonga、MyISAM、MyRocks、QQGraph、Sequence Storage Engine、SphinxSE、Spider、TokuDB</p>
<div class="blog_h2"><span class="graybg">其它特性</span></div>
<div class="blog_h3"><span class="graybg">不可见列</span></div>
<p>MariaDB 上可用，MySQL 不支持该功能。这个功能允许创建不再 SELECT * 语句中出现的列，而在进行插入时，如果它们的名字没有出现在 INSERT 语句中，就不需要为这些列提供值。</p>
<div class="blog_h3"><span class="graybg">JSON数据类型</span></div>
<p>从 5.7 版本开始，MySQL 支持由 RFC 7159 定义的原生 JSON 数据类型，可以高效地访问 JSON 文档中的数据。</p>
<p>MariaDB 没有提供这一增强功能，认为 JSON 数据类型不是 SQL 标准的一部分。但为了支持从 MySQL 复制数据，MariaDB 为 JSON 定义了一个别名，实际上就是一个 LONGTEXT 列。</p>
<div class="blog_h3"><span class="graybg">线程池</span></div>
<p><span style="background-color: #c0c0c0;">MariaDB 支持连接线程池，这对于短查询和 CPU 密集型的工作负载（OLTP）来说非常有用</span>。<span style="background-color: #c0c0c0;">在 MySQL 的社区版本中，线程数是固定</span>的，因而限制了这种灵活性。MySQL 计划在企业版中增加线程池功能。</p>
<div class="blog_h1"><span class="graybg">Galera基础</span></div>
<p>MariaDB Galera Cluster提供MariaDB多主集群，仅仅对XtraDB/InnoDB存储引擎提供支持。</p>
<p>从MariaDB 10.1开始，<a href="https://github.com/codership/mysql-wsrep">MySQL-wsrep</a>补丁被合并到MariDB，这意味着使用标准的MariaDB+<a href="https://github.com/codership/galera/">Galera wsrep提供者库</a>即可构建Galera集群。该补丁由<a href="https://groups.google.com/forum/?fromgroups#!forum/codership-team">Codership</a>开发，提供<a href="https://galeracluster.com/library/documentation/architecture.html">wsrep API</a>支持。</p>
<div class="blog_h2"><span class="graybg">简介</span></div>
<div class="blog_h3"><span class="graybg">特性</span></div>
<p>Galera的主要特性包括：</p>
<ol>
<li>几乎同步的（virtually synchronous ）的复制</li>
<li>Active-Active多主拓扑支持，不需要failover</li>
<li>针对集群的任何节点进行读写操作</li>
<li>自动的集群成员控制，失败的节点能够自动的移出集群</li>
<li>自动节点加入</li>
<li>真正的（从底层）并行复制</li>
<li>不需要读写分离</li>
<li>Slave支持多线程以提升性能</li>
</ol>
<div class="blog_h3"><span class="graybg">优势</span></div>
<p>基于Galera的集群的主要优势包括：</p>
<ol>
<li>没有Slave的延迟</li>
<li>不会丢失事务</li>
<li>读操作的可扩容性</li>
<li>更小的客户端延迟</li>
</ol>
<div class="blog_h3"><span class="graybg">场景</span></div>
<p>Galera的使用场景包括：</p>
<ol>
<li>从Master读写：Galera支持这种传统场景，但是和MariaDB传统主从复制相比，任何一个<span style="background-color: #c0c0c0;">节点都可以随时成为Master</span>，而其它节点仅仅是客户端将其作为Slave看待而以。由于Slave可以并行的应用writeset，集群的吞吐量会快的多，此外复制的延迟也被消除</li>
<li>广域网集群：可以支持这种网络下的几乎同步复制，可能会存在延迟，取决于RTT。这些延迟仅仅影响commit操作</li>
<li>灾难恢复：广域网集群的子用例。这种情况下，位于某个数据中心的节点仅被动接受复制，不处理任何客户端事务。由于Galera的几乎同步的复制，它不会丢失数据，并可以在主节点宕机后立即升级为新的主节点</li>
<li>延迟消除：让客户端访问靠近自己的节点，可以消除读操作的延迟。延迟仅仅在写操作时才发生</li>
</ol>
<div class="blog_h2"><span class="graybg">版本</span></div>
<p>MariaDB、Galera库、wsrep API版本对应关系如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">MariaDB</td>
<td style="text-align: center;">Galera/wsrep提供者库</td>
<td style="text-align: center;">wsrep API</td>
</tr>
</thead>
<tbody>
<tr>
<td>MariaDB 10.4+</td>
<td>4</td>
<td>26</td>
</tr>
<tr>
<td>MariaDB 10.3-</td>
<td>3</td>
<td>25</td>
</tr>
</tbody>
</table>
<p>使用包名galera-4安装Galera 4 wsrep provider。版本26.4.6对应MariaDB的版本是10.5.7 / 10.4.16</p>
<p>使用包名galera安装Galera 3 wsrep provider。版本25.3.31对应MariaDB的版本是10.3.26 / 10.2.35 / 10.1.48</p>
<div class="blog_h2"><span class="graybg">通用复制库</span></div>
<p>Galera复制功能实现为一个共享库，可以连接到任何实现了wsrep API的事务处理系统。它是由一系列组件组成的协议栈，提供<span style="background-color: #c0c0c0;">准备、复制、应用事务writeset</span>的功能。</p>
<p>在Galera 4很多组件被重新设计。</p>
<div class="blog_h3"><span class="graybg">wsrep API</span></div>
<p>即写入集复制API（writeset replication API），它定义了Galera Replication和MariaDB之间的接口，以及各自的职责。 </p>
<div class="blog_h3"><span class="graybg">wsrep hooks</span></div>
<p>在RDBMS引擎中的wsrep集成点。</p>
<div class="blog_h3"><span class="graybg">Galera Provider</span></div>
<p>为Galera库实现wsrep API。</p>
<p>尽管galera provider负责在提交时，在所有节点认证writeset，但是<span style="background-color: #c0c0c0;">writeset不需要被立即apply到节点</span>。<span style="background-color: #c0c0c0;">实际上writeset被放置在节点的接收队列上，由节点的某个galera slave thread最终apply到数据库中</span>。</p>
<p>Galera slave thread的数量可以通过系统变量<pre class="crayon-plain-tag">wsrep_slave_threads</pre>配置。</p>
<p>Galera slave thread们负责自行判断哪些writeset可以被安全的并行apply，但是，如果<span style="background-color: #c0c0c0;">节点经常出现数据一致性问题，可以将线程数量设置为1</span>。</p>
<div class="blog_h3"><span class="graybg">certification layer</span></div>
<p>负责准备writeset，执行认证测试。</p>
<div class="blog_h3"><span class="graybg">replication</span></div>
<p>管理复制协议，提供整体上的排序能力（ordering capabilities）</p>
<div class="blog_h3"><span class="graybg">GCS framework</span></div>
<p>为Group Communication系统（GCS）提供插件化架构。很多GCS实现可以被适配。内置实现是vsbes、gemini </p>
<div class="blog_h2"><span class="graybg">状态转移</span></div>
<p>所谓状态转移是指从现有节点复制数据，以使得新节点/宕机后恢复的节点到达同步状态的操作。</p>
<div class="blog_h3"><span class="graybg">状态快照转移</span></div>
<p>在一次State Snapshot Transfers / SST中，集群通过从一个节点拷贝完整的数据集，来创建新节点。</p>
<p>当新节点加入集群后，它会触发一次SST。</p>
<div class="blog_h3"><span class="graybg">增量状态转移</span></div>
<p>在一次Incremental State Transfers /IST中，集群从正常节点，拷贝某个节点缺失的writeset。</p>
<p>如果节点离开集群时间比较短，则IST通常比SST更块。</p>
<div class="blog_h2"><span class="graybg">原理</span></div>
<p>在Galera集群中，服务器在提交（Commit）期间进行事务复制。其做法是，<span style="background-color: #c0c0c0;">将事务关联的写入集（writeset）广播到所有节点</span>。 </p>
<div class="blog_h3"><span class="graybg">同步/异步复制</span></div>
<p>同步复制能够保证：如果一个节点上的数据发生变更，那么这些变更会同时发生在其它节点上。</p>
<p>异步复制不提供上述保证，这意味着，在第一个节点上发生变更后，复制完成之前该节点宕机，<span style="background-color: #c0c0c0;">可能会导致数据丢失</span>。</p>
<p>同步复制的优势：</p>
<ol>
<li>使用<span style="background-color: #c0c0c0;">同步复制的集群，总是能保证HA和数据一致性</span>。节点崩溃不会导致数据丢失</li>
<li>同步复制允许<span style="background-color: #c0c0c0;">事务在所有节点上并行的执行</span>。而不是进行Replay</li>
<li>因果律保证：如果先在A节点发生事务，然后在B节点执行查询，那么该查询一定能看到A节点那个事务的结果</li>
</ol>
<p>典型的同步复制利用<span style="background-color: #c0c0c0;">两阶段提交或者分布式锁</span>来实现，非常缓慢。再加上实现的复杂度，导致MySQL、PostgreSQL等传统RDBMS都仅仅支持异步复制，或者所谓半同步复制（主库在执行完客户端提交的事务后不是立刻返回给客户端，而是等待至少一个从库接收到并写到Relay log中才返回给客户端）。</p>
<div class="blog_h3"><span class="graybg">基于认证的复制</span></div>
<p>Galera使用基于认证的复制（Certification-based replication），实现几乎完全同步（virtually synchronous）的数据复制。</p>
<p>Galera的基本思想是乐观执行（ optimistic execution）：<span style="background-color: #c0c0c0;">假设不存在冲突，正常执行事务，直到达到提交点（commit point）</span>，在提交之前，通过认证来检查冲突</p>
<p>Galera的核心机制包括：</p>
<ol>
<li>分组通信（group communication）：定义了数据库节点之间的通信pattern，保证复制数据的一致性</li>
<li>写入集（writeset）：将若干数据库写操作合并为一个消息，避免了每次数据库操作都引发节点之间的协作。<span style="background-color: #c0c0c0;">writeset中包含了事务的所有必要信息</span></li>
<li>数据库状态机（database state machine）：只读事务仅在节点本地执行。非只读事务首先在单个节点本地执行，然后广播writeset到其它节点，认证并apply到数据库</li>
<li>全局事务排序（global transaction reordering）：在复制期间，<span style="background-color: #c0c0c0;">Galera为每个事务分配一个全局的序号即seqno，每个节点将以相同的顺序接收（和应用）事务</span>。当事务到达提交点时，节点会检查上一次成功的事务的seqno。这两次事务seqno的差值，提示了尚未apply的事务数量。节点会对这些没处理的事务进行主键冲突检查，发现冲突则触发认证失败</li>
</ol>
<div class="blog_h3"><span class="graybg">复制流程</span></div>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2021/01/certificationbasedreplication.png"><img class="size-full wp-image-35431 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2021/01/certificationbasedreplication.png" alt="certificationbasedreplication" width="800" height="480" /></a></p>
<p>Galera执行数据复制的流程如下：</p>
<ol>
<li>
<p>当客户端发出一个 commit 指令，在<span style="background-color: #c0c0c0;">事务在服务器端被正式（数据已经写入，只差）提交之前，所有对数据库的更改都会被writeset收集起来</span>，并且<span style="background-color: #c0c0c0;">将 writeset 纪录广播</span>给其他节点</p>
</li>
<li>
<p><span style="background-color: #c0c0c0;">writeset 将在每个节点（包括发起writeset的原始节点）上、基于主键进行决定性认证测试（deterministic certification test,）</span>，测试结果决定节点能否 apply writeset</p>
</li>
<li>
<p>如果认证测试失败：节点丢弃writeset，集群<span style="background-color: #c0c0c0;">回滚原始事务</span></p>
</li>
<li>如果认证测试成功：<span style="background-color: #c0c0c0;">事务被正式提交，其余节点则apply writeset</span></li>
</ol>
<p>只有所有节点都认证成功，达成共识，那么发起事务的原始节点就可以正式提交并给客户端响应。在这个时间点，用户的写入集已经同步到了所有节点，不会因为原始节点故障而导致数据丢失</p>
<div class="blog_h2"><span class="graybg">注意点</span></div>
<p>关于从普通MariaDB转到Galera的TIPS，参考：<a href="https://mariadb.com/kb/en/tips-on-converting-to-galera/">https://mariadb.com/kb/en/tips-on-converting-to-galera/</a></p>
<div class="blog_h3"><span class="graybg">仅支持InnoDB</span></div>
<p>对任何其它引擎的表的写入，包括系统表（mysql.*，使用MyISAM引擎）都不会复制到其它节点。<span style="background-color: #c0c0c0;">例外情况是DDL语句，这些语句虽然修改mysql.*表，但是却会被复制</span>。</p>
<p>对MyISAM复制目前提供试验性支持，设置系统变量：wsrep_replicate_myisam。</p>
<p>FLUSH PRIVILEGES 不会被复制。</p>
<div class="blog_h3"><span class="graybg">不支持明确锁定</span></div>
<p>LOCK TABLES、FLUSH TABLES (table list) WITH READ LOCK等语句不被支持，可以通过适当的事务控制来达到相同效果。</p>
<p>全局性的锁操作，例如FLUSH TABLES WITH READ LOCK则可以被支持。</p>
<div class="blog_h3"><span class="graybg">必须有主键</span></div>
<p>所有表必须具有主键（支持复合主键）。针对无主键表的DELETE操作是不支持的。没有主键的表，可能在不同节点上的顺序不同。</p>
<div class="blog_h3"><span class="graybg">查询日志必须写到文件</span></div>
<p>普通查询日志、缓慢查询日志，不支持写入到表中。如果启用这些日志，你必须设置log_out=FILE</p>
<div class="blog_h3"><span class="graybg">不支持XA事务</span></div>
<div class="blog_h3"><span class="graybg">事务尺寸的限制</span></div>
<p>尽管Galera没有明确限制，但是writeset被存储在内存中，这隐含了对事务尺寸的限制。</p>
<p>为了避免非常大的事务严重影响节点性能。系统变量的默认值设置为：</p>
<ol>
<li>wsrep_max_ws_rows  事务最大影响行数，128K</li>
<li>wsrep_max_ws_size 事务最大尺寸（写入集大小）2GB</li>
</ol>
<p>此外根据社区的反馈，大尺寸事务可能有<span style="background-color: #c0c0c0;">额外的不可忽略的开销</span>。你写入100K的行，可能需要额外200-300MB甚至数G内存。</p>
<div class="blog_h3"><span class="graybg">牵涉DDL时锁语义改变</span></div>
<p>你的DML语句操作一个锁，而另外一个DDL已经发起。正常情况下MySQL会等待元数据锁，但是Galera则会立即执行DML。甚至仅有单个节点的情况下，也会发生这种情况。</p>
<div class="blog_h3"><span class="graybg">自增列空隙</span></div>
<p>不要假设自增长的列是一个个顺序增长的。Galera为了保证没有冲突，为每个节点的自增长键都设置了空虚。</p>
<div class="blog_h3"><span class="graybg">网络分区</span></div>
<p>当出现网络分区，并且当前节点不再大多数节点所在分区。命令会导致ER_UNKNOWN_COM_ERROR：WSREP has not yet prepared node for application use / Unknown command。这个报错是为了放置数据不一致。网络分区状态可以通过检查<pre class="crayon-plain-tag">wsrep_ready</pre>变量发现。</p>
<p>少部分节点所在的分区，节点可能会<span style="background-color: #c0c0c0;">丢弃所有客户端连接</span>，这个行为可能比较意外 —— 因为客户端可能是空闲的，甚至不知道发生什么连接就忽然断了。</p>
<p>这种被分区的节点重新连接到集群后，可能有大量数据需要同步。在同步完成之前，它仍然会报告unknown command。</p>
<div class="blog_h3"><span class="graybg">不要修改binlog格式</span></div>
<p>在运行时修改binlog格式，不但会导致复制失败，还会导致其它节点崩溃。</p>
<div class="blog_h3"><span class="graybg">性能问题</span></div>
<p>根据Galera的设计，<span style="background-color: #c0c0c0;">集群的整体性能取决于最慢的节点</span>。</p>
<p>此外，即使<span style="background-color: #c0c0c0;">只有一个节点。性能也可能比非集群模式的MariaDB低很多</span>，特别是对于大事务。</p>
<div class="blog_h3"><span class="graybg">查询缓存问题</span></div>
<p>在MariaDB Galera Cluster 5.5.40, MariaDB Galera Cluster 10.0.14以及MariaDB 10.1.2之前，需要设置query_cache_size=0以禁用查询缓存。</p>
<div class="blog_h3"><span class="graybg">异步复制模式</span></div>
<p>启用异步复制模式的情况下，不支持在Slave节点上进行并行复制（slave-parallel-threads 大于1）。</p>
<div class="blog_h3"><span class="graybg">表结构不一致</span></div>
<p>节点的表结构可能不一致，特别是在执行rolling schema upgrade期间。</p>
<div class="blog_h3"><span class="graybg">读写分离</span></div>
<p>读写分离不是必须的，但是仍然建议做读写分离，未来底层架构可能变化。</p>
<div class="blog_h1"><span class="graybg">变量清单</span></div>
<div class="blog_h2"><span class="graybg">状态变量</span></div>
<p>所有状态变量具有一致的前缀：</p>
<pre class="crayon-plain-tag">SHOW STATUS LIKE 'wsrep%';</pre>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class=" blog_h3">wsrep_applier_thread_count</td>
<td>当前applier线程的数量，这种线程负责将writeset写入到数据库</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_apply_oooe</strong></td>
<td>writeset被乱序的apply的频繁程度，可以提示并行处理的效率</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_apply_oool</strong></td>
<td>具有高序列号的writeset，在具有低序列号的writeset之前被apply —— 这种情况发生的频繁程度，可以提示处理缓慢的writeset</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_apply_window</strong></td>
<td>并发的被apply的writeset的最高、最低序列号的平均差值</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cert_deps_distance</td>
<td>可能被并行apply的writeset序列号的平均差值，提示潜在的并行度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cert_index_size</td>
<td>认证索引中的条目数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cert_interval</td>
<td>在一个事务被复制期间，平均接收到的新事务的数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_conf_id</strong></td>
<td>集群成员关系发生变化的总次数</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_size</strong></td>
<td>集群成员当前数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_state_uuid</strong></td>
<td>集群当前状态的唯一标识。如果和wsrep_local_state_uuid相同，意味着本节点和集群同步（in sync）</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_cluster_status</strong></td>
<td>
<p>集群组件状态，可能值：</p>
<ol>
<li>PRIMARY：primary group configuration, quorum present</li>
<li>NON_PRIMARY：non-primary group configuration, quorum lost</li>
<li>DISCONNECTED：not connected to group, retrying</li>
</ol>
</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cluster_weight</td>
<td>当前集群成员们的总weight，即当前Primary Component中的节点的pc.weight的求和</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_commit_oooe</strong></td>
<td>事务被乱序commit的频繁程度</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_commit_window</strong></td>
<td>并发的被commit的最大、最小seqno的平均差值</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_connected</strong></td>
<td>MariaDB是否连接到wsrep provider，值ON/OFF</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_desync_count</td>
<td>需要节点临时失去同步状态的、进行中的操作数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_delayed</td>
<td>当前节点在哪些节点上注册到了delayed list</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_evict_list</td>
<td>从集群驱除的节点的UUIDs。在重启mysqld进程之前，被驱逐的节点无法重新加入集群</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_repl_latency</td>
<td>
<p>提示组通信（group communication）的复制（replication）延迟。即从消息发出到消息接收的时延（秒）</p>
<p>由于复制是组操作（group operation），因此该参数提示了最慢的ACK和最长的RTT时间</p>
</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_evs_state</td>
<td>显示EVS协议的内部状态</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_paused</td>
<td>由于流控而导致复制暂停的时长，从上一个FLUSH STATUS命令开始记时</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_paused_ns</td>
<td>总计处于暂体状态的纳秒数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_recv</td>
<td>从最近一次状态查询以来，FC_PAUSE事件的接收总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_flow_control_sent</td>
<td>从最近一次状态查询以来，FC_PAUSE事件的发送总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_gcomm_uuid</td>
<td>节点的UUID（用于组通信）</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_incoming_addresses</td>
<td>在cluster component中的incoming服务器地址列表</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_last_committed</td>
<td>最近一次事务的seqno</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_bf_aborts</strong></td>
<td>被slave transcation中断的本地事务的总数，中断由于认证失败（存在冲突）导致</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_cached_downto</td>
<td>writeset缓存（GCache）中最小的seqno值</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_cert_failures</strong></td>
<td>在认证测试中失败（存在冲突）的本地事务总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_commits</td>
<td>在本节点上提交的本地事务总数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_index</td>
<td>集群中本节点的索引，索引zero-based</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_recv_queue</strong></td>
<td>当前接收队列长度，即等待被apply的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_recv_queue_avg</td>
<td>自最近一次状态查询以来，接收队列的平均长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_recv_queue_max</td>
<td>自最近一次FLUSH STATUS以来，接收队列的最大长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_recv_queue_min</td>
<td>自最近一次FLUSH STATUS以来，接收队列的最小长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_replays</td>
<td>由于asymmetric lock granularity导致replay的事务的总量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_send_queue</strong></td>
<td>当前发送队列的长度，即等待被发送的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_send_queue_avg</td>
<td>自最近一次状态查询以来，发送队列的平均长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_send_queue_max</td>
<td>自最近一次FLUSH STATUS以来，发送队列的最大长度</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_local_send_queue_min</td>
<td>自最近一次FLUSH STATUS以来，发送队列的最小长度</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_state</strong></td>
<td>内部Galera Cluster FSM state</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_state_comment</strong></td>
<td>
<p>上个字段的可读文本</p>
<p>如果节点是Primary Component的成员，则值可能是：</p>
<p style="padding-left: 30px;">Joining, Waiting on SST, Joined, Synced, Donor</p>
<p>如果节点不是PC成员，则值是：Initialized</p>
<p>节点状态转换示意图：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2021/01/galerafsm.png"><img class="size-full wp-image-35459 alignleft" src="https://blog.gmem.cc/wp-content/uploads/2021/01/galerafsm.png" alt="galerafsm" width="315" height="338" /></a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ol>
<li>节点启动，并连接到Primary Component</li>
<li>节点发起状态转移请求，开始缓存writeset</li>
<li>节点接收SST，获得了所有集群数据，开始apply writeset</li>
<li>节点赶上集群进度。现在它的slave queue是空的，开始启用FC，保持slave queue为空。节点设置wsrep_ready=1，它可以处理事务了</li>
<li>节点接收其它节点发来的SST请求，配合进行状态转移。缓存所有它无法apply的writeset（某些SST会阻塞donor）</li>
</ol>
</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_local_state_uuid</strong></td>
<td>节点的UUID状态，如果和wsrep_cluster_state_uuid相同则本节点保持了同步</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_open_connections</td>
<td>在wsrep provider中打开的连接数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_open_transactions</strong></td>
<td>
<p>在wsrep provider中已经注册的、本地运行中的事务的数量</p>
<p>这个变量提示了生成writeset的事务的数量，只读事务不在统计中</p>
</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_protocol_version</td>
<td>wsrep协议版本</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider_name</td>
<td>wsrep提供者名称</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider_vendor</td>
<td>wsrep提供者vendor</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider_version</td>
<td>wsrep提供者版本</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_ready</strong></td>
<td>wsrep提供者是否就绪</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_received</strong></td>
<td>从其它节点接收到的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_received_bytes</td>
<td>从其它节点接收到的writeset字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_data_bytes</td>
<td>复制的数据字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_keys</td>
<td>复制的键数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_keys_bytes</td>
<td>复制的键字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_repl_other_bytes</td>
<td>复制的其它bits数量</td>
</tr>
<tr>
<td class=" blog_h3"><strong>wsrep_replicated</strong></td>
<td>复制到其它节点的writeset数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_replicated_bytes</td>
<td>复制到其它节点的writeset字节数</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_rollbacker_thread_count</td>
<td>rollbacker线程数量</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_thread_count</td>
<td>wsrep线程（applier/rollbacker）总数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">系统变量</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">wsrep_auto_increment_control</td>
<td>
<p>如果设置为1（默认），自动根据集群规模调整auto_increment_increment/auto_increment_offset </p>
<p>集群规模变化时也会调整</p>
<p>避免由于auto_increment导致的复制冲突</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_causal_reads</strong></td>
<td>
<p>默认OFF。如果设置为ON，确保集群范围内的隔离级别：读取已提交</p>
<p>如果master比slave apply的速度快很多，意味着失去同步。该选项设置为<span style="background-color: #c0c0c0;">ON，则让slave在事件处理完毕之前，不去处理查询</span></p>
<p>设置为ON，会导致更大的读延迟</p>
<p>已经废弃：用 wsrep_sync_wait=1 代替</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_certification_rules</td>
<td>
<p>集群中使用的认证规则（certification rules ），可选值：</p>
<ol>
<li>strict，可能导致更多的认证失败</li>
<li>optimized，允许更多的并发，减少认证失败</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_certify_nonPK</td>
<td>默认ON。如果设置为ON，允许复制没有PK的表，但是可能导致未定义的行为</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_cluster_address</strong></td>
<td>
<p>节点启动时连接的地址，格式：</p>
<p style="padding-left: 30px;">&lt;schema&gt;://&lt;cluster_address&gt;[?option1=value1[&amp;option2=value2]]</p>
<p>示例：</p>
<p style="padding-left: 30px;">gcomm://192.168.0.1:1234?gmcast.listen_addr=0.0.0.0:2345</p>
<p>建议指定所有可能的节点地址：</p>
<p style="padding-left: 30px;">gcomm://&lt;node1 or ip:port&gt;,&lt;node2 or ip2:port&gt;,&lt;node3 or ip3:port&gt;</p>
<p><span style="background-color: #c0c0c0;">如果指定空白（gcomm://）则节点会发起一个新的集群</span></p>
<p>option指的是wsrep provider选项。这些选项的默认值来自 wsrep_provider_options ，<span style="background-color: #c0c0c0;">这里可以覆盖之</span></p>
<p>某些配置下，该变量可以在运行时修改，这会导致节点断开现有连接，并连接到其它集群</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_cluster_name</strong></td>
<td>集群的名称，节点<span style="background-color: #c0c0c0;">不能连接到和不同名称的集群</span></td>
</tr>
<tr>
<td class="blog_h3">wsrep_convert_LOCK_to_trx</td>
<td>
<p>将LOCK/UNLOCK TABLES语句转换为BEGIN/COMMIT，用于适配老的应用程序（免修改）</p>
<p>谨慎使用，可能导致巨大的writeset</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_data_home_dir </strong></td>
<td>wsrep provider存储内部文件的目录 </td>
</tr>
<tr>
<td class="blog_h3">wsrep_dbug_option </td>
<td>向wsrep provider传递调试选项</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_debug</strong> </td>
<td>
<p>wsrep调试日志级别。NONE（默认）, SERVER, TRANSACTION, STREAMING, CLIENT</p>
<p>开启日志，可以记录冲突的事务</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_desync</td>
<td>
<p>默认OFF。当节点接收太多writeset，无法即使消化（apply），则事务被存放到接收队列中。如果接收队列积蓄的writeset太多（由于wsrep选项gcs.fc_limit指定），则节点enage流控</p>
<p>如果将此字段设置为ON，则针对desync的节点禁用流控（flow control）。desync的节点会继续慢慢悠悠的处理队列中的writeset，这可能导致积累越来越多</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_dirty_reads</strong></td>
<td>可session，默认OFF。默认情况下，如果节点没有和群组（集群）同步（状态wsrep_ready=OFF），则它会拒绝除了SET/SHOW之外的任何查询请求。如果将此选项设置为ON，那么不会导致数据变化的查询请求可以被节点所接受</td>
</tr>
<tr>
<td class="blog_h3">wsrep_drupal_282555_workaround</td>
<td>默认OFF。如果启用，这启用针对缺陷<a href="https://www.drupal.org/node/282555">Drupal/MySQL/InnoDB bug #282555</a>的workaround。该缺陷可能导致向AUTO_INCREMENT列插入DEFAULT值时产生重复KEY </td>
</tr>
<tr>
<td class="blog_h3">wsrep_forced_binlog_format</td>
<td>
<p>覆盖任何session级别binlog格式设置</p>
<p>取值：STATEMENT, ROW, MIXED, NONE</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_gtid_domain_id</td>
<td>用于wsrep GTID（Global Transaction ID）模式的GTID domain ID</td>
</tr>
<tr>
<td class="blog_h3">wsrep_gtid_mode</td>
<td>
<p>wsrep GTID mode尝试在所有节点上，保持writeset的GTID的一致性。在加入新节点时，通过SST将GTID状态复制到节点</p>
<p>如果打算使用MariaDB Replication，启用wsrep GTID mode可能有用</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_gtid_seq_no</td>
<td>仅session，设置WSREP GTID seqno</td>
</tr>
<tr>
<td class="blog_h3">wsrep_ignore_apply_errors</td>
<td>
<p>apply错误应该被忽略，还是报告给provider：</p>
<p style="padding-left: 30px;">0 不跳过任何错误<br />1 忽略某些DDL错误（DROP DATABASE, DROP TABLE, DROP INDEX, ALTER TABLE）<br />2 忽略DML错误（仅仅忽略DELETE错误）<br />4 忽略所有DDL错误</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_log_conflicts</td>
<td>默认OFF。如果设置为ON，冲突的MDL以及InnoDB所的详细信息被记录 </td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_max_ws_rows</strong></td>
<td>
<p>一个writeset允许的最大行数</p>
<p>为了保证向后兼容，默认值0，表示允许任意大小 </p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_max_ws_size</strong></td>
<td>一个writeset允许的最大bytes</td>
</tr>
<tr>
<td class="blog_h3">wsrep_mysql_replication_bundle</td>
<td>
<p>可以被分组到一起处理的复制事件的数量。一个试验性的实现允许辅助一个slave节点处理commit时延</p>
<p>默认0，不支持分组</p>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_node_address</strong></td>
<td>节点的网络地址。默认0.0.0.0:4567</td>
</tr>
<tr>
<td class="blog_h3">wsrep_node_incoming_address</td>
<td>监听客户端连接的地址</td>
</tr>
<tr>
<td class="blog_h3">wsrep_node_name</td>
<td>节点的名称，可以用在wsrep_sst_donor中</td>
</tr>
<tr>
<td class="blog_h3">wsrep_notify_cmd</td>
<td>每当节点状态或集群成员关系发生变化时调用的命令 </td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_on</strong></td>
<td>
<p>可session，默认OFF。是否启用wsrep复制，如果设置为OFF则无法加载wsrep provider，不能加入集群</p>
<p>如果在session级别设置为OFF，则该会话的操作不会被复制，但是其它会话、applier线程仍然正常工作</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_OSU_method</td>
<td>
<p>指定在线Schema更新方法（schema upgrade method） ：</p>
<ol>
<li>TOI（默认）：Total Order Isolation。在每个节点上，DDL以相同的顺序执行，保证数据一致性。数据库被影响的部分在全集群范围锁定</li>
<li>RSU：Rolling Schema Upgrade。DDL仅仅在本地节点处理，用户需要手工在其它节点处理</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_patch_version</td>
<td>wsrep补丁版本</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_provider</strong></td>
<td>
<p>wsrep provider库的位置，可能位置：</p>
<ol>
<li>Ubuntu：/usr/lib/libgalera_smm.so</li>
<li>CentOS：/usr/lib64/libgalera_smm.so</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_provider_options</td>
<td>传递给wsrep provider的、分号（;）分隔的选项列表 </td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_recover</strong></td>
<td>如果在服务器启动时设置为ON，则节点会尝试恢复最近一次apply的writeset的seqno，并让服务器退出</td>
</tr>
<tr>
<td class="blog_h3">wsrep_reject_queries</td>
<td>
<p>用于进入维护状态，节点会继续apply writeset，但是客户端查询会导致Error 1047: Unknown command错误。取值：</p>
<ol>
<li>NONE（默认）：查询正常处理</li>
<li>ALL：所有客户端查询请求被拒绝，已经存在的客户端连接继续维护</li>
<li>ALL_KILL：所有查询被拒绝，同时关闭所有连接</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_replicate_myisam</strong></td>
<td>是否对MyISAM表的DML可以被复制。试验性功能</td>
</tr>
<tr>
<td class="blog_h3">wsrep_restart_slave</td>
<td>默认OFF。如果设置为ON，则节点重新加入集群后replication slave自动重启</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_retry_autocommit</strong></td>
<td>
<p>在报告客户端以错误之前，因为集群范围冲突而导致无法提交的auto-commit请求重试的次数</p>
<p>默认1。设置为0则不会重试</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_slave_FK_checks</td>
<td>默认ON。如果设置为ON，则applier slave thread会执行外键约束检查</td>
</tr>
<tr>
<td class="blog_h3">wsrep_slave_UK_checks</td>
<td>默认OFF。如果设置为ON，则applier slave thread会执行辅助索引的唯一性检查</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_slave_threads</strong></td>
<td>默认1。用于并行apply writeset的slave thread的总数。这些线程能够自行决定writeset 能否被安全的并行apply，但是，如果你发现集群中频繁出现不一致性问题，考虑设置为1</td>
</tr>
<tr>
<td class="blog_h3">wsrep_sr_store </td>
<td>streaming replication fragments的存储方式</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sst_auth</strong></td>
<td>SST时使用的身份验证信息</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sst_donor</strong></td>
<td>SST的偏好的源节点 </td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_donor_rejects_queries</td>
<td>默认OFF。如果设置为ON则供给（donor）节点在SST期间拒绝查询请求并返回UNKNOWN COMMAND</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sst_method</strong></td>
<td>默认rsync。SST方法 </td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_receive_address</td>
<td>供给节点（donor）连接到此地址，来发送状态转移更新</td>
</tr>
<tr>
<td class="blog_h3">wsrep_strict_ddl</td>
<td>默认OFF。如果设置为ON，则禁止对不支持Galera Replication的表（即非InnoDB）的DDL</td>
</tr>
<tr>
<td class="blog_h3"><strong>wsrep_sync_wait</strong></td>
<td>
<p>在该参数指定的操作类型执行之前，执行causality checks 以确保操作在完全同步的前提下执行</p>
<p>在causality check期间，所有查询请求被阻塞，以保证节点应用了所有到发起check那个时间点的所有更新。一旦check结束，操作开始在节点上执行</p>
<p>操作类型：</p>
<p style="padding-left: 30px;">0 - 禁用等待同步（默认）<br />1 - READ， 即SELECT、BEGIN/START TRANSACTION / SHOW。等价于wsrep_causal_read=1<br />2 - UPDATE and DELETE<br />3 - READ, UPDATE and DELETE;<br />4 - INSERT and REPLACE;<br />5 - READ, INSERT and REPLACE;<br />6 - UPDATE, DELETE, INSERT and REPLACE;<br />7 - READ, UPDATE, DELETE, INSERT and REPLACE; <br />8 - SHOW <br />9 - READ and SHOW<br />10 - UPDATE, DELETE and SHOW<br />11 - READ, UPDATE, DELETE and SHOW<br />12 - INSERT, REPLACE and SHOW<br />13 - READ, INSERT, REPLACE and SHOW<br />14 - UPDATE, DELETE, INSERT, REPLACE and SHOW <br />15 - READ, UPDATE, DELETE, INSERT, REPLACE and SHOW</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_trx_fragment_size </td>
<td>
<p>对于流式复制（streaming replication），事务分片（ transaction fragments）的大小</p>
<p>单位由wsrep_trx_fragment_unit指定</p>
</td>
</tr>
<tr>
<td class="blog_h3">wsrep_trx_fragment_unit)</td>
<td>
<p>事务分片的单位：</p>
<p style="padding-left: 30px;">bytes  事务的binlog事件缓冲的字节数<br />rows  事务影响的行数<br />statements  多语句事务中执行的SQL数量</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装配置</span></div>
<div class="blog_h2"><span class="graybg">普通安装</span></div>
<p>为了安装MariaDB Galera Cluster，需要以下包：</p>
<ol>
<li>支持Galera的MariDB</li>
<li>Galera wsrep提供者库</li>
</ol>
<p>为了支持基于SST的备份，可能需要额外的包。</p>
<div class="blog_h3"><span class="graybg">安装MariaDB</span></div>
<p>添加源配置：</p>
<pre class="crayon-plain-tag"># MariaDB 10.5 CentOS repository list - created 2021-01-15 07:44 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos8-amd64
module_hotfixes=1
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1</pre>
<p>执行安装：</p>
<pre class="crayon-plain-tag">dnf install MariaDB-server
systemctl start mariadb</pre>
<div class="blog_h3"><span class="graybg">安装Galera wsrep提供者</span></div>
<pre class="crayon-plain-tag">dnf install galera4 </pre>
<div class="blog_h3"><span class="graybg">启动新集群 </span></div>
<p>集群的第一个节点需要以下面的命令行选项来自举（bootstrap）：</p>
<pre class="crayon-plain-tag">mysqld --wsrep-new-cluster</pre>
<p>这个选项告诉MariaDB，不需要连接到既有集群。对于一个现有的节点，以该选项重启，会导致创建新的识别集群身份的UUID，并且它不会再连接到老集群。</p>
<p>对于使用Systemd的系统，应该使用下面的命令创建新集群：</p>
<pre class="crayon-plain-tag">galera_new_cluster</pre>
<div class="blog_h3"><span class="graybg">添加新节点 </span></div>
<p>要添加新节点，需要通过wsrep_cluster_address选项指定集群地址，如果第一个节点的地址是192.168.0.1，则可以用下面的配置加入新节点：</p>
<pre class="crayon-plain-tag">[mariadb]
; 支持IP地址或DNS名称
wsrep_cluster_address=gcomm://192.168.0.1</pre>
<p>使用上述配置，新节点将会连接到一个既有集群节点，并随后发现所有的节点。<span style="background-color: #c0c0c0;">最好在此配置中，指定所有集群成员的地址</span>，免得因为单点的故障 导致无法加入集群。</p>
<p>当所有成员加入到集群，状态不一致的集群可能通过IST/SST来达到一致性。</p>
<div class="blog_h3"><span class="graybg">重启集群</span></div>
<p>当集群所有节点关闭后，需要依次重启节点。</p>
<p>如果第一个节点以常规方式启动， 则它会尝试连接到wsrep_cluster_address中的其它节点，这必然失败。因此，集群完全关闭之后，需要对第一个节点进行bootstrap操作。</p>
<p><span style="background-color: #c0c0c0;">第一个节点，必须是数据最新的节点</span>。如果节点检测到它可能<span style="background-color: #c0c0c0;">不是数据最新（如果它不是最后一个关闭的，或者它是崩掉的）</span>的节点，它会拒绝bootstrap。</p>
<p>查看所有节点的grastate.dat文件，其中seqno字段的值最大的，就是数据最新的节点。如果节点是崩掉的且seqno=-1，则可以使用下面的命令恢复seqno：</p>
<pre class="crayon-plain-tag">mysqld --wsrep_recover</pre>
<p>确定数据最新节点后，可以修改它的数据目录下的grastate.dat文件，设置：</p>
<pre class="crayon-plain-tag">safe_to_bootstrap=1</pre>
<p>使用Systemd的情况下，节点的seqno可以通过命令：<pre class="crayon-plain-tag">galera_recovery</pre>恢复。 galera_recovery脚本调用mysqld时，错误日志会写入到/tmp/wsrep_recovery.XXXXXX。错误日志会有类似下面的内容：</p>
<pre class="crayon-plain-tag">#                                 集群group id                         seqno
[Note] WSREP: Recovered position: 7bff636d-50c7-11eb-81c8-6f8dd75e7fb4:1202</pre>
<p>启用Galera时，MariaDB的Systemd服务会在启动数据库之前自动运行galera_recovery。</p>
<div class="blog_h3"><span class="graybg">状态监控</span></div>
<p>和Galera相关的状态变量，都放在wsrep_前缀下：</p>
<pre class="crayon-plain-tag">SHOW GLOBAL STATUS LIKE 'wsrep_%';</pre>
<div class="blog_h2"><span class="graybg">容器化 </span></div>
<p>参考<a href="#k8s">在K8S中运行</a>一节。</p>
<div class="blog_h2"><span class="graybg">主要配置项说明</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">配置</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2"><strong><em>基础配置</em></strong></td>
</tr>
<tr>
<td class=" blog_h3">wsrep_provider</td>
<td>wsrep提供者库的位置</td>
</tr>
<tr>
<td class="blog_h3">wsrep_cluster_name</td>
<td>Galera集群名称</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_cluster_address</td>
<td>
<p>集群地址配置</p>
<p>格式：&lt;schema&gt;://&lt;cluster_address&gt;[?option1=value1[&amp;option2=value2]]</p>
</td>
</tr>
<tr>
<td class=" blog_h3">binlog_format</td>
<td>二进制日志格式，默认ROW</td>
</tr>
<tr>
<td class=" blog_h3">default_storage_engine</td>
<td>要设置为InnoDB</td>
</tr>
<tr>
<td class=" blog_h3">innodb_autoinc_lock_mode</td>
<td>生成AUTO_INCREMENT值时的锁模式，要设置为2（interleaved lock mode）</td>
</tr>
<tr>
<td class=" blog_h3">innodb_doublewrite</td>
<td>
<p>保持默认值1，也就是说InnoDB在写入数据文件之前，首先将数据存储到一个双重写入缓冲（InnoDB Doublewrite Buffer），以提升容错能力</p>
<p>双重写入缓冲用于从不完全写入的page中恢复，如果InnoDB在将页写入磁盘时断电，会出现不完全写入的问题</p>
</td>
</tr>
<tr>
<td class=" blog_h3">query_cache_size</td>
<td>对于5.5.40-  10.0.14- 以及10.1.2，需要设置为0</td>
</tr>
<tr>
<td class=" blog_h3">wsrep_on</td>
<td>设置为ON，启用wsrep复制，10.1.2+</td>
</tr>
<tr>
<td colspan="2"><em><strong>性能配置</strong></em></td>
</tr>
<tr>
<td class="blog_h3">innodb_flush_log_at_trx_commit</td>
<td>可以设置为0，尽管在标准的MariaDB中设置为0会有丢数据的风险，但是在Galera中要安全，因为<span style="background-color: #c0c0c0;">数据的不一致性总是可以从另外一个节点恢复</span></td>
</tr>
<tr>
<td colspan="2"><strong><em>复制行为控制</em></strong></td>
</tr>
<tr>
<td class="blog_h3">log_slave_updates</td>
<td>
<p>设置为ON，则节点将接收到的writeset写入到binlog，默认不写入</p>
<p>如果你希望节点作为传统MariaDB Replication的Master，开启此选项</p>
</td>
</tr>
<tr>
<td class="blog_h3">binlog_do_db</td>
<td>影响到此选项列出的数据库的DML操作，被写入binlog</td>
</tr>
<tr>
<td class="blog_h3">binlog_ignore_db</td>
<td>影响到此选项列出的数据库的DML操作，不写入binlog</td>
</tr>
<tr>
<td class="blog_h3">replicate_wild_do_table</td>
<td>只有该选项匹配的表的DML才被应用到此slave</td>
</tr>
<tr>
<td class="blog_h3">replicate_wild_ignore_table</td>
<td>该选项匹配的表的DML被当前slave丢弃</td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_auth</td>
<td>SST的 username:password配置，mysqldump SST需要</td>
</tr>
<tr>
<td colspan="2"><strong><em>端口配置</em></strong></td>
</tr>
<tr>
<td class="blog_h3">wsrep_node_address</td>
<td>wsrep复制使用的地址端口，默认0.0.0.04567</td>
</tr>
<tr>
<td class="blog_h3">ist.recv_addr</td>
<td>IST端口，默认4568</td>
</tr>
<tr>
<td class="blog_h3">wsrep_sst_receive_address</td>
<td>SST端口，默认4444。对于任何除mysqldump之外的状态快照转移方式，都使用此端口</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">状态转移</span></div>
<p>当<span style="background-color: #c0c0c0;">新加入一个节点</span>到Galera集群时，此节点需要从集群节点获取数据，这个复制过程被称作状态转移（State Transfer），Galera支持两种状态传输方式：</p>
<ol>
<li>State Snapshot Transfers (SST) 传输一个节点的完整状态（即所有数据），全量复制</li>
<li>Incremental State Transfers (IST) 只传输缺失的数据，增量复制</li>
</ol>
<div class="blog_h1"><span class="graybg">IST</span></div>
<p>使用 IST 的方式时，集群会判断加入集群的节点所缺失的数据，而不必传输完整的数据集。使用 IST 必须满足特定的条件：</p>
<ol>
<li>加入节点的 state UUID 必须和此集群的相同</li>
<li>加入节点所缺失的数据都在 donor 节点的 writeset cache 中存在</li>
</ol>
<p>如果上述条件满足，<span style="background-color: #c0c0c0;">供给节点会发送加入节点上缺失的事务，按照顺序replay它们，直到加入节点赶上集群进度</span>。</p>
<p><span style="background-color: #c0c0c0;">IST不会阻塞供给节点</span></p>
<p>使用 IST 最重要的参数是 donor 节点的 gcache.size 大小，这个参数表示分配多少空间用于缓存 writeset。缓存的空间越多，能够使用 IST 的几率就越大</p>
<div class="blog_h1"><span class="graybg">SST</span></div>
<p>所谓状态快照转移（SST），就是指集群通过将某个up-to-date节点的数据集，完整的拷贝到另外一个节点的过程/操作。</p>
<div class="blog_h2"><span class="graybg">SST类型</span></div>
<p>从概念上来看，存在两种状态转移方式。</p>
<div class="blog_h3"><span class="graybg">逻辑</span></div>
<p>即mysqldump SST，该SST方法，实际上是调用mysqldump，从源节点获取数据库的逻辑dump文件。</p>
<p>要使用mysqldump SST，新加入的节点必须完全初始化，并且准备好接受连接。</p>
<p>该SST方法会阻塞源节点，在状态转移期间，无法对源节点进行修改。该SST方法是最慢的一种，在高负载集群中可能导致问题。</p>
<div class="blog_h3"><span class="graybg">物理</span></div>
<p>这类SST方法将数据文件从源节点拷贝到新加入的节点。节点需要在状态转移之后再完成初始化。mariabackup SST等属于这一类别。</p>
<p>这类方法比mysqldump SST快很多，但是具有一些限制条件：</p>
<ol>
<li>只能在服务器启动时使用</li>
<li>新节点必须和源节点配置很相似，例如 innodb_file_per_table取值必须相同</li>
</ol>
<p>这类方法中的某些，例如mariabackup SST，是非阻塞的，也就是说，在状态转移期间源节点可以继续处理请求。</p>
<div class="blog_h2"><span class="graybg">选择SST方法</span></div>
<p>通过设置全局系统变量，可以修改使用的SST方法：</p>
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_method='mariabackup';</pre>
<p>SST方法也可以在配置文件中修改：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_method = mariabackup</pre>
<p>注意：<span style="background-color: #c0c0c0;">源节点和新节点必须使用相同的SST方法</span>。 建议所有节点使用一致的SST方法。</p>
<div class="blog_h2"><span class="graybg"><a id="sst-methods"></a>内置SST方法</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">SST方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">mariabackup</td>
<td>
<p>利用工具mariabackup执行SST，这是两种非阻塞的SST方法之一</p>
<p>如果希望在SST期间，能够在源节点上执行查询，推荐使用该方法</p>
<p>该方法需要在节点上安装socat</p>
</td>
</tr>
<tr>
<td class="blog_h3">rsync</td>
<td>
<p>默认SST方法，使用rsync来创建源节点的数据快照。在执行SST期间，源节点使用使用<span style="background-color: #c0c0c0;">读锁锁定</span></p>
<p>这是<span style="background-color: #c0c0c0;">速度最快的SST方法，特别是对于大数据集</span>（因为仅仅拷贝文件）</p>
<p>如果不希望SST期间，源节点上可以执行查询，推荐使用该方法</p>
</td>
</tr>
<tr>
<td class="blog_h3">mysqldump</td>
<td>在源节点上执行mysqldump，然后通过管道发送给连接新节点的mysql客户端</td>
</tr>
<tr>
<td class="blog_h3">xtrabackup-v2</td>
<td>
<p>使用Percona XtraBackup 执行SST，这是两种非阻塞的SST方法之一</p>
<p>需要安装额外的软件</p>
</td>
</tr>
<tr>
<td class="blog_h3">xtrabackup</td>
<td>已经被xtrabackup-v2代替</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">身份验证</span></div>
<p>除了rsync SST之外，都需要配置基于用户名:密码的身份验证：</p>
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_auth = 'mariabackup:password';</pre>
<p>或者修改配置文件：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_auth = mariabackup:password</pre>
<p>某些身份认证插件，例如unix_socket或gssapi，不需要密码，这种情况下只需要指定用户名： </p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_auth = mariabackup:</pre>
<div class="blog_h2"><span class="graybg">最小集群规模</span></div>
<p>为了防止脑裂的出现，推荐最小集群规模为3。</p>
<p>另外一个需要3节点的原因是，当SST阻塞了源节点时，仍然可以有一个节点能响应查询。</p>
<div class="blog_h2"><span class="graybg">SST失败</span></div>
<p>如果状态转移失败，新节点一般是不可用的。</p>
<p>如果使用mysqldump SST，可能还需要手工恢复某些MariaDB管理表。</p>
<div class="blog_h2"><span class="graybg">mariabackup SST</span></div>
<p>本节介绍如何使用这种SST方法。</p>
<div class="blog_h3"><span class="graybg">设置SST方法</span></div>
<p>修改配置文件：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_method = mariabackup </pre>
<p>或者通过命令行设置：</p>
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_method='mariabackup';</pre>
<div class="blog_h3"><span class="graybg">配置身份验证</span></div>
<p>该SST方法需要在所有供给（donor）节点上进行<span style="background-color: #c0c0c0;">本地身份验证</span>。</p>
<p>首先需要创建专用的数据库账号： </p>
<pre class="crayon-plain-tag">CREATE USER 'mariabackup'@'localhost' IDENTIFIED BY 'mypassword';
GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'mariabackup'@'localhost';</pre>
<p>然后修改配置文件或执行命令行设置：</p>
<pre class="crayon-plain-tag">[mariadb]
wsrep_sst_auth = mariabackup:mypassword</pre><br />
<pre class="crayon-plain-tag">SET GLOBAL wsrep_sst_auth = 'mariabackup:mypassword';</pre>
<div class="blog_h3"><span class="graybg">免密码认证</span></div>
<p>可以使用unix_socket身份认证插件，这样就不需要指定密码： </p>
<pre class="crayon-plain-tag">CREATE USER 'mysql'@'localhost' IDENTIFIED VIA unix_socket;
GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'mysql'@'localhost';</pre>
<div class="blog_h3"><span class="graybg">选择供给节点 </span></div>
<p>当mariadbbackup在供给（donor）节点创建备份时，它需要在备份最后创建一个全局的锁：</p>
<ol>
<li>在10.3-，锁通过  <pre class="crayon-plain-tag">FLUSH TABLES WITH READ LOCK</pre>实现</li>
<li>在10.4+，锁通过 <pre class="crayon-plain-tag">BACKUP STAGE BLOCK_COMMIT</pre>实现</li>
</ol>
<p>如果集群中某个节点正作为“主”节点来使用 —— 也就是说应用程序连接到它来写入数据，那么该节点不应该用作SST源，因为全局锁会干扰应用程序的写操作。</p>
<p>可以通过下面的系统变量，来设置偏好使用哪些节点作为源：</p>
<pre class="crayon-plain-tag">[mariadb]
; 如果集群由 node1, node2, node3, node4, node5构成。node1作为主使用
; 那么node2可以这样配置：
wsrep_sst_donor=node3,node4,node5,
; 结尾的逗号表示，如果偏好的节点不可用，允许选择任何节点作为源</pre>
<div class="blog_h3"><span class="graybg">配置日志</span></div>
<p>mariabackup SST具有独立的日志，不和数据库日志在一起：</p>
<pre class="crayon-plain-tag">[sst]
sst-log-archive=1
sst-log-archive-dir=/var/log/mysql/sst/
; 记录到syslog中
sst-syslog=1</pre>
<div class="blog_h1"><span class="graybg">负载均衡</span></div>
<p><a href="https://fromdual.com/galera-load-balancer-documentation">Galera Load Balancer</a>（GLB）是一个简单的、特地为Galera设计负载均衡器。该LB支持多种负载均衡策略。</p>
<p>节点可以被<span style="background-color: #c0c0c0;">设置不同的权重，优先使用高权重的节点</span>，根据选择的策略，<span style="background-color: #c0c0c0;">低权重节点可能被忽略</span>（除非高权重节点挂掉）。</p>
<p>一个轻量级的守护进程glbd负责接收客户端连接，并执行LB算法，重定向客户端请求到适当的节点。</p>
<div class="blog_h2"><span class="graybg">特性</span></div>
<ol>
<li>可以在运行时配置后端节点列表</li>
<li>支持drain节点，也就是不分配新的连接给它，但是不会kill服务器 —— 让它优雅关闭</li>
<li>使用epool提升性能</li>
<li>支持多线程，可以利用多核心CPU</li>
<li>支持可选的watchdog来监控后端并调整路由表</li>
</ol>
<div class="blog_h2"><span class="graybg">策略</span></div>
<p>GLB支持以下负载均衡策略：</p>
<ol>
<li>least connected： 最少连接，新连接并分发给连接最少的节点，需要考虑节点权重</li>
<li>round-robin：依次循环调度给每个节点</li>
<li>single：所有连接调度给最高权重的节点，除非此节点挂了，或者引入一个新的、更高权重的节点</li>
<li>random：随机调度</li>
<li>source tracking：来自同一源地址的连接，总是被调度给同一节点</li>
</ol>
<div class="blog_h1"><span class="graybg">仲裁节点</span></div>
<p>推荐的部署模式是3节点以上，奇数节点。</p>
<p>如果部署第3个节点（例如只有两个数据中心）的成本过高，可以考虑引入仲裁节点（Galera Arbitrator）以防止脑裂。仲裁节点：</p>
<ol>
<li>参与投票，来决定Primary Component。当两节点集群出现网络分区时，能够看到仲裁节点的那个构成Primary Component</li>
<li>不参与复制。<span style="background-color: #c0c0c0;">尽管仲裁节点不参与复制，但是它会接收所有数据（但是不存储），这意味着你不能将其放置在网络连接差的地方，否则可能导致很差的集群性能</span></li>
</ol>
<p><span style="background-color: #c0c0c0;">仲裁节点还有另外一个用途——用于备份</span>，它可以请求数据库状态的一致性快照。</p>
<div class="blog_h2"><span class="graybg">启动仲裁节点</span></div>
<p>由独立的守护进程garbd来运行仲裁节点：</p>
<pre class="crayon-plain-tag">garbd --group=example_cluster \
     --address="gcomm://192.168.1.1,192.168.1.2,192.168.1.3" \
     --option="socket.ssl_key=/etc/ssl/galera/server-key.pem;socket.ssl_cert=/etc/ssl/galera/server-cert.pem;socket.ssl_ca=/etc/ssl/galera/ca-cert.pem;socket.ssl_cipher=AES128-SHA256""</pre>
<p>配置信息也可以放在独立文件中：</p>
<pre class="crayon-plain-tag">group = example_cluster
address = gcomm://192.168.1.1,192.168.1.2,192.168.1.3</pre><br />
<pre class="crayon-plain-tag">garbd --cfg /path/to/arbitrator.config</pre>
<div class="blog_h1"><span class="graybg">备份和恢复</span></div>
<p>由于Galera集群的复制特性，执行备份时我们只需要在一个节点上进行。而在恢复时，也仅仅需要在一个节点上执行，剩下节点的同步，可以依靠状态转移自动完成。</p>
<div class="blog_h2"><span class="graybg">备份节点</span></div>
<p>任何时候，都可以针对某个Galera节点，使用常规的MariaDB备份工具进行备份。</p>
<p>上文中的<a href="sst-methods">内置SST方法</a>，对应了各种备份工具，混合使用多种备份方法，能够提高额外的数据安全性。这里挑选几个工具说明用法：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 120px; text-align: center;">备份工具</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>mysqldump</td>
<td>
<p>逻辑备份工具，也就是说它是面向SQL的，适合在不同版本的数据库之间进行数据迁移</p>
<p>备份时可以使用<pre class="crayon-plain-tag">--single-transaction</pre>选项，这样可以得到InnoDB表的一致性备份</p>
<p>mysqldump实际上使用SELECT * FROM table的方式来加载数据，这样会有个问题，数据量大的时候，会导致InnoDB缓冲池反复刷新，导致节点性能严重下降，进而导致整个Galera集群性能下降 —— Galera集群<span style="background-color: #c0c0c0;">性能取决于最慢的那个节点</span>。如果设置<pre class="crayon-plain-tag">wsrep_desync=OFF</pre>，让备份中的节点临时的不再同步，则集群其他节点的性能不会受到影响</p>
<pre class="crayon-plain-tag"># 备份所有数据库
mysqldump -u root -p pswd –all-databases &gt; data.sql
# 仅仅备份gmem数据库的表结构
mysqldump -u root -p pswd –no-data gmem &gt; schema.sql


# 恢复数据库，可能需要先清空
mysql&gt; drop database gmem;

mysql -u root -p pswd &lt;  schema.sql</pre>
</td>
</tr>
<tr>
<td>xtrabackup</td>
<td>
<p>物理备份工具，针对数据块，支持全量备份、增量备份。该工具来自Percona，包含一个C语言编写的xtrabackup、Perl语言编写的innobackupex，支持备份InnoDB、XtraDB、MyIASM等引擎的表
<p>在备份期间，<span style="background-color: #c0c0c0;">xtrabackup不会锁定（热备份）数据库。对于100G+的大数据库，它的恢复性能要比mysqldump好的多</span></p>
<p>基于xtrabackup的备份/恢复流程包括三个步骤：备份、准备、恢复</p>
<p>使用--backup子命令，可以进行备份。</p>
<pre class="crayon-plain-tag">#                   数据目录                  备份存放目录
xtrabackup --backup --datadir=/var/lib/mysql/ --target-dir=/data/backups/mysql/</pre>
<p>命令将会启动一个文件拷贝线程，拷贝ibdata文件，一个日志扫描线程，复制事务日志：</p>
<pre class="crayon-plain-tag">xtrabackup: Transaction log of lsn (&lt;SLN&gt;) to (&lt;LSN&gt;) was copied.</pre>
<p>备份完毕后，备份存放目录下，会出现类似下面的文件：</p>
<pre class="crayon-plain-tag"># 假设使用innodb_file_per_table选项
/data/backups/mysql/ibdata1
/data/backups/mysql/test
/data/backups/mysql/test/tbl1.ibd
/data/backups/mysql/xtrabackup_checkpoints
/data/backups/mysql/xtrabackup_logfile</pre>
<p>--backup子命令得到的备份，需要进行--prepare操作后，才能实现针对某个特定时间点的、数据文件的一致性。这是由于，不同数据文件是在不同时间点拷贝的，在这个拷贝过程中，先前的数据文件可能又存在新的修改。使用这些数据文件去启动数据库，InnoDB可能会检测到数据损坏并拒绝启动</p>
<p>--prepare子命令调用方式如下：</p>
<pre class="crayon-plain-tag">xtrabackup --prepare --target-dir=/data/backups/mysql/

# 结束时会打印
101107 16:40:15  InnoDB: Shutdown completed; log sequence number &lt;LSN&gt;</pre>
<p>此时的备份是满足数据一致性，可以用于恢复。直接拷贝数据文件即可：</p>
<pre class="crayon-plain-tag">rsync -avrP /data/backup/ /var/lib/mysql/
chown -R mysql:mysql /var/lib/mysql</pre>
<p>注意：xtrabackup仅仅备份InnoDB数据文件，你需要额外备份MySQL系统数据库、MyISAM数据、表定义文件（.frm）等。或者使用innobackupex让这一切都自动化：</p>
<pre class="crayon-plain-tag"># 备份      数据库帐号密码                      备份根目录            不创建基于时间戳的子目录
innobackupex --user=DBUSER --password=DBUSERPASS /path/to/BACKUP-DIR/ --no-timestamp
#            指定MySQL配置文件
innobackupex --defaults-file=/tmp/other-my.cnf --user=DBUSER --password=DBUSERPASS /path/to/BACKUP-DIR/

# 准备                   允许使用多少内存
innobackupex --apply-log --use-memory=100M  /path/to/BACKUP-DIR

# 恢复
innobackupex --copy-back /path/to/BACKUP-DIR</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">备份集群</span></div>
<p><span style="background-color: #ffffff;">由于</span><span style="background-color: #c0c0c0;">所有Galera节点具有相同的数据</span>，因此你可以使用常规的MariaDB备份工具进行备份，但是这种方式存在缺点 —— 缺少全局事务ID信息（GTID）。使用这种备份来恢复，不能让节点进入良好定义的状态。此外，某些备份工具会阻塞节点，这也需要注意。
<p>因此，理想的备份方式是发起一次SST：</p>
<ol>
<li>节点会在一个良好定义的点发起备份</li>
<li>节点会将GTID关联到备份</li>
<li>备份期间，节点从集群desync，避免影响性能 —— 备份过程可能阻塞节点</li>
<li>集群知道节点正在进行备份，因此不会将其选为donor</li>
</ol>
<p>我们需要利用仲裁节点来发起备份：</p>
<pre class="crayon-plain-tag"># garbd可以以函数方式执行，调用一个命令
#                                   如果在集群节点上发起，则4567已被占用，另外选择一个端口
garbd --address gcomm://192.168.1.2?gmcast.listen_addr=tcp://0.0.0.0:4444 \
#   机器名称               供给节点名称           在供给节点上执行的脚本的后缀
  --group example_cluster --donor galera-3 --sst backup_mysqldump</pre>
<p>你也可以通过配置文件指定各种选项：</p>
<pre class="crayon-plain-tag">; 填写集群的wsrep_cluster_name
group='example_cluster'
; 填写集群的wsrep_cluster_address
address="gcomm://172.31.30.39:4567,172.31.18.53:4567,172.31.26.106:4567"
; 填写集群的wsrep_provider_options。listen_addr添加garbd进程的监听端口
options="gmcast.listen_addr=tcp://0.0.0.0:4444"
; 指定哪个节点执行备份
donor="galera-3"
; 日志文件路径
log='/var/log/garbd.log'
; 备份节点上的备份脚本的后缀，对于下面的后缀，脚本名是wsrep_sst_backup_mysqldump
sst='backup_mysqldump'</pre>
<p>下面是一个脚本的示例，它使用了bash + mysqldump，但是任何脚本语言、备份工具都是可以的： </p>
<pre class="crayon-plain-tag">#!/bin/bash

# SET VARIABLES
db_user='admin_backup'
db_passwd='Rover123!'

backup_dir='/backup'
backup_sub_dir='temp'

today=`date +"%Y%m%d"`
# Dump文件路径
backup_today="galera-mysqldump-$today.sql"
# GTID文件路径
gtid_file="gtid-$today.dat"


# 加载此脚本，它提供GTID变量
. /usr/bin/wsrep_sst_common


# 备份配置文件
cp /etc/my.cnf $backup_dir/$backup_sub_dir/
cp /etc/garb.cnf $backup_dir/$backup_sub_dir/
# 备份GTID
echo "GTID: ${WSREP_SST_OPT_GTID}" &gt; $backup_dir/$backup_sub_dir/$gtid_file


# 进行数据库Dump
mysqldump --user="$db_user" --password="$db_passwd" \
          --flush-logs --all-databases \
          &gt; $backup_dir/$backup_sub_dir/$backup_today

# 打包
cd $backup_sub_dir
tar -czf $backup_dir/$backup_today.tgz * --transform "s,^,$backup_today/," </pre>
<div class="blog_h2"><span class="graybg">恢复集群</span></div>
<p>将备份恢复到一个新集群，或者既有集群，可以使用相同的步骤：</p>
<ol>
<li>选择一个节点，作为恢复节点</li>
<li>停止其它节点</li>
<li>关闭恢复节点的Galera Replication，进入Standalone模式：<br />
<pre class="crayon-plain-tag">SET GLOBAL wsrep_provider = 'none';

SHOW STATUS LIKE 'wsrep_connected';

+-----------------+-------+
 
| Variable_name   | Value |
 
+-----------------+-------+
 
| wsrep_connected | OFF   |
 
+-----------------+-------+</pre>
</li>
<li>调用备份工具，执行恢复</li>
<li>在恢复节点上重新开启Galera Replication</li>
<li>启动被停止的节点重新加入集群，并通过SST重新获得同步</li>
</ol>
<div class="blog_h1"><span class="graybg">wsrep提供者</span></div>
<div class="blog_h2"><span class="graybg">GCache</span></div>
<p>用于存放writeset的特殊缓存，叫作writeset cache或GCache。GCache支持三种存储：</p>
<ol>
<li>Permanent In-Memory Store：直接使用操作系统的内存，适合有很多空余内存的系统，默认情况是禁用的</li>
<li>Permanent Ring-Buffer File：在缓存初始化时就预先分配的一块磁盘空间，默认情况下，它的大小是 128Mb</li>
<li>On-Demand Page Store：根据需要在运行时动态分配内存映射页文件，默认情况下是 128Mb，但如果需要更大的 write-set，它可以动态的增长。当 page file 不再使用时，Galera Cluster 会删除这些文件，可以对 page file 的总大小进行限制</li>
</ol>
<p>Galera Cluster 的缓存分配算法会按上面的顺序来分配缓存空间，gcache.dir 参数可以指定缓存保存在磁盘的位置。</p>
<div class="blog_h2"><span class="graybg">复制流控</span></div>
<p>Galera允许一个节点根据需要来暂停/恢复复制，这叫作Flow Control。FC可以节点在apply事务方面，过于落后其它节点。</p>
<p>我们知道，所有节点收到writeset并进行认证之后，原始的接收请求的节点就可以在本地完成提交并应答客户端了。此时，其它节点不一定也apply/commit了事务（取决于配置）。尚未apply/commit的事务（writeset）会存放在接收队列中。</p>
<p>如果某个节点速度缓慢，那么它的接收队列会不断积压，到达一定程度之后，就会触发FC：</p>
<ol>
<li>暂停复制</li>
<li>处理接收队列的积压</li>
<li>当队列到达可管理的大小之后，再恢复复制</li>
</ol>
<div class="blog_h2"><span class="graybg">隔离级别</span></div>
<p>在Galera中，需要去分单节点内的、集群范围的事务隔离级别。单节点内的事务隔离级别不必多提，参考MariaDB提供的配置项即可。</p>
<p>集群范围的事务隔离，受到复制协议的影响，不同节点上发起的事务，可能不会完全等同的隔离。</p>
<div class="blog_h2"><span class="graybg">Component</span></div>
<p>集群中能互相保持心跳通信的节点的集合叫作Component，两个Component之间存在网络分区，无法相互通信。理想情况下，集群只有一个Component。</p>
<div class="blog_h3"><span class="graybg">Primary Component</span></div>
<p>大部分节点组成的Component叫作Primary Component —— <span style="background-color: #c0c0c0;">PC必须满足大多数原则，在三节点集群中，至少两个节点才能组成PC</span>。</p>
<p><span style="background-color: #c0c0c0;">只有位于PC中的节点，才能继续修改数据库状态</span>。</p>
<p>Galera Cluster 会周期性的检查每个节点的连接是否正常， evs.inactive_check_peroid 参数可以设置检查的周期。如果一个节点的失效时间超过了 evs.suspect_timeout 的值，那么节点将被标记为 suspect。如果 Primary Component 中的所有节点都将某个节点标记为 suspect ，那么此节点被移出集群。</p>
<p>如果一个节点的失效时间超过了 evs.inactive_timeout 的值，此节点将直接被移出集群而不需要协商。故障的节点将不可读不可写。</p>
<div class="blog_h3"><span class="graybg">节点权重</span></div>
<p>在计算是否构成Primary Component / Quorum时，节点是可以被加权的。节点的权重通过pc.weight配置。例如下面的配置：</p>
<pre class="crayon-plain-tag">node1: pc.weight = 2
node2: pc.weight = 1
node3: pc.weight = 0</pre>
<p>即使同时杀掉node2和node3，node1仍然构成PC </p>
<div class="blog_h2"><span class="graybg">提供者选项</span></div>
<p>提供者选项在系统变量 wsrep_provider_options 中配置，选项之间用;隔开。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class=" blog_h3">base_dir</td>
<td>数据目录</td>
</tr>
<tr>
<td class=" blog_h3">base_host</td>
<td>默认127.0.0.1。内部使用，不要设置</td>
</tr>
<tr>
<td class=" blog_h3">base_port</td>
<td>默认4567。内部使用，不要设置</td>
</tr>
<tr>
<td class=" blog_h3">cert.log_conflicts</td>
<td>默认no。是否记录认证失败的详细信息</td>
</tr>
<tr>
<td class=" blog_h3">cert.optimistic_pa</td>
<td>
<p>默认yes。控制在slave上并行apply的行为</p>
<p>如果设置，允许认证算法（ certification algorithm）所确定（determined）的全范围并行化（full range of parallelization ）</p>
<p>如果不设置，并行apply窗口（parallel applying window）不会超过在master上看到的窗口。并且，一旦它所看到的master上操作被commit之后，立即开始apply</p>
</td>
</tr>
<tr>
<td class=" blog_h3">debug</td>
<td>默认no。是否启用调试</td>
</tr>
<tr>
<td class=" blog_h3">evs.auto_evict</td>
<td>
<p><span style="background-color: #c0c0c0;">触发自动驱除</span>协议（Auto Eviction protocol）之前，允许一个<span style="background-color: #c0c0c0;">落后（delayed）节点的entry的数量</span></p>
<p>每当接收到某个节点的一个delayed response，就在delayed list中添加一个entry</p>
<p>默认0，表示禁用</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.debug_log_mask</td>
<td>
<p>开启EVS调试日志，要求wsrep_debug=on</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.delay_margin</td>
<td>
<p>在此节点<span style="background-color: #c0c0c0;">加入一个entry到delayed list之前</span>，<span style="background-color: #c0c0c0;">响应时间的延迟</span>可达到多少。必须设置的比节点之间的RTT大</p>
<p>其实就是，evs.delay_margin决定了多慢的情况下进行一次计数，evs.auto_evict决定了计数多少次后发起驱除</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.delayed_keep_period</td>
<td>如果一个节点被当前节点加入自己的delayed list，那么该节点要持续多长时间不再缓慢，才能从延迟列表移除</td>
</tr>
<tr>
<td class=" blog_h3">evs.evict</td>
<td>
<p>如果设置为当前节点的gcomm UUID，则当前节点从集群移除</p>
<p>如果设置为空，则当前节点的eviction list被清空</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.inactive_check_period</td>
<td>检测不活动成员（peer）（即响应延迟的节点）的周期。超过此周期之后，节点才可能被加入到delayed list，并在之后进行驱除</td>
</tr>
<tr>
<td class=" blog_h3">evs.inactive_timeout</td>
<td>节点被断定为挂掉之前，inactive的最大持续时间</td>
</tr>
<tr>
<td class=" blog_h3">evs.info_log_mask</td>
<td>记录额外EVS信息的日志</td>
</tr>
<tr>
<td class=" blog_h3">evs.install_timeout</td>
<td>等待install message确认的超时时间</td>
</tr>
<tr>
<td class=" blog_h3">evs.join_retrans_period</td>
<td>组成集群成员关系时，EVS join消息重传的频率</td>
</tr>
<tr>
<td class=" blog_h3">evs.keepalive_period</td>
<td>没有其它流量时，多就发送一次心跳</td>
</tr>
<tr>
<td class=" blog_h3">evs.max_install_timeouts</td>
<td>默认3。install message允许的超时总次数</td>
</tr>
<tr>
<td class=" blog_h3">evs.send_window</td>
<td>
<p>单次允许复制的packet的数量。必须比evs.user_send_window大</p>
<p>仅仅影响数据包，在WAN环境下可以设置的比默认值（4）大的多，例如512</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.stats_report_period</td>
<td>报告EVS统计信息的周期</td>
</tr>
<tr>
<td class=" blog_h3">evs.suspect_timeout</td>
<td>在此超时之后，节点被怀疑已经挂了。如果所有节点怀疑它挂了，那么在evs.install_timeout之前节点会被移除集群</td>
</tr>
<tr>
<td class=" blog_h3">evs.use_aggregate</td>
<td>默认true。如果为true则在可能的情况下小的packet会被聚合为大的</td>
</tr>
<tr>
<td class=" blog_h3">evs.user_send_window</td>
<td>
<p>单次允许复制的packet的数量。必须比evs.send_window小，推荐 1/2</p>
</td>
</tr>
<tr>
<td class=" blog_h3">evs.version</td>
<td>EVS协议版本</td>
</tr>
<tr>
<td class=" blog_h3">evs.view_forget_timeout</td>
<td>从view history中移除之前需要经过的时间</td>
</tr>
<tr>
<td class=" blog_h3">gcache.dir</td>
<td>
<p>GCache存放的目录</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcache.keep_pages_size</td>
<td>缓存页的总数量</td>
</tr>
<tr>
<td class=" blog_h3">gcache.mem_size</td>
<td>GCache缓存使用内存大小</td>
</tr>
<tr>
<td class=" blog_h3">gcache.name</td>
<td>GCache缓存ring buffer文件的名字</td>
</tr>
<tr>
<td class=" blog_h3"><strong>gcache.page_size</strong></td>
<td>GCache缓存页面映射文件的大小</td>
</tr>
<tr>
<td class=" blog_h3">gcache.recover</td>
<td>在节点启动时，是否进行GCache恢复。如果能够恢复GCache，则节点加入集群后可以提供IST —— 这在集群完全重启的时候有用</td>
</tr>
<tr>
<td class=" blog_h3"><strong>gcache.size</strong></td>
<td>缓存ring buffer大小，即用于缓存writeset的空间大小，启动时分配</td>
</tr>
<tr>
<td class=" blog_h3">gcomm.thread_prio</td>
<td>Gcomm线程策略和优先级</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_debug</td>
<td>默认0，大于0时会输出SST流控信息</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_factor</td>
<td>默认1.0，接收队列中writeset数量跌到gcs.fc_limit的多少比例后，恢复复制</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_limit</td>
<td>默认16。接收队列中writeset超过此数量后，复制暂停。在master-slave用法下，可以大大增加</td>
</tr>
<tr>
<td class=" blog_h3">gcs.fc_master_slave</td>
<td>默认no。是否假设集群仅仅有一个master —— 就是所有客户端都连接到一个节点进行写查询</td>
</tr>
<tr>
<td class=" blog_h3">gcs.max_packet_size</td>
<td>最大packet大小，超过此大小则writeset被分片</td>
</tr>
<tr>
<td class=" blog_h3">gcs.max_throttle</td>
<td>
<p>在状态转移时，为了避免内存消耗过大，可以进行复制限流</p>
<p>默认0.25。设置为0则为了完成状态转移，可以暂停复制</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcs.recv_q_hard_limit</td>
<td>
<p>接收队列大小的硬限制。超过此大小则服务器终止运作</p>
<p>推荐设置为内存的 1/2 + swap大小</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcs.recv_q_soft_limit</td>
<td>
<p>接收队列软限制。相对于gcs.recv_q_hard_limit的因子，默认0.25</p>
<p>到达软限制后，即开始限流。限流的速度随着队列积压，线性增加</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gcs.sync_donor</td>
<td>
<p>默认no。是否集群其它成员需要和donor保持同步</p>
<p>如果设置为yes，则当donor因为状态转移而阻塞时整个集群被阻塞</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.listen_addr</td>
<td>默认tcp:0.0.0.0:4567。Galera监听来自其它节点的连接的地址</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.mcast_addr</td>
<td>
<p>默认空。如果设置，则使用UDP组播进行复制，所有节点必须设置一致</p>
<p>示例：gmcast.mcast_addr=239.192.0.11</p>
</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.mcast_ttl</td>
<td>组播的TTL值</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.peer_timeout</td>
<td>initiating message relaying的连接超时</td>
</tr>
<tr>
<td class=" blog_h3">gmcast.segment</td>
<td>
<p>定义节点属于的段（segment）。默认情况下，所有节点都在段0。通常将同一数据中心的节点放在同一个段</p>
<p>Galera协议数据，仅仅会重定向到同一段中的单个节点，然后由该节点再repay给段中其它节点</p>
<p>可以降低跨数据中心的流量</p>
<p>取值0-255之间</p>
</td>
</tr>
<tr>
<td class=" blog_h3">ist.recv_addr</td>
<td>监听IST的地址</td>
</tr>
<tr>
<td class=" blog_h3">pc.ignore_quorum</td>
<td>
<p>默认false。是否忽略quorum计算</p>
<p>如果设置为true，如果master和其它slaves产生了网络分区，它仍然继续工作。但是会导致master-slave模式下slave不能自动重新连接到master</p>
</td>
</tr>
<tr>
<td class=" blog_h3">pc.ignore_sb</td>
<td>默认false。是否允许脑裂情况下继续处理数据修改，在multi-master模式下会导致数据不一致</td>
</tr>
<tr>
<td class=" blog_h3">pc.recovery</td>
<td>默认true。如果设置为true，则Primary Component状态被存储在磁盘中。当集群完全崩溃后，可以进行自动恢复，后续的集群优雅完全重启会要求从一个新的Primary Component自举</td>
</tr>
<tr>
<td class=" blog_h3">pc.wait_prim</td>
<td>默认true。如果设置为true，节点会在pc.wait_prim_timeout之内等待Primary Component。用于启动non-primary component并使通过pc.bootstrap使之称为primary</td>
</tr>
<tr>
<td class=" blog_h3">pc.wait_prim_timeout</td>
<td>等待Primary Component的超时</td>
</tr>
<tr>
<td class=" blog_h3">pc.weight</td>
<td>节点的权重，用于quorum计算</td>
</tr>
<tr>
<td class=" blog_h3">protonet.backend</td>
<td>传输后端，目前仅仅支持ASIO</td>
</tr>
<tr>
<td class=" blog_h3">repl.causal_read_timeout</td>
<td>causal reads（读取已提交）超时</td>
</tr>
<tr>
<td class=" blog_h3">repl.commit_order</td>
<td>
<p>是否、何时允许乱序commit：</p>
<p style="padding-left: 30px;">0 BYPASS  不监控commit顺序<br />1 OOOC 对于所有事务允许乱序提交<br />2 LOCAL_OOOC 仅仅允许本地事务的乱序提交<br />3 NO_OOOC 不允许乱序提交</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"><a id="k8s"></a>在K8S中运行</span></div>
<div class="blog_h2"><span class="graybg">Orange方案</span></div>
<p>Orange开发了一个Galera Operator，简化K8S中的Galera的部署和运维。支持的特性包括：</p>
<ol>
<li>创建、销毁集群</li>
<li>集群规模括缩容</li>
<li>垂直括缩：修改容器CPU/内存、持久卷大小</li>
<li>节点崩溃后自动故障转移</li>
<li>备份Galera集群到外部S3存储</li>
<li>从外部备份恢复Galera集群</li>
</ol>
<p>&nbsp;</p>
<div class="blog_h2"><span class="graybg">severalnines方案</span></div>
<p>来自 <a href="https://github.com/severalnines/galera-docker-mariadb">severalnines / galera-docker-mariadb</a>。此方案在K8S中运行Maria DB 10.1，支持Galera，需要同时运行一个Etcd集群。</p>
<div class="blog_h3"><span class="graybg">entrypoint.sh</span></div>
<p>逻辑比较乱，主要是通过查询Etcd来获得集群节点列表、各节点的seqno信息：</p>
<pre class="crayon-plain-tag">#!/bin/bash
set -e

# if command starts with an option, prepend mysqld
if [ "${1:0:1}" = '-' ]; then
  CMDARG="$@"
fi

[ -z "$TTL" ] &amp;&amp; TTL=10

if [ -z "$CLUSTER_NAME" ]; then
  echo &gt;&amp;2 'Error:  You need to specify CLUSTER_NAME'
  exit 1
fi

DATADIR="$("mysqld" --verbose --help 2&gt;/dev/null | awk '$1 == "datadir" { print $2; exit }')"
echo &gt;&amp;2 "Content of $DATADIR:"
ls -al $DATADIR

if [ ! -s "$DATADIR/grastate.dat" ]; then
  # 没有grastate.dat文件，意味着集群尚未初始化
  INITIALIZED=1
  if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
    echo &gt;&amp;2 'error: database is uninitialized and password option is not specified '
    echo &gt;&amp;2 '  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'
    exit 1
  fi
  mkdir -p "$DATADIR"
  chown -R mysql:mysql "$DATADIR"

  # 初始化MariaDB
  echo 'Running mysql_install_db'
  mysql_install_db --user=mysql --datadir="$DATADIR" --rpm
  echo 'Finished mysql_install_db'

  # 在后台启动MariaDB
  mysqld --user=mysql --datadir="$DATADIR" --skip-networking &amp;
  pid="$!"

  mysql=(mysql --protocol=socket -uroot)

  # 等待MariaDB初始化完毕
  for i in {30..0}; do
    if echo 'SELECT 1' | "${mysql[@]}" &amp;&gt;/dev/null; then
      break
    fi
    echo 'MySQL init process in progress...'
    sleep 1
  done
  if [ "$i" = 0 ]; then
    echo &gt;&amp;2 'MySQL init process failed.'
    exit 1
  fi

  # sed is for https://bugs.mysql.com/bug.php?id=20545
  mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql
  if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
    MYSQL_ROOT_PASSWORD="$(pwmake 128)"
    echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
  fi

  # 在本地（禁止复制）创建root xtrabackup等用户
  "${mysql[@]}" &lt;&lt;-EOSQL
	  -- What's done in this file shouldn't be replicated
	  --  or products like mysql-fabric won't work
	  SET @@SESSION.SQL_LOG_BIN=0;
	  DELETE FROM mysql.user ;
	  CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
	  GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
	  CREATE USER 'xtrabackup'@'localhost' IDENTIFIED BY '$XTRABACKUP_PASSWORD';
	  GRANT RELOAD,LOCK TABLES,REPLICATION CLIENT ON *.* TO 'xtrabackup'@'localhost';
	  GRANT REPLICATION CLIENT ON *.* TO monitor@'%' IDENTIFIED BY 'monitor';
	  DROP DATABASE IF EXISTS test ;
	  FLUSH PRIVILEGES ;
EOSQL
  if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then
    mysql+=(-p"${MYSQL_ROOT_PASSWORD}")
  fi

  # 创建额外的数据库、用户
  if [ "$MYSQL_DATABASE" ]; then
    echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}"
    mysql+=("$MYSQL_DATABASE")
  fi

  if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
    echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" | "${mysql[@]}"

    if [ "$MYSQL_DATABASE" ]; then
      echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" | "${mysql[@]}"
    fi

    echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}"
  fi

  if [ ! -z "$MYSQL_ONETIME_PASSWORD" ]; then
    "${mysql[@]}" &lt;&lt;-EOSQL
		ALTER USER 'root'@'%' PASSWORD EXPIRE;
EOSQL
  fi
  if ! kill -s TERM "$pid" || ! wait "$pid"; then
    echo &gt;&amp;2 'MySQL init process failed.'
    exit 1
  fi

  echo
  echo 'MySQL init process done. Ready for start up.'
  echo
fi
chown -R mysql:mysql "$DATADIR"

function join {
  local IFS="$1"
  shift
  echo "$*"
}


# 将当前MariaDB实例加入到Galera集群，通过访问Etcd获取必要信息
if [ -z "$DISCOVERY_SERVICE" ]; then
  cluster_join=$CLUSTER_JOIN
else
  echo
  echo '&gt;&gt; Registering in the discovery service'

  etcd_hosts=$(echo $DISCOVERY_SERVICE | tr ',' ' ')
  flag=1

  echo
  # 寻找健康Etcd节点
  for i in $etcd_hosts; do
    echo "&gt;&gt; Connecting to https://${i}/health"
    curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key https://${i}/health || continue
    if curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key https://$i/health | jq -e 'contains({ "health": "true"})'; then
      healthy_etcd=$i
      flag=0
      break
    else
      echo &gt;&amp;2 "&gt;&gt; Node $i is unhealty. Proceed to the next node."
    fi
  done

  # 如果没有Etcd节点则放弃
  if [ $flag -ne 0 ]; then
    echo "&gt;&gt; Couldn't reach healthy etcd nodes."
    exit 1
  fi

  echo
  echo "&gt;&gt; Selected healthy etcd: $healthy_etcd"

  if [ ! -z "$healthy_etcd" ]; then
    URL="https://$healthy_etcd/v2/keys/galera/$CLUSTER_NAME"

    set +e
    echo &gt;&amp;2 "&gt;&gt; Waiting for $TTL seconds to read non-expired keys.."
    # 防止刚刚死去的Galera节点的注册信息仍然存在
    sleep $TTL

    # 获取活动Galera节点列表
    echo &gt;&amp;2 "&gt;&gt; Retrieving list of keys for $CLUSTER_NAME"
    addr=$(curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key $URL | jq -r '.node.nodes[]?.key' | awk -F'/' '{print $(NF)}')
    cluster_join=$(join , $addr)

    ipaddr=$(hostname -i | awk {'print $1'})
    [ -z $ipaddr ] &amp;&amp; ipaddr=$(hostname -I | awk {'print $1'})

    echo
    if [ -z $cluster_join ]; then
      # 没有活动Galera节点。集群尚未初始化，或者集群完全宕机后重启（需要bootstrap）
      echo &gt;&amp;2 "&gt;&gt; KV store is empty. This is a the first node to come up."
      echo
      echo &gt;&amp;2 "&gt;&gt; Registering $ipaddr in https://$healthy_etcd"
      # 写入当前节点信息到Etcd
      curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key $URL/$ipaddr/ipaddress -X PUT -d "value=$ipaddr"
    else
      # 存在活动的Galera节点，看看有没有Synced的
      curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out
      running_nodes=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("wsrep_local_state_comment")) | select(.value == "Synced") | .key' | awk -F'/' '{print $(NF-1)}' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')
      echo
      echo "&gt;&gt; Running nodes: [${running_nodes}]"

      if [ -z "$running_nodes" ]; then
        # 没有Synced节点，必须恢复本节点的seqno，如果本节点最大，则可以自举
        TMP=/var/lib/mysql/$(hostname).err
        echo &gt;&amp;2 "&gt;&gt; There is no node in synced state."
        echo &gt;&amp;2 "&gt;&gt; It's unsafe to bootstrap unless the sequence number is the latest."
        echo &gt;&amp;2 "&gt;&gt; Determining the Galera last committed seqno using --wsrep-recover.."
        echo

        # 恢复seqno
        mysqld_safe --wsrep-cluster-address=gcomm:// --wsrep-recover
        cat $TMP
        seqno=$(cat $TMP | tr ' ' "\n" | grep -e '[a-z0-9]*-[a-z0-9]*:[0-9]' | head -1 | cut -d ":" -f 2)
        # 此容器是新启动的，才会设置INITIALIZED。对于新节点，seqno设置为0
        if [ $INITIALIZED -eq 1 ]; then
          echo &gt;&amp;2 "&gt;&gt; This is a new container, thus setting seqno to 0."
          seqno=0
        fi

        echo
        if [ ! -z $seqno ]; then
          # 将当前节点的seqno写入到Etcd
          echo &gt;&amp;2 "&gt;&gt; Reporting seqno:$seqno to ${healthy_etcd}."
          WAIT=$(($TTL * 2))
          curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key $URL/$ipaddr/seqno -X PUT -d "value=$seqno&amp;ttl=$WAIT"
        else
          seqno=$(cat $TMP | tr ' ' "\n" | grep -e '[a-z0-9]*-[a-z0-9]*:[0-9]' | head -1)
          echo &gt;&amp;2 "&gt;&gt; Unable to determine Galera sequence number."
          exit 1
        fi
        rm $TMP

        echo
        echo &gt;&amp;2 "&gt;&gt; Sleeping for $TTL seconds to wait for other nodes to report."
        sleep $TTL

        echo
        echo &gt;&amp;2 "&gt;&gt; Retrieving list of seqno for $CLUSTER_NAME"
        bootstrap_flag=1

        # Retrieve seqno from etcd
        curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out
        cluster_seqno=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("seqno")) | .value' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')

        for i in $cluster_seqno; do
          if [ $i -gt $seqno ]; then
            # 如果存在其它节点的seqno更大，本节点不处理bootstrap
            bootstrap_flag=0
            echo &gt;&amp;2 "&gt;&gt; Found another node holding a greater seqno ($i/$seqno)"
          fi
        done

        echo
        if [ $bootstrap_flag -eq 1 ]; then
          # 本节点seqno最大，处理bootstrap
          # Find the earliest node to report if there is no higher seqno
          # node_to_bootstrap=$(cat /tmp/out | jq -c '.node.nodes[].nodes[]?' | grep seqno | tr ',:\"' ' ' | sort -k 11 | head -1 | awk -F'/' '{print $(NF-1)}')
          ## The earliest node to report if there is no higher seqno is computed wrongly: issue #6
          node_to_bootstrap=$(cat /tmp/out | jq -c '.node.nodes[].nodes[]?' | grep seqno | tr ',:"' ' ' | sort -k5,5r -k11 | head -1 | awk -F'/' '{print $(NF-1)}')
          if [ "$node_to_bootstrap" == "$ipaddr" ]; then
            echo &gt;&amp;2 "&gt;&gt; This node is safe to bootstrap."
            cluster_join=
          else
            echo &gt;&amp;2 "&gt;&gt; Based on timestamp, $node_to_bootstrap is the chosen node to bootstrap."
            echo &gt;&amp;2 "&gt;&gt; Wait again for $TTL seconds to look for a bootstrapped node."
            sleep $TTL
            curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out

            # Look for a synced node again
            running_nodes2=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("wsrep_local_state_comment")) | select(.value == "Synced") | .key' | awk -F'/' '{print $(NF-1)}' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')

            echo
            echo &gt;&amp;2 "&gt;&gt; Running nodes: [${running_nodes2}]"

            if [ ! -z "$running_nodes2" ]; then
              cluster_join=$(join , $running_nodes2)
            else
              echo
              echo &gt;&amp;2 "&gt;&gt; Unable to find a bootstrapped node to join."
              echo &gt;&amp;2 "&gt;&gt; Exiting."
              exit 1
            fi
          fi
        else
          # seqno不是最大的，不处理bootstrap
          echo &gt;&amp;2 "&gt;&gt; Refusing to start for now because there is a node holding higher seqno."
          echo &gt;&amp;2 "&gt;&gt; Wait again for $TTL seconds to look for a bootstrapped node."
          sleep $TTL

          # Look for a synced node again
          curl -s -k --cacert /etc/etcd/ca.crt --cert /etc/etcd/client.crt --key /etc/etcd/client.key ${URL}?recursive=true\&amp;sorted=true &gt;/tmp/out
          running_nodes3=$(cat /tmp/out | jq -r '.node.nodes[].nodes[]? | select(.key | contains ("wsrep_local_state_comment")) | select(.value == "Synced") | .key' | awk -F'/' '{print $(NF-1)}' | tr "\n" ' ' | sed -e 's/[[:space:]]*$//')

          echo
          echo &gt;&amp;2 "&gt;&gt; Running nodes: [${running_nodes3}]"

          if [ ! -z "$running_nodes2" ]; then
            cluster_join=$(join , $running_nodes3)
          else
            echo
            echo &gt;&amp;2 "&gt;&gt; Unable to find a bootstrapped node to join."
            echo &gt;&amp;2 "&gt;&gt; Exiting."
            exit 1
          fi
        fi
      else
        # if there is a Synced node, join the address
        cluster_join=$(join , $running_nodes)
      fi
    fi
    set -e

    echo
    echo &gt;&amp;2 "&gt;&gt; Cluster address is gcomm://$cluster_join"
  else
    echo
    echo &gt;&amp;2 '&gt;&gt; No healthy etcd host detected. Refused to start.'
    exit 1
  fi
fi

echo
echo &gt;&amp;2 "&gt;&gt; Starting reporting script in the background"
nohup /report_status.sh root $MYSQL_ROOT_PASSWORD $CLUSTER_NAME $TTL $DISCOVERY_SERVICE &amp;

# set IP address based on the primary interface
sed -i "s|WSREP_NODE_ADDRESS|$ipaddr|g" /etc/my.cnf

echo
echo &gt;&amp;2 "&gt;&gt; Starting mysqld process"
if [ -z $cluster_join ]; then
  export _WSREP_NEW_CLUSTER='--wsrep-new-cluster'
  # set safe_to_bootstrap = 1
  GRASTATE=$DATADIR/grastate.dat
  [ -f $GRASTATE ] &amp;&amp; sed -i "s|safe_to_bootstrap.*|safe_to_bootstrap: 1|g" $GRASTATE
else
  export _WSREP_NEW_CLUSTER=''
fi

exec mysqld --wsrep_cluster_name=$CLUSTER_NAME --wsrep-cluster-address="gcomm://$cluster_join" --wsrep_sst_auth="xtrabackup:$XTRABACKUP_PASSWORD" $_WSREP_NEW_CLUSTER $CMDARG</pre>
<div class="blog_h3"><span class="graybg">report_status.sh</span></div>
<p>该脚本会每TTL报告一次当前Galera容器的状态。</p>
<div class="blog_h3"><span class="graybg">配置</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">环境变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>MYSQL_ROOT_PASSWORD</td>
<td>
<p>MySQL数据库的密码</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>XTRABACKUP_PASSWORD</td>
<td>
<p>此方案使用的SST方法是XtraBackup SST，它使用用户xtrabackup@localhost，如果需要定制密码，设置该变量</p>
</td>
</tr>
<tr>
<td>CLUSTER_NAME</td>
<td>Galera集群的名称</td>
</tr>
<tr>
<td>DISCOVERY_SERVICE</td>
<td>
<p>如果需要启用发现服务（目前仅仅支持Etcd），需要配置该变量，形式IP:PORT，多个地址需要逗号分隔</p>
<p>容器会访问发现服务，使用CLUSTER_NAME来查询，如果发现已经存在对应的Galera集群，则加入；否则创建新的Galera集群</p>
</td>
</tr>
<tr>
<td>CLUSTER_JOIN</td>
<td>
<p>如果不启用发现服务，则需要设置此变量：</p>
<ol>
<li>如果变量为空，则创建新集群</li>
<li>如果不为空，则连接到此地址所指向的、既有集群的节点</li>
</ol>
</td>
</tr>
<tr>
<td>TTL</td>
<td>
<p>如果节点是alive的（wsrep_cluster_state_comment=Synced），每TTL - 2秒，report_status.sh会报告自身的状态</p>
<p>如果节点宕掉了，那么Etcd中的key无法刷新，因为过期而删除，这样构建Galera通信地址URI时就会跳过宕掉的节点</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">服务发现</span></div>
<p>如果Galera集群名字为my_wsrep_cluster，则此方案会在Etcd中存储以下数据：</p>
<pre class="crayon-plain-tag">// url -s "http://192.168.55.111:2379/v2/keys/galera/my_wsrep_cluster?recursive=true"
{
    "action": "get",
    "node": {
        "createdIndex": 10049,
        "dir": true,
        "key": "/galera/my_wsrep_cluster",
        "modifiedIndex": 10049,
        "nodes": [
            {
                "createdIndex": 10067,
                "dir": true,
                // 每个节点在 /galera/$CLUSTER_NAME/下有个键
                "key": "/galera/my_wsrep_cluster/10.255.0.6",
                "modifiedIndex": 10067,
                "nodes": [
                    {
                        "createdIndex": 10075,
                        "expiration": "2016-11-29T10:55:35.37622336Z",
                        // 存储节点seqno
                        "key": "/galera/my_wsrep_cluster/10.255.0.6/wsrep_last_committed",
                        "modifiedIndex": 10075,
                        "ttl": 10,
                        "value": "0"
                    },
                    {
                        "createdIndex": 10073,
                        "expiration": "2016-11-29T10:55:34.788170259Z",
                        // 存储节点状态
                        "key": "/galera/my_wsrep_cluster/10.255.0.6/wsrep_local_state_comment",
                        "modifiedIndex": 10073,
                        "ttl": 10,
                        "value": "Synced"
                    }
                ]
            }...
        ]
    }
}</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/galera-study-note">Galera学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/galera-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
