Java 中的 Sharding 模式:掌握水平分区以提高应用程序吞吐量
也称为
- 数据分区
- 水平分区
Sharding 设计模式的意图
Sharding 是一种重要的 Java 设计模式,它通过水平分区显著提高数据库的可扩展性和性能。
Sharding 模式的详细解释,附带现实世界示例
现实世界示例
假设一个大型电子商务网站拥有数百万用户和交易。为了处理海量数据并确保系统保持响应,用户数据被分片到多个数据库服务器上。例如,ID 以 0-4 结尾的用户可能存储在一个服务器上,而以 5-9 结尾的用户存储在另一个服务器上。这种分布允许系统通过将读写操作并行化到多个服务器上,来处理更高的负载。
通俗地说
将 Web 应用程序中的处理逻辑与视图分离,以提高可维护性和可扩展性。
维基百科说
水平分区是一种数据库设计原则,其中数据库表中的行被单独存储,而不是被拆分为列(这是规范化和垂直分区的做法,程度有所不同)。每个分区构成一个分片的一部分,而分片又可能位于不同的数据库服务器或物理位置。
水平分区方法有很多优点。由于表被分割并分布到多个服务器上,每个数据库中每个表中的总行数减少了。这减少了索引大小,通常会提高搜索性能。数据库分片可以放在单独的硬件上,多个分片可以放在多台机器上。这使得数据库能够分布在大量机器上,从而大大提高性能。此外,如果数据库分片基于数据的某些真实世界分割(例如,欧洲客户与美国客户),那么可能能够轻松自动地推断出适当的分片成员身份,并仅查询相关分片。
Java 中 Sharding 模式的编程示例
Sharding 是一种数据库分区类型,它将非常大的数据库分离成更小、更快、更易于管理的部分,称为数据分片。Shard 这个词的意思是整体的一小部分。在软件架构中,它指的是数据库或搜索引擎中的水平分区。每个单独的分区称为分片或数据库分片。
在给定的代码中,我们有一个 ShardManager
类来管理分片。它有两个子类 HashShardManager
和 RangeShardManager
,它们实现了不同的分片策略。Shard
类代表存储数据的分片。Data
类代表要存储在分片中的数据。
ShardManager
是一个抽象类,它提供了管理分片的基本结构。它有一个 storeData
方法,用于将数据存储在分片中,以及一个 allocateShard
方法,用于确定将数据存储在哪个分片中。allocateShard
方法是抽象的,必须由子类实现。
public abstract class ShardManager {
protected Map<Integer, Shard> shardMap = new HashMap<>();
public abstract int storeData(Data data);
protected abstract int allocateShard(Data data);
}
HashShardManager
是 ShardManager
的一个子类,它实现了基于哈希的分片策略。在 allocateShard
方法中,它计算数据键的哈希值,并使用它来确定将数据存储在哪个分片中。
public class HashShardManager extends ShardManager {
@Override
protected int allocateShard(Data data) {
var shardCount = shardMap.size();
var hash = data.getKey() % shardCount;
return hash == 0 ? hash + shardCount : hash;
}
}
RangeShardManager
是 ShardManager
的另一个子类,它实现了基于范围的分片策略。在 allocateShard
方法中,它使用数据类型来确定将数据存储在哪个分片中。
public class RangeShardManager extends ShardManager {
@Override
protected int allocateShard(Data data) {
var type = data.getType();
return switch (type) {
case TYPE_1 -> 1;
case TYPE_2 -> 2;
case TYPE_3 -> 3;
};
}
}
Shard
类代表一个分片。它有一个 storeData
方法,用于将数据存储在分片中,以及一个 getDataById
方法,用于通过 ID 从分片中检索数据。
public class Shard {
@Getter
private final int id;
private final Map<Integer, Data> dataStore;
public Shard(final int id) {
this.id = id;
this.dataStore = new HashMap<>();
}
public void storeData(Data data) {
dataStore.put(data.getKey(), data);
}
public Data getDataById(final int id) {
return dataStore.get(id);
}
}
Data
类代表要存储在分片中的数据。它有一个键、一个值和一个类型。
@Getter
@Setter
public class Data {
private int key;
private String value;
private DataType type;
public Data(final int key, final String value, final DataType type) {
this.key = key;
this.value = value;
this.type = type;
}
enum DataType {
TYPE_1, TYPE_2, TYPE_3
}
}
这是示例的 main
函数,它演示了三种不同的分片策略:查找、范围和哈希。每种策略都以不同的方式确定将数据存储在哪个分片中。查找策略使用查找表,范围策略使用数据类型,哈希策略使用数据键的哈希值。
public static void main(String[] args) {
var data1 = new Data(1, "data1", Data.DataType.TYPE_1);
var data2 = new Data(2, "data2", Data.DataType.TYPE_2);
var data3 = new Data(3, "data3", Data.DataType.TYPE_3);
var data4 = new Data(4, "data4", Data.DataType.TYPE_1);
var shard1 = new Shard(1);
var shard2 = new Shard(2);
var shard3 = new Shard(3);
var manager = new LookupShardManager();
manager.addNewShard(shard1);
manager.addNewShard(shard2);
manager.addNewShard(shard3);
manager.storeData(data1);
manager.storeData(data2);
manager.storeData(data3);
manager.storeData(data4);
shard1.clearData();
shard2.clearData();
shard3.clearData();
var rangeShardManager = new RangeShardManager();
rangeShardManager.addNewShard(shard1);
rangeShardManager.addNewShard(shard2);
rangeShardManager.addNewShard(shard3);
rangeShardManager.storeData(data1);
rangeShardManager.storeData(data2);
rangeShardManager.storeData(data3);
rangeShardManager.storeData(data4);
shard1.clearData();
shard2.clearData();
shard3.clearData();
var hashShardManager = new HashShardManager();
hashShardManager.addNewShard(shard1);
hashShardManager.addNewShard(shard2);
hashShardManager.addNewShard(shard3);
hashShardManager.storeData(data1);
hashShardManager.storeData(data2);
hashShardManager.storeData(data3);
hashShardManager.storeData(data4);
shard1.clearData();
shard2.clearData();
shard3.clearData();
}
最后,这是程序输出
18:32:26.503 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=1, value='data1', type=TYPE_1} is stored in Shard 2
18:32:26.505 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=2, value='data2', type=TYPE_2} is stored in Shard 2
18:32:26.505 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=3, value='data3', type=TYPE_3} is stored in Shard 1
18:32:26.505 [main] INFO com.iluwatar.sharding.LookupShardManager -- Data {key=4, value='data4', type=TYPE_1} is stored in Shard 1
18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=1, value='data1', type=TYPE_1} is stored in Shard 1
18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=2, value='data2', type=TYPE_2} is stored in Shard 2
18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=3, value='data3', type=TYPE_3} is stored in Shard 3
18:32:26.506 [main] INFO com.iluwatar.sharding.RangeShardManager -- Data {key=4, value='data4', type=TYPE_1} is stored in Shard 1
18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=1, value='data1', type=TYPE_1} is stored in Shard 1
18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=2, value='data2', type=TYPE_2} is stored in Shard 2
18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=3, value='data3', type=TYPE_3} is stored in Shard 3
18:32:26.506 [main] INFO com.iluwatar.sharding.HashShardManager -- Data {key=4, value='data4', type=TYPE_1} is stored in Shard 1
何时在 Java 中使用 Sharding 模式
- 在处理超出单个数据库容量的大型数据集时使用。
- Sharding 非常适合需要强大可扩展性的 Java 应用程序,它通过有效地分配数据库负载来提高性能。
- 对于需要高可用性和容错性的应用程序很有用。
- 在读写操作可以在分片之间并行化的环境中有效。
Java 中 Sharding 模式的现实世界应用
- 分布式数据库,例如 Apache Cassandra、MongoDB 和 Amazon DynamoDB。
- 大型 Web 应用程序,例如社交网络、电子商务平台和 SaaS 产品。
Sharding 模式的优缺点
优点
- 通过分配负载来提高性能。
- 通过允许水平扩展来提高可扩展性。
- 通过将故障隔离到单个分片来提高可用性和容错性。
权衡
- 管理和维护多个分片的复杂性。
- 随着数据增长,重新平衡分片可能存在挑战。
- 跨分片查询的延迟增加。
相关的 Java 设计模式
- 缓存:可与 Sharding 结合使用,以进一步提高性能。
- 数据映射器:有助于抽象和封装数据库交互的细节,这在分片环境中可能很复杂。
- 存储库:提供了一种以集中方式管理数据访问逻辑的方法,这在处理多个分片时非常有用。
- 服务定位器:可用于在分布式系统中查找和交互不同的分片。