mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-30 23:26:43 +08:00
837 lines
30 KiB
HTML
837 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>24 现状解读:分布式数据库的最新发展情况.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/00 开篇词 吃透分布式数据库,提升职场竞争力.md">00 开篇词 吃透分布式数据库,提升职场竞争力.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/01 导论:什么是分布式数据库?聊聊它的前世今生.md">01 导论:什么是分布式数据库?聊聊它的前世今生.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/02 SQL vs NoSQL:一次搞清楚五花八门的“SQL”.md">02 SQL vs NoSQL:一次搞清楚五花八门的“SQL”.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/03 数据分片:如何存储超大规模的数据?.md">03 数据分片:如何存储超大规模的数据?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/04 数据复制:如何保证数据在分布式场景下的高可用?.md">04 数据复制:如何保证数据在分布式场景下的高可用?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/05 一致性与 CAP 模型:为什么需要分布式一致性?.md">05 一致性与 CAP 模型:为什么需要分布式一致性?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/06 实践:设计一个最简单的分布式数据库.md">06 实践:设计一个最简单的分布式数据库.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/07 概要:什么是存储引擎,为什么需要了解它?.md">07 概要:什么是存储引擎,为什么需要了解它?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/08 分布式索引:如何在集群中快速定位数据?.md">08 分布式索引:如何在集群中快速定位数据?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/09 日志型存储:为什么选择它作为底层存储?.md">09 日志型存储:为什么选择它作为底层存储?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/10 事务处理与恢复(上):数据库崩溃后如何保证数据不丢失?.md">10 事务处理与恢复(上):数据库崩溃后如何保证数据不丢失?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/11 事务处理与恢复(下):如何控制并发事务?.md">11 事务处理与恢复(下):如何控制并发事务?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/12 引擎拓展:解读当前流行的分布式存储引擎.md">12 引擎拓展:解读当前流行的分布式存储引擎.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/13 概要:分布式系统都要解决哪些问题?.md">13 概要:分布式系统都要解决哪些问题?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/14 错误侦测:如何保证分布式系统稳定?.md">14 错误侦测:如何保证分布式系统稳定?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/15 领导选举:如何在分布式系统内安全地协调操作?.md">15 领导选举:如何在分布式系统内安全地协调操作?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/16 再谈一致性:除了 CAP 之外的一致性模型还有哪些?.md">16 再谈一致性:除了 CAP 之外的一致性模型还有哪些?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/17 数据可靠传播:反熵理论如何帮助数据库可靠工作?.md">17 数据可靠传播:反熵理论如何帮助数据库可靠工作?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/18 分布式事务(上):除了 XA,还有哪些原子提交算法吗?.md">18 分布式事务(上):除了 XA,还有哪些原子提交算法吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/19 分布式事务(下):Spanner 与 Calvin 的巅峰对决.md">19 分布式事务(下):Spanner 与 Calvin 的巅峰对决.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/20 共识算法:一次性说清楚 Paxos、Raft 等算法的区别.md">20 共识算法:一次性说清楚 Paxos、Raft 等算法的区别.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/21 知识串讲:如何取得性能和可扩展性的平衡?.md">21 知识串讲:如何取得性能和可扩展性的平衡?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/22 发展与局限:传统数据库在分布式领域的探索.md">22 发展与局限:传统数据库在分布式领域的探索.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/23 数据库中间件:传统数据库向分布式数据库的过渡.md">23 数据库中间件:传统数据库向分布式数据库的过渡.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/24讲吃透分布式数据库-完/24 现状解读:分布式数据库的最新发展情况.md">24 现状解读:分布式数据库的最新发展情况.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/加餐1 概念解析:云原生、HTAP、图与内存数据库.md">加餐1 概念解析:云原生、HTAP、图与内存数据库.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/加餐2 数据库选型:我们该用什么分布式数据库?.md">加餐2 数据库选型:我们该用什么分布式数据库?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>24 现状解读:分布式数据库的最新发展情况</h1>
|
||
|
||
<p>你好,恭喜你坚持到了课程的最后一讲。</p>
|
||
|
||
<p>上一讲,我们探讨了实现数据库中间件的几种技术,包括全局唯一主键、分片策略和跨分片查询,其中最重要的就是分布式事务,希望你可以掌握它。这一讲作为收尾,我将为你介绍 NewSQL 数据库。</p>
|
||
|
||
<p>首先我试着去定义 NewSQL,它是一类现代的关系型数据库,同时它又具备 NoSQL 的扩展能力。其擅长在 OLTP 场景下提供高性能的读写服务,同时可以保障事务隔离性和数据一致性。我们可以简单理解为,NewSQL 要将 2000 年左右发展而来的 NoSQL 所代表的扩展性与 20 世纪 70 年代发展的关系模型 SQL 和 ACID 事务进行结合,从而获得一个高并发关系型的分布式数据库。</p>
|
||
|
||
<p>如果我们使用 NewSQL 数据库,可以使用熟悉的 SQL 来与数据库进行交互。SQL 的优势我在模块一中已经有了深入的介绍。使用 SQL 使得原有基于 SQL 的应用不需要改造(或进行微量改造)就可以直接从传统关系型数据库切换到 NewSQL 数据库。而与之相对,NoSQL 数据库一般使用 SQL 变种语言或者定制的 API,那么用户切换到 NoSQL 数据库将会面临比较高的代价。</p>
|
||
|
||
<p>对于 NewSQL 的定义和适用范围一直存在争议。有人认为 Vertica、Greenplum 等面向 OLAP 且具有分布式特点的数据库也应该归到 NewSQL 里面。但是,业界更加广泛接受的 NewSQL 标准包括:</p>
|
||
|
||
<ol>
|
||
|
||
<li>执行短的读写事务,也就是不能出现阻塞的事务操作;</li>
|
||
|
||
<li>使用索引去查询一部分数据集,不存在加载数据表中的全部数据进行分析;</li>
|
||
|
||
<li>采用 Sharded-Nothing 架构;</li>
|
||
|
||
<li>无锁的高并发事务。</li>
|
||
|
||
</ol>
|
||
|
||
<p>根据以上这些特点,我总结为:<strong>一个 NewSQL 数据库是采用创新架构,透明支持 Sharding,具有高并事务的 SQL 关系型数据库</strong>。</p>
|
||
|
||
<blockquote>
|
||
|
||
<p>请注意 DistributedSQL 是一类特殊的 NewSQL,它们可以进行全球部署。</p>
|
||
|
||
</blockquote>
|
||
|
||
<p>下面就按照我给出的定义中的关键点来向你详细介绍 NewSQL 数据库。</p>
|
||
|
||
<h3>创新的架构</h3>
|
||
|
||
<p>使用创新的数据库架构是 NewSQL 数据库非常引人注目的特性。这种新架构一般不会依靠任何遗留的代码,这与我在“22 | 发展与局限:传统数据库在分布式领域的探索”中介绍的依赖传统数据库作为计算存储节点非常不同。我们以 TiDB 这个典型的 NewSQL 数据库为例。</p>
|
||
|
||
<p><img src="assets/Cgp9HWBmw-WATtXZAAHEn31cNYM969.png" alt="image.png" /></p>
|
||
|
||
<p>可以看到其中的创新点有以下几个。</p>
|
||
|
||
<ol>
|
||
|
||
<li>存储引擎没有使用传统数据库。而使用的是新型基于 LSM 的 KV 分布式存储引擎,有些数据库使用了完全内存形式的存储引擎,比如 NuoDB。</li>
|
||
|
||
<li>Sharded-Nothing 架构。底层存储到上层工作负载都是独立部署的。</li>
|
||
|
||
<li>高性能并发事务。TiDB 实现了基于 Percolator 算法的高性能乐观事务。</li>
|
||
|
||
<li>透明分片。TiDB 实现了自动的范围分片,可以弹性地增减节点。</li>
|
||
|
||
<li>基于复制技术的自动容灾。使用 Raft 算法实现高可用的数据复制,自动进行故障转移。</li>
|
||
|
||
</ol>
|
||
|
||
<p>可以说,NewSQL 与传统关系型数据库之间的交集在于 SQL 与 ACID 事务,从而保证用户的使用习惯得以延续。</p>
|
||
|
||
<p>以上描述的创新点我在前两个模块都有详细的说明。这里不知道你是否注意到一个问题:与使用传统数据库构建分布式数据库相比,NewSQL 最为明显的差异来自存储引擎。特别是以 Spanner 为首的一众 NewSQL 数据库,如 YugaByte DB、CockroachDB 和 TiDB 都是使用 LSM 树作为存储引擎,并且都是 KV 结构。如此选择的原理是什么呢?</p>
|
||
|
||
<p>我在介绍 LSM 的时候,提到其可以高性能地写入与读取,但是牺牲了空间。可能你就据此得出结论,NewSQL 数据库面对 OLTP 场景,希望得到吞吐量的提升,故选择 LSM 树存储引擎,而不选择 B 树类的存储引擎。但是,我也介绍过有很多方法可以改进 B 树的吞吐量。所以这一点并不是关键点。</p>
|
||
|
||
<p>我曾经也会困惑于这个问题,经过大量研究,并与项目组人员交流。从而得到了一个“结论”:<strong>开源的 NewSQL 选择的并不是 LSM 树,而是 RocksDB</strong>。选择 RocksDB 难道不是因为它是 LSM 结构的?答案是否定的。大部分以 RocksDB 为存储引擎的开源 NewSQL 数据库看中的是 RocksDB 的性能与功能。可以有一个合理的推论,如果有一款在性能和功能上碾压 RocksDB 的 B 树存储引擎,那么当代开源 NewSQL 数据库的存储引擎版图又会是另一番景象了。</p>
|
||
|
||
<p>你不用诧异,这种发展趋势其实代表了 IT 技术的一种实用特性。从 TCP/IP 协议的普及,到 Java 企业领域 Spring 替代 EJB,都体现了这种实用性。那就是真正胜利的技术是一定有实用价值的,这种实用价值要胜过任何完美的理论。这也启发我们在观察一个分布式数据库时,不要着急给它分类,因为今天我要介绍的评判 NewSQL 的标准,是基于现有数据库特性的总结,并不能代表未来的发展。我们要学会掌握每种数据库核心特性。</p>
|
||
|
||
<p>到这里,我还要提一个比较特别的数据库,那就是 OceanBase。OceanBase 的读写跟传统数据库有很大的一点不同就是:OceanBase 的写并不是直接在数据块上修改,而是新开辟一块增量内存用于存放数据的变化。同一笔记录多次变化后,增量块会以链表形式组织在一起,这些增量修改会一直在内存里不落盘。OceanBase 读则是要把最早读入内存的数据块加上后续相关的增量块内容合并读出。这种特点其实与 LSM 树的内存表和数据表有类似之处,只是 OceanBase 在更高维度上。</p>
|
||
|
||
<p>总结完架构上的创新,下面我将介绍 NewSQL 中关于分片的管理。</p>
|
||
|
||
<h3>透明的 Sharding</h3>
|
||
|
||
<p>我们上文介绍过数据库中间件是如何进行 Sharding 的,那就是使用逻辑的分片键来进行分片计算,将不同的行写入到不同的目标数据库。其中,需要用户深度地参与。比如,需要用户去指定哪个键为分片键,逻辑表与物理表的映射规则,必要的时候还需要进行 SQL 改造,等等。故中间件模式的 Sharding 我们一般称作显示 Sharding,与之相对的就是 NewSQL 数据库提供的透明 Sharding。</p>
|
||
|
||
<p>透明 Sharding 顾名思义,用户不需要去指定任何的规则,数据就能分散到整个集群中,并自动做了备份处理。那么 NewSQL 是怎么确定分片键的呢?我们以 TiDB 为例。</p>
|
||
|
||
<p>一个表中的数据是按照 Region 来进行 Sharding 的。每个 Region 有多个副本,从而保障了数据的高可用。不同的表将会有不同的 Region,而不是如传统分库那样每个库里的表都是相同的。 那么一个表下,每一行数据存储在哪个 Region 下是如何确定的呢?</p>
|
||
|
||
<p>首先要确定的是行是怎么映射到 KV 结构的: key 由 table_id 表 id+rowid 主键组成。如:</p>
|
||
|
||
<pre><code>t[table_id]_r[row_id]
|
||
|
||
</code></pre>
|
||
|
||
<p>而 value 保存的就是一整行的数据。那么我们就使用这个 key 来计算这行数据应该落在哪个 Region 里面。</p>
|
||
|
||
<p>TiDB 是使用范围策略来划分数据。有索引情况下,负责索引的 Region 会根据索引字段范围来划分。基于 key 经过一个转换,将会得出一个数字,然后按范围划分多个区间,每个区间由一个 Region 管理。范围分片的好处我们之前介绍过,就是存储平衡和访问压力的平衡。其原因是,范围分片有机会做更好的动态调度,只有动态了,才能实时自动适应各种动态的场景。</p>
|
||
|
||
<p>虽然透明 Sharding 为用户带来了使用的便利。但聪明的你可以注意到了,这种隐式的分片方案相比上一讲介绍的方案,从功能上讲是存在缺失的。最明显的是它缺少跨分片 Join 的功能。可以想到,此种 NewSQL 与具有 ER 分片的数据库中间件在性能上是存在差异的。针对此种情况,TiDB 引入了 Placement Rules 来制定将相关联的数据表放在同一个 KV 存储中,从而达到 ER 分片的效果。</p>
|
||
|
||
<p>当然,随着 NewSQL 的逐步发展,哈希分片也逐步被引入 NewSQL 中。我们知道范围分片有热点问题,虽然可以通过动态拆分合并分片来缓解,但终究不是从根本上解决该问题。特别是对于具有强顺序的数据,比如时间序列,该问题就会变得很突出。而哈希分片就是应对该问题的有效手段。基于这个原因,Cockroach DB 引入哈希分片索引来实现针对序列化数据的扩展能力。</p>
|
||
|
||
<p>我们小结一下,透明的 Sharding 针对的往往是主键,一般会选择行 ID 作为主键。而如果要实现功能完善的 Sharding,一些用户参与的配置操作还是有必要的。</p>
|
||
|
||
<p>解决了 Sharding 问题,让我们看看 SQL 层面需要解决的问题吧。</p>
|
||
|
||
<h3>分布式的 SQL</h3>
|
||
|
||
<p>NewSQL 数据库相对于 NoSQL 最强大的优势还是对 SQL 的支持。我曾经在模块一中与你讨论过,SQL 是成功的分布式数据库一个必要的功能。SQL 的重要性我们已经讨论过了,但实现 SQL 一直是被认为很困难的,究其原因主要来源于以下两个方面。</p>
|
||
|
||
<ol>
|
||
|
||
<li>SQL 的非标准性。虽然我们有 SQL99 这种事实标准,但是在工业界,没有一种流行的数据库完全使用标准去构建 SQL。每家都有自己的方言、自己独有的特性,故我们看到的 NewSQL 数据库大部分都会按照已经存在的数据库方言去实现 SQL 语义,比如 TiDB 实现了 MySQL 的语法,而 CockroachDB 实现了 PostgreSQL。</li>
|
||
|
||
<li>声明式语言。SQL 语言是一种更高级的语言,比我们熟悉的 Java、Go 等都要高级。主要体现为它表述了希望得到什么结果,而没有指示数据库怎么做。这就引出了所谓的执行计划优化等概念,为实现高效的查询引擎带来了挑战。</li>
|
||
|
||
</ol>
|
||
|
||
<p>以上是传统数据库实现 SQL 的挑战,而对于分布式数据库来说还需要将数据分散带来的问题考虑进去,同时在查询优化方面要将网络延迟作为一个重要因素来考量。</p>
|
||
|
||
<p>对于 NewSQL 而言,如果使用了上面所描述的创新架构,特别是底层使用 KV 存储,那么就需要考虑数据与索引如何映射到底层 KV 上。我在前面已经说明了数据是如何映射到 KV 上的,那就是 key 由 table_id 表 id+rowid 主键组成,value 存放行数据:</p>
|
||
|
||
<pre><code>t[table_id]_r[row_id] => row_data
|
||
|
||
</code></pre>
|
||
|
||
<p>而对于索引,我们就要区分唯一索引和非唯一索引。</p>
|
||
|
||
<p>对于唯一索引,我们将 table_id、index_id 和 index_value 组成 key,value 为上面的 row_id。</p>
|
||
|
||
<pre><code>t[table_id]_i[index_id][index_value] => row_id
|
||
|
||
</code></pre>
|
||
|
||
<p>对于非唯一索引,就需要将 row_id 放到 key 中,而 value 是空的。</p>
|
||
|
||
<pre><code>t[table_id]_i[index_id][index_value]_r[row_id] => null
|
||
|
||
</code></pre>
|
||
|
||
<p>底层映射问题解决后,就需要进入执行层面了。这里面牵扯大量的技术细节,我不会深入其中,而是为你展示一些 NewSQL 数据库会面临的挑战。</p>
|
||
|
||
<p>首先是正确性问题。是的,要实现正确的 SQL 语法本身就是挑战。因为 SQL 可以接受用户自定义表和索引,查询使用名字,而执行使用的是 ID,这之间的映射关系会给正确性带来挑战。而其中最不好处理的就是 Null,不仅仅是 NewSQL,传统数据库在处理 Null 的时候也经过了长时间的“折磨”。这也是为什么一个 SQL 类数据库需要经过长时间的迭代才能达到稳定状态的原因。</p>
|
||
|
||
<p>然后就是性能了。性能问题不仅仅是数据库开发人员,DBA 和最终用户也对它非常关注。如果你经常与数据库打交道,一定对 SQL 优化有一定了解。这背后的原因其实就是 SQL 声明式语言导致的。</p>
|
||
|
||
<p>大部分 NewSQL 数据库都需要实现传统数据库的优化手段,这一般分为两类:基于规则和基于代价。前者是根据 SQL 语义与数据库特点进行的静态分析,也称为逻辑优化,常见有列裁剪、子查询优化为连接、谓词下推、TopN 下推,等等;而基于代价的优化,需要数据库实时产生统计信息,根据这些信息来决定 SQL 是否查询索引、执行的先后顺序,等等。</p>
|
||
|
||
<p>而具有分布式数据库特色的优化包括并行执行、流式聚合,基于代价的优化需要考虑网络因素等。故实现分布式数据库的 SQL 优化要更为复杂。</p>
|
||
|
||
<p>从上文可以看到,NewSQL 的数据库实现 SQL 层所面临的挑战要远远大于传统数据库。但由于 SQL 执行与优化技术经过多年的积累,现在已经形成了完整的体系。故新一代 NewSQL 数据库可以在此基础上去构建,它们站在巨人的肩膀上,实现完整的 SQL 功能将会事半功倍。</p>
|
||
|
||
<p>介绍完了 NewSQL 数据库在 SQL 领域的探索,最后我们来介绍其高性能事务的特点。</p>
|
||
|
||
<h3>高性能事务</h3>
|
||
|
||
<p>NewSQL 的事务是其能够得到广泛应用的关键。在上一个模块中我们讨论的 Spanner 与 Calvin,就是典型的创新事务之争。这一讲我将总结几种 NewSQL 数据库处理事务的常见模式,并结合一定的案例来为你说明。</p>
|
||
|
||
<p>首先的一种分类方式就是集中化事务管理与去中心化事务管理。前者的优势是很容易实现事务的隔离,特别是实现序列化隔离,代价就是其吞吐量往往偏低;而后者适合构建高并发事务,但是需要逻辑时钟来进行授时服务,并保证操作竞争资源的正确性。</p>
|
||
|
||
<p>以上是比较常规的认识,但是我们介绍过 Calvin 事务模型。它其实是一种集中化的事务处理模式,但却在高竞争环境下具有非常明显的吞吐量的优势。其关键就是通过重新调度事务的执行,消除了竞争,从而提高了吞吐量。采用该模式的除了 Calvin 外,还有 VoltDB。其原理和 Calvin 类似,感兴趣的话你可以自行学习。</p>
|
||
|
||
<p>而采用去中心化事务的方案一般需要结合:MVCC、2PC 和 PaxosGroup。将它们结合,可以实现序列化的快照隔离,并保证执行过程中各个组件的高可用。采用 PaxosGroup 除了提供高可用性外,一个重要的功能就是将数据进行分区,从而降低获取锁的竞争,达到提高并发事务效率的目的。这种模式首先被 Spanner 所引入,故我们一般称其为 Spanner 类事务模型。</p>
|
||
|
||
<p>而上一讲我们介绍了京东为 ShardingShpere 打造的 JDTX 又是另一番景象,数据库中间件由于无法操作数据库底层,所以事务方案就被锁死。而 JDTX 采用类似 OceanBase 的模式,首先在数据库节点之外构建了一个独立的 MVCC 引擎,查询最新的数据需要结合数据库节点与 MVCC 引擎中修改的记录,从而获得最新数据。而 MVCC 中的数据会异步落盘,从而保证数据被释放。JDTX 的模式打破了中间件无法实现高性能事务模型的诅咒,为我们打开了思路。</p>
|
||
|
||
<p>可以看到,目前 NewSQL 的并发事务处理技术往往使用多种经过广泛验证的方案。可以将它比喻为当代的航空母舰,虽然每个部件都没有创新点,但是将它们结合起来却实现了前人无法企及的成就。</p>
|
||
|
||
<h3>总结</h3>
|
||
|
||
<p>这一讲,我介绍了 NewSQL 数据库的定义,并为你详细分析了一个 NewSQL 数据库的关键点,即以下四个。</p>
|
||
|
||
<ol>
|
||
|
||
<li>创新的架构:分布式系统与存储引擎都需要创新。</li>
|
||
|
||
<li>透明的 Sharding:自动的控制,解放用户,贴合云原生。</li>
|
||
|
||
<li>分布式 SQL:打造商业可用分布式数据库的关键。</li>
|
||
|
||
<li>高性能事务:NewSQL 创新基地,是区别不同种类 NewSQL 的关键。</li>
|
||
|
||
</ol>
|
||
|
||
<p>至此,我们课程的主要内容就已经全部介绍完了。</p>
|
||
|
||
<p>NewSQL 和具有全球部署能力的 DistributedSQL 是当代分布式数据库的发展方向,可以说我们介绍过的所有知识都是围绕在 NewSQL 体系内的。接下来在加餐环节,我会为你介绍其他种类的分布式数据库,以帮助你拓展思路。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/23 数据库中间件:传统数据库向分布式数据库的过渡.md">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/24讲吃透分布式数据库-完/加餐1 概念解析:云原生、HTAP、图与内存数据库.md">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996e29bbb33d60","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>
|
||
|