вторник, декабря 21, 2010

Установка Sphinx 1.10-beta в Fedora 13

Shpinx 0.9.9-1 есть в репозиториях, поэтому для установки этой версии достаточно yum install sphinx

Для установки Sphinx 1.10-beta выкачиваем здесь, или из svn. Смотрим мануал по установке
Будем ставить по максимуму, то есть w/MySQL+PgSQL+libstemmer+id64 support. Что должно быть:
yum install mysql-server mysql-devel postgresql postgresql-devel gcc-c++
Далее 4 простых шага:
1. Выкачиваем sphinx-1.10-beta.tar.gz, распаковываем
wget http://sphinxsearch.com/downloads/sphinx-1.10-beta.tar.gz
tar xzvf sphinx-1.10-beta.tar.gz
cd sphinx-1.10-beta
2. Выкачиваем libstemmer_c.tgz, распаковываем в папку libstemmer_c внутри sphinx-1.10-beta
wget http://snowball.tartarus.org/dist/libstemmer_c.tgz
tar xzvf libstemmer_c.tgz
3. Конфигурируем по максимуму
./configure --prefix=/usr/local/sphinx --with-mysql --with-mysql-includes=/usr/include/mysql --with-mysql-libs=/usr/lib/mysql --with-pgsql --with-pgsql-includes=/usr/include --with-pgsql-libs=/usr/lib/pgsql --enable-id64 --with-libstemmer
4. make
5. make install

6. Создаем /etc/init.d/searchd или берем уже установленный от 0.9.9-1 и корректируем путь к конфигу
#!/bin/sh
#
# sphinx searchd Free open-source SQL full-text search engine
#
# chkconfig:   - 20 80
# description: Starts and stops the sphinx searchd daemon that handles \
#        all search requests.

### BEGIN INIT INFO
# Provides: searchd
# Required-Start: $local_fs $network
# Required-Stop: $local_fs $network
# Should-Start: $remote_fs
# Should-Stop: $remote_fs
# Default-Start: 
# Default-Stop: 0 1 2 3 4 5 6
# Short-Description: start and stop sphinx searchd daemon
# Description: Sphinx is a free open-source SQL full-text search engine     
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

exec="/usr/local/sphinx/bin/searchd"
prog="searchd"
config="/usr/local/sphinx/etc/sphinx.conf"
username="sphinx"

lockfile=/var/lock/subsys/searchd

start() {
    [ -x $exec ] || exit 5
    [ -f $config ] || exit 6
    echo -n $"Starting $prog: "
    # if not running, start it up here, usually something like "daemon $exec"
    daemon --user=$username $exec --config $config
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    # stop it here, often "killproc $prog"
    killproc $prog
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    stop
    start
}

reload() {
    restart
}

force_reload() {
    restart
}

rh_status() {
    # run checks to determine if the service is running or use generic status
    status $prog
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}


case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
        restart
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
        exit 2
esac
exit $?



Все, осталось переименовать и поправить конфиг в /usr/local/sphinx/etc/sphinx.conf
проиндексировать
indexer --all
и запустить сервис
service searchd start

PS: к сожалению, обнаружил неприятную багу (симптомы аналогичны, кажется проявляется только для type = pgsql) с индексатором в sphinx-1.10-beta и в текущей версии. При этом indexer из 0.9.9 работает как надо. Приходится searchd использовать из sphinx-1.10-beta (Server version: 1.10-id64-beta (r2420)) или текущей development версии (Server version: 1.11-id64-dev (r2617)), а indexer из 0.9.9.
Другая проблема - работа sphinx на протоколе mysql41 с mysql jdbc-драйвером. В текущей development версии (Server version: 1.11-id64-dev (r2617)) он просто виснет на DriverManager.getConnection. При этом в версии sphinx-1.10-beta (Server version: 1.10-id64-beta (r2420)) все нормально.

понедельник, декабря 20, 2010

Scala/Lift/Mapper и SphinxQL 1.10-beta

Вчера возникла задача подключить Sphinx для полнотекстового поиска к проекту на Scala\Lift. Поскольку начиная с версии 0.0.9 Sphinx поддерживает работу по протоколу MySQL 10 версии (не забываем listen = localhost:3307:mysql41 в конфигурации Sphinx), то логичным было подключить его через стандартный jdbc-драйвер mysql MySQL Connector/J версии 5.1.14 и использовать в запросах SphinxQL. Как обычно бывает, сразу все не завелось даже в самом элементарном случае, но опыт других (раз, два), подсказал направление движения. Как стало ясно, дополнительные параметры characterEncoding и maxAllowedPacket в строке подключения "jdbc:mysql://127.0.0.1:3307?characterEncoding=utf8&maxAllowedPacket=512000" сделают свое дело. Однако попытка определения стандартного StandardDBVendor в Lift с такой строкой подключения неизменно приведет к Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: MySQL Versions Older than 3.23.15 do not support transactions. Ковыряние в сырцах com.mysql.jdbc.Connection показало наличие в public void setAutoCommit(boolean autoCommit) кидания new SQLException("MySQL Versions Older than 3.23.15 "+"do not support transactions", "08003") при условии transactionsSupported!=true && autoCommit == false && this.relaxAutoCommit == false. Сырцы net.liftweb.mapper, в частности StandardDBVendor, ProtoDBVendor показали что имеется многократное setAutoCommit(false) в разных местах, как то: DB.newConnection, ProtoDBVendor.testConnection, ProtoDBVendor.newConnection и т.д. Переписывание классов показалось нецелесообразным и дополнительное исследование параметров jdbc-драйвера mysql показало наличие параметра relaxAutoCommit, которое дословно отвечает за "If the version of MySQL the driver connects to does not support transactions, still allow calls to commit(), rollback() and setAutoCommit() (true/false, defaults to 'false')?" и по умолчанию его значение равно false. Параметр работает, добавление relaxAutoCommit=true решает проблему.
Следующий тест должен проходить:

import java.sql.*;

public class TestSetAutoCommitWithSphinx {

      public static void main(String[] args) throws SQLException {
  Connection con = null;
  String driver = "com.mysql.jdbc.Driver";
  String user = "root";
  String pass = "";
  ResultSet rs = null;
  ResultSetMetaData rsmd = null;

  try {
   Class.forName(driver);
  } catch (Exception e) {
   e.printStackTrace();
  }
        // relaxAutoCommit important if you setAutoCommit(false) with SphinxQL
  con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3307?characterEncoding=utf8&maxAllowedPacket=512000&relaxAutoCommit=true", user, pass);

        // This throw com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: MySQL Versions Older than 3.23.15 do not support transactions
        // without relaxAutoCommit=true in connectionstring 
        con.setAutoCommit(false);

        Statement s = con.createStatement();
        rs =s.executeQuery("select @id as search_id, @weight as search_weight from phoneindex WHERE MATCH('John')");

  rsmd = rs.getMetaData();
  int cc = rsmd.getColumnCount();
  for (int i = 1; i <= cc; i ++)
   System.out.println(rsmd.getColumnName(i));

  while (rs.next()) {
   System.out.println("Id: " + rs.getInt(1) + ", Weight: " + rs.getInt(2));
  }
  rs.close();
 }
}
Cледующая проблема с интеграцией Lift/Mapper и SphinxQL - это реализация протокола mysql в SphinxQL, которая возвращает resultset с полями, имеющими sqltype=-2, то есть BINARY. При меппинге бинарных полей методами Lift/Mapper типа DB.exec и DB.runQuery вызывается метод resultSetTo(rs: ResultSet): (List[String], List[List[String]]), который вызовом DB.asString приводит все в строки. От приведения BINARY в строку через RecordSet.getObject ничего хорошего не получается. Единственные методы, которые возвращают честный ResultSet это def exec[T](db: SuperConnection, query: String)(f: (ResultSet) => T): T и def exec[T](statement: PreparedStatement)(f: (ResultSet) => T): T, поэтому имеется только две конструкция с использованием net.liftweb.mapper.DB, которые будут работать:
DB.use(EventTextSearchDB) { conn =>
          DB.exec(conn,"select * from eventindex WHERE MATCH('Это')") { rs =>
            while (rs.next) {
              // rs.getInt(1) for 32bit doc_id, rs.getLong(1) for 64bit doc_id
              println("Id: " + rs.getLong(1) + ", Weight: " + rs.getInt(2))
            }
          }
          DB.prepareStatement("select * from eventindex WHERE MATCH(?)",conn) { stmt =>
            stmt.setString(1,"Это")
            val rs=stmt.executeQuery
            while (rs.next) {
              println("Id: " + rs.getLong(1) + ", Weight: " + rs.getInt(2))
            }
          }
        }

Кажется все.

PS: Может быть поиск решения работы с Sphinx из Scala/Lift/Mapper и не стоил того - проще было прикрутить любой имеющийся connection pool и сделать свои обертки, но стандартизация требует жертв.

вторник, декабря 14, 2010

VirtualBox 2.6.34.7-63.fc13.i686.PAE VBoxVMM.so: cannot open shared object file

Изредка тестирую web-приложения в Windows под VirtualBox. Сегодня обнаружил, что он перестал запускаться с сообщением VirtualBox: supR3HardenedMainGetTrustedMain: dlopen("/usr/lib/virtualbox/VirtualBox.so",) failed: VBoxVMM.so: cannot open shared object file: No such file or directory

Помогло
find /usr/lib/virtualbox/ -name \*.so -exec ln -s -t /lib '{}' ';'
И не надо забывать чтобы пользователь был в группе vboxusers

понедельник, ноября 15, 2010

Scala/Lift и аналог if из http://java.sun.com/jsp/jstl/core taglib

Создатель Lift framework David Pollak, так же как и создатели Wicket категорически против вычисляемых выражений в разметке шаблонов. С ними, судя по реализации, не согласны авторы JSP с jstl. Лично мне кажется что истина где-то посредине и плохо иметь невалидные шаблоны, также как и плохо не иметь возможности использования небольшого количества простых вычисляемых конструкций в шаблонах. Тут как всегда важен баланс и мера.
Вот допустим стоит простая практическая задача - разное меню для авторизованного и не авторизованного пользователя. Конечно, этот вопрос может быть решен совершенно разными способами, самый прямой из которых - отдельный сниппет:
def userMenu(in: NodeSeq): NodeSeq = {

    User.loggedIn_? match {
      case true => Text(" userLoggedInMenu markup")
      case false => Text("userNotLoggedInMenu markup")

    }}
В коде шаблон и из файла можно взять или динамически составить, да по сути что угодно сделать. Но иногда хочется чтобы все было в разметке, потому что из вычислений, по большому счету, только одно сравнение, а переносить из-за этого все в код не кажется разумным. Явно не хватает аналога if из taglib http://java.sun.com/jsp/jstl/core. На самом деле, со снипеттами это совершенно не сложно и следующая конструкция даст вполне жизнеспособный вариант:
class UserSnippets  {

  def userLoggedIn(in: NodeSeq): NodeSeq = {
     bind("userLoggedIn",in, if (User.loggedIn_?) "true"  -> {node:NodeSeq => node} else "true" -> Text(""),
                             if (User.loggedIn_?) "false" -> Text("") else "false" -> {node:NodeSeq => node})
  }}

Теперь в разметке можно написать:



<lift:usersnippets.userloggedin>
   <userloggedin:true>
         userLoggedInMenu markup
  </userloggedin:true>
  
  <userloggedin:false>
         userNotLoggedInMenu markup
  </userloggedin:false>
</lift:usersnippets.userloggedin>


