zoukankan      html  css  js  c++  java
  • Redis大Key优化

    Redis 大key

    Redis使用过程中经常会有各种大key的情况, 比如:

    1. 单个简单的key存储的value很大
    2. hash, set,zset,list 中存储过多的元素(以万为单位)

    由于redis是单线程运行的,如果一次操作的value很大会对整个redis的响应时间造成负面影响,所以,业务上能拆则拆,下面举几个典型的分拆方案。

    业务场景:

    即通过hash的方式来存储每一天用户订单次数。那么key = order_20181010, field = order_id, value = 10。那么如果一天有百万千万甚至上亿订单的时候,key后面的值是很多,存储空间也很大,造成所谓的大key。

    大key的风险:

    1. 读写大key会导致超时严重,甚至阻塞服务。
    2. 如果删除大key,DEL命令可能阻塞Redis进程数十秒,使得其他请求阻塞,对应用程序和Redis集群可用性造成严重的影响。
    3. 建议每个key不要超过M级别。

    单个简单的key存储的value很大

    改对象需要每次都整存整取

    可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的IO影响;

    该对象每次只需要存取部分数据

    可以像第一种做法一样,分拆成几个key-value, 也可以将这个存储在一个hash中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性

    hash, set,zset,list 中存储过多的元素

    类似于场景一种的第一个做法,可以将这些元素分拆。

    以hash为例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value)
    现在,固定一个桶的数量,比如 10000, 每次存取的时候,先在本地计算field的hash值,模除 10000, 确定了该field落在哪个key上。

    newHashKey  =  hashKey + (*hash*(field) % 10000);   
    hset (newHashKey, field, value) ;  
    hget(newHashKey, field)
    
    • 1
    • 2
    • 3

    set, zset, list 也可以类似上述做法.

    但有些不适合的场景,比如,要保证 lpop 的数据的确是最早push到list中去的,这个就需要一些附加的属性,或者是在 key的拼接上做一些工作(比如list按照时间来分拆)。

    如何优雅地删除Redis大键

    关于Redis大键(Key),我们从[空间复杂性]和访问它的[时间复杂度]两个方面来定义大键。
    前者主要表示Redis键的占用内存大小;后者表示Redis集合数据类型(set/hash/list/sorted set)键,所含有的元素个数。以下两个示例:

    1个大小200MB的String键(String Object最大512MB);内存空间角度占用较大
    1个包含100000000(1kw)个字段的Hash键,对应访问模式(如hgetall)时间复杂度高

    因为内存空间复杂性处理耗时都非常小,测试 del 200MB String键耗时约1毫秒,
    而删除一个含有1kw个字段的Hash键,却会阻塞Redis进程数十秒。所以本文只从时间复杂度分析大的集合类键。删除这种大键的风险,以及怎么优雅地删除。

    在Redis集群中,应用程序尽量避免使用大键;直接影响容易导致集群的容量和请求出现”倾斜问题“,具体分析见文章:redis-cluster-imbalance。但在实际生产过程中,总会有业务使用不合理,出现这类大键;当DBA发现后推进业务优化改造,然后删除这个大键;如果直接删除它,DEL命令可能阻塞Redis进程数十秒,对应用程序和Redis集群可用性造成严重的影响。

    直接删除大Key的风险

    DEL命令在删除单个集合类型的Key时,命令的时间复杂度是O(M),其中M是集合类型Key包含的元素个数。

    DEL key
    Time 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已经故障,出现故障切换;或应用程序出现雪崩的情况。

    说明:Redis是单线程处理。
    单个耗时过大命令,导致阻塞其他命令,容易引起应用程序雪崩或Redis集群发生故障切换。
    所以避免在生产环境中使用耗时过大命令。

    Redis删除大的集合键的耗时, 测试估算,可参考;和硬件环境、Redis版本和负载等因素有关

    Key类型Item数量耗时
    Hash ~100万 ~1000ms
    List ~100万 ~1000ms
    Set ~100万 ~1000ms
    Sorted Set ~100万 ~1000ms

    当我们发现集群中有大key时,要删除时,如何优雅地删除大Key?

    如何优雅地删除各类大Key

    从Redis2.8版本开始支持SCAN命令,通过m次时间复杂度为O(1)的方式,遍历包含n个元素的大key.
    这样避免单个O(n)的大命令,导致Redis阻塞。 这里删除大key操作的思想也是如此。

    Delete Large Hash Key

    通过hscan命令,每次获取500个字段,再用hdel命令,每次删除1个字段。
    Python代码:

    def del_large_hash():
        r = redis.StrictRedis(host='redis-host1', port=6379)
        large_hash_key ="xxx" #要删除的大hash键名
        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])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Delete Large Set Key

    删除大set键,使用sscan命令,每次扫描集合中500个元素,再用srem命令每次删除一个键
    Python代码:

    def del_large_set():
    	r = redis.StrictRedis(host='redis-host1', port=6379)
    	large_set_key = 'xxx' # 要删除的大set的键名
    	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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Delete Large List Key

    删除大的List键,未使用scan命令; 通过ltrim命令每次删除少量元素。
    Python代码:

    def del_large_list():
    	r = redis.StrictRedis(host='redis-host1', port=6379)
    	large_list_key = 'xxx' #要删除的大list的键名
    	while r.llen(large_list_key)>0:
    		r.ltrim(large_list_key, 0, -101) #每次只删除最右100个元素
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Delete Large Sorted set key

    删除大的有序集合键,和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)#时间复杂度更低 , 每次删除O(log(N)+100)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Redis Lazy Free

    应该从3.4版本开始,Redis会支持lazy delete free的方式,删除大键的过程不会阻塞正常请求。

    参考

    Redis大Key优化
    Redis 单key值过大 优化方式
    如何优雅地删除Redis大键

  • 相关阅读:
    在庫購買管理(MM)
    指図ステータス
    購買発注変更、照会画面に初期表示される発注伝票はどのように決まっているのか
    金額処理
    翻訳
    mysql 与mongodb的特点与优劣
    PHP经典算法
    Linux下PHP安装redis扩展
    Linux上安装Redis教程
    PHP插入法排序
  • 原文地址:https://www.cnblogs.com/shoshana-kong/p/14832359.html
Copyright © 2011-2022 走看看