воскресенье, 19 июля 2009 г.

Работа с memcachedb в JAVA.

Нет причин тормазить свое развитие.
Георгий Александров.
Здравствуйте друзья!!!
В этом посте я бы хотел вам рассказать про возможности использования memcachedb на java.

О memcachedb
Давайте начнем наш разговор с того, что разберемся, что же это за зверь такой memcachedb. В двух словах это такой вид субд, обращение к которой происходит через протокол memcached (интересная презентация на эту тему), а бэкэндом является BerkeleyDB. Более подробно можно прочитать здесь.

Установка и запуск
Ну что ж приступим к установке у себя на компьютере этой прелести. Сразу хочу оговориться, что устанавливать мы будем на Ubuntu, но на других платформах я думаю, никаких сложностей с установкой у вас не возникнет.
Для начала нам нужно установить libevent.
sudo apt-get install libevent1 libevent-dev
Теперь нам необходимо поставить BerkeleyDB. Исходники скачать можно здесь.
tar -xvf db-4.7.25.tar.gz
cd db-4.7.25/build_unix
../dist/configure
make
sudo make install
А вот теперь можно качнуть и поставить memcachedb. Исходники скачать можно здесь.
tar -xvf memcachedb-1.2.0.tar.gz
cd memcachedb-1.2.0
./configure
make
sudo make install
Ну вот мы все установили и теперь можем запустить.
memcachedb -d -v -u root -f some.db -N -H ~/memcachedb
Java клиент
Как я уже говорил ранее, интерфейсом для работы является memcached. Существует несколько клиентов для работы с memcached. Полный список клиентов можно посмотреть здесь. Я выбрал spymemcached, поэтому дальнейшее повествование пойдет о нем.

