摘要:通过几个简单的实验,测试下对于大key这一“性能杀手”,GaussDB(for Redis)的表现怎样,和原生Redis相比在性能上有哪些改进?

本文分享自华为云社区​​《墨天轮评测:GaussDB(for Redis)大Key操作的影响》​​,作者: 高斯Redis官方博客。

在前一篇文章​​《墨天轮评测:GaussDB(for Redis)稳定性与扩容表现》​​ 中,我们使用多线程压测工具memtier_benchmark对华为GaussDB(for Redis)和原生Redis进行了对比压测,发现原生Redis容易出现OOM故障,且扩容操作会很慢,给运维带来很大压力。反观华为GaussDB(for Redis)不仅性能稳定,还具备在压测过程中秒级扩容的能力,扩容操作对业务读写无影响。华为GaussDB(for Redis)支持全量数据落盘,GaussDB基础组件服务提供底层数据三副本冗余保存,能够保证数据零丢失。如果使用场景既要满足KV查询的高性能,又希望数据得到重视能够不丢,则华为GaussDB(for Redis)是合适的选型。

我们大多在实际生产环境中都遇到过big key对Redis性能的严重影响。接下来我们通过几个简单的实验,测试下对于大key这一“性能杀手”,GaussDB(for Redis)的表现怎样,和原生Redis相比在性能上有哪些改进?

1、访问大key

首先分别在GaussDB(for Redis)和原生redis中创建一个大的hash类型的key。

编辑一个简单的lua脚本,向一个hash key中插入一千万条数据。

# vim redis-bigkey.lua
local result
for var=1,10000000,1 do
redis.call('hset',KEYS[1],var,var)
redis.call('lpush',KEYS[2],var)
redis.call('sadd',KEYS[3],var)
end
return result

向GaussDB(for Redis)【192.168.0.226:8635】中插入大key

# redis-cli  -h  192.168.0.226 -a XXXXXXX -p 8635   --eval /root/redis-bigkey.lua 3 hset_test list_test set_test
(nil)

# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 hlen hset_test
(integer) 10000000
# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 scard sadd_test
(integer) 10000000
# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 llen lpush_test
(integer) 10000000

向原生Redis【192.168.0.135】中插入大key

# redis-cli  -h  192.168.0.135 -a XXXXXXX -p 6379   --eval /root/redis-bigkey.lua 3 hset_test list_test set_test                
(nil)

# redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 hlen hset_test
(integer) 10000000
# redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 scard sadd_test
(integer) 10000000
# redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 llen lpush_test
(integer) 10000000

使用memtier_benchmark模拟业务压力的同时 对大key进行访问,观察对业务qps的影响。

编辑一个简单shell脚本,对大key进行频繁的访问

#!/bin/bash
while true
do
redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 hget hset_test $RANDOM
redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 hget hset_test $RANDOM
redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 LREM lpush_test 0 $RANDOM
redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 LREM lpush_test 0 $RANDOM
redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 SISMEMBER sadd_test $RANDOM
redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 SISMEMBER sadd_test $RANDOM
done

使用memtier_benchmark进行压测,读写混合场景。通过反馈的性能数据可以看到 GaussDB(for Redis) 和Redis每秒的操作数ops/sec 分别为14万和13万,差别不大。

#GaussDB(for Redis)
memtier_benchmark -s 192.168.0.226 -a XXXXXXX -p 8635 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=1:1 --out-file=./result_gauss_setget.log

[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 3%, 4 secs] 12 threads: 582045 ops, 144053 (avg: 145472) ops/sec, 21.16MB/sec (avg: 21.51MB/sec), 1.33 (avg: 1.32) msec latency
#原生Redis
memtier_benchmark -s 192.168.0.135 -a XXXXXXX -p 6379 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=1:1 --out-file=./result_redis_setget.log

[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 7%, 11 secs] 12 threads: 1430798 ops, 132637 (avg: 130051) ops/sec, 70.51MB/sec (avg: 68.79MB/sec), 1.44 (avg: 1.47) msec latency

启动shell脚本后再次观察,发现GaussDB(for Redis) 的每秒操作数几乎无变化,而原生Redis的每秒操作数波动巨大,甚至降低到了3k左右。说明大key操作对原生Redis性能有较大影响,对GaussDB(for Redis) 的影响可控。

# bash hget_bigkey.sh

