Memcounter

[главная] [changelog]

Что такое memcounter

memcounter — это высокопроизводительный демон для хранения счётчиков.

Почему memcounter, а не база данных? Потому memcounter быстрее, он хранит всё в памяти и оптимизирован под работу со счётчиками.

А чем memcached не устроил? memcached предназначен для кеширования данных, а не множества маленьких счётчиков к которым очень часто обращаются. Плюс memcounter позволяет за один запрос выполнить пакет команд. Например, в memcounter хранится статистика по баннеру с четырмя полями — показов всего (1), показов за сегодня (2), ограничение показов всего (3), ограничение показов в день (4). Мы показали баннер (с идентификатором 123) и хотим обновить счётчики. Всё это делается одним запросом из четырёх команд:

inc 1-123;inc 2-123;dec 3-123;dec 4-123;\n
а в ответ получаем значения счётчиков после внесения изменений:
1001;101;999;99;\n
хотите знать что было до внесения изменений, пожалуйста:
get 1-123;get 2-123;get 3-123;get 4-123;inc 1-123;inc 2-123;dec 3-123;dec 4-123;\n
а в ответ:
1000;100;1000;100;1001;101;999;99;\n
Прикольно, да? Сам тащусь ;)

Как это установить

Установить libevent http://www.monkey.org/~provos/libevent/ и Judy http://judy.sourceforge.net/ если они ещё не стоят.

Скачать memcounter-0.0.5.tar.gz.

gunzip memcounter-0.0.5.tar.gz
tar -xf memcounter-0.0.5.tar
cd memcounter-0.0.5
./configure
make
sudo make install
Если не получилось, пишем мне гневное письмо на saterenko ну тут понятное дело собака gmail.com. Не забываем в письмо вставить то что вывело на экран и ждём пока я отвечу. Либо разбираемся сами и всёравно пишем мне гневное письмо о том что случилось и как это полечилось.

Как это запускается

Сначала про опции запуска. Пишем memcounter -h и видим:

Usage: memcounter [options]
Options:
  -c <size>        Specify connection pool size (1024 by default)
  -d               Daemonize (not daemonized by default)
  -h               Display this information
  -l <ip>          Specify listen IP ("127.0.0.1" by default)
  -m <size>        Specify initial memory pool size in Mb (1Mb by default)
  -o <file>        Specify the file to read data ("./memcounter.dat" by default)
  -p <port>        Specify listen port (11711 by default)
  -P <file>        Specify the pid file ("./memcounter.pid" by default)
  -t               Just test options and exit
  -u <user>        Set process user (user not changed by default)
  -v               The program version
  -V               Verbose (off by default)
где:

Если при запуске выдаёт что-то типа

memcounter: error while loading shared libraries: libevent.so.1: cannot open shared object file: No such file or directory
или
memcounter: error while loading shared libraries: libJudy.so.1: cannot open shared object file: No such file or directory
пишем в консоли export LD_LIBRARY_PATH=/usr/local/lib

Какие команды оно понимает

Формат запроса следующий:

[команда 1] [идентификатор 1] [значение 1];[команда 2] [идентификатор 2] [значение 2];...\n
Параметр [значение] можно опускать, тогда для set оно принимает значение 0, для inc и dec — 1. Синтаксис является строгим. Перед командами не может быть никаких символов (между ; и командой тоже ничего не должно быть). Между командой и идентификатором должен быть пробел. В конце запроса должен быть символ \n. Допускается наличие символа \r. Не допускается использование идентификаторов частью которых являются другие идентификаторы. Например, одновременно не допускается использовать идентификаторы 1, 1-1, 1-1-1 и тп.

По команде flush данные сбрасываются в файл заданный опцией -o. Комманду flush можно использовать как саму по себе, так и в рамках пакетного запроса. Команда возвращает количество записанных пар идентификатор-значение. Команда flush выполняется последовательно, как и остальные команды.

Примеры:

