Примеры (можно также посмотреть тесты):
Создание и конфигурирование адаптера:
$db = new Core_Db_Adapter_Scale(array(
//список таблиц с которыми работает адаптер
"tables" => array(
"users" => array(
"scale"=> array(
// по какому полю ведется скайлинг
"field" => "id",
// какую стратегию использовать
"strategy" => "Core_Db_Adapter_Scale_Strategy",
),
"primary" => array(
// примари ключ
"field"=>"id",
// включить эмуляцию автоинкремента
"autogenerate" => true,
// стратегия генерации автонреметного ключа
"strategy" => "Core_Db_Adapter_Scale_Primary_Generate_Strategy"
)
),
),
// список шардов (соединений к БД в которых располагаются данные)
"shards" => array(
0 => Zend_Db::factory(
"Pdo_Mysql",
array(
"host" => "127.0.0.1",
"username" => "root",
"password" => "",
"dbname" => "test1",
)
),
1 => Zend_Db::factory(
"Pdo_Mysql",
array(
"host" => "127.0.0.1",
"username" => "root",
"password" => "",
"dbname" => "test2",
)
),
)));
Пример работы с стандартным ОРМ:
class Users extends Zend_Db_Table_Abstract {}
$Users = new Users($db);
$Users->insert(array("name"=>"joseph"));
$UsersRowset = $Users->find(1);
$User = $UsersRowset->current();
$User->name = "peter";
$User->save();
Тоесть никаких отличий для приложения нет. что используется стандарный один адаптер, что используется Core_Db_Adapter_Scale. Что позволяет гибко внедрять туда где он нужен без переписывания большой чати приложения.
Бонусы:
- Возможность хранить в таблице неограниченое количество строк милиарды и более
- Возможность хранить в таблице неограниченое количесво данных Тб и более
- Возможность гибко размазывать нагрузку по всем шардам
- Возможность задать неграниченое количество частей разбиения
- Адаптер не зависит от БД, в качестве шардом может использоваться любая БД
- Возможность задания пользовательского алгоритма выбора шарда
- Эмуляция автоикрементного поля
- Интерфейс Zend_Db_Adapter
Ограничения:
- Во всех запросах требутся использовать поле по которому идет скайлинг.
- Не возможность выборки всех данных из таблицы
- Не возможность JOIN
Исходные коды: #svn/trunk/Vendor/Core/Db/Adapter
Глосарий
Горизонтальная масштабируемость
Разбиение системы на более мелкие структурные компоненты и разнесение их по отдельным физическим машинам (или их группам) и/или увеличение количества серверов параллельно выполняющих одну и ту же функцию. Подробнее ...
Горизонтальная масштабируемость
Разбиение системы на более мелкие структурные компоненты и разнесение их по отдельным физическим машинам (или их группам) и/или увеличение количества серверов параллельно выполняющих одну и ту же функцию. Подробнее ...
13 коментарів:
1. А как происходит "Эмуляция автоикрементного поля" ? Для твоего примера - id юзера уникален среди всех шардов ? (ZF-код слабо понимаю)
2. Ну и передавать объекты _всех_ шардов при создании Core_Db_Adapter_Scale - по-моему излишне :)
Вообще шардинг - очень специфическая штука, очень сильно зависит от приложения. И сложно сделать его универсальным и эффективным на уровне фреймворка. ИМХО.
Спасибо, как раз вовремя =)
2Макс
Использую эту стратегию.
http://code.google.com/p/zrails/source/browse/trunk/Vendor/Core/Db/Adapter/Scale/Primary/Generate/Strategy.php
Сделано для более низкого старта.
Работает следующим образом:
1. генерится случаное число
2. ищется на шардах
3. если не нашлось возвращается
4. если нашлось - к шагу 1
Да реализация не очень красивая, но обеспечивает, то что требуется. И добратся до того момента когда эта реализация будет не оправдывать себя, еще нужно.
2Макс
>2. Ну и передавать объекты _всех_ шардов при создании Core_Db_Adapter_Scale - по-моему излишне :)
В лююбом случае. что для шардинга что для кластера, что мастер-слейв репликации нужно передавать все параметры конфигурации. Приложение должно знать где находится шард и иметь возможность к нему подключится.
>Вообще шардинг - очень специфическая штука, очень сильно зависит от приложения.
>И сложно сделать его универсальным и эффективным на уровне фреймворка. ИМХО.
Не совсем много приложений нуждаются в этом не не могут себе этого позволить. Вообще отлично шардятся любые простые запросы(без JOIN) главное только подумать предварительно как лучше разбивать на части.
Касательно сложности, не сказал бы. Да есть некоторые ограничения . которые нужно помнить, но они не так критичны по сравнению с тем что получаешь. А вообще тот же Hibernate horizontal scale-up (http://www.hivedb.org/)Дело просто в пониманиии, того как оно работает и реализации базовых инструментов. Инструменты обеспецивают низкий порог входа.
насчет псевдо-авто-инкремента.
С этой стратегии потом сложно слезть, потому что используемые id-шники размазаны по всему пространству int.
Например у нас для этих целей используется центральная база с таблицей с автоинкрементным полем (вобщем-то для этого можно было счетчик в memcachedb / redis использовать - у них inc атомарный)
Такая стратегия с центральынм счетчиком дает мне, знание что id от нуля до текущего значения была использована, а все что выше свободно.
И с такой стратегии на твою можно легко перепрыгнуть - в mt_rand($текущее_значение_счетчика+1, PHP_MAX_INT).
А вот с твоей стратегии на стратегию с центральынм счетчиком - не получится, потому что у тебя занятые id-ки размазаны случайным образом.
Вобщем-то ничего ужасного в ней нет, но ИМХО стратегия с отдельным сервером для автоинкремента более управляема.
> Касательно сложности, не сказал бы. Да есть некоторые ограничения . которые нужно помнить, но они не так критичны по сравнению с тем что получаешь.
Для реальной работы с шардами нужно написать немало системых утилит :
- которые бы умели создавать/удалять/альтерить таблицы на всех шардах
- умели считать нужную статистку по всем шардам
- умели переносить часть данных с одного шарда на другой (и очень желательно чтобы пользователи почти ничего не заметили)
Кроме того программист обязан следить за коннектами, которые открываются с веб-скриптов на шарды (более 2-х реальных коннектов к разным DB-серверам из веб-скрипта - имхо нонсенс).
А еще юзеры с разных DB-серверов могут как-то взаимодействовать на сайте и нужно писать на обе базы и обеспечить, чтобы обе транзакции прошли (ну здесь не столько шардинг, сколько
понимание что есть 2 транзакции которые должны пройти на разные сервера - но суть шардинга именно в понимании, что у нас много DB-серверов)
Я все это к тому, что чтобы шардинг заработал эффективно, он должен быть частью ядра системы, а не маленькой незаметной надстройкой над классом DB.
2Макс
А зачем вообще автоинрементное поле ?
Ты же не собирашься перебирать пользователей по id, или собирать какую-то статистику с этого автоинкремента. Он абсолютно - бесполезен. Основная задача получить уникальй UIN ключ для записи, вот и все. А как релизовать это можно например посмотреть доклад fisher-a на последнем highload.
Вообще с моей стретегии предполагатеся переходить к буквенно-цифровым ключам :) к примеру вот к такому sdfs-0909-sdf9-sdff-4353
Кастельно соединений к БД. Все зендовский конекторы поддреживают стратегию lazy-load. Тоесть непосредственно конект осущуетсяляется перед запросом. Если нет обращения к серверу - нет и соединения с сервером БД.
> что есть 2 транзакции которые должны пройти на разные сервера
Для этого может лучше использовать стандарные механизмы Replication. Хотя мне сложно судить о таком странном архитектурном решении.
>Для реальной работы с шардами нужно написать немало системых утилит
Никто и не спорит что когда-то это понадобится. Но все эти утилиты уже будт зависит непостредственно от архитектуры Вашего приложения. Следовательно реализовать их в какм бы то нибыло самом общем случае - просто глупо.
>Кроме того программист обязан следить за коннектами
Да, конечно. Но это зависит уже от того кто создает архитектуру. Продумать как сложить данные по шардам таким образом, чтоб в 99% случаев не приходилось бегать и искать данные по всем доступным шардам.
auto-increment имхо один из самых простых способ получить уникальные id-шки. Взяв новое значение автоинкремента я гарантировано (если правильно запрограммировать и настроить) получаю значение которое еще не использовалось в системе.
А в варианте с mt_rand() надо сгенерить уникальное число, проверить не занято ли оно, попытаться сохранить юзера с этим id. При этом теоретически 2 процесса могут получить одну id-шку, а значит если вставка не прошла из duplicate-entry именно для id, нужно перегенерить id-шку и снова попытаться вставить
Вобщем с авто-инкрементом код проще :)
А в чем преимущество буквенно-циферных ? больше диапазон и меньше вероятность попасть на занятую id-ку ? Все таки потеряшь немного на занимаемой памяти и производительности
> auto-increment имхо один из самых простых способ получить уникальные id-шки
С одной стороны простой. С другой ты получаешь точку отказа. И точку которая не поддается маштабированию вообще. Такми же образом между тем как ты получил значение и сделал автоинкремент может вклинится другой процесс и ты получишь проблему когда разные данные имеют один идентификатор. Но в отличие от рандома, растояние между 2 числами = 1 следовательно вероятность такой проблемы выше. Рамдом обеспечивает маштабируемость, и азмазывает равномерно все числа по всему доступному пространству.
> А в чем преимущество буквенно-циферных ?
Отсутсвие ограничения в 2^32 для поля типа int.
И да уже при превышении 1 милиарда записей, вероятность попать случано сгенерированым числом в заняую обсать - значительно возрастает.
> С другой ты получаешь точку отказа
Да, с этим согласен, но масштабирование придумать можно. Например поднять 2 сервиса на разных машинах. На одном сделать четные значения, на втором - нечетные. Выбирать случайным образом и инкрементить на 2. Хотя это я конечно уже выкручиваюсь :)
А вот с вклиниванием другого процесса в инкремент - ты не прав (как минимум для mysql, memcachedb и readis)
Memcachedb и readis - имеют атомарный инкремент. То есть ты просто шлешь на сервер
INC some_key 1
и тебе возвращается новое значение. Тебе не надо делать get, вручную добавлять единицу и потом set.
Для mysql есть как минимум 2 варианта (фактически 2 вариации с функцией last_insert_id()):
Создать какую-то таблицу с auto_increment полем, делать туда insert, и получать id вставленной записи через mysql_insert_id() или SELECT LAST_INSERT_ID().
Они возвращают последний id записи вставленной _в_этом_конкретном_mysql_соединении_
Второй вариант :
см
http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_last-insert-id
на тему :
CREATE TABLE sequence (id INT NOT NULL);
INSERT INTO sequence VALUES (0);
UPDATE sequence SET id=LAST_INSERT_ID(id+1);
SELECT LAST_INSERT_ID();
Насчет других БД - не знаю, но наверняка что-то есть (им же надо как-то с mysql конкурировать на рынке веб-приложений)
не readis - а redis (рука на автомате - привыкла слово "read" писать :))
mecached - ты наверное имел ввиду memcounter, так как memcache поддерживает только GET/SET/REPLACE
(http://www.danga.com/memcached/)
>Насчет других БД
Autoincrement - Это изобретение Mysql. Можно легко проверить посмотрев исходный код адаптеров Oracle, Postgres, DB2
> mecached - ты наверное имел ввиду memcounter, так как memcache поддерживает только GET/SET/REPLACE
http://ru2.php.net/memcache_increment
http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt (раздел Increment/Decrement)
и я писал именно про memcacheDB - который данные на диске хранит.
> Можно легко проверить посмотрев исходный код адаптеров Oracle, Postgres, DB2
А может адаптерам не повезло с авторами ? ;)
вот для постгрес нашел вот это:
http://www.postgresql.org/docs/8.1/static/functions-sequence.html
то есть там nextval() может заменить mysql-ный last_insert_id().
похожее есть и для oracle : http://blog.aggregatedintelligence.com/2008/12/oracle-sequences-and-concurrency.html
>А может адаптерам не повезло
Из всего можно выкрутится. Виртуальные таблицы содержащие последовательность, тригеры опять с теми же последовательностями.
Просто везде за исключением Mysql єто появилось позднее и выглядит как прикрученое сбоку 5 колесо.
Дописати коментар