Как увеличить счетчик в Кассандре?
Я бы хотел использовать Кассандру для хранения прилавка. Например, сколько раз данная страница была просмотрена. Счетчик никогда не уменьшится. Значение счетчика не обязательно должно быть точным, но оно должно быть точным со временем.
Моей первой мыслью было сохранить значение в виде столбца и просто прочитать текущий счетчик, увеличить его на единицу, а затем вставить обратно. Однако, если другая операция также пытается увеличить счетчик, я думаю, что окончательное значение будет просто один с последней отметкой времени.
Другой мыслью было бы сохранить каждую загрузку страницы как новый столбец в CF. Тогда я мог бы просто бежать get_count()
на этот ключ и получить количество столбцов. Читая документацию, кажется, что это не очень эффективная операция вообще.
Я неправильно подхожу к проблеме?
5 ответов
Счетчики были добавлены в Cassandra 0.8
Используйте метод incr, чтобы увеличить значение столбца на 1.
[default@app] incr counterCF [ascii('a')][ascii('x')];
Value incremented.
[default@app] incr counterCF [ascii('a')][ascii('x')];
Value incremented.
Опишите здесь: http://www.jointhegrid.com/highperfcassandra/?p=79
Или это можно сделать программно
CounterColumn counter = new CounterColumn();
ColumnParent cp = new ColumnParent("page_counts_by_minute");
counter.setName(ByteBufferUtil.bytes(bucketByMinute.format(r.date)));
counter.setValue(1);
c.add(ByteBufferUtil.bytes( bucketByDay.format(r.date)+"-"+r.url)
, cp, counter, ConsistencyLevel.ONE);
Описано здесь: http://www.jointhegrid.com/highperfcassandra/?cat=7
[Обновление] Похоже, поддержка счетчиков будет готова к прайм-тайм в 0.8!
Я определенно не буду использовать get_count, так как это операция O(n), которая запускается каждый раз, когда вы читаете "счетчик". Хуже, чем просто O(n), он может охватывать несколько узлов, что приведет к задержке в сети. И, наконец, зачем связывать все это дисковое пространство, если все, что вас волнует, это одно число?
На данный момент я бы вообще не использовал Кассандру для прилавков. Они работают над этой функциональностью, но она еще не готова в прайм-тайм.
https://issues.apache.org/jira/browse/CASSANDRA-1072
В то же время у вас есть несколько вариантов.
1) (Плохо) Сохраните ваш счет в одной записи, и один и только один поток вашего приложения будет отвечать за управление счетчиком.
2) (Лучше) Разделить счетчик на n осколков, и n потоков управляют каждым осколком как отдельным счетчиком. Вы можете рандомизировать, какой поток используется вашим приложением каждый раз для балансировки нагрузки без учета состояния между этими потоками. Просто убедитесь, что каждая нить отвечает только за один осколок.
3a) (Наилучший) Используйте отдельный инструмент, который является транзакционным (он же СУБД) или поддерживает атомарные операции приращения (memcached, redis).
[Update.2] Я бы не использовал распределенную блокировку (см. Мьютексы memcached и zookeeper), так как это очень нетерпимо к отказу узла или разбиению сети при неправильной реализации.
В итоге я использовал get_count() и кешировал результат в кешируемой колонке.
Таким образом, я мог получить общее предположение на счет, но все равно получить точное количество, когда бы я ни захотел.
Кроме того, я смог отрегулировать, насколько устаревшие данные я готов был принять для каждого запроса.
Мы собираемся решить аналогичную проблему, сохраняя текущее значение счетчика в распределенном кэше (например, memcached). Когда счетчик обновится, мы сохраним его значение в Кассандре. Поэтому, даже если какой-то узел кеша выйдет из строя, мы сможем получить значение из базы данных.
Это решение не идеально. Однако данные такого счетчика посещений не очень чувствительны, поэтому, на мой взгляд, допускаются незначительные несоответствия.
Интересно, что я не вижу никого, кто упомянул бы возможность рассчитывать для каждого приложения на компьютере. Скажем, ваше приложение работает на 5 машинах с именами a1, a2, ... a5. Затем вы можете иметь блокировку для каждой машины (т. Е. Файл, который вы открываете с помощью O_EXCL или используете блокировку для ожидания выполнения других экземпляров с помощью счетчика) и добавлять либо одну строку на машину, либо один столбец в зависимости от вашей реализации. Что-то вроде
machine_lock();
this_column_family[machine-name][my-counter] += 1;
machine_unlock();
Таким образом, вы получаете один счетчик на машину. Когда вам нужно общее количество, вы просто читаете a1, a2,... a5 и суммируете их.
total = 0;
foreach(machines as m) {
total += this_column_family[m][my-counter];
}
(это псевдокод, который более или менее будет работать с libQtCassandra.)
Таким образом, вы избегаете блокировки, которая блокирует все узлы, и при этом вы все равно получаете безопасный / последовательный подсчет (очевидно, что чтение + сумма не идеальна, и это только дает вам приблизительное значение, но оно все еще остается непротиворечивым).
Я не слишком уверен, что то, на что указал Бен Бернс в отношении n осколков и n нитей, было бы то же самое, но для меня это звучит не совсем так.
А начиная с версии 0.8.x вы можете использовать счетчики Cassandra, что, безусловно, намного проще, хотя не всегда может соответствовать вашим потребностям.