set 1-2-3 10;set 3-2-1\n ответ: 10;0\n
dec 1-2-3;inc 3-2-1\n    ответ: 9;1\n
Как видно из примера, демон в ответ для каждой команды возвращает значение счётчика.

Ограничения:

Варианты ответов в случае ошибки:

EBC, EBS, ECS, EIS возвращают позицию в которой обнаружена ошибка (первый символ — 0). Например:
inc 1-1-1;int 1-1;get 1-1\n    ответ: EBC 10\n
Все E* ошибки прерывают выполнение запроса. I и N не прерывают выполнение запроса, а просто заменяют собой значение счётчика. Например:
inc 1-1-1;inc 1-1;get 1-1\n    ответ: 1;I;N\n

Маленький пример

Допустим, нам необходимо хранить счётчики заходов на сайт из определённой страны в определённое время суток. Таким образом у нас есть идентификатор сайта (site_id), идентификатор страны (country_id) и час (hour).

Запускаем демон:

memcounter -d
Сайт 10, страна 32, час 10:
inc 1-10;inc 2-10-32;inc 3-10-32-10\n                              ответ: 1;1;1;\n
Сайт 10, страна 15, час 10:
inc 1-10;inc 2-10-15;inc 3-10-15-10\n                              ответ: 2;1;1;\n
Сайт 10, страна 15, час 10:
get 1-10;get 2-10-15;get 2-10-32;get 3-10-15-10;get 3-10-32-10\n   ответ: 2;1;1;1;1;\n

Что с производительностью

Нормального тестирования не проводил, ограничился сурогатом на php:

<?php
	$time = time();
	
	$fp = fsockopen('localhost', 11711);
	if (!$fp) {
		die("cant open socket");
	}
	
	for ($i = 0; $i < 1000000; $i++) {
		fwrite($fp, "inc 1-".rand(1, 500000)." $i;\n");
	
		while (true) {
			$c = fgetc($fp);
			if ($c == "\n") {
				break;
			}
		}
	}
	
	fclose($fp);
	
	echo  1000000 / (time() - $time), " sec\n";
?>
получилось:
vinny$ php send1.php 
15384.615384615 sec
  PID COMMAND      %CPU   TIME   #TH #PRTS #MREGS RPRVT  RSHRD  RSIZE  VSIZE
17414 php         64.7%  0:35.51   1    15    30  1.14M  4.18M  3.02M  47.4M
17385 memcounter  41.6%  1:17.50   1     9    27  1.11M  1.21M  1.58M  28.8M
это на моём MacBook Pro:
Model Name:                 MacBook Pro 15"
Model Identifier:           MacBookPro1,1
Processor Name:             Intel Core Duo
Processor Speed:            2 GHz
Number Of Processors:       1
Total Number Of Cores:      2
L2 Cache (per processor):   2 MB
Memory:                     2 GB
Bus Speed:                  667 MHz
Boot ROM Version:           MBP11.0055.B08
SMC Version:                1.2f10
Serial Number:              W86430E1VWW
Sudden Motion Sensor:
  State:    Enabled
а это на нашем с trent-ом сервере (Dual Core AMD Opteron(tm) Processor 270 / 2Ghz / 2Gb RAM / Linux ghost 2.6.15-26-amd64-server #1 SMP Fri Sep 8 20:33:15 UTC 2006 x86_64 GNU/Linux), сервер под лёгкой нагрузкой по %CPU соотношение примерно как на ноуте:
vinny@ghost:~$ php send1.php 
23809.5238095 sec
Ещё раз, это не тест, это сурогат чтобы понять порядок производительности. Только щас заметил, — не "sec", а "req/sec".

По количеству "поглощаемой" памяти, на моём ноуте (32 бита) 10.000.000 записей (двухуровневый идентификатор типа 33-12345) заняло порядка 84Мб памяти. Сброс (flush) этого безобразия в файл занимает порядка 3-4 секунд, поднятие из файла тоже порядка 3-4 секунд. Файл занимает порядка 130Мб.

Задолбался писать

Вопросы и пожелания слать на saterenko ну тут понятное дело собака gmail.com.