分库分表中间件原理
背景
分库分表这个词相信很多人都不陌生,在互联网公司数据到达一定规模的时候,多数都会对数据进行分库分表,或者也有人叫分片,英文翻译为Sharding;更加准确来说我们常常关心的是水平分片,即单个业务的某些表到达一定规模后,即使建立索引也无法从根本上带来很大的性能提升,这时我们会考虑把单表拆分,以MySQL为例,B+树索引的深度会随着记录的增多而逐渐加深,根据索引查询的开销也会越来越大,而单表拆分成多个表之后,B+树深度降低,每个单表独立查询的速度也会加快,如果同时还分库的话,并且在不同的实例上,大量的查询压力也会分担到不同的机器上,这对单个数据库机器减压也带来好处。
分库分表的技术方案总体上来讲分为两大类:应用层依赖类中间件、中间层代理类中间件。
应用层依赖类中间件
这类分库分表中间件的特点就是和应用强耦合,需要应用显示依赖相应的jar包(以Java为例),比如知名的TDDL、当当开源的sharding-jdbc、蘑菇街的TSharding、携程开源的Ctrip-DAL、支付宝开源但比较低调的zdal等。
此类中间件的基本思路就是重新实现JDBC的API,通过重新实现DataSource、PrepareStatement等操作数据库的接口,让应用层在基本(注意:这里用了基本)不改变业务代码的情况下透明地实现分库分表的能力。中间件给上层应用提供熟悉的JDBC API,内部通过sql解析、sql重写、sql路由等一系列的准备工作获取真正可执行的sql,然后底层再按照传统的方法(比如数据库连接池)获取物理连接来执行sql,最后把数据结果合并处理成ResultSet返回给应用层。
此类中间件的优点很明显,就是无需额外部署,只要和应用绑定一起发布即可,但是缺点也很明显,就是不能跨语言,比如Java写的sharding-jdbc显然不能用在C#项目中,所以携程的dal也要重新写一套C#的客户端。
中间层代理类中间件
这类分库分表中间件的核心原理是在应用和数据库的连接之间搭起一个代理层,上层应用以标准的MySQL协议来连接代理层,然后代理层负责转发请求到底层的MySQL物理实例,这种方式对应用只有一个要求,就是只要用MySQL协议来通信即可,所以用MySQL Workbench这种纯的客户端都可以直接连接你的分布式数据库,自然也天然支持所有的编程语言。比较有代表性的产品有开创性质的Amoeba、阿里开源的Cobar、社区发展比较好的Mycat 等。
在技术实现上除了和应用层依赖类中间件基本相似外,代理类的分库分表产品必须实现标准的MySQL协议,某种意义上讲数据库代理层转发的就是MySQL协议请求,就像Nginx转发的是Http协议请求。
上述无论哪种类型的产品,除了实现分库分表这一主要功能外,都会额外实现一些其他很有实用价值的功能,比如读写分离、负载均衡等。
如果使用分库分表方式,存在三个技术通用需求需要实现。
1、SQL组合:因为我们关联的表名是动态的,所以我们需要根据逻辑组装动态的SQL。
2、数据库路由:因为数据库名也是动态的,所以我们需要根据不同的逻辑使用不同的数据库。
3、执行结果合并:有些需求需要通过多个分库执行,再合并归集使用。
而市面上能解决以上问题的中间件分为2类:Proxy模式、Client模式。
![](https://img2020.cnblogs.com/blog/2107925/202111/2107925-20211124190751906-1242082640.png)
以上这种设计模式,把SQL组合、数据库路由、执行结果合并等功能全部存放在一个代理服务中,而与分库分表相关的处理逻辑全部存放在另外的服务中,这种设计模式的优点是对业务代码无侵入,业务只需要关注自身的业务逻辑即可。
(2)Client模式:还是借用shardingSphere官方文档的图来说明,如下图所示:
![](https://img2020.cnblogs.com/blog/2107925/202111/2107925-20211124190801616-1026489037.png)
以上这种设计模式,把分库分表相关逻辑存放在客户端,一版客户端的应用会引用一个jar,然后再jar中处理SQL组合、数据库路由、执行结果合并等相关功能。
市面上,关于这两种模式的中间件有如下选择:
![](https://img2020.cnblogs.com/blog/2107925/202111/2107925-20211124190810664-1859835038.png)
看到这里,我们已经知道市面上开源中间件的设计模式,那么我们到底该选择哪种模式呢?简单对比下这2个模式的优缺点,你就知道答案了。
![](https://img2020.cnblogs.com/blog/2107925/202111/2107925-20211124190820128-1739192316.png)
因为看重代码灵活可控这个优势,所以我们选择了Client模式里的Sharding-JDBC来实现分库分表。当然,关于拆分存储选择哪种技术,在实际工作中我们需要根据各自的实际情况来定。