Redis

Redis

(1) Jedis 的用法

(1.1) 多线程环境

单个 Jedis 实例不是线程安全的,于是我看见了这样的代码,这样的问题在于不停地在创建 Jedis 实例,而每一个 Jedis 就意味着一条连接:

1
2
3
4
5
6
7
8
9
10
11
while (true) {
Jedis jedis = null;

if ( tasknum.get() > 49 ){
Thread.sleep(1000);
continue;
}

jedis = new Jedis(redisip, 6379);
// ...
}

所以,推荐使用 JedisPool 来获得更好的性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final static JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");

Jedis jedis = null;
try {
jedis = pool.getResource();
/// ... do stuff here ... for example
jedis.set("foo", "bar");
String foobar = jedis.get("foo");
jedis.zadd("sose", 0, "car"); jedis.zadd("sose", 0, "bike");
Set<String> sose = jedis.zrange("sose", 0, -1);
} finally {
if (jedis != null) {
jedis.close();
}
}
/// ... when closing your application:
pool.destroy();

(2) 查看日志

1
cat /var/log/redis

(3) Redis 简介

Redis 的全称是:

Redis 可以做:

  • 缓存
  • 排行榜系统
  • 计数器应用
  • 社交网络
  • 消息队列系统

Redis 不可以做:

  • 存放大规模数据
  • 存放冷数据

豌豆荚开源了基于 ProxyRedis 分布式实现 Codis


查看版本Redis 借鉴了 Linux 操作系统对于版本号的命名规则: 版本号第二位如果是奇数,则为非稳定版本,如果是偶数,则为稳定版本:

1
2
$redis-cli -v
redis-cli 3.0.6

安装完 redis 之后,多了几个 redis 工具:


启动:

  • 使用 redis-sever 启动:

这种方式无法自定义配置,生成环境中根本不会使用

  • 使用 redis-server --port 6380 启动:

如果需要修改的配置较多或者希望将配置保存到文件中,不建议使用这种方式

  • 使用 redis-sever xxx.conf 配置文件来启动:
1
redis-server ~/Documents/github/redis/redis.conf


redis 默认的几个比较重要的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize no
# Accept connections on the specified port, default is 6379.
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""

redis-cli 连接redis-server两种方式:

1
2
3
4
# 交互方式
redis-cli -h 127.0.0.1 -p 6379
# 命令方式
redis-cli -h 127.0.0.1 -p 6379 get hello

停止 redis:

1
2
3
/etc/init.d/redis-server restart
/etc/init.d/redis-server stop
/etc/init.d/redis-server start

Mac OS (未测试):

1
redis-cli shutdown

除了可以通过 shutdown 命令关闭 redis 服务之外,还可以通过 kill pid 的方式关闭 redis,但是不要粗暴地使用 kill -9 强制杀死 redis 服务,不但不会做持久化操作,还会造成缓冲区等资源不能被优雅关闭,极端情况会造成 AOF 和复制丢失数据的情况。

(4) Redis 命令

全局命令:

1
2
3
4
5
# 输出所有键
keys *

# 键的总数
dbsize

dbsize 命令在计算键总数时不会遍历所有键,而是直接获取 Redis 内置的键总数变量,所以 dbsize 命令的时间复杂度是 O(1)。而 keys 命令会遍历所有键,所以它的时间复杂度是 O(n),当 Redis 保存了大量键时,线上环境禁止使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 检查键是否存在
exists key

# 删除键
del key [key ...]

# 键过期,超过指定时间后,会自动删除键
expire key seconds

# 返回键的剩余过期时间: -1: 键没有过期时间; -2: 键不存在
ttl key

# 键的数据结构类型,注意是键的,不是值的
type key

string, hash, list, set, zset 只是 Redis 对外的数据结构,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样 Redis 会在合适的场景选择合适的内部编码,可以通过 object encoding 命令查询内部编码:

1
2
3
4
127.0.0.1:6379> object encoding msg
"embstr"
127.0.0.1:6379> object encoding mlist
"ziplist"

(5) Redis 的单线程架构

