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