前言
Redis作为时下最火爆的NoSQL数据库以性能强悍、数据结构丰富著称,同时其成长的脚步也从未停止,自诞生伊始已经历多次蜕变不断推出新功能。
社区最新GA版本Redis 4.0推出已近一年,阿里云数据库Redis 4.0版也上线近半年,之前关于Redis 4.0的系列文章从源码实现来分析这些新功能,本文旨在从用户角度出发,让Redis的用户能够快速了解并使用Redis 4.0带来的福利。
Lazyfree
大key删除的问题想必很多用户都遇到过,Redis除string外还支持list、set、hash和sorted set等复杂数据结构,这些数据结构丰富了Redis的用法,但是如果使用不当造成单key体积过大的话就会引起一些问题。
举个简单的例子,假如某社交网站有一个大V,有上百万的粉丝,我们可以用set集合类型的数据结构来存储他的粉丝ID,存储粉丝集合的key叫做funs好了,我们来看下粉丝数:
127.0.0.1:6379> SCARD funs
(integer) 6320505
的确是大V,有600多万的粉丝,但是很不幸的有一天这个大V注销了,这时就要删除他的信息,我们用DEL命令来删除这个key:
127.0.0.1:6379> DEL funs
(integer) 1
(3.11s)
127.0.0.1:6379> slowlog get
1) 1) (integer) 4
2) (integer) 1528169923
3) (integer) 3104812
4) 1) "DEL"
2) "funs"
5) "127.0.0.1:48398"
6) ""
- 小插曲:Redis 4.0扩展了slowlog的返回结果,展示了产生慢日志的客户端IP:PORT以便追本溯源。
可以看到删除这个动作居然耗时3秒多,也就意味着这3秒内Redis无法执行其他命令,这对于线上业务来讲是有伤害的,那么如何避免删除大key时的阻塞问题呢?Redis 4.0推出了Lazyfree这一功能,使用UNLINK命令来删除大key,主线程只负责把key从数据库中"摘除",真正的释放动作放在了BIO后台线程去做,我们来看下效果:
127.0.0.1:6379> UNLINK funs
(integer) 1
(3.11s)
127.0.0.1:6379> slowlog get
(empty list or set)
可以看到UNLINK执行很快没有产生slowlog。
Lazyfree一共有3个命令:
- UNLINK:异步删除key
- FLUSHDB ASYNC:异步清空当前DB
- FLUSHALL ASYNC:异步清空所有DB
以及4个配置项:
- lazyfree-lazy-expire:异步删除过期key
- lazyfree-lazy-eviction:异步淘汰key
- lazyfree-lazy-server-del:隐式删除时采取异步删除,比如rename a b,若b存在则需删除b
- slave-lazy-flush:全量同步时,slave异步清空所有DB
对于源码实现有兴趣的读者可以阅读《Redis 4.0之Lazyfree》。
Lua脚本支持随机操作
Redis内嵌了Lua环境来支持用户扩展功能,但是出于数据一致性考虑,要求脚本必须是纯函数的形式,也就是说对于一段Lua脚本给定相同的参数,重复执行其结果都是相同的。
为什么要有这个限制呢?原因是Redis不仅仅是单机版的内存数据库,它还支持主从复制和持久化,执行过的Lua脚本会复制给slave以及持久化到磁盘,如果重复执行得到结果不同,那么就会出现内存、磁盘、slave之间的数据不一致,在failover或者重启之后造成数据错乱影响业务。
还是以具体例子来看,假设有这么一段Lua脚本,目的很简单就是想记录下当前时间:
local now = redis.call('time')[1]
redis.call('set','now',now)
return redis.call('get','now')
这里使用了Redis的TIME命令来获取时间戳,然后存储到名为now的key中,但是其执行时会报错:
$redis-cli --eval escript
(error) ERR Error running script (call to f_cfba5ec6a699dad183456f19d1099d8dabfdb80c):
@user_script:3: @user_script: 3: Write commands not allowed after non deterministic commands.
Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.
错误提示也很明显,如果执行过非确定性命令(也就是TIME,因为时间是随机的),Redis就不允许执行写命令,以此来保证数据一致性。那如何才能实现随机写入呢?刚才的错误提示也给出了答案,使用redis.replicate_commands(),在执行redis.replicate_commands()之后,Redis就不再是把整个Lua脚本同步给slave和持久化,而是把脚本中调用Redis的写命令直接去做复制,那么slave和持久化也可以得到确定的结果。
脚本修改如下:
redis.replicate_commands()
local now = redis.call('time')[1]
redis.call('set','now',now)
return redis.call('get','now')
再执行就可以实现随机写入了:
$redis-cli --eval escript
"1528191578"
$redis-cli --eval escript
"1528191804"
基于LFU的热点key发现机制
LFU是Redis 4.0新增的一类内存逐出策略,提供了更精确的内存淘汰算法,其本质是记录了一段时间内key的访问频率,同时也带来了额外的福利就是热点key的发现。
LFU简单来讲就是用0-255来表示key的访问频率,值越大说明访问频率越高,并且这里对频率的计数采用的是基于对数的概率增长,LFU为255可以代表100W次的访问,关于LFU的实现有兴趣的读者可以参考《Redis 4.0之基于LFU的热点key发现机制》。
使用OBJECT FREQ命令即可获取指定key的访问频率,不过需要首先把内存逐出策略设置为allkeys-lfu或者volatile-lfu:
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
127.0.0.1:6379> object freq counter:000000006889
(error) ERR An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.
127.0.0.1:6379> config set maxmemory-policy allkeys-lfu
OK
127.0.0.1:6379> object freq counter:000000006889
(integer) 3
使用scan命令遍历所有key,再通过OBJECT FREQ获取访问频率并排序,即可得到热点key。为了方便用户使用,Redis自带的客户端redis-cli也提供了热点key发现功能,执行redis-cli时加上--hotkeys选项即可,示例如下:
$./redis-cli --hotkeys
# Scanning the entire keyspace to find hot 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%] Hot key 'counter:000000000002' found so far with counter 87
[00.00%] Hot key 'key:000000000001' found so far with counter 254
[00.00%] Hot key 'mylist' found so far with counter 107
[00.00%] Hot key 'key:000000000000' found so far with counter 254
[45.45%] Hot key 'counter:000000000001' found so far with counter 87
[45.45%] Hot key 'key:000000000002' found so far with counter 254
[45.45%] Hot key 'myset' found so far with counter 64
[45.45%] Hot key 'counter:000000000000' found so far with counter 93
-------- summary -------
Sampled 22 keys in the keyspace!
hot key found with counter: 254 keyname: key:000000000001
hot key found with counter: 254 keyname: key:000000000000
hot key found with counter: 254 keyname: key:000000000002
hot key found with counter: 107 keyname: mylist
hot key found with counter: 93 keyname: counter:000000000000
hot key found with counter: 87 keyname: counter:000000000002
hot key found with counter: 87 keyname: counter:000000000001
hot key found with counter: 64 keyname: myset
MEMORY内存分析命令
分析内存可以优化Redis的使用方式,全新的MEMORY命令可以帮助用户来实现这一操作。
MEMORY命令一共有5个子命令,可以通过MEMORY HELP来查看:
127.0.0.1:6379> memory help
1) "MEMORY DOCTOR - Outputs memory problems report"
2) "MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key"
3) "MEMORY STATS - Show memory usage details"
4) "MEMORY PURGE - Ask the allocator to release memory"
5) "MEMORY MALLOC-STATS - Show allocator internal stats"
关于各个子命令的详细使用方式可以参考《Redis 4.0之MEMORY命令详解》。
开始体验Redis 4.0
作者简介
赵钊,花名仲肥,阿里云技术专家,专注于阿里云数据库Redis版的开发工作,活跃于Redis开源社区,致力让开发者使用最好的云数据库服务。
欢迎加入:阿里云-技术专家-KVstore