Redis 是使用了 单线程架构I/O多路复用模型 来实现高性能的内存数据库服务。Redis 是单线程来处理命令的,所以命令到达 Redis 后并不会立即执行,而是进入队列之后逐个执行。对于差不多同时到达的命令执行的顺序是无法确定的。

为什么单线程还能如此快?

  • 1. 纯内存访问
  • 2. 非阻塞 I/O,使用 epoll 作为 I/O 多路复用技术的实现:

  • 3. 单线程避免了线程切换和竞态产生的消耗

但是单线程会有一个问题,对于每个命令的执行时间是有要求的。如果某个命令执行过长,会造成其他命令的阻塞,对于 Redis 这种高性能的服务来说是致命的。

(6) 字符串

1
2
3
4
5
6
7
8
9
10
# 设置秒级、毫秒级过期时间
# nx: 键必须不存在,才可以设置成功,用于添加
# xx: 与 nx 想法,键必须存在,才可以设置成功,用于更新
set key value [ex seconds] [px milliseconds] [nx|xx]

# 与 ex 作用一样
setex key seconds value

# 与 nx 作用一样
setnx key value

setnxsetxx 在实际使用中有什么应用场景吗?以 setnx 为例子,由于 Redis 的单线程命令处理机制,如果有多个客户端同时执行 setnx key value,根据 setnx 的特性,只有一个客户端才能设置成功,setnx 可以作为分布式锁的一种实现方案。

1
2
3
4
5
# 批量设置键值对
mset key value [key value ...]

# 批量获取值
mget key [key ...]

设计合理的键名,有利于放置键冲突和项目的可维护性,比较推荐的方式是使用:

1
业务名:对象名:id:[属性]

共享 Session :

(7) 哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 设置值 {name=Tom}
hset user:1 name Tom

# 获取值
hget user:1 name

# 删除值
hdel user:1 name

# 得到字段的个数
hlen user:1

# 批量设置
hmset user:1 name Tom age 23

# 批量获取
hmget user:1 name age

# 获取所有的字段
hkeys user:1

# 获取所有的值
hvals user:1

# 获取所有的 {key=value}
hgetall user:1

在使用 hgetall 的时候,如果哈希元素个数比较多,会存在阻塞 Redis 的可能。如果开发人员只需要获取部分字段,可以使用 hmget,如果一定要获取全部 field-value,可以使用 hscan 命令,该命令会渐进式遍历哈希类型

两种情况会触发哈希类型的内部编码发生变化:

(8) 列表

列表两端插入和弹出操作:

列表获取、删除等操作:

列表中的元素是有序可重复的。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 从右边插入元素
rpush user:1:message a b c d e

# 从左边插入元素
lpush user:1:message e d c b a

# 从左到右获取列表的所有元素
lrange user:1:message 0 -1
lrange user:1:message 0 4
lrange user:1:message -5 -1

# 向某个元素前或者后插入元素,从列表中找到等于 d 的元素,然后在前面插入 item
# a b c item d e
linsert user:1:message before d item

# 获取索引为 3 的元素
lindex user:1:message 3

# 获取列表长度
llen user:1:message

# 从列表左侧弹出元素
lpop user:1:message

# 从列表右侧弹出元素
rpop user:1:message

# 删除所有值为 a 元素
lrem user:1:message 0 a

# 从左到右,删除最多 3 个值为 a 的元素
lrem user:1:message 3 a

# 从右到做,删除最多 |-3| 个值为 a 的元素
lrem user:1:message -3 a

# 保留第 2 到第 4 个元素
ltrim user:1:message 1 3

# 修改下标为 2 处的元素值为 newValue
lset user:1:message 2 newValue

lrange 的操作会获取列表指定索引范围所有的元素。索引下标有两个特点:

  • 索引下标从做导游分别是 0 到 N-1,但是从右到左分别是 -1 到 -N
  • lrange 中的 end 选项包含了自身,这个和很多编程语言不包含 end 不太相同

阻塞操作:

1
2
blpop key [key...] timeout
brpop key [key...] timeout

如果 timeout 设置为 0,那么如果列表为空,客户端会一直阻塞下去。

