分片模式 Sharding Pattern
描述
将一个数据存储到一组水平分区或碎片。存储和访问大量数据时,这个模式可以提高可扩展性。
背景和问题
单一的服务器的数据存储会受到一些限制。
1. 存储空间。
2. 计算资源。
3. 网络带宽。
4. 地理。
垂直扩展和水平扩展
垂直扩展
通过增加更多的磁盘容量来实现。虽然增加了容量,但是还是会受到处理器,内存,宽带的影响。
水平扩展
通过增加更多的服务器来实现。理论上这样的扩展几乎是无限的。
解决方案
把数据分配到水平分区或碎片上。
优点
- 易扩展。添加额外的存储节点即可。
- 成本低。对可扩展的服务器要求较低。
- 云服务器距离用户更近。
- 资源之间的竞争更少。
分片策略
1. 查找策略。
实现
维护请求路由和物理分区之间的映射。
优点
添加新的物理分区的时候,不用修改应用程序的代码,直接维护请求路由和物理分区之间的映射即可。
缺点
查找碎片的位置需要额外的性能开销。
2. Range范围。
实现
不同的时间段的数据放到不同的分区中。
优点
容易实现和使用范围查询好。
缺点
解决不了分配不均衡的问题,大多数活跃的数据分片都是相邻的。
3. Hash哈希。
实现
通过哈希函数计算出请求路由和物理分区的关系。
优点
通过计算即可得到结果,没有必要来维护它们的映射。
缺点
计算哈希会有额外的性能开销。添加新的物理分区需要重新平衡所有碎片,成本较大。
4. 按照自己业务设计的策略。VIP用户使用高性能数据分区,普通用户使用普通分区等。
注意事项
- 分区的数据要保证均匀分布。
- 切割分片的维度要稳定。
- 要确保分区数据id是唯一的。雪花算法是一个不错的选择。
何时使用
当数据存储的要求超过单个服务器的资源时。
结构中包含的角色
- Shard 分片抽象
- ConcreteShard 具体分片
- ShardStrategy 抽象分片策略
- ConcreteShardStrategy 具体分片策略
- ShardFacade 分片门面
- Application 应用程序
可用到的设计模式思维
- 分片使用到分片策略。这个点可以使用策略模式处理。
- 整个流程可以看成是应用程序与存储系统的交互。存储系统下有很多存储子系统(分片应用),这里可以使用门面(外观)模式处理。
最小可表达代码
// 分片抽象
abstract class Shard
{
protected $number;
public function __construct($number)
{
$this->number = $number;
}
public function getNumber()
{
return $this->number;
}
}
// 具体分片
class ConcreteShard extends Shard {}
// 抽象分片策略类
abstract class ShardStrategy
{
protected $shards = [];
public function __construct($shards)
{
$this->shards = $shards;
}
public abstract function algorithm($requestKey) : Shard;
}
// 具体分片策略类
class HashConcreteShardStrategy extends ShardStrategy
{
public function algorithm($requestKey) : Shard
{
$number = $this->makeHash($requestKey);
foreach ($this->shards as $shard) {
if ($number == $shard->getNumber()) {
return $shard;
}
}
exit('找不到分片,报错!');
}
protected function makeHash($requestKey)
{
return $requestKey * 2;
}
}
// 分片门面
class ShardFacade
{
private $shardStrategy;
public function __construct()
{
$this->shardStrategy = new HashConcreteShardStrategy(
[
new ConcreteShard(2), new ConcreteShard(4),
]
);
}
public function selectShard($id) : Shard
{
return $this->shardStrategy->algorithm($id);
}
public function getNumberById($id)
{
return $this->selectShard($id)->getNumber();
}
}
// 应用程序
class Application
{
public function getNumberById($id)
{
return (new ShardFacade)->getNumberById($id);
}
}
// 运行
$id = 1;
$number = (new Application)->getNumberById($id);
var_dump($number);
推荐文章: