learn.lianglianglee.com/专栏/ShardingSphere 核心原理精讲-完/18 路由引擎:如何实现数据访问的分片路由和广播路由?.md.html
2022-05-11 18:57:05 +08:00

1291 lines
39 KiB
HTML
Raw Permalink 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>18 路由引擎:如何实现数据访问的分片路由和广播路由?.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="/专栏/ShardingSphere 核心原理精讲-完/00 如何正确学习一款分库分表开源框架?.md.html">00 如何正确学习一款分库分表开源框架?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/01 从理论到实践:如何让分库分表真正落地?.md.html">01 从理论到实践:如何让分库分表真正落地?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/02 顶级项目ShardingSphere 是一款什么样的 Apache 开源软件?.md.html">02 顶级项目ShardingSphere 是一款什么样的 Apache 开源软件?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/03 规范兼容JDBC 规范与 ShardingSphere 是什么关系?.md.html">03 规范兼容JDBC 规范与 ShardingSphere 是什么关系?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/04 应用集成:在业务系统中使用 ShardingSphere 的方式有哪些?.md.html">04 应用集成:在业务系统中使用 ShardingSphere 的方式有哪些?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/05 配置驱动ShardingSphere 中的配置体系是如何设计的?.md.html">05 配置驱动ShardingSphere 中的配置体系是如何设计的?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/06 数据分片:如何实现分库、分表、分库+分表以及强制路由?(上).md.html">06 数据分片:如何实现分库、分表、分库+分表以及强制路由?(上).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/07 数据分片:如何实现分库、分表、分库+分表以及强制路由?(下).md.html">07 数据分片:如何实现分库、分表、分库+分表以及强制路由?(下).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/08 读写分离:如何集成分库分表+数据库主从架构?.md.html">08 读写分离:如何集成分库分表+数据库主从架构?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/09 分布式事务:如何使用强一致性事务与柔性事务?.md.html">09 分布式事务:如何使用强一致性事务与柔性事务?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/10 数据脱敏:如何确保敏感数据的安全访问?.md.html">10 数据脱敏:如何确保敏感数据的安全访问?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/11 编排治理:如何实现分布式环境下的动态配置管理?.md.html">11 编排治理:如何实现分布式环境下的动态配置管理?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/12 从应用到原理:如何高效阅读 ShardingSphere 源码?.md.html">12 从应用到原理:如何高效阅读 ShardingSphere 源码?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/13 微内核架构ShardingSphere 如何实现系统的扩展性?.md.html">13 微内核架构ShardingSphere 如何实现系统的扩展性?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/14 分布式主键ShardingSphere 中有哪些分布式主键实现方式?.md.html">14 分布式主键ShardingSphere 中有哪些分布式主键实现方式?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/15 解析引擎SQL 解析流程应该包括哪些核心阶段?(上).md.html">15 解析引擎SQL 解析流程应该包括哪些核心阶段?(上).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/16 解析引擎SQL 解析流程应该包括哪些核心阶段?(下).md.html">16 解析引擎SQL 解析流程应该包括哪些核心阶段?(下).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/17 路由引擎:如何理解分片路由核心类 ShardingRouter 的运作机制?.md.html">17 路由引擎:如何理解分片路由核心类 ShardingRouter 的运作机制?.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/ShardingSphere 核心原理精讲-完/18 路由引擎:如何实现数据访问的分片路由和广播路由?.md.html">18 路由引擎:如何实现数据访问的分片路由和广播路由?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/19 路由引擎:如何在路由过程中集成多种路由策略和路由算法?.md.html">19 路由引擎:如何在路由过程中集成多种路由策略和路由算法?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/20 改写引擎:如何理解装饰器模式下的 SQL 改写实现机制?.md.html">20 改写引擎:如何理解装饰器模式下的 SQL 改写实现机制?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/21 执行引擎:分片环境下 SQL 执行的整体流程应该如何进行抽象?.md.html">21 执行引擎:分片环境下 SQL 执行的整体流程应该如何进行抽象?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/22 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(上).md.html">22 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(上).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/23 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(下).md.html">23 执行引擎:如何把握 ShardingSphere 中的 Executor 执行模型?(下).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/24 归并引擎:如何理解数据归并的类型以及简单归并策略的实现过程?.md.html">24 归并引擎:如何理解数据归并的类型以及简单归并策略的实现过程?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/25 归并引擎:如何理解流式归并和内存归并在复杂归并场景下的应用方式?.md.html">25 归并引擎:如何理解流式归并和内存归并在复杂归并场景下的应用方式?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/26 读写分离:普通主从架构和分片主从架构分别是如何实现的?.md.html">26 读写分离:普通主从架构和分片主从架构分别是如何实现的?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/27 分布式事务:如何理解 ShardingSphere 中对分布式事务的抽象过程?.md.html">27 分布式事务:如何理解 ShardingSphere 中对分布式事务的抽象过程?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/28 分布式事务ShardingSphere 中如何集成强一致性事务和柔性事务支持?(上).md.html">28 分布式事务ShardingSphere 中如何集成强一致性事务和柔性事务支持?(上).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/29 分布式事务ShardingSphere 中如何集成强一致性事务和柔性事务支持?(下).md.html">29 分布式事务ShardingSphere 中如何集成强一致性事务和柔性事务支持?(下).md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/30 数据脱敏:如何基于改写引擎实现低侵入性数据脱敏方案?.md.html">30 数据脱敏:如何基于改写引擎实现低侵入性数据脱敏方案?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/31 配置中心:如何基于配置中心实现配置信息的动态化管理?.md.html">31 配置中心:如何基于配置中心实现配置信息的动态化管理?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/32 注册中心:如何基于注册中心实现数据库访问熔断机制?.md.html">32 注册中心:如何基于注册中心实现数据库访问熔断机制?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/33 链路跟踪:如何基于 Hook 机制以及 OpenTracing 协议实现数据访问链路跟踪?.md.html">33 链路跟踪:如何基于 Hook 机制以及 OpenTracing 协议实现数据访问链路跟踪?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/34 系统集成:如何完成 ShardingSphere 内核与 Spring+SpringBoot 的无缝整合?.md.html">34 系统集成:如何完成 ShardingSphere 内核与 Spring+SpringBoot 的无缝整合?.md.html</a>
</li>
<li>
<a href="/专栏/ShardingSphere 核心原理精讲-完/35 结语ShardingSphere 总结及展望.md.html">35 结语ShardingSphere 总结及展望.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>18 路由引擎:如何实现数据访问的分片路由和广播路由?</h1>
<p>在上一课时中,我们看到起到承上启下作用的 ShardingRouter 会调用 RoutingEngine 获取路由结果,而在 ShardingSphere 中存在多种不同类型的 RoutingEngine分别针对不同的应用场景。</p>
<p>我们可以按照<strong>是否携带分片键信息</strong>将这些路由方式分成两大类,即分片路由和广播路由,而这两类路由中又存在一些常见的 RoutingEngine 实现类型,如下图所示:</p>
<p><img src="assets/Ciqc1F81FdqANHr4AACO1I-IihE703.png" alt="image" /></p>
<p>我们无意对所有这些 RoutingEngine 进行详细 的 展开,但在接下来的内容中,我们会分别对分片路由和广播路由中具有代表性的 RoutingEngine 进行讨论。</p>
<h3>分片路由</h3>
<p>对于分片路由而言,我们将重点介绍<strong>标准路由</strong>,标准路由是 ShardingSphere 推荐使用的分片方式。</p>
<p>在使用过程中,我们需要首先考虑标准路由的适用范围。标准路由适用范围有两大场景:一种面向不包含关联查询的 SQL另一种则适用于仅包含绑定表关联查询的 SQL。前面一种场景比较好理解而针对后者我们就需要引入绑定表这个 ShardingSphere 中的重要概念。</p>
<p>关于绑定表,我们已经在 [《06 | 数据分片:如何实现分库、分表、分库+分表以及强制路由(上)?》]中进行了讨论,在明确了这些概念之后,我们来看标准路由的具体实现过程。</p>
<h4>1.StandardRoutingEngine 的创建过程</h4>
<p>明确了标准路由的基本含义之后,我们回顾一下上一课时中介绍的工厂类 RoutingEngineFactory。RoutingEngineFactory 类根据上下文中的路由信息构建对应的 RoutingEngine但在其 newInstance 方法中我们并没有发现直接创建StandardRoutingEngine 的代码。事实上StandardRoutingEngine 的创建是在 newInstance 方法中的最后一个代码分支,即当所有前置的判断都不成立时会进入到最后的 getShardingRoutingEngine 代码分支中,如下所示:</p>
<pre><code>private static RoutingEngine getShardingRoutingEngine(final ShardingRule shardingRule, final SQLStatementContext sqlStatementContext,
final ShardingConditions shardingConditions, final Collection&lt;String&gt; tableNames) {
//根据分片规则获取分片表
Collection&lt;String&gt; shardingTableNames = shardingRule.getShardingLogicTableNames(tableNames);
//如果目标表只要一张或者说目标表都是绑定表关系则构建StandardRoutingEngine
if (1 == shardingTableNames.size() || shardingRule.isAllBindingTables(shardingTableNames)) {
return new StandardRoutingEngine(shardingRule, shardingTableNames.iterator().next(), sqlStatementContext, shardingConditions);
}
//否则构建ComplexRoutingEngine
return new ComplexRoutingEngine(shardingRule, tableNames, sqlStatementContext, shardingConditions);
}
</code></pre>
<p>这段代码首先根据解析出来的逻辑表获取分片表,以如下所示的 SQL 语句为例:</p>
<pre><code>SELECT record.remark_name FROM health_record record JOIN health_task task ON record.record_id=task.record_id WHERE record.record_id = 1
</code></pre>
<p>那么 shardingTableNames 应该为 health_record 和 health_task。如果分片操作只涉及一张表或者涉及多张表但这些表是互为绑定表的关系时则使用 StandardRoutingEngine 进行路由。</p>
<p>基于绑定表的概念,当多表互为绑定表关系时,每张表的路由结果是相同的,所以只要计算第一张表的分片即可;反之,如果不满足这一条件,则构建一个 ComplexRoutingEngine 进行路由。</p>
<p>这里我们来看一下代码中的 isAllBindingTables 方法如何对多表互为绑定表关系进行判定,该方法位于 ShardingRule 中,如下所示:</p>
<pre><code>public boolean isAllBindingTables(final Collection&lt;String&gt; logicTableNames) {
if (logicTableNames.isEmpty()) {
return false;
}
//通过传入的logicTableNames构建一个专门的BindingTableRule
Optional&lt;BindingTableRule&gt; bindingTableRule = findBindingTableRule(logicTableNames);
if (!bindingTableRule.isPresent()) {
return false;
}
Collection&lt;String&gt; result = new TreeSet&lt;&gt;(String.CASE_INSENSITIVE_ORDER);
//获取BindingTableRule中的LogicTable
result.addAll(bindingTableRule.get().getAllLogicTables());
//判断获取的LogicTable是否与传入的logicTableNames一致
return !result.isEmpty() &amp;&amp; result.containsAll(logicTableNames);
}
</code></pre>
<p>这段代码会通过传入的 logicTableNames 构建一个专门的 BindingTableRule然后看最终获取的 BindingTableRule 中的 LogicTable 是否与传入的 logicTableNames 一致。这里构建 BindingTableRule 的过程实际上是根据传入的 logicTableName 来从 ShardingRule 中自身保存的 Collection<code>&lt;BindingTableRule&gt;</code> 获取对应的 BindingTableRule如下所示</p>
<pre><code>public Optional&lt;BindingTableRule&gt; findBindingTableRule(final String logicTableName) {
for (BindingTableRule each : bindingTableRules) {
if (each.hasLogicTable(logicTableName)) {
return Optional.of(each);
}
}
return Optional.absent();
}
</code></pre>
<p>上述代码的 bindingTableRules 就是 ShardingRule 中自身保存的 BindingTableRule 集合,我们在 ShardingRule 构造函数中发现了初始化 bindingTableRules 的代码,如下所示:</p>
<pre><code>bindingTableRules = createBindingTableRules(shardingRuleConfig.getBindingTableGroups());
</code></pre>
<p>显然,这个构建过程与规则配置机制有关。如果基于 Yaml 配置文件,绑定表的配置一般会采用如下形式:</p>
<pre><code>shardingRule:
bindingTables:
health_record,health_task
</code></pre>
<p>针对这种配置形式ShardingRule 会对其进行解析并生成 BindingTableRule 对象,如下所示:</p>
<pre><code>private BindingTableRule createBindingTableRule(final String bindingTableGroup) {
List&lt;TableRule&gt; tableRules = new LinkedList&lt;&gt;();
for (String each : Splitter.on(&quot;,&quot;).trimResults().splitToList(bindingTableGroup)) {
tableRules.add(getTableRule(each));
}
return new BindingTableRule(tableRules);
}
</code></pre>
<p>至此,我们终于把绑定表相关的概念以及实现方式做了介绍,也就是说完成了 RoutingEngineFactory 中进入到 StandardRoutingEngine 这条代码分支的介绍。</p>
<h4>2.StandardRoutingEngine 的运行机制</h4>
<p>现在,我们已经创建了 StandardRoutingEngine接下来就看它的运行机制。作为一种具体的路由引擎实现方案StandardRoutingEngine 实现了 RoutingEngine 接口,它的 route 方法如下所示:</p>
<pre><code>@Override
public RoutingResult route() {
return generateRoutingResult(getDataNodes(shardingRule.getTableRule(logicTableName)));
}
</code></pre>
<p>这里的核心方法就是 generateRoutingResult在此之前需要先通过 getDataNodes 方法来获取数据节点信息,该方法如下所示:</p>
<pre><code>private Collection&lt;DataNode&gt; getDataNodes(final TableRule tableRule) {
//如基于Hint进行路由
if (isRoutingByHint(tableRule)) {
return routeByHint(tableRule);
}
//基于分片条件进行路由
if (isRoutingByShardingConditions(tableRule)) {
return routeByShardingConditions(tableRule);
}
//执行混合路由
return routeByMixedConditions(tableRule);
}
</code></pre>
<p>我们看到这个方法的入参是一个 TableRule 对象,而 TableRule 属于分片规则 ShardingRule 中的一部分。我们在上一课时中知道该对象主要保存着与分片相关的各种规则信息,其中就包括 ShardingStrategy。从命名上看ShardingStrategy 属于一种分片策略,用于指定分片的具体 Column以及执行分片并返回目标 DataSource 和 Table。</p>
<p>这部分内容我们会在下一课时中进行展开。这里,我们先梳理与 ShardingStrategy 相关的类结构,如下所示:</p>
<p>![image(assets/CgqCHl81FfKAYWCOAACN0o0OVu8479.png)</p>
<p>在 StandardRoutingEngine 中,整体结构也与上图类似。在 StandardRoutingEngine 中,前面所介绍的 getDataNodes 方法的第一个判断分支 isRoutingByHint 方法中会判断是否根据 Hint 来进行路由,其判断依据是它的 DatabaseShardingStrategy 和 TableShardingStrategy 是否都为 HintShardingStrategy这个方法就用到了 ShardingRule 的这两个ShardingStrategy 对象,如下所示:</p>
<pre><code>private boolean isRoutingByHint(final TableRule tableRule) {
return shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy &amp;&amp; shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy;
}
</code></pre>
<p>在 ShardingSphere 中Hint 代表的是一种强制路由的方法,是一条流程的支线。然后,我们再看 getDataNodes 方法中的 isRoutingByShardingConditions 判断。想要判断是否根据分片条件进行路由,其逻辑在于 DatabaseShardingStrategy 和 TableShardingStrategy 都不是 HintShardingStrategy 时就走这个代码分支。而最终如果 isRoutingByHint 和 isRoutingByShardingConditions 都不满足也就是说DatabaseShardingStrategy 或 TableShardingStrategy 中任意一个是 HintShardingStrategy则执行 routeByMixedConditions 这一混合的路由方式。</p>
<p>以上三条代码分支虽然处理方式有所不同,但<strong>本质上都是获取 RouteValue 的集合</strong>,我们在上一课时中介绍路由条件 ShardingCondition 时知道RouteValue 保存的就是用于路由的表名和列名。在获取了所需的 RouteValue 之后,在 StandardRoutingEngine 中,以上三种场景最终都会调用 route0 基础方法进行路由,该方法的作用就是根据这些 RouteValue 得出目标 DataNode 的集合。同样,我们也知道 DataNode 中保存的就是具体的目标节点,包括 dataSourceName和tableName。route0 方法如下所示:</p>
<pre><code>private Collection&lt;DataNode&gt; route0(final TableRule tableRule, final List&lt;RouteValue&gt; databaseShardingValues, final List&lt;RouteValue&gt; tableShardingValues) {
//路由DataSource
Collection&lt;String&gt; routedDataSources = routeDataSources(tableRule, databaseShardingValues);
Collection&lt;DataNode&gt; result = new LinkedList&lt;&gt;();
//路由Table并完成DataNode集合的拼装
for (String each : routedDataSources) {
result.addAll(routeTables(tableRule, each, tableShardingValues));
}
return result;
}
</code></pre>
<p>可以看到,该方法首先路由 DataSource然后再根据每个 DataSource 路由 Table最终完成 DataNode 集合的拼装。在上述 routeDataSources 和 routeTables 方法中,最终都会分别依赖 DatabaseShardingStrategy 和 TableShardingStrategy 完成背后的路由计算以获取目标 DataSource 以及 Table。</p>
<p>当获取了 DataNode 集合之后,我们回到 StandardRoutingEngine 的 generateRoutingResult 方法,该方法用于组装路由结果并返回一个 RoutingResult</p>
<pre><code>private RoutingResult generateRoutingResult(final Collection&lt;DataNode&gt; routedDataNodes) {
RoutingResult result = new RoutingResult();
for (DataNode each : routedDataNodes) {
//根据每个DataNode构建一个RoutingUnit对象
RoutingUnit routingUnit = new RoutingUnit(each.getDataSourceName());
//填充RoutingUnit中的TableUnit
routingUnit.getTableUnits().add(new TableUnit(logicTableName, each.getTableName()));
result.getRoutingUnits().add(routingUnit);
}
return result;
}
</code></pre>
<p>这部分代码的作用就是根据每个 DataNode 构建一个 RoutingUnit 对象,然后再填充 RoutingUnit 中的 TableUnit。关于 RoutingUnit 和 TableUnit 的数据结构我们在上一课时中已经进行了介绍,这里不再展开。</p>
<p>至此,对标准路由引擎 StandardRoutingEngine 的介绍就告一段落,标准路由是 ShardingSphere 最为推荐使用的分片方式,在日常开发中应用也最广泛。</p>
<h3>广播路由</h3>
<p>对于不携带分片键的 SQL路由引擎会采取广播路由的方式。在 ShardingSphere根据输入 SQL 的类型,存在很多种用于广播的路由引擎,我们同样可以回顾 RoutingEngineFactory 中创建 RoutingEngine的 方法。</p>
<p>首先,如果输入的是 TCLStatement即授权、角色控制等数据库控制语言那么直接执行 DatabaseBroadcastRoutingEngine同样如果执行的是用于数据定义的 DDLStatement则执行 TableBroadcastRoutingEngine 中的路由方法,判断条件如下所示:</p>
<pre><code>//全库路由
if (sqlStatement instanceof TCLStatement) {
return new DatabaseBroadcastRoutingEngine(shardingRule);
}
//全库表路由
if (sqlStatement instanceof DDLStatement) {
return new TableBroadcastRoutingEngine(shardingRule, metaData.getTables(), sqlStatementContext);
}
</code></pre>
<p>DatabaseBroadcastRoutingEngine 的路由方法非常直接,即基于每个 DataSourceName 构建一个 RoutingUnit然后再拼装成 RoutingResult如下所示</p>
<pre><code>public final class DatabaseBroadcastRoutingEngine implements RoutingEngine {
private final ShardingRule shardingRule;
@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
for (String each : shardingRule.getShardingDataSourceNames().getDataSourceNames()) {
//基于每个DataSourceName构建一个RoutingUnit
result.getRoutingUnits().add(new RoutingUnit(each));
}
return result;
}
}
</code></pre>
<p>同样也可以想象 TableBroadcastRoutingEngine 的实现过程,我们根据 logicTableName 获取对应的 TableRule然后根据 TableRule 中的真实 DataNode 构建 RoutingUnit 对象,这一过程如下所示:</p>
<pre><code>private Collection&lt;RoutingUnit&gt; getAllRoutingUnits(final String logicTableName) {
Collection&lt;RoutingUnit&gt; result = new LinkedList&lt;&gt;();
//根据logicTableName获取对应的TableRule
TableRule tableRule = shardingRule.getTableRule(logicTableName);
for (DataNode each : tableRule.getActualDataNodes()) {
//根据TableRule中的真实DataNode构建RoutingUnit对象
RoutingUnit routingUnit = new RoutingUnit(each.getDataSourceName());
//根据DataNode的TableName构建TableUnit
routingUnit.getTableUnits().add(new TableUnit(logicTableName, each.getTableName()));
result.add(routingUnit);
}
return result;
}
</code></pre>
<p>接着我们来看针对 DALStatement 的场景,这一场景相对复杂,根据输入的 DALStatement 的不同类型,会有几个不同的处理分支,如下所示:</p>
<pre><code>private static RoutingEngine getDALRoutingEngine(final ShardingRule shardingRule, final SQLStatement sqlStatement, final Collection&lt;String&gt; tableNames) {
//如果是Use语句则什么也不做
if (sqlStatement instanceof UseStatement) {
return new IgnoreRoutingEngine();
}
//如果是Set或ResetParameter语句则进行全数据库广播
if (sqlStatement instanceof SetStatement || sqlStatement instanceof ResetParameterStatement || sqlStatement instanceof ShowDatabasesStatement) {
return new DatabaseBroadcastRoutingEngine(shardingRule);
}
//如果存在默认数据库,则执行默认数据库路由
if (!tableNames.isEmpty() &amp;&amp; !shardingRule.tableRuleExists(tableNames) &amp;&amp; shardingRule.hasDefaultDataSourceName()) {
return new DefaultDatabaseRoutingEngine(shardingRule, tableNames);
}
//如果表列表不为空,则执行单播路由
if (!tableNames.isEmpty()) {
return new UnicastRoutingEngine(shardingRule, tableNames);
}
//
return new DataSourceGroupBroadcastRoutingEngine(shardingRule);
}
</code></pre>
<p>我们分别来看一下这里面的几个路由引擎。首先是最简单的 IgnoreRoutingEngine它只返回一个空的 RoutingResult 对象,其他什么都不做,如下所示:</p>
<pre><code>public final class IgnoreRoutingEngine implements RoutingEngine {
@Override
public RoutingResult route() {
return new RoutingResult();
}
}
</code></pre>
<p>本质上UnicastRoutingEngine 代表单播路由,用于获取某一真实表信息的场景,它只需要从任意库中的任意真实表中获取数据即可。例如 DESCRIBE 语句就适合使用 UnicastRoutingEngine因为每个真实表中的数据描述结构都是相同的。</p>
<p>UnicastRoutingEngine 实现过程如下所示,由于方法比较长,我们裁剪了代码,直接使用注释来标明每个分支的执行逻辑:</p>
<pre><code>@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
if (shardingRule.isAllBroadcastTables(logicTables)) {
//如果都是广播表则对每个logicTable组装TableUnit再构建RoutingUnit
} else if (logicTables.isEmpty()) {
//如果表为null则直接组装RoutingUnit不用构建TableUnit
} else if (1 == logicTables.size()) {
//如果只有一张表则组装RoutingUnit和单个表的TableUnit
} else {
//如果存在多个实体表则先获取DataSource再组装RoutingUnit和TableUnit
}
return result;
}
</code></pre>
<p>DefaultDatabaseRoutingEngine顾名思义是对默认的数据库执行路由。那么这个默认数据库是怎么来的呢我们可以从 ShardingRule的ShardingDataSourceNames 类中的 getDefaultDataSourceName 方法中找到答案。</p>
<p>一般这种默认数据库可以通过配置的方式进行设置。明白这一点DefaultDatabaseRoutingEngine 的路由过程也就不难理解了,其 route 方法如下所示:</p>
<pre><code>@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
List&lt;TableUnit&gt; routingTables = new ArrayList&lt;&gt;(logicTables.size());
for (String each : logicTables) {
routingTables.add(new TableUnit(each, each));
}
//从ShardingRule中获取默认所配置的数据库名
RoutingUnit routingUnit = new RoutingUnit(shardingRule.getShardingDataSourceNames().getDefaultDataSourceName());
routingUnit.getTableUnits().addAll(routingTables);
result.getRoutingUnits().add(routingUnit);
return result;
}
</code></pre>
<p>最后,我们来看一下针对数据控制语言 DCLStatement 的处理流程。在主从环境下,对于 DCLStatement 而言,有时候我们希望 SQL 语句只针对主数据库进行执行,所以就有了如下所示的 MasterInstanceBroadcastRoutingEngine</p>
<pre><code>@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
for (String each : shardingRule.getShardingDataSourceNames().getDataSourceNames()) {
if (dataSourceMetas.getAllInstanceDataSourceNames().contains(each)) {
//通过MasterSlaveRule获取主从数据库信息
Optional&lt;MasterSlaveRule&gt; masterSlaveRule = shardingRule.findMasterSlaveRule(each);
if (!masterSlaveRule.isPresent() || masterSlaveRule.get().getMasterDataSourceName().equals(each)) {
result.getRoutingUnits().add(new RoutingUnit(each));
}
}
}
return result;
}
</code></pre>
<p>可以看到,这里引入了一个 MasterSlaveRule 规则,该规则提供 getMasterDataSourceName 方法以获取主 DataSourceName这样我们就可以针对这个主数据执行如 Grant 等数据控制语言。</p>
<h3>从源码解析到日常开发</h3>
<p>在 ShardingSphere 中,我们还是有必要再次强调其在配置信息管理上的一些设计和实践。基于 ShardingRule 和 TableRule 这两个配置类ShardingSphere 把大量纷繁复杂的配置信息从业务流程中进行隔离,而这些配置信息往往需要灵活进行设置,以及多种默认配置值。基于 ShardingRule 和 TableRule 的两层配置体系,系统能够更好地完成业务逻辑的变化和配置信息变化之间的有效整合,值得我们在日常开发过程中进行尝试和应用。</p>
<h3>小结与预告</h3>
<p>今天我们关注的是 ShardingSphere 中各种路由引擎的实现过程ShardingSphere 中实现了多款不同的路由引擎,可以分为分片路由和广播路由两大类。我们针对这两类路由引擎中的代表性实现方案分别展开了讨论。</p>
<p><strong>这里给你留一道思考题ShardingSphere 中如何判断两张表是互为绑定表关系?</strong> 欢迎你在留言区与大家讨论,我将一一点评解答。</p>
<p>从今天的内容中,我们也看到了路由引擎中路由机制的实现需要依赖于分片策略及其背后分片算法的集成,下一课时将对 ShardingSphere 中的各种分片策略进行具体的展开。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/ShardingSphere 核心原理精讲-完/17 路由引擎:如何理解分片路由核心类 ShardingRouter 的运作机制?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/ShardingSphere 核心原理精讲-完/19 路由引擎:如何在路由过程中集成多种路由策略和路由算法?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709974c5f99b3d60","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>