Redis Geo地理位置服务计算性能测试

Redis Geo地理位置服务计算性能测试

  • 背景
  • 测试对象
  • 思路
  • 系统&工具版本
  • 基准测试
  • JMeter测试
  • Jmeter测试结果
  • 代码
  • 参考
  • 结论

背景

之前和领导交流的时候,探讨过基于redis的LBS服务,当时他表示“redis geo性能很垃圾,自己亲自压测过,只有200多qps”。我对这个测试结果表示怀疑的,200qps太少了,和数据库差不多。

由于不知道他当时是怎么测试的,因此我只能动手自己测试下。

测试对象

基于Geo的服务,通常需要计算两个经纬度之间的距离,本文就围绕这个需求展开进行测试。看看qps能到多少。

思路

  1. 先进行基准测试
  2. 使用jmeter进行Java对象的压测(ps:不压测HTTP接口,是为了排除web服务器的性能影响)

系统&工具版本

  • 操作系统 Ubuntu 22.04 LTS
  • CPU 单CPU,4核心
  • 内存 16G
  • Redis version=6.0.16,
  • openjdk version “1.8.0_342”
  • Linux内核版本 5.15.0-25-generic
  • apache-jmeter-5.5

基准测试

使用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();
    }

}

JMeter测试

为了排除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连接池关闭");
    }
}

Jmeter测试结果

8线程情况下,QPS= 36544
Redis Geo地理位置服务计算性能测试_第1张图片

Redis Geo地理位置服务计算性能测试_第2张图片

代码

redis-jmh

参考

Java微基准测试框架JMH
JMH官网
jmeter压测
jmeter官网

结论

目前根据我自己的测试结果,redis geo操作应该不会存在什么性能瓶颈的问题,至于前面同事说的200qps,可能测试过程中实物导致的。

你可能感兴趣的:(redis,redis)