learn.lianglianglee.com/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html
2022-08-14 03:40:33 +08:00

400 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>21 案例:分布式 MySQL 集群工具 Vitess 实践分析.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="/专栏/Kubernetes 实践入门指南/00 为什么我们要学习 Kubernetes 技术.md.html">00 为什么我们要学习 Kubernetes 技术</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md.html">01 重新认识 Kubernetes 的核心组件</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md.html">02 深入理解 Kubernets 的编排对象</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md.html">03 DevOps 场景下落地 K8s 的困难分析</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md.html">04 微服务应用场景下落地 K8s 的困难分析</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md.html">05 解决 K8s 落地难题的方法论提炼</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md.html">06 练习篇K8s 核心实践知识掌握</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md.html">07 容器引擎 containerd 落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md.html">08 K8s 集群安装工具 kubeadm 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md.html">09 南北向流量组件 IPVS 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md.html">10 东西向流量组件 Calico 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md.html">11 服务发现 DNS 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md.html">12 练习篇K8s 集群配置测验</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md.html">13 理解对方暴露服务的对象 Ingress 和 Service</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md.html">14 应用网关 OpenResty 对接 K8s 实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md.html">15 Service 层引流技术实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md.html">16 Cilium 容器网络的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md.html">17 应用流量的优雅无损切换实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md.html">18 练习篇:应用流量无损切换技术测验</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md.html">19 使用 Rook 构建生产可用存储环境实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md.html">20 有状态应用的默认特性落地分析</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html">21 案例:分布式 MySQL 集群工具 Vitess 实践分析</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html">22 存储对象 PV、PVC、Storage Classes 的管理落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md.html">23 K8s 集群中存储对象灾备的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md.html">24 练习篇K8s 集群配置测验</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>21 案例:分布式 MySQL 集群工具 Vitess 实践分析</h1>
<p>对于 Kubernetes 的有状态应用部署来说,当然最有挑战的例子就是拿 MySQL 集群部署最为经典。在近 10 年的数据库流行度来讲,每一个开发者接触到最多的就是 MySQL 数据库了。几乎人人都知道 MySQL Master/Slave 方式的集群搭建方式,其架构的复杂度可想而知。当我们技术把 MySQL 集群搭建到 Kubernetes 集群的时候就不得不考虑如何利用云原生特性把集群搭建起来。这里笔者并不想去分析如何徒手分解安装 MySQL 集群的 YAML而是通过有过成功迁移云原生集群工具 Vitess 来总结真实的实践过程。</p>
<h3>Vitess 工具介绍</h3>
<p>Vitess 号称可以水平扩展 MySQL 数据库集群管理工具。最早被我们熟知的新闻就是京东在 618 大促中全面采用云原生技术,其中数据库分片集群管理这块就是采用的 Vitess。接下来我们首先快速体验一下在 Kubernetes 下使用 Vitess 的过程。</p>
<h4><strong>初始化环境</strong></h4>
<p>采用单机部署,在 AWS 上启动一台内存大于 8G 的虚拟机,通过安装 K3s 快速构建一套 Kubernetes 环境。</p>
<pre><code class="language-bash"># 初始化 Kubernetes 单机集群
curl https://releases.rancher.com/install-docker/19.03.sh | sh
curl -sfL https://get.k3s.io | sh -
# 下载 kubectl
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.14.9/bin/linux/amd64/kubectl
# 安装 MySQL 客户端
apt install mysql-client
# 下载安装客户端 vtctlclient 最新版本:
wget https://github.com/vitessio/vitess/releases/download/v8.0.0/vitess-8.0.0-7e09d0c.tar.gz
tar zxvf vitess-8.0.0-7e09d0c.tar.gz &amp;&amp; cp vitess-8.0.0-7e09d0c/bin/vtctlclient /usr/local/bin/
# 下载 vitess operator 例子
git clone https://github.com/vitessio/vitess.git
cd vitess/examples/operator
k3s kubectl apply -f operator.yaml
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7f0d10100b3f160f524e484d524c4e524d48524d4f4c">[email&#160;protected]</a>:~/vitess/examples/operator# k3s kubectl get po
NAME READY STATUS RESTARTS AGE
vitess-operator-784458658c-mzhzx 1/1 Running 0 59s
# 初始化集群
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8cfee3e3f8cce5fca1bdbbbea1bfbda1bebba1bebcbf">[email&#160;protected]</a>:~/vitess/examples/operator# k3s kubectl apply -f 101_initial_cluster.yaml
vitesscluster.planetscale.com/example created
secret/example-cluster-config created
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="691b06061d29001944585e5b445a58445b5e445b595a">[email&#160;protected]</a>:~/vitess/examples/operator# k3s kubectl get pods
NAME READY STATUS RESTARTS AGE
vitess-operator-784458658c-mzhzx 1/1 Running 0 3m38s
example-etcd-faf13de3-2 1/1 Running 0 111s
example-etcd-faf13de3-1 1/1 Running 0 111s
example-etcd-faf13de3-3 1/1 Running 0 111s
example-zone1-vtctld-1d4dcad0-68484d7b88-428dc 1/1 Running 2 111s
example-zone1-vtgate-bc6cde92-c6499cf87-w86rz 1/1 Running 2 111s
example-vttablet-zone1-2469782763-bfadd780 3/3 Running 2 111s
example-vttablet-zone1-2548885007-46a852d0 3/3 Running 2 111s
</code></pre>
<p>为了方便连接 Vitess 这个 proxy需要初始化一下端口转发的环境</p>
<pre><code class="language-bash">./pf.sh &amp;
alias vtctlclient=&quot;vtctlclient -server=localhost:15999&quot;
alias mysql=&quot;mysql -h 127.0.0.1 -P 15306 -u user&quot;
</code></pre>
<p>加载数据库表结构:</p>
<pre><code class="language-bash">vtctlclient ApplySchema -sql=&quot;$(cat create_commerce_schema.sql)&quot; commerce
vtctlclient ApplyVSchema -vschema=&quot;$(cat vschema_commerce_initial.json)&quot; commerce
</code></pre>
<p>通过 MySQL 连接 Vitess Proxy 访问 MySQL Server</p>
<pre><code class="language-bash">~/vitess/examples/operator$ mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.9-Vitess MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql&gt; show databases;
+-----------+
| Databases |
+-----------+
| commerce |
+-----------+
1 row in set (0.00 sec)
</code></pre>
<p>至此,我们的体验和安装一套本地的 MySQL Server 是一样的。这种透明的体验值得我们接下来持续挖掘更高级的特性。</p>
<p>下图说明了 Vitess 的组件架构,我们需要熟悉这些术语:</p>
<p><img src="assets/36be7e60-2cc7-11eb-90f6-fbd19bda6e6e" alt="18-1-vitess-arch33" /></p>
<p><strong>Topology</strong></p>
<p>拓扑服务是一个元数据存储对象,包含有关正在运行的服务器、分片方案和复制关系图的信息。拓扑由一致性的数据存储支持,默认支持 etcd2 插件。您可以使用 vtctl命令行和 vtctldweb查看拓扑信息。</p>
<p><strong>VTGate</strong></p>
<p>VTGate 是一个轻型代理服务器,它将流量路由到正确的 VTTablet并将合并的结果返回给客户端。应用程序向 VTGate 发起查询。客户端使用起来非常简单,它只需要能够找到 VTGate 实例就能使 Vitess。</p>
<p><strong>VTTablet</strong></p>
<p>VTTablet 是一个位于 MySQL 数据库前面的代理服务器,执行的任务试图最大化吞吐量,同时保护 MySQL 不受有害查询的影响。它的特性包括连接池、查询重写和重用重复数据。</p>
<p><strong>Keyspace</strong></p>
<p>关键空间是一个逻辑数据库。如果使用 Sharding一个 keyspace 映射到多个 MySQL 数据库;如果不使用 Sharding一个 keyspace 直接映射到一个 MySQL 数据库名。无论哪种情况,从应用程序的角度来看,一个关键空间都是作为一个单一的数据库出现的。</p>
<p>从一个关键空间读取数据就像从 MySQL 数据库读取数据一样。然而根据读取操作的一致性要求Vitess 可能会从主数据库或副本中获取数据。通过将每个查询路由到适当的数据库Vitess 允许你的代码结构化,就像从一个 MySQL 数据库中读取一样。</p>
<h3>Vitess 高级特性介绍</h3>
<p>Sharding 是一种水平分区数据库的方法,用于在两个或多个数据库服务器上存储数据。下面我们讲解 Vitess 中的 Sharding 如何工作以及 Vitess 支持的 Sharding 类型。</p>
<p>Vitess 中的 keyspace 可以是分片的,也可以是非碎片化的,非分片化的 keyspace 可以直接映射到 MySQL 数据库。如果是分片的keyspace 的行被分割到相同模式的不同数据库中。</p>
<p>例如,如果一个应用程序的 &quot;User&quot; keyspace 被分割成两个分片,那么每个分片包含了该应用程序大约一半用户的记录。同样,每个用户的信息也只存储在一个 Shard 中。</p>
<p>请注意Sharding 与MySQL复制是正交的。一个 Vitess Shard 通常包含一个 MySQL 主程序和许多 MySQL 副本。主程序处理写操作而副本则处理只读流量、批处理操作和其他任务。除了一些复制滞后外Shard 内的每个 MySQL 实例都应该有相同的数据。</p>
<table>
<thead>
<tr>
<th align="left">需求</th>
<th align="left">动作</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">统一增加读容量</td>
<td align="left">增加副本或分片</td>
</tr>
<tr>
<td align="left">统一增加写容量</td>
<td align="left">分片 shards</td>
</tr>
<tr>
<td align="left">回收过剩的资源</td>
<td align="left">合并 shards 或 keyspaces</td>
</tr>
<tr>
<td align="left">增加地理多样性</td>
<td align="left">增加新的分区和副本</td>
</tr>
<tr>
<td align="left">热表处理</td>
<td align="left">对于只读热表,多加副本或分片;对于写表,直接分片</td>
</tr>
</tbody>
</table>
<p>应用新的 VSchema 会指示 Vitess 键空间是分片的,这可能会阻止一些复杂的查询。在进行这一步之前,最好先验证一下。如果你确实注意到某些查询开始失败,你总是可以通过恢复旧的 VSchema 来暂时恢复。确保在进入 Reshard 过程之前修复了所有的查询。</p>
<pre><code class="language-bash">vtctlclient ApplySchema -sql=&quot;$(cat create_commerce_seq.sql)&quot; commerce
vtctlclient ApplyVSchema -vschema=&quot;$(cat vschema_commerce_seq.json)&quot; commerce
vtctlclient ApplySchema -sql=&quot;$(cat create_customer_sharded.sql)&quot; customer
vtctlclient ApplyVSchema -vschema=&quot;$(cat vschema_customer_sharded.json)&quot; customer
</code></pre>
<p>在这一点上,你已经最终确定了你的分片 VSchema并审核了所有的查询以确保它们仍然有效。现在是时候重新分片了。</p>
<p>重新 Sharding 的过程是通过将现有的 shard 分割成更小的 shard。这种类型的重新 Sharding 是最适合 Vitess 的。在某些情况下,您可能希望引入一个新的分片,并在最近创建的分片中添加新行。在 Vitess 中,可以通过拆分 Shard 的方式来实现这一点。</p>
<pre><code class="language-bash">kubectl apply -f 302_new_shards.yaml
killall kubectl
./pf.sh &amp;
# With Operator on Start the Reshard
vtctlclient Reshard customer.cust2cust '-' '-80,80-'
</code></pre>
<p>在 Reshard 完成后,我们可以使用 VDiff 来检查数据的完整性,确保我们的源和目标分片是一致的。</p>
<pre><code class="language-bash">vtctlclient VDiff customer.cust2cust
# 返回如下内容
Summary for customer: {ProcessedRows:5 MatchingRows:5 MismatchedRows:0 ExtraRowsSource:0 ExtraRowsTarget:0}
Summary for corder: {ProcessedRows:5 MatchingRows:5 MismatchedRows:0 ExtraRowsSource:0 ExtraRowsTarget:0}
</code></pre>
<p>手工切换读、写操作到新分片。确保数据库正常执行:</p>
<pre><code class="language-bash">vtctlclient SwitchReads -tablet_type=rdonly customer.cust2cust
vtctlclient SwitchReads -tablet_type=replica customer.cust2cust
vtctlclient SwitchWrites customer.cust2cust
mysql --table &lt; ../common/select_customer-80_data.sql
Using customer/-80
Customer
+-------------+--------------------+
| customer_id | email |
+-------------+--------------------+
| 1 | <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0c6d60656f694c6863616d6562226f6361">[email&#160;protected]</a> |
| 2 | <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d2b0bdb092b6bdbfb3bbbcfcb1bdbf">[email&#160;protected]</a> |
| 3 | <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ee8d868f9c82878bae8a81838f8780c08d8183">[email&#160;protected]</a> |
| 5 | <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="086d7e6d486c6765696166266b6765">[email&#160;protected]</a> |
+-------------+--------------------+
COrder
+----------+-------------+----------+-------+
| order_id | customer_id | sku | price |
+----------+-------------+----------+-------+
| 1 | 1 | SKU-1001 | 100 |
| 2 | 2 | SKU-1002 | 30 |
| 3 | 3 | SKU-1002 | 30 |
| 5 | 5 | SKU-1002 | 30 |
+----------+-------------+----------+-------+
mysql --table &lt; ../common/select_customer80-_data.sql
Using customer/80-
Customer
+-------------+----------------+
| customer_id | email |
+-------------+----------------+
| 4 | <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2b4f4a456b4f44464a424505484446">[email&#160;protected]</a> |
+-------------+----------------+
COrder
+----------+-------------+----------+-------+
| order_id | customer_id | sku | price |
+----------+-------------+----------+-------+
| 4 | 4 | SKU-1002 | 30 |
+----------+-------------+----------+-------+
</code></pre>
<h3>总结</h3>
<p>应用 Vitess Operator 之后,收获最大的就是完全不用操心 MySQL 复制集群的架构设计,由 Vitess Operator 来管理高可用和数据库的分片,把复杂的分布式部署的运维问题屏蔽了一大半。当然,作为运维人员需要注意的是,因为 Vitess 是一个 Proxy它和 MySQL 原生接口的协议还是有一些不一样的地方,需要适配。因为京东在 618 大促中采用了 Vitess 技术来支撑数据库集群,让我们可以放心大胆地使用它。</p>
<h3>参考资料</h3>
<ul>
<li><a href="https://vitess.io/zh/docs/get-started/kubernetes/">https://vitess.io/zh/docs/get-started/kubernetes/</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709972848c073d60","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>