brpop 为例,在使用 brpop 时,有两点需要注意:

  • 如果是多个键,那么 brpop从左至右遍历键,一旦有一个键能弹出元素,客户端会立即返回。
  • 如果多个客户端对同一个键执行 brpop,那么最先执行 brpop 命令的客户端可以获取到弹出的值,另外两个客户端继续阻塞。

TODO 此处应该有 1091 页图

(9) 集合

集合不允许有重复元素,并且集合中的元素是无序的,Redis 除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 添加多个元素
sadd user:1:follow it music sports

# 删除多个元素
srem user:1:follow it music

# 计算元素个数
scard user:1:follow

# 判断元素是否在集合中
sismember user:1:follow it

# 随机从集合中返回指定个数的元素
srandmember user:1:follow 2

# 从集合中随机弹出一个元素
spop user:1:follow

# 获取所有元素
smembers user:1:follow

smemberslrangehgetall 都属于比较重的命令,如果元素过多存在阻塞 Redis 的可能性,这时候可以使用 sscan 来完成。

1
2
3
4
5
6
7
8
9
10
11
# 求多个集合的交集
sinter user:1:follow user:2:follow

# 求多个集合的并集
sunion user:1:follow user:2:follow

# 求多个集合的差集
# user:1 中有,user:2 中没有的
sdiff user:1:follow user:2:follow
# user:2 中有,user:1 中没有的
sdiff user:2:follow user:1:follow

典型使用场景: 标签:

给用户添加标签:

1
2
sadd user:1:tags tag1 tag2 tag3
sadd user:2:tags tag2 tag4 tag5

给标签添加用户:

1
2
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3

删除用户下的标签:

1
srem user:1:tags tag1 tag3

删除标签下的用户:

1
srem tag1:users user:1

用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致。

(10) 有序集合

为每个元素设置一个 score 作为排序的依据,有序集合中的元素不能重复,但是 score 可以重复,就像一个班里学生学号不能重复,但是考试成绩可以相同一个道理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 添加成员
zadd user:ranking 251 Tom

# 计算成员个数
zcard user:ranking

# 计算某个成员的分数
zscore user:ranking Tom

# 分数从低到高排序
zrank user:ranking member

# 删除成员
zrem user:ranking Tom [member ...]

# 为 Tom 增加 19 分
zincrby user:ranking 19 Tom

# 返回指定排名范围 (后三名) 的成员 (包含分数), zrange 是从低到高排名的
zrange user:ranking 0 2 withscores
# 返回前三名
zrevrange user:ranking 0 2 withscores

# 返回 200 > 221 之间的成员
zrangebyscore user:ranking 200 221 withscores
# 返回 221 -> 200 之间的成员
zrevrangebyscore user:ranking 221 200 withscores
# 返回 >= 200 的成员
zrangebyscore user:ranking 200 +inf withscores

# 返回 200 -> 221 之间的成员个数
zcount user:ranking 200 221

# 删除成绩最低的三名成员
zremrangebyrank user:ranking 0 2

# 删除指定分数范围内的成员
zremrangebyscore user:ranking 250 +inf

有序集合比集合提供了排序字段,但是也产生了代价,zadd 的时间复杂度为 O(log(n))有序集合一般用在排行榜系统中

(11) 键管理

  • 对于字符串类型,执行 set 命令会去掉过期时间,这个问题很容易在开发中被忽视。
  • setex 命令作为 set + expire 的组合,不但是原子执行,同时减少了一次网络通讯的时间

迁移键:

(1) move

1
2
# Redis 内部多个数据库之间互相迁移,不建议使用
move key db

(2) dump + restore

在源 Redis 上执行 dump:

1
2
3
4
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> dump hello
"\x00\x05world\x06\x00\x8f<T\x04%\xfcNQ"

在目标 Redis 上执行 restore:

1
2
3
4
5
6
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> restore hello 0 "\x00\x05world\x06\x00\x8f<T\x04%\xfcNQ"
OK
127.0.0.1:6379> get hello
"world"

(3) migrate

migrate 命令就是将 dump, restore, del 这三个命令进行组合,从而简化了操作流程。migrate 命令具有原子性,而且从 Redis 3.0.6 以后已经支持迁移多个键的功能。

(12) 输入输出缓冲

