什么是大key
很多铁子可能会认为大key,是这个key的值很大其实不是,而是key的value值很大一般对于下面这些我们可以称为大key.
- string 类型值大于10kb。
- hash、list、set、zset类型元素个数超过5000个。
大key会造成什么影响
这个大key主要会带来下面这几种影响:
- 阻塞工作线程:如果我们使用del命令删除大key,会阻塞工作线程这样就没有办法处理其他客户端发过来的命令了。
- 内存分布不均: 集群模型在slot分片均匀情况下会出现数据和查询倾斜的情况,部分有大key的redis结点占用内存较多。
- 客户端超时阻塞: redis的工作线程只有一个,操作这个大key会比较耗时会阻塞redis在客户端看来就说很久很久没有响应。
- 引发网络阻塞: 每次获取大key产生的网络流量比较大,这对于网卡比较小的服务器是灾难性的。
如何找到大key
1.我们可以通过redis客户端提供的命令 redis-cli --bigkeys.来查看
[root@vm-4-17-centos redis]# redis-cli --bigkeys # scanning the entire keyspace to find biggest keys as well as # average sizes per key type. you can use -i 0.1 to sleep 0.1 sec # per 100 scan commands (not usually needed). [00.00%] biggest string found so far '"k1"' with 2 bytes [00.00%] biggest hash found so far '"ksyi"' with 100 fields -------- summary ------- sampled 2 keys in the keyspace! total key length in bytes is 6 (avg len 3.00) biggest hash found '"ksyi"' has 100 fields biggest string found '"k1"' has 2 bytes 0 lists with 0 items (00.00% of keys, avg size 0.00) 1 hashs with 100 fields (50.00% of keys, avg size 100.00) 1 strings with 2 bytes (50.00% of keys, avg size 2.00) 0 streams with 0 entries (00.00% of keys, avg size 0.00) 0 sets with 0 members (00.00% of keys, avg size 0.00) 0 zsets with 0 members (00.00% of keys, avg size 0.00)
注意:
- 在使用这个命令来查询大key时,最好在从节点上执行。如果在主节点上执行会阻塞从节点。
- 如果没有这个从节点,那么我们可以选择在redis业务压力比较轻的时候执行避免影响正常的业务功能;或者我们可以使用**-i选项**来控制扫描间隔避免长时间扫描降低redis的性能。
这个方式其实也有缺点,他之只能返回每种类型最大的那个bigkey,无法获得大小在前n位的bigkey。
2.使用memory usage命令进行查询
- memory usage命令给出一个key和它的值在ram中所占用的字节数。返回的结果是key的值以及为管理该key分配的内存总字节数。
- 对于嵌套数据类型,可以使用选项samples,其中count表示抽样的元素个数,默认值为5。当需要抽样所有元素时,使用samples 0 。
其语法如下:
memory usage key [samples count]
下面我们来演示一下
127.0.0.1:6379> memory usage ksyi (integer) 496
3.通过scan命令来查找大key
- 我们可以先使用scan命令对数据库进行扫描,然后用type命令返回每一个key的类型。
- 对于string类型,我们可以直接使用strlen命令获取字符串的长度,也就是占用内存的字节数。
如何删除大key
如果我们一下子释放大量的内存,空闲内存块链表操作时间会增加,相应地就会造成redis主线程阻塞,如果redis主线程发生了阻塞其他客户端的请求可能会超时,如果超时的连接越来越多会造成各自异常问题。
因此我们删除大key这一个动作我们要特别的小心具体要怎么做这里给出两种方法:
- 渐进式删除
- 异步删除(unlink)
渐进式删除
1.对于string类型
对于这个string类型的大key我们可以使用del直接删除如果这个他确实很大我们可以使用unlink来进行异步删除 。这个特别简单在这里就不演示了。
2.对于hash类型
对于删除大的hash类型,我们可以使用hscan命令每次获取100个字段,这个个数根据业务来确定然后我们在使用hdel命令每次删除一个字段即可。
- 对应golang代码
package main import ( "fmt" "github.com/go-redis/redis" ) func delbighashkey(conn *redis.client) { bigkey := "xxx" cursor := uint64(0) for { scanret := conn.hscan(bigkey, cursor, "", 100) keys, c, err := scanret.result() if err != nil { panic(err) } fmt.println(keys) for i := 0; i < len(keys); i++ { conn.hdel(bigkey, keys[i]) } if c == 0 { break } cursor = c } } func main() { conn := redis.newclient(&redis.options{ addr: "101.35.98.26:6379", // url password: "", //redis密码 db: 0, // 0号数据库 }) _, err := conn.ping().result() if err != nil { fmt.println("ping err :", err) return } delbighashkey(conn) }
3.对应删除大的list,通过ltrim命令每次迭代删除少量元素
// delbiglistkey 删除大的listkey func delbiglistkey(conn *redis.client) { bigkey := "xxx" //要删除的大key for conn.llen(bigkey).val() > 0 { conn.ltrim(bigkey, 0, -101) //每次删除最右边100个元素 } }
4.删除大的set 可以先使用sscan获取部分元素,比如每次扫描集合当中100个元素在用srem命令每次删除一个键
func delbigsetkey(conn *redis.client) { bigkey := "xxx" //要删除的大key cursor := uint64(0) //游标 for { keys, c, err := conn.sscan(bigkey, cursor, "", 100).result() if err != nil { panic(err) } cursor = c for i := 0; i < len(keys); i++ { fmt.println(conn.srem(bigkey, keys[i]).val()) } if c == 0 { //删除完毕 break } } }
对于删除zset,使用zremrangebyrank命令每次删除top 100元素
func delbigzsetkey(conn *redis.client) { bigkey := "xxx" for conn.zcard(bigkey).val() > 0 { conn.zremrangebyrank(bigkey, 0, 99) } }
以上就是针对大key删除的方案。
异步删除
除了上面的删除方案我们可以采用异步删除的方式可以使用unlink命令代替del来删除这样就不会阻塞主线程。
如何禁用keys*、flushdb 、flushall等命令
如果我们想要禁用这些命令我们可以在redis.conf 默认配置文件,找到 security 区域,如以下所示:
这样这些命令就被禁用了。
redis有两个原语来删除键。一种称为del,是对象的阻塞删除。这意味着服务器停止处理新命令,以便以同步方式回收与对象关联的所有内存。如果删除的键与一个小对象相关联,则执行del命令所需的时间非常短,可与大多数其他命令相媲美
redis 中的o(1)或o(log_n)命令。但是,如果键与包含数百万个元素的聚合值相关联,则服务器可能会阻塞很长时间(甚至几秒钟)才能完成操作。
基于上述原因,redis还提供了非阻塞删除原语,例如unlink(非阻塞del)以及flushall和flushdb命令的async选项,以便在后台回收内存。这些命令在恒定时间内执行。另一个线程将尽可能快地逐步释放后台中的对象。
flushall和flushdb的del、unlink和async选项是用户控制的。这取决于应用程序的设计,以了解何时使用其中一个是个好主意。然而,作为其他操作的副作用,redis服务器有时不得不删除键或刷新整个数据库。具体而言,redis在以下场景中独立于用户调用删除对象:
我们可以将配置文件当中的这些参数设置为yes,也就是懒释放
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论