1、Redis提供了两种持久化方式:RDB和AOF,即可以用这两种策略将内存的数据保存到硬盘中。
2、复制功能是分布式Redis的基础。
3、一般推荐使用的安装方式:源码的方式进行安装。下面以3.0.7版本为例(只需6步):
$ wget http://download.redis.io/releases/redis-3.0.7.tar.gz
$ tar xzf redis-3.0.7.tar.gz
$ ln -s redis-3.0.7 redis // 建立一个redis目录的软连接,指向redis-3.0.7。
$ cd redis
$ make // 编译(编译前确保操作系统已经安装gcc)
$ make install
$ redis-cli -v // 查看Redis的版本
4、Redis可执行文件说明
可执行文件 | 作用 |
---|---|
redis-server | 启动Redis |
redis-cli | Redis命令行客户端 |
redis-benchmark | Redis基准测试工具 |
redis-check-aof | Redis AOF持久化文件检测和修复工具 |
redis-check-dump | Redis RDB持久化文件检测和修复工具 |
redis-sentinel | 启动Redis Sentinel |
5、启动Redis
有三种方法启动Redis:默认配置、运行配置、配置文件启动。
$ redis-server
# redis-server --configKey1 configValue1 --configKey2 configValue2
# redis-server --port 6380
3)配置文件启动
# redis-server /opt/redis/redis.conf
6、Redis的基础配置(Redis有60多个配置)
配置名 | 配置说明 |
---|---|
port | 端口 |
logfile | 日志文件 |
dir | Redis工作目录(存放持久化文件和日志文件) |
daemonize | 是否以守护进程的方式启动Redis |
7、Redis命令行客户端
1)第一种是交互式方式:通过redis-cli -h {host} -p {port}的方式连接到Redis服务,之后所有的操作都是通过交互的方式实现,不需要再执行redis-cli了。
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
2)第二种是命令方式:通过redis-cli -h {host} -p {port} {command}就可以直接得到命令的返回结果。
redis-cli -h 127.0.0.1 -p 6379 get hello
"world"
8、 停止Redis服务
Redis提供了shutdown命令来停止Redis服务。
$ redis-cli shutdown // 停掉127.0.0.1上6379端口上的Redis服务
$ redis-cli shutdown nosave|save
1、全局命令
1)查看所有键
key *
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> set java jredis
OK
127.0.0.1:6379> set python redis-py
OK
127.0.0.1:6379> keys *
1) "python"
2) "java"
3) "hello"
2)键总数
dbsize
127.0.0.1:6379> rpush mylist a b c d e f g // 插入一个列表类型的键值对(值是多个元素组成)
(integer) 7
127.0.0.1:6379> dbsize
(integer) 4 // hello、java、python、mylist
3)检查键是否存在
exists key
127.0.0.1:6379> exists java
(integer) 1
127.0.0.1:6379> exists not_exist_key
(integer) 0
4)删除键
del key [key ...]
127.0.0.1:6379> del java
(integer) 1
127.0.0.1:6379> exists java
(integer) 0
127.0.0.1:6379> del mylist
(integer) 1
127.0.0.1:6379> exists mylist
(integer) 0
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
OK
127.0.0.1:6379> del a b c
(integer) 3
5)键过期
expire key seconds
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> expire hello 10 // 为键hello设置了10秒过期时间
(integer) 1
127.0.0.1:6379> ttl hello
(integer) 7 // 剩余7秒
127.0.0.1:6379> ttl hello
(integer) -2 // 说明键hello已经被删除
127.0.0.1:6379> get hello
(nil)
2、键的数据结构类型
type key
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> type a
string
127.0.0.1:6379> rpush mylist a b c d e f g
(integer) 7
127.0.0.1:6379> type mylist
list
127.0.0.1:6379> type not_exsit_key
none
3、数据结构和内部编码
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"ziplist"
4、单线程架构
5、字符串
1)字符串常用命令
①设置值:
set key value [ex seconds] [px milliseconds] [nx|xx]
setex key seconds value
setnx key value
127.0.0.1:6379> exists hello
(integer) 0
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> setnx hello redis
(integer) 0 // setnx失败,返回结果为0。
127.0.0.1:6379> set hello jredis xx
OK // set xx成功,返回结果为OK。
②获取值:
get key
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> get not_exist_key
(nil) // 如果要获取的键不存在,则返回nil(空)。
③批量设置值:
mset key value [key value ...]
127.0.0.1:6379> mset a 1 b 2 c 3 d 4 // 通过mset命令一次性设置4个键值对
OK
④批量获取值:
mget key [key ...]
127.0.0.1:6379> mget a b c d // 批量获取键a、b、c、d的值
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> mget a b c f
1) "1"
2) "2"
3) "3"
4) (nil)
⑤计数:
incr key
decr key
incrby key increment
decrby key decrement
incrbyfloat key increment
127.0.0.1:6379> exists key
(integer) 0
127.0.0.1:6379> incr key
(integer) 1
127.0.0.1:6379> incr key
(integer) 2
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> incr hello
(error) ERR value is not an integer or out of range
2)字符串不常用命令
①追加值:
append key value
127.0.0.1:6379> get key
"redis"
127.0.0.1:6379> append key world
(integer) 10
127.0.0.1:6379> get key
"redisworld"
②字符串长度:
strlen key
127.0.0.1:6379> get key
"redisworld"
127.0.0.1:6379> strlen key
(integer) 10
127.0.0.1:6379> set hello "世界"
OK
127.0.0.1:6379> strlen hello
(integer) 6 // 每个中文占用3个字节。
③设置并返回原值:
getset key value
127.0.0.1:6379> getset hello world
(nil)
127.0.0.1:6379> getset hello redis
"world"
④设置指定位置的字符
setrange key offeset value
127.0.0.1:6379> set redis pest
OK
127.0.0.1:6379> setrange redis 0 b
(integer) 4
127.0.0.1:6379> get redis
"best"
⑤获取部分字符串
getrange key start end // start和end分别是开始和结束的偏移量,偏移量从0开始计算。
127.0.0.1:6379> getrange redis 0 1
"be"
6、字符串内部编码
1)字符串类型的内部编码有3种:
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
127.0.0.1:6379> set key 8653
OK
127.0.0.1:6379> object encoding key
"int"
127.0.0.1:6379> set key "hello,world"
OK
127.0.0.1:6379> object encoding key
"embstr"
127.0.0.1:6379> set key "one string greater than 39 byte........."
OK
127.0.0.1:6379> object encoding key
"raw"
127.0.0.1:6379> strlen key
(integer) 40
7、字符串典型使用场景
1)缓存功能
UserInfo getUserInfo(long id)
{
userRedisKey = "user:info" + id
value = redis.get(userRedisKey);
UserInfo userInfo;
if(value != null)
{
userInfo = deserialize(value);
}
else
{
userInfo = mysql.get(id); // 从MySQL获取用户信息
if(userInfo != null)
redis.setex(userRedisKey, 3600, serialize(userinfo)); // 将userInfo序列化,并存入Redis
}
return userInfo;
}
与MySQL等关系型数据库不同的是,Redis没有命令空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用“业务名:对象名[属性]”作为键名(也可以不是分号)。
2)计数
long incrVideoCounter(long id)
{
key = "video:playCount:" + id;
return redis.incr(key);
}
3)共享Session
4)限速
phoneNum = "138xxxxxxx";
key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
isExists = redis.set(key, 1, "EX 60", "NX");
if(isExists != null || redis.incr(key) <= 5)
// 通过
else
// 限速
8、哈希命令
在Redis中,哈希类型是指键值本身又是一个键值对结构,形如value={{field1, value1},…{fieldN, valueN}}。
①设置值:
hset key field value
127.0.0.1:6379> hset user:1 name tom // 为user:1添加一对field-value
(integer) 1
②获取值:
hget key field
127.0.0.1:6379> hget user:1 name // 获取user:1的name域(属性)对应的值
"tom"
127.0.0.1:6379> hget user:2 name // 如果键或filed不存在,会返回nil。
(nil)
127.0.0.1:6379> hget user:1 age
(nil)
③删除field:
hdel key field [field ...]
127.0.0.1:6379> hdel user:1 name
(integer) 1
127.0.0.1:6379> hdel user:1 age
(integer) 0
④计算field个数:
hlen key
127.0.0.1:6379> hset user:1 name tom
(integer) 1
127.0.0.1:6379> hset user:1 age 23
(integer) 1
127.0.0.1:6379> hset user:1 city tianjin
(integer) 1
127.0.0.1:6379> hlen user:1
(integer) 3
⑤批量设置或获取field-value:
hmget key field [field ...]
hmset key field value [field value ...]
127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin
OK
127.0.0.1:6379> hmget user:1 name city
1) "mike"
2) "tianjin"
⑥判断field是否存在:
hexists key field
127.0.0.1:6379> hexists user:1 name
(integer) 1
⑦获取所有field:
hkeys key
127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "city"
⑧获取所有value:
hvals key
127.0.0.1:6379> hvals user:1
1) "mike"
2) "12"
3) "tianjin"
⑨获取所有的field-value:
hgetall key
127.0.0.1:6379> hgetall user:1
1) "name"
2) "mike"
3) "age"
4) "12"
5) "city"
6) "tianjin"
⑩hincrby hincrbyfloat
hincrby key field
hincrbyfloat key field
⑪计算value的字符串长度(需要Redis3.2以上)
hstrlen key field
127.0.0.1:6379> hstrlen user:1 name
(integer) 3 // hget user:1 name的value是tom,所有hstrlen的返回结果是3.
9、哈希内部编码
哈希类型的内部编码有两种:
1、当field个数比较少且没有大的value时,内部编码为ziplist:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
2、当有value大于64字节,内部编码会由ziplist变为hashtable:
127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 byte ...忽略..."
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
3、当field个数超过512,内部编码也会由ziplist变为hashtable:
127.0.0.1:6379> hset hashkey f1 v1 f2 v2 f3 v3 ...忽略... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"
10、哈希使用场景
1)例如用户信息表。相对于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操作上会更加便捷。可以将每个用户的id定义为键后缀,多对field-value对应每个用户的属性。
UserInfo getUserInfo(long id)
{
// 用户id作为key后缀
userRedisKey = "user:info:" + id;
// 使用hgetall获取所有用户信息映射关系
userInfoMap = redis.hgetAll(userRedisKey);
UserInfo userInfo;
if(userInfoMap != null)
{
// 将映射关系转换为UserInfo
userInfo = transferMapToUserInfo(userInfoMap);
}
else
{
// 从MySQL中获取用户信息
userInfo = mysql.get(id);
// 将userInfo变为映射关系使用hmset保存到Redis中
redis.hmset(userRedisKey, transferUserInfoToMap(userInfo));
// 添加过期时间
redis.expire(userRedisKey, 3600);
}
return userInfo;
}
但是需要注意的是哈希类型和关系型数据库有两点不同之处:
11、到目前为止,我们已经能够使用三种方法缓存用户信息,下面给出三种方案的实现方法和优缺点分析:
1)原生字符串类型:每个属性一个键。
set user:1:name tom
set user:1:age 23
set user:1:city beijing
2)序列化字符串类型:将用户信息序列化后用一个键保存。
set user:1 serialize(userInfo)
3)哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。
hmset user:1 name tom age 23 city beijing
12、列表概述
列表类型有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。第二、列表中的元素可以是重复的。
13、列表命令
操作类型 | 操作 |
---|---|
添加 | rpush lpush linsert |
查 | lrange lindex llen |
删除 | lpop rpop lrem ltrim |
修改 | lset |
阻塞操作 | blpop brpop |
1)添加操作
①从右边插入元素:
rpush key value [value . . .]
127.0.0.1:6379> rpush listkey c b a // 从右向左插入元素c、b、a
(integer) 3
127.0.0.1:6379> lrange listkey 0 -1 // 从左到右获取列表的所有元素
1) "c"
2) "b"
3) "a"
②从左边插入元素:
rpush key value [value . . .]
③向某个元素前或者后插入元素:
linsert key before|after pivot value
127.0.0.1:6379> linsert listkey before b java // 在列表的元素b前插入java。
(integer) 4 // 返回结果4,代表当前列表的长度。
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "java"
3) "b"
4) "a"
2)查找
①获取指定范围内的元素列表:
lrange key start end
127.0.0.1:6379> lrange listkey 1 3 // 获取列表的第2到第4个元素。
1) "java"
2) "b"
3) "a"
②获取列表指定索引下标的元素:
lindex key index
127.0.0.1:6379> lindex listkey -1 // 当前列表的最后一个元素为a
"a"
③获取列表长度:
llen key
127.0.0.1:6379> llen listkey
(integer) 4
3)删除
①从列表左侧弹出元素:
lpop key
127.0.0.1:6379> lpop listkey
"c"
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "a"
②从列表右侧弹出:
rpop key
③删除指定元素:
lrem key count value
④按照索引范围修剪列表:
ltrim key start end
127.0.0.1 :6379> lrange listkey 0 -1
1) "a"
2) "java"
3) "b"
4) "a"
127.0.0.1 :6379> ltrim listkey 1 3 // 只保留列表listkey的第2个到第4个元素。
OK
127.0.0.1 :6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "a"
4)修改
①修改指定索引下标的元素:
lset key index newValue
127.0.0.1:6379> lset listkey 2 python // 将列表listkey中的第3个元素设置为python
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "python"
5)阻塞操作
blpop key [key . . .] timeout
brpop key [key . . .] timeout
14、列表的内部编码
列表类型的内部编码有两种。
1、当元素个数较少且没有大元素时,内部编码为ziplist:
127.0.0.1:6379> rpush listkey e1 e2 e3
(integer) 3
127.0.0.1:6379> object encoding listkey
"ziplist"
2、当元素个数超过512个,内部编码变为linkedlist:
127.0.0.1:6379> rpush listkey e4 e5 . . .忽略 . . . e512 e513
(integer) 513
127.0.0.1:6379> object encoding listkey
"linkedlist"
3、当某个元素超过64字节,内部编码也会变为linkedlist:
127.0.0.1:6379> rpush listkey "one string is bigger than 64 byte . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . "
(integer) 4
127.0.0.1:6379> object encoding listkey
"linkedlist"
15、使用场景
1)消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
2)文章列表
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
使用列表类型保存和获取文章列表会存在两个问题。
3)实际上列表的使用场景很多,在选择时可以参考以下口诀:
16、集合概述
17、集合命令
1)集合内操作
①添加元素:
sadd key element [element . . .]
127.0.0.1:6379> exists myset
(integer) 0
127.0.0.1:6379> sadd myset a b c
(integer) 3
127.0.0.1:6379> sadd myset a b
(integer) 0
②删除元素:
srem key element [element . . .]
127.0.0.1:6379> srem myset a b
(integer) 2
127.0.0.1:6379> srem myset hello
(integer) 0
③计算元素个数:
scard key
127.0.0.1:6379> scard myset
(integer) 1
④判断元素是否在集合中:
sismember key element
127.0.0.1:6379> sismember myset c
(integer) 1
⑤随机从集合返回指定个数元素:
srandmember key [count]
127.0.0.1:6379> srandmember myset 2
1) "a"
2) "c"
127.0.0.1:6379> srandmember myset
"d"
⑥从集合随机弹出元素:
spop key
127.0.0.1:6379> spop myset
"c"
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
⑦获取所有元素
smembers key
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
2)集合间操作
①求多个集合的交集
sinter key [key . . .]
127.0.0.1:6379> sinter user:1:follow user:2:follow
1) "sports"
2) "it"
②求多个集合的并集
suinon key [key . . .]
127.0.0.1:6379> sunion user:1:follow user:2:follow
1) "sports"
2) "it"
3) "his"
4) "news"
5) "music"
6) "ent"
③求多个集合的差集
sdiff key [key . . .]
127.0.0.1:6379> sdiff user:1:follow user:2:follow
1) "music"
2) "his"
④将交集、并集、差集的结果保存
sinterstore destination key [key . . .]
suionstore destination key [key . . .]
sdiffstore destination key [key . . .]
127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow
(integer) 2
127.0.0.1:6379> type user:1_2:inter
set
127.0.0.1:6379> smembers user:1_2:inter
1) "it"
2) "sports"
18、集合的内部编码
集合类型的内部编码有两种:
1、当元素个数较少且都为整数时,内部编码为intset:
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"
2、当元素个数超过512个,内部编码变为hashtable:
127.0.0.1:6379> sadd setkey 1 2 3 4 5 6 ... 512 513
(integer) 509
127.0.0.1:6379> scard setkey
(integer) 513
127.0.0.1:6379> object encoding
"hashtable"
3、当某个元素不为整数时,内部编码也会变为hashtable:
127.0.0.1 :6379> sadd setkey a
(integer) 1
127.0.0.1 :6379> object encoding setkey
"hashtable"
19、集合的使用场景
1)集合类型比较典型的使用场景是标签(tag)。例如给用户打上喜好的标签(音乐、体育等等)。
下面使用集合类型实现标签功能的若干功能。
1、给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
. . .
sadd user:k:tags tag1 tag2 tag4
. . .
2、给标签添加用户
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:2
3、删除用户下的标签
srem user:1:tags tag1 tag5
. . .
4、删除标签下的用户
srem tag1:users user:1
srem tag5:users user:1
. . .
5、计算用户共同感兴趣的标签(可以使用sinter命令,来计算用户共同感兴趣的标签)
sinter user:1:tags user:2:tags
2)集合类型的应用场景通常为以下几种:
20、有序集合概述
有序集合保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
注意:有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。
下表为列表、集合、有序集合三者的异同点:
数据结构 | 是否允许重复元素 | 是否有序 | 有序实现方式 | 应用场景 |
---|---|---|---|---|
列表 | 是 | 是 | 索引下标 | 时间轴、消息队列等 |
集合 | 否 | 否 | 无 | 标签、社交等 |
有序集合 | 否 | 是 | 分值 | 排行榜系统、社交等 |
21、有序结合命令
1)集合内
①添加成员
zadd key score member [score member . . .]
127.0.0.1:6379> zadd user:ranking 251 tom
(integer) 1
127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin (integer) 5
②计算成员个数
zcard key
127.0.0.1:6379> zcard user:ranking
(integer) 5
③计算某个成员的分数
zscore key member
127.0.0.1:6379> zscore user:ranking tom
"251"
127.0.0.1:6379> zscore user:ranking test
(nil)
④计算成员的排名
zrank key member
zrevrank key member
127.0.0.1:6379> zrank user:ranking tom
(integer) 5
127.0.0.1:6379> zrevrank user:ranking tom
(integer) 0
⑤删除成员
zrem key member [member . . .]
127.0.0.1:6379> zrem user:ranking mike
(integer) 1
⑥增加成员的分数
zincrby key increment member
127.0.0.1:6379> zincrby user:ranking 9 tom
"260"
⑦返回指定排名范围的成员
zrange key start end [withscores]
zrevrange key start end [withscores]
127.0.0.1:6379> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
127.0.0.1:6379> zrevrange user:ranking 0 2 withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"
⑧返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
127.0.0.1:6379> zrangebyscore user:ranking 200 221 withscores
1) "frank"
2) "200"
3) "tim"
4) "220"
127.0.0.1:6379> zrevrangebyscore user:ranking 221 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"
127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"
6) "260"
⑨返回指定分数范围成员个数
zcount key min max
127.0.0.1:6379> zcount user:ranking 200 221 // 返回200到221分的成员的个数
(integer) 2
⑩删除指定排名内的升序元素
zremrangebyrank key start end
127.0.0.1:6379> zremrangebyrank user:ranking 0 2
(integer) 3
⑪删除指定分数范围的成员
zremrangebyscore key min max
127.0.0.1:6379> zremrangebyscore user:ranking (250 +inf // 将250分以上的成员全部删除
(integer) 2
2)集合外
①交集
zinterstore destination numkeys key [key . . .] [weights weight [weight . . .]] [aggregate sum|min|max]
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
127.0.0.1 :6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max // user:ranking:2的权重变为0.5,并且聚合效果使用max
(integer) 3
127.0.0.1 :6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"
②并集
zunionstore destination numkeys key [key ...] [weights weight [weight ...] [aggregate sum|min|max]
127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking :2
(integer) 7
127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
1) "kris"
2) "1"
3) "james"
4) "8"
5) "mike"
6) "168"
7) "frank"
8) "200"
9) "tim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139"
22、有序集合内部编码
有序集合类型的内部编码有两种:
1、当元素个数较少且每个元素较小时,内部编码为skiplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
2、当元素个数超过128个,内部编码变为ziplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 12 e4 ...忽略... 84 e129
(integer) 129
127.0.0.1:6379> object encoding
"skiplist"
3、当某个元素大于64字节时,内部编码也会变为hashtable:
127.0.0.1:6379> zadd zsetkey 20 "one string is bigger than 64 byte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
(integer) 1
127.0.0.1:6379> object encoding
"skiplist"
23、有序集合使用场景
有序集合比较典型的使用场景就是排行榜系统。
1、添加用户赞数
zadd user:ranking:2016_03_15 mike 3
2、取消用户赞数
zrem user:ranking:2016_03_15 mike
3、展示获取赞数最多的十个用户
zrevrangebyrank user:ranking:2016_03_15 0 9
4、展示用户信息以及用户分数
hgetall user:info:tom
zscore user:ranking:2016_03_15 mike // 用户分数
zrank user:ranking:2016_03_15 mike // 用户排名
24、单个键管理
1、键重命名
rename key newkey
127.0.0.1:6379> get python
"jedis"
127.0.0.1:6379> set python jedis
OK
127.0.0.1:6379> rename python java // 如果在rename之前,键java 已经存在,那么它的值也将被覆盖
OK
127.0.0.1:6379> get python
(nil)
127.0.0.1:6379> get java
"jedis"
127.0.0.1:6379> set java jedis
OK
127.0.0.1:6379> set python redis-py
OK
127.0.0.1:6379> renamenx java python
(integer) 0 // 返回结果是0代表没有完成重命名
127.0.0.1:6379> get java
"jedis"
127.0.0.1:6379> get python
"redis-py"
2、随机返回一个键
randomkey
127.0.0.1:6379> dbsize
1000
127.0.0.1:6379> randomkey
"hello"
127.0.0.1:6379> randomkey
"jedis"
3、键过期
127.0.0.1:6379> expireat hello 1469980800 // 将键hello在2016-08-01 00:00:00(秒级时间戳为1469980800)过期
(integer) 1
127.0.0.1:6379> expire not_exist_key 30
(integer) 0
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> expire hello -2
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> hset key f1 v1
(integer) 1
127.0.0.1:6379> expire key 50
(integer) 1
127.0.0.1:6379> ttl key
(integer) 46
127.0.0.1:6379> persist key
(integer) 1
127.0.0.1:6379> ttl key
(integer) -1
127.0.0.1:6379> expire hello 50
(integer) 1
127.0.0.1:6379> ttl hello
(integer) 46
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> ttl hello
(integer) -1
4、迁移键
①move key db
②dump key
restore key ttl value
第一步:在源Redis上执行dump:
redis-source> set hello world
OK
redis-source> dump hello "\x00\x05world\x06\x00\x8f get hello
(nil)
redis-target> restore hello 0 "\x00\x05world\x06\x00\x8f get hello
"world"
上面2步对应的伪代码如下:
Redis sourceRedis = new Redis ("sourceMachine", 6379);
Redis targetRedis = new Redis ("targetMachine", 6379);
targetRedis.restore("hello", 0, sourceRedis.dump(key));
③migrate
migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key ...]]
127.0.0.1:6379> migrate 127.0.0.1 6379 hello 0 1000 replace
OK
127.0.0.1:6379> migrate 127.0.0.1 6380 "" 0 5000 keys key1 key2 key3
OK
// 如下是Redis源码中,set命令的函数setKey,可以看到最后执行了removeExpire(db ,key)函数去掉了过期时间
void setKey (redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite (db,key) == NULL) {
dbAdd (db,key,val);
} else {
dbOverwrite (db,key,val);
}
incrRefCount (val);
// 去掉过期时间
removeExpire (db,key);
signalModifiedKey (db,key);
}
笔者建议使用migrate命令进行键值迁移。
下表为move、dump+restore、migrate三种迁移方式的异同点:
命令 | 作用域 | 原子性 | 支持多个键 |
---|---|---|---|
move | Redis实例内部 | 是 | 否 |
dump+restore | Redis实例之间 | 否 | 否 |
migrate | Redis实例之间 | 是 | 是 |
25、遍历键
Redis提供了两个命令遍历所有的键,分别是keys和scan。
1、全量遍历键
keys pattern
127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> mset hello world redis best jedis best hill high
OK
127.0.0.1:6379> keys *
1) "hill"
2) "jedis"
3) "redis"
4) "hello"
127.0.0.1:6379> keys [j,r]edis // 匹配以j,r开头,紧跟edis字符串的所有键
1) "jedis"
2) "redis"
127.0.0.1:6379> keys hll*
1) "hill"
2) "hello"
redis-cli keys video* | xargs redis-cli del // 删除所有以video字符串开头的键
2、渐进式遍历
scan cursor [match pattern] [count number]
// 第一次执行scan0,返回结果分为两个部分:第一个部分6就是下次scan需要的cursor,第二个部分是10个键:
127.0.0.1:6379> scan 0
1) "6"
2) 1) "w"
2) "i"
3) "e"
4) "x"
5) "j"
6) "q"
7) "y"
8) "u"
9) "b"
10) "o"
127.0.0.1:6379> scan 6
1) "11"
2) 1) "h"
2) "n"
3) "m"
4) "t"
5) "c"
6) "d"
7) "g"
8) "p"
9) "z"
10) "a"
127.0.0.1:6379> scan 11
1) "0"
2) 1) "s"
2) "f"
3) "r"
4) "v"
5) "k"
6) "l"
// 以sscan为例子进行说明,当前集合有两种类型的元素,例如分别以old:user和new:user开头,先需要将old:user开头的元素全部删除:
String key = "myset";
// 定义pattern
String pattern = "old:user*";
// 游标每次从0开始
String cursor = "0";
while (true) {
// 获取扫描结果
ScanResult scanResult = redis.sscan (key, cursor, pattern);
List elements = scanResult.getResult ();
if (elements != null && elements.size () > 0) {
// 批量删除
redis.srem (key, elements);
}
// 获取新的游标
cursor = scanResult.getStringCursor ();
// 如果游标为0表示遍历结束
if ("0".equals (cursor)) {
break;
}
}
26、数据库管理
Redis提供了几个面向Redis数据库的操作,它们分别是dbsize、select、flushdb/flushall命令
1、切换数据库
select dbIndex
127.0.0.1:6379> set hello world // 默认进到0号数据库
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> select 15 // 切换到15号数据库
OK
127.0.0.1:6379 [15]> get hello // 因为15号数据库和0号数据库是隔离的,所以get hello为空
(nil)
2、flushdb/flushall
127.0.0.1:6379> dbsize
(integer) 4 // 当前0号数据库有四个键值对
127.0.0.1:6379> select 1
OK
127.0.0.1:6379 [1]> dbsize
(integer) 3
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> select 1
OK
127.0.0.1:6379 [1]> dbsize
(integer) 3
127.0.0.1:6379> flushall // 在任意数据库执行flushall会将所有数据库清除.
OK
127.0.0.1:6379> select 1
OK
127.0.0.1:6379 [1]> dbsize
(integer) 0
1、慢查询分析
所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来,Redis也提供了类似的功能。
注意,慢查询只统计执行命令的时间,所以没有慢查询并不代表客户端没有超时问题(网络延时、服务端待处理命令较多等等)。
2、慢查询的两个配置参数
1)在Redis 中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set命令动态修改。
// 将slowlog-log-slower-than设置为20000微秒,slowlog-max-len设置为1000:
config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite // // 将配置持久化到本地配置文件
2)实现对慢查询日志的访问和管理的命令
1、获取慢查询日志
slowlog get [n]
127.0.0.1:6379> slowlog get
1) 1) (integer) 666
2) (integer) 1456786500
3) (integer) 11615
4) 1) "BGREWRITEAOF"
2) 1) (integer) 665
2) (integer) 1456718400
3) (integer) 12006
4) 1) "SETEX"
2) "video_info_200"
3) "300"
4) "2"
...
2、获取慢查询日志列表当前的长度
slowlog len
127.0.0.1:6379> slowlog len
(integer) 45
3、慢查询日志重置(实际是对列表做清理操作)
slowlog reset
127.0.0.1:6379> slowlog len
(integer) 45
127.0.0.1:6379> slowlog reset
OK
127.0.0.1:6379> slowlog len
(integer) 0
3、慢查询功能可以有效地帮助我们找到Redis可能存在的瓶颈,但在实际使用过程中要注意以下几点:
4、Redis提供了redis-cli、redis-server、redis-benchmark等Shell工具。
5、redis-cli详解
要了解redis-cli的全部参数,可以执行redis-cli-help命令来进行查看。
1)-r
$ redis-cli -r 3 ping // 执行三次ping命令
pong
pong
pong
2)-i
$ redis-cli -r 5 -i 1 ping // 每隔1秒执行一次ping命令,一共执行5次
PONG
PONG
PONG
PONG
PONG
$ redis-cli -r 100 -i 1 info | grep used_memory_human // 每隔1秒输出内存的使用量,一共输出 100次
used_memory_human:2.95G
used_memory_human:2.95G
. . . . . . . . . . . . . . . . . . . . . .
used_memory_human:2.94G
3)-x
$ echo "world" | redis-cli -x set hello
OK
4)-c
5)-a
6)--scan和--pattern
7)--slave
①下面开启第一个客户端,使用--slave选项,看到同步已完成:
$ redis-cli --slave
SYNC with master, discarding 72 bytes of bulk transfer . . .
SYNC done . Logging commands from master .
②再开启另一个客户端做一些更新操作:
redis-cli
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> incr count
1
127.0.0.1:6379> get hello
"world"
③第一个客户端会收到Redis节点的更新操作:(PING命令是由于主从复制产生的)
redis-cli --slave
SYNC with master, discarding 72 bytes of bulk transfer . . .
SYNC done . Logging commands from master .
"PING"
"PING"
"PING"
"PING"
"PING"
"SELECT","0"
"set","hello","world"
"set","a","b"
"PING"
"incr","count"
8)--rdb
9)--pipe
下面操作同时执行了set hello world和incr counter两条命令:
echo -en '*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n*2\r\n$4\r\nincr\r\ n$7\r\ncounter\r\n ' | redis-cli --pipe
10)--bigkeys
11)--eval
12)--latency
redis-cli -h {machineB} --latency
min: 0, max: 1, avg: 0.07 (4211 samples)
// 延时信息每15秒输出一次,可以通过-i参数控制间隔时间
redis-cli -h 10.10.xx.xx --latency-history
min: 0, max: 1, avg: 0.28 (1330 samples) -- 15.01 seconds range…
min: 0, max: 1, avg: 0.05 (1364 samples) 15.01 seconds range
13)--stat
14)--raw和--no-raw
①在Redis中设置一个中文的value:
$redis-cli set hello "你好"
OK
②如果正常执行get或者使用--no-raw选项,那么返回的结果是二进制格式;如果使用了--raw选项,将会返回中文。
$redis-cli get hello
"\xe4\xbd\xa0\xe5\xa5\xbd"
$redis-cli --no-raw get hello
"\xe4\xbd\xa0\xe5\xa5\xbd"
$redis-cli --raw get hello
你好
6、redis-server详解
redis-server除了启动Redis外,还有一个–test-memory选项。redis-server–test-memory可以用来检测当前操作系统能否稳定地分配指定容量的内存给Redis,通过这种检测可以有效避免因为内存问题造成Redis崩溃。
下面操作检测当前操作系统能否提供1G的内存给Redis:
redis-server --test-memory 1024
7、redis-benchmark详解
redis-benchmark可以为Redis做基准性能测试。
1)-c
2)-n
redis-benchmark -c 100 -n 20000代表100个客户端同时请求Redis,一共执行20000次。
redis-benchmark会对各类数据结构的命令进行测试,并给出性能指标.
====== GET ======
20000 requests completed in 0.27 seconds
100 parallel clients
3 bytes payload keep alive: 1
99.11% <= 1 milliseconds
100.00% <= 1 milliseconds
73529.41 requests per second
3)-q
$redis-benchmark -c 100 -n 20000 -q
PING_INLINE: 74349.45 requests per second
PING_BULK: 68728.52 requests per second
SET : 71174.38 requests per second…
LRANGE_500 (first 450 elements) : 11299.44 requests per second
LRANGE_600 (first 600 elements) : 9319.67 requests per second
MSET (10 keys) : 70671.38 requests per second
4)-r
在一个空的Redis上执行了redis-benchmark会发现只有3个键:
127.0.0.1:6379> dbsize
(integer) 3
127.0.0.1:6379> keys *
1) "counter:__rand_int__"
2) "mylist"
3) "key:__rand_int__"
$redis-benchmark -c 100 -n 20000 -r 10000
5)-P
6)-k
7)-t
redis-benchmark -t get,set -q
SET: 98619.32 requests per
GET: 97560.98 requests per
8)--csv
redis-benchmark -t get,set --csv
"SET","81300.81"
"GET","79051.38"
8、Pipeline概念
Redis客户端执行一条命令分为如下四个过程:
①发送命令
②命令排队
③命令执行
④返回结果
其中①+④称为Round Trip Time (RTT,往返时间)。
9、Pipeline性能测试
10、原生批量命令与Pipeline对比
Pipeline与原生批量命令的区别,具体包含以下几点:
11、Pipeline最佳实践
12、事务
为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题。
事务表示一组动作,要么全部执行,要么全部不执行。
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的。
// 用户关注的例子
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1
127.0.0.1:6379> discard
OK
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> zadd user:b:fans 1 user:a
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1
#T1:客户端1
127.0.0.1:6379> set key "java"
OK
#T2:客户端1
127.0.0.1:6379> watch key
OK
#T3:客户端1
127.0.0.1:6379> multi
OK
#T4:客户端2
127.0.0.1:6379> append key python
(integer) 11
#T5:客户端1
127.0.0.1:6379> append key jedis
QUEUED
#T6:客户端1
127.0.0.1:6379> exec
(nil)
#T7:客户端1
127.0.0.1:6379> get key
"javapython"
13、Lua用法概述
Redis将Lua作为脚本语言可帮助开发者定制自己的Redis命令,在这之前,必须修改源码。
1)数据类型及其逻辑处理
Lua语言提供了如下几种数据类型: booleans(布尔)、numbers(数值)、strings(字符串)、tables(表格),和许多高级语言相比,相对简单。
1、字符串
-- local代表val是一个局部变量,如果没有local代表是全局变量。 print函数可以打印出变量的值
local strings val = "world";
-- 结果是"world"
print (hello)
2、数组
local tables myArray = {"redis", "jedis", true, 88.0}
-- true
print (myArray[3])
①for
local int sum = 0
for i = 1, 100
do
sum = sum + i
end
-- 输出结果为5050
print (sum)
for i = 1, #myArray
do
print (myArray [i])
end
for index,value in ipairs (myArray)
do
print (index)
print (value)
end
②while
local int sum = 0
local int i = 0
while i <= 100
do
sum = sum +i
i = i + 1
end
--输出结果为5050
print (sum)
③if else
local tables myArray = {"redis", "jedis", true, 88 .0}
for i = 1, #myArray
do
if myArray [i] == "jedis"
then
print ("true")
break
else
--do nothing
end
end
3、哈希
local tables user_1 = {age = 28, name = "tome"}
--user_1 age is 28
print ("user_1 age is " . . user_1 ["age"])
for key,value in pairs (user_1)
do print (key . . value)
end
2)函数定义
在Lua中,函数以function开头,以end结尾,funcName是函数名,中间部分是函数体。
function funcName ()
. . .
end
// contact函数将两个字符串拼接:
function contact (str1, str2)
return str1 . . str2
end
--"hello world"
print (contact ("hello ", "world"))
14、在Redis中使用Lua
在Redis中执行Lua脚本有两种方法:eval和evalsha。
1)eval
eval 脚本内容 key个数 key列表 参数列表
// 此时KEYS[1]="redis",ARGV[1]="world",所以最终的返回结果是"hello redisworld"。
127.0.0.1:6379> eval 'return "hello " . . KEYS [1] . . ARGV [1] ' 1 redis world
"hello redisworld"
2)evalsha
①加载脚本:script load命令可以将脚本内容加载到Redis内存中:
将lua_get.lua加载到Redis中,得到SHA1
# redis-cli script load "$ (cat lua_get.lua)"
"7413dc2440db1fea7c0a0bde841fa68eefaf149c"
②执行脚本:evalsha 的使用方法如下,参数使用SHA1值,执行逻辑和eval一致。
evalsha 脚本SHA1值 key个数 key列表 参数列表
127.0.0.1 :6379> evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world
"hello redisworld"
15、Lua的Redis API
Lua可以使用redis.call函数实现对Redis的访问。
redis.call ("set", "hello", "world")
redis.call ("get", "hello")
放在Redis 的执行效果如下:
127.0.0.1 :6379> eval 'return redis.call ("get", KEYS [1]) ' 1 hello
"world"
15、Lua脚本功能为Redis开发和运维人员带来如下三个好处:
16、Redis如何管理Lua脚本
Redis提供了4个命令实现对Lua脚本的管理:
1、script load
script load script
2、script exists
scripts exists sha1 [sha1 … ]
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 1
3、script flush
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 1
127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists a5260dd66ce02462c5b5231c727b3f7772c0bcc5
1) (integer) 0
4、script kill
127.0.0.1:6379> eval 'while 1==1 do end ' 0 // 死循环,当前客户端会阻塞
127.0.0.1:6379> get hello
(error) BUSY Redis is busy running a script . You can only call SCRIPT KILL or
SHUTDOWN NOSAVE
127.0.0.1:6379> script kill
OK
127.0.0.1:6379> get hello
"world"
17、Bitmaps数据结构模型
Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作。
Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量。
18、Bitmaps命令
假设将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id。
1、设置值
setbit key offset value
// 将第0、5、11位用户设置为1
127.0.0.1:6379> setbit unique:users:2016-04-05 0 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 5 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 11 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 15 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2016-04-05 19 1
(integer) 0
2、获取值
getbit key offset
127.0.0.1:6379> getbit unique:users:2016-04-05 8
(integer) 0
127.0.0.1:6379> getbit unique:users:2016-04-05 5
(integer) 1
3、获取Bitmaps指定范围值为1的个数
bitcount [start] [end]
127.0.0.1:6379> bitcount unique:users:2016-04-05
(integer) 5
// 计算用户id在第1个字节到第3个字节之间的独立访问用户数,对应的用户id是11,15,19
127.0.0.1:6379> bitcount unique:users:2016-04-05 1 3
(integer) 3
4、Bitmaps间的运算
bitop op destkey key [key . . . .]
127.0.0.1:6379> bitop and unique:users:and:2016-04-04_03 unique:users:2016-04- unique:users:2016-04-03
(integer) 2
127.0.0.1:6379> bitcount unique:users:and:2016-04-04_03
(integer) 2
5、计算Bitmaps中第一个值为targetBit的偏移量
bitpos key targetBit [start] [end]
// 计算2016-04-04当前访问网站的最小用户id:
127.0.0.1:6379> bitpos unique:users:2016-04-04 1
(integer) 1
// 计算第0个字节到第1个字节之间,第一个值为0的偏移量
127.0.0.1:6379> bitpos unique:users:2016-04-04 0 0 1
(integer) 0 // id=0的用户
19、Bitmaps分析
当用户量很少的时候,对比用set,用Bitmaps会占用更大的内存。因为set是随着用户量一个一个增长内存的,而Bitmaps是根据id的,因此一开始就是这么大的内存,此时使用Bitmaps也不太合适,因为大部分位都是0。
20、HyperLogLog
1、添加
pfadd key element [element … ]
127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-4"
(integer) 1
2、计算独立用户个数
pfcount key [key … ]
127.0.0.1:6379> pfcount 2016_03_06:unique:ids
(integer) 4
127.0.0.1:6379> pfadd 2016_03_06:unique:ids "uuid-1" "uuid-2" "uuid-3" "uuid-90"
(integer) 1
127.0.0.1:6379> pfcount 2016_03_06:unique:ids
(integer) 5 // 新增uuid-90
// 向HyperLogLog插入100万个id,插入前记录一下info memory:
127.0.0.1:6379> info memory
# Memory
used_memory:835144
used_memory_human:815.57K
. . .向2016_05_01:unique:ids插入100万个用户,每次插入1000条:
elements=""
key="2016_05_01:unique:ids"
for i in `seq 1 1000000`
do
elements="${elements} uuid-"${i}
if [ [ $ ( (i%1000)) == 0 ]];
then
redis-cli pfadd ${key} ${elements}
elements=""
fi
done
127.0.0.1:6379> info memory
# Memory
used_memory :850616
used_memory_human :830.68K // 内存只增加了15K左右.
127.0.0.1:6379> pfcount 2016_05_01:unique:ids // pfcount的执行结果并不是100万
(integer) 1009838
// 如果使用集合,内存使用约84MB,但独立用户数为100万。
3、合并
pfmerge destkey sourcekey [sourcekey . . .]
21、发布订阅概述
Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息,订阅该频道的每个客户端都可以收到该消息。
22、发布订阅的命令
Redis主要提供了发布消息、订阅频道、取消订阅以及按照模式订阅和取消订阅等命令。
1、发布消息
publish channel message
// 向channel:sports频道发布一条消息“Tim won the championship”
127.0.0.1:6379> publish channel:sports "Tim won the championship"
(integer) 0
2、订阅消息
subscribe channel [channel . . .]
(一客户端A)
127.0.0.1:6379> subscribe channel:sports
Reading messages . . . (press Ctrl-C to quit)
1) "subscribe"
2) "channel:sports"
3) (integer) 1
(另一客户端B)
127.0.0.1:6379> publish channel:sports "James lost the championship"
(integer) 1
(一客户端A)
127.0.0.1:6379> subscribe channel:sports
Reading messages . . . (press Ctrl-C to quit)
. . .
1) "message"
2) "channel:sports"
3) "James lost the championship"
3、取消订阅
unsubscribe [channel [channel . . .]]
127.0.0.1:6379> unsubscribe channel:sports
1) "unsubscribe"
2) "channel:sports"
3) (integer) 0
4、按照模式订阅和取消订阅
psubscribe pattern [pattern . . .]
punsubscribe [pattern [pattern . . .]]
127.0.0.1:6379> psubscribe it* // 订阅以it开头的所有频道
Reading messages . . . (press Ctrl-C to quit)
1) "psubscribe"
2) "it*"
3) (integer) 1
5、查询订阅
①查看活跃的频道
pubsub channels [pattern]
127.0.0.1:6379> pubsub channels
1) "channel:sports"
2) "channel:it"
3) "channel:travel"
127.0.0.1 :6379> pubsub channels channel:*r*
1) "channel:sports"
2) "channel:travel"
②查看频道订阅数
pubsub numsub [channel . . .]
127.0.0.1:6379> pubsub numsub channel:sports
1) "channel:sports"
2) (integer) 2
③查看模式订阅数
pubsub numpat
127.0.0.1:6379> pubsub numpat
(integer) 1 // 当前只有一个客户端通过模式来订阅
23、发布订阅的使用场景
聊天室、公告牌、服务之间利用消息解耦都可以使用发布订阅模式。
24、GEO
Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。
1、增加地理位置信息
geoadd key longitude latitude member [longitude latitude member . . .]
127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing
(integer) 1
127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing
(integer) 0
127.0.0.1:6379> geoadd cities:locations 117.12 39.08 tianjin 114.29 38.02 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding
(integer) 4
2、获取地理位置信息
geopos key member [member . . .]
127.0.0.1:6379> geopos cities:locations tianjin
1) 1) "117.12000042200088501"
2) "39.0800000535766543"
3、获取两个地理位置的距离
geodist key member1 member2 [unit]
127.0.0.1:6379> geodist cities:locations tianjin beijing km
"89.2061"
4、获取指定位置范围内的地理信息位置集合
georadius key longitude latitude radiusm |km |ft |mi [withcoord] [withdist] [withhash] [COUNT count] [asc |desc] [store key] [storedist key]
georadiusbymember key member radiusm |km |ft |mi [withcoord] [withdist]
[withhash] [COUNT count] [asc |desc] [store key] [storedist key]
// 计算五座城市中,距离北京150公里以内的城市
127.0.0.1:6379> georadiusbymember cities:locations beijing 150 km
1) "beijing"
2) "tianjin"
3) "tangshan"
4) "baoding"
5、获取geohash
geohash key member [member . . .]
127.0.0.1:6379> geohash cities:locations beijing
1) "wx4ww02w070"
127.0.0.1:6379> type cities:locations
zset
6、删除地理位置信息
zrem key member
geohash长度与精度对应关系表
geohash长度 | 精确度(km) |
---|---|
1 | 2500 |
2 | 630 |
3 | 78 |
4 | 20 |
5 | 2.4 |
6 | 0.61 |
7 | 0.076 |
8 | 0.019 |
9 | 0.002 |
1、客户端通信协议
// 客户端发送一条set hello world命令给服务端,按照RESP的标准,客户端需要将其封装为如下格式(每行用\r\n分隔) :
*3
$3
SET
$5
hello
$5
world
实际传输格式为如下代码:
*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n
// 这样Redis服务端能够按照RESP将其解析为set hello world命令,执行后回复的格式如下:
+OK
下面对命令的一些格式进行说明:
1、发送命令格式
RESP的规定一条命令的格式如下,CRLF代表"\r\n"。
*<参数数量> CRLF
$<参数1的字节数量> CRLF
<参数1> CRLF
. . .
$<参数N的字节数量> CRLF
<参数N> CRLF
2、返回结果格式
Redis的返回结果类型分为以下五种:
· 状态回复: 在RESP中第一个字节为"+"。例如set。
· 错误回复: 在RESP中第一个字节为"-"。例如错误命令。
· 整数回复: 在RESP中第一个字节为":"。例如incr。
· 字符串回复: 在RESP中第一个字节为"$"。例如get。
· 多条字符串回复: 在RESP中第一个字节为"*"。例如mget。
redis-cli.c源码对命令结果的解析结构如下:
static sds cliFormatReplyTTY (redisReply *r, char *prefix) {
sds out = sdsempty ();
switch (r->type) {
case REDIS_REPLY_ERROR :
// 处理错误回复
case REDIS REPLY STATUS :
// 处理状态回复
case REDIS_REPLY_INTEGER :
// 处理整数回复
case REDIS_REPLY_STRING :
// 处理字符串回复
case REDIS_REPLY_NIL :
// 处理空
case REDIS_REPLY_ARRAY :
// 处理多条字符串回复
return out;
}
为了看到Redis服务端返回的“真正” 结果,可以使用nc命令、telnet命令、甚至写一个socket程序进行模拟。
以nc命令进行演示,首先使用 nc 127.0.0.1 6379连接到Redis:
nc 127.0.0.1 6379
状态回复:set hello world的返回结果为+OK:
set hello world
+OK
错误回复:由于sethx这条命令不存在,那么返回结果就是"-"号加上错误消息:
sethx
-ERR unknown command 'sethx '
整数回复:当命令的执行结果是整数时,返回结果就是整数回复:
incr counter
:1
字符串回复:当命令的执行结果是字符串时,返回结果就是字符串回复:
get hello
$5
world
多条字符串回复: 当命令的执行结果是多条字符串时,返回结果就是多条字符串回复:
mset java jedis python redis-py
+OK
mget java python
*2
$5
jedis
$8
redis-py
注意,无论是字符串回复还是多条字符串回复,如果有nil值,那么会返回$- 1。
get not_exist_key
$-1
如果批量操作中包含一条为nil值的结果,那么返回结果如下:
mget hello not_exist_key java
*3
$5
world
$-1
$5
jedis
有了RESP提供的发送命令和返回结果的协议格式,各种编程语言就可以利用其来实现相应的Redis客户端。
2、Java客户端Jedis
略…
3、Python客户端redis-py
1)获取redis-py。
redis-py需要Python2.7以上版本。
如何获取安装redis-py,方法有三种:
第一,使用pip进行安装:
pip install redis
第二,使用easy_install进行安装:
easy_install redis
第三,使用源码安装:
wget https://github.com/andymccurdy/redis-py/archive/2.10.5.zip
unzip redis-2.10.5.zip
cd redis-2.10.5
#安装redis-py
python setup.py install
2)redis-py的基本使用方法。
①导入依赖库:
import redis
②生成客户端连接:需要Redis的实例IP和端口两个参数:
client = redis.StrictRedis (host='127.0.0.1', port=6379)
③执行命令:redis-py的API保留了Redis API的原始风格:
# True
client.set (key, "python-redis")
# world
client.get (key)
import redis
client = redis.StrictRedis (host='127.0.0.1', port=6379)
key = "hello"
setResult = client.set (key, "python-redis")
print setResult
value = client.get (key)
print "key :" + key + ", value :" + value
3)redis-py的Pipeline的使用。
①引入依赖,生成客户端连接:
import redis
client = redis.StrictRedis (host='127.0.0.1 ', port=6379)
②生成Pipeline:注意client.pipeline包含了一个参数,如果transaction=False代表不使用事务:
pipeline = client.pipeline (transaction=False)
③将命令封装到Pipeline中,此时命令并没有真正执行:
pipeline.set ("hello","world")
pipeline.incr ("counter")
④执行Pipeline:
# [True, 3]
result = pipeline.execute ()
4)redis-py的Lua脚本使用。
redis-py提供了三个重要的函数实现Lua脚本的执行:
eval (String script, int keyCount, String . . . params)
script_load (String script)
evalsha (String sha1, int keyCount, String . . . params)
import redis
client = redis.StrictRedis (host='127.0.0.1', port=6379)
script = "return redis.call ('get', KEYS [1])"
#输出结果为world
print client.eval (script,1,"hello")
script_load和evalsha 函数要一起使用,首先使用script_load将脚本加载到Redis中:
import redis
client = redis.StrictRedis (host='127.0.0.1', port=6379)
script = "return redis.call ('get ',KEYS [1])"
scriptSha = client.script_load (script)
print client.evalsha (scriptSha, 1, "hello");
4、客户端API
1)client list命令能列出与Redis服务端相连的所有客户端连接信息。
// 在一个Redis实例上执行client list的结果:
127.0.0.1:6379> client list
id=254487 addr=10.2.xx.234:60240 fd=1311 name= age=8888581 idle=8888581 flags=N
db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cm
id=300210 addr=10.2.xx.215:61972 fd=3342 name= age=8054103 idle=8054103 flags=N
sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cm
...
127.0.0.1:6390> info memory
...
maxmemory_human:4.00G.
...
127.0.0.1:6390> info clients
···
connected clients:1414 // 当前Redis的连接数
client_longest_output_list:4869 // client_longest_output_list代表输出缓冲区列表最大对象数
client_biggest_input_buf:2097152
···
输出缓冲区对应的配置规则是:
client-output-buffer-limit
:客户端类型,分为三种。a)normal:普通客户端;b)slave:slave客户端,用于复制;c)pubsub:发布订阅客户端。
:如果客户端使用的输出缓冲区大于,客户端会被立即关闭。
和:如果客户端使用的输出缓冲区超过了并且持续了秒,客户端会被立即关闭。
Redis的默认配置是:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
通过过Redis源码中redis.h的redisClient结构体(Redis3.2版本变为Client)可以看到固定缓冲区和动态缓冲区的实现细节:
typedef struct redisClient {
// 动态缓冲区列表
list *reply;
// 动态缓冲区列表的长度 (对象个数)
unsigned long reply_bytes;
// 固定缓冲区已经使用的字节数
int bufpos;
// 字节数组作为固定缓冲区
char buf [REDIS_REPLY_CHUNK_BYTES];
} redisClient;
可以通过config set maxclients对最大客户端连接数进行动态设置:
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "10000"
127.0.0.1:6379> config set maxclients 50
OK
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "50"
#Redis默认的timeout是0,也就是不会检测客户端的空闲
127.0.0.1:6379> config set timeout 30
OK
下表对比client list和info clients监控输入缓冲区的优劣势
命令 | 优点 | 缺点 |
---|---|---|
client list | 能精准分析每个客户端来定位问题 | 执行速度较慢(尤其是连接数较多的情况下),频繁执行存在阻塞Redis的可能 |
info clients | 执行速度比client list快,分析过程较为简单 | 不能精准定位到客户端;不能显示所有输入缓冲区的总量,只能显示最大量 |
客户端类型表
序号 | 客户端类 | 说明 |
---|---|---|
1 | N | 普通客户端 |
2 | M | 当前客户端是master节点 |
3 | S | 当前客户端是slave节点 |
4 | O | 当前客户端正在执行monitor命令 |
5 | x | 当前客户端正在执行事务 |
6 | b | 当前客户端正在等待阻塞时间 |
7 | i | 当前客户端正在等待VM I/O,但是此状态目前已经废弃不用 |
8 | d | 一个受监视的键已被修改,EXEC命令将失败 |
9 | u | 客户端未被阻塞 |
10 | c | 回复完成输出后,关闭连接 |
11 | A | 尽可能快地关闭连接 |
2)client setName和client getName
client setName xx
client getName
3)client kill
client kill ip:port
4)client pause
client pause timeout (毫秒)
5)monitor
127.0.0.1:6379> monitor
OK
···
5、客户端相关配置
除了上面介绍的部分配置外,还有下面这些配置:
6、客户端统计片段
127.0.0.1:6379> info clients
# Clients
connected_clients :1414
client_longest_output_list :0
client_biggest_input_buf :2097152
blocked clients :0
127.0.0.1:6379> info stats
# Stats
total_connections_received: 80
. . .
rejected_connections: 0
7、Jedis客户端常见异常
1)无法从连接池获取到连接
2)客户端读写超时
3)客户端连接超时
4)客户端缓冲区异常
5)Lua脚本正在执行
6)Redis正在加载持久化文件
7)Redis使用的内存超过maxmemory配置
8)客户端连接数过大
7、客户端案例分析-Redis内存陡增
1)现象
2)分析原因
①确实有大量写入,但是主从复制出现问题:查询了Redis复制的相关信息,复制是正常的,主从数据基本一致。
127.0.0.1:6379> dbsize // 主节点的键个数
(integer) 2126870
127.0.0.1:6380> dbsize // 从节点的键个数
(integer) 2126870
②其他原因造成主节点内存使用过大:排查是否由客户端缓冲区造成主节点内存陡增,使用info clients命令查询相关信息如下:
127.0.0.1:6379> info clients
# Clients
connected_clients :1891
client_longest_output_list :225698 // 输出缓冲区不太正常,最大的客户端输出缓冲区队列已经超过了20万个对象
client_biggest_input_buf :0
blocked clients :0
通过client list命令找到omem不正常的连接,一般来说大部分客户端的omem为0(因为处理速度会足够快)
redis-cli client list | grep -v "omem=0"
3)处理方法和后期处理
8、客户端案例分析-客户端周期性的超时
1)现象
①客户端现象:客户端出现大量超时,经过分析发现超时是周期性出现的。
②服务端现象:服务端并没有明显的异常,只是有一些慢查询操作。
2)分析
3)处理方法和后期处理
1、持久化概述
Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。
2、RDB触发机制
手动触发分别对应save和bgsave命令:
3、RDB流程说明
4、RDB文件的处理
1)保存
2)压缩
3)校验
如果Redis加载损坏的RDB文件时拒绝启动,并打印日志如下:
# Short read or OOM loading DB. Unrecoverable error, aborting now.
这时可以使用Redis提供的redis-check-dump工具检测RDB文件并获取对应的错误报告。
5、RDB的优缺点
1)RDB的优点
2)RDB的缺点
RDB使用一次性生成内存快照的方式,产生的文件紧凑压缩比更高,因此读取RDB恢复速度更快。由于每次生成RDB开销较大,无法做到实时持久化,一般用于数据冷备和复制传输。
6、使用AOF
7、AOF的命令写入
8、AOF的文件同步
Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制,不同值的含义如下表(AOF缓冲区同步文件策略):
可配置值 | 说明 |
---|---|
always | 命令写入aof_buf后调用fsync操作同步到AOF文件,fsync完成后线程返回。 |
everysec | 命令写入aof_buf后调用系统write操作,write完成后线程返回。fsync同步文件操作由专门线程每秒调用一次。 |
no | 命令写入aof_buf后调用write操作,不对AOF文件做fsync同步,同步硬盘操作由操作系统负责,通常同步周期最长30秒。 |
系统调用write和fsync说明:
9、AOF的重写机制
当触发AOF重写时,内部做了哪些事呢?
10、AOF的重启加载
AOF和RDB文件都可以用于服务器重启时的数据恢复。
Redis持久化文件加载流程如下:
1)AOF持久化开启且存在AOF文件时,优先加载AOF文件。日志打印:
DB loaded from append only file: 5.841 seconds
2)AOF关闭或者AOF文件不存在时,加载RDB文件。日志打印:
* DB loaded from disk: 5.586 seconds
3)加载AOF/RDB文件成功后,Redis启动成功。
4)AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。
11、AOF的文件校验
1)加载损坏的AOF文件时会拒绝启动,并打印如下日志:
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix
2)对于错误格式的AOF文件,先进行备份,然后采用redis-check-aof–fix命令进行修复,修复后使用diff-u对比数据的差异,找出丢失的数据,有些可以人工修改补全。
3)AOF文件可能存在结尾不完整的情况,比如机器突然掉电导致AOF尾部文件命令写入不全。Redis为我们提供了aof-load-truncated配置来兼容这种情况,默认开启。加载AOF时,当遇到此问题时会忽略并继续启动,同时打印如下警告日志:
# ! ! ! Warning: short read while loading the AOF file ! ! !
# ! ! ! Truncating the AOF at offset 397856725 ! ! !
# AOF loaded anyway because aof-load-truncated is enabled
12、问题定位与优化-fork操作
持久化阻塞主线程场景有:fork阻塞和AOF追加阻塞。fork阻塞时间跟内存量和系统有关,AOF追加阻塞说明硬盘资源紧张。
当Redis做RDB或AOF重写时,一个必不可少的操作就是执行fork操作创建子进程,对于大多数操作系统来说fork是个重量级错误。虽然fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。例如对于10GB的Redis进程,需要复制大约20MB的内存页表,因此fork操作耗时跟进程总内存量息息相关,如果使用虚拟化技术,特别是Xen虚拟机,fork操作会更耗时。
fork耗时问题定位:对于高流量的Redis实例OPS可达5万以上,如果fork操作耗时在秒级别将拖慢Redis几万条命令执行,对线上应用延迟影响非常明显。正常情况下fork耗时应该是每GB消耗20毫秒左右。可以在info stats统计中查latest_fork_usec指标获取最近一次fork操作耗时,单位微秒。
如何改善fork操作的耗时:
13、问题定位与优化-子进程开销监控和优化
子进程负责AOF或者RDB文件的重写,它的运行过程主要涉及CPU、内存、硬盘三部分的消耗。
1)CPU
2)内存
3)硬盘
14、问题定位与优化-AOF追加阻塞
15、多实例部署
Redis单线程架构导致无法充分利用CPU多核特性,通常的做法是在一台机器上部署多个Redis实例。当多个实例开启AOF重写后,彼此之间会
产生对CPU和IO的竞争。
单机下部署多个实例时,为了防止出现多个子进程执行重写操作,建议做隔离控制,避免CPU和IO资源竞争。
Redis在info Persistence中为我们提供了监控子进程运行状况的度量指标,如下表:
属性名 | 属性值 |
---|---|
rdb_bgsave_in_progress | bgsave子进程是否正在运行 |
rdb_current_bgsave_time_sec | 当前运行bgsave的时间,-1表示未运行 |
aof_enabled | 是否开启AOF功能 |
aof_rewrite_in_progress | AOF重写子进程是否正在运行 |
aof_rewrite_scheduled | 在bgsave结束后是否运行AOF重写 |
aof_current_rewrite_time_sec | 当前运行AOF重写的时间,-1表示未运行 |
aof_current_size | AOF文件当前字节数 |
aof_base_size | AOF上次重写rewrite的字节数 |
我们基于以上指标,可以通过外部程序轮询控制AOF重写操作的执行。流程说明:
1、复制功能是高可用Redis的基础,哨兵和集群都是在复制的基础上实现高可用的。
2、建立复制
配置复制的方式有以下三种:
slaveof配置都是在从节点发起。
// 6379作为主节点,6380作为从节点
127.0.0.1:6380> slaveof 127.0.0.1 6379
127.0.0.1:6379> set hello redis
OK
127.0.0.1:6379> get hello
"redis"
127.0.0.1:6380> get hello
"redis"
// 主节点6379复制状态信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6379,state=online,offset=43,lag=0
. . . .
// 从节点6380复制状态信息
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago: 4
master_sync_in_progress:0
. . . .
3、断开复制
1)通过在从节点执行slaveof no one来断开与主节点复制关系。
断开复制主要流程:
从节点断开复制后并不会抛弃原有数据,只是无法再获取主节点上的数据变化。
2)通过slaveof命令还可以实现切主操作,所谓切主是指把当前从节点对主节点的复制切换到另一个主节点。执行slaveof{newMasterIp} {newMasterPort}命令即可。
切主操作流程如下:
切主后从节点会清空之前所有的数据,线上人工操作时小心slaveof在错误的节点上执行或者指向错误的主节点。
4、配置的安全性
5、配置只读
6、传输延迟
Redis为我们提供了repl-disable-tcp-nodelay参数用于控制是否关闭TCP_NODELAY,默认关闭。
7、拓扑
Redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:一主一从、一主多从、树状主从结构。
1)一主一从结构
2)一主多从结构
3)树状主从结构
8、复制过程的原理
在从节点执行slaveof命令后,复制过程便开始运作,复制过程大致分为6个过程:
1)保存主节点(master)信息。
2)主从建立socket连接。
3)发送ping命令。
4)权限验证。
5)同步数据集。
6)命令持续复制。
9、数据同步的原理
Redis在2.8及以上版本使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。
部分复制是对老版复制的重大优化,有效避免了不必要的全量复制操作。因此当使用复制功能时,尽量采用2.8以上版本的Redis。
psync命令运行需要以下组件支持:
1)复制偏移量
2)复制积压缓冲区
复制缓冲区相关统计信息保存在主节点的info replication中:
127.0.0.1:6379> info replication
# Replication
role :master
. . .
repl_backlog_active :1 // 开启复制缓冲区
repl_backlog_size :1048576 // 缓冲区最大长度
repl_backlog_first_byte_offset :7479 // 起始偏移量,计算当前缓冲区可用范围
repl_backlog_histlen :1048576 // 已保存数据的有效长度。
3)主节点运行ID
10、全量复制的原理
触发全量复制的命令是sync和psync。
psync全量复制的完整运行流程(与2.8以前的sync全量复制机制基本一致):
M * Full resync requested by slave 127.0.0.1:6380
16:24:03.057 * MASTER <-> SLAVE sync : receiving 24777842 bytes from master
16:24:02.234 * MASTER <-> SLAVE sync : Flushing old data
16:24:03.578 * MASTER <-> SLAVE sync : Loading DB in memory
16:24:06.756 * MASTER < > SLAVE sync : Finished with success
M 27 May 12:10:31.169 # Timeout receiving bulk data from MASTER . . .
11、部分复制的原理
部分复制的流程:
12、心跳的原理
主从节点在建立复制后,它们之间维护着长连接并彼此发送心跳命令。
主从心跳判断机制:
主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信,通过client list命令查看复制相关客户端信息,主节点的连接状态为flags=M,从节点连接状态为flags=S。
主节点默认每隔10秒对从节点发送ping命令,判断从节点的存活性和连接状态。可通过参数repl-ping-slave-period控制发送频率。
从节点在主线程中每隔1秒发送replconf ack{offset}命令,给主节点上报自身当前的复制偏移量。replconf命令主要作用如下:
13、异步复制的原理
主节点不但负责数据读写,还负责把写命令同步给从节点。写命令的发送过程是异步完成,也就是说主节点自身处理完写命令后直接返回给客户端,并不等待从节点复制完成。
主节点复制流程:
由于主从复制过程是异步的,就会造成从节点的数据相对主节点存在延迟。具体延迟多少字节,我们可以在主节点执行info replication命令查看相关指标获得。如下:
// offset表示当前从节点的复制偏移量,master_repl_offset表示当前主节点的复制偏移量,两者的差值就是当前从节点复制延迟量。
slave0:ip=127.0.0.1,port=6380,state=online,offset=841,lag=1
master_repl_offset:841
Redis的复制速度取决于主从之间网络环境,repl-disable-tcp-nodelay,命令处理速度等。正常情况下,延迟在1秒以内。
14、开发与运维中的问题-读写分离
对于读占比较高的场景,可以通过把一部分读流量分摊到从节点(slave)来减轻主节点(master)压力,同时需要注意永远只对主节点执行写操作。
当使用从节点响应读请求时,业务端可能会遇到如下问题:
1)数据延迟
2)读到过期数据
当主节点存储大量设置超时的数据时,如缓存数据,Redis内部需要维护过期数据删除策略。
删除策略主要有两种:惰性删除和定时删除。
定时删除:如果此时数据大量超时,主节点采样速度跟不上过期速度且主节点没有读取过期键的操作,那么从节点将无法收到del命令。这时在从节点上可以读取到已经超时的数据。Redis在3.2版本解决了这个问题,从节点读取数据之前会检查键的过期时间来决定是否返回数据,可以升级到3.2版本来规避这个问题。
3)从节点故障问题
笔者建议大家在做读写分离之前,可以考虑使用Redis Cluster等分布式解决方案,这样不止扩展了读性能还可以扩展写性能和可支撑数据规模,并且一致性和故障转移也可以得到保证,对于客户端的维护逻辑也相对容易。
15、开发与运维中的问题-主从配置不一致
对于有些配置主从之间是可以不一致,比如:主节点关闭AOF在从节点开启。但对于内存相关的配置必须要一致,比如maxmemory,hash-max-ziplist-entries等参数。
16、开发与运维中的问题-规避全量复制
对需要进行全量复制的场景逐个分析:
17、开发与运维中的问题-规避复制风暴
复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。
1)单主节点复制风暴
2)单机器复制风暴
由于Redis的单线程架构,通常单台机器会部署多个Redis实例。如果这台机器出现故障或网络长时间中断,当它重启恢复后,会有大量 从节点(slave)针对这台机器的主节点进行全量复制,会造成当前机器网络带宽耗尽。
避免的方法如下:
1、发现阻塞
2、内在原因-API或数据结构使用不合理
对于高并发的场景我们应该尽量避免在大对象上执行算法复杂度超过O(n)的命令。
1)如何发现慢查询
发现慢查询后,可以按照以下两个方向去调整:
2)如何发现大对象
3、内在原因-CPU饱和
4、内在原因-持久化阻塞
1)fork阻塞
2)AOF刷盘阻塞
Asynchronous AOF fsync is taking too long (disk is busy) . Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis .
3)HugePage写操作阻塞
5、外在原因-CPU竞争
CPU竞争问题如下:
6、外在原因-内存交换
内存交换(swap)对于Redis来说是非常致命的,Redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。
识别Redis内存交换的检查方法如下:
1、查询Redis进程号:
# redis cli p 6383 info server | grep process_id
process_id :4476
2、根据进程号查询内存交换信息:
# cat /proc/4476/smaps | grep Swap
Swap : 0 kB
Swap : 0 kB
Swap : 4 kB
Swap : 0 kB
Swap : 0 kB
......
如果交换量都是0KB或者个别的是4KB,则是正常现象,说明Redis进程内存没有被交换。
预防内存交换的方法有:
7、外在原因-网络问题
1)连接拒绝
当出现网络闪断或者连接数溢出时,客户端会出现无法连接Redis的情况。我们需要区分这三种情况:网络闪断、Redis连接拒绝、连接溢出。
# netstat -s | grep overflowed
663 times the listen queue of a socket overflowed
2)网络延迟
3)网卡软中断
1、内存使用统计
可通过执行info memory命令获取内存相关指标。
下表为info memory详细解释
属性名 | 属性说明 |
---|---|
used_memory | Redis分配器分配的内存总量,也就是内部存储的所有数据内存占用量 |
used_memory_human | 以可读的格式返回used_memory |
used_memory_rss | 从操作系统的角度显示Redis进程占用的物理内存总量 |
used_memory_peak | 内存使用是最大值,表示used_memory的峰值 |
used_memory_peak_human | 以可读的格式返回used_memory_peak |
used_memory_lua | Lua引擎所消耗的内存大小 |
mem_fragmentation_ratio | used_memory_rss/used_memory比值,表示内存碎片率 |
mem_allocator | Redis所使用的内存分配器。默认为jemalloc |
2、内存消耗划分
Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片,其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。
另外三种内存消耗:
1)对象内存
2)缓存内存
3)内存碎片
出现高内存碎片问题时常见的解决方式如下:
3、子进程内存消耗
子进程内存消耗总结如下:
4、设置内存上限
Redis主要通过控制内存上限和回收策略实现内存管理。
Redis使用maxmemory参数限制最大可用内存。限制内存的目的主要有:
需要注意,maxmemory限制的是Redis实际使用的内存量,也就是used_memory统计项对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。
通过设置内存上限可以非常方便地实现一台服务器部署多个Redis进程的内存控制。
得益于Redis单线程架构和内存限制机制,即使没有采用虚拟化,不同的Redis进程之间也可以很好地实现CPU和内存的隔离性。
5、动态调整内存上限
Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。
Redis-1> config set maxmemory 6GB
Redis-2> config set maxmemory 2GB
6、内存回收策略
Redis的内存回收机制主要体现在以下两个方面:
1)删除到达过期时间的键对象。
2)内存使用达到maxmemory上限时触发内存溢出控制策略。
7、内存优化-redisObject对象
8、内存优化-缩减键值对象
降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。
key长度: 如在设计键时,在完整描述业务情况下,键值越短越好。如 user:{uid}:friends:notify:{fid}可以简化为u:{uid}:fs:nt:{fid}。
value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。
值对象除了存储二进制数据之外,通常还会使用通用格式存储数据,比如:json、xml等作为字符串存储在Redis中。这种方式优点是方便调试和跨语言,但是同样的数据相比字节数组所需的空间更大,在内存紧张的情况下,可以使用通用压缩算法压缩json、xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。
当频繁压缩解压json等文本数据时,开发人员需要考虑压缩速度和计算开销成本,这里推荐使用Google的Snappy压缩工具,在特定的压缩率情况下效率远远高于GZIP等传统压缩工具,且支持所有主流语言环境。
9、内存优化-共享对象池
整数对象池在Redis 中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount命令查看对象引用数验证是否启用整数对象池技术。
// 设置键foo等于100时,直接使用共享池内整数对象,因此引用数是2,再设置键bar等于100时,引用数又变为3
redis> set foo 100
OK
redis> object refcount foo
(integer) 2
redis> set bar 100
OK
redis> object refcount bar
(integer) 3
需要注意的是对象池并不是只要存储[0-9999]的整数就可以工作。当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池。
为什么开启maxmemory和LRU淘汰策略后对象池无效?
为什么只有整数对象池?
10、内存优化-字符串优化
所有的键都是字符串类型,值对象数据除了整数之外都使用字符串存储。
1)字符串结构
2)预分配机制
3)字符串重构
11、内存优化-编码优化
1)了解编码
编码不同将直接影响数据的内存占用和读写效率。使用object encoding{key}命令获取编码类型。
type和encoding对应关系如下表:
类型 | 编码方式 | 数据结构 |
---|---|---|
string | raw | 动态字符串编码 |
string | embstr | 优化内存分配的字符串编码 |
string | int | 整数编码 |
hash | hashtable | 散列表编码 |
hash | ziplist | 压缩列表编码 |
list | linkedlist | 双向链表编码 |
list | ziplist | 压缩列表编码 |
list | quicklist | 3.2版本新的列表编码 |
set | hashtable | 散列表编码 |
set | intset | 整数集合编码 |
zset | skiplist | 跳跃表编码 |
zset | ziplist | 压缩列表编码 |
2)控制编码类型
编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。
Redis之所以不支持编码回退,主要是数据增删频繁时,数据向压缩编码转换非常消耗CPU,得不偿失。
hash、list、set、zset内部编码配置如下表:
类型 | 编码 | 决定条件 |
---|---|---|
hash | ziplist | 满足所有条件:value最大空间(字节)<=hash-max-ziplist-value field个数<=hash-max-ziplist-entries |
hash | hashtable | 满足任意条件:value最大空间(字节)>hash-max-ziplist-value field个数>hash-max-ziplist-entries |
list | ziplist | 满足所有条件:value最大空间(字节)<=list-max-ziplist-value 链表长度<=list-max-ziplist-entries |
list | linkedlist | 满足任意条件:value最大空间(字节)>list-max-ziplist-value 链表长度>list-max-ziplist-entries |
list | quicklist | 3.2版本新编码: 废弃 list-max-ziplist-entries和list-max-ziplist-entries 配置 使用新配置: list-max-ziplist-size:表示最大压缩空间或长度 最大空间使用[-5-1]范围配置,默认-2表示8KB 正整数表示最大压缩长度 list-compress-depth:表示最大压缩深度,默认=0不压缩 |
set | intset | 满足所有条件: 元素必须为整数 集合长度<=set-max-intlist-entries |
set | hashtable | 满足任意条件: 元素非整数类型 集合长度>hash-max-ziplist-entries |
zset | ziplist | 满足所有条件: value最大空间(字节)<=zset-max-ziplist-value 有序集合长度<=zset-max-ziplist-entries |
zset | skiplist | 满足任意条件: value最大空间(字节)>zset-max-ziplist-value 有序集合长度>zset-max-ziplist-entries |
3)ziplist编码
ziplist编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。
ziplist编码是应用范围最广的一种,可以分别作为hash、list、zset类型的底层数据结构实现。
首先从ziplist编码结构开始分析,它的内部结构类似这样: <…> 。
一个ziplist可以包含多个entry(元素),每个entry保存具体的数据(整数或者字节数组)。
ziplist结构字段含义:
ziplist数据结构的特点:
使用ziplist可以分别作为hash、list、zset数据类型实现。使用ziplist编码类型可以大幅降低内存占用。ziplist实现的数据类型相比原生结构,命令操作更加耗时,不同类型耗时排序:list
ziplist压缩编码的性能表现跟值长度和元素个数密切相关,正因为如此Redis提供了{type}-max-ziplist-value和{type}-max-ziplist-entries相关参数来做控制ziplist编码转换。最后再次强调使用ziplist压缩编码的原则:追求空间和时间的平衡。
针对性能要求较高的场景使用ziplist,建议长度不要超过1000 ,每个元素大小控制在512字节以内。
命令平均耗时使用info Commandstats命令获取,包含每个命令调用次数、总耗时、平均耗时,单位为微秒。
3)intset编码
intset编码是集合(set)类型编码的一种,内部表现为存储有序、不重复的整数集。当集合只包含整数且长度不超过set-max-intset-entries配置时被启用。
intset的字段结构含义:
intset保存的整数类型根据长度划分,当保存的整数超出当前类型时,将会触发自动升级操作且升级后不再做回退。升级操作将会导致重新申请内存空间,把原有数据按转换类型后拷贝到新数组。
使用intset编码的集合时,尽量保持整数范围一致,如都在int-16范围内。防止个别大整数触发集合升级操作,产生内存浪费。
当使用整数集合时尽量使用intset编码。
使用ziplist编码的hash类型依然 比使用hashtable编码的集合节省大量内存。
12、内存优化-控制键的数量
当使用Redis存储大量数据时,通常会存在大量键,过多的键同样会消耗大量内存。Redis本质是一个数据结构服务器,它为我们提供多种数据结构,如hash、list、set、zset等。使用Redis时不要进入一个误区,大量使用get/set这样的API,把Redis当成Memcached使用。对于存储相同的数据内容利用Redis的数据结构降低外层键的数量,也可以节省大量内存。
hash结构降低键数量分析:
同样的数据使用ziplist编码的hash类型存储比string类型节约内存。节省内存量随着value空间的减少越来越明显。hash-ziplist类型比string类型写入耗时,但随着value空间的减少,耗时逐渐降低。
使用hash重构后节省内存量这种内存优化技巧的关键点:
关于hash键和field键的设计:
当键离散度较高时,可以按字符串位截取,把后三位作为哈希的field,之前部分作为哈希的键。如:key=1948480哈希key=group#️⃣1948,哈希field=480。
当键离散度较低时,可以使用哈希算法打散键,如:使用crc32(key)&10000函数把所有的键映射到“0-9999”整数范围内,哈希field存储键的原始值。
尽量减少hash键和field的长度,如使用部分键内容。
客户端需要预估键的规模并设计hash分组规则,加重客户端开发成本。
hash重构后所有的键无法再使用超时(expire)和LRU淘汰机制自动删除,需要手动维护删除。
对于大对象,如1KB以上的对象,使用hash-ziplist结构控制键数量反而得不偿失。
对于大量小对象的存储场景,非常适合使用ziplist编码的hash类型控制键的规模来降低内存。
使用ziplist+hash优化keys后,如果想使用超时删除功能,开发人员可以存储每个对象写入的时间,再通过定时任务使用hscan命令扫描数据,找出hash内超时的数据项删除即可。
13、【汇总】内存优化的思路包括:
1、内存使用统计
可通过执行info memory命令获取内存相关指标。
下表为info memory详细解释
属性名 | 属性说明 |
---|---|
used_memory | Redis分配器分配的内存总量,也就是内部存储的所有数据内存占用量 |
used_memory_human | 以可读的格式返回used_memory |
used_memory_rss | 从操作系统的角度显示Redis进程占用的物理内存总量 |
used_memory_peak | 内存使用是最大值,表示used_memory的峰值 |
used_memory_peak_human | 以可读的格式返回used_memory_peak |
used_memory_lua | Lua引擎所消耗的内存大小 |
mem_fragmentation_ratio | used_memory_rss/used_memory比值,表示内存碎片率 |
mem_allocator | Redis所使用的内存分配器。默认为jemalloc |
2、内存消耗划分
Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片,其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。
另外三种内存消耗:
1)对象内存
2)缓存内存
3)内存碎片
出现高内存碎片问题时常见的解决方式如下:
3、子进程内存消耗
子进程内存消耗总结如下:
4、设置内存上限
Redis主要通过控制内存上限和回收策略实现内存管理。
Redis使用maxmemory参数限制最大可用内存。限制内存的目的主要有:
需要注意,maxmemory限制的是Redis实际使用的内存量,也就是used_memory统计项对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。
通过设置内存上限可以非常方便地实现一台服务器部署多个Redis进程的内存控制。
得益于Redis单线程架构和内存限制机制,即使没有采用虚拟化,不同的Redis进程之间也可以很好地实现CPU和内存的隔离性。
5、动态调整内存上限
Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。
Redis-1> config set maxmemory 6GB
Redis-2> config set maxmemory 2GB
6、内存回收策略
Redis的内存回收机制主要体现在以下两个方面:
1)删除到达过期时间的键对象。
2)内存使用达到maxmemory上限时触发内存溢出控制策略。
7、内存优化-redisObject对象
8、内存优化-缩减键值对象
降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。
key长度: 如在设计键时,在完整描述业务情况下,键值越短越好。如 user:{uid}:friends:notify:{fid}可以简化为u:{uid}:fs:nt:{fid}。
value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。
值对象除了存储二进制数据之外,通常还会使用通用格式存储数据,比如:json、xml等作为字符串存储在Redis中。这种方式优点是方便调试和跨语言,但是同样的数据相比字节数组所需的空间更大,在内存紧张的情况下,可以使用通用压缩算法压缩json、xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。
当频繁压缩解压json等文本数据时,开发人员需要考虑压缩速度和计算开销成本,这里推荐使用Google的Snappy压缩工具,在特定的压缩率情况下效率远远高于GZIP等传统压缩工具,且支持所有主流语言环境。
9、内存优化-共享对象池
整数对象池在Redis 中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount命令查看对象引用数验证是否启用整数对象池技术。
// 设置键foo等于100时,直接使用共享池内整数对象,因此引用数是2,再设置键bar等于100时,引用数又变为3
redis> set foo 100
OK
redis> object refcount foo
(integer) 2
redis> set bar 100
OK
redis> object refcount bar
(integer) 3
需要注意的是对象池并不是只要存储[0-9999]的整数就可以工作。当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池。
为什么开启maxmemory和LRU淘汰策略后对象池无效?
为什么只有整数对象池?
10、内存优化-字符串优化
所有的键都是字符串类型,值对象数据除了整数之外都使用字符串存储。
1)字符串结构
2)预分配机制
3)字符串重构
11、内存优化-编码优化
1)了解编码
编码不同将直接影响数据的内存占用和读写效率。使用object encoding{key}命令获取编码类型。
type和encoding对应关系如下表:
类型 | 编码方式 | 数据结构 |
---|---|---|
string | raw | 动态字符串编码 |
string | embstr | 优化内存分配的字符串编码 |
string | int | 整数编码 |
hash | hashtable | 散列表编码 |
hash | ziplist | 压缩列表编码 |
list | linkedlist | 双向链表编码 |
list | ziplist | 压缩列表编码 |
list | quicklist | 3.2版本新的列表编码 |
set | hashtable | 散列表编码 |
set | intset | 整数集合编码 |
zset | skiplist | 跳跃表编码 |
zset | ziplist | 压缩列表编码 |
2)控制编码类型
编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。
Redis之所以不支持编码回退,主要是数据增删频繁时,数据向压缩编码转换非常消耗CPU,得不偿失。
hash、list、set、zset内部编码配置如下表:
类型 | 编码 | 决定条件 |
---|---|---|
hash | ziplist | 满足所有条件:value最大空间(字节)<=hash-max-ziplist-value field个数<=hash-max-ziplist-entries |
hash | hashtable | 满足任意条件:value最大空间(字节)>hash-max-ziplist-value field个数>hash-max-ziplist-entries |
list | ziplist | 满足所有条件:value最大空间(字节)<=list-max-ziplist-value 链表长度<=list-max-ziplist-entries |
list | linkedlist | 满足任意条件:value最大空间(字节)>list-max-ziplist-value 链表长度>list-max-ziplist-entries |
list | quicklist | 3.2版本新编码: 废弃 list-max-ziplist-entries和list-max-ziplist-entries 配置 使用新配置: list-max-ziplist-size:表示最大压缩空间或长度 最大空间使用[-5-1]范围配置,默认-2表示8KB 正整数表示最大压缩长度 list-compress-depth:表示最大压缩深度,默认=0不压缩 |
set | intset | 满足所有条件: 元素必须为整数 集合长度<=set-max-intlist-entries |
set | hashtable | 满足任意条件: 元素非整数类型 集合长度>hash-max-ziplist-entries |
zset | ziplist | 满足所有条件: value最大空间(字节)<=zset-max-ziplist-value 有序集合长度<=zset-max-ziplist-entries |
zset | skiplist | 满足任意条件: value最大空间(字节)>zset-max-ziplist-value 有序集合长度>zset-max-ziplist-entries |
3)ziplist编码
ziplist编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。
ziplist编码是应用范围最广的一种,可以分别作为hash、list、zset类型的底层数据结构实现。
首先从ziplist编码结构开始分析,它的内部结构类似这样: <…> 。
一个ziplist可以包含多个entry(元素),每个entry保存具体的数据(整数或者字节数组)。
ziplist结构字段含义:
ziplist数据结构的特点:
使用ziplist可以分别作为hash、list、zset数据类型实现。使用ziplist编码类型可以大幅降低内存占用。ziplist实现的数据类型相比原生结构,命令操作更加耗时,不同类型耗时排序:list
ziplist压缩编码的性能表现跟值长度和元素个数密切相关,正因为如此Redis提供了{type}-max-ziplist-value和{type}-max-ziplist-entries相关参数来做控制ziplist编码转换。最后再次强调使用ziplist压缩编码的原则:追求空间和时间的平衡。
针对性能要求较高的场景使用ziplist,建议长度不要超过1000 ,每个元素大小控制在512字节以内。
命令平均耗时使用info Commandstats命令获取,包含每个命令调用次数、总耗时、平均耗时,单位为微秒。
3)intset编码
intset编码是集合(set)类型编码的一种,内部表现为存储有序、不重复的整数集。当集合只包含整数且长度不超过set-max-intset-entries配置时被启用。
intset的字段结构含义:
intset保存的整数类型根据长度划分,当保存的整数超出当前类型时,将会触发自动升级操作且升级后不再做回退。升级操作将会导致重新申请内存空间,把原有数据按转换类型后拷贝到新数组。
使用intset编码的集合时,尽量保持整数范围一致,如都在int-16范围内。防止个别大整数触发集合升级操作,产生内存浪费。
当使用整数集合时尽量使用intset编码。
使用ziplist编码的hash类型依然 比使用hashtable编码的集合节省大量内存。
12、内存优化-控制键的数量
当使用Redis存储大量数据时,通常会存在大量键,过多的键同样会消耗大量内存。Redis本质是一个数据结构服务器,它为我们提供多种数据结构,如hash、list、set、zset等。使用Redis时不要进入一个误区,大量使用get/set这样的API,把Redis当成Memcached使用。对于存储相同的数据内容利用Redis的数据结构降低外层键的数量,也可以节省大量内存。
hash结构降低键数量分析:
同样的数据使用ziplist编码的hash类型存储比string类型节约内存。节省内存量随着value空间的减少越来越明显。hash-ziplist类型比string类型写入耗时,但随着value空间的减少,耗时逐渐降低。
使用hash重构后节省内存量这种内存优化技巧的关键点:
关于hash键和field键的设计:
当键离散度较高时,可以按字符串位截取,把后三位作为哈希的field,之前部分作为哈希的键。如:key=1948480哈希key=group#️⃣1948,哈希field=480。
当键离散度较低时,可以使用哈希算法打散键,如:使用crc32(key)&10000函数把所有的键映射到“0-9999”整数范围内,哈希field存储键的原始值。
尽量减少hash键和field的长度,如使用部分键内容。
客户端需要预估键的规模并设计hash分组规则,加重客户端开发成本。
hash重构后所有的键无法再使用超时(expire)和LRU淘汰机制自动删除,需要手动维护删除。
对于大对象,如1KB以上的对象,使用hash-ziplist结构控制键数量反而得不偿失。
对于大量小对象的存储场景,非常适合使用ziplist编码的hash类型控制键的规模来降低内存。
使用ziplist+hash优化keys后,如果想使用超时删除功能,开发人员可以存储每个对象写入的时间,再通过定时任务使用hscan命令扫描数据,找出hash内超时的数据项删除即可。
13、【汇总】内存优化的思路包括:
1、内存使用统计
可通过执行info memory命令获取内存相关指标。
下表为info memory详细解释
属性名 | 属性说明 |
---|---|
used_memory | Redis分配器分配的内存总量,也就是内部存储的所有数据内存占用量 |
used_memory_human | 以可读的格式返回used_memory |
used_memory_rss | 从操作系统的角度显示Redis进程占用的物理内存总量 |
used_memory_peak | 内存使用是最大值,表示used_memory的峰值 |
used_memory_peak_human | 以可读的格式返回used_memory_peak |
used_memory_lua | Lua引擎所消耗的内存大小 |
mem_fragmentation_ratio | used_memory_rss/used_memory比值,表示内存碎片率 |
mem_allocator | Redis所使用的内存分配器。默认为jemalloc |
2、内存消耗划分
Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片,其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。
另外三种内存消耗:
1)对象内存
2)缓存内存
3)内存碎片
出现高内存碎片问题时常见的解决方式如下:
3、子进程内存消耗
子进程内存消耗总结如下:
4、设置内存上限
Redis主要通过控制内存上限和回收策略实现内存管理。
Redis使用maxmemory参数限制最大可用内存。限制内存的目的主要有:
需要注意,maxmemory限制的是Redis实际使用的内存量,也就是used_memory统计项对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。
通过设置内存上限可以非常方便地实现一台服务器部署多个Redis进程的内存控制。
得益于Redis单线程架构和内存限制机制,即使没有采用虚拟化,不同的Redis进程之间也可以很好地实现CPU和内存的隔离性。
5、动态调整内存上限
Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。
Redis-1> config set maxmemory 6GB
Redis-2> config set maxmemory 2GB
6、内存回收策略
Redis的内存回收机制主要体现在以下两个方面:
1)删除到达过期时间的键对象。
2)内存使用达到maxmemory上限时触发内存溢出控制策略。
7、内存优化-redisObject对象
8、内存优化-缩减键值对象
降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。
key长度: 如在设计键时,在完整描述业务情况下,键值越短越好。如 user:{uid}:friends:notify:{fid}可以简化为u:{uid}:fs:nt:{fid}。
value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。
值对象除了存储二进制数据之外,通常还会使用通用格式存储数据,比如:json、xml等作为字符串存储在Redis中。这种方式优点是方便调试和跨语言,但是同样的数据相比字节数组所需的空间更大,在内存紧张的情况下,可以使用通用压缩算法压缩json、xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。
当频繁压缩解压json等文本数据时,开发人员需要考虑压缩速度和计算开销成本,这里推荐使用Google的Snappy压缩工具,在特定的压缩率情况下效率远远高于GZIP等传统压缩工具,且支持所有主流语言环境。
9、内存优化-共享对象池
整数对象池在Redis 中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount命令查看对象引用数验证是否启用整数对象池技术。
// 设置键foo等于100时,直接使用共享池内整数对象,因此引用数是2,再设置键bar等于100时,引用数又变为3
redis> set foo 100
OK
redis> object refcount foo
(integer) 2
redis> set bar 100
OK
redis> object refcount bar
(integer) 3
需要注意的是对象池并不是只要存储[0-9999]的整数就可以工作。当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池。
为什么开启maxmemory和LRU淘汰策略后对象池无效?
为什么只有整数对象池?
10、内存优化-字符串优化
所有的键都是字符串类型,值对象数据除了整数之外都使用字符串存储。
1)字符串结构
2)预分配机制
3)字符串重构
11、内存优化-编码优化
1)了解编码
编码不同将直接影响数据的内存占用和读写效率。使用object encoding{key}命令获取编码类型。
type和encoding对应关系如下表:
类型 | 编码方式 | 数据结构 |
---|---|---|
string | raw | 动态字符串编码 |
string | embstr | 优化内存分配的字符串编码 |
string | int | 整数编码 |
hash | hashtable | 散列表编码 |
hash | ziplist | 压缩列表编码 |
list | linkedlist | 双向链表编码 |
list | ziplist | 压缩列表编码 |
list | quicklist | 3.2版本新的列表编码 |
set | hashtable | 散列表编码 |
set | intset | 整数集合编码 |
zset | skiplist | 跳跃表编码 |
zset | ziplist | 压缩列表编码 |
2)控制编码类型
编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。
Redis之所以不支持编码回退,主要是数据增删频繁时,数据向压缩编码转换非常消耗CPU,得不偿失。
hash、list、set、zset内部编码配置如下表:
类型 | 编码 | 决定条件 |
---|---|---|
hash | ziplist | 满足所有条件:value最大空间(字节)<=hash-max-ziplist-value field个数<=hash-max-ziplist-entries |
hash | hashtable | 满足任意条件:value最大空间(字节)>hash-max-ziplist-value field个数>hash-max-ziplist-entries |
list | ziplist | 满足所有条件:value最大空间(字节)<=list-max-ziplist-value 链表长度<=list-max-ziplist-entries |
list | linkedlist | 满足任意条件:value最大空间(字节)>list-max-ziplist-value 链表长度>list-max-ziplist-entries |
list | quicklist | 3.2版本新编码: 废弃 list-max-ziplist-entries和list-max-ziplist-entries 配置 使用新配置: list-max-ziplist-size:表示最大压缩空间或长度 最大空间使用[-5-1]范围配置,默认-2表示8KB 正整数表示最大压缩长度 list-compress-depth:表示最大压缩深度,默认=0不压缩 |
set | intset | 满足所有条件: 元素必须为整数 集合长度<=set-max-intlist-entries |
set | hashtable | 满足任意条件: 元素非整数类型 集合长度>hash-max-ziplist-entries |
zset | ziplist | 满足所有条件: value最大空间(字节)<=zset-max-ziplist-value 有序集合长度<=zset-max-ziplist-entries |
zset | skiplist | 满足任意条件: value最大空间(字节)>zset-max-ziplist-value 有序集合长度>zset-max-ziplist-entries |
3)ziplist编码
ziplist编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。
ziplist编码是应用范围最广的一种,可以分别作为hash、list、zset类型的底层数据结构实现。
首先从ziplist编码结构开始分析,它的内部结构类似这样: <…> 。
一个ziplist可以包含多个entry(元素),每个entry保存具体的数据(整数或者字节数组)。
ziplist结构字段含义:
ziplist数据结构的特点:
使用ziplist可以分别作为hash、list、zset数据类型实现。使用ziplist编码类型可以大幅降低内存占用。ziplist实现的数据类型相比原生结构,命令操作更加耗时,不同类型耗时排序:list
ziplist压缩编码的性能表现跟值长度和元素个数密切相关,正因为如此Redis提供了{type}-max-ziplist-value和{type}-max-ziplist-entries相关参数来做控制ziplist编码转换。最后再次强调使用ziplist压缩编码的原则:追求空间和时间的平衡。
针对性能要求较高的场景使用ziplist,建议长度不要超过1000 ,每个元素大小控制在512字节以内。
命令平均耗时使用info Commandstats命令获取,包含每个命令调用次数、总耗时、平均耗时,单位为微秒。
3)intset编码
intset编码是集合(set)类型编码的一种,内部表现为存储有序、不重复的整数集。当集合只包含整数且长度不超过set-max-intset-entries配置时被启用。
intset的字段结构含义:
intset保存的整数类型根据长度划分,当保存的整数超出当前类型时,将会触发自动升级操作且升级后不再做回退。升级操作将会导致重新申请内存空间,把原有数据按转换类型后拷贝到新数组。
使用intset编码的集合时,尽量保持整数范围一致,如都在int-16范围内。防止个别大整数触发集合升级操作,产生内存浪费。
当使用整数集合时尽量使用intset编码。
使用ziplist编码的hash类型依然 比使用hashtable编码的集合节省大量内存。
12、内存优化-控制键的数量
当使用Redis存储大量数据时,通常会存在大量键,过多的键同样会消耗大量内存。Redis本质是一个数据结构服务器,它为我们提供多种数据结构,如hash、list、set、zset等。使用Redis时不要进入一个误区,大量使用get/set这样的API,把Redis当成Memcached使用。对于存储相同的数据内容利用Redis的数据结构降低外层键的数量,也可以节省大量内存。
hash结构降低键数量分析:
同样的数据使用ziplist编码的hash类型存储比string类型节约内存。节省内存量随着value空间的减少越来越明显。hash-ziplist类型比string类型写入耗时,但随着value空间的减少,耗时逐渐降低。
使用hash重构后节省内存量这种内存优化技巧的关键点:
关于hash键和field键的设计:
当键离散度较高时,可以按字符串位截取,把后三位作为哈希的field,之前部分作为哈希的键。如:key=1948480哈希key=group#️⃣1948,哈希field=480。
当键离散度较低时,可以使用哈希算法打散键,如:使用crc32(key)&10000函数把所有的键映射到“0-9999”整数范围内,哈希field存储键的原始值。
尽量减少hash键和field的长度,如使用部分键内容。
客户端需要预估键的规模并设计hash分组规则,加重客户端开发成本。
hash重构后所有的键无法再使用超时(expire)和LRU淘汰机制自动删除,需要手动维护删除。
对于大对象,如1KB以上的对象,使用hash-ziplist结构控制键数量反而得不偿失。
对于大量小对象的存储场景,非常适合使用ziplist编码的hash类型控制键的规模来降低内存。
使用ziplist+hash优化keys后,如果想使用超时删除功能,开发人员可以存储每个对象写入的时间,再通过定时任务使用hscan命令扫描数据,找出hash内超时的数据项删除即可。
13、【汇总】内存优化的思路包括:
1、缓存的收益和成本
2、缓存更新策略
1)LRU/LFU/FIFO算法剔除
2)超时剔除
3)主动更新
下表为缓存的三种常见更新策略的对比:
策略 | 一致性 | 维护成本 |
---|---|---|
LRU/LFU/FIFO算法剔除 | 最差 | 低 |
超时剔除 | 较差 | 较低 |
主动更新 | 强 | 高 |
4)最佳实践建议
3、 缓存粒度控制
从通用性、空间占用、代码维护三个角度进行说明:
4、穿透优化
下表为缓存空对象和布隆过滤器方案对比:
解决缓存穿透 | 使用场景 | 维护成本 |
---|---|---|
缓存空对象 | - 数据命中不高 - 数据频繁变化实时性高 |
- 代码维护简单 - 需要过多的缓存空间 - 数据不一致 |
布隆过滤器 | - 数据命中不高 - 数据相对固定实时性低 |
- 代码维护复杂 - 缓存空间占用少 |
5、无底洞优化
下表为四种批量操作解决方案对比:
方案 | 优点 | 缺点 | 网络IO |
---|---|---|---|
串行命令 | 1)编程简单 2)如果少量keys,性能可以满足要求 |
大量keys请求延迟严重 | O(keys) |
串行IO | 1)编程简单 2)少量节点,性能满足要求 |
大量node延迟严重 | O(nodes) |
并行IO | 利用并行特性,延迟取决于最慢的节点 | 1)编程复杂 2)由于多线程,问题定位可能较难 |
O(max_slow(nodes)) |
hash_tag | 性能最高 | 1)业务维护成本较高 2)容易出现数据倾斜 |
O(1) |
6、雪崩优化
7、热点key重建优化
1)互斥锁
2)永远不过期
“永远不过期”包含两层意思:
从实战看,此方法有效杜绝了热点key产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。
互斥锁(mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
“永远不过期” :这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
下表为两种热点key的解决方法:
解决方案 | 优点 | 缺点 |
---|---|---|
简单分布式锁 | - 思路简单 - 保证一致性 |
- 代码复杂度增大 - 存在死锁的风险 - 存在线程池阻塞的风险 |
“永远不过期” | 基本杜绝热点key问题 | - 不保证一致性 - 逻辑过期时间增加代码维护成本和内存成本 |
8、汇总
1、Linux配置优化之内存分配控制
1)vm.overcommit_memory
vm.overcommit_memory用来设置内存分配策略,有三个可选值,如下表:
值 | 含义 |
---|---|
0 | 表示内核将检查是否有足够的可用内存,如果有足够的可用内存,内存申请通过,否则内存申请失败,并把错误返回给应用进程。 |
1 | 表示内核允许超量使用内存直到用完为止 |
2 | 表示内核决不过量的(“never overcommit”)使用内存,即系统整个内存地址空间不能超过swap+50%的RAM值,50%是overcommit_ratio默认值,此参数同样支持修改。 |
2)获取和设置
// 获取
# cat /proc/sys/vm/overcommit_memory
0
// 设置
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
3)最佳实践
2、Linux配置优化之swappiness
1)参数说明
swapniess重要值策略说明如下表:
值 | 策略 |
---|---|
0 | Linux3.5以及以上:宁愿用OOM killer也不用swap Linux3.4以及更早:宁愿swap也不用OOM killer |
1 | Linux3.5以及以上:宁愿swap也不用OOM killer |
60 | 默认值 |
100 | 操作系统会主动地使用swap |
2)设置方法
echo {bestvalue} > /proc/sys/vm/swappiness
// 但是上述方法在系统重启后就会失效,为了让配置在重启Linux操作系统后立即生效,只需要在/etc/sysctl.conf追加vm.swappiness={bestvalue}即可。
echo vm.swappiness={bestvalue} >> /etc/sysctl.conf
3)如何监控swap
// 通过info server获取Redis的进程号process_id:
redis-cli -h ip -p port info server | grep process_id
process_id:986
// 通过cat/proc/986/smaps查询Redis的smaps信息,会输出多个内存块信息。其中Swap字段代表该内存块存在swap分区的数据大小。
// 通过执行如下命令,就可以找到每个内存块镜像信息中,这个进程使用到的swap量,通过求和就可以算出总的swap用量:
cat /proc/986/smaps | grep Swap
3、Linux配置优化之THP
禁用方法如下:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
为了使机器重启后THP配置依然生效,可以在/etc/rc.local中追加echo never>/sys/kernel/mm/transparent_hugepage/enabled。
echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled
4、Linux配置优化之OOM killer
设置方法如下:
echo {value} > /proc/${process_id}/oom_adj
// 将所有Redis的oom_adj设置为最低值
for redis_pid in $ (pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done
5、Linux配置优化之使用NTP
6、Linux配置优化之ulimit
Open files的设置方法如下:
ulimit –Sn {max-open-files}
7、Linux配置优化之TCP backlog
查看方法:
# cat /proc/sys/net/core/somaxconn
128
修改方法:
echo 511 > /proc/sys/net/core/somaxconn
8、flushall/flushdb误操作之缓存与存储
9、flushall/flushdb误操作之借助AOF机制恢复
10、flushall/flushdb误操作之RDB变化
11、flushall/flushdb误操作之从节点变化
12、flushall/flushdb误操作之快速恢复数据
下面使用AOF作为数据源进行恢复演练:
1、防止AOF重写。快速修改Redis主从的auto-aof-rewrite-percentage和auto-aof-rewrite-min-size变为一个很大的值,从而防止了AOF重写的发生,例如:
config set auto-aof-rewrite-percentage 1000
config set auto-aof-rewrite-min-size 100000000000
2、去掉主从AOF文件中的flush相关内容:
*1
$8
flushall
3、重启Redis主节点服务器,恢复数据。
13、安全的Redis概述
14、安全的Redis之Redis密码机制
1)简单的密码机制
15、安全的Redis之伪装危险命令
16、安全的Redis之防火墙
17、安全的Redis之bind
18、安全的Redis之定期备份数据
19、安全的Redis之不使用默认端口
20、安全的Redis之使用非root用户启动
21、处理bigkey
22、bigkey的危害
23、如何发现bigkey
24、如何删除bigkey
25、bigkey最佳实践思路
26、寻找热点key
1)客户端
2)代理端
3)Redis服务端
4)机器
方案 | 优点 | 缺点 |
---|---|---|
客户端 | 实现简单 | - 内存泄漏隐患 - 维护成本高 - 只能统计单个客户端 |
代理 | 代理是客户端和服务端的桥梁,实现最方案最系统 | 增加代理端的开发部署成本 |
服务端 | 实现简单 | - Monitor本身的使用成本和危害,只能短时间使用 - 只能统计单个Redis节点 |
机器 | 对于客户端和服务端无侵入和影响 | 需要专业的运维团队开发,并且增加了机器的部署成本 |
下面是三种方案的思路:
27、汇总
1、info系统状态说明
info命令所有的section(info all命令涉及的所有section,其中每个模块名就是我们上面提到的section):
模块名 | 模块含义 |
---|---|
Server | 服务器信息 |
Clients | 客户端信息 |
Memory | 内存信息 |
Persistence | 持久化信息 |
Stats | 全局统计信息 |
Replication | 复制信息 |
CPU | CPU消耗信息 |
Commandstats | 命令统计信息 |
Cluster | 集群信息 |
Keyspace | 数据库键统计信息 |
1)info Server模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
redis_version | 3.0.7 | Redis服务版本 |
redis_git_sha1 | 00000000 | Git SHA1 |
redis_git_dirty | 0 | Git dirty flag |
redis_build_id | 186eba9451cf9390 | Redis build id |
redis_mode | cluster | 运行模式,分为:Cluster、Sentinel、Standalone |
os | Linux 2.6.18-274.el5 x86_64 | Redis所在机器的操作系统 |
arch_bits | 64 | 架构(32或64位) |
multiplexing_api | epoll | Redis所使用的事件处理机制 |
gcc_version | 4.1.2 | 编译Redis时所使用的GCC版本 |
process_id | 31524 | Redis服务进程的PID |
run_id | fd8b97739c469526f640b8895a5084d669ed151f | Redis服务的标识符 |
tcp_port | 6384 | 监听端口 |
uptime_in_seconds | 9753347 | 自Redis服务启动以来,运行的秒数 |
uptime_in_days | 112 | 自Redis服务启动以来,运行的天数 |
hz | 10 | serverCron每秒运行次数 |
lru_clock | 16388503 | 以分钟为单位进行自增的时钟,用于LRU管理 |
config_file | /opt/cachecloud/conf/redis-cluster-6384.conf | Redis的配置文件 |
2)info Clients模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
connected_clients | 262 | 当前客户端连接数 |
client_longest_output_list | 0 | 当前所有输出缓冲区中队列对象个数的最大值 |
client_biggest_input_buf | 0 | 当前所有输入缓冲区中占有的最大容量 |
blocked_clients | 0 | 正在等待阻塞命令(例如BLPOP等)的客户端数量 |
3)info Memory模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
used_memory | 183150904 | Redis分配器分配的内存总量,也就是内部存储的所有数据内存占用量 |
used_memory_human | 174.67M | 以可读的格式返回used_memory |
used_memory_rss | 428621824 | 从操作系统的角度,Redis进程占用的物理内存总量 |
used_memory_peak | 522768352 | 内存使用的最大值,表示used_memory的峰值 |
used_memory_peak_human | 498.55M | 以可读的格式返回used_memory_peak |
used_memory_lua | 35840 | Lua引擎所消耗的内存大小 |
mem_fragmentation_ratio | 2.34 | used_memory_rss/used_memory比值,表示内存碎片率 |
mem_allocator | jemalloc-3.6.0 | Redis所使用的内存分配器。默认为:jemalloc |
4)info Persistence模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
loading | 0 | 是否在加载持久化文件。0否,1是 |
rdb_changes_since_last_save | 53308858 | 自上次RDB后,Redis数据改动条数 |
rdb_bgsave_in_progress | 0 | 标识RDB的bgsave操作是否进行中。0否,1是 |
rdb_last_save_time | 1456376460 | 上次bgsave操作的时间戳 |
rdb_last_bgsave | ok | 上次bgsave操作状态 |
rdb_last_bgsave_time_sec | 3 | 上次bgsave操作使用的时间(单位是秒) |
rdb_current_bgsave_time_sec | -1 | 如果bgsva操作正在进行,则记录当前bgsave操作使用的时间(单位是秒) |
aof_enabled | 1 | 是否开启了AOF功能。0否,1是 |
aof_rewrite_in_progress | 0 | 标识AOF的rewrite操作是否在进行中。0否,1是 |
aof_rewrite_scheduled | 0 | 标识是否将要在RDB的bgsave操作结束后执行AOF rewrite操作 |
aof_last_rewrite_time_sec | 0 | 上次AOF rewrite操作使用的时间(单位是秒) |
aof_current_rewrite_time_sec | -1 | 如果rewrite操作正在进行,则记录当前AOF rewrite所使用的时间(单位是秒) |
aof_last_bgrewrite_status | ok | 上次AOF重写操作的状态 |
aof_last_write_status | ok | 上次AOF写磁盘的结果 |
aof_current_size | 186702421 | AOF当前尺寸(单位是字节) |
aof_base_size | 134279710 | AOF上次启动或rewrite的尺寸(单位是字节) |
aof_buffer_length | 0 | AOF buffer的大小 |
aof_rewrite_buffer_length | 0 | AOF rewrite buffer的大小 |
aof_pending_bio_fsync | 0 | 后台IO队列中等待fsync任务的个数 |
aof_delayed_fsync | 64 | 延迟的fsync计数器 |
5)info Stats模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
total_connections_received | 495967 | 连接过的客户端总数 |
total_commands_processed | 5139857171 | 执行过的命令总数 |
instantaneous_ops_per_sec | 511 | 每秒处理命令总数 |
total_net_input_bytes | 282961395316 | 输入总网络流量(以字节为单位) |
total_net_output_bytes | 1760503612586 | 输出总网络流量(以字节为单位) |
instantaneous_input_kbps | 28.24 | 每秒输入的字节数 |
instantaneous_output_kbps | 234.90 | 每秒输出的字节数 |
rejected_connections | 0 | 拒绝的连接个数 |
sync_full | 4 | 主从完全同步成功次数 |
sync_partial_ok | 0 | 主从部分同步成功次数 |
sync_partial_err | 0 | 主从部分同步失败次数 |
expired_keys | 45534039 | 过期的key数量 |
evicted_keys | 0 | 剔除(超过了maxmemory后)的key数量 |
keyspace_hits | 3923837939 | 命中次数 |
keyspace_misses | 1078922155 | 不命中次数 |
pubsub_channels | 0 | 当前使用中的频道数量 |
pubsub_patterns | 0 | 当前使用中的模式数量 |
latest_fork_usec | 16194 | 最近一次fork操作消耗的时间(微秒) |
migrate_cached_sockets | 0 | 记录当前Redis正在进行migrate操作的目标Redis个数。例如Redis A分别向Redis B和C执行migrate操作,那么这个值就是2 |
6)info Replication模块统计信息
角色 | 属性名 | 属性值 | 属性描述 |
---|---|---|---|
通用配置 | role | master | slave |
主节点 | connected_slaves | 1 | 连接的从节点个数 |
主节点 | slave0 | slabe0:op=10.10.xx.169,port=6382,state=online,offset=426978948465,lag=1 | 连接的从节点信息 |
主节点 | master_repl_offset | 426978955146 | 主节点偏移量 |
从节点 | master_host | 10.10.xx.64 | 主节点IP |
从节点 | master_port | 6387 | 主节点端口 |
从节点 | master_link_status | up | 与主节点的连接状态 |
从节点 | master_last_io_seconds_ago | 0 | 主节点最后与从节点的通信时间间隔,单位为秒 |
从节点 | master_sync_in_progress | 0 | 从节点是否正在全量同步主节点RDB文件 |
从节点 | slave_repl_offset | 426978956171 | 复制偏移量 |
从节点 | slave_priority | 100 | 从节点优先级 |
从节点 | slave_read_only | 1 | 从节点是否只读 |
从节点 | connected_slaves | 0 | 连接从节点个数 |
从节点 | master_repl_offset | 0 | 当前从节点作为其他节点的主节点时的复制偏移量 |
通用配置 | repl_backlog_active | 1 | 复制缓冲区状态 |
通用配置 | repl_backlog_size | 10000000 | 复制缓冲区尺寸(单位:字节) |
通用配置 | repl_backlog_first_byte_offset | 426968955147 | 复制缓冲区起始偏移量,标识当前缓冲区可用范围 |
通用配置 | repl_backlog_histlen | 10000000 | 标识复制缓冲区已存有效数据长度 |
7)info CPU模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
used_cpu_sys | 31957.30 | Redis主进程在内核态所占用的CPU时钟总和 |
used_cpu_user | 72484.27 | Redis主进程在用户态所占用的CPU时钟总和 |
used_cpu_sys_children | 121.49 | Redis子进程在内核态所占用的CPU时钟总和 |
used_cput_user_children | 195.13 | Redis子进程在用户态所占用的CPU时钟总和 |
8)info Commandstats模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
cmdstat_get | calls=3738730699,usec=11054972404,usec_per_call=2.96 | get命令调用总次数、总耗时、平均耗时(单位:微秒) |
cmdstat_set | calls=50174458,usec=323143686,usec_per_call=6.44 | set命令调用总次数、总耗时、平均耗时(单位:微秒) |
9)info Cluster模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
cluster_enabled | 1 | 节点是否为cluster模式。1是,0否 |
10)info Keyspace模块统计信息
属性名 | 属性值 | 属性描述 |
---|---|---|
db0 | db0:keys=106430,expires=56107,avg_ttl=60283952 | 当前数据库key总数,带有过期时间的key总数,平均存活时间 |
2、standalone配置说明和分析(Redis单机模式)
1)Redis 的一些总体配置,例如端口、 日志、数据库等。
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
daemonize | 是否是守护进程 | no | yes|no | 不可以 |
port | 端口号 | 6379 | 整数 | 不可以 |
loglevel | 日志级别 | notice | debug|verbose|notice|warning | 可以 |
logfile | 日志文件名 | 空 | 自定义,建议以端口号为名 | 不可以 |
databases | 可用的数据库数 | 16 | 整数 | 不可以 |
unixsocket | unix套接字 | 空(不通过unix套接字来监听) | 指定套接字文件 | 不可以 |
unixsocketperm | unix套接字权限 | 0 | Linux三位数权限 | 不可以 |
pidfile | Redis运行的进程pid文件 | /var/run/redis.pid | /var/run/redis-{port}.pid | 不可以 |
lua-time-limit | Lua脚本"超时时间"(单位:毫秒) | 5000 | 整数,但是此超时不会真正停止脚本运行 | 可以 |
tcp-backlog | tcp-backlog | 511 | 整数 | 不可以 |
watchdog-period | 看门狗,用于诊断Redis的延迟问题,此参数是检查周期。(此参数需要在运行时配置才能生效) | 0 | 整数 | 可以 |
activerehashing | 指定是否激活重置哈希 | yes | yes|no | 可以 |
dir | 工作目录(aof、rdb、日志文件都存放在此目录) | ./(当前目录) | 自定义 | 可以 |
2)Redis内存相关配置。
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
maxmemory | 最大可用内存(单位字节) | 0(没有限制) | 整数 | 可以 |
maxmemory-policy | 内存不够时,淘汰策略 | noeviction | volatile-lru -> 用lru算法删除过期的键值 allkeys-lru -> 用lru算法删除所有键值 volatile-random -> 随机删除过期的键值 allkeys-random -> 随机删除任何键值 volatile-ttl -> 删除最近要到期的键值 noeviction -> 不删除键 |
可以 |
maxmemory-samples | 检测LRU采样数 | 5 | 整数 | 可以 |
3)AOF方式持久化相关配置
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
appendonly | 是否开启AOF持久化模式 | no | no|yes | 可以 |
appendfsync | AOF同步磁盘频率 | everysec | always|everysec|no | 可以 |
appendfilename | AOF文件名 | appendonly.aof | appendonly-{port}.aof | 不可以 |
aof-load-truncated | 加载AOF文件时,是否忽略AOF文件不完整的情况,让Redis正常启动 | yes | no | 可以 |
no-appendfsync-on-rewrite | 设置为yes表示rewrite期间对新写操作不fsync,暂时存在缓冲区中,等rewrite完成后再写入 | no | no|yes | 可以 |
auto-aof-rewrite-min-size | 触发rewrite的AOF文件最小阈值(单位:兆) | 64m | 整数+m(代表兆) | 可以 |
auto-aof-rewrite-percentage | 触发rewrite的AOF文件的增长比例条件 | 100 | 整数 | 可以 |
aof-rewrite-incremental-fsync | AOF重写过程中,是否采取增量文件同步策略 | yes | yes|no | 可以 |
4)RDB方式持久化相关配置
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
save | RDB保存条件 | save 900 1 save 300 10 save 60 10000 |
如果没有该配置,代表不使用自动RDB策略 | 可以 |
dbfilename | RDB文件名 | dump.rdb | dump-{port}.rdb | 可以 |
rdbcompression | RDB文件是否压缩 | yes | yes|no | 可以 |
rdbchecksum | RDB文件是否使用校验和 | yes | yes|no | 可以 |
stop-write-on-bgsave-error | bgsave执行错误,是否停止Redis接受写请求 | yes | yes|no | 可以 |
5)Redis慢查询相关配置
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
slow-log-slower-than | 慢查询被记录的阀值(单位微妙) | 10000 | 整数 | 可以 |
slowlog-max-len | 最多记录慢查询的条数 | 128 | 整数 | 可以 |
latency-monitor-threshold | Redis服务内存延迟监控 | 0(关闭) | 整数 | 可以 |
6)Redis数据结构优化的相关配置
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
hash-max-ziplist-entries | hash数据结构优化参数 | 512 | 整数 | 可以 |
hash-max-ziplist-value | hash数据结构优化参数 | 64 | 整数 | 可以 |
list-max-ziplist-entries | list数据结构优化参数 | 512 | 整数 | 可以 |
list-max-ziplist-value | list数据结构优化参数 | 64 | 整数 | 可以 |
set-max-intset-entries | set数据结构优化参数 | 512 | 整数 | 可以 |
zset-max-ziplist-entries | zset数据结构优化参数 | 128 | 整数 | 可以 |
zset-max-ziplist-value | zset数据结构优化参数 | 64 | 整数 | 可以 |
hll-sparse-max-bytes | HyperLogLog数据结构优化参数 | 3000 | 整数 | 可以 |
7)Redis复制相关的配置
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
slaveof | 指定当前从节点复制哪个主节点,参数:主节点的ip和port | 空 | ip和端口 | 不可以,但可以用slaveof命令设置 |
repl-ping-slave-period | 主节点定期向从节点发送ping命令的周期,用于判定从节点是否存活。(单位:秒) | 10 | 整数 | 可以 |
repl-timeout | 主从节点复制超时时间(单位:秒) | 60 | 整数 | 可以 |
repl-backlog-size | 复制积压缓存区大小 | 1M | 整数 | 可以 |
repl-backlog-ttl | 主节点在没有从节点的情况下多长时间后释放复制积压缓存区空间 | 3600 | 整数 | 可以 |
slave-priority | 从节点的优先级 | 100 | 0-100 | 可以 |
min-slaves-to-write | 当主节点发现从节点数量小于min-slaves-to-write且延迟小于等于min-slaves-max-lag时,master停止写入操作 | 0 | 整数 | 可以 |
min-slaves-max-lag | 当主节点发现从节点数量小于min-slaves-to-write且延迟小于等于min-slaves-max-lag时,master停止写入操作 | 10 | 整数 | 可以 |
slave-serve-stale-data | 当从节点与主节点连接中断时,如果此参数值设置为"yes",从节点可以继续处理客户端的请求。否则除info和slaveof命令之外,拒绝的所有请求并统一回复"SYNC with master in process" | yes | yes|no | 可以 |
slave-read-only | 从节点是否开启只读模式,集群架构下从节点默认读写都不可用,需要调用readonly命令开启只读模式 | yes | yes|no | 可以 |
repl-disble-tcp-nodelay | 是否开启主从复制socket的NO_DELAY选项: yes:Redis会合并小的TCP包来节省带宽,但是这样增加同步延迟,造成主从数据不一致 no:主节点会立即发送同步数据,没有延迟 |
no | yes|no | 可以 |
repl-diskless-sync | 是否开启无盘复制 | no | yes|no | 可以 |
repl-diskless-sync-delay | 开启无盘复制后,需要延迟多少秒后进行创建RDB操作,一般用于同时加入多个从节点时,保证多个从节点可共享RDB | 5 | 整数 | 可以 |
8)Redis客户端的相关配置
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
maxclients | 最大客户端连接数 | 10000 | 整数 | 可以 |
client-output-buffer-limit | 客户端输出缓冲区限制 | normal 0 0 0 slave 268435456 67108864 60 pubsub 33554432 8388608 60 | 整数 | 可以 |
timeout | 客户端闲置多少秒后关闭连接(单位:秒) | 0(永不关闭) | 整数 | 可以 |
tcp-keepalive | 检测TCP连接活性的周期(单位:秒) | 0(不检测) | 整数 | 可以 |
9)Redis安全的相关配置
配置名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
requirepass | 密码 | 空 | 自定义 | 可以 |
bind | 绑定IP | 空 | 自定义 | 不可以 |
masterauth | 从节点需要配置的主节点密码 | 空 | 主节点的密码 | 可以 |
3、Sentinel配置说明和分析
1)Sentinel节点是特殊的Redis节点,有几个特殊的配置
Redis Sentinel节点配置说明
参数名 | 含义 | 默认值 | 可选值 | 可否支持sentinel set配置热生效 |
---|---|---|---|---|
sentinel monitor | 定义监控的主节点名、ip、port、主观下线票数 | sentinel monitor mymaster 127.0.0.1 6379 2 | 自定义masterName、实际的ip:port、票数 | 支持 |
sentinel down-after-milliseconds | Sentinel判定节点不可达的毫秒数 | sentinel down-after-milliseconds mymaster 30000 | 整数 | 支持 |
sentinel parallel-sync | 在执行故障转移时,最多有多少个从服务器同时对新的主服务器进行同步 | sentinel parallel-sync mymaster 1 | 大于0,不超过从服务器个数 | 支持 |
sentinel failover-timeout | 故障迁移超时时间 | sentinel failover-timeout mymaster 180000 | 整数 | 支持 |
sentinel auth-pass | 主节点密码 | 空 | 主节点密码 | 支持 |
sentinel notification-script | 故障转移期间脚本通知 | 空 | 脚本文件路径 | 支持 |
sentinel client-reconfig-script | 故障转移成功后脚本通知 | 空 | 脚本文件路径 | 支持 |
4、Cluster配置说明和分析
1)Cluster节点是特殊的Redis节点,有几个特殊的配置
Redis Cluster配置说明
参数名 | 含义 | 默认值 | 可选值 | 可否支持config set配置热生效 |
---|---|---|---|---|
cluster-node-timeout | 集群节点超时时间(单位:毫秒) | 15000 | 整数 | 可以 |
cluster-migration-barrier | 主从节点切换需要的从节点数最小个数 | 1 | 整数 | 可以 |
cluster-slave-validity-factor | 从节点有效性判断因子,当从节点与主节点最后通行时间超过(cluster-node-timeout*slave-validity-factor)+repl-ping-slave-period时,对应从节点不具备故障转移资格,防止断线时间过长的从节点进行故障转移。设置为0标叔从节点永不过期。 | 10 | 整数 | 可以 |
cluster-require-full-converage | 集群是否需要所有的slot都分配给在线节点,才能正常访问 | yes | yes|no | 可以 |
cluster-enabled | 是否开启集群模式 | yes | yes|no | 不可以 |
cluster-config-file | 集群配置文件名称 | node.conf | nodes-{port}.conf | 不可以 |