Redis 为每个客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时 Redis 会从输入缓冲区拉取命令并执行,输入缓冲区为客户端发送命令到 Redis 执行命令提供了缓冲功能:

输入缓冲区使用不当会产生两个问题:

  • 每个客户端的输入缓冲区的大小都不能超过 1G,超过后客户端将会被关闭
  • 输入缓冲区不受 maxmemory 控制

同样,Redis 为每个客户端分配了输出缓冲区,它的作用是保存命令执行的结果返回给客户端,为 Redis 和客户端交互返回结果提供缓冲:

输出缓冲区做的很细致,按照客户端的不同分为三种: 普通客户端、发布订阅客户端、slave 客户端:

对应的配置规则为:

1
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
  • <class>: 客户端类型: normal, slave, pubsub
  • <hard limit>: 如果客户端使用的输出缓冲区大于 <hard limit>,那么客户端会立即关闭
  • <sort limit>, <soft seconds>: 如果客户端使用的输出缓冲区超过了 <soft limit> 并且持续了 <sort seconds> 秒,客户端会被立即关闭。

实际上输出缓冲区由两部分组成: 固定缓冲区 (16KB) 和动态缓冲区,其中固定缓冲区返回比较小的执行结果,而动态缓冲区返回比较大的结果。固定缓冲区使用的是字节数组,动态缓冲区使用的是列表。当固定缓冲区存满后会将 Redis 新的返回结果存放在动态缓冲区的队列中:

输出缓冲区同样也不会收到 maxmemory 的限制,如果使用不当同样会造成 maxmemory 用满产生的数据丢失、键值淘汰、OOM 等情况。

(13) 如何识别 Redis 是否有内存交换

1
2
3
4
# 查询进程号
redis-cli -p 6379 info server | grep process_id
# 查询内存交换信息
sudo cat /proc/1166/smaps | grep Swap

如果交换量都是 0KB 或者个别的是 4KB,则是正常现象,说明 Redis 进程内存没有被交换。预防内存交换主要就是:

  • 增加内存
  • 确保所有 Redis 实例设置了 maxmemory

(14) Redis 可视化

(15) 内存占用

maxmemory 限制的是 Redis 实际使用的内存量,也就是 used_memory 统计项对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比 maxmemory 设置的更大,实际使用时要小心这部分内存溢出。

Redis 存储的所有值对象在内部定义为 redisObject 结构体:

降低 Redis 内存使用最直接的方式就是缩减键 (key) 和值 (value) 的长度。

  • key: user:{uid}:friends:notiry:{fid} 可简化为 u:{uid}:fs:nt:{fid}
  • value: 业务对象序列化成二进制对象放入 Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。

Redis 内部维护 [0 - 9999] 的整数对象池,用于节约内存。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存:

1
2
3
4
5
6
7
8
127.0.0.1:6379> set foo 100
OK
127.0.0.1:6379> object refcount foo
(integer) 2
127.0.0.1:6379> set bar 100
OK
127.0.0.1:6379> object refcount bar
(integer) 3

需要注意的是对象池并不是只要存储 [0 - 9999] 就可以工作。当设置 maxmemory 并启用 LRU 相关淘汰策略如: volatile-lru, allkeys-lru 时,Redis 禁止使用共享对象池。

为什么开启 maxmemoryLRU 淘汰策略后对象池无效? LRU 算法需要获取对象最后被访问时间,而每个对象最后访问时间存储在 redisObject 对象的 lru 字段中。对象存储意味着多个引用共享同一个 redisObject,这时 lru 字段也会被共享,导致无法获取每个对象的最后访问时间。因此两者冲突,使用时需要注意。

(16) 遍历键

  • 1. 全量遍历:
1
2
3
4
5
6
# 匹配任意字符
keys *
# . 代表匹配一个字符
keys a.c
# 匹配 jedis, redis
keys [j,r]edis
  • 2. 渐进式遍历:

Redis 存储键值对实际使用的是 hashtable 的数据结构,其简化模型如下:

每次执行 scan,可以想象成只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕,scan 用法如下:

