麒麟v10 上部署 TiDB v5.1.2 生产环境优化实践
897
2023-04-14
数据库的中间件有啥用?
Part1 数据库中间件有啥用
有一天,你去三亚玩耍,就想玩个冲浪,即时你不差钱,难道还要自己采买快艇、滑板等等装备来满足这为数不多的心血来潮么。租一个就行了嘛。这其实就是连接池的作用。
越复杂的系统,数据库中间件的作用越大。就拿zdal来说,它提供分库分表,结果集合并,sql解析,数据库failover动态切换等数据访问层统一解决方案。下面就一起来看下,其内部实现是怎么样的。
Part2 架构剖析之高屋建瓴
2.1 整体概述
如上图所示,zdal有四个重要的组成部分:
价值体现--客户端Client包。对外暴露基本操作接口,用于业务层简单黑盒的操作数据源;业务只和client交互,动态切换/路由等逻辑只需要进行规则配置,相关逻辑由zdal实现。
核心功能--连接管理datasource包。最核心的能力,提供多种类型数据库的连接管理;不管功能多花哨,最终目的还是为了解决数据库连接的问题。
扩展能力--库表路由rule包。根据parser解析出的字段确定逻辑库表和物理库表。
2.2 组件图看架构
组件图对整体架构和各组件及相互联系的理解可以起到很好的帮助。一个简版的组件图画了好久,还有不少错,不过大概是这么个意思,哎,基本功要丢~
对照上图可以比较清晰的看到:
Client包对应用层暴露的数据源、负责监听配置动态变更的监听组件、负责加载组织各部分的配置组件、负责加载spring bean 和库表规则的配置组件;
Client中加载了规则组件,实现逻辑表和数据库的路由规则。
Client中的库表配置调用datasource中的数据源管理服务并构建连接池的连接池;
Client中的SqlDispatcher服务调用SQL解析组件实现SQL解析。
Part3 细节剖析之一叶知秋
3.1 配置加载和bean初始化
大部分情况下,我们使用如mybatis这样的ORM框架来进行数据库操作,其实不管是ORM还是其他方式,应用层都需要对数据源进行配置。
所以,client对外暴露了一个符合JDBC标准的datasource数据源,用来满足应用层ORM等框架配置数据源的要求--ZdalDataSource
ZdalDataSource#init()方法即为配置加载的核心入口,init中负责加载spring配置,根据配置初始化数据源,并创建连接池,同时,将逻辑表和物理库的对应关系都维护起来供后续路由调用。
从上面的类图和这里的两个入口方法大概了解到zdal配置加载的启动流程。下面我们就来详细看一下,读写分离和分库分表的规则是怎么被加载,怎么起作用的。
3.2 细说读写分离
读写分离配置的加载
首先,我们需要有数据源的相关配置,如下图:
此XML配置会在init方法被调用时,被初始化,解析成ZdalConfig类的属性,ZdalConfig类的主要成员见下面代码:
可以看到,xml中的规则,被解析到xxxRules里。这里以groupRules为例,failover同理。
下一步则是通过解析得到的zdalConfig 来初始化数据源:
initForLoadBalance的方法如下:
private void initForLoadBalance(DBType dbType) { Map dsSelectors = this.buildRwDbSelectors(this.rwDataSourcePoolConfig); this.runtimeConfigHolder.set(new ZdalRuntime(dsSelectors)); this.setDbTypeForDBSelector(dbType);}
可以看到,首先构建出了DB选择器,然后赋值给了runtimeConfigHolder供运行时获取。而构建DB选择器的时候,其实是按读写两个维度,把所有数据源都构建了一遍,即group_r和group_w下都包含5个数据源,只不过各自的权重不一样:
//比如按上面的配置写库只有一个,但是也会包含全数据源group_0_w_0 :< bean:read0DataSource , writeWeight:0>group_0_w_1 :< bean:writeDataSource , writeWeight:10>group_0_w_2 :< bean:read1DataSource , writeWeight:0>group_0_w_3 :< bean:read2DataSource , writeWeight:0>group_0_w_4 :< bean:read3DataSource , writeWeight:0>//上述就是写相关的DBSelecter的内容。
读写分离怎么起作用
以delete为例,更新删除是要操作写库的
public void delete(ZdalDataSource dataSource) { String deleteSql = "delete from test"; Connection conn = null; PreparedStatement pst = null; try { conn = dataSource.getConnection(); pst = conn.prepareStatement(deleteSql); pst.execute(); } catch (Exception e) { //... } finally { //资源关闭 } }
getConnection会从上文中提到的runtimeConfigHolder中获取DBSelecter,然后执行execute方法
public boolean execute() throws SQLException { SqlType sqlType = getSqlType(sql); // SELECT相关的就选择group_r对应的DBSelecter if (sqlType == SqlType.SELECT || sqlType == SqlType.SELECT_FOR_UPDATE|| sqlType == SqlType.SELECT_FROM_DUAL) { //略 return true; //update/delete相关的就选择group_w对应的DBSelecter } else if (sqlType == SqlType.INSERT || sqlType == SqlType.UPDATE|| sqlType == SqlType.DELETE) { if (super.dbConfigType == DataSourceConfigType.GROUP) { executeUpdate0(); } else { executeUpdate(); } return false; } }
如果是读取相关的,那就选_r的DBSelecter,如果是写相关的,那就选_W的DBSelecter。那么executeUpdate0中是怎么执行区分读写数据源的呢,其实就是把这一组的数据源根据权重筛选一遍。
Part4 总结
本篇文章,把阿里数据库中间件相关的组件和加载流程进行了总结,就一个最基本的分组读写分离的流程,对内部实现进行了阐述。说是解析,其实是提供给大家一种阅读的思路,毕竟篇幅有限,如果对中间件感兴趣的同学,可以fork下代码,按上述逻辑自己阅读下。
看源码时,比如dubbo这些中间件其实是比较容易入手的,因为他们都依托于Spring进行JavaBean的装载,所有,对Spring容器暴露的那些init、load方法,就是很好的切入点。个人思路,希望对大家有所帮助。
,>,>,>,>,>,>,>,>,>,>
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。