и получить аналог if по User.loggedIn_? в разметке.
Видимо, следует ожидать в ближайшее время появление какого-нибудь lssl - Lift Standart Snippet Library, куда соберут самые массово используемые аналоги taglib из http://java.sun.com/jsp/jstl/core.

Upd: то же самое делает сниппет net.liftweb.builtin.snippet.TestCond. http://stackoverflow.com/questions/4297220/lifttestcond-how-it-works

Scala/Lift Links 2010-11

Lift Wiki
Актуальный репозиторий lift framework с примерами
Пример lift-приложения на GAE
Многоуровневое меню
Стилизация меню
Установка sbt
Пример конфигурации sbt
Один ко многим в lift mapper
Пример приложения 

Интересные топики из Lift Groups:

воскресенье, ноября 07, 2010

среда, августа 04, 2010

Jetty и блокирование статических файлов в Windows

При разработке веб-приложений на java c использованием Jetty часто приходится править статические файлы, например html, js, css. По умолчанию в Jetty при работе со статическими файлами используются memory mapped files, которые в Windows лочатся. То есть в Windows приходится перезапускать контейнер после каждого изменения статики. Решить проблему можно отказом от использования memory mapped files, что производится установкой useFileMappedBuffer в false внутри $(jetty.home)/etc/webdefault.xml. Но самое правильное это в своем web.xml прописать

<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
<init-param>
<param-name>useFileMappedBuffer</param-name>
<param-value>false</param-value>
</init-param>
</servlet>

Ссылки по теме: Files are locked on Windows and can't be replaced, Disable Jetty's locking of static files.

пятница, июля 16, 2010

Apache Cassandra и YAML

Иногда бывает необходимо быстро загрузить в базу или выгрузить из базы небольшой объем данных в файл. Например, строки для интернационализации приложения. В последнее время в качестве контейнера для сериализованных данных активно используется YAML. Андрей Сомов делает замечательный проект SnakeYAML для использования YAML в java. Там реализован лаконичный, с возможностями настройки, (де)сериализатор бинов, коллекций и всего прочего. Именно им я пользуюсь в случае работы с YAML.
Напомню, что любой key-value DB, и Кассандра в частности является ни чем иным, как Map<String,Map<String,String>> при строковом ключе. Это вполне позволяет сделать универсальный загрузчик данных, который и будет представлен далее.

Интерфейс
/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.applusion.cassadraspring;

import static me.prettyprint.cassandra.utils.StringUtils.bytes;
import static me.prettyprint.cassandra.utils.StringUtils.string;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import me.prettyprint.cassandra.dao.SpringCommand;
import me.prettyprint.cassandra.service.CassandraClient;
import me.prettyprint.cassandra.service.CassandraClientPool;
import me.prettyprint.cassandra.service.Keyspace;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.KeyRange;
import org.apache.cassandra.thrift.NotFoundException;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.AbstractMessageSource;

/**
 * MessageSource implementation that reads the messages from a Apache Cassandra.
 * 
 * @author Oleg Smith
 */