1
2
3
# 每次 scan 完,返回当前游标的值,直到重新变为 0
# count number: 每次要遍历的键的个数,默认为 10
scan cursor [match pattern] [count number]
1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> set b 2
OK
127.0.0.1:6379> set c 3
...
127.0.0.1:6379> set x 24
OK
127.0.0.1:6379> set y 25
OK
127.0.0.1:6379> set z 26
OK

第一次扫描:

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> scan 0
1) "17"
2) 1) "a"
2) "e"
3) "o"
4) "f"
5) "k"
6) "q"
7) "g"
8) "s"
9) "l"
10) "y"

第二次扫描:

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> scan 17
1) "19"
2) 1) "z"
2) "i"
3) "d"
4) "u"
5) "r"
6) "w"
7) "p"
8) "x"
9) "h"
10) "b"

第三次扫描:

1
2
3
4
5
6
7
8
127.0.0.1:6379> scan 19
1) "0"
2) 1) "m"
2) "v"
3) "j"
4) "n"
5) "t"
6) "c"

cursor 返回 0,说明所有键已经被遍历过了。

除了 scan 以外,Redis 提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如 hgetallsmemberszrange 可能产生的阻塞问题,对应的命令分别是 hscansscanzscan,它们的用法和 scan 基本类似。

(17) redis-benchmark

  • -c: 代表客户端的并发数量,默认 50
  • -n: 客户端请求总量,默认 100000
1
2
# 100 个客户端同时请求 Redis,一共执行 20000 次
redis-benchmark -c 100 -n 20000

在一个空的 Redis 上执行了 redis-benchmark 后会发现只有 3 个键:

1
2
3
4
127.0.0.1:6379> keys *
1) "key:__rand_int__"
2) "mylist"
3) "counter:__rand_int__"

如果想向 Redis 插入更多的键,可以执行使用 -r (random) 选项,向 Redis 插入更多随机键。

(18) Pipeline

可以使用 Pipeline 模拟出批量操作的效果,但是在使用时要注意它与原生批量命令的区别:

  • 原生批量命令是原子的,Pipeline 是非原子的
  • 原生批量命令是一个命令对应多个 keyPipeline 支持多个命令。
  • 原生批量命令是 Redis 服务端支持实现的,而 Pipeline 需要服务端和客户端的共同实现。

Pipeline 虽然好用,但是每次 Pipeline 组装的命令个数不能没有节制,否则一次组装 Pipeline 数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的 Pipeline 拆分成多次较小的 Pipeline 来完成。


为了保证多条命令组合的原子性,Redis 提供了简单的事务功能以及集成 Lua 脚本来解决这个问题。Redis 提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算,当然也体现了 Redis 的 “keep it simple” 的特性。

(19) RDB 持久化

RDB 持久化是把当前进程数据生成快照保存到硬盘的过程,触发 RDB 持久化过程分为手动触发和自动触发

  • bgsave 手动触发: Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。运行 bgsave 命令对应的 Redis 日志如下:

查看最近一次 fork 操作的耗时 (723 微秒):

1
2
zk@zk-pc:~$ redis-cli info stats | grep latest_fork_usec
latest_fork_usec:723

子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换,执行 lastsave 命令可以获取最后一次生成 RDB 的时间:

1
2
127.0.0.1:6379> lastsave
(integer) 1502870682
  • 自动触发:
    • 使用 save 相关配置,如 save m n。表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave
    • 如果从节点执行全量复制操作,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点。
    • 执行 debug reload 命令重新加载 Redis 时,也会自动触发 save 操作。
    • 默认情况下执行 shutdown 命令时,如果没有开启 AOF 持久化功能则自动执行 bgsave

1) RDB 的优点:

  • RDB 是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每 6 小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
  • Redis 加载 RDB 恢复数据远远快于 AOF 的方式。

2) RDB 的缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为 bgsave 每次运行都要执行 fork 操作创建子进程,属于重量级操作,频繁执行成本过高。
  • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题

Redis 的 RDB 文件保存的位置,如果使用 Redis 默认自带的配置文件 redis.conf 来启动 redis-server 的话,那么其工作目录如下所示:

1
dir ./

