Почему это так медленно с 100000 записей при использовании конвейера в Redis?
Он сказал, что pipeline
это лучший способ, когда многие set/get
требуется в Redis, так что это мой тестовый код:
public class TestPipeline {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
JedisShardInfo si = new JedisShardInfo("127.0.0.1", 6379);
List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
list.add(si);
ShardedJedis jedis = new ShardedJedis(list);
long startTime = System.currentTimeMillis();
ShardedJedisPipeline pipeline = jedis.pipelined();
for (int i = 0; i < 100000; i++) {
Map<String, String> map = new HashMap<String, String>();
map.put("id", "" + i);
map.put("name", "lyj" + i);
pipeline.hmset("m" + i, map);
}
pipeline.sync();
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
}
Когда я его запускал, с этой программой некоторое время не было ответа, но когда я не работал с pipe
, это занимает всего 20073 мс, поэтому я запутался, почему это даже лучше без pipeline
и как большой разрыв!
Спасибо за ответ, несколько вопросов, как вы рассчитываете данные 6MB? Когда я отправляю данные 10K, конвейер всегда быстрее обычного режима, но с 100k конвейер не будет отвечать. Я думаю, что 100-1000 операций - это рекомендуемый выбор, как указано ниже. Есть ли что-то с JIT, так как я не понимаю этого?
1 ответ
Есть несколько моментов, которые вы должны рассмотреть, прежде чем писать такой тест (и особенно тест с использованием JVM):
на большинстве (физических) машин Redis способен обрабатывать более 100 тыс. операций в секунду при использовании конвейерной обработки. Ваш бенчмарк имеет дело только с предметом 100K, поэтому он не длится достаточно долго для получения значимых результатов. Кроме того, нет времени для последовательных этапов JIT.
абсолютное время не очень актуальная метрика. Отображение пропускной способности (т. Е. Количества операций в секунду) при сохранении производительности эталона в течение не менее 10 секунд будет лучшим и более стабильным показателем.
Ваш внутренний цикл генерирует много мусора. Если вы планируете тестировать Jedis+Redis, то вам нужно снизить накладные расходы вашей собственной программы.
поскольку вы определили все в основную функцию, ваш цикл не будет компилироваться JIT (в зависимости от используемой вами JVM). Могут быть только внутренние вызовы методов. Если вы хотите, чтобы JIT был эффективным, обязательно инкапсулируйте свой код в методы, которые могут быть скомпилированы JIT.
При желании вы можете захотеть добавить фазу прогрева перед выполнением фактического измерения, чтобы избежать учета накладных расходов на выполнение первых итераций с помощью простого интерпретатора и стоимости самого JIT.
Теперь, что касается конвейеризации Redis, ваш конвейер слишком длинный. 100K команд в конвейере означает, что Jedis должен создать буфер 6 МБ перед отправкой чего-либо в Redis. Это означает, что буферы сокетов (на стороне клиента и, возможно, на стороне сервера) будут переполнены, и что Redis также будет иметь дело с 6 МБ коммуникационными буферами.
Кроме того, ваш тест по-прежнему синхронный (использование конвейера волшебным образом не делает его асинхронным). Другими словами, Jedis не начнет читать ответы до тех пор, пока последний запрос вашего конвейера не будет отправлен в Redis. Когда трубопровод слишком длинный, у него есть потенциал, чтобы заблокировать вещи.
Рассмотрим ограничение размера трубопровода до 100-1000 операций. Конечно, он будет генерировать больше циклов, но нагрузка на стек связи будет снижена до приемлемого уровня. Например, рассмотрим следующую программу:
import redis.clients.jedis.*;
import java.util.*;
public class TestPipeline {
/**
* @param args
*/
int i = 0;
Map<String, String> map = new HashMap<String, String>();
ShardedJedis jedis;
// Number of iterations
// Use 1000 to test with the pipeline, 100 otherwise
static final int N = 1000;
public TestPipeline() {
JedisShardInfo si = new JedisShardInfo("127.0.0.1", 6379);
List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
list.add(si);
jedis = new ShardedJedis(list);
}
public void push( int n ) {
ShardedJedisPipeline pipeline = jedis.pipelined();
for ( int k = 0; k < n; k++) {
map.put("id", "" + i);
map.put("name", "lyj" + i);
pipeline.hmset("m" + i, map);
++i;
}
pipeline.sync();
}
public void push2( int n ) {
for ( int k = 0; k < n; k++) {
map.put("id", "" + i);
map.put("name", "lyj" + i);
jedis.hmset("m" + i, map);
++i;
}
}
public static void main(String[] args) {
TestPipeline obj = new TestPipeline();
long startTime = System.currentTimeMillis();
for ( int j=0; j<N; j++ ) {
// Use push2 instead to test without pipeline
obj.push(1000);
// Uncomment to see the acceleration
//System.out.println(obj.i);
}
long endTime = System.currentTimeMillis();
double d = 1000.0 * obj.i;
d /= (double)(endTime - startTime);
System.out.println("Throughput: "+d);
}
}
С помощью этой программы вы можете тестировать с конвейерной передачей или без нее. Обязательно увеличьте число итераций (параметр N) при использовании конвейерной обработки, чтобы он выполнялся не менее 10 секунд. Если вы раскомментируете println в цикле, вы поймете, что программа медленная в начале и станет быстрее, когда JIT начнет оптимизировать вещи (поэтому программа должна работать не менее нескольких секунд, чтобы дать значимый результат).
На моем оборудовании (старой коробке Athlon) я могу увеличить пропускную способность в 8-9 раз при использовании конвейера. Программа может быть улучшена за счет оптимизации форматирования ключа / значения во внутреннем цикле и добавления фазы прогрева.