ShardingSphere-JDBC 支持分库分表和读写分离。
是一种Datasource 实现,通过托管原有的Datasource,根据策略实现请求分发/路由。本文关注读写分离的主要实现。
spring
shardingsphere
master-slave:
name: ds_ms
master-data-source-name: ds_master
slave-data-source-names:
- ds_slave0
- ds_slave1
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 4.0.0-RC2 之后版本 负载均衡策略配置方式 -->
<master-slave:load-balance-algorithm id="randomStrategy" type="RANDOM" />
<master-slave:data-source id="masterSlaveDataSource" master-data-source-name="ds_master" slave-data-source-names="ds_slave0, ds_slave1" strategy-ref="randomStrategy">
<master-slave:props>
<prop key="sql.show">true</prop>
<prop key="executor.size">10</prop>
</master-slave:props>
</master-slave:data-source>
</beans>
监控、动态配置等辅助功能,在开发和测试场景中,并不需要。但是会在spring 启动加载过程中拖慢启动速度,开发、自测效率也受影响。
方案:配置spring profile,排除这些配置文件。在开发环境中,只加载关注的配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="!speed">
<!-- 开发环境不需要的配置 -->
<!-- 例如:缓存,连接redis io操作 -->
<!-- 例如:mq 消费,连接broker io操作 -->
</beans>
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
JVM 配置 -Dspring.profiles.active="speed,profile2"
Bean definition profiles provide a mechanism in the core container that allows for registration of different beans in different environments.
关键类:org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
/**
* 解析并注册beans 标签下声明的bean。 Since:3.1
*/
protected void doRegisterBeanDefinitions(Element root) {
if (this.delegate.isDefaultNamespace(root)) {
// "profile"
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
// profile 格式解析,支持分隔符",; "
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 判断profile 是否激活,如果不符合快速失败,直接return
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 正常解析和注册 bean
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
}
注解可以在类和方法上使用。
// Spring condition match 特性实现选择性加载, Since:4.0
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
profile 思想应用广泛,常见的还有maven profile。
spring profile 提供一整套环境切换的方案,更加简化了工程治理。
本文作为 Code Insight 目录,梳理 h2 数据库的知识点以及感兴趣的实现细节。
作为一个教学演示用的数据库,性能优化肯定不是其优势,重点在协议、SQL规范实现的思路和解决方案上。❌
h2 数据库提供DBMS 完整的实现、丰富的特性和其简练的实现,通过了解其设计思路,举一反三,在日常开发设计、MySQL 数据库深度研究都有借鉴意义。✔
核心实现的原理分析会持续更新、补充链接。
h2 数据库支持嵌入式(开源产品 demo 演示)和服务器模式,可以使用磁盘存储(B 树索引存储引擎、日志结构存储引擎)或内存数据库,并提供事务支持和多版本并发控制(MVCC)。
同时还提供了一个基于浏览器的控制台应用程序(JavaWeb),支持加密数据库和全文搜索(based on Apache Lucene)等扩展特性。功能非常丰富,使用Java 编写,可以作为学习数据库原理和架构的范例。
H2 使用教程 10min 带你玩转 H2 数据库 😀
H2 支持的SQL 语法 增删改查、存储过程、触发器等,兼容 ANSI-SQL89 规范。
H2 特性介绍和原理 Code Insight 主要参考资料
目录结构参考 官网介绍架构 的Top-down Overview。
结合关系型数据库和 DBMS 相关的知识点,理论结合实际,深入理解数据库思想。
org.h2.Driver
org.h2.engine.SessionRemote
参考 Database URL Overview URL 连接和配置示例
org.h2.command.Parser
SQL 语法解释器使用递归下降分析器(recursive-descent),按照语法规则解析输入的文本。有性能问题,优点是易于实现和理解。
package org.h2.command.dml
org.h2.expression.Expression
h2 没有生成查询 IR(中间表示)这一中间步骤,而是直接生成一个命令执行对象。然后对命令对象进行一些优化步骤(org.h2.expression.Expression#optimize),生成更有效的命令。
Insight H2 database auto increment
org.h2.table.RegularTable
org.h2.mvstore.db.MVTable
org.h2.index.PageBtreeIndex
RegularTable 采用常用的数据表实现方案,使用B Tree 索引结构,聚集索引数据存储等。
MVTable 是基于新一代的存储引擎 MVStore 实现的数据表实现。
Undo log, 撤销日志是每个会话独立的,用于回滚操作或撤销失败的更改。
redo log, 重做日志也是每个会话独立的,用于在崩溃后恢复数据库。
transaction log, 事务日志是在所有会话之间共享的(MVCC),用于记录数据库的所有更改。
基于 B-tree 的存储引擎,使用磁盘页面(块)存储数据。
B树是一种树状数据结构,用于组织和存储数据,可用于在大量数据集中快速查找和访问数据。针对磁盘存储介质,实现数据快速检索和更新。
笔记 h2database BTree 设计实现与查询优化思考
MVStore 是一种持久化的、基于日志结构的键值存储。用作新版本 H2 的默认存储引擎。
H2 数据库官网特有一章节用来描述 MVStore。
这种引擎将修改的数据缓存在内存中,然后在累积足够的修改后,将它们一次性写入磁盘。这种方式可以提高写入性能,特别是对于不支持小随机写入的文件系统和存储系统(如Btrfs),以及SSD。
每个修改集合称为一个“chunk”,其中包含了所有被修改的B树的父节点和根节点,以及元数据。
为了重用磁盘空间,会压缩具有最少活动数据的chunk。与传统存储引擎相比,这种引擎更简单、更灵活,并且通常需要更少的磁盘操作。
org.h2.store.FileStore
org.h2.mvstore.OffHeapStore
文件抽象层,方便存储引擎层进行操作。封装了seek、readFully、write、sync 等方法,屏蔽了具体存储(也可以使用堆外存储)的实现细节。
ByteBuffer.allocateDirect
for update 应用场景:对需要读写操作(query data and then insert or update related data within the same transaction)的数据进行加锁,防止其他写操作的影响。把业务并发彻底变为串行。业务操作变安全。
MySQL :: MySQL 5.7 Reference Manual :: 14.7.2.4 Locking Reads
多个session(事务) 在没有修改操作时,都可以对当前的数据加共享锁。
如果有session 有修改操作,那么加锁会阻塞,一直等待操作session commit后,加锁成功,并且返回最新的数据。
这种适用于对加锁数据只读操作的场景。文档Example 以parent /child表 级联操作案例来说明应用场景。
多个session(事务) ,对当前数据,只有一个可以加锁成功,其余等待(包括LOCK IN SHARE MODE)。
这种适用于对加锁数据进行读写操作的场景。文档Example 以 counter 累加的案例说明应用场景。
START TRANSACTION;
-- try lock
SELECT counter_field FROM child_codes FOR UPDATE;
-- modify...
UPDATE child_codes SET counter_field = counter_field + 1;
-- release lock
COMMIT;
START TRANSACTION;
SELECT * FROM child_codes WHERE id = 10567 LOCK IN SHARE MODE;
-- 多个session 在此互相等待,死锁发生
UPDATE child_codes SET name = 'foo' WHERE id = 10567;
COMMIT;
mysql 通过locking reads 机制,解决了并发场景下读写数据不一致的问题。
我们比较常用的是 for update。优点是简单,缺点是tps不高。
还有其他的控制业务并发的方案,比如乐观锁机制(自旋)。需要根据场景去选用合适的技术方案。
如果使用不当,会造成SQL 死锁。又一种死锁的方式😂。
使用MybatisPlus 过程中,发现有逻辑删除的内置功能,比较好奇,引出此次的Code Insight。 注意:逻辑删除是设置针对全局的。
mybatis-plus:
check-config-location: fals
# 这个是 Mybatis 的配置
configuration:
auto-mapping-unknown-column-behavior: partial
cache-enabled: false
call-setters-on-nulls: true
map-underscore-to-camel-case: true
# 这个是MybatisPlus 的配置
global-config:
db-config:
logic-delete-field: isDelete
# 逻辑删除全局值(默认 1、表示已删除)
logic-delete-value: 1
logic-not-delete-value: 0
com.baomidou.mybatisplus.core.config.GlobalConfig 插件的配置项, 对应上述的配置
com.baomidou.mybatisplus.annotation.TableLogic 表字段逻辑处理注解(逻辑删除)
com.baomidou.mybatisplus.core.metadata.TableFieldInfo 数据库表字段信息
initLogicDelete:316, TableFieldInfo initTableFields:319, TableInfoHelper initTableInfo:144, TableInfoHelper inspectInject:53, AbstractSqlInjector parserInjector:133, MybatisMapperAnnotationBuilder parse:123, MybatisMapperAnnotationBuilder addMapper:83, MybatisMapperRegistry bindMapperForNamespace:432, XMLMapperBuilder
/**
* 逻辑删除初始化
*
* @param dbConfig 数据库全局配置
* @param field 字段属性对象
*/
private void initLogicDelete(...) {
/* 获取注解属性,逻辑处理字段 */
TableLogic tableLogic = field.getAnnotation(TableLogic.class);
if (null != tableLogic) {
// ...
} else if (!existTableLogic) {
// 'isDelete'
String deleteField = dbConfig.getLogicDeleteField();
if (StringUtils.isNotBlank(deleteField) && this.property.equals(deleteField)) {
// 0
this.logicNotDeleteValue = dbConfig.getLogicNotDeleteValue();
this.logicDeleteValue = dbConfig.getLogicDeleteValue();
this.logicDelete = true;
}
}
}
/**
* 应用场景
* 根据ID 查询一条数据(MappedStatement 模板)
* 自动追加逻辑删除条件(AND is_delete = 0)
*/
public class SelectById extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
// SELECT %s FROM %s WHERE %s=#{%s} %s 最后一个参数:逻辑删除条件
SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),
sqlSelectColumns(tableInfo, false),
tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
// " AND " + logicDeleteFieldInfo.getColumn() + "=" + logicDeleteFieldInfo.getLogicNotDeleteValue();
tableInfo.getLogicDeleteSql(true, true)), Object.class);
return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
}
}
MybatisPlus 和tk.Mybatis 针对@Table 处理思路一样。在初始化过程中,会缓存表的相关信息,方便后续拼装SQL 使用。logic-delete
就是应用在初始化过程中。