Demo application
Наконец-то мы все скачали, установили, запустили и подключили. Теперь можем написать небольшое демонстрационное приложение. Наше приложение будет некое подобие тестовой системы. Оно будет задавать пользователю вопрос, ждать пока пользователь ответит на вопрос и сверять правильно ли ответил пользователь на поставленный вопрос. Так как приложение мы пишем для демонстрации работы с memcachedb то, особо сильной логикой перегружать его не будем.
Немного расскажу как у нас будут хранится данные так как memcachedb является бд key=value то мы будем использовать так называемые пространства имен т.е.[namespace]:[key]=[value].
Ну что ж вроде все обговарили, код с комментариями в судию :)
  1. /**
  2. * Copyright (c) 2009 alekseiko alekseiko@gmx.com
  3. */
  4. package memcachedb.example.main;
  5. import java.io.BufferedReader;
  6. import java.io.IOException;
  7. import java.io.InputStreamReader;
  8. import java.net.InetSocketAddress;
  9. import net.spy.memcached.MemcachedClient;
  10. /**
  11. * This is main class for example.
  12. *
  13. * @author alekseiko
  14. *
  15. */
  16. public class Main {
  17. // Customize for memcachedb
  18. private static MemcachedClient client;
  19. private static final String SERVER_NAME = "localhost";
  20. private static final int SERVER_PORT = 21201;
  21. private static final int EXPIRED = 0;
  22. // Memcachedb namespace and key
  23. private static final String TASK_SEQUENCE = "taskSequence";
  24. private static final String TASK_QUESTION = "task_question:";
  25. private static final String TASK_ANSWERS = "task_answers:";
  26. private static final String TASK_TRUE_ANSWER = "task_true_answer:";
  27. // User command
  28. private static final String STOP_COMMAND = "stop";
  29. private static final String NEXT_TASK_COMMAND = "n";
  30. // Splitters
  31. private static final String ANSWER_SPLITTER = ",";
  32. private static final String ANSWER_NUMBER_SPLITTER = ") ";
  33. // Messages
  34. private static final String CONGRATULATION_MESSAGE = "Congratulation! " +
  35. "Enter 'n' for get next question";
  36. private static final String QUESTIONS_IS_COMPLETE_MESSAGE = "Question is" +
  37. " complete.";
  38. private static final String DATA_IS_CORRUPT_MESSAGE = "Data is corrupt.";
  39. private static final String BAD_ANSWER = "Sorry you answer is incorrect. " +
  40. "Try again.";
  41. /**
  42. * General logic for work with memcachedb and user i/o.
  43. *
  44. * @param args
  45. * @throws IOException
  46. */
  47. public static void main(String[] args) throws IOException {
  48. // Connect to memcachedb
  49. client = new MemcachedClient(new InetSocketAddress(SERVER_NAME,
  50. SERVER_PORT));
  51. // Hardcode fill memcachedb of tasks.
  52. fillMemcachedbOfTask();
  53. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  54. String msg = null;
  55. // Default currentTask=0 because task begin with 1
  56. int currentTaskId = 0;
  57. // Get last sequence value
  58. int maxTaskId = (Integer) client.get(TASK_SEQUENCE);
  59. while(!STOP_COMMAND.equals(msg = br.readLine())) {
  60. if (NEXT_TASK_COMMAND.equals(msg)) {
  61. if (currentTaskId >= maxTaskId) {
  62. System.out.println(QUESTIONS_IS_COMPLETE_MESSAGE);
  63. break;
  64. }
  65. currentTaskId++;
  66. // Get task from memcachedb.
  67. String question = (String) client.get(TASK_QUESTION + currentTaskId);
  68. String answers = (String) client.get(TASK_ANSWERS + currentTaskId);
  69. if (question !=null && answers != null) {
  70. // Out question
  71. System.out.println(question);
  72. // Split answers
  73. String[] answerList = answers.split(ANSWER_SPLITTER);
  74. // Out answers
  75. for (int i = 0;i < answerList.length;i++) {
  76. System.out.println((i+1) + ANSWER_NUMBER_SPLITTER
  77. + answerList[i]);
  78. }
  79. } else {
  80. // Out error message if data in memcachedb is corrupt.
  81. System.out.println(DATA_IS_CORRUPT_MESSAGE);
  82. break;
  83. }
  84. } else {
  85. // Get true answer
  86. String trueAnswer = (String) client.get(TASK_TRUE_ANSWER
  87. + currentTaskId);
  88. if ((trueAnswer != null) && (trueAnswer.equals(msg))) {
  89. // Out congratulation message if answer is true.
  90. System.out.println(CONGRATULATION_MESSAGE);
  91. } else {
  92. // Out bad message if answer is bad.
  93. System.out.println(BAD_ANSWER);
  94. }
  95. }
  96. }
  97. // Shutdown client
  98. client.shutdown();
  99. }
  100. private static void fillMemcachedbOfTask() {
  101. // Hardcode firs task
  102. // Set task question
  103. client.set(TASK_QUESTION + 1, EXPIRED, "What is your name?");
  104. // Set task answers
  105. client.set(TASK_ANSWERS + 1, EXPIRED, "Aleksei,Petia,Katia,Sveta");
  106. // Set true task
  107. client.set(TASK_TRUE_ANSWER + 1, EXPIRED, "Aleksei");
  108. // Hardcode second task
  109. // Set task question
  110. client.set(TASK_QUESTION + 2, EXPIRED, "Do you speak english?");
  111. // Set task answers
  112. client.set(TASK_ANSWERS + 2, EXPIRED, "yes,no");
  113. // Set true task
  114. client.set(TASK_TRUE_ANSWER + 2, EXPIRED, "yes");
  115. // Hardcode task sequence
  116. client.set(TASK_SEQUENCE, EXPIRED, 2);
  117. }
  118. }
Ну вот вроде и все, ничего сложного казалось бы нет. Выше приведенный код демонстрирует основные операции для работы с memcachedb. НО! есть одно НО! это работа с memcachedb в многопоточной среде. Простейшая операция увеличения sequence для получения id новой записи, реализованная следующим образом:
  1. int nextTaskId = (Integer) client.get(TASK_SEQUENCE);
  2. nextTaskId++;
  3. client.set(TASK_SEQUENCE, EXPIRED, nextTaskId);
может привести к коллизии.
Для того чтобы таких ситуаций не возникало существют incr/decr, инкримент и дикримент соответственно. Они увеличивают/уменьшают значение ключа и возвращают результат.
Что ж я вроде сказал все что хотел сказать.
Надеюсь мой небольшой очерк вам поможет...
Всем спасибо!

Интересные ссылки по теме:
Твиттер на основе Memcachedb и PHP
memcached на пальцах

Читать далее