public class CassandraResourceBundleMessageSource extends AbstractMessageSource
  implements InitializingBean {

 private CassandraClientPool cassandraClientPool;
 private String keyspace = "Keyspace1";
 private String columnFamilyName = "Standard2";
 private ConsistencyLevel consistencyLevel = CassandraClient.DEFAULT_CONSISTENCY_LEVEL;

 /**
  * Local cache. Universal Map<String, Map<String, String>>
  */
 private Map<String, Map<String, String>> cache = new HashMap<String, Map<String, String>>();

 public CassandraResourceBundleMessageSource() {
 }

 /**
  * resolveCode implementation
  */

 @Override
 protected MessageFormat resolveCode(String code, Locale locale) {
  MessageFormat codeInCache;
  if ((codeInCache = resolveCodeInCache(code, locale)) != null) {
   return codeInCache;
  } else {
   return resolveCodeInCassandra(code, locale);
  }
 }

 /**
  * Resolve code in local cache.
  */

 private MessageFormat resolveCodeInCache(String code, Locale locale) {
  Map<String, String> cacheValues = cache.get(code);
  if (cacheValues != null) {
   String cacheValue = cacheValues.get(locale.getLanguage());
   if (cacheValue != null)
    return new MessageFormat(cacheValue, locale);
   cacheValue = cacheValues.get("en");
   if (cacheValue != null)
    return new MessageFormat(cacheValue, locale);
  }
  return null;
 }

 /**
  * Resolve code in Cassandra.
  */

 private MessageFormat resolveCodeInCassandra(String code, Locale locale) {
  List<Column> columns;
  try {
   columns = getColumnsByKey(code);
   Map<String, String> cacheValues = new HashMap<String, String>();
   for (Column columni : columns) {
    cacheValues.put(string(columni.name), string(columni.value));
   }

   cache.put(code, cacheValues);

   if (cacheValues.get(locale.getLanguage()) != null)
    return new MessageFormat(cacheValues.get(locale.getLanguage()),
      locale);
   if (cacheValues.get("en") != null)
    return new MessageFormat(cacheValues.get("en"), locale);

  } catch (Exception e) {
   e.printStackTrace();
  }
  ;
  return null;
 }

 @Override
 public void afterPropertiesSet() throws Exception {
  loadAll();
 }

 /**
  * Load all strings from Cassandra repeatedly.
  */

 private void loadAll() {
  Map<String, List<Column>> keys;
  try {
   keys = getColumnsByKeys("", 1000);
   boolean isFirstIteration = true;

   while (keys.size() != 0 && keys.size() != 1) {
    Iterator<String> it = keys.keySet().iterator();
    String key = null;
    if (!isFirstIteration)
     it.next();
    while (it.hasNext()) {
     key = it.next();
     List<Column> columns = (List<Column>) keys.get(key);
     Map<String, String> columnsValues = new HashMap<String, String>();
     for (Column columni : columns) {
      columnsValues.put(string(columni.name),
        string(columni.value));
     }
     cache.put(key, columnsValues);
    }
    if (key != null)
     keys = getColumnsByKeys(key, 1000);
    isFirstIteration = false;
   }

  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 /**
  * Get 1500 columns for keyCount keys
  */

 private Map<String, List<Column>> getColumnsByKeys(final String keyStart,
   final int keyCount) throws Exception {
  return execute(new SpringCommand<Map<String, List<Column>>>(
    cassandraClientPool) {
   public Map<String, List<Column>> execute(final Keyspace ks)
     throws Exception {
    ColumnParent clp = new ColumnParent(columnFamilyName);
    SliceRange sr = new SliceRange(new byte[0], new byte[0], false,
      1500);
    SlicePredicate sp = new SlicePredicate();
    sp.setSlice_range(sr);

    KeyRange range = new KeyRange(keyCount);
    range.setStart_key(keyStart);
    range.setEnd_key("");

    Map<String, List<Column>> keys = ks.getRangeSlices(clp, sp,
      range);
    return keys;
   }
  });
 }

 /**
  * Get 1500 columns from given key
  */

 public List<Column> getColumnsByKey(final String key) throws Exception {
  return execute(new SpringCommand<List<Column>>(cassandraClientPool) {
   public List<Column> execute(final Keyspace ks) throws Exception {
    try {
     ColumnParent clp = new ColumnParent(columnFamilyName);
     SliceRange sr = new SliceRange(new byte[0], new byte[0],
       false, 1500);
     SlicePredicate sp = new SlicePredicate();
     sp.setSlice_range(sr);
     List<Column> cols = ks.getSlice(key, clp, sp);

     return cols;
    } catch (NotFoundException e) {
     return null;
    }
   }
  });
 }

 protected ColumnPath createColumnPath(String columnName) {
  return new ColumnPath(columnFamilyName).setColumn(bytes(columnName));
 }

 protected <T> T execute(SpringCommand<T> command) throws Exception {
  CassandraClient c = cassandraClientPool.borrowClient();
  Keyspace ks = c.getKeyspace(keyspace, consistencyLevel);
  try {
   return command.execute(ks);
  } finally {
   cassandraClientPool.releaseClient(ks.getClient());
  }
 }

 public CassandraClientPool getCassandraClientPool() {
  return cassandraClientPool;
 }

 public void setCassandraClientPool(CassandraClientPool cassandraClientPool) {
  this.cassandraClientPool = cassandraClientPool;
 }

 public String getKeyspace() {
  return keyspace;
 }

 public void setKeyspace(String keyspace) {
  this.keyspace = keyspace;
 }

 public String getColumnFamilyName() {
  return columnFamilyName;
 }

 public void setColumnFamilyName(String columnFamilyName) {
  this.columnFamilyName = columnFamilyName;
 }

}

Настройка Spring
<bean id="backup" class="com.applusion.cassadraspring.CassandraYAMLLoader">
  <property name="cassandraClientPool"  ref="cassandraClientPool"/>
  <property name="keyspace" ref="keyspace"/> 
 </bean>

<bean id="keyspace" class="java.lang.String">
  <constructor-arg><value>Keyspace1</value></constructor-arg>
 </bean>

<bean id="cassandraClientMonitor" class="me.prettyprint.cassandra.service.CassandraClientMonitor"/>

    <bean id="jmxMonitor" class="me.prettyprint.cassandra.service.JmxMonitor" factory-method="getInstance"/>

    <bean id="cassandraClientPoolFactory" class="me.prettyprint.cassandra.service.CassandraClientPoolFactory" factory-method="getInstance"/>

    <bean id="cassandraClientPool" factory-bean="cassandraClientPoolFactory" factory-method="createNew" >
        <constructor-arg><ref bean="cassandraHostConfigurator"/></constructor-arg>
    </bean>

    <bean id="cassandraHostConfigurator" class="me.prettyprint.cassandra.service.CassandraHostConfigurator">
        <constructor-arg value="localhost:9160"/>
    </bean>

Ну все, теперь остается только вызвать методы инжектированного backup. Например backup.LoadYAML("dbyaml/yaml.yml", "i18n") загрузит данные из файла dbyaml/yaml.yml в ColumnFamily i18n в Keyspace1, а backup.SaveYAML("dbyaml/yaml.yml", "i18n") сохранит.

формат yml будет какой-то такой:
cancel: {en: Cancel, ru: Отмена}
save: {en: Save, ru: Сохранить}
from: {en: From, ru: От}
password: {en: Password, ru: Пароль}
time: {en: Time, ru: Время}
timezone: {en: TimeZone, ru: Часовой пояс}
more: {en: Next 20, ru: Следующие 20}
search: {en: Search, ru: Поиск}

четверг, июля 15, 2010

Apache Cassandra и Spring Security UserDetailsService

Если Вы в Spring MVC приложении хотите использовать Apache Cassandra как персистентное хранилище информации о пользователях и использовать ее при разграничении доступа, может быть полезной представленная далее реализация UserDetailsService. В качестве клиента Cassandra используется Hector.
Допустим у нас есть следующие декларации в конфиге Cassandra:
<Keyspace Name="Keyspace1">
<ColumnFamily Name="Configuration" CompareWith="UTF8Type"/> 
<ColumnFamily Name="Users" CompareWith="UTF8Type"/>
...
</Keyspace>
Мы объявили две ColumnFamily. В Users будем хранить записи пользователей, в Configuration общие настройки приложения, в частности в одной предопределенной записи, скажем с именем "GroupsAuthority", в столбцах будет определение групп и прав.

ColumnFamily:Users
|--------------------------------------------------------------------------
|UserName\Property| enabled | group      |           password (MD5)       |
|--------------------------------------------------------------------------
|user1            |  true   | user,admin |24c9e15e52afc47c225b757e7bee1f9d|
|userN            |  false  | editor     |362edb266f062db3ddbd1098b8affcbd|
|--------------------------------------------------------------------------

ColumnFamily:Configuration
|-------------------------------------------------------------------
|               |   user   | editor   |           admin            |
|-------------------------------------------------------------------
|GroupsAuthority| canView  | canEdit  | canView, canEdit, canBackup|
|-------------------------------------------------------------------

Далее необходимая конфигурация в Spring
<authentication-manager>
    <authentication-provider user-service-ref="userServiceBean">
        <password-encoder hash="md5"/>                                 
    </authentication-provider>
</authentication-manager>

<beans:bean id="userServiceBean" class="com.applusion.cassadraspring.CassandraHectorUserDetailsServiceImpl">
 <beans:property name="cassandraClientPool" ref="cassandraClientPool"/>
 <beans:property name="keyspace" value="Keyspace1"/>
 <beans:property name="usersCF" value="Users"/>
 <beans:property name="passwordColumn" value="password"/>
 <beans:property name="enabledColumn" value="enabled"/>
 <beans:property name="groupColumn" value="group"/>
 <beans:property name="configurationCF" value="Configuration"/>
 <beans:property name="groupsAuthorityKey" value="GroupsAuthority"/>
</beans:bean>

Не забудем совершенно типичную конфигурацию в Spring с использованием Cassandra и Hector:

    
<bean id="cassandraClientMonitor" class="me.prettyprint.cassandra.service.CassandraClientMonitor"/>

<bean id="jmxMonitor" class="me.prettyprint.cassandra.service.JmxMonitor" factory-method="getInstance"/>

<bean id="cassandraClientPoolFactory" class="me.prettyprint.cassandra.service.CassandraClientPoolFactory" factory-method="getInstance"/>

<bean id="cassandraClientPool" factory-bean="cassandraClientPoolFactory" factory-method="createNew" >
        <constructor-arg><ref bean="cassandraHostConfigurator"/></constructor-arg>
</bean>

<bean id="cassandraHostConfigurator" class="me.prettyprint.cassandra.service.CassandraHostConfigurator">
<constructor-arg value="localhost:9160"/>
</bean>

Сам класс:
/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.applusion.cassadraspring;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import me.prettyprint.cassandra.dao.SpringCommand;
import me.prettyprint.cassandra.service.CassandraClient;
import me.prettyprint.cassandra.service.CassandraClientPool;
import me.prettyprint.cassandra.service.Keyspace;

import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.NotFoundException;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

/**
 * UserDetailsService implementation that reads user details from a Apache Cassandra.
 * 
 * @author Oleg Smith
 */

public class CassandraHectorUserDetailsServiceImpl implements UserDetailsService {
 private CassandraClientPool cassandraClientPool;
 private String keyspace = "Keyspace1";
 private String usersCF = "Standard2";
 private String passwordColumn = "password";
 private String enabledColumn = "enabled";
 private String groupColumn = "group";
 private String configurationCF="Configuration";
 private String groupsAuthorityKey="GroupsAuthority";

 
 private ConsistencyLevel consistencyLevel = CassandraClient.DEFAULT_CONSISTENCY_LEVEL;

 protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
 
 public CassandraHectorUserDetailsServiceImpl() {
   } 
 
 public CassandraHectorUserDetailsServiceImpl(CassandraClientPool cassandraClientPool) {
     this.cassandraClientPool = cassandraClientPool;
   }
 
 @Override
 public UserDetails loadUserByUsername(String username)
   throws UsernameNotFoundException, DataAccessException {
  List<Column> cl, cl2;
  
  try {
   cl = getUserProperties(username);
   if (cl.size()==0) {
     throw new UsernameNotFoundException(
                      messages.getMessage("CassandraHectorUserDetailsServiceImpl.notFound", new Object[]{username}, "Username {0} not found"), username);   
   }
   
   cl2 = getGroupsAuthority();
   if (cl2.size()==0) {
     throw new UsernameNotFoundException(
                      messages.getMessage("CassandraHectorUserDetailsServiceImpl.notFound", new Object[]{username}, "Not found any GroupsAuthority in base"));   
   }
      
   Map<String,String> userProperties=new HashMap<String,String>();
   for (Column cli:cl) {
    userProperties.put(new String(cli.name),new String(cli.value));
   }
   String[] userGroups=userProperties.get(groupColumn).split(",");
   String password=userProperties.get(passwordColumn);
   boolean enabled=userProperties.get(enabledColumn).trim().equals("true") ? true: false;
   
   
   Map<String,String> groupsAuthority=new HashMap<String,String>();
   for (Column cli:cl2) {
    groupsAuthority.put(new String(cli.name),new String(cli.value));
   }

   List<String> authorities=new ArrayList<String>(); 
   
   for (String userGroup:userGroups) {
    String[] gauthorities=groupsAuthority.get(userGroup).split(","); 
    for (String authority:gauthorities) {
     authorities.add(authority); 
    }    
   }
      
   Collection<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>();
   
   for (String auth:authorities) {
    if (!auth.equals("")) dbAuths.add(new GrantedAuthorityImpl(auth.trim()));
   }
   
   return new User(username, password, enabled, true,true, true, dbAuths);
   

   
  } catch (Exception e) {
   e.printStackTrace();
  }

  return null;
 }
 
 
   public List<Column> getUserProperties(final String key) throws Exception {
      return execute(new SpringCommand<List<Column>>(cassandraClientPool){
        public List<Column> execute(final Keyspace ks) throws Exception {
          try {
           ColumnParent clp = new ColumnParent(usersCF);           
              SliceRange sr = new SliceRange(new byte[0], new byte[0], false, 150);
              SlicePredicate sp = new SlicePredicate();
              sp.setSlice_range(sr);
              List<Column> cols = ks.getSlice(key, clp, sp);           
           return cols;
          } catch (NotFoundException e) {
            return null;
          }
        }
      });
    } 
 
   public List<Column> getGroupsAuthority() throws Exception {
      return execute(new SpringCommand<List<Column>>(cassandraClientPool){
        public List<Column> execute(final Keyspace ks) throws Exception {
          try {
           ColumnParent clp = new ColumnParent(configurationCF);           
              SliceRange sr = new SliceRange(new byte[0], new byte[0], false, 150);
              SlicePredicate sp = new SlicePredicate();
              sp.setSlice_range(sr);
              List<Column> cols = ks.getSlice(groupsAuthorityKey, clp, sp);           
           return cols;
          } catch (NotFoundException e) {
            return null;
          }
        }
      });
    }  
   
   
  protected <T> T execute(SpringCommand<T> command) throws Exception {
      CassandraClient c = cassandraClientPool.borrowClient();
      Keyspace ks = c.getKeyspace(keyspace, consistencyLevel);
      try {
        return command.execute(ks);
      } finally {
        cassandraClientPool.releaseClient(ks.getClient());        
      }
  }

 public CassandraClientPool getCassandraClientPool() {
  return cassandraClientPool;
 }

 public void setCassandraClientPool(CassandraClientPool cassandraClientPool) {
  this.cassandraClientPool = cassandraClientPool;
 }

 public String getKeyspace() {
  return keyspace;
 }

 public void setKeyspace(String keyspace) {
  this.keyspace = keyspace;
 }

 public String getUsersCF() {
  return usersCF;
 }

 public void setUsersCF(String usersCF) {
  this.usersCF = usersCF;
 }

 public String getPasswordColumn() {
  return passwordColumn;
 }

 public void setPasswordColumn(String passwordColumn) {
  this.passwordColumn = passwordColumn;
 }

 public String getEnabledColumn() {
  return enabledColumn;
 }

 public void setEnabledColumn(String enabledColumn) {
  this.enabledColumn = enabledColumn;
 }

 public String getGroupColumn() {
  return groupColumn;
 }

 public void setGroupColumn(String groupColumn) {
  this.groupColumn = groupColumn;
 }

 public String getConfigurationCF() {
  return configurationCF;
 }

 public void setConfigurationCF(String configurationCF) {
  this.configurationCF = configurationCF;
 }

 public String getGroupsAuthorityKey() {
  return groupsAuthorityKey;
 }

 public void setGroupsAuthorityKey(String groupsAuthorityKey) {
  this.groupsAuthorityKey = groupsAuthorityKey;
 }

 public ConsistencyLevel getConsistencyLevel() {
  return consistencyLevel;
 }

 public void setConsistencyLevel(ConsistencyLevel consistencyLevel) {
  this.consistencyLevel = consistencyLevel;
 }
      
}

Вроде все. Теперь в контроллере можно аннотациями ограничивать права. Например так PreAuthorize("hasRole('canBackup')")

среда, июля 14, 2010

Apache Cassandra и Spring MessageSource

Если Вы в Spring MVC приложении хотите использовать Apache Cassandra как персистентное хранилище строковых сообщений при интернационализации приложения, может быть полезной представленная далее реализация AbstractMessageSource. К особенностям ее работы стоит отнести кеширование ресурсов на старте и кеширование отсутствующих в кеше значений, затребованных во время выполнения. В качестве клиента Cassandra используется Hector.
Итак, допустим у нас есть следующие декларации в конфиге Cassandra:
<Keyspace Name="Keyspace1">
<ColumnFamily Name="i18n" CompareWith="UTF8Type"/> 
...
</Keyspace>
По сути мы всего лишь объявили новую ColumnFamily, в которой в строках будут наименования строк сообщений, в столбцах значения. Наименование столбца должно соответствовать языку локали (locale.getLanguage()).
ColumnFamily:i18n
|-----------------------------------------------------------------------
|resourceName\Locale|        en     |       ru       |       de        |
|-----------------------------------------------------------------------
|hello              |      Hello    |     Привет     |     Hallo       |
|resourceStringN    |ResourceStringN|РесурснаяСтрокаN|RessourcenStringN|
|-----------------------------------------------------------------------

Далее совершенно типичная конфигурация в Spring с использованием Cassandra и Hector:

<!-- Configures MessageSource -->
<bean id="messageSource" class="com.applusion.cassadraspring.CassandraResourceBundleMessageSource">
  <property name="cassandraClientPool"  ref="cassandraClientPool"/>
  <property name="keyspace" ref="keyspace"/>
  <property name="columnFamilyName" value="i18n"/>
</bean>
<!-- Configures keyspace name -->
<bean id="keyspace" class="java.lang.String">
  <constructor-arg><value>Keyspace1</value></constructor-arg>
</bean>
    
<bean id="cassandraClientMonitor" class="me.prettyprint.cassandra.service.CassandraClientMonitor"/>

<bean id="jmxMonitor" class="me.prettyprint.cassandra.service.JmxMonitor" factory-method="getInstance"/>

<bean id="cassandraClientPoolFactory" class="me.prettyprint.cassandra.service.CassandraClientPoolFactory" factory-method="getInstance"/>

<bean id="cassandraClientPool" factory-bean="cassandraClientPoolFactory" factory-method="createNew" >
        <constructor-arg><ref bean="cassandraHostConfigurator"/></constructor-arg>
</bean>

<bean id="cassandraHostConfigurator" class="me.prettyprint.cassandra.service.CassandraHostConfigurator">
<constructor-arg value="localhost:9160"/>
</bean>

Все что снизу относится к Hector, чуть выше объявление keyspace как строки с названием кейспейса, и, собственно, бин messageSource для которого определены пул соединений с Cassandra, кейспейс и наименование ColumnFamily, в котором хранятся ресурсы.

Сам класс:
/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.applusion.cassadraspring;

import static me.prettyprint.cassandra.utils.StringUtils.bytes;
import static me.prettyprint.cassandra.utils.StringUtils.string;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import me.prettyprint.cassandra.dao.SpringCommand;
import me.prettyprint.cassandra.service.CassandraClient;
import me.prettyprint.cassandra.service.CassandraClientPool;
import me.prettyprint.cassandra.service.Keyspace;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.KeyRange;
import org.apache.cassandra.thrift.NotFoundException;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.AbstractMessageSource;

/**
 * MessageSource implementation that reads the messages from a Apache Cassandra.
 * 
 * @author Oleg Smith
 */

public class CassandraResourceBundleMessageSource extends AbstractMessageSource
  implements InitializingBean {

 private CassandraClientPool cassandraClientPool;
 private String keyspace = "Keyspace1";
 private String columnFamilyName = "Standard2";
 private ConsistencyLevel consistencyLevel = CassandraClient.DEFAULT_CONSISTENCY_LEVEL;

 /**
  * Local cache. Universal Map<String, Map<String, String>>
  */
 private Map<String, Map<String, String>> cache = new HashMap<String, Map<String, String>>();

 public CassandraResourceBundleMessageSource() {
 }

 /**
  * resolveCode implementation
  */

 @Override
 protected MessageFormat resolveCode(String code, Locale locale) {
  MessageFormat codeInCache;
  if ((codeInCache = resolveCodeInCache(code, locale)) != null) {
   return codeInCache;
  } else {
   return resolveCodeInCassandra(code, locale);
  }
 }

 /**
  * Resolve code in local cache.
  */

 private MessageFormat resolveCodeInCache(String code, Locale locale) {
  Map<String, String> cacheValues = cache.get(code);
  if (cacheValues != null) {
   String cacheValue = cacheValues.get(locale.getLanguage());
   if (cacheValue != null)
    return new MessageFormat(cacheValue, locale);
   cacheValue = cacheValues.get("en");
   if (cacheValue != null)
    return new MessageFormat(cacheValue, locale);
  }
  return null;
 }

 /**
  * Resolve code in Cassandra.
  */

 private MessageFormat resolveCodeInCassandra(String code, Locale locale) {
  List<Column> columns;
  try {
   columns = getColumnsByKey(code);
   Map<String, String> cacheValues = new HashMap<String, String>();
   for (Column columni : columns) {
    cacheValues.put(string(columni.name), string(columni.value));
   }

   cache.put(code, cacheValues);

   if (cacheValues.get(locale.getLanguage()) != null)
    return new MessageFormat(cacheValues.get(locale.getLanguage()),
      locale);
   if (cacheValues.get("en") != null)
    return new MessageFormat(cacheValues.get("en"), locale);

  } catch (Exception e) {
   e.printStackTrace();
  }
  ;
  return null;
 }

 @Override
 public void afterPropertiesSet() throws Exception {
  loadAll();
 }

 /**
  * Load all strings from Cassandra repeatedly.
  */

 private void loadAll() {
  Map<String, List<Column>> keys;
  try {
   keys = getColumnsByKeys("", 1000);
   boolean isFirstIteration = true;

   while (keys.size() != 0 && keys.size() != 1) {
    Iterator<String> it = keys.keySet().iterator();
    String key = null;
    if (!isFirstIteration)
     it.next();
    while (it.hasNext()) {
     key = it.next();
     List<Column> columns = (List<Column>) keys.get(key);
     Map<String, String> columnsValues = new HashMap<String, String>();
     for (Column columni : columns) {
      columnsValues.put(string(columni.name),
        string(columni.value));
     }
     cache.put(key, columnsValues);
    }
    if (key != null)
     keys = getColumnsByKeys(key, 1000);
    isFirstIteration = false;
   }

  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 /**
  * Get 1500 columns and keyCount keys
  */

 private Map<String, List<Column>> getColumnsByKeys(final String keyStart,
   final int keyCount) throws Exception {
  return execute(new SpringCommand<Map<String, List<Column>>>(
    cassandraClientPool) {
   public Map<String, List<Column>> execute(final Keyspace ks)
     throws Exception {
    ColumnParent clp = new ColumnParent(columnFamilyName);
    SliceRange sr = new SliceRange(new byte[0], new byte[0], false,
      1500);
    SlicePredicate sp = new SlicePredicate();
    sp.setSlice_range(sr);

    KeyRange range = new KeyRange(keyCount);
    range.setStart_key(keyStart);
    range.setEnd_key("");

    Map<String, List<Column>> keys = ks.getRangeSlices(clp, sp,
      range);
    return keys;
   }
  });
 }

 /**
  * Get 1500 columns from given key
  */

 public List<Column> getColumnsByKey(final String key) throws Exception {
  return execute(new SpringCommand<List<Column>>(cassandraClientPool) {
   public List<Column> execute(final Keyspace ks) throws Exception {
    try {
     ColumnParent clp = new ColumnParent(columnFamilyName);
     SliceRange sr = new SliceRange(new byte[0], new byte[0],
       false, 1500);
     SlicePredicate sp = new SlicePredicate();
     sp.setSlice_range(sr);
     List<Column> cols = ks.getSlice(key, clp, sp);

     return cols;
    } catch (NotFoundException e) {
     return null;
    }
   }
  });
 }

 protected ColumnPath createColumnPath(String columnName) {
  return new ColumnPath(columnFamilyName).setColumn(bytes(columnName));
 }

 protected <T> T execute(SpringCommand<T> command) throws Exception {
  CassandraClient c = cassandraClientPool.borrowClient();
  Keyspace ks = c.getKeyspace(keyspace, consistencyLevel);
  try {
   return command.execute(ks);
  } finally {
   cassandraClientPool.releaseClient(ks.getClient());
  }
 }

 public CassandraClientPool getCassandraClientPool() {
  return cassandraClientPool;
 }

 public void setCassandraClientPool(CassandraClientPool cassandraClientPool) {
  this.cassandraClientPool = cassandraClientPool;
 }

 public String getKeyspace() {
  return keyspace;
 }

 public void setKeyspace(String keyspace) {
  this.keyspace = keyspace;
 }

 public String getColumnFamilyName() {
  return columnFamilyName;
 }

 public void setColumnFamilyName(String columnFamilyName) {
  this.columnFamilyName = columnFamilyName;
 }

}

Осталось закачать в Cassandra сообщения программно или иным способом. Например, с помощью консольного клиента cassandra-cli это выглядело бы так:
set Keyspace1.i18n['hello']['en'] ='Hello'
set Keyspace1.i18n['hello']['ru'] ='Привет'
set Keyspace1.i18n['hello']['de'] ='Hallo'


Теперь, если в Cassandra в jsp написать
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

то вы увидите свое интернациональное сообщение.

При интернационализации не забудьте определить обработчик локали в Spring:
<mvc:interceptors>
  <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
 </mvc:interceptors>

понедельник, июля 12, 2010

Eclipse, UrlRewriteFilter 3.2 и Referenced file contains errors http://tuckey.org/res/dtds/urlrewrite3.2.dtd

Иногда при открытии проекта Spring MVC с использованием UrlRewriteFilter 3.2 Eclipse ругается ошибкой "Referenced file contains errors (http://tuckey.org/res/dtds/urlrewrite3.2.dtd). For more information, right click on the message in the Problems View and select "Show Details...". Не очень понятно в чем конкретно дело, возможно что Eclipse как-то не до конца скачивает схему, но решить проблему помогает отчистка кеша Eclipse. Делается это через меню Window/Preferences далее закладки General/Network Connections/Cache. Удаляете в списке закешированных сетевых ресурсов все urlrewrite3.X.dtd в надежде на то, что в следующий раз закешируется валидная схема. Если и это не поможет - помещайте схему в папку WEB-INF.

Линк по теме:
Error while creating a Spring MVC project in STS

вторник, июня 29, 2010

Тестирование производительности memcached, redis, hazelcast с java-клиентами

Недавно с моим коллегой, Артемом Наумовым, для одного веб-проекта на java (Spring MVC) выбирали внешний сетевой in-memory cache. Обратили внимание на три проекта: Memcached 1.4.0, Redis 1.2.6, Hazelcast 1.8.5. Первые два это сишные проекты под GCC, Hazelcast - на java. Для Memcached был использован клиент jmemcached, для Redis - jredis, для Hazelcast - стандартный явовский клиент. При тестировании Hazelcast в зачет шел второй прогон, для уверенности что ява-машина разогрета.

Все сервера были установлены на одну железку. На java написаны идентичные клиентские тесты для каждого из серверов и запускались на другой железке. Между железками сетка шириной в 1 гигабит.

Кеш-сервер представлял из себя следующее:
Железка: AMD Atlon 64 X2 Dual Core Processor 3800+, 2 Гб оперативной памяти
ОС: CentOS 5.5 ядро 2.6.18-194.el5 #1 SMP Fri Apr 2 14:58:35 EDT 2010 i686 athlon i386 GNU/Linux
Ява: Java(TM) SE Runtime Environment (build 1.6.0_20-b02)

В кеш загоняли 1 млн. записей. Каждая запись - кастомно сериализованный в байты Java class, состоящий из 6 полей по 8 байт (2 long + 4 double) + 1 String от 50 до 400 символов. Чтение производилось многопоточно - в 1, 10, 50, 100, 500 потоков. В каждом потоке синхронно делалось по 100 запросов. При чтении в 1 поток делалось 10000 запросов. В общем все в соответствии со следующей таблицей:
1 поток - 10000 запросов
10 потоков - 100 запросов в каждом
50 потоков - 100 запросов в каждом
100 потоков - 100 запросов в каждом
500 потоков - 100 запросов в каждом

По результатам строились гистограммы времени отклика. По одной оси количество запросов в процентах, по другой миллисекунды. Интерпретация следующая - если при 0.2 мс значение в процентах 60, это означает что этот сервер успевает 60% всех запросов обработать за 0.2 мс.
Нотация в легенде "наименование-сервера_количество-потоков_количество-запросов-в-каждом", т.е. jredis_10_100 означает что тестировался jredis, нагрузка в 10 потоков, к каждом потоке по 100 запросов.

Итак, самая главная картинка




Как видно, самые большие и ярко выраженные пики - это чтение в один поток. Кто бы сомневался, что тут все будет просто отлично. Redis и Memcached показывают отличную и примерно одинаковую производительность - успевают за 0.20-0.23 мс обработать 50-55% всех запросов. Hazelcast отстает в два раза - 40% за 0.40-0.43 мс. В случае увеличения потоков на чтение распределение более пологое, без ярких провалов. Тут Redis и Memcached также показывают примерно одинаковые распределения из которых следует, что большая часть запросов будет обработана за время до 0.5 мс. Заметно выделяется по производительности Memcached на 10 потоков - там 15% запросов пройдут за 0.2 мс. При всех проигрышах по времени в два раза Hazelcast показывает почти идеальное нормальное распределение с медианой около 0.42 мс вне зависимости от количества потоков. В этом смысле он более детерминирован в части ожидаемого отклика, нежели остальные.

Вот табличный вариант. Интервалы с шагом 0.025 мс от 0 до 1 мс . Значения - это количество откликов в заданном интервале в процентах. В сумме проценты меньше 100, понятно, что эта разность реализовалась где-то после 1 мс.

Интервал, мсjmemcached_1_10000jmemcached_10_100jmemcached_50_100jmemcached_100_100jmemcached_500_100jredis_1_10000jredis_10_100jredis_50_100jredis_100_100jredis_500_100hazelcast_1_10000hazelcast_10_100hazelcast_50_100hazelcast_100_100hazelcast_500_100
0000000000000000
0,025000000000000000
0,05000000000000000
0,075000000000000000
0,1000000000000000
0,1250,070,40,660,380,21000,060,050,0400000
0,150,962,46,265,844,940,120,50,920,960,7500000
0,1752,811,99,225,824,180,845,86,525,154,7400000
0,249,654,817,412,0111,3653,447,16,86,035,900000
0,22536,16313,868,67,4542,948,211,68,348,97000,040,020,03
0,252,597,28,489,5610,60,726,16,126,996,380,030,30,380,360,21
0,2752,256,16,668,078,120,3410,59,27,988,080,11,10,980,850,79
0,31,7512,15,567,79,070,2311,48,348,78,90,141,51,161,51,05
0,3251,928,85,15,886,50,248,36,77,317,511,082,32,582,491,86
0,350,96,43,645,025,480,295,15,386,587,252,335,55,025,184,75
0,3750,184,13,684,754,820,184,64,985,235,427,759,99,589,169,55
0,40,314,42,483,563,770,294,44,25,255,1540,8812,915,412,6715,35
0,4250,153,42,063,363,950,074,23,744,564,4137,1411,113,2611,0912,42
0,450,063,31,762,163,390,073,133,633,365,896,46,727,248,11
0,4750,072,91,31,892,320,043,12,623,052,752,685,65,44,874,88
0,50,043,11,021,891,760,012,32,42,512,30,623,93,984,023,87
0,5250,033,21,261,691,280,040,71,91,922,060,253,43,563,323,35
0,550,013,80,921,331,090,032,31,361,581,470,191,63,13,412,91
0,5750,022,40,841,130,840,0211,221,641,440,173,93,023,032,72
0,60,0230,540,990,810,021,31,021,281,320,223,52,682,853
0,62502,20,320,740,71010,860,9810,162,91,92,632,23
0,6501,40,30,550,490,010,50,740,790,940,011,71,51,731,56
0,67501,60,260,50,4300,40,760,670,810,032,21,31,421,3
0,70,010,80,360,540,5500,90,90,690,570,020,811,10,99
0,72500,70,220,510,4200,60,680,660,640,011,30,781,021,04
0,7501,10,260,440,3600,50,640,390,5101,20,861,070,83
0,77500,40,180,350,3900,80,60,490,550,051,50,81,11,01
0,800,20,160,390,360,010,70,280,380,560,060,60,680,840,83
0,8250,010,50,140,280,170,010,60,30,420,310,050,70,640,720,79
0,8500,10,080,220,240,020,40,180,30,1500,80,380,770,66
0,87500,20,120,230,190,010,20,240,310,2400,80,440,540,58
0,900,40,080,250,1400,20,30,240,260,010,30,420,360,34
0,9250,010,20,10,20,0900,10,30,160,220,0100,380,410,45
0,9500,20,080,180,0700,40,260,270,170,010,30,60,470,43
0,97500,20,180,150,1400,40,240,180,2600,30,240,370,42
100,30,120,230,0900,20,20,290,20,020,30,440,40,34



В общем и целом вердикт простой - Redis и Memcached имеют одинаковую производительность при использовании простого базового API уровня get-put. Можно выбирать любой, помня, впрочем, что функционал Redis более обширен. Hazelcast в качестве кеша проигрывает в два раза по скорости и в три по памяти, но у него есть свои плюсы: интероперабельность, широкая функциональность для реализации базовых распределенных структур на java.

Теперь, немного консольной фактуры. Гистограммы по каждому тесту отдельно, тесты естественно идентичные, но другие прогоны, поэтому числа могут отличаться. В большинстве случаев на один тест дается две гистограммы, одна по всей кривой распределения, другая - уточняющая пики.
Это интересно не только для того, чтобы подтвердить статистически значимые результаты, но и для показа того, насколько отдельные запросы могут тормозить. Видно, например, что отдельные единичные запросы в Memcached могут тормозить и выполняться до 379 мс. Это тот случай, когда имеет смысл вводить очень жесткие таймауты на время чтения из кеша.

Memcached
1 thread 10000 requests
Histogram data:
        elements: 10000
        min: 0,110953 ms, max: 1,027573 ms
        max x steps: 25, density per x step: 400,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,11 ms -     0,11 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++++++++++++++++++++      |     0,11 ms -     0,21 ms |   8048 ( 80,48%)
 |++++                      |     0,21 ms -     0,31 ms |   1531 ( 15,31%)
 |+                         |     0,31 ms -     0,41 ms |    398 (  3,98%)
 |                          |     0,41 ms -     0,51 ms |     11 (  0,11%)
 |                          |     0,51 ms -     0,61 ms |      5 (  0,05%)
 |                          |     0,61 ms -     0,71 ms |      4 (  0,04%)
 |                          |     0,71 ms -     0,81 ms |      0 (  0,00%)
 |                          |     0,81 ms -     0,91 ms |      2 (  0,02%)
 |                          |     0,91 ms -     1,01 ms |      0 (  0,00%)
 |                          |     1,01 ms -     1,11 ms |      1 (  0,01%)
-----------------------------------------------------------------
 |                          |     1,11 ms -     1,11 ms |      0 (  0,00%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 10000
        min: 0,110953 ms, max: 1,027573 ms
        max x steps: 25, density per x step: 400,000000
        dx: 12000
-----------------------------------------------------------------
 |                          |     0,11 ms -     0,15 ms |    174 (  1,74%)
-----------------------------------------------------------------
 |                          |     0,15 ms -     0,16 ms |    198 (  1,98%)
 |                          |     0,16 ms -     0,17 ms |    130 (  1,30%)
 |                          |     0,17 ms -     0,18 ms |    142 (  1,42%)
 |+++++                     |     0,18 ms -     0,19 ms |   2059 ( 20,59%)
 |+++++++++++               |     0,19 ms -     0,21 ms |   4462 ( 44,62%)
 |+++                       |     0,21 ms -     0,22 ms |   1133 ( 11,33%)
 |                          |     0,22 ms -     0,23 ms |    122 (  1,22%)
 |                          |     0,23 ms -     0,24 ms |    133 (  1,33%)
 |+                         |     0,24 ms -     0,25 ms |    223 (  2,23%)
 |+                         |     0,25 ms -     0,27 ms |    268 (  2,68%)
 |                          |     0,27 ms -     0,28 ms |    173 (  1,73%)
 |                          |     0,28 ms -     0,29 ms |    116 (  1,16%)
 |                          |     0,29 ms -     0,30 ms |    143 (  1,43%)
-----------------------------------------------------------------
 |+                         |     0,30 ms -     1,03 ms |    524 (  5,24%)
-----------------------------------------------------------------

10 threads - 10000 requests (by 1000)
Histogram data:
        elements: 10000
        min: 0,103438 ms, max: 7,951487 ms
        max x steps: 25, density per x step: 400,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,10 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++++                      |     0,10 ms -     0,20 ms |   1682 ( 16,82%)
 |++++++++++                |     0,20 ms -     0,30 ms |   4196 ( 41,96%)
 |+++++                     |     0,30 ms -     0,40 ms |   1884 ( 18,84%)
 |+++                       |     0,40 ms -     0,50 ms |   1042 ( 10,42%)
 |+                         |     0,50 ms -     0,60 ms |    285 (  2,85%)
 |+                         |     0,60 ms -     0,70 ms |    269 (  2,69%)
 |+                         |     0,70 ms -     0,80 ms |    255 (  2,55%)
 |                          |     0,80 ms -     0,90 ms |    108 (  1,08%)
 |                          |     0,90 ms -     1,00 ms |     83 (  0,83%)
-----------------------------------------------------------------
 |                          |     1,00 ms -     8,00 ms |    196 (  1,96%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 10000
        min: 0,103438 ms, max: 7,951487 ms
        max x steps: 25, density per x step: 400,000000
        dx: 25000
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,10 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,13 ms |     23 (  0,23%)
 |+                         |     0,13 ms -     0,15 ms |    224 (  2,24%)
 |++                        |     0,15 ms -     0,18 ms |    643 (  6,43%)
 |++                        |     0,18 ms -     0,20 ms |    792 (  7,92%)
 |+++                       |     0,20 ms -     0,23 ms |   1208 ( 12,08%)
 |+++                       |     0,23 ms -     0,25 ms |   1199 ( 11,99%)
 |+++                       |     0,25 ms -     0,28 ms |   1032 ( 10,32%)
 |++                        |     0,28 ms -     0,30 ms |    757 (  7,57%)
 |+                         |     0,30 ms -     0,33 ms |    522 (  5,22%)
 |+                         |     0,33 ms -     0,35 ms |    484 (  4,84%)
 |+                         |     0,35 ms -     0,38 ms |    438 (  4,38%)
 |+                         |     0,38 ms -     0,40 ms |    440 (  4,40%)
 |+                         |     0,40 ms -     0,43 ms |    336 (  3,36%)
 |+                         |     0,43 ms -     0,45 ms |    299 (  2,99%)
 |+                         |     0,45 ms -     0,48 ms |    245 (  2,45%)
 |                          |     0,48 ms -     0,50 ms |    162 (  1,62%)
-----------------------------------------------------------------
 |+++                       |     0,50 ms -     7,95 ms |   1196 ( 11,96%)
-----------------------------------------------------------------

50 threads 50000 requests (by 1000)
Histogram data:
        elements: 50000
        min: 0,094517 ms, max: 379,023834 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,09 ms -     0,09 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++++                      |     0,09 ms -     0,19 ms |   8512 ( 17,02%)
 |+++++++++                 |     0,19 ms -     0,29 ms |  18728 ( 37,46%)
 |+++++                     |     0,29 ms -     0,39 ms |  10993 ( 21,99%)
 |+++                       |     0,39 ms -     0,49 ms |   6434 ( 12,87%)
 |+                         |     0,49 ms -     0,59 ms |   1695 (  3,39%)
 |                          |     0,59 ms -     0,69 ms |    898 (  1,80%)
 |                          |     0,69 ms -     0,79 ms |    593 (  1,19%)
 |                          |     0,79 ms -     0,89 ms |    323 (  0,65%)
 |                          |     0,89 ms -     0,99 ms |    189 (  0,38%)
 |                          |     0,99 ms -     1,09 ms |    168 (  0,34%)
-----------------------------------------------------------------
 |+                         |     1,09 ms -   379,09 ms |   1467 (  2,93%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 50000
        min: 0,094517 ms, max: 379,023834 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 50000
-----------------------------------------------------------------
 |                          |     0,09 ms -     0,09 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+                         |     0,09 ms -     0,14 ms |   1525 (  3,05%)
 |+++                       |     0,14 ms -     0,19 ms |   6987 ( 13,97%)
 |+++++                     |     0,19 ms -     0,24 ms |   9874 ( 19,75%)
 |++++                      |     0,24 ms -     0,29 ms |   8854 ( 17,71%)
 |+++                       |     0,29 ms -     0,34 ms |   6024 ( 12,05%)
 |++                        |     0,34 ms -     0,39 ms |   4969 (  9,94%)
 |++                        |     0,39 ms -     0,44 ms |   4139 (  8,28%)
 |+                         |     0,44 ms -     0,49 ms |   2295 (  4,59%)
 |+                         |     0,49 ms -     0,54 ms |   1092 (  2,18%)
 |                          |     0,54 ms -     0,59 ms |    603 (  1,21%)
 |                          |     0,59 ms -     0,64 ms |    458 (  0,92%)
 |                          |     0,64 ms -     0,69 ms |    440 (  0,88%)
 |                          |     0,69 ms -     0,74 ms |    335 (  0,67%)
-----------------------------------------------------------------
 |+                         |     0,74 ms -   379,04 ms |   2405 (  4,81%)
-----------------------------------------------------------------



100 threads 10000 requests (100)
Histogram data:
        elements: 10000
        min: 0,102470 ms, max: 108,289052 ms
        max x steps: 25, density per x step: 400,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,10 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+++++++++                 |     0,10 ms -     0,20 ms |   3400 ( 34,00%)
 |+++++++++                 |     0,20 ms -     0,30 ms |   3405 ( 34,05%)
 |++++                      |     0,30 ms -     0,40 ms |   1666 ( 16,66%)
 |++                        |     0,40 ms -     0,50 ms |    620 (  6,20%)
 |+                         |     0,50 ms -     0,60 ms |    235 (  2,35%)
 |                          |     0,60 ms -     0,70 ms |    157 (  1,57%)
 |                          |     0,70 ms -     0,80 ms |     88 (  0,88%)
 |                          |     0,80 ms -     0,90 ms |     51 (  0,51%)
 |                          |     0,90 ms -     1,00 ms |     36 (  0,36%)
 |                          |     1,00 ms -     1,10 ms |     35 (  0,35%)
 |                          |     1,10 ms -     1,20 ms |     17 (  0,17%)
 |                          |     1,20 ms -     1,30 ms |     15 (  0,15%)
 |                          |     1,30 ms -     1,40 ms |     18 (  0,18%)
 |                          |     1,40 ms -     1,50 ms |     10 (  0,10%)
 |                          |     1,50 ms -     1,60 ms |      7 (  0,07%)
 |                          |     1,60 ms -     1,70 ms |      8 (  0,08%)
 |                          |     1,70 ms -     1,80 ms |      9 (  0,09%)
 |                          |     1,80 ms -     1,90 ms |      6 (  0,06%)
 |                          |     1,90 ms -     2,00 ms |      6 (  0,06%)
 |                          |     2,00 ms -     2,10 ms |      5 (  0,05%)
-----------------------------------------------------------------
 |+                         |     2,10 ms -   108,30 ms |    206 (  2,06%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 10000
        min: 0,102470 ms, max: 108,289052 ms
        max x steps: 25, density per x step: 400,000000
        dx: 25000
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,10 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,13 ms |     85 (  0,85%)
 |+                         |     0,13 ms -     0,15 ms |    540 (  5,40%)
 |++                        |     0,15 ms -     0,18 ms |    911 (  9,11%)
 |+++++                     |     0,18 ms -     0,20 ms |   1864 ( 18,64%)
 |+++                       |     0,20 ms -     0,23 ms |   1255 ( 12,55%)
 |++                        |     0,23 ms -     0,25 ms |    866 (  8,66%)
 |++                        |     0,25 ms -     0,28 ms |    687 (  6,87%)
 |+                         |     0,28 ms -     0,30 ms |    597 (  5,97%)
 |+                         |     0,30 ms -     0,33 ms |    524 (  5,24%)
 |+                         |     0,33 ms -     0,35 ms |    455 (  4,55%)
 |+                         |     0,35 ms -     0,38 ms |    375 (  3,75%)
 |+                         |     0,38 ms -     0,40 ms |    312 (  3,12%)
 |+                         |     0,40 ms -     0,43 ms |    243 (  2,43%)
 |                          |     0,43 ms -     0,45 ms |    159 (  1,59%)
 |                          |     0,45 ms -     0,48 ms |    129 (  1,29%)
 |                          |     0,48 ms -     0,50 ms |     89 (  0,89%)
-----------------------------------------------------------------
 |++                        |     0,50 ms -   108,30 ms |    909 (  9,09%)
-----------------------------------------------------------------

threads 500 50000 requests (by 100)
Histogram data:
        elements: 50000
        min: 0,098075 ms, max: 244,454622 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,10 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+++++++                   |     0,10 ms -     0,20 ms |  14658 ( 29,32%)
 |+++++++++                 |     0,20 ms -     0,30 ms |  18663 ( 37,33%)
 |+++++                     |     0,30 ms -     0,40 ms |   9067 ( 18,13%)
 |++                        |     0,40 ms -     0,50 ms |   4080 (  8,16%)
 |+                         |     0,50 ms -     0,60 ms |   1215 (  2,43%)
 |                          |     0,60 ms -     0,70 ms |    546 (  1,09%)
 |                          |     0,70 ms -     0,80 ms |    367 (  0,73%)
 |                          |     0,80 ms -     0,90 ms |    174 (  0,35%)
 |                          |     0,90 ms -     1,00 ms |    129 (  0,26%)
 |                          |     1,00 ms -     1,10 ms |     90 (  0,18%)
 |                          |     1,10 ms -     1,20 ms |    101 (  0,20%)
-----------------------------------------------------------------
 |                          |     1,20 ms -   244,50 ms |    910 (  1,82%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 50000
        min: 0,098075 ms, max: 244,454622 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 50000
-----------------------------------------------------------------
 |                          |     0,10 ms -     0,10 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+                         |     0,10 ms -     0,15 ms |   2566 (  5,13%)
 |++++++                    |     0,15 ms -     0,20 ms |  12092 ( 24,18%)
 |++++++                    |     0,20 ms -     0,25 ms |  11932 ( 23,86%)
 |+++                       |     0,25 ms -     0,30 ms |   6731 ( 13,46%)
 |+++                       |     0,30 ms -     0,35 ms |   5135 ( 10,27%)
 |++                        |     0,35 ms -     0,40 ms |   3932 (  7,86%)
 |+                         |     0,40 ms -     0,45 ms |   2771 (  5,54%)
 |+                         |     0,45 ms -     0,50 ms |   1309 (  2,62%)
 |                          |     0,50 ms -     0,55 ms |    730 (  1,46%)
-----------------------------------------------------------------
 |+                         |     0,55 ms -   244,50 ms |   2802 (  5,60%)
-----------------------------------------------------------------



Redis
1 thread - 10000 requests
Histogram data:
        elements: 10000
        min: 0,142482 ms, max: 16,454733 ms
        max x steps: 25, density per x step: 400,000000
        dx: 1000000
-----------------------------------------------------------------
 |                          |     0,14 ms -     0,14 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+++++++++++++++++++++++++ |     0,14 ms -     1,14 ms |   9998 ( 99,98%)
 |                          |     1,14 ms -     2,14 ms |      0 (  0,00%)
 |                          |     2,14 ms -     3,14 ms |      1 (  0,01%)
 |                          |     3,14 ms -     4,14 ms |      0 (  0,00%)
 |                          |     4,14 ms -     5,14 ms |      0 (  0,00%)
 |                          |     5,14 ms -     6,14 ms |      0 (  0,00%)
 |                          |     6,14 ms -     7,14 ms |      0 (  0,00%)
 |                          |     7,14 ms -     8,14 ms |      0 (  0,00%)
 |                          |     8,14 ms -     9,14 ms |      0 (  0,00%)
 |                          |     9,14 ms -    10,14 ms |      0 (  0,00%)
 |                          |    10,14 ms -    11,14 ms |      0 (  0,00%)
 |                          |    11,14 ms -    12,14 ms |      0 (  0,00%)
 |                          |    12,14 ms -    13,14 ms |      0 (  0,00%)
 |                          |    13,14 ms -    14,14 ms |      0 (  0,00%)
 |                          |    14,14 ms -    15,14 ms |      0 (  0,00%)
 |                          |    15,14 ms -    16,14 ms |      0 (  0,00%)
 |                          |    16,14 ms -    17,14 ms |      1 (  0,01%)
-----------------------------------------------------------------
 |                          |    17,14 ms -    17,14 ms |      0 (  0,00%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 10000
        min: 0,142482 ms, max: 16,454733 ms
        max x steps: 25, density per x step: 400,000000
        dx: 2500
-----------------------------------------------------------------
 |+                         |     0,14 ms -     0,19 ms |    203 (  2,03%)
-----------------------------------------------------------------
 |++                        |     0,19 ms -     0,19 ms |    725 (  7,25%)
 |++++                      |     0,19 ms -     0,19 ms |   1474 ( 14,74%)
 |++                        |     0,19 ms -     0,19 ms |    769 (  7,69%)
 |++                        |     0,19 ms -     0,20 ms |    799 (  7,99%)
 |++++                      |     0,20 ms -     0,20 ms |   1477 ( 14,77%)
 |+++                       |     0,20 ms -     0,20 ms |   1038 ( 10,38%)
 |+                         |     0,20 ms -     0,20 ms |    530 (  5,30%)
 |+++                       |     0,20 ms -     0,21 ms |   1194 ( 11,94%)
 |+++                       |     0,21 ms -     0,21 ms |   1262 ( 12,62%)
 |+                         |     0,21 ms -     0,21 ms |    314 (  3,14%)
-----------------------------------------------------------------
 |+                         |     0,21 ms -    16,45 ms |    215 (  2,15%)
-----------------------------------------------------------------

10 threads - 10000 requests (by 1000)
Histogram data:
        elements: 10000
        min: 0,133164 ms, max: 6,673282 ms
        max x steps: 25, density per x step: 400,000000
        dx: 50000
-----------------------------------------------------------------
 |                          |     0,13 ms -     0,13 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+                         |     0,13 ms -     0,18 ms |    357 (  3,57%)
 |++                        |     0,18 ms -     0,23 ms |    963 (  9,63%)
 |++++                      |     0,23 ms -     0,28 ms |   1785 ( 17,85%)
 |++++++                    |     0,28 ms -     0,33 ms |   2423 ( 24,23%)
 |+++                       |     0,33 ms -     0,38 ms |   1132 ( 11,32%)
 |+++                       |     0,38 ms -     0,43 ms |   1247 ( 12,47%)
 |++                        |     0,43 ms -     0,48 ms |    826 (  8,26%)
 |+                         |     0,48 ms -     0,53 ms |    248 (  2,48%)
 |                          |     0,53 ms -     0,58 ms |    159 (  1,59%)
 |                          |     0,58 ms -     0,63 ms |    107 (  1,07%)
-----------------------------------------------------------------
 |++                        |     0,63 ms -     6,68 ms |    753 (  7,53%)
-----------------------------------------------------------------

50 threads - 10000 requests (by 200)
Histogram data:
        elements: 10000
        min: 0,119990 ms, max: 47,799053 ms
        max x steps: 25, density per x step: 400,000000
        dx: 5000000
-----------------------------------------------------------------
 |                          |     0,12 ms -     0,12 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++++++++++++++++++++++++  |     0,12 ms -     5,12 ms |   9436 ( 94,36%)
 |                          |     5,12 ms -    10,12 ms |    102 (  1,02%)
 |                          |    10,12 ms -    15,12 ms |     53 (  0,53%)
 |                          |    15,12 ms -    20,12 ms |    143 (  1,43%)
 |                          |    20,12 ms -    25,12 ms |    157 (  1,57%)
 |                          |    25,12 ms -    30,12 ms |     44 (  0,44%)
 |                          |    30,12 ms -    35,12 ms |     47 (  0,47%)
 |                          |    35,12 ms -    40,12 ms |      5 (  0,05%)
 |                          |    40,12 ms -    45,12 ms |     10 (  0,10%)
 |                          |    45,12 ms -    50,12 ms |      3 (  0,03%)
-----------------------------------------------------------------
 |                          |    50,12 ms -    50,12 ms |      0 (  0,00%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 10000
        min: 0,119990 ms, max: 47,799053 ms
        max x steps: 25, density per x step: 400,000000
        dx: 50000
-----------------------------------------------------------------
 |+                         |     0,12 ms -     0,17 ms |    490 (  4,90%)
-----------------------------------------------------------------
 |++                        |     0,17 ms -     0,22 ms |    940 (  9,40%)
 |+++                       |     0,22 ms -     0,27 ms |   1153 ( 11,53%)
 |++++                      |     0,27 ms -     0,32 ms |   1585 ( 15,85%)
 |+++                       |     0,32 ms -     0,37 ms |   1229 ( 12,29%)
 |+++                       |     0,37 ms -     0,42 ms |   1191 ( 11,91%)
 |++                        |     0,42 ms -     0,47 ms |    916 (  9,16%)
 |+                         |     0,47 ms -     0,52 ms |    494 (  4,94%)
 |+                         |     0,52 ms -     0,57 ms |    235 (  2,35%)
 |                          |     0,57 ms -     0,62 ms |    171 (  1,71%)
 |                          |     0,62 ms -     0,67 ms |    135 (  1,35%)
 |                          |     0,67 ms -     0,72 ms |    112 (  1,12%)
 |                          |     0,72 ms -     0,77 ms |    130 (  1,30%)
-----------------------------------------------------------------
 |+++                       |     0,77 ms -    47,82 ms |   1219 ( 12,19%)
-----------------------------------------------------------------

100 threads - 10000 requests (by 100)
Histogram data:
        elements: 10000
        min: 0,125251 ms, max: 71,117095 ms
        max x steps: 25, density per x step: 400,000000
        dx: 5000000
-----------------------------------------------------------------
 |                          |     0,13 ms -     0,13 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++++++++++++++++++++++++  |     0,13 ms -     5,13 ms |   9624 ( 96,24%)
 |                          |     5,13 ms -    10,13 ms |     13 (  0,13%)
 |                          |    10,13 ms -    15,13 ms |      2 (  0,02%)
 |                          |    15,13 ms -    20,13 ms |     16 (  0,16%)
 |                          |    20,13 ms -    25,13 ms |     22 (  0,22%)
 |                          |    25,13 ms -    30,13 ms |     35 (  0,35%)
 |                          |    30,13 ms -    35,13 ms |     22 (  0,22%)
 |                          |    35,13 ms -    40,13 ms |     13 (  0,13%)
 |                          |    40,13 ms -    45,13 ms |     56 (  0,56%)
 |                          |    45,13 ms -    50,13 ms |     64 (  0,64%)
 |                          |    50,13 ms -    55,13 ms |     25 (  0,25%)
 |                          |    55,13 ms -    60,13 ms |     41 (  0,41%)
 |                          |    60,13 ms -    65,13 ms |     51 (  0,51%)
 |                          |    65,13 ms -    70,13 ms |     14 (  0,14%)
 |                          |    70,13 ms -    75,13 ms |      2 (  0,02%)
-----------------------------------------------------------------
 |                          |    75,13 ms -    75,13 ms |      0 (  0,00%)
-----------------------------------------------------------------
расшифровка диапаозна
Histogram data:
        elements: 10000
        min: 0,125251 ms, max: 71,117095 ms
        max x steps: 25, density per x step: 400,000000
        dx: 50000
-----------------------------------------------------------------
 |+                         |     0,13 ms -     0,18 ms |    583 (  5,83%)
-----------------------------------------------------------------
 |+++                       |     0,18 ms -     0,23 ms |   1129 ( 11,29%)
 |+++                       |     0,23 ms -     0,28 ms |   1368 ( 13,68%)
 |++++                      |     0,28 ms -     0,33 ms |   1723 ( 17,23%)
 |+++                       |     0,33 ms -     0,38 ms |   1180 ( 11,80%)
 |+++                       |     0,38 ms -     0,43 ms |   1135 ( 11,35%)
 |++                        |     0,43 ms -     0,48 ms |    797 (  7,97%)
 |+                         |     0,48 ms -     0,53 ms |    406 (  4,06%)
 |+                         |     0,53 ms -     0,58 ms |    201 (  2,01%)
 |                          |     0,58 ms -     0,63 ms |    127 (  1,27%)
 |                          |     0,63 ms -     0,68 ms |    114 (  1,14%)
 |                          |     0,68 ms -     0,73 ms |    120 (  1,20%)
 |                          |     0,73 ms -     0,78 ms |    133 (  1,33%)
 |                          |     0,78 ms -     0,83 ms |    105 (  1,05%)
-----------------------------------------------------------------
 |++                        |     0,83 ms -    71,13 ms |    879 (  8,79%)
-----------------------------------------------------------------


500 threads - 50000 requests (by 100)
Histogram data:
        elements: 50000
        min: 0,117327 ms, max: 413,838795 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 10000000
-----------------------------------------------------------------
 |                          |     0,12 ms -     0,12 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++++++++++++++++++++++++  |     0,12 ms -    10,12 ms |  47432 ( 94,86%)
 |                          |    10,12 ms -    20,12 ms |     28 (  0,06%)
 |                          |    20,12 ms -    30,12 ms |     37 (  0,07%)
 |                          |    30,12 ms -    40,12 ms |     15 (  0,03%)
 |                          |    40,12 ms -    50,12 ms |      9 (  0,02%)
 |                          |    50,12 ms -    60,12 ms |     23 (  0,05%)
 |                          |    60,12 ms -    70,12 ms |     41 (  0,08%)
 |                          |    70,12 ms -    80,12 ms |     73 (  0,15%)
 |                          |    80,12 ms -    90,12 ms |     41 (  0,08%)
 |                          |    90,12 ms -   100,12 ms |     23 (  0,05%)
 |                          |   100,12 ms -   110,12 ms |     53 (  0,11%)
 |                          |   110,12 ms -   120,12 ms |    147 (  0,29%)
 |                          |   120,12 ms -   130,12 ms |     88 (  0,18%)
 |                          |   130,12 ms -   140,12 ms |    115 (  0,23%)
 |                          |   140,12 ms -   150,12 ms |     62 (  0,12%)
 |                          |   150,12 ms -   160,12 ms |     37 (  0,07%)
 |                          |   160,12 ms -   170,12 ms |    160 (  0,32%)
 |                          |   170,12 ms -   180,12 ms |     97 (  0,19%)
 |                          |   180,12 ms -   190,12 ms |    113 (  0,23%)
 |                          |   190,12 ms -   200,12 ms |     55 (  0,11%)
-----------------------------------------------------------------
 |+                         |   200,12 ms -   420,12 ms |   1351 (  2,70%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 50000
        min: 0,117327 ms, max: 413,838795 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 50000
-----------------------------------------------------------------
 |+                         |     0,12 ms -     0,17 ms |   2508 (  5,02%)
-----------------------------------------------------------------
 |+++                       |     0,17 ms -     0,22 ms |   5669 ( 11,34%)
 |+++                       |     0,22 ms -     0,27 ms |   6008 ( 12,02%)
 |++++                      |     0,27 ms -     0,32 ms |   7732 ( 15,46%)
 |+++                       |     0,32 ms -     0,37 ms |   5873 ( 11,75%)
 |+++                       |     0,37 ms -     0,42 ms |   5756 ( 11,51%)
 |++                        |     0,42 ms -     0,47 ms |   4308 (  8,62%)
 |+                         |     0,47 ms -     0,52 ms |   2299 (  4,60%)
 |+                         |     0,52 ms -     0,57 ms |   1205 (  2,41%)
 |                          |     0,57 ms -     0,62 ms |    859 (  1,72%)
 |                          |     0,62 ms -     0,67 ms |    634 (  1,27%)
 |                          |     0,67 ms -     0,72 ms |    561 (  1,12%)
 |                          |     0,72 ms -     0,77 ms |    575 (  1,15%)
 |                          |     0,77 ms -     0,82 ms |    509 (  1,02%)
 |                          |     0,82 ms -     0,87 ms |    328 (  0,66%)
 |                          |     0,87 ms -     0,92 ms |    217 (  0,43%)
 |                          |     0,92 ms -     0,97 ms |    231 (  0,46%)
 |                          |     0,97 ms -     1,02 ms |    199 (  0,40%)
-----------------------------------------------------------------
 |++                        |     1,02 ms -   413,87 ms |   4529 (  9,06%)
-----------------------------------------------------------------



ADDITIONAL TESTS!!!
500 threads - 50000 requests - BUT Jredis Service count increased to 100!
Histogram data:
        elements: 50000
        min: 0,125896 ms, max: 94,820788 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,13 ms -     0,13 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++++                      |     0,13 ms -     0,23 ms |   7455 ( 14,91%)
 |++++++++                  |     0,23 ms -     0,33 ms |  15776 ( 31,55%)
 |++++++                    |     0,33 ms -     0,43 ms |  12704 ( 25,41%)
 |+++                       |     0,43 ms -     0,53 ms |   5908 ( 11,82%)
 |+                         |     0,53 ms -     0,63 ms |   1982 (  3,96%)
 |+                         |     0,63 ms -     0,73 ms |   1384 (  2,77%)
 |+                         |     0,73 ms -     0,83 ms |   1112 (  2,22%)
 |                          |     0,83 ms -     0,93 ms |    531 (  1,06%)
 |                          |     0,93 ms -     1,03 ms |    383 (  0,77%)
 |                          |     1,03 ms -     1,13 ms |    299 (  0,60%)
 |                          |     1,13 ms -     1,23 ms |    405 (  0,81%)
 |                          |     1,23 ms -     1,33 ms |    205 (  0,41%)
 |                          |     1,33 ms -     1,43 ms |    155 (  0,31%)
 |                          |     1,43 ms -     1,53 ms |    125 (  0,25%)
 |                          |     1,53 ms -     1,63 ms |    143 (  0,29%)
 |                          |     1,63 ms -     1,73 ms |     77 (  0,15%)
 |                          |     1,73 ms -     1,83 ms |     80 (  0,16%)
 |                          |     1,83 ms -     1,93 ms |     70 (  0,14%)
 |                          |     1,93 ms -     2,03 ms |     94 (  0,19%)
-----------------------------------------------------------------
 |+                         |     2,03 ms -    94,83 ms |   1112 (  2,22%)
-----------------------------------------------------------------



Hazelcast
1 thread - 10000 requests
Histogram data:
        elements: 10000
        min: 0,215282 ms, max: 101,412167 ms
        max x steps: 25, density per x step: 400,000000
        dx: 10000
-----------------------------------------------------------------
 |+                         |     0,22 ms -     0,34 ms |    149 (  1,49%)
-----------------------------------------------------------------
 |+                         |     0,34 ms -     0,35 ms |    103 (  1,03%)
 |+                         |     0,35 ms -     0,36 ms |    148 (  1,48%)
 |+                         |     0,36 ms -     0,37 ms |    170 (  1,70%)
 |++                        |     0,37 ms -     0,38 ms |    430 (  4,30%)
 |++++                      |     0,38 ms -     0,39 ms |   1489 ( 14,89%)
 |+++                       |     0,39 ms -     0,40 ms |   1009 ( 10,09%)
 |+++++++++                 |     0,40 ms -     0,41 ms |   3264 ( 32,64%)
 |+++                       |     0,41 ms -     0,42 ms |   1157 ( 11,57%)
 |+++                       |     0,42 ms -     0,43 ms |   1131 ( 11,31%)
 |+                         |     0,43 ms -     0,44 ms |    243 (  2,43%)
 |+                         |     0,44 ms -     0,45 ms |    169 (  1,69%)
 |+                         |     0,45 ms -     0,46 ms |    148 (  1,48%)
 |+                         |     0,46 ms -     0,47 ms |    108 (  1,08%)
-----------------------------------------------------------------
 |+                         |     0,47 ms -   101,42 ms |    282 (  2,82%)
-----------------------------------------------------------------

10 threads - 10000 requests (by 1000 in thread)
Histogram data:
        elements: 10000
        min: 0,220889 ms, max: 101,739531 ms
        max x steps: 25, density per x step: 400,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,22 ms -     0,22 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+                         |     0,22 ms -     0,32 ms |    368 (  3,68%)
 |++++++++++                |     0,32 ms -     0,42 ms |   3770 ( 37,70%)
 |++++++                    |     0,42 ms -     0,52 ms |   2169 ( 21,69%)
 |++++                      |     0,52 ms -     0,62 ms |   1320 ( 13,20%)
 |++                        |     0,62 ms -     0,72 ms |    542 (  5,42%)
 |+                         |     0,72 ms -     0,82 ms |    374 (  3,74%)
 |+                         |     0,82 ms -     0,92 ms |    187 (  1,87%)
 |+                         |     0,92 ms -     1,02 ms |    166 (  1,66%)
 |+                         |     1,02 ms -     1,12 ms |     99 (  0,99%)
 |+                         |     1,12 ms -     1,22 ms |    100 (  1,00%)
 |+                         |     1,22 ms -     1,32 ms |     68 (  0,68%)
 |+                         |     1,32 ms -     1,42 ms |     69 (  0,69%)
 |+                         |     1,42 ms -     1,52 ms |     46 (  0,46%)
 |+                         |     1,52 ms -     1,62 ms |     56 (  0,56%)
 |+                         |     1,62 ms -     1,72 ms |     37 (  0,37%)
 |+                         |     1,72 ms -     1,82 ms |     34 (  0,34%)
 |+                         |     1,82 ms -     1,92 ms |     38 (  0,38%)
 |+                         |     1,92 ms -     2,02 ms |     27 (  0,27%)
 |+                         |     2,02 ms -     2,12 ms |     27 (  0,27%)
 |+                         |     2,12 ms -     2,22 ms |     23 (  0,23%)
 |+                         |     2,22 ms -     2,32 ms |     21 (  0,21%)
 |+                         |     2,32 ms -     2,42 ms |     25 (  0,25%)
 |+                         |     2,42 ms -     2,52 ms |     12 (  0,12%)
 |+                         |     2,52 ms -     2,62 ms |     16 (  0,16%)
 |+                         |     2,62 ms -     2,72 ms |     14 (  0,14%)
-----------------------------------------------------------------
 |+                         |     2,72 ms -   101,82 ms |    392 (  3,92%)
-----------------------------------------------------------------


50 threads - 10000 requests (by 200)
Histogram data:
        elements: 10000
        min: 0,220611 ms, max: 100,343166 ms
        max x steps: 25, density per x step: 400,000000
        dx: 5000000
-----------------------------------------------------------------
 |                          |     0,22 ms -     0,22 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+++++++++++++++++++++++++ |     0,22 ms -     5,22 ms |   9675 ( 96,75%)
 |+                         |     5,22 ms -    10,22 ms |    167 (  1,67%)
 |+                         |    10,22 ms -    15,22 ms |     96 (  0,96%)
 |+                         |    15,22 ms -    20,22 ms |     35 (  0,35%)
 |+                         |    20,22 ms -    25,22 ms |     10 (  0,10%)
 |+                         |    25,22 ms -    30,22 ms |      7 (  0,07%)
 |+                         |    30,22 ms -    35,22 ms |      2 (  0,02%)
 |+                         |    35,22 ms -    40,22 ms |      6 (  0,06%)
 |+                         |    40,22 ms -    45,22 ms |      1 (  0,01%)
 |                          |    45,22 ms -    50,22 ms |      0 (  0,00%)
 |                          |    50,22 ms -    55,22 ms |      0 (  0,00%)
 |                          |    55,22 ms -    60,22 ms |      0 (  0,00%)
 |                          |    60,22 ms -    65,22 ms |      0 (  0,00%)
 |                          |    65,22 ms -    70,22 ms |      0 (  0,00%)
 |                          |    70,22 ms -    75,22 ms |      0 (  0,00%)
 |                          |    75,22 ms -    80,22 ms |      0 (  0,00%)
 |                          |    80,22 ms -    85,22 ms |      0 (  0,00%)
 |                          |    85,22 ms -    90,22 ms |      0 (  0,00%)
 |                          |    90,22 ms -    95,22 ms |      0 (  0,00%)
 |                          |    95,22 ms -   100,22 ms |      0 (  0,00%)
 |+                         |   100,22 ms -   105,22 ms |      1 (  0,01%)
-----------------------------------------------------------------
 |                          |   105,22 ms -   105,22 ms |      0 (  0,00%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 10000
        min: 0,220611 ms, max: 100,343166 ms
        max x steps: 25, density per x step: 400,000000
        dx: 50000
-----------------------------------------------------------------
 |                          |     0,22 ms -     0,22 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+                         |     0,22 ms -     0,27 ms |    142 (  1,42%)
 |+                         |     0,27 ms -     0,32 ms |    386 (  3,86%)
 |++++                      |     0,32 ms -     0,37 ms |   1295 ( 12,95%)
 |+++++++                   |     0,37 ms -     0,42 ms |   2516 ( 25,16%)
 |++++                      |     0,42 ms -     0,47 ms |   1261 ( 12,61%)
 |++                        |     0,47 ms -     0,52 ms |    762 (  7,62%)
 |++                        |     0,52 ms -     0,57 ms |    627 (  6,27%)
 |++                        |     0,57 ms -     0,62 ms |    517 (  5,17%)
 |+                         |     0,62 ms -     0,67 ms |    296 (  2,96%)
 |+                         |     0,67 ms -     0,72 ms |    219 (  2,19%)
 |+                         |     0,72 ms -     0,77 ms |    192 (  1,92%)
 |+                         |     0,77 ms -     0,82 ms |    171 (  1,71%)
-----------------------------------------------------------------
 |+++++                     |     0,82 ms -   100,37 ms |   1616 ( 16,16%)
-----------------------------------------------------------------


100 threads - 10000 requests (by 100)
Histogram data:
        elements: 10000
        min: 0,222737 ms, max: 42,075268 ms
        max x steps: 25, density per x step: 400,000000
        dx: 5000000
-----------------------------------------------------------------
 |                          |     0,22 ms -     0,22 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+++++++++++++++++++++++++ |     0,22 ms -     5,22 ms |   9648 ( 96,48%)
 |+                         |     5,22 ms -    10,22 ms |    164 (  1,64%)
 |+                         |    10,22 ms -    15,22 ms |    110 (  1,10%)
 |+                         |    15,22 ms -    20,22 ms |     44 (  0,44%)
 |+                         |    20,22 ms -    25,22 ms |     13 (  0,13%)
 |+                         |    25,22 ms -    30,22 ms |      5 (  0,05%)
 |+                         |    30,22 ms -    35,22 ms |      4 (  0,04%)
 |+                         |    35,22 ms -    40,22 ms |      3 (  0,03%)
 |+                         |    40,22 ms -    45,22 ms |      9 (  0,09%)
-----------------------------------------------------------------
 |                          |    45,22 ms -    45,22 ms |      0 (  0,00%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 10000
        min: 0,222737 ms, max: 42,075268 ms
        max x steps: 25, density per x step: 400,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,22 ms -     0,22 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |++                        |     0,22 ms -     0,32 ms |    452 (  4,52%)
 |++++++++++                |     0,32 ms -     0,42 ms |   3719 ( 37,19%)
 |++++++                    |     0,42 ms -     0,52 ms |   2091 ( 20,91%)
 |+++                       |     0,52 ms -     0,62 ms |   1139 ( 11,39%)
 |++                        |     0,62 ms -     0,72 ms |    561 (  5,61%)
 |+                         |     0,72 ms -     0,82 ms |    353 (  3,53%)
 |+                         |     0,82 ms -     0,92 ms |    226 (  2,26%)
 |+                         |     0,92 ms -     1,02 ms |    167 (  1,67%)
-----------------------------------------------------------------
 |++++                      |     1,02 ms -    42,12 ms |   1292 ( 12,92%)
-----------------------------------------------------------------


500 threads - 50000 requests (by 100)
Histogram data:
        elements: 50000
        min: 0,207696 ms, max: 151,666026 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 10000000
-----------------------------------------------------------------
 |                          |     0,21 ms -     0,21 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+++++++++++++++++++++++++ |     0,21 ms -    10,21 ms |  49021 ( 98,04%)
 |+                         |    10,21 ms -    20,21 ms |    795 (  1,59%)
 |+                         |    20,21 ms -    30,21 ms |    120 (  0,24%)
 |+                         |    30,21 ms -    40,21 ms |     42 (  0,08%)
 |+                         |    40,21 ms -    50,21 ms |     20 (  0,04%)
 |+                         |    50,21 ms -    60,21 ms |      1 (  0,00%)
 |                          |    60,21 ms -    70,21 ms |      0 (  0,00%)
 |                          |    70,21 ms -    80,21 ms |      0 (  0,00%)
 |                          |    80,21 ms -    90,21 ms |      0 (  0,00%)
 |                          |    90,21 ms -   100,21 ms |      0 (  0,00%)
 |                          |   100,21 ms -   110,21 ms |      0 (  0,00%)
 |                          |   110,21 ms -   120,21 ms |      0 (  0,00%)
 |                          |   120,21 ms -   130,21 ms |      0 (  0,00%)
 |                          |   130,21 ms -   140,21 ms |      0 (  0,00%)
 |                          |   140,21 ms -   150,21 ms |      0 (  0,00%)
 |+                         |   150,21 ms -   160,21 ms |      1 (  0,00%)
-----------------------------------------------------------------
 |                          |   160,21 ms -   160,21 ms |      0 (  0,00%)
-----------------------------------------------------------------
расшифровка диапазона
Histogram data:
        elements: 50000
        min: 0,207696 ms, max: 151,666026 ms
        max x steps: 25, density per x step: 2000,000000
        dx: 100000
-----------------------------------------------------------------
 |                          |     0,21 ms -     0,21 ms |      0 (  0,00%)
-----------------------------------------------------------------
 |+                         |     0,21 ms -     0,31 ms |   1371 (  2,74%)
 |+++++++++                 |     0,31 ms -     0,41 ms |  16113 ( 32,23%)
 |+++++++                   |     0,41 ms -     0,51 ms |  12499 ( 25,00%)
 |++++                      |     0,51 ms -     0,61 ms |   6322 ( 12,64%)
 |++                        |     0,61 ms -     0,71 ms |   3167 (  6,33%)
 |++                        |     0,71 ms -     0,81 ms |   2007 (  4,01%)
 |+                         |     0,81 ms -     0,91 ms |   1205 (  2,41%)
 |+                         |     0,91 ms -     1,01 ms |    873 (  1,75%)
-----------------------------------------------------------------
 |++++                      |     1,01 ms -   151,71 ms |   6443 ( 12,89%)
-----------------------------------------------------------------

Мой список блогов