#GaussDB(for Redis)
# memtier_benchmark -s 192.168.0.226 -a XXXXXXX -p 8635 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=1:1 --out-file=./result_gauss_setget.log

[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 47%, 64 secs] 12 threads: 9099444 ops, 139186 (avg: 142163) ops/sec, 20.60MB/sec (avg: 20.96MB/sec), 1.38 (avg: 1.35) msec latency
#原生Redis
# memtier_benchmark -s 192.168.0.135 -a XXXXXXX -p 6379 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=1:1 --out-file=./result_redis_setget.log

[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 29%, 75 secs] 12 threads: 5607700 ops, 3329 (avg: 74759) ops/sec, 1.80MB/sec (avg: 40.08MB/sec), 52.35 (avg: 2.55) msec latencyy

2、删除大key

继续使用memtier_benchmark对GaussDB(for Redis) 和原生Redis进行测试,只读场景。在GaussDB(for Redis)中删除大key很快就能完成,且对性能几乎无影响。

#GaussDB(for Redis)
# memtier_benchmark -s 192.168.0.226 -a XXXXXXX -p 8635 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=0:1 --out-file=./result_gauss_setget.log
Writing results to ./result_gauss_setget.log...
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 4%, 5 secs] 12 threads: 719216 ops, 151326 (avg: 143795) ops/sec, 22.16MB/sec (avg: 21.13MB/sec), 1.27 (avg: 1.33) msec latency
# time redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 del sadd_test
(integer) 1

real 0m0.003s
user 0m0.001s
sys 0m0.002s
# memtier_benchmark -s 192.168.0.226 -a XXXXXXX -p 8635 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=0:1 --out-file=./result_gauss_setget.log
Writing results to ./result_gauss_setget.log...
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 42%, 57 secs] 12 threads: 8031731 ops, 144874 (avg: 140890) ops/sec, 21.46MB/sec (avg: 20.77MB/sec), 1.32 (avg: 1.36) msec latency

反观原生Redis,删除大key耗时3秒,且在删除期间对性能影响较大。可以观察到在删除期间ops/sec 变成0,也就是说大key删除期间操作是没有办法正常响应的。

#原生Redis
# memtier_benchmark -s 192.168.0.135 -a XXXXXXX -p 6379 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=0:1 --out-file=./result_redis_setget.log
Writing results to ./result_redis_setget.log...
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 6%, 7 secs] 12 threads: 1107132 ops, 157621 (avg: 158125) ops/sec, 16.07MB/sec (avg: 16.13MB/sec), 1.22 (avg: 1.21) msec latency

# time redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 del sadd_test
(integer) 1

real 0m3.001s
user 0m0.000s
sys 0m0.003s

# memtier_benchmark -s 192.168.0.135 -a XXXXXXX -p 6379 -c 16 -t 12 -n 100000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000000 --key-minimum=1 --key-prefix= --ratio=0:1 --out-file=./result_redis_setget.log
Writing results to ./result_redis_setget.log...
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 57%, 68 secs] 12 threads: 1015893 ops, 0 (avg: 126961) ops/sec, 0.00KB/sec (avg: 12.98MB/sec), -nan (avg: 1.13) msec latencyy

手动删除大key对性能的影响差别明显,如果设置大key的过期时间交由Redis删除过期数据 是否会有性能影响呢?下面简单测试下

手动设置大key的过期时间,并启动memtier_benchmark读写混合测试,查看对性能的影响。通过测试发现大key的过期对于GaussDB(for Redis)的性能几乎没有影响。

#GaussDB(for Redis)
[root@ecs-ef13-0001 ~]# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 EXPIRE sadd_test 8 && redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 EXPIRE sadd_test1 12
(integer) 1
(integer) 1

[root@ecs-ef13-0001 ~]# memtier_benchmark -s 192.168.0.226 -a XXXXXXX -p 8635 -c 16 -t 12 -n 10000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000 --key-minimum=1 --key-prefix= --ratio=1:1 --out-file=./result_gauss_setget.log
Writing results to ./result_gauss_setget.log...
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 100%, 17 secs] 0 threads: 1920000 ops, 106367 (avg: 109940) ops/sec, 105.02MB/sec (avg: 108.55MB/sec), 1.74 (avg: 1.74) msec latency

在对原生Redis测试时,我们发现大key过期操作几乎阻塞了正常的读写,在memtier_benchmark测试时ops/sec 指标为0,只有当大key过期操作结束后才恢复正常。

#原生Redis

