Шардинг - хранение разбитых по некоторому принципу данных на нескольких серверах. Например, хранение большой таблицы истории покупок в интернет магазине на 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 не поддерживает шардинг из коробки, было бы намного проще прописать несколько правил в конфигурации и не усложнять архитектуру уровня приложения.