PHP

Шардинг в MySQL

Шардинг - хранение разбитых по некоторому принципу данных на нескольких серверах. Например, хранение большой таблицы истории покупок в интернет магазине на 3х серверах, где выбор сервера зависит от первой цифры идентификатора пользователя.

Обычно этот вид масштабирования используется в последнюю очередь, когда количество данных растет и партиционирование уже не помогает.

Так как MySQL не поддерживает автоматического шардинга, его приходиться делать вручную. Ниже рассмотрим небольшой практический пример реализации.

Для начала опишем тип данных который будем хранить в базе, пусть это будет некоторый заказ сделанный пользователем интернет магазина:
Order
 - id: int
 - name: varchar
 - date: datetime
 - user_id: int
 - sum: float

Таким образом получим класс:
class Order {
     public $id;
     public $name;
     public $date;
     public $user_id;
     public $sum;

     public function __construct($name, $date, $user_id, $sum) {
          $this->name = $name;
          $this->date = $date;
          $this->user_id = $user_id;
          $this->sum = $sum;
     }
}

Для упрощения делаем все поля публичными, по-хорошему они должны быть приватными c геттерами и сеттерами.

Теперь перейдем к описанию класса, который будет заниматься сохранением объектов Order в базе данных:
class OrderStorage {
     public function insert(Order $order) {
          //добавить запись и вернуть объект с id
     }
     public function update() {
          //обновить объект
     }
     public function delete() {
          //удалить объект
     }
}

Если бы нашей целью НЕ был шардинг мы бы могли на этом остановиться и использовать эти 2 класса для работы с данными:
$storage = new OrderStorage();

$someOrder = new Order('test order1', date('Ymd'), 1, 100);
$storage->insert($someOrder);

Но нам еще нужно прикрутить выбор сервера в зависимости от идентификатора пользователя. Для этого создадим синглтон класс ShardingStragegy, который будет выдавать соединение к базе данных в зависимости от значений свойств объекта Order.
class ShardingStragegy {
    protected static $instance = null;
    protected $server1;
    protected $server2;

    protected function __construct() {
         $this->server1 = mysql_connect('server1', 'user1', 'pass1');
         $this->server2 = mysql_connect('server2', 'user2', 'pass2');
    }

    public static function getInstance() {
        if (static::$instance == null) {
            static::$instance = new self();
        }

        return static::$instance;
    }

    public function getConnection(Order $order) {
         $server = $this->server1;
         if ($order->user_id % 2 == 0) $server = $this->server2; 
         return $server;
    }
}

При создании инстанса подключаемся к серверам, а при вызове метода getConnection, в зависимости от парности идентификатора пользователя, возвращаем нужное соединение.

Осталось добавить в OrderStorage метод который будет выполнять запрос на правильном сервере и заиспользовать его в insert/update/delete:
class OrderStorage {
     protected function runQuery($query, Order $order) {
          mysql_query($query, ShardingStragegy::getInstance()->getConnection($order));
     }

     public function insert(Order $order) {
          //добавить запись и вернуть объект с id
          $this->runQuery("insert lalalal", $order);
          return mysql_insert_id();
     }

     public function update(Order $order) {
          //обновить объект
          $this->runQuery("update lalala", $order);
     }

     public function delete(Order $order) {
          //удалить объект
          $this->runQuery("delete lalalal", $order);
     }
}

Вот и все, данные заказов будут распределяться в зависимости от идентификатора пользователя.

Если же мы захотим шардить еще например и пользователей, класс ShardingStragegy можно запросто расширить, не усложняя структуру:
abstract class ObjectShardingStrategy {
     abstract public function getConnection($object, $server1, $server2);
}

class OrderShardingStrategy extends ObjectShardingStrategy {
     public function getConnection($order, $server1, $server2) {
          $server = $this->server1;
          if ($order->user_id % 2 == 0) $server = $this->server2; 
          return $server;
     }
}

class UserShardingStrategy extends ObjectShardingStrategy {
     public function getConnection($user, $server1, $server2) {
          $server = $this->server1;
          if ($user->id % 2 == 0) $server = $this->server2; 
          return $server;
     }
}

class ShardingStragegy {
    protected static $instance = null;
    protected $server1;
    protected $server2;

    protected function __construct() {
         $this->server1 = mysql_connect('server1', 'user1', 'pass1');
         $this->server2 = mysql_connect('server2', 'user2', 'pass2');
    }

    public static function getInstance() {
        if (static::$instance == null) {
            static::$instance = new self();
        }
        return static::$instance;
    }

    public function getConnection($object) {
         $strategy = null;

         if ($object instanceof Order) $strategy = new OrderShardingStrategy();
         elseif ($object instanceof User) $strategy = new UserShardingStrategy();

         if ($strategy == null) throw new Exception('No strategy found');

         return $strategy->getConnection($object, $server1, $server2);
    }
}

Логика выбора сервера перенесена в отдельные для каждого класса стратегии OrderShardingStrategy и UserShardingStrategy, а ShardingStragegy просто выбирает необходимую стратегию на основе типа объекта.

Конечно жаль что MySQL не поддерживает шардинг из коробки, было бы намного проще прописать несколько правил в конфигурации и не усложнять архитектуру уровня приложения.
Добавлено: 11 Сентября 2013 02:52:18 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...