本文概述了数据库中间件的用途架构分析2.1的高层视图。总体概述2.2。再来说说读写分离。总结Part1数据库中间件有什么用?有一天,你去三亚玩,想去冲浪。就算不需要钱,为什么还不需要买快艇、滑板等装备来满足需求呢?一时兴起?就租一个吧这其实就是连接池的作用。数据库中间件可以理解为一个具有连接池功能的辅助组件,但是比连接池更高级,还有很多附加功能。它不仅可以出租冲浪板,还可以提供位置推荐、保险等服务。根据网上的资料,zdal应该算是半开源的。之前好像是开源的,但是不准备维护,然后删掉了。不过github上已经fork了很多,也只是随便找了下。目前,它只是一个旧版本。目前ant里面的zdal好像已经更新到zdal5了,所以看不到。系统越复杂,数据库中间件的作用就越大。以zdal为例,它提供了分库分表、结果??集合并、SQL分析、数据库failover动态切换等统一的数据访问层解决方案,我们来看看它的内部实现是什么样的。Part2高处境架构分析2.1总体概述如上图所示,zdal有四个重要组成部分:ValueReflection--Client包。对外暴露基础操作接口,在业务层作为简单的黑盒操作数据源;业务只与客户端交互,动态切换/路由等逻辑只需要在规则中配置,相关逻辑由zdal实现。核心功能——连接管理数据源包。核心能力是提供各类数据库的连接管理;再花哨的功能,最终目的都是为了解决数据库连接问题。关键能力——SQL解析解析器包。基本的SQL解析能力;解析sql类型、字段名、数据库等,并根据规则进行路由扩展能力--数据库表路由规则包。根据解析器解析出的字段,确定逻辑库表和物理库表。2.2组件图查看架构组件图对于理解整体架构、组件及其相互关系非常有帮助。简单版的元件图画了好久,还有很多错误,不过大概是这个意思。哎,基本功丢了~对比上图可以看的清楚:Client包负责监控应用层暴露的数据源。配置动态变化的监控组件,负责加载组织各部分的配置组件,负责加载springbean和库表规则的配置组件;客户端加载规则组件,实现逻辑表和数据库的路由规则。客户端中的库表配置调用数据源中的数据源管理服务,为连接池建立连接池;客户端中的SqlDispatcher服务调用SQL解析组件实现SQL解析。Part3详析一叶知秋3.1配置加载和bean初始化大多数情况下,我们使用mybatis等ORM框架来进行数据库操作。其实无论是ORM还是其他方式,应用层都需要配置数据源。因此,客户端暴露了一个符合JDBC标准的datasource数据源,满足应用层ORM等框架配置数据源的要求——ZdalDataSource如果图片被压缩看不清,后台会回复给get//只提供了一个init方法,也是spring启动时必须调用的初始化方法。所有功能都从这里开始。"...");thrownewZdalClientException(e);}}ZdalDataSource#init()方法是配置加载的核心入口,init负责加载spring配置,根据配置初始化数据源,创建连接池,同时为后续的路由调用维护逻辑表和物理库的对应关系。/*父类的init方法*/protectedvoidinitZdalDataSource(){/*使用FileSystemXmlApplicationContext加载配置文件中的数据源和规则并转化为zdalConfig对象*/this.zdalConfig=ZdalConfigurationLoader.getInstance().getZdalConfiguration(appName,dbmode,appDsName,configPath);this.dbConfigType=zdalConfig.getDataSourceConfigType();this.dbType=zdalConfig.getDbType();//初始化数据源this.initDataSources(zdalConfig);this.inited.set(true);}}从上面的类图和这里的两个入口方法大致了解了zdal配置加载的启动过程。下面我们来详细了解一下读写分离和分库分表的规则是如何加载的以及它们是如何工作的。3.2读写分离加载详解读写分离配置首先我们需要有数据源的相关配置,如下图所示:Picture这个xml配置会在调用init方法的时候初始化,并解析成ZdalConfig类的属性,ZdalConfig类的主要成员如下代码所示:publicclassZdalConfig{/**key=dsName;value=DataSourceParameter所有物理数据源的配置项,如用户名,密码,libraryname等*/privateMapdataSourceParameters=newConcurrentHashMap();/**逻辑数据源与物理数据源的对应关系:key=logicDsName,value=physicDsName*/privateMaplogicPhysicsDsNames=newConcurrentHashMap();/**数据源读写规则,比如只读,或者读写配置*/privateMapgroupRules=newConcurrentHashMap();/**异常传输的数据源规则*/privateMapfailoverRules=newConcurrentHashMap();//完整的读写分离和分流数据库分表规则配置privateAppRuleappRootRule;可以看到xml中的规则被解析成了xxxRules。这里以groupRules为例,failover同理。接下来就是通过解析得到的zdalConfig来初始化数据源:protectedfinalvoidinitDataSources(ZdalConfigzdalConfig){//DataSourceParameter存储数据源参数,比如用户名密码,最大最小连接数等for(Entryentry:zdalConfig.getDataSourceParameters().entrySet()){try{//初始化连接池ZDataSourcezDataSource=newZDataSource(/*设置最大最小连接数*/createDataSourceDO(entry.getValue(),zdalConfig.getDbType(),appDsName+"."+entry.getKey()));this.dataSourcesMap.put(entry.getKey(),zDataSource);}catch(Exceptione){//...}}//其他分支省略,看最简单的分组方式if(dbConfigType.isGroup()){//读写配置赋值this.rwDataSourcePoolConfig=zdalConfig.getGroupRules();//初始化多个读库下的负载均衡this.initForLoadBalance(zdalConfig.getDbType());}//注册收听:为了我et动态切换this.initConfigListener();}initForLoadBalance方法如下:;this.setDbTypeForDBSelector(dbType);}可以看到先构建了DB选择器,然后为运行时获取的runtimeConfigHolder赋值并构建DB选择器后,其实所有的数据源都是按照读写两个维度构建的,即group_r和group_w都包含5个数据源,但是每个数据源的权重是different://比如按照上面的配置只有一个write库,但是也会包含所有的数据源group_0_w_0:group_0_w_1:group_0_w_2:group_0_w_3:group_0_w_4://上面是写入相关DBSelecter的内容。读写分离如何实现以delete为例,update和delete是操作publicvoiddelete(ZdalDataSourcedataSource){StringdeleteSql="deletefromtest";Connectionconn=null;PreparedStatementpst=null;try{conn=dataSource.getConnection();pst=conn.prepareStatement(deleteSql);pst.execute();}catch(Exceptione){//...}finally{//resourceclose}}getConnection会从上面提到的runtimeConfigHolder中获取DBSelecter,然后执行executemethodpublicbooleanexecute()throwsSQLException{SqlTypesqlType=getSqlType(sql);//SELECT相关选择group_r对应的DBSelecter(sqlType==SqlType.SELECT||sqlType==SqlType.SELECT_FOR_UPDATE||sqlType==SqlType.SELECT_FROM_DUAL){//略returntrue;//update/delete相关,选择group_w对应的DBSelecter}elseif(sqlType==SqlType.INSERT||sqlType==SqlType.UPDATE||sqlType==SqlType.DELETE){if(super.dbConfigType==DataSourceConfigType.GROUP){executeUpdate0();}else{executeUpdate();}returnfalse;}我如果是读相关,则选择_r的DBSelecter,如果是写相关,则选择_W的DBSelecter。那么executeUpdate0如何实现读写数据源的区分,其实就是根据权重过滤这组数据源。//WeightRandom#select(int[],java.lang.String[])privateStringselect(int[]areaEnds,String[]keys){//这里的areaEnds数组是一个累加的范围值数据//比如三个库Weight1098//那么areaEnds就是101927,就是每一个权重的累加,最后的值就是求和(sum);for(inti=0;i