背景
有相当一部分开发同学认为,有了Redis就没必要使用应用缓存。这其实是相当错误的观念。
为了验证,我通过基于JMH基准测试工具,对应用内存操作及同一台机器上的Redis进行操作进行性能对比。通过实际结果来展示两者的性能差距。
测试平台
软件
java version "11.0.16" 2022-07-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.16+11-LTS-199)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.16+11-LTS-199, mixed mode)
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jmh.version>1.36</jmh.version>
</properties>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.20.0</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</dependency>
硬件
# sysctl machdep.cpu
machdep.cpu.cores_per_package: 10
machdep.cpu.core_count: 10
machdep.cpu.logical_per_package: 10
machdep.cpu.thread_count: 10
machdep.cpu.brand_string: Apple M1 Pro
测试结果
应用缓存的测试结果
Benchmark Mode Cnt Score Error Units
MemoryBenchmark.testAtomicLong thrpt 0.061 ops/ns
MemoryBenchmark.testAtomicLong avgt 68.881 ns/op
MemoryBenchmark.testAtomicLong sample 168554 227.721 ± 7.093 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p0.00 sample ≈ 0 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p0.50 sample 167.000 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p0.90 sample 375.000 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p0.95 sample 458.000 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p0.99 sample 500.000 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p0.999 sample 1374.000 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p0.9999 sample 30643.232 ns/op
MemoryBenchmark.testAtomicLong:testAtomicLong·p1.00 sample 279040.000 ns/op
MemoryBenchmark.testAtomicLong ss 1646.000 ns/op
同机Redis的测试结果
Benchmark Mode Cnt Score Error Units
RedisBenchmark.testAtomicLong thrpt ≈ 10⁻⁵ ops/ns
RedisBenchmark.testAtomicLong avgt 682971.881 ns/op
RedisBenchmark.testAtomicLong sample 12349 647815.826 ± 5691.209 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p0.00 sample 264192.000 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p0.50 sample 623616.000 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p0.90 sample 831488.000 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p0.95 sample 909312.000 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p0.99 sample 1180672.000 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p0.999 sample 2361344.000 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p0.9999 sample 5163376.640 ns/op
RedisBenchmark.testAtomicLong:testAtomicLong·p1.00 sample 5169152.000 ns/op
RedisBenchmark.testAtomicLong ss 1994145.750 ns/op
结论
可以看到,应用内存操作AtomicLong自增,平均一次操作为68.881
纳秒,而通过Redis进行一次操作为682971.881
纳秒。耗时为10000倍,而这样的开销主要体现在网络传输上,而非Redis自身。
为此我还写了段lua脚本,测试直接以Redis Cli的方式执行100w次自增所花费的之间。
测试代码
内存代码
/**
* @Author ZhouLiangCheng
* @Date 2023/4/27
* @Version 1.0
*/
@BenchmarkMode({Mode.All})
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 1, time = 2)
@Threads(4)
@Measurement(iterations = 1, time = 2)
@Timeout(time = 5)
public class MemoryBenchmark {
Logger logger = LoggerFactory.getLogger(MemoryBenchmark.class);
AtomicLong atomicLong;
@Setup(Level.Trial)
public void init() {
atomicLong = new AtomicLong();
}
@TearDown(Level.Trial)
public void end() {
logger.info(atomicLong.get() + "");
}
@Benchmark
public void testAtomicLong() {
atomicLong.incrementAndGet();
}
}
Redis测试代码
/**
* @Author ZhouLiangCheng
* @Date 2023/4/27
* @Version 1.0
*/
@BenchmarkMode({Mode.All})
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 1, time = 2)
@Threads(4)
@Measurement(iterations = 1, time = 2)
@Timeout(time = 5)
public class RedisBenchmark {
Logger logger = LoggerFactory.getLogger(MemoryBenchmark.class);
RedissonClient client;
@Setup
public void init() {
Config config = new Config();
config.useSingleServer()
.setTimeout(10000)
.setAddress("redis://10.1.1.66:6379")
.setDatabase(0)
.setPassword("12345678")
.setConnectionPoolSize(16)
.setConnectionMinimumIdleSize(16)
;
client = Redisson.create(config);
}
@TearDown
public void end() {
client.shutdown();
RAtomicLong abc = client.getAtomicLong("a");
logger.info(abc.get() + "");
}
@Benchmark
public void testAtomicLong() {
RAtomicLong abc = client.getAtomicLong("a");
long l = abc.addAndGet(1);
}
}
Lua脚本代码