1.什么是 ShardingSphere?
Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。
Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态。
它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。 它站在数据库的上层视角,关注它们之间的协作多于数据库自身。
ShardingSphere官网
ShardingSphere-JDBC
ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。
ShardingSphere-Proxy
ShardingSphere-Proxy 定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。
特性 | 定义 |
---|---|
数据分片 | 数据分片,是应对海量数据存储与计算的有效手段。ShardingSphere 基于底层数据库提供分布式数据库解决方案,可以水平扩展计算和存储。 |
分布式事务 | 事务能力,是保障数据库完整、安全的关键技术,也是数据库的核心技术。基于 XA 和 BASE 的混合事务引擎,ShardingSphere 提供在独立数据库上的分布式事务功能,保证跨数据源的数据安全。 |
读写分离 | 读写分离,是应对高压力业务访问的手段。基于对 SQL 语义理解及对底层数据库拓扑感知能力,ShardingSphere 提供灵活的读写流量拆分和读流量负载均衡。 |
数据迁移 | 数据迁移,是打通数据生态的关键能力。ShardingSphere 提供跨数据源的数据迁移能力,并可支持重分片扩展。 |
联邦查询 | 联邦查询,是面对复杂数据环境下利用数据的有效手段。ShardingSphere 提供跨数据源的复杂查询分析能力,实现跨源的数据关联与聚合。 |
数据加密 | 数据加密,是保证数据安全的基本手段。ShardingSphere 提供完整、透明、安全、低成本的数据加密解决方案。 |
影子库 | 在全链路压测场景下,ShardingSphere 支持不同工作负载下的数据隔离,避免测试数据污染生产环境。 |
2.整合 shardingsphere-jdbc
为方便演示,示例(水平分片-分表)使用 H2 内存数据库,直接启动项目即可,其余示例需要自行配置数据库
2.1 引入依赖
示例使用最新版本 5.4.1(后期同步更新)
<dependency>
<groupId></groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>${}</version>
</dependency>
这里有两个坑,网上搜一堆都是老版本整合,差距较大,问题较多;
坑一、需要引入额外依赖:
<!-- 高版本中已独立,需要单独添加依赖,否则启动报错 -->
<dependency>
<groupId></groupId>
<artifactId>jaxb-api</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>jaxb-impl</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>jaxb-runtime</artifactId>
<version>${}</version>
</dependency>
坑二、覆盖原路径重写 snakeyaml 中的类
/**
* 高版本 snakeyaml 在 spring-boot-autoconfigure 加载配置文件时报错
* : .***.***: method <init>()V not found
* 覆盖源码,添加无参构造函数
* <p>
* public Representer() {
* super(new DumperOptions());
* (null, new RepresentJavaBean());
* }
* </p>
*/
public class Representer extends SafeRepresenter {
protected Map<Class<? extends Object>, TypeDescription> typeDefinitions = ();
public Representer() {
super(new DumperOptions());
(null, new RepresentJavaBean());
}
public Representer(DumperOptions options) {
super(options);
(null, new RepresentJavaBean());
}
public TypeDescription addTypeDescription(TypeDescription td) {
if (Collections.EMPTY_MAP == typeDefinitions) {
typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
}
if (() != null) {
addClassTag((), ());
}
(getPropertyUtils());
return ((), td);
}
@Override
public void setPropertyUtils(PropertyUtils propertyUtils) {
(propertyUtils);
Collection<TypeDescription> tds = ();
for (TypeDescription typeDescription : tds) {
(propertyUtils);
}
}
protected class RepresentJavaBean implements Represent {
public Node representData(Object data) {
return representJavaBean(getProperties(()), data);
}
}
/**
* Tag logic: - explicit root tag is set in serializer - if there is a predefined class tag it is
* used - a global tag with class name is always used as tag. The JavaBean parent of the specified
* JavaBean may set another tag (tag:,2002:map) when the property class is the same as
* runtime class
*
* @param properties JavaBean getters
* @param javaBean instance for Node
* @return Node to get serialized
*/
protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
List<NodeTuple> value = new ArrayList<NodeTuple>(());
Tag tag;
Tag customTag = (());
tag = customTag != null ? customTag : new Tag(());
// flow style will be chosen by BaseRepresenter
MappingNode node = new MappingNode(tag, value, );
(javaBean, node);
FlowStyle bestStyle = ;
for (Property property : properties) {
Object memberValue = (javaBean);
Tag customPropertyTag = memberValue == null ? null : (());
NodeTuple tuple = representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
if (tuple == null) {
continue;
}
if (!((ScalarNode) ()).isPlain()) {
bestStyle = ;
}
Node nodeValue = ();
if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
bestStyle = ;
}
(tuple);
}
if (defaultFlowStyle != ) {
(defaultFlowStyle);
} else {
(bestStyle);
}
return node;
}
/**
* Represent one JavaBean property.
*
* @param javaBean - the instance to be represented
* @param property - the property of the instance
* @param propertyValue - value to be represented
* @param customTag - user defined Tag
* @return NodeTuple to be used in a MappingNode. Return null to skip the property
*/
protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
ScalarNode nodeKey = (ScalarNode) representData(());
// the first occurrence of the node must keep the tag
boolean hasAlias = (propertyValue);
Node nodeValue = representData(propertyValue);
if (propertyValue != null && !hasAlias) {
NodeId nodeId = ();
if (customTag == null) {
if (nodeId == ) {
// generic Enum requires the full tag
if (() != ) {
if (propertyValue instanceof Enum<?>) {
();
}
}
} else {
if (nodeId == ) {
if (() == ()) {
if (!(propertyValue instanceof Map<?, ?>)) {
if (!().equals()) {
();
}
}
}
}
checkGlobalTag(property, nodeValue, propertyValue);
}
}
}
return new NodeTuple(nodeKey, nodeValue);
}
/**
* Remove redundant global tag for a type safe (generic) collection if it is the same as defined
* by the JavaBean property
*
* @param property - JavaBean property
* @param node - representation of the property
* @param object - instance represented by the node
*/
@SuppressWarnings("unchecked")
protected void checkGlobalTag(Property property, Node node, Object object) {
// Skip primitive arrays.
if (().isArray() && ().getComponentType().isPrimitive()) {
return;
}
Class<?>[] arguments = ();
if (arguments != null) {
if (() == ) {
// apply map tag where class is the same
Class<? extends Object> t = arguments[0];
SequenceNode snode = (SequenceNode) node;
Iterable<Object> memberList = ();
if (().isArray()) {
memberList = ((Object[]) object);
} else if (object instanceof Iterable<?>) {
// list
memberList = (Iterable<Object>) object;
}
Iterator<Object> iter = ();
if (()) {
for (Node childNode : ()) {
Object member = ();
if (member != null) {
if ((())) {
if (() == ) {
();
}
}
}
}
}
} else if (object instanceof Set) {
Class<?> t = arguments[0];
MappingNode mnode = (MappingNode) node;
Iterator<NodeTuple> iter = ().iterator();
Set<?> set = (Set<?>) object;
for (Object member : set) {
NodeTuple tuple = ();
Node keyNode = ();
if ((())) {
if (() == ) {
();
}
}
}
} else if (object instanceof Map) { // ends-up here
Class<?> keyType = arguments[0];
Class<?> valueType = arguments[1];
MappingNode mnode = (MappingNode) node;
for (NodeTuple tuple : ()) {
resetTag(keyType, ());
resetTag(valueType, ());
}
} else {
// the type for collection entries cannot be
// detected
}
}
}
private void resetTag(Class<? extends Object> type, Node node) {
Tag tag = ();
if ((type)) {
if ((type)) {
();
} else {
();
}
}
}
/**
* Get JavaBean properties to be serialised. The order is respected. This method may be overridden
* to provide custom property selection or order.
*
* @param type - JavaBean to inspect the properties
* @return properties to serialise
*/
protected Set<Property> getProperties(Class<? extends Object> type) {
if ((type)) {
return (type).getProperties();
}
return getPropertyUtils().getProperties(type);
}
}
注意:实体类ID生成算法使用分布式雪花算法,mapper层、service层照旧即可。
@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("tb_user")
@Schema(description = "用户")
public class User implements Serializable {
@Serial
private static final long serialVersionUID = 1129496589110730436L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
@Schema(description = "主键ID")
private Long id;
@TableField("create_time")
@Schema(description = "创建时间")
private Long createTime;
@TableField("name")
@Schema(description = "姓名")
private String name;
}
示例表结构(此处以水平分片-分表为例)
DROP TABLE IF EXISTS tb_user;
CREATE TABLE `tb_user`
(
`id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
`create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
`name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
);
DROP TABLE IF EXISTS tb_user0;
CREATE TABLE `tb_user0`
(
`id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
`create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
`name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
);
DROP TABLE IF EXISTS tb_user1;
CREATE TABLE `tb_user1`
(
`id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
`create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
`name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
)
配置文件(此处以水平分片-分表为例),其他可自行配置数据源进行测试:
# 数据源配置
dataSources:
dsA:
dataSourceClassName:
driverClassName: org.
url: jdbc:h2:mem:db_sharding
username: sa # 用户名,用于控制台登录
password: 123456 # 密码,用于控制台登录
dsB0:
dataSourceClassName:
driverClassName: org.
url: jdbc:h2:mem:db_sharding
username: sa # 用户名,用于控制台登录
password: 123456 # 密码,用于控制台登录
dsB1:
dataSourceClassName:
driverClassName: org.
url: jdbc:h2:mem:db_sharding
username: sa # 用户名,用于控制台登录
password: 123456 # 密码,用于控制台登录
# 规则配置
rules:
- !SINGLE
tables:
- "*.*"
- !SHARDING
tables:
# 逻辑表名
tb_user:
actualDataNodes: dsB0.tb_user${0..1}
# 分库策略
tableStrategy:
standard:
# 分片列名称
shardingColumn: id
# 分片算法名称
shardingAlgorithmName: inline_id
# 分片算法配置
shardingAlgorithms:
# 标准分片算法-行表达式分片算法
inline_id:
# 基于行表达式的分片算法
type: INLINE
props:
algorithm-expression: tb_user${id % 2}
# 属性配置
props:
sql-show: true
最后,启动项目
访问 http://localhost:8080/ 接口文档进行测试
访问 http://localhost:8080/h2 数据库控制台,输入用户名、密码,查看数据库信息
整合DEMO仓库地址