之前和领导交流的时候,探讨过基于redis的LBS服务,当时他表示“redis geo性能很垃圾,自己亲自压测过,只有200多qps”。我对这个测试结果表示怀疑的,200qps太少了,和数据库差不多。
由于不知道他当时是怎么测试的,因此我只能动手自己测试下。
基于Geo的服务,通常需要计算两个经纬度之间的距离,本文就围绕这个需求展开进行测试。看看qps能到多少。
使用JMH,预热3次,重复测试10次,得到QPMS=43.477/ms,即QPS=43477/s
Benchmark Mode Cnt Score Error Units
RedisGeobenchmark.testGeoDistanceCal thrpt 50 43.477 ± 0.588 ops/ms
package com.evan;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.util.FileUtils;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.args.GeoUnit;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* redis geo 距离查询基准测试
*/
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS)
@Threads(8)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class RedisGeobenchmark {
JedisPooled jedis;
List jwdLists = new ArrayList<>(10000);
Random random = new Random();
@Setup
public void prepare() throws IOException {
double GEO_LAT_MIN = -85.05112878d;
double GEO_LAT_MAX = 85.05112878d;
double GEO_LONG_MIN = -180;
double GEO_LONG_MAX = 180;
jedis = new JedisPooled("localhost", 6379);
Collection datas = FileUtils.readAllLines(new File("/home/evan/IdeaProjects/redis-jmh/JWD"));
for (String data : datas) {
String[] jwd = data.split(",");
double lon = Double.parseDouble(jwd[0]);
double lat = Double.parseDouble(jwd[1]);
//过滤无效的经纬度
if (lon < GEO_LONG_MIN || lon > GEO_LONG_MAX ||
lat < GEO_LAT_MIN || lat > GEO_LAT_MAX) {
continue;
}
jwdLists.add(jwd);
}
System.out.println(jwdLists.size());
}
@TearDown
public void shutdown() {
jedis.del("dis-cal");
jedis.close();
}
@Benchmark
public void testGeoDistanceCal() {
int rand = random.nextInt(Integer.MAX_VALUE);
int rand2 = random.nextInt(Integer.MAX_VALUE);
int size = jwdLists.size();
int i = rand % size;
int j = rand2 % size;
jedis.geoadd("dis-cal", Double.parseDouble(jwdLists.get(i)[0]), Double.parseDouble(jwdLists.get(i)[1]), "m" + i);
jedis.geoadd("dis-cal", Double.parseDouble(jwdLists.get(j)[0]), Double.parseDouble(jwdLists.get(j)[1]), "m" + (j));
jedis.geodist("dis-cal", "m" + i, "m" + j, GeoUnit.KM);
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(RedisGeobenchmark.class.getSimpleName())
.output("/home/evan/文档/dis-geo压力测试")
.build();
new Runner(options).run();
}
}
为了排除web容器的影响,因此我采用JMeter Java对象测试方式,我们自定义压力测试类编写业务测试逻辑,主要是实现JMeter的接口,然后打成Jar包,丢到JMeter的lib目录下运行。
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;
import org.openjdk.jmh.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.args.GeoUnit;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
/**
* Geo 压力测试
*/
public class RedisGeoStressTester extends AbstractJavaSamplerClient {
private static final Logger log = LoggerFactory.getLogger(RedisGeoStressTester.class);
JedisPooled jedis;
List jwdLists = new ArrayList<>(10000);
Random random = new Random();
@Override
public void setupTest(JavaSamplerContext context) {
double GEO_LAT_MIN = -85.05112878d;
double GEO_LAT_MAX = 85.05112878d;
double GEO_LONG_MIN = -180;
double GEO_LONG_MAX = 180;
jedis = new JedisPooled("localhost", 6379);
Collection datas = null;
try {
datas = FileUtils.readAllLines(new File("/home/evan/IdeaProjects/redis-jmh/JWD"));
for (String data : datas) {
String[] jwd = data.split(",");
double lon = Double.parseDouble(jwd[0]);
double lat = Double.parseDouble(jwd[1]);
//过滤无效的经纬度
if (lon < GEO_LONG_MIN || lon > GEO_LONG_MAX ||
lat < GEO_LAT_MIN || lat > GEO_LAT_MAX) {
continue;
}
jwdLists.add(jwd);
}
} catch (IOException e) {
log.error("读取文件失败", e);
}
System.out.println("初始化经纬度样本空间:" + jwdLists.size());
// 初始化jedis连接池
jedis = new JedisPooled("localhost", 6379);
System.out.println("jedis连接池初始化成功");
}
@Override
public SampleResult runTest(JavaSamplerContext javaSamplerContext) {
// 测试结果
SampleResult sampleResult = new SampleResult();
sampleResult.setSampleLabel("geo distance");
sampleResult.sampleStart();
try {
int rand = random.nextInt(Integer.MAX_VALUE);
int rand2 = random.nextInt(Integer.MAX_VALUE);
int size = jwdLists.size();
int i = rand % size;
int j = rand2 % size;
jedis.geoadd("dis-cal", Double.parseDouble(jwdLists.get(i)[0]), Double.parseDouble(jwdLists.get(i)[1]), "m" + i);
jedis.geoadd("dis-cal", Double.parseDouble(jwdLists.get(j)[0]), Double.parseDouble(jwdLists.get(j)[1]), "m" + (j));
Double geodist = jedis.geodist("dis-cal", "m" + i, "m" + j, GeoUnit.KM);
log.info("[{},{}]- [{}, {}] distance = {} km", jwdLists.get(i)[0], jwdLists.get(i)[1], jwdLists.get(j)[0], jwdLists.get(j)[1], geodist);
sampleResult.setSuccessful(true);
} catch (Exception e) {
log.error("geo distance execute fail");
sampleResult.setSuccessful(false);
} finally {
sampleResult.sampleEnd();
}
return sampleResult;
}
@Override
public void teardownTest(JavaSamplerContext context) {
jedis.del("dis-cal");
jedis.close();
System.out.println("jedis连接池关闭");
}
}
redis-jmh
Java微基准测试框架JMH
JMH官网
jmeter压测
jmeter官网
目前根据我自己的测试结果,redis geo操作应该不会存在什么性能瓶颈的问题,至于前面同事说的200qps,可能测试过程中实物导致的。