Redis被广泛的应用,得益于它支持高性能访问(微秒级)。作为一个DBA,经常需要去维护bigkey。如果现在业务方需要你去删除一个hash类型的key,且这个key有3000多万个成员,内存占用超过1.8G。如何优雅的删除这个bigkey呢?下面让我来简单的介绍一下。
bigkey定义:
key本身的数据量过大:一个string类型的key,它的值为5 MB。key中的成员数过多:一个zset类型的key,它的成员数量为10万个。key中成员的数据量过大:一个hash类型的key,它的成员数量虽然只有1000个但这些成员的value(值)总大小为100 MB。
bigkey危害:
长尾延迟,客户端执行命令的时长变慢。 对bigkey执行读请求,会使Redis实例的带宽使用率被占满,导致自身服务变慢,同时易波及相关的服务。
对bigkey执行删除操作,易造成主库较长时间的阻塞,进而可能引发同步中断或主从切换。
Redis内存达到maxmemory参数定义的上限引发操作阻塞或重要的Key被逐出,甚至引发内存溢出(Out Of Memory)。
集群架构下,容易导致数据分片的内存资源倾斜、CPU使用率倾斜、带宽倾斜。
案例描述:
生产环境,DBA错误的使用了DEL命令删除一个bigkey,导致Redis出现阻塞。
案例警示:
1.合理制度规范(风险操作需要审核,多沟通和多确认),能有效的减少故障。
2.避免使用bigkey。
3.控制Redis实例容量。
技术回放
redis_version:4.0.14,支持unlink,异步操作。Redis4.0及之后版本:可以通过UNLINK命令安全地删除大Key甚至特大Key,该命令能够以非阻塞的方式,逐步地清理传入的Key)127.0.0.1:6379[3]>infoKeyspace# Keyspace 整个实例只有一个key,在db3 这是测试环境,生产推荐使用db0db3:keys=1,expires=0,avg_ttl=0127.0.0.1:6379>select3OK127.0.0.1:6379[3]>SCAN01)"0"2)1)"hash_bigkey_test"127.0.0.1:6379[3]>typehash_bigkey_testhash127.0.0.1:6379[3]>ttlhash_bigkey_test(integer) -1表示为设置过期时间127.0.0.1:6379[3]>HLENhash_bigkey_test(integer) 31414065hash_bigkey_test成员数量为3000多万127.0.0.1:6379[3]>infomemory# Memoryused_memory:2028479472used_memory_human:1.89Ghash_bigkey_test占用内存为1.89G,是非常的大了used_memory_rss:2076168192used_memory_rss_human:1.93G拓展:我喜欢在slave上做bgsave,然后分析bigkey;rdb.py可以分析出成员的个数、过期时间、占用内存和类型等/root/rdbtools-0.1.15/rdbtools/cli/rdb.py-cutf-8-cmemorydump.rdb>dump1221.log--查找bigkey的一种方法,优点:从库执行,对线上服务影响小;缺点:时效性差,RDB文件较大时耗时较长。moredump1221.logdatabase,type,key,size_in_bytes,encoding,num_elements,len_largest_element,expiry3,hash,hash_bigkey_test,2161840836,hashtable,31414065,13, 你猜一下3000多万的key,占用内存为1.89G的bigkey,通过del删除,需要多少时间呢?执行timeredis-cli-p6379-a密码-n3delhash_bigkey_test(integer) 1real0m26.527s---答案是26秒(我是用空闲的物理机做测试的,如果规格是:CPU2C内存4GB,阻塞时间会更长))user0m0.002ssys0m0.000s127.0.0.1:6379>SLOWLOGget---查询慢日志1)1) (integer) 10---slowlog唯一的id,重启后会被重置2) (integer) 1703158202---以unix时间戳表示的日志记录时间2023-12-2119:30:023) (integer) 26524988---命令执行时间,单位微秒,26秒4)1)"del"2)"hash_bigkey_test"5)"127.0.0.1:18390"6)""证明Redis确实阻塞了26秒,如下:编写一个脚本redis_ping.py,不断对Redis发起ping操作,返回的结果输入到redis_ping.log中tailf-n20redis_ping.log---输出响应结果和时间ping执行时间2023-12-2119:29:35.931984: 返回结果:pong, 耗时: 0:00:00.000152ping执行时间2023-12-2119:29:35.932159: 返回结果:pong, 耗时: 0:00:00.000164ping执行时间2023-12-2119:29:35.932321: 返回结果:pong, 耗时: 0:00:00.000151ping执行时间2023-12-2119:29:35.932483: 返回结果:pong, 耗时: 0:00:00.000151ping执行时间2023-12-2119:30:02.460248: 返回结果:pong, 耗时: 0:00:26.527754---ping命令发送后,pong返回时间隔了26秒,说明Redis阻塞了26秒ping执行时间2023-12-2119:30:02.463208: 返回结果:pong, 耗时: 0:00:00.002367ping执行时间2023-12-2119:30:02.463781: 返回结果:pong, 耗时: 0:00:00.000479优雅的删除hash_bigkey_test这个key:1.和业务方项目组沟通,确认hash_bigkey_test可以删除。(做好沟通,很重要)2.将key改名字:RENAMEhash_bigkey_testhash_bigkey_test_20231221。(key改名后,保留一段时间,二次确认无异常访问)3.业务低峰期,渐进式遍历hash_bigkey_test_20231221,每次取出100个成员,然后删除,直到全部删除。nohuppythonhdel_big_key.py>>hdel_big_key.log&morehdel_big_key.logKey: field1633170, Value: value1633170Response: 1, ExecutionTime: 0.000195026397705secondsKey: field87462, Value: value87462Response: 1, ExecutionTime: 0.000221014022827secondsKey: field818659, Value: value818659Response: 1, ExecutionTime: 0.000204086303711seconds优雅的删除hash_bigkey_testhdel_big_key.py代码如下:#!/usr/bin/python# coding=utf-8importredisimportsysimporttimedb_host="110.110.110.110"db_port=6379pwd="密码"r=redis.StrictRedis(host=db_host, port=db_port, password=pwd, db=3) cursor=0whileTrue: # 使用 HSCAN 命令获取 100 个成员及下一个游标位置result=r.hscan("hash_bigkey_test_20231221", cursor, count=100) # 获取返回结果中的成员和下一个游标位置elements=result[1] cursor=result[0] # 处理获取的成员forkey, valueinelements.items(): # 输出成员print("Key: {}, Value: {}".format(key, value)) # 测量删除操作的执行时间start_time=time.time() response=r.hdel("hash_bigkey_test_20231221", key) end_time=time.time() # 输出删除操作的执行时间execution_time=end_time-start_timeprint("Response: {}, Execution Time: {} seconds".format(response, execution_time)) # 如果游标为 0,表示已经遍历完所有成员,结束循环ifcursor==0: break其他类型的key,比如list、set也可以采取渐进式遍历,并小批量的删除bigkey。分享数据库故障处理的微信公众号:MySQL_DBA,欢迎关注,谢谢!