[SpringBoot Advanced] SpringBoot は Sharding-JDBC サブデータベースとサブテーブルを統合します
アパッチ・シャーディングスフィア
Apache ShardingSphere (Incubator) は、オープン ソースの分散データベース ミドルウェア ソリューションで構成されたエコシステムであり、独立しているが混合展開が可能な 3 つの製品で構成されています。これらはすべて、標準化されたデータ シャーディング、分散トランザクション、およびデータベース ガバナンス機能を提供し、Java 同型、異種言語、クラウド ネイティブなどのさまざまなアプリケーション シナリオに適用できます。
ShardingSphere は、まったく新しいリレーショナル データベースを実装するのではなく、分散シナリオでリレーショナル データベースのコンピューティングおよびストレージ機能を十分かつ合理的に利用することを目的としたリレーショナル データベース ミドルウェアとして位置づけられています。変わらないものにフォーカスすることで、物事の本質を捉えます。リレーショナルデータベースは現在でも巨大な市場を占めており、各社の基幹事業の礎となっており、今後も揺るぎないものとなっておりますが、現段階では転覆ではなく、本来のベースでの伸びに注目しております。Apache の公式リリースは、バージョン 4.0.0 から始まります。
サブライブラリとサブテーブル
データベース内のデータの量は必ずしも制御可能ではありません. サブデータベースのサブテーブルがなければ, 時間とビジネスの発展に伴い, データベース内のテーブルがますます増え, テーブル内のデータの量も増加します. . 値が大きいほど、それに応じてデータ操作、追加、削除、変更、およびクエリのオーバーヘッドも増加します. また、分散配置ができないため、リソース (CPU、ディスク、メモリ、IO など)最終的には、データベースが処理できるデータ量とデータ処理能力がボトルネックになります。
サブデータベースサブテーブルは、過剰なデータ量によるデータベースのパフォーマンス低下の問題を解決するためのものです.元の独立したデータベースはいくつかのデータベースに分割され、大きなデータテーブルはいくつかのデータテーブルに分割され、単一のデータベースと単一データ データベースのパフォーマンスを向上させる目的を達成するために、テーブル内のデータ量が少なくなります。
サブデータベースサブテーブルのやり方
データベースのセグメンテーションとは、単一のデバイス、つまりサブデータベースの負荷を分散する効果を達成するために、特定の特定の条件を介して、同じデータベースに保存されているデータを複数のデータベース (ホスト) に分散することを指します。サブテーブル。
データセグメンテーションは、セグメンテーションルールの種類に応じて、垂直セグメンテーションと水平セグメンテーションに分けることができます
- 垂直セグメンテーション: 1 つのテーブルを複数のテーブルに分割し、それらを異なるデータベース (ホスト) に分散します。
- 水平分割: テーブル内のデータの論理関係に従って、テーブル内のデータは特定の条件に従って複数のデータベースに分割されます
縦割り
データベースは複数のテーブルで構成されており、各テーブルは異なる業務に対応しています.垂直セグメンテーションとは、テーブルを業務別に分類し、異なるデータベースに分散することで、データが異なるデータベース(専用データベース)に分散されるようにすることです.
垂直テーブル
データベースでテーブルを操作し、このテーブルのフィールド データの一部を新しいテーブルに保存してから、このテーブルのフィールド データの他の部分を別のテーブルに保存します。
垂直サブライブラリ
単一データベースを業務別に分割、専用データベーステーブル
垂直分割の利点は次のとおりです。
-
分割後の業務は明確で、システム間の統合や拡張も容易です。
-
プライズ テーブルは、コスト、アプリケーション レベル、アプリケーション タイプなどに応じてさまざまなマシンに配置されるため、管理が容易で、データのメンテナンスも簡単です。
垂直分割の欠点は次のとおりです。
-
一部のビジネス テーブルは関連付け (結合) できず、インターフェイスを介してのみ解決できるため、システムの複雑さが増します。
-
業務ごとに異なる制限があるため、単一データベースのパフォーマンスがボトルネックとなり、データの拡張とパフォーマンスの向上が困難になります。
-
トランザクション処理が複雑になります。
水平に分割
垂直セグメンテーションと比較して、水平セグメンテーションは、テーブルを分類するのではなく、特定のフィールドの特定のルールに従って複数のライブラリに分散させます.各テーブルにはデータの一部が含まれ、すべてのテーブルが合計されます.データ.
簡単に言えば、データの水平分割は、データ行に従って分割すること、つまり、テーブル内のいくつかの行を 1 つのデータベース テーブルに分割し、他の行を別のデータベース テーブルに分割することと理解できます。
水平サブライブラリ
レベルテーブル
水平分割の利点:
-
1 つのデータベースと 1 つのテーブルのデータは一定のレベルに保たれるため、パフォーマンスの向上に役立ちます。
-
分割されたテーブルの構造は同じで、アプリケーション層の変更はほとんど必要なく、ルーティング ルールを追加するだけで済みます。
-
システムの安定性と負荷容量の向上。
水平分割の欠点は次のとおりです。
-
分割後、データが散らばり、データベースの結合操作が使いづらく、クロスデータベース結合のパフォーマンスが低下します。
-
断片化されたトランザクションの一貫性は解決が難しく、データ拡張の難しさと維持は非常に大きいです。
サブデータベースとサブテーブルに起因する問題
- クロスノード結合に問題があります。
- クロスノード マージの並べ替えとページングの問題があります。
- 複数のデータ ソースの管理に問題がある
サブデータベースとサブテーブルのミドルウェア
現在、国内で使用されているサブデータベースとサブテーブル用のミドルウェアは数多くあります。主に次のものが含まれます。
- アパッチ・シャーディングスフィア
- 私の猫
シャーディング-JDBC
Sharding-JDBCはDangdang社が開発したオープンソースの分散型データベースミドルウェアで、バージョン3.0からSharding-Sphereに組み込まれ、その後Apacheインキュベーターに入り、バージョン4.0以降がApache版となります。メイヴン座標
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
Sharding-JDBC は、ShardingSphere の最初の製品であり、ShardingSphere の前身です。これは、Java の JDBC 層の上に追加のサービスを提供する軽量の Java フレームワークとして位置づけられています。クライアントを使用してデータベースに直接接続し、追加のデプロイメントや依存関係なしに jar パッケージの形式でサービスを提供します. JDBC ドライバーの拡張バージョンとして理解でき、JDBC およびさまざまな ORM フレームワークと完全に互換性があります.
Sharding-JDBC のコア機能は、データのシャーディングと読み取りと書き込みの分離です. Sharding-JDBC を介して、アプリケーションは透過的に jdbc を使用して、データベースとテーブルに分割され、読み取りと書き込みから分離された複数のデータ ソースにアクセスできます。データ ソースの数とデータの配布方法。
- JPA、Hibernate、Mybatis、Spring JDBC テンプレートなどの JDBC ベースの ORM フレームワークに適用するか、JDBC を直接使用します。
- DBCP、C3P0、BoneCP、Druid、HikariCP などのサードパーティ データベース接続プールをサポートします。
- JDBC 仕様を実装するすべてのデータベースがサポートされています。現在、MySQL、Oracle、SQLServer、PostgreSQL、および SQL92 標準に準拠する任意のデータベースをサポートしています。
sharding-jdbc は水平テーブル シャーディングを実装します
データベーススクリプト
デシベル:order_db_1
テーブル: t_order_1、t_order_2
CREATE TABLE `t_order_1` (
`id` int NOT NULL,
`order_type` int DEFAULT NULL,
`customer_id` int DEFAULT NULL,
`amount` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
ポンポン
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
注文
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private Integer orderType;
private Integer customerId;
private Double amount;
}
アプリケーション.yml
spring:
shardingsphere:
datasource:
# 配置数据源的名称
names: ds1
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
# 打开sql输出日志
props:
sql:
show: true
sharding:
tables:
# 逻辑表,SQL语句中写对应的逻辑表
# 如:insert into t_order(id,order_type,customer_id,amount) values(?,?,?,?)
# 虽然写的是添加数据到t_order表,实际会根据分表策略路由到t_order_1或t_order_2
t_order:
# 指定t_order表的分布情况,配置表在哪个数据库中,表名称是什么
actual-data-nodes: ds1.t_order_$->{
1..2}
# 指定t_order表里主键id生成策略
key-generator:
column: id
type: SNOWFLAKE
# 指定分片策略。根据id的奇偶性来判断插入到哪个表
table-strategy:
inline:
algorithm-expression: t_order_${
id%2+1}
sharding-column: id
検定試験
@SpringBootTest(classes = ShardingJdbcApplication.class)
public class OrderTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testSave() {
String saveSQL = "insert into t_order(id,order_type,customer_id,amount) values(?,?,?,?)";
ArrayList<Order> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Order(i, i, i,1000.0*i));
}
jdbcTemplate.batchUpdate(saveSQL, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
Order order = list.get(i);
preparedStatement.setInt(1,order.getId());
preparedStatement.setInt(2,order.getOrderType());
preparedStatement.setInt(3,order.getCustomerId());
preparedStatement.setDouble(4,order.getAmount());
}
@Override
public int getBatchSize() {
return list.size();
}
});
}
@Test
public void getOrder() {
String querySQL = "select * from t_order";
List<Order> orders = jdbcTemplate.query(querySQL, new BeanPropertyRowMapper<>(Order.class));
orders.forEach(System.out::println);
}
}
1.testSave()メソッドの実行結果
2.getOrderメソッドの実行結果
sharding-jdbc は、水平データベース シャーディングを実装します
デシベル
アプリケーション.yml
spring:
shardingsphere:
datasource:
# 配置不同的数据源
names: ds1,ds2
#配置ds1数据源的基本信息
ds1:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/order_db_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
#配置ds2数据源的基本信息
ds2:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/order_db_2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
#打开sql输出日志
props:
sql:
show: true
sharding:
tables:
# 逻辑表,必须配置正确,如:分表t_order_1,t_order_2,则逻辑表为 t_order
t_order:
#指定数据库表的分布情况
actual-data-nodes: ds$->{
1..2}.t_order_$->{
1..2}
#指定库分片策略,根据customer_id的奇偶性来添加到不同的库中,customer_id为基数路由到ds2,customer_id为偶数路由到ds1
database-strategy:
inline:
sharding-column: customer_id
algorithm-expression: ds$->{
customer_id%2+1}
#指定t_order表的主键生成策略
key-generator:
column: id
type: SNOWFLAKE
#指定表分片策略,根据id的奇偶性来添加到不同的表中,id为基数路由到ds2,id为偶数路由到ds1
table-strategy:
inline:
sharding-column: id
algorithm-expression: t_order_$->{
id%2+1}
ルーティング テストの検証
ルーティング キーが指定されていません
/**
* 不指定路由键,扫描全库全表查询汇总,效率低
*/
@Test
public void getOrder() {
String querySQL = "select * from t_order";
List<Order> orders = jdbcTemplate.query(querySQL, new BeanPropertyRowMapper<>(Order.class));
orders.forEach(System.out::println);
}
ライブラリ ルーティング キーを指定する
/**
* 指定库路由键
*/
@Test
public void getOrderByCustomerId() {
String querySQL = "select * from t_order where customer_id = ?";
List<Order> orders = jdbcTemplate.query(querySQL, new BeanPropertyRowMapper<>(Order.class), 2);
orders.forEach(System.out::println);
}
テーブル ルーティング キーを指定する
/**
* 指定表路由键
*/
@Test
public void getOrderById() {
String querySQL = "select * from t_order where id = ?";
List<Order> orders = jdbcTemplate.query(querySQL, new BeanPropertyRowMapper<>(Order.class), 2);
orders.forEach(System.out::println);
}
ライブラリ ルーティング キーとテーブル ルーティング キーを指定する
/**
* 指定库路由键和表路由键 效率最高
*/
@Test
public void getOrderByCustomerIdAndId() {
String querySQL = "select * from t_order where customer_id = ? and id = ?";
List<Order> orders = jdbcTemplate.query(querySQL, new BeanPropertyRowMapper<>(Order.class), 2,2);
orders.forEach(System.out::println);
}
sharding-jdbc は垂直データベース シャーディングを実装します
異なるデータ ノードに同じライブラリ order_db_1 を作成します。
ローカル データベースがあり、仮想マシンは docker を使用してデータベースを起動します
デシベル
docker pull mysql:5.7
docker run -d -p 3306:3306 --privileged=true -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
アプリケーション.yml
spring:
shardingsphere:
datasource:
# 配置不同的数据源
names: ds1,ds2
#配置ds1数据源的基本信息
ds1:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/order_db_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
#配置ds2数据源的基本信息
ds2:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://192.168.56.10:3306/order_db_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
#打开sql输出日志
props:
sql:
show: true
sharding:
tables:
# 逻辑表,必须配置正确
customer:
#配置customer表所在的数据节点
actual-data-nodes: ds2.customer
#指定orders表的主键生成策略
key-generator:
column: id
type: SNOWFLAKE
#指定表分片策略
table-strategy:
inline:
sharding-column: id
algorithm-expression: customer
# 逻辑表,SQL语句中写对应的逻辑表,必须配置正确,否则报表不存在异常
# 如:insert into t_order(id,order_type,customer_id,amount) values(?,?,?,?)
# 虽然写的是添加数据到t_order表,实际会根据分表策略路由到t_order_1或t_order_2
t_order:
#实际t_order表所在的数据库表
actual-data-nodes: ds1.t_order_1
垂直シャーディング戦略をテストする
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
private Integer id;
private String name;
}
/**
* 测试垂直分库
*/
@Test
public void testSaveCustomer() {
String saveCustomerSQL = "insert into customer(id,name) values(?,?)";
String saveOrderSQL = "insert into t_order(id,order_type,customer_id,amount) values(?,?,?,?)";
Customer customer = new Customer(10001,"李星云");
ArrayList<Order> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Order(i, i, customer.getId(),1000.0*i));
}
jdbcTemplate.batchUpdate(saveOrderSQL, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
Order order = list.get(i);
preparedStatement.setInt(1,order.getId());
preparedStatement.setInt(2,order.getOrderType());
preparedStatement.setInt(3,order.getCustomerId());
preparedStatement.setDouble(4,order.getAmount());
}
@Override
public int getBatchSize() {
return list.size();
}
});
jdbcTemplate.update(saveCustomerSQL, preparedStatement -> {
preparedStatement.setInt(1,customer.getId());
preparedStatement.setString(2, customer.getName());
});
}
サブデータベースとサブテーブルのまとめ
1.サブデータベースとサブテーブルにsharding-jdbcを使用する場合、ルールに従って対応するライブラリルーティングキーとテーブルルーティングキーを指定するのが最も効率的であるため、ルーティングキーは対応するテーブルのインデックスとして設定できますクエリ効率を改善します。開発中に 2 つのルーティング キーを設定することが本当に不可能な場合は、1 つを設定して、データベースとテーブル全体をスキャンしないようにしてください。低効率
2. spring.shardingsphere.sharding.tables.(logical table).actual-data-nodes=ds1.t_order_1 の構成に関して、
正しい論理テーブル ルーティング構成は次のようになります。