[root@ecs-ef13-0001 ~]# redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 EXPIRE sadd_test 8 && redis-cli -h 192.168.0.135 -a XXXXXXX -p 6379 EXPIRE sadd_test1 12
(integer) 1
(integer) 1

深度评测丨GaussDB(for Redis)大Key操作的影响_大Key

深度评测丨GaussDB(for Redis)大Key操作的影响_redis_02

深度评测丨GaussDB(for Redis)大Key操作的影响_压测_03

[root@ecs-ef13-0001 ~]# memtier_benchmark -s 192.168.0.135 -a XXXXXXX -p 6379 -c 16 -t 12  -n 10000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000 --key-minimum=1 --key-prefix= --ratio=1:1 --out-file=./result_redis_setget.log
Writing results to ./result_redis_setget.log...
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 100%, 42 secs] 0 threads: 1920000 ops, 134502 (avg: 45551) ops/sec, 132.80MB/sec (avg: 44.98MB/sec), 1.43 (avg: 4.21) msec latency

开源redis的过期虽然也支持异步,但需要用户手动配置策略;删除操作则需用UNLINK替换常规的DEL,具体对性能的影响可能会有所降低,本次不做深入验证。华为GaussDB(for Redis)的删除和过期对性能0影响。

3、大key对GaussDB(for Redis)扩容操作的影响

在上一篇文章中我们测试了GaussDB(for Redis)的在线扩容功能,经测试GaussDB(for Redis)可以在不影响业务读写的前提下实现秒级的扩容。这次我们增加一些“难度”,看看存在大key的情况下GaussDB(for Redis)扩容操作是否还能做到秒级和业务零感知。

预先在GaussDB(for Redis)中插入大key

[root@ecs-ef13-0001 ~]# redis-cli  -h  192.168.0.226 -a XXXXXXX -p 8635 scard sadd_test4
(integer) 10000000
[root@ecs-ef13-0001 ~]# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 scard sadd_test5
(integer) 10000000
[root@ecs-ef13-0001 ~]# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 scard sadd_test6
(integer) 10000000
[root@ecs-ef13-0001 ~]# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 scard sadd_test7
(integer) 10000000
[root@ecs-ef13-0001 ~]# redis-cli -h 192.168.0.226 -a XXXXXXX -p 8635 scard sadd_test8
(integer) 10000000

使用memtier_benchmark模拟读写请求,同时在控制台上进行扩容操作。同之前的测试效果一样,GaussDB(for Redis)同样实现了对业务零感知的秒级扩容

[root@ecs-ef13-0001 ~]# memtier_benchmark -s 192.168.0.226 -a XXXXXXX -p 8635 -c 16 -t 12  -n 50000 --random-data --randomize --distinct-client-seed -d 1000 --key-maximum=65000 --key-minimum=1 --key-prefix= --ratio=0:1 --out-file=./result_gauss_setget.log  
Writing results to ./result_gauss_setget.log...
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 9%, 20 secs] 12 threads: 902361 ops, 42634 (avg: 45112) ops/sec, 41.99MB/sec (avg: 44.44MB/sec), 4.53 (avg: 4.23) msec latencycy

深度评测丨GaussDB(for Redis)大Key操作的影响_数据_04深度评测丨GaussDB(for Redis)大Key操作的影响_redis_05

4、总结

原生Redis对大key的访问,删除等操作会严重阻塞业务的正常访问,这是由Redis自身单线程处理请求的架构决定的。使用原生Redis时需要严格限制大key的使用,一旦出现大key对系统的性能影响通常是“致命”的。

反观GaussDB(for Redis)由于采用多线程架构,对大key的访问、删除,以及存在大key情况下的扩容操作,对性能的影响都是可控的。1)大key访问场景中,由于GaussDB(for Redis)采用的多线程的架构,不易阻塞其他业务操作。2)大key删除的场景中,由于GaussDB(for Redis)实现的逻辑不同,删除操作能够快速完成,对业务无影响。3)扩容场景中,GaussDB(for Redis)不涉及key迁移,大key对扩容更是0影响。

综上,虽然一般推荐业务设计避免大key,但在一些需要操作少量大key的业务场景,华为云GaussDB(for Redis)表现更佳。

此外,从业务开发角度看,当多业务共用一个实例时,使用GaussDB(for Redis)的话,即使其他业务引入大key,自己的业务也不至于受太大影响。

​点击关注,第一时间了解华为云新鲜技术~​