深度评测丨 GaussDB(for Redis) 大 Key 操作的影响
本文分享自华为云社区《墨天轮评测: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



[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


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,自己的业务也不至于受太大影响。
点击关注,第一时间了解华为云新鲜技术~