其代表的含义是,在哪个目录运行的 redis-server 启动的 redis 服务器,那么 dir 目录就会指向哪个文件,相应的 dump.rdb 文件也就会保存在这个目录下:

dump.rdb 文件也在这个目录下:

另外使用命令 redis-cli config get dir 也能得到工作目录:

1
2
1) "dir"
2) "/home/zk/Documents/bupt_518/appsec_search_redis/search-redis"

(20) AOF 持久化

(21) 客户端管理

1
2
# 列出能与 Redis 服务端相连的所有客户端连接信息
client list

ageidle 字段分别代表当前客户端已经连接的时间和最近一次的空闲时间:

某些情况下,可能存在大量 idle 连接,无论是从网络连接的成本还是超过 maxclients 的后果来说都不是什么好事,因此 Redis 提供了 timeout (单位为秒) 参数来限制连接的最大空闲时间,一旦客户端连接的 idle 时间超过了 timeout,连接将会被关闭

1
2
127.0.0.1:6379> config set timeout 300
OK

实际开发中,应该在客户端使用上添加空闲检测和验证等措施,例如 JedisPool 使用 common-pool 提供的三个属性:

  • minEvictableIdleTimeMillis
  • testWhileIdle
  • timeBetweenEvictionRunsMills

client list 中的 flags 用于标识当前客户端的类型:


杀掉指定 IP 地址和端口的客户端:

1
client kill ip:port

(22) 卸载 Redis

(23) 阿里云 Redis

理解内存


3) 内存回收策略 - 删除过期键对象:

Redis 采用 惰性删除定时任务删除机制实现过期键的内存回收。

  • 惰性删除:惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空,这种策略是出于节省CPU 成本考虑,不需要单独维护TTL 链表来处理过期键的删除。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。正因为如此,Redis 还提供另一种定时任务删除机制作为惰性删除的补充。
  • 定时任务删除:Redis 内部维护一个定时任务,默认每秒运行 10 次(通过配置hz 控制)。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键。

快慢两种模式内部删除逻辑相同,只是执行的超时时间不同

3) 内存回收策略 - 内存溢出控制策略:

当 Redis 所用内存达到 maxmemory 上限时会触发相应的溢出控制策略。 具体策略受 maxmemory-policy 参数控制, Redis 支持 6 种策略,如下所示:

  • noeviction默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息 (error)OOM command not allowed when used memory,此时Redis 只响应读操作。
  • volatile-lru:根据 LRU 算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到 noeviction 策略。
  • allkeys-lru:根据 LRU 算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
  • allkeys-random随机删除所有键,直到腾出足够空间为止。
  • volatile-random随机删除过期键,直到腾出足够空间为止。
  • volatile-ttl:根据键值对象的 ttl 属性,删除最近将要过期数据。如果没有,回退到 noeviction 策略。

内存溢出控制策略可以采用 config set maxmemory-policy {policy} 动态配置。 Redis 支持丰富的内存溢出应对策略,可以根据实际需求灵活定制,比如当设置 volatile-lru 策略时,保证具有过期属性的键可以根据 LRU 剔除,而未设置超时的键可以永久保留。还可以采用 allkeys-lru 策略把 Redis 变为纯缓存服务器使用。当Redis 因为内存溢出删除键时,可以通过执行 info stats 命令查看 evicted_keys 指标找出当前 Redis 服务器已剔除的键数量。每次Redis 执行命令时如果设置了 maxmemory 参数,都会尝试执行回收内存操作。当 Redis 一直工作在内存溢出 (used_ memory> maxmemory) 的状态下且设置非 noeviction 策略时,会频繁地触发回收内存的操作,影响Redis 服务器的性能。

查看当前运行的 Redis 的配置文件

1
2
3
4
5
127.0.0.1:6379> INFO server
# Server
redis_version:3.0.6
...
config_file:/etc/redis/redis.conf

Redis 监听在多个网卡

1
2
3
# 文件: redis.conf
# 默认监听在多个网卡
# bind 127.0.0.1

Redis 暂停、重启、开始

1
/ets/init.d/redis-server [restart|stop|start]

命令行发送给 Redis 命令

1
2
# Add a new core to the list of cores
echo LPUSH cores 1 | redis-cli

推荐文章