关于Redis大键(Key),咱们从[空间复杂性]和访问它的[时间复杂度]两个方面来定义大键。前者主要表示Redis键的占用内存大小;后者表示Redis集合数据类型(set/hash/list/sorted set)键,所含有的元素个数。如下两个示例:git
1个大小200MB的String键(String Object最大512MB),内存空间占用较大;1个包含100000000(1kw)个字段的Hash键,对应访问模式(如hgetall)时间复杂度高
由于内存空间复杂性处理耗时都很是小,测试 del 200MB String键耗时约1毫秒,而删除一个含有1kw个字段的Hash键,却会阻塞Redis进程数十秒。因此本文只从时间复杂度分析大的集合类键。删除这种大键的风险,以及怎么优雅地删除。github
在Redis集群中,应用程序尽可能避免使用大键;直接影响容易致使集群的容量和请求出现”倾斜问题“,具体分析见文章:redis-cluster-imbalance。但在实际生产过程当中,总会有业务使用不合理,出现这类大键;当DBA发现后推动业务优化改造,而后删除这个大键;若是直接删除它,DEL命令可能阻塞Redis进程数十秒,对应用程序和Redis集群可用性形成严重的影响。redis
DEL命令在删除单个集合类型的Key时,命令的时间复杂度是O(M),其中M是集合类型Key包含的元素个数。测试
DEL keyTime complexity: O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).优化
生产环境中遇到过屡次因业务删除大Key,致使Redis阻塞,出现故障切换和应用程序雪崩的故障。测试删除集合类型大Key耗时,通常每秒可清理100w~数百w个元素; 若是数千w个元素的大Key时,会致使Redis阻塞上10秒可能致使集群判断Redis已经故障,出现故障切换;或应用程序出现雪崩的状况。this
说明:Redis是单线程处理。单个耗时过大命令,致使阻塞其余命令,容易引发应用程序雪崩或Redis集群发生故障切换。因此避免在生产环境中使用耗时过大命令。spa
Redis删除大的集合键的耗时, 测试估算,可参考;和硬件环境、Redis版本和负载等因素有关线程
Key类型 | Item数量 | 耗时 |
---|---|---|
Hash | ~100万 | ~1000ms |
List | ~100万 | ~1000ms |
Set | ~100万 | ~1000ms |
Sorted Set | ~100万 | ~1000ms |
当咱们发现集群中有大key时,要删除时,如何优雅地删除大Key?code
从Redis2.8版本开始支持SCAN命令,经过m次时间复杂度为O(1)的方式,遍历包含n个元素的大key.这样避免单个O(n)的大命令,致使Redis阻塞。 这里删除大key操做的思想也是如此。进程
经过hscan命令,每次获取500个字段,再用hdel命令,每次删除1个字段。Python代码:
def del_large_hash(): r = redis.StrictRedis(host='redis-host1', port=6379) large_hash_key ="xxx" cursor = '0' while cursor != 0: cursor, data = r.hscan(large_hash_key, cursor=cursor, count=500) for item in data.items(): r.hdel(large_hash_key, item[0])
删除大set键,使用sscan命令,每次扫描集合中500个元素,再用srem命令每次删除一个键Python代码:
def del_large_set(): r = redis.StrictRedis(host='redis-host1', port=6379) large_set_key = 'xxx' cursor = '0' while cursor != 0: cursor, data = r.sscan(large_set_key, cursor=cursor, count=500) for item in data: r.srem(large_size_key, item)
删除大的List键,未使用scan命令; 经过ltrim命令每次删除少许元素。Python代码:
def del_large_list(): r = redis.StrictRedis(host='redis-host1', port=6379) large_list_key = 'xxx' while r.llen(large_list_key)>0: r.ltrim(large_list_key, 0, -101)
删除大的有序集合键,和List相似,使用sortedset自带的zremrangebyrank命令,每次删除top 100个元素。Python代码:
def del_large_sortedset(): r = redis.StrictRedis(host='large_sortedset_key', port=6379) large_sortedset_key='xxx' while r.zcard(large_sortedset_key)>0: r.zremrangebyrank(large_sortedset_key,0,99)
应该从3.4版本开始,Redis会支持lazy delete free的方式,删除大键的过程不会阻塞正常请求。