文章目录
MyCat 为了最高效的利用后端的 MySQL 连接,采取了不同于 Cobar 也不同于传统 JDBC 连接池的做法,**传统的做法是基于 Database 的连接池,即一个 MySQL 服务器上有 5 个 Database,则每个 Database 独占最大200 个连接。**这种模式的最大问题在于,将一个数据库所具备的最大 1000 个连接,隔离成了更新小的连接池,于是可能产生一个应用的连接不够,但其他应用的连接却很空闲的资源浪费情况,而对于分片这种场景,这个缺陷则几乎是致命的,因为每个分片所对应的 Database 的连接数量被限制在了一个很小的范围内,从而导致系统并发能力的大幅降低。而 Mycat 则采用了基于 MySQL 实例的连接池模式,每个 Database 都可以用现有的 1000 个连接中的空闲连接
5.1 核心对象
5.1.1 ConMap 和 ConQueue
在 MyCat 的连接池里,当前可用的、空闲的 MySQL 连接是放到一个 ConcurrentHashMap
的数据结构里,Key 为当前连接对应的 database 名,另外还有二级分类 ConQueue
,按照连接是自动提交模式还是手动提交模式进行区分,这个设计是为了高效的查询匹配的可用连接。ConMap
和 ConQueue
包含的关键对象有:
ConcurrentHashMap<String, ConQueue> items
:可用的 MySQL 连接容器,key 为当前连接对应的 database 名,value 为ConQueue
对象,里面包含了两个存储数据库连接的队列ConcurrentLinkedQueue<BackendConnection> autoCommitCons
:自动提交的数据库连接ConcurrentLinkedQueue<BackendConnection> manCommitCons
:手动提交的数据库连接
public class ConMap {
/**
* key:当前连接对应的 Database
* ConQueue:数据库连接队列(按照连接是自动提交模式还是手动提交模式进行区分,这个设计是为了高效的查询匹配的可用连接)
*/
private final ConcurrentHashMap<String, ConQueue> items = new ConcurrentHashMap<String, ConQueue>();
}
public class ConQueue {
private final ConcurrentLinkedQueue<BackendConnection> autoCommitCons = new ConcurrentLinkedQueue<BackendConnection>();
private final ConcurrentLinkedQueue<BackendConnection> manCommitCons = new ConcurrentLinkedQueue<BackendConnection>();
private long executeCount;
}
BackendConnection
为后端数据库连接,其实现有JDBCConnection
、MySQLConnection
等
5.1.2 PhysicalDatasource
对应于 <dataHost>
节点下的 <writeHost>
或 <readHost>
子节点,表示一个物理数据库实例。每个数据库实例中保存了多个可用的数据库连接(BackendConnection
),MyCat 初始化时,根据 <dataHost>
节点的 minCon
属性值初始化多个可用的数据库连接。其关键对象有:
name
:<writeHost>
的 host 属性值size
:读或写连接池的最大连接数conMap
:存放当前可用的数据库连接DataHostConfig
:<dataHost>
节点对应的配置DBHostConfig
:<writeHost>
节点配置
public abstract class PhysicalDatasource {
private final String name;
private final int size;
private final DBHostConfig config;
private final ConMap conMap = new ConMap();
private final boolean readNode;
private final DataHostConfig hostConfig;
private PhysicalDBPool dbPool;
}
PhysicalDatasource
的实现类有:
5.1.3 PhysicalDBPool
对应于 <dataHost name="localhost1" >
节点,表示物理数据库实例池。由于 <datahost>
节点可包含多个 <writeHost>
节点,因此 PhysicalDBPool
可以包含多个物理数据库实例,其关键对象有:
hostName
:<dataHost>
标签的 name 属性writeSources
和readSources
:可写和可读的多个物理数据库实例,对应于<writeHost>
和<readHost>
activedIndex
:表明了当前是哪个写节点的数据源在生效
public class PhysicalDBPool {
private final String hostName;
protected PhysicalDatasource[] writeSources;
protected Map<Integer, PhysicalDatasource[]> readSources;
protected volatile int activedIndex;
private final DataHostConfig dataHostConfig;
}
5.1.4 PhysicalDBNode
对应于 <dataNode />
节点,表示一个数据库分片,PhysicalDBNode
包含的关键对象有:
name
:dataNode 名称,对应于<dataNode>
标签的 name 属性database
:数据库名称,对应于<dataNode>
标签的 database 属性dbPool
:MySQL 连接池,里面包含了多个数据库实例PhysicalDatasource
,并将其按照读节点和写节点分类,实现读写分类和节点切换的功能。其中activedIndex
属性表明了当前是哪个写节点的数据源在生效
public class PhysicalDBNode {
protected final String name;
protected final String database;
protected final PhysicalDBPool dbPool;
}
若 schema.xml 中配置了一下分片节点:
<dataNode name="dn1" dataHost="localhost1" database="db_demo_01"/>
<dataNode name="dn2" dataHost="localhost1" database="db_demo_02"/>
<dataNode name="dn3" dataHost="localhost1" database="db_demo_03"/>
当某个用户会话需要一个自动提交的,到分片 <dataNode name="dn1" dataHost="localhost1" database="db_demo_01"/>
的 SQL 连接的时候,分片节点 dn1
首先在连接池 dbPool
中查找是否有数据库 db_demo_01
(对应于 PhysicalDatasource
)上的可用连接,若有则看是否有自动提交模式的连接,找到就返回,否则返回 db_demo_01
上的手动提交模式的连接;若没有 db_demo_01
的可用连接,则随机返回一个其他数据库(db_demo_02
或 db_demo_03
)对应的可用连接;若没有其他数据库也没有可用连接,并且连接池还没达到上限,则创建一个新连接并返回
上述获取数据库连接的逻辑有一种情况是:用户会话得到的数据库连接可能不是来自于 db_demo_01
的,因此在执行具体的 SQL 之前,还有一个自动同步数据库连接的过程:包括事务隔离级别、事务模式、字符集、database 等四个指标。同步完成以后,才会执行具体的 SQL 请求
通过共享一个 MySQL 上的所有数据库的可用连接,并结合连接状态同步的特性,MyCat 的连接池做到了最佳的吞吐量,也在一定程度上提升了整个系统的并发支撑能力
5.2 创建数据库连接
5.2.1 创建新数据库连接时机
创建新数据库连接的方法为 PhysicalDatasource#createNewConnection(io.mycat.backend.mysql.nio.handler.ResponseHandler, java.lang.Object, java.lang.String)
,其有两个创建连接的触发时机:
-
io.mycat.backend.datasource.PhysicalDatasource#createByIdleLitte
执行空闲检测时触发,若当前数据库连接总数(空闲连接数和活动链接数之和)小于连接池的最大连接数,且空闲连接数小于连接池最小连接数,则调用
PhysicalDatasource#createByIdleLitte
方法创建新数据库连接if ((createCount > 0) && (idleCons + activeCons < size) && (idleCons < hostConfig.getMinCon())) { createByIdleLitte(idleCons, createCount); }
-
io.mycat.backend.datasource.PhysicalDatasource#getConnection
首先调用
ConMap#tryTakeCon(java.lang.String, boolean)
获取当前 database 的可用连接,若有则立即返回,否则从其他的 database 上找一个可用连接返回。若ConMap#tryTakeCon
返回 null,表示数据库连接池中没有空闲连接,则调用PhysicalDatasource#createNewConnection
创建新连接public void getConnection(String schema, boolean autocommit, final ResponseHandler handler, final Object attachment) throws IOException { // 从当前连接 map 中拿取已建立好的后端连接 BackendConnection con = this.conMap.tryTakeCon(schema, autocommit); if (con != null) { //如果不为空,则绑定对应前端请求的 handler takeCon(con, handler, attachment, schema); }