-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ShardingSelector: 增强的 ShardingAlgorithm 设计与实现 #157
Comments
我要额外补充一个,即我提到的分集群分库分表。如果进一步考虑,我们其实能够设想到,在规模庞大的互联网公司里面,一般还伴随着 zone 、机房之类的概念。 zone 我已经说过了,本质上不同 zone 就是不同机房,然后再加上一些隔离和防火墙之类的东西,然后根据各个国家的监管搞一些访问规则控制。但是本质上就是不同的机房。 那么也就是说,在分集群的基础上可能还有更加宽泛的概念,比如说分机房。也就是进一步区分北京机房或者上海机房这种。比如说某个业务规则说,如果我 ctx 里面带上了一个地理区域信息,那么你需要根据地理区域信息,判断我这个数据要丢过去哪个机房。 但是这种也可以通过扩展接口实现来达成目标。从这里进一步引申出来的,就是所谓的数据库异地多活,比如说在上海有一个机房,而后在北京有一个机房,北京机房只是作为上海机房的备份,在上海机房崩溃之后,业务方可能希望分库分表中间件能够自动顺滑地切换到北京机房,那么同样也可以通过扩展我们的接口来达成类似的目标。不过这可能需要考虑更加多的问题,比如说 Algorithm 的这个异地容灾实现里面,它需要解决探活之类的问题。 但是正如我所说的,这一类需求,如果某个公司有,那么就证明他们的业务很复杂,体量非常大,也就是说他们有非常多优秀的程序员,那么他们应该自己开发分库分表中间件,而不是用我们这种开源的。毕竟我是从来不会考虑这种超大规模厂的独特需求的——你考虑了也没有用,里面的人要刷 KPI 是能找出一千个一万个理由来自研。 |
这里的 Broadcast(ctx context.Context) 是否是按用户指定的分库分表,还是根据算法推导所有的分库分表,如果是自定义的话,是需要一个保存所有库名和一个保存所有表名的容器,但是看设计,这个设计是倾向于通过算法推导的 |
如果要在接口外边控制只分库或者只分表,仅当前的接口设计可能不够又或者说通过 ctx 传递标记位控制 |
又或者说,在可以直接通过装饰器由使用者自定义该能力 |
// 分库分表 会出现只分集群分库,或者分集群分表的 情况吗?个人认为不太可能,因为都集群了,那肯定意味着分库分表了 |
<!--
/* Font Definitions */
@font-face
{font-family:SimSun;
panose-1:2 1 6 0 3 1 1 1 1 1;}
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
{font-family:DengXian;
panose-1:2 1 6 0 3 1 1 1 1 1;}
@font-face
{font-family:DengXian;
panose-1:2 1 6 0 3 1 1 1 1 1;}
@font-face
{font-family:SimSun;
panose-1:2 1 6 0 3 1 1 1 1 1;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0cm;
text-align:justify;
text-justify:inter-ideograph;
font-size:10.5pt;
font-family:DengXian;}
a:link, span.MsoHyperlink
{mso-style-priority:99;
color:blue;
text-decoration:underline;}
.MsoChpDefault
{mso-style-type:export-only;}
/* Page Definitions */
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 72.0pt 72.0pt 72.0pt;}
div.WordSection1
{page:WordSection1;}
-->但是这个地方不影响你的主体设计,因为具体用户是要分集群,还是分库分表,都和我们的核心逻辑没有任何的关系 从 Windows 版邮件发送 发件人: Stone-afk发送时间: 2023年3月2日 20:04收件人: ecodeclub/eorm抄送: Ming Deng; Author主题: Re: [ecodeclub/eorm] ShardingSelector: 增强的 ShardingAlgorithm 设计与实现 (Issue #157) // 分库分表// 只分库// 只分表// 分集群分库// 分集群分表// 分集群分库分表会出现只分集群分库,或者分集群分表的 情况吗?个人认为不太可能,因为都集群了,那肯定意味着分库分表了—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
仅限中文
使用场景
目前在合并请求 #145 里面我们初步解决了最简单的分库分表场景,即只考虑等值查询的条件,该如何生成SQL。
现在我们需要进一步强化 “分库分表” 规则这么一个概念。对于一个分库分表规则来说,它需要解决:
进一步说,如果在提供了分库分表键的情况下我们只能确定库,那么分库分表规则能够返回该库下面的所有的命中的表,也就是第一种情况的一个特例而已。
基本分库分表
但是进一步思考,我们会发现这个 ShardingAlgorithm 不是很好设计。因为用户需要的各种分库分表规则简直千变万化:
复合分库分表规则
很多时候,分库分表并不是只使用我们前面提到的那些基本分库分表的做法,而是可能涉及到多个组合在一起。
最典型的就是在压测的情况下,同时业务数据本身就是分库分表的。举个例子,假如说现在我们的生产库 user_db 是按照 user_id / 32 % 32 来分库,按照 user_id %32 来进行分表,那么对于生产库,我们可以用表达式写成:
与之对应的影子库影子表则是:
那么两个合并在一起则是:
这里面有一个假设,就是影子库和影子表的分库分表规则和线上库的是一模一样的。但是有些时候有些公司不按套路来出牌,那么影子库和影子表的分库分表规则就是不同的,例如说生产库按照 32 来划分,而影子库影子表因为数据比较少,采用了 4 来划分,那么就变成了:
这个问题类似于后面我们提到的 zone 问题。这是一个很典型的场景:
某一个分库分表的键取值,会影响其他分库分表键对应的分库分表逻辑
。zone 问题
所谓 zone,简单直白的解释就是不同的机房,不同 zone 之间可能允许通信,也可能不允许通信。比如说一个国际大厂,它的 zone 可能分成美国 zone,东南亚 zone,中国 zone。当然这只是一个简单的例子,具体 zone 怎么划分都是各个公司根据自己的业务和合规情况来划分的。
那么很多时候业务方都会要求分库分表解决 zone 的问题,比较棘手的就是不同 zone 内部分库分表的规则又不同,比如说中国大陆人口众多,那么按照 32 来分是合理的,而台湾作为一个省,屁大点人口,可能只需要按照 4 来分,于是我们就有两个:
一般来说,zone 的划分不太会影响分表。
那么也可以看出来,它本质上和影子库影子表面临的问题是一样的。
表达式问题
前面我已经多次提到了分库分表的一种形式表达,比如说在影子库影子表里面使用的:
那么这里就会有一个问题,我们的框架要不要支持这种表达式?以及如果支持的话,这种表达式应该如何定位,是应该认定为是一个框架必须要支持的核心功能,还是应该要认定为它只是组织分库分表表达式的一种形态?
那么很显然,既然我会问这个问题,而且根据我一贯的设计风格,那么我的答案是:
表达式确实只应该作为一个扩展功能
。也就是从设计上来说,即便分库分表不支持任何的表达式,那么用户依旧可以通过编程接口来指定自己的分库分表规则,类似于:
那么为什么现在普遍分库分表都有类似的表达式呢?很简单,就是一个历史惯性而已。最开始的分库分表都倾向于为了减轻用户的接入难度。然而就我的观察来说,一些人很难学会怎么写这些表达式,比如说我在某处接到最多的问题就是这个分库分表该怎么写。
因此我认为表达式整体上不如编程接口。或者说,在提供了编程接口之后,我们没有十分强烈的动机去提供这么一种表达式解析的支持。
未来我们可以考虑支持。
分库分表对 DSN 的影响
注意,这里我们讨论的是 DSN(data source name),而不是 DNS。这里我依旧采用前面的这种表达式形式来阐述这个问题。
前面我举例的都是只影响到了 DB 名字和表名。那么从实际情况上来看,事情还要复杂一点。
比如说,这种分库分表规则:
那么显然我们的分库分表影响到了数据库的连接信息。
实际上,如果从最宽泛的角度谈,那么分库分表本身会影响 DSN 的任何一个部分。也就是说,包含端口、参数部分。
大多数情况下,一家公司内部如果使用分库分表的话,端口大多数时候都是同一个,不管你是分库还是读写分离的从库,都是使用一个固定的端口,比如说 3306,或者出于安全的考虑,大家都用另外一个,例如 4406 等。
分库分表对主从分离的影响
一般来说,如果一个公司准备采用分库分表的解决方案了,那么基本上可以认为这家公司肯定用了读写分离,也就是说数据库大概率都是一个主从集群。
所以我们在设计分库分表的时候要考虑到这个情况:
实际上,公司可能有多个从库藏在这个从库的 DSN 背后,但是公司也可能一个从库给一个 DSN:
大体上我们认为,在分库分表的时候确定的应该是一个主从集群,至于这个主从集群内部究竟是怎么搞的,分库分表算法一点都不关心。
可行方案
我们的解决方案有一个非常核心的原则:
分库分表中间件只知道接口,而不知道任何细节
。形象点说,就是我把具体的分库分表算法,比如说哈希分库分表、范围分库分表或者复合分库分表算法从整个框架里面挪走,我的分库分表依旧能够正常运作。这意味着:
实际上,因为常规来说我们一直说的都是分库分表,但是从前面的场景分析,我们应该能够看出来,严格说法应该是分集群分库分表。在这里我将 zone 之类的概念看做是一个是由集群衍生出来的业务规则上的概念,作为分库分表中间件,实际上没有什么zone,region 之类的概念。只有在特定的分集群分库分表规则实现里面会有
核心接口
核心接口只需要一个:
注意:
那么用伪代码来描述使用起来的效果则是:
用文字来简短描述则是:
统一的 Datasource 抽象
我计划支持一个统一的 Datasource 抽象。它会有以下实现:
目前在第一期里面,这个不需要支持,后续再支持,我会额外创建 issue。
AND OR NOT
在引用了这个抽象之后,对 AND、OR、NOT 的支持是要发生变更的,但是我们保持已有的逻辑不变,只是在取并集或者合集的时候,将 Dst.Name 纳入考虑。即只有 Name, DB, Table 三者相等我们才认为是完全相同的。
但是在已有逻辑实现中,我们会碰到巨大的性能问题,即我们会频繁调用 Sharding 方法,引入额外的内存分配,这是我们后续要考虑优化的地方。
哈希实现
这里我们讨论一个简单的实现,即数据源相同的,或者说返回的 Dst 里面的 Name 都是同一个的情况。那么一个哈希实现我们很容易设计出来:
不过现实中一般哈希分库分表都不会那么简单粗暴,比如说它们可能用时前面的那种 DB/32%32 这种,那么简单修改这个 Hash 就可以提供一个新的实现。
基于范围的分库分表实现也是类似。但是基于范围的分库分表有一个地方比较恶心。比如说分表是按照日,分库是按照月,分集群是按照年。那么就意味着在转年的时候我们必须要有办法初始化一个新的数据源。
复合分库分表
我们可以简单写出来一个混合了影子库和哈希分库分表的:
如果采用了影子表,或者影子集群,或者甚至于影子库和影子表的分库分表规则都不同,那么就做类似的修改
基于表达式的实现
我们可以考虑提供类似于其它框架支持的表达式的分库分表算法实现。
那么问题其实就剩下了表达式解析与字符串替换的问题了。比如说:
那么也就是要把表达式 $(user_id/32%32) 提取出来,而且要知道知道 user_id 的值要从 SkValues 中拿到,然后再执行 /32%32。
后续考虑支持,第一期不支持。
测试
单元测试
单元测试必须覆盖以下所有的场景:
以上所有的测试用例要进一步考虑 AND,OR,NOT 的效果
其它
多种分库分表规则
在一些特定情况下,用户对一张表都有多套分库分表,这种我们暂时不打算支持。我总体上认为是业务层面上设计不合理,所以中间件犯不着支持,毕竟我们是开源又不是公司内部项目,老板说支持就必须支持。
而且大多数情况下,用户可以通过组合 Algorithm 来达成类似的目标。
其它语句
正如我之前说过的,我们并不打算解决所有的问题。我们这里主要集中解决增删改查,而且是对业务数据的增删改查。
那么一些特别的语句,比如说 CREATE USER 之类的。当然并不是说不能执行,而是说犯不着特意在分库分表内核为它们留下接口。
只是说随缘,如果不需要额外的努力也恰好可以支持,那就支持;如果需要额外的努力,那么我们就不支持。
审慎思考核心与非核心
前面梳理了不同的分库分表场景之后,我要强烈批评两个错误设计:
就第一个问题来说,我认为很多分库分表中间件都犯了这个错误。比如说对哈希分库分表进行了特殊处理,或者说给予了特殊的地位。甚至于在设计表达式的时候,都给予了它特殊的地位。
这里我要提及一个案例,就是某司的分库分表中间件,欠缺一个对分库分表算法的一个抽象。后来果然在扩展支持一些功能的时候遇到了问题。比如说另我印象很深刻的就是支持 zone 概念的时候,在分库分表中间件中引入了和 zone 相关的很多概念。
他们的做法不同于我在这里提及的把 zone 等概念限定到某一个具体的分库分表算法的实现里面,而是在分库分表核心内部就引入了很多和 zone 相关的东西。
这就是典型的巨大的设计错误。而且在可以预计的未来,他们还会遇到更多的困难。
当然这并不是这家公司这部分开发人员才会犯下的错误,而是大多数设计者在设计中间件都会犯的错误。
常见的原因则是中间件研发者往往会受到业务研发的影响。一个业务研发认为重要的功能,中间件研发者很容易被误导,做成框架的核心功能。
而实际上,业务上认为重要的功能并不等于中间件要解决的核心问题。那么对于一个研发者来说,他就要审慎思考当公司要求中间件提供一个功能的时候,这个功能究竟是不是中间件的核心功能。
The text was updated successfully, but these errors were encountered: