Redis 总结

介绍

全称:Remote Dictionary Server,基于C语言开发的开源高性能键值对数据库,非关系型数据库的一种,支持多种数据结构类型,由于数据放在内存中,存取速度快,支持数据的持久化,并且也支持事务操作等,常用于数据缓存、分布式存储控制、排行榜等业务场景当中

注:
redis是内存数据库:为了使用的效率,redis会将大部分数据都载入到内存当中(未必是全部数据,但是key会全部载入)

和关系型数据库对比

关系型数据库缺点:性能瓶颈(磁盘IO性能低下)、扩展瓶颈(数据关系复杂,扩展性差)
nosql解决思路:降低磁盘IO次数、去除数据库间的关系,仅存储数据

MySQL、MongoDB、Redis 数据库之间的对比参考:
https://blog.csdn.net/CatStarXcode/article/details/79513425

特点
  • 数据间没有必然的关联关系
  • 基于单线程工作
  • 高性能
  • 多种数据类型支持
  • 持久化支持
适合场景
  • 存储热点数据,加速查询
  • 任务队列
  • 即时信息查询(如:在线人数)
  • 数据失效控制(如:验证码)
  • 分布式数据分享(如:session分离)
  • 消息队列
  • 分布式锁
  • 海量用户
  • 高并发

更多参考:
https://www.php.cn/faq/420209.html
https://www.jianshu.com/p/40dbc78711c8

支持数据类型

  • string:字符串
  • list:列表
  • set:集合
  • zset:有序集合
  • hash:哈希表

注:
数据类型指的是value的类型,key的类型只能是字符串

安装

下载地址

Linux版:链接
windows版:链接
下载安装完成后运行redis-server文件,没问题就代表redis服务启动成功

配置windows的redis服务

输入以下命令:

redis-server --service-install redis.windows.conf --loglevel verbose  
可视化工具下载

免费、更强大:https://github.com/qishibo/AnotherRedisDesktopManager

以前常用的,新版开始要收费:https://github.com/uglide/RedisDesktopManager/releases?after=0.9.0-alpha5

启动方式

相关命令
  • 服务端指定端口启动:
redis-server --port 6380
  • 服务端指定配置文件启动:
redis-server xxx.conf
  • 客户端连接指定主机和端口:
redis-cli -h 127.0.0.1 -p 6379
# 指定主机和端口
redis常用配置
port 数字    端口号
daemonize yes|no    是否设置守护进程(守护进程启动后日志将不打印到当前终端)
logfile "xxx.log"   日志文件名
dir "/xxx/data" 服务文件保存目录(包括日志文件、持久化文件等)

基本操作

交互操作
清屏命令
clear
查看帮助
  • 格式:
help 命令
# 查看某个命令的使用方式
help @组名
# 查看某个组下有哪些命令
help 
# 查看提示

举例:

127.0.0.1:6379> help set
# 查看set命令的使用方式
  SET key value [EX seconds] [PX milliseconds] [NX|XX]
  summary: Set the string value of a key
  since: 1.0.0
  group: string
# 其中第一行是命令格式,summary是功能描述,since是出现的版本,group是所属群组

127.0.0.1:6379> help @string
# 查看string群组下的所有命令
  APPEND key value
  summary: Append a value to a key
  since: 2.0.0

  BITCOUNT key [start end]
  summary: Count set bits in a string
  since: 2.6.0
  ...
退出命令
quit
exit

key操作
help @generic
# 查看key通用的操作
del k
# 删除指定key
exists key
# 查看指定key是否存在
type key
# 获取key的类型
  • 示例:
127.0.0.1:6379> set aaa 111
OK
127.0.0.1:6379> sadd set 111 222
(integer) 0
127.0.0.1:6379> type aaa
string
127.0.0.1:6379> type set
set
127.0.0.1:6379> exists aaa
(integer) 1
127.0.0.1:6379> del aaa
(integer) 1
127.0.0.1:6379> exists aaa
(integer) 0
时效性控制操作
expire k seconds
# 设置key的有效期
pexpire k milliseconds

expireat k timestamp

pexpireat k milliseconds-timestamp

ttl k
# 获取key的有效期
pttl k

persist k
# 设置key有效期变为永久
  • 举例:
127.0.0.1:6379> set aaa 111
OK
127.0.0.1:6379> ttl aaa
(integer) -1
# -1代表有效期为永久
127.0.0.1:6379> expire aaa 5
(integer) 1
# 设置5s有效期
127.0.0.1:6379> ttl aaa
(integer) 3
# 大于0的数值代表剩余有效期
127.0.0.1:6379> ttl aaa
(integer) -2
# -2代表已过期或者不存在
127.0.0.1:6379> get aaa
(nil)
127.0.0.1:6379> set aaa 111
OK
127.0.0.1:6379> expire aaa 100
(integer) 1
127.0.0.1:6379> ttl aaa
(integer) 98
127.0.0.1:6379> persist aaa
(integer) 1
# 设置有效期为永久
127.0.0.1:6379> ttl aaa
(integer) -1
查询操作
keys pattern
# 查询key
  • 常用模式规则:
*   匹配任意长度的任意符号
?   匹配长度为1的任意符号
[]  匹配长度为1的指定符号
  • 举例:
keys *
# 查询所有key
keys a*
# 查询a开头的k
keys a[0-9]
# 查询a1/a2/...这样格式的key
改名/排序操作
rename k newk
# 将key改成新的key名(如果newk存在则会覆盖)
renamenx k newk
# 只有newk不存在才能改名成功
sort k
# 对key的数据进行排序(数据类型必须是list、set、zset,并且只是返回一个排好序的结果,不会对原来数据进行改变)
  • 举例:
127.0.0.1:6379> mset a 1 b 2
OK
127.0.0.1:6379> rename a b
OK
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379> get b
"1"
# 可以看到b被覆盖了
127.0.0.1:6379> set a 100
OK
127.0.0.1:6379> renamenx a b
(integer) 0
# 因为b已经存在,所以操作失败
127.0.0.1:6379> get a
"100"
127.0.0.1:6379> get b
"1"
# 可以看到b没有被覆盖
127.0.0.1:6379> rpush li 5 3 2 1 6
(integer) 5
127.0.0.1:6379> lrange li 0 -1
1) "5"
2) "3"
3) "2"
4) "1"
5) "6"
127.0.0.1:6379> sort li
1) "1"
2) "2"
3) "3"
4) "5"
5) "6"
# sort排序后返回一个新的结果,不对原来的数据进行修改
127.0.0.1:6379> lrange li 0 -1
1) "5"
2) "3"
3) "2"
4) "1"
5) "6"
# 可以看到原来的数据没发生改变
数据库操作
select index
# 切换数据库(redis提供16个数据库,编号为0~15)
move k db
# 将当前db的key移动到别的db里
dbsize
# 查看当前db的key数量
flushdb
# 清空当前db
flushall
# 清空所有db
其他操作
config get *
# 查看配置
info命令
# 查看当前redis中的所有信息
time命令
# 输出当前时间戳
ping message
# 测试客户端是否连通,可以发送想要返回的信息,默认message为pong
echo message
# 输出语句

基本类型介绍

string
  • 存储格式:一个空间保存一个数据,通常存储字符串,如果字符串是整数形式,则可以进行数字操作
基本操作
set key value
# 添加/修改数据,如果key已存在则会被覆盖(即修改)
get key
# 获取数据,如果不存在则返回空
del key
# 删除数据,删除成功返回1,否则返回0

举例:

127.0.0.1:6379> set aaa 1
OK
127.0.0.1:6379> get aaa
"1"
127.0.0.1:6379> get bbb
(nil)
127.0.0.1:6379> del aaa
(integer) 1
127.0.0.1:6379> del aaa
(integer) 0
# 已经被删除,不存在的数据删除失败
其他操作
mset k1 v1 k2 v2 ...
# 添加/修改多个数据
mget k1 k2 ...
# 获取多个数据
strlen k
# 获取数据长度(字符串的长度)
append k v
# 将信息追加到尾部,如果不存在则创建
  • 举例:
127.0.0.1:6379> mset a 1 b 2
OK
127.0.0.1:6379> mget a b
1) "1"
2) "2"
127.0.0.1:6379> strlen a
(integer) 1
127.0.0.1:6379> append a 2
(integer) 2
# 返回的是新的数据的长度
127.0.0.1:6379> get a
"12"
127.0.0.1:6379> append c xyz
(integer) 3
# 不存在就创建
127.0.0.1:6379> get c
"xyz"

注:
在多数据操作的情况下,使用mset/mget可以减少与数据库的交互,因此相比于set/get更加高效

数值操作
incr k
# 将数值自增1,返回自增后的结果,如果字符串不是整数则会报错
incrby k increment
# 将数值增加指定值,必须是整数
incrbyfloat key increment
# 将数值增加指定值,可以是浮点数
decr k
# 自减
decrby k increment
# 自减指定值

举例:

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> incr a
(integer) 2
127.0.0.1:6379> incrby a 10
(integer) 12
127.0.0.1:6379> incrby a 1.1
(error) ERR value is not an integer or out of range
# incrby传入非整数则会报错
127.0.0.1:6379> incrbyfloat a 1.1
"13.1"
127.0.0.1:6379> decr a
(error) ERR value is not an integer or out of range
# 自增自减的对象也必须是整数
127.0.0.1:6379> incrbyfloat a -0.1
"13"
# 传入负数实现减法
127.0.0.1:6379> decr a
(integer) 12

注:
字符串作为数值操作机制——redis内部默认存储是字符串,如果遇到数值操作,则会将数据先转成数值类型再进行计算
数值计算时,如果数据超过了redis的数值返回则会报错,redis中数值的上限是9223372036854775807(即2^63-1)

有效期操作
setex k seconds v
psetex k milliseconds v

举例:

127.0.0.1:6379> setex a 5 xxx
OK
# 设置5秒后过期
127.0.0.1:6379> get a
"xxx"
127.0.0.1:6379> get a
(nil)
# 5秒后数据不存在
127.0.0.1:6379> setex a 0 xxx
(error) ERR invalid expire time in setex
# 设置的有效期必须是正整数
127.0.0.1:6379> setex a 1.1 xxx
(error) ERR value is not an integer or out of range
127.0.0.1:6379> psetex a 1000 xxx
OK
# 设置1000毫秒后过期
127.0.0.1:6379> get a
(nil)
注意事项
  • 操作返回值:表示执行结果是否成功:1-成功,0-失败;表示运行结果
  • 数据获取失败:返回空(nil)
  • 数据最大存储量:512MB
  • 数值计算最大范围:2^63-1
命名规范

redis中存储的如热点数据一般是来自mysql等数据库当中,因此一般对于key的命名可以遵守如下规范:

表名:主键名:主键值:字段名

举例:

test:id:12938274981:name
test:id:12938274981:age
hash
基本操作
hset k field v
# 设置k的某个字段值(字段的值为字符串类型)
hget k field
# 获取k的某个字段值
hgetall k
# 获取k中所有字段的值
hdel k field1 field2 ...
# 删除某个字段值
hmset k field1 v1 field2 v2 ...
# 设置多个字段
hmget k field1 field2 ...
# 获取多个字段
hlen k
# 获取字段数量
hexists k field
# 查看是否存在某个字段
hkeys k
# 查看所有字段名
hvals k
# 查看所有字段值
hsetnx k field v
# 字段不存在则插入,否则不操作

举例:

127.0.0.1:6379> hset book name xxx
(integer) 1
127.0.0.1:6379> hget book name
"xxx"
127.0.0.1:6379> hset book price 100
(integer) 1
127.0.0.1:6379> hgetall book
1) "name"
2) "xxx"
3) "price"
4) "100"
# 依次按k、v格式返回
127.0.0.1:6379> hdel book name price
(integer) 2
# 返回删除成功的数量
127.0.0.1:6379> hget book name
(nil)
127.0.0.1:6379> hmset book name yyy price 10
OK
127.0.0.1:6379> hmget book name price
1) "yyy"
2) "10"
127.0.0.1:6379> hlen book
(integer) 2
127.0.0.1:6379> hexists book name
(integer) 1
127.0.0.1:6379> hkeys book
1) "price"
2) "name"
127.0.0.1:6379> hvals book
1) "10"
2) "yyy"
127.0.0.1:6379> hsetnx book num 10
(integer) 1
127.0.0.1:6379> hsetnx book num 100
(integer) 0
# 数据存在则操作失败
127.0.0.1:6379> hget book num
"10"
数值操作
hincrby k field increment
# 添加指定值
hincrbyfloat k field increment
注意事项
  • hash中所有的字段值都是字符串类型,不存在嵌套现象
  • 每个hash可以存2^32-1个键值对
  • hgetall可以获取所有的键值对,但如果键值对过多,那么就会导致效率偏低,成为性能瓶颈
  • string与hash相比,string倾向于读数据多的场景,而hash倾向于改数据多的场景
list
基本操作
lpush k v1 v2 ...
# 往列表左边插入数据
rpush k v1 v2 ...
# 往列表右边插入数据
lrange k start stop
# 获取指定范围的数据
lindex k index
# 获取指定索引的数据
llen k
# 获取列表长度
lpop k
# 从左边弹出一个数据
rpop k
# 从右边弹出一个数据
  • 举例:
127.0.0.1:6379> lpush li 1 2 3
(integer) 3
# 依次往左边插入数据,所以结果是3,2,1
127.0.0.1:6379> rpush li 4 5
(integer) 5
# 从右边插入数据
127.0.0.1:6379> lpush li 0
(integer) 6
127.0.0.1:6379> lrange li 0 -1
1) "0"
2) "3"
3) "2"
4) "1"
5) "4"
6) "5"
# 查看全部数据,可以看到从左边和右边插入的区别
127.0.0.1:6379> lindex li 2
"2"
# 获取list中第3个位置的数据
127.0.0.1:6379> llen li
(integer) 6
127.0.0.1:6379> lpop li
"0"
# 将最左边的数据弹出
127.0.0.1:6379> lrange li 0 -1
1) "3"
2) "2"
3) "1"
4) "4"
5) "5"
127.0.0.1:6379> rpop li
"5"
  • 等待获取:
blpop k1 k2 ... timeout
# 规定时间内,从左到右如果有一个list里有数据就lpop一个出来,否则等待,适合一些生产者消费者模型的场景
brpop k1 k2 ... timeout
  • 举例:
127.0.0.1:6379> blpop li 5
1) "li"
2) "3"
# 5s内如果li中有数据,则左弹出一个,返回结果是弹出数据的list对应的key,和弹出的值
127.0.0.1:6379> blpop li1 li 5
1) "li"
2) "2"
# 如果li1或li里有一个有数据,就弹出
127.0.0.1:6379> blpop li1 li2 li3 3
(nil)
(3.08s)
# 超时就返回空
指定删除操作
lrem k count v
# 删除值为v的,删除count个
  • 举例:
127.0.0.1:6379> lpush li 1 2 1 3 2 1
(integer) 6
127.0.0.1:6379> lrem li 2 1
(integer) 2
# 删除2个1
127.0.0.1:6379> lrange li 0 -1
1) "2"
2) "3"
3) "2"
4) "1"
注意事项
  • list中保存的也是字符串类型的数据
  • list最大长度不超过2^32-1个
  • list支持负值索引(例如-1就是最后一个数据)
set
特点
  • 能够存储大量的数据,并且查询效率高
  • 和hash的存储结构一样,但只存储键,而值全部是空
基本操作
sadd k member1 member2 ...
# 往集合添加数据
smembers k
# 获取集合全部数据
srem k member1 member2 ...
# 删除数据
scard k
# 获取集合数据总量
sismember k member
# 判断集合中是否存在指定数据
  • 举例:
127.0.0.1:6379> sadd set abc 123 aaa
(integer) 3
127.0.0.1:6379> smembers set
1) "aaa"
2) "123"
3) "abc"
127.0.0.1:6379> scard set
(integer) 3
127.0.0.1:6379> sismember set abc
(integer) 1
127.0.0.1:6379> sismember set 111
(integer) 0
127.0.0.1:6379> srem set abc 111
(integer) 1
127.0.0.1:6379> smembers set
1) "aaa"
2) "123"
随机获取/随机弹出
srandmember k count
# 从集合中随机取一定量的数据
spop k
# 随机从集合中弹出一个数据
  • 举例:
127.0.0.1:6379> sadd s1 abc aaa bbb ccc 111 222 333
(integer) 7
127.0.0.1:6379> srandmember s1
"aaa"
# 默认随机获取1个
127.0.0.1:6379> srandmember s1 3
1) "aaa"
2) "111"
3) "ccc"
# 随机获取3个
127.0.0.1:6379> scard s1
(integer) 7
127.0.0.1:6379> spop s1
"aaa"
# 随机弹出1个
127.0.0.1:6379> scard s1
(integer) 
# 可以看到集合数量-1
127.0.0.1:6379> spop s1 3
1) "abc"
2) "222"
3) "333"
127.0.0.1:6379> scard s1
(integer) 3
集合运算(交集/并集/差集)
sinter k1 k2 ...
# 求集合的交集
sunion k1 k2 ...
# 求集合的并集
sdiff k1 k2 ...
# 求集合的差集
sinterstore destination k1 k2 ...
# 求集合的交集,并将结果存到指定的集合中
sunionstore destination k1 k2 ...
sdiffstore destination k1 k2 ...
smove source destination member
# 将集合中某个数据移到其他集合中
  • 举例:
127.0.0.1:6379> sadd ss1 aaa bbb ccc 111
(integer) 4
127.0.0.1:6379> sadd ss2 aaa abc ddd 222
(integer) 4
127.0.0.1:6379> sadd ss3 111 222
127.0.0.1:6379> sinter ss1 ss2
1) "aaa"
# 获取ss1、ss2的交集
127.0.0.1:6379> sinter ss1 ss2 ss3
(empty list or set)
# 获取ss1、ss2、ss3的交集
127.0.0.1:6379> sunion ss1 ss2
1) "aaa"
2) "abc"
3) "111"
4) "ccc"
5) "bbb"
6) "ddd"
7) "222"
127.0.0.1:6379> sdiff ss1 ss2
1) "bbb"
2) "ccc"
3) "111"
# 计算ss1-ss2的差集(ss1集合减去ss1和ss2的并集)
127.0.0.1:6379> sdiff ss2 ss1
1) "ddd"
2) "abc"
3) "222"
127.0.0.1:6379> sinterstore ss3 ss1 ss2
(integer) 1
# 将ss1和ss2的交集存到ss3中,ss3如果之前有值则会被覆盖
127.0.0.1:6379> smembers ss3
1) "aaa"
127.0.0.1:6379> smove ss2 ss3 ddd
(integer) 1
# 将ss2中的ddd移动到ss3中
127.0.0.1:6379> smembers ss2
1) "aaa"
2) "222"
3) "abc"
127.0.0.1:6379> smembers ss3
1) "ddd"
2) "aaa"
注意事项
  • 根据集合的性质,set类型中的值都是不允许重复的,因此如果添加已存在的值,则会失败
  • set虽然和hash存储结构相同,但value部分是无法使用的
sorted_set
类型结构

相比于set,sorted_set是在其基础上添加了可排序字段

基本操作
zadd k score1 member1 score2 member2 ...
# 往集合里添加数据,并设置数据的score
zrange k start stop [withscores]
# 获取集合中指定范围的数据,如果后面加上withscores,则会将score值一起输出
zrevrange k start stop [withscores]
# 逆序获取集合中指定范围的数据,...
zrem k member1 member2 ...
# 删除集合中数据
  • 举例:
127.0.0.1:6379> zadd zset 100 aaa 200 bbb 10 ccc -1 ddd
(integer) 4
127.0.0.1:6379> zrange zset 0 -1
1) "ddd"
2) "ccc"
3) "aaa"
4) "bbb"
# zrange是默认值越小的排越前面,zrevrange反之
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "ddd"
2) "-1"
3) "ccc"
4) "10"
5) "aaa"
6) "100"
7) "bbb"
8) "200"
# 将对应的score也输出
127.0.0.1:6379> zrem zset aaa bbb
(integer) 2
127.0.0.1:6379> zrange zset 0 -1
1) "ddd"
2) "ccc"
条件操作
zrangebyscore k min max [withscores] [limit] [offset]
# 获取指定score范围的数据
zrevrangebyscore k min max [withscores] [limit] [offset]
zremrangebyrank k start stop
# 删除排名在指定范围内的数据
zremrangebyscore k min max
# 删除score在指定范围内的数据
  • 举例:
127.0.0.1:6379> zadd zset1 100 aaa 10 bbb 50 ccc -1 ddd 0 eee 10 fff 80 ggg
(integer) 7
127.0.0.1:6379> zrangebyscore zset1 0 100 withscores
 1) "eee"
 2) "0"
 3) "bbb"
 4) "10"
 5) "fff"
 6) "10"
 7) "ccc"
 8) "50"
 9) "ggg"
10) "80"
11) "aaa"
12) "100"
# 获取score在[0, 100]的数据
127.0.0.1:6379> zrangebyscore zset1 0 100 limit 1 3
1) "bbb"
2) "fff"
3) "ccc"
# 获取score在[0,100]的数据,从第二个开始取3个
127.0.0.1:6379> zremrangebyrank zset1 0 2
(integer) 3
# 删除排名在0~2的
127.0.0.1:6379> zremrangebyscore zset1 0 100
(integer) 4
# 删除score在0到100的
集合操作
zcard key
# 获取集合的数据量
zcount key min max
# 在指定score范围获取集合的数据量
zinterstore destination numkeys k1 k2 ...
# 将集合的交集存到指定的集合,numkeys指定集合的数量
zunionstore destination numkeys k1 k2 ...
  • 举例:
127.0.0.1:6379> zadd zs 100 aaa 20 bbb 1 ccc -5 ddd 0 eee
(integer) 5
127.0.0.1:6379> zadd zs1 30 aaa 1000 bbb 5 abc
(integer) 3
127.0.0.1:6379> zcard zs
(integer) 5
127.0.0.1:6379> zcount zs 0 100
(integer) 4
127.0.0.1:6379> zinterstore zs2 2 zs zs1
(integer) 2
# 指定两个集合zs、zs1取交集,存到zs2中
127.0.0.1:6379> zrange zs2 0 -1 withscores
1) "aaa"
2) "130"
3) "bbb"
4) "1020"
# 可以看出新的集合中对应值的score是将集合里的score进行叠加的
获取索引/score操作
zrank k member
# 获取对应值的排名
zrevrank k member
zscore k member
# 获取对应值的score
zincrby k increment member
# 修改score值
  • 举例:
127.0.0.1:6379> zadd ss 100 aaa 10 bbb -10 ccc 0 ddd
(integer) 4
127.0.0.1:6379> zrank ss aaa
(integer) 3
# 查看aaa的排名
127.0.0.1:6379> zrevrank ss aaa
(integer) 0
127.0.0.1:6379> zscore ss aaa
"100"
# 查看aaa的score
127.0.0.1:6379> zincrby ss 10 aaa
"110"
# 修改aaa的score
127.0.0.1:6379> zscore ss aaa
"110"
注意事项
  • score保存的数据存储空间是64位,所以是有范围限制的(-2^63 ~ 2^63-1)
  • socre也可以是double类型的值
  • sorted_set底层还是基于set结构,因此数据不能重复,如果出现相同的数据,新的将会覆盖旧的

发布订阅模式

设置发布者发布消息,而订阅则可以接收消息,redis客户端可以订阅任意数量的频道

基本操作
subscribe channel channel1 channel2 ...
# 订阅频道
psubsrcibe pattern pattern1 pattern2 ...
# 订阅指定模式的频道(即支持通配符*/?/[])
publish channel message
# 往指定频道发消息
unsubscribe channel1 channel2 ...
# 退订频道
punsubscribe pattern1 pattern2 ...
# 退订指定模式的频道
示例
  • 订阅者:
127.0.0.1:6379> subscribe channel aaa bbb
Reading messages... (press Ctrl-C to quit)
# 客户端订阅aaa和bbb频道
  • 发送端:
127.0.0.1:6379> publish bbb xyz
(integer) 1
# 往bbb频道发送消息

此时会在订阅者出接收到对应的消息

高级数据类型

bitmaps

可以以位为单位进行数据的操作,相当于二进制数组

基本操作
getbit k offset
# 获取指定位上的数据
setbit k offset v
# 设置指定位上的数据,value只能是0或1
  • 举例:
127.0.0.1:6379> setbit a 3 1
(integer) 0
# 设置第三位为1
127.0.0.1:6379> getbit a 3
(integer) 1
127.0.0.1:6379> setbit a 3 0
(integer) 1
# setbit返回值时当前位之前的值
127.0.0.1:6379> setbit a 3 1
(integer) 0
127.0.0.1:6379> getbit a 10
(integer) 0
# 没设置过的位置默认是0
127.0.0.1:6379> get a
"\x10"
# 查看二进制格式
127.0.0.1:6379> setbit a 5 1
(integer) 0
127.0.0.1:6379> get a
"\x14"
  • 优点:可以操作最小的单位——位,极大地利用了内存,在一些场景如布隆过滤器中很实用
  • 缺点:在进行高位操作时效率偏低,所以对于连续存储的大数字,建议将不必要的部分减去以后再存,例如编号从1000000开始递增的数据,那么建议存储数据前先减去1000000再进行存储
位运算操作
bitop op destk k1 k2 ...
# 对指定key进行交(and)、并(or)、非(not)、异或(xor)等位操作,并将结果存到destkey当中
bitcount k [start end]
# 统计key中1的数量
  • 举例:
127.0.0.1:6379> setbit a 3 1
(integer) 0
127.0.0.1:6379> setbit a 5 1
(integer) 0
127.0.0.1:6379> setbit b 4 1
(integer) 0
127.0.0.1:6379> setbit b 5 1
(integer) 0
127.0.0.1:6379> bitop and c a b
(integer) 1
# 对a和b进行与运算,并将结果存到c
127.0.0.1:6379> getbit c 5
(integer) 1
# 查看运算结果
127.0.0.1:6379> getbit c 4
(integer) 0
127.0.0.1:6379> bitop or d a b
(integer) 1
127.0.0.1:6379> get d
"\x1c"
# 查看二进制结果
127.0.0.1:6379> bitcount d
(integer) 3
# 获取d里1的数量
hyperloglog

hyperloglog专门用于统计基数(不重复数据的数量),虽然使用之前的set也可以实现,但是set需要空间来存储每个数据,而hyperloglog只记录数量而不记录数据(由于不记录数据,所以数量也是基于某个算法(loglog算法)来实现的,具有一定的偏差)

基本操作
pfadd k element1 element2 ...
# 添加数据
pfcount k1 k2 ...
# 统计数据
pfmerge destkey k1 k2 ...
# 合并数据
  • 举例:
127.0.0.1:6379> pfadd p 1 2 1 3 4 2 1
(integer) 1
127.0.0.1:6379> pfadd q 1 2 5 1
(integer) 1
127.0.0.1:6379> pfcount p
(integer) 4
# p中只有1/2/3/4四种数据
127.0.0.1:6379> pfcount q
(integer) 3
127.0.0.1:6379> pfcount p q
(integer) 5
# p+q总共有1/2/3/4/5
127.0.0.1:6379> pfmerge pq p q
OK
127.0.0.1:6379> pfadd pq 1 3 10
(integer) 1
127.0.0.1:6379> pfcount pq
(integer) 6
# pq里有1/2/3/4/5/10
注意事项
  • hyperloglog只是进行基数统计,只记录了数量,而不像集合有记录数据
  • 其核心是基数估算算法,所以结果存在一定的误差
  • 消耗空间少
  • pfmerge合并后的数据占用空间为12k,无论之间数据量多少
GEO

专门用于坐标、地理位置的计算

基本操作
geoadd k longtitude1 latitude1 member1 longtitude2 latitude2 member2 ...
# 添加坐标点
geopos k member1 member2 ...
# 获取坐标点
geodist k member1 member2 [unit(单位-m, km, ft, mi)]
# 计算坐标点距离
  • 举例:
127.0.0.1:6379> geoadd map 1 1 a 2 2 b 1 2 c
(integer) 3
# 添加3个点,key名称map可以理解成一个地图,而这些点都描绘在这张地图上
127.0.0.1:6379> geopos map a b c
1) 1) "0.999999940395355"
   2) "0.999999459142977"
2) 1) "2.000002562999725"
   2) "2.000000185646549"
3) 1) "0.999999940395355"
   2) "2.000000185646549"
# 查看3个点的对应坐标
127.0.0.1:6379> geodist map a c m
"111226.3808"
# 计算a和c点间的距离,单位是米
127.0.0.1:6379> geodist map a b km
"157.2701"
# 以千米为单位
127.0.0.1:6379> type map
zset
# 可以看到是基于zset实现的
127.0.0.1:6379> zrange map 0 -1 withscores
1) "a"
2) "3377822707026402"
3) "c"
4) "3378080408407459"
5) "b"
6) "3378191666521995"
范围计算
georadius k lontitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
# 获取key中符合指定坐标范围内的数据
georadiusbymember k member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
# 获取key中符合指定点范围内的数据
geohash k member1 member2 ...
# 获取指定点对应的坐标哈希值
  • 举例:
127.0.0.1:6379> geoadd m 1 1 a 2 2 b 3 3 c 1 2 d 2 1 e 2 3 f
(integer) 6
127.0.0.1:6379> georadius m 2.5 3 200 km withdist
1) 1) "f"
   2) "55.5366"
2) 1) "c"
   2) "55.5369"
3) 1) "b"
   2) "124.3307"
# 查看m里符合坐标(2.5, 3)半径200km内的所有数据,并打印对应的距离
127.0.0.1:6379> georadiusbymember m b 150 km withcoord
1) 1) "d"
   2) 1) "0.999999940395355"
      2) "2.000000185646549"
2) 1) "b"
   2) 1) "2.000002562999725"
      2) "2.000000185646549"
3) 1) "f"
   2) 1) "2.000002562999725"
      2) "3.000000912150107"
4) 1) "e"
   2) 1) "2.000002562999725"
      2) "0.999999459142977"
# 查看m里在b点周围150km内的数据,并打印坐标
127.0.0.1:6379> geohash m a b
1) "s00twy01mt0"
2) "s037ms06g70"
# 获取a、b点的哈希值

服务器常用配置

基础配置
daemonize yes|no
# 设置服务器以守护进程方式运行
bind 127.0.0.1
# 绑定主机地址
port 6379
# 绑定端口号
databases 16
# 设置数据库数量
日志配置
loglevel debug|verbose|notice|warning
# 越右边的选项日志信息越少,开发时建议verbose,生产时notice即可
logfile xxx.log
# 日志文件名
客户端配置
maxclients 0
# 同一时间最大客户端连接数,默认无限制(0),当达到上限后,会关闭新的连接
timeout 300
# 客户端闲置等待最大时长,当超过指定时间后,将会关闭连接,设置0则代表不关闭连接
公共文件导入配置
include xxx.conf
# 导入其他文件的配置信息,可以将公共配置放在其他文件当中

持久化

概念

通过永久性的存储介质(如磁盘)对数据进行保存,并在特定时间内将保存的数据进行恢复的工作机制
注:
redis作为一个内存数据库,会在启动时将数据库中的所有key都加载到内存当中,如果没有持久化的支持,那么每当关闭redis,数据库就相当于被清空,而通过持久化,可以保证之前的数据还存在,并且在开启服务后会自动将之前的数据都载入内存

在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别。当物理内存用完时,Redis可以将一些很久没用到的value交换到磁盘。Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。这种特性使得Redis可以保持超过其机器本身内存大小的数据。
参考:https://www.cnblogs.com/middleware/articles/9052394.html

持久化方式
  • 保存数据的结果,类似于快照,直接存储数据,存储格式简单
  • 保存操作的过程,类似于操作日志,存储格式复杂
RDB

RDB是基于存储数据结果的方式,常用的保存方式有两种——立刻保存和后台保存

立刻保存

手动启动RDB命令:

save
# 手动对数据进行持久化保存

执行完可以发现在配置的dir目录下生成了一个dump.rdb文件

  • save指令特点:redis是单线程的,而save指令会阻塞后面的指令,因此线上环境不建议使用
save相关文件配置
dbfilename xxx.rdb  持久化数据的文件名,一般设置为dump-端口号.rdb
dir xxx/data 存储rdb文件的路径,一般设置子目录名称为data
rdbcompression yes|no  存储本地数据库时是否压缩,默认为yes,采用lzf压缩,如果想要节省CPU运行时间,可以设置为no,但文件会变大
rdbchecksum yes|no 是否对rdb文件进行格式校验,默认开启,关闭的话可以节约10%左右损耗,但数据有损坏的风险
后台保存

由于save进行保存时会阻塞其他操作,因此对于并发量大的时候,不建议使用save保存,而推荐使用后台保存的方式,后台保存命令:

bgsave
# 后台进行保存操作,但不是立即执行
  • bgsave工作原理:bgsave是对save阻塞问题进行的优化,其会在接收到指令时调用fork函数,在子进程中进行保存操作,并在保存完成后返回成功提示,redis内部的RDB操作基本都是采用bgsave的方式
bgsave相关文件配置
dbfilename xxx.rdb
dir xxx/data
rdbcompression yes|no
rdbchecksum yes|no
stop-writes-on-bgsave-error yes|no  后台保存时如果出现错误,是否停止保存操作,默认开启
save second changes 设置指定时间内变化的key数量达到指定量就进行保存操作
  • 自动保存配置:
save second changes 设置指定时间内变化的key数量达到指定量就进行保存操作

举例:

save 900 1
# 900s内有1个key变化就进行保存
save 300 10
save 60 10000
# save配置可以设置很多个,最好按时间递减顺序进行配置

save配置后台用的是bgsave

  • 特殊启动方式:
debug reload
# 重启服务器
shutdown save
# 在关闭服务器时保存指定数据
RDB优缺点

优点:

  • RDB文件是一个紧凑压缩的二进制文件,存储效率高
  • RDB存储的是在某个时间点的数据快照,十分适合数据备份、全量复制等场景
  • RDB恢复数据的速度比AOF要快

缺点:

  • RDB无法做到实时持久化,宕机时可能会丢失较大的时间段内的数据
  • 后台保存时需要创建子进程,会消耗性能
  • redis的多个版本中,rdb文件格式没有全部统一,可能会出现不兼容的情况(解决:先通过兼容的版本读取数据,再进行数据转移工作)
  • 数据存储量大时,IO性能下降,效率较低
AOF

以独立日志的方式记录每次的写命令,重启时再重新执行AOF文件中的命令,从而恢复数据,与RDB相比,AOF就是专门记录数据产生的过程。AOF主要是解决的数据持久化实时性的文件,也是目前redis持久化的主流方式

AOF写入操作过程

执行写操作->将写命令添加到AOF缓冲区->将缓存区的命令同步到AOF文件当中

AOF三种策略
  • always:每次写入操作都同步到AOF文件当中,不存在误差,但性能较低
  • everysec:每秒将缓冲区指令同步到AOF文件中,是结合准确性和性能的较好方案,也会默认配置,但系统如果宕机,可能会损失1s内的数据
  • no:由操作系统去控制同步到AOF文件的周期,过程不可控
AOF配置
appendonly yes|no   是否开启AOF持久化,默认关闭
appendfsync always|everysec|no  设置AOF写操作的策略
appendfilename filename AOF持久化文件名,默认为appendonly.aof,建议配置为appendonly-端口号.aof
dir AOF持久化文件的保存路径
AOF重写

随着写操作的增加,文件会不断增大,因此可以基于AOF的重写机制来压缩文件体质,主要是对若干命令的执行结果转化成最终结果数据所对应的指令进行记录(例如执行了3次+1操作,就压缩成1次+3操作)

重写优势
  • 提高磁盘空间利用率
  • 提高持久化效率,降低写的时间
  • 提高数据恢复时的效率
重写规则
  • 进程内已超时的数据不写入文件
  • 忽略无效指令,如删除指令
  • 将对同一数据的多条写命令合并为一条,例如多次字符串赋值,则只记录最后一个
重写方式
  • 手动重写命令:
bgrewriteaof
# bg开头可以看出是在后台进行重写操作
  • bgrewriteaof原理:参考bgsave

  • 自动重写配置:

auto-aof-rewrite-min-size size
# 超过最小尺寸时触发自动重写操作
auto-aof-rewrite-percentage percentage
# 超过基础尺寸多少百分比时触发
  • 自动重写配置参考参数(可以通过info persistence命令查看):
aof_current_size    当前aof尺寸
aof_base_size   基础尺寸
RDB/AOF对比
  • RDB:占用空间小,存储速度慢,恢复速度快,易丢失数据,资源消耗高,启动优先级低
  • AOF:占用空间大,存储速度快,恢复速度慢,根据策略决定丢失数据情况,资源消耗低,启动优先级高
总结

对数据要求很高,建议使用AOF,如果追求大量数据集的恢复速度,建议使用RDB,备份数据也建议用RDB

事务

redis的事务就是一个命令指令的任务队列,将多条命令包装成一个整体,依次按照添加的顺序执行

事务操作
相关命令
multi
# 开启事务,该命令执行后,后续的命令都会添加到事务当中
exec
# 执行事务,设定事务的结束位置,并执行事务(在没有执行exec命令前,所有事务的命令都不会执行),和multi结合使用
  • 举例:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set xxx 1
QUEUED
# 将任务添加到队列当中
127.0.0.1:6379> get xxx
QUEUED
127.0.0.1:6379> exec
1) OK
2) "1"
# 执行exec以后才会将任务队列当中的内容执行
  • 取消事务:
discard
# 在multi开启事务之后,如果希望取消事务,则可以使用该命令(要在exec之前执行,否则执行exec时已经执行事务操作了)
  • 举例:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set xxx 2
QUEUED
127.0.0.1:6379> discard
OK
# 取消事务
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
# 可以看到事务被取消了
注意事项
  • 定义事务的语句中,如果存在语法错误,则事务中所有命令都不会执行
  • 如果不存在语法错误,但是无法正确运行(例如对list使用get操作),那么正确的命令将会正常执行,而错误的则不会执行
  • 对于已经执行完毕的命令,对应的数据不会进行回滚

示例:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set yyy 1
QUEUED
127.0.0.1:6379> sda
(error) ERR unknown command 'sda'
# 错误的命令
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 整体事务都不会执行
127.0.0.1:6379> get yyy
(nil)
# 可以看出set yyy没有执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set yyy 1
QUEUED
127.0.0.1:6379> lpush ll 1 2 3
QUEUED
127.0.0.1:6379> get ll
QUEUED
# 对list类型使用get操作会出错
127.0.0.1:6379> set zzz 2
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 3
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
4) OK
# 可以看出返回每一个命令的执行结果,即使出错也不影响后面的执行
127.0.0.1:6379> get zzz
"2"
# 可以看到出错位置之后的命令正常执行
手动回滚实现

由于redis事务即使中间执行失败,也不会进行回滚操作,因此需要我们自己去实现回滚操作,实现思路如下:

  • 记录事务操作会影响的数据在执行事务之前的状态
  • 设置指令恢复所有被修改的项
监视锁
watch k1 k2 ...
# 对key添加监视锁,在执行exec前如果某个key发生了变化,则终止事务执行
unwatch
# 取消对所有key的监视
  • 举例:
127.0.0.1:6379> watch aaa
OK
# 监视aaa
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set aaa 100
QUEUED
127.0.0.1:6379> set aaa 200
OK
# 在另一个客户端进行的操作
127.0.0.1:6379> exec
(nil)
# 发现事务不执行了
127.0.0.1:6379> get aaa
"200"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> watch aaa
(error) ERR WATCH inside MULTI is not allowed
# watch必须要在事务开启前执行
分布式锁

在分布式场景下,可以通过setnxdel来实现分布式锁的控制,命令如下:

setnx k v
# 通过setnx设置一个公共锁,如果key存在则设置失败,不存在则设置成功,对于设置成功的允许进行操作,失败的则需要进行排队等待,操作完成后通过del释放锁
  • 举例:
127.0.0.1:6379> setnx lock 1
(integer) 1
# 设置锁,如果返回1代表则允许进行接下来的操作,否则等待
127.0.0.1:6379> set aaa 1
OK
127.0.0.1:6379> del lock
(integer) 1
# 释放锁
死锁

由于分布式锁由用户来进行加锁和解锁操作,所以就可能存在未解锁的情况,导致死锁的产生,因此一种有效的解决方法就是给锁设置有效期,举例:

127.0.0.1:6379> setnx lock 1
(integer) 1
127.0.0.1:6379> expire lock 5
(integer) 1
# 上锁后给锁设置有效期

删除/驱逐策略

数据特征

由于redis是内存数据库,所有的数据都存在内存当中,而内存中的数据则可以通过ttl命令查看数据的状态,其中三种结果状态如下:

>=0的数值   具有时效性的数据
-1  永久有效的数据
-2  已过期、被删除、未定义的数据
数据删除策略

针对的过期数据,有以下几种删除策略:

  • 定时删除
  • 惰性删除
  • 定期删除
过期数据的底层存储结构

删除策略的目标:如果过期数据立刻删除,那么就需要对数据是否有效进行实时监控,对CPU要求较高;如果不立刻删除,过期数据堆积多了又会导致内存过大,因此目标就是要在内存和CPU之间的平衡

三种策略
定时删除

创建一个定时器,当key到达过期时间时,定时器任务立刻对key进行删除

  • 优点:节省内存,立刻释放不需要的内存占用
  • 缺点:CPU压力大,会影响服务的响应时间和指令吞吐量
惰性删除

如果数据到达过期时间时不做处理。当访问该数据时,如果没过期则返回数据,如果过期则执行删除操作,并返回空

  • 优点:节省CPU性能,当发现需要删除的时候才删除
  • 缺点:内存压力大,可能有大量的内存浪费
定期删除

周期性轮询redis库中的时效性数据,采用随机抽取策略,利用过期数据占比的方式控制删除频度,可自定义检测频度

  • 优点:内存定时清理,并且CPU开销相对较小,
  • 缺点:依然存在内存的浪费,以及每秒需要花费一定的CPU资源去进行清理工作
逐出算法

redis在执行每条命令前,会先通过freeMemoryIfNeeded()函数检测内存是否充足,如果内存不满足新加入数据的最低存储要求,redis就会临时删除一些数据从而提供新的命令所需的空间(如果删除一次依然空间不够,则会反复执行,如果对全部数据尝试完毕后依然空间不够,则会抛出OOM错误),而清理数据的方式就是对应的逐出算法。

逐出算法相关配置
maxmemory   最大可使用内存比例,默认为0,表示不限制,一般设置在50%以上
maxmemory-samples   每次选取待删除数据的个数
maxmemory-policy    达到内存最大限制后,对挑选出待删除的数据进行删除的逐出算法
逐出算法类别(maxmemory-policy选项)

检测易失数据:

  • volatile-lru:将最近最少使用的逐出(最长时间没有用)
  • volatile-lfu:选择使用次数最少的逐出(频率最少)
  • volatile-ttl:选择将过期的逐出
  • volatile-random:随机选择逐出

检测全库数据(只有易失数据才有ttl,所以这里没有allkeys-ttl选项):

  • allkeys-lru:挑选最近最少使用的逐出
  • allkeys-lfu:挑选使用次数最少的逐出
  • allkeys-random:随机逐出

放弃数据驱逐:

  • no-enviction:禁止数据驱逐
逐出算法配置依据

可以通过info命令查看缓存的命中率和丢失了,从而根据需求调整适合的结果,举例:

127.0.0.1:6379> info stats
# Stats
total_connections_received:7
total_commands_processed:305
instantaneous_ops_per_sec:0
total_net_input_bytes:10387
total_net_output_bytes:35647152
instantaneous_input_kbps:0.00
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:6
evicted_keys:0
keyspace_hits:115
# 命中次数
keyspace_misses:15
# 丢失次数
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:32236
migrate_cached_sockets:0

主从复制

主从复制作用
  • 读写分离:只有master能写,slave负责读,提高服务器的读写负载能力
  • 负载均衡:基于主从结构,配合读写分离,多个slave节点分担读操作的负载
  • 故障恢复:当master节点出问题时,可以选一个slave提供master服务,实现快速的故障恢复
  • 数据冗余:实现数据热备份,是持久化以外的另一种数据冗余方式
  • 高可用基石:基于主从结构,可以构建哨兵模式以及redis集群
工作流程

分为三个阶段:

  • 建立连接阶段
  • 数据同步阶段
  • 命令传播阶段
主从连接阶段
主从连接方式
  • 客户端命令:
slaveof masterip masterport
# 客户端发送命令使当前服务器作为从节点连接master
  • 服务器启动参数配置:
redis-server -slaveof masterip masterport
# 配置服务器启动时作为某个master的从节点
  • 服务器文件配置:
slaveof masterip masterport
# 和上一个等价
客户端断开连接

命令:

slaveof no one
连接master密码配置
  • master端配置文件:
requirepass password
# master端连接密码
  • master端命令配置密码:
config set requirepass password
# master客户端设置密码命令
config get requirepass
# master客户端获取密码
  • slave端配置文件:
masterauth password
# 配置master端的密码
  • slave端命令配置密码:
auth password
  • slave端启动配置密码:
redis-cli -a password
数据同步阶段
注意事项
  • 当master数据量很大时,应避开流量高峰期进行数据同步,避免造成master阻塞
  • 复制缓冲区大小如果设置不合理,会导致数据溢出,例如复制时间过长,当进行部分复制以后发现已经存在数据丢失的情况,就会再次进行全量复制(复制缓冲区大小配置:repl-backlog-size 1mb
  • master占用内存比例不应过大,应该留下30%~50%内存用于执行bgsave命令和创建复制缓冲区
  • 当数据同步时,为了避免全量复制、部分复制造成服务器阻塞等情况,建议该期间关闭对外服务(配置:slave-serve-stale-data yes
  • 当slave过多时,建议采用树状结构,中间的节点既能作为master,也是slave,但树的深度不能过大,否则容易导致深度高的slave节点与master间的数据同步延迟
命令传播阶段
  • 当master状态发生了修改(进行了写操作)时,会导致主从状态不一致,因此需要让主从进行数据同步,而同步的动作就在命令传播阶段
  • master将接收到的数据变更命令发送给slave,slave接收到以后执行对应命令
心跳机制

master和slave连接后,为了保证双方都在线,从而判断能够进行信息交换,将会使用心跳机制来进行维护,可以通过下面的配置设置心跳机制的周期:

repl-ping-slave-period 10
# 设置master判断slave是否在线的心跳周期,默认10s
  • 其他配置:
min-slaves-to-write 2
# 配置当slave数量小于2时,强制关闭master的写功能,并停止数据同步
min-slaves-max-lag 10
# 当slave延迟都大于10s时,强制关闭master的写功能,并停止数据同步

哨兵模式

概要

当主从结构中的master宕机了,那么为了保证服务能够继续正常允许,需要找一个slave作为master来提供服务,而哨兵就是负责这项工作的

哨兵是一个分布式系统,会对主从结构中的每个服务器进行监控,当出现故障时会通过投票机制选择新的master,并将所有的slave连接到新的master(哨兵可以配置多个,都是专门监控master和slave是否在正常工作)

注:

  • 哨兵也是一个redis服务,只是不提供数据服务
  • 基于哨兵的选举原理,建议配置数量为奇数个
步骤
  • 将宕机的master下线
  • 选一个slave作为master
  • 通知所有的slave连接新的master
  • 启动新的master和slave
  • 对slave进行全量复制+部分复制
  • 当宕机的master恢复后,作为slave加入
哨兵功能
  • 监控:不断检查master和slave是否正常运行
  • 通知:当被监控的服务器出现问题时,向其他哨兵、客户端发送通知
  • 故障转移:当master出现故障后,断开master和slave连接,并选举一个slave作为master,将其他slave连接到新的master,并告诉客户端新的master地址
启动哨兵模式
  • 启动哨兵命令:
redis-server sentinel-端口号.conf --sentinel
示例
  • 哨兵文件配置:
bind 127.0.0.1
port 16379
dir ./16379
sentinel monitor mastername 127.0.0.1 6379 2
# 哨兵监控的master,mastername随意,然后指定master的ip、端口,以及当多少个哨兵认为master宕机了,就进行相应的处理
sentinel down-after-milliseconds mastername 30000
# 设置master多久没响应就认为是宕机了,这里设置30s
sentinel parallel-syncs mastername 1
# 当master宕机以后,起多少个同步任务去进行同步(一次同步几个slave)
sentinel failover-timeout mastername 180000
# 同步在多久内完成算有效,这里设置180s
  • 启动顺序:master->slave->哨兵
  • 启动哨兵:
redis-server.exe sentinel-16379.conf --sentinel
  • 客户端连接哨兵:
C:\Users\Dawsonenjoy>redis-cli -p 16379
127.0.0.1:16379> keys *
(error) ERR unknown command 'keys'
# 可以看出哨兵不支持常用的数据操作
127.0.0.1:16379> get aaa
(error) ERR unknown command 'get'
  • 查看哨兵info信息:
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mastername,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=1
# 可以看到这里一个master,name为mastername,以及两个slave和一个哨兵
  • 启动哨兵以后再次查看哨兵的配置文件:
# 原本的内容省略
...
# Generated by CONFIG REWRITE
sentinel known-slave mastername 127.0.0.1 6381
sentinel known-slave mastername 127.0.0.1 6380
sentinel current-epoch 0

可以看到这里增加了监听节点的配置,以及其他哨兵的配置

哨兵原理
主从切换

哨兵在进行主从切换时经历了三个阶段:

  • 监控
  • 通知
  • 故障转移
监控阶段
  • 同步信息
通知阶段
  • 保持连通
故障转移阶段
  • 发现问题
  • 竞选负责的哨兵
  • 负责的哨兵选出新的master
  • 新的master选出以后,其他slave切换master,而之前的master恢复后,会重新作为slave加入

企业级解决方案

缓存预热

系统启动前,提前将相关的缓存数据直接加载到缓存系统,避免用户在请求时还需要先查询再缓存的问题,用户只需要直接查询预热好的缓存数据即可

使用场景
  • 请求数量高
  • 主从间数据同步量大,且频度高
缓存雪崩

较短时间内,较多的key集中过期,导致大量的数据需要重新查询,从而给服务器造成巨大压力

解决
  • 构建多级缓存架构
  • 灾难预警监控
  • 短时间内进行限流处理(会降低客户体验)
  • 数据有效期策略调整
  • 定期维护过期数据,对这些数据进行访问量分析,对热点数据进行延迟过期或者直接转成永久
缓存击穿

某个高热数据过期的瞬间,出现对该数据的大量访问,而这些访问都无法成功命中redis,因此都会对数据库进行同一数据的访问,从而给服务器造成巨大压力

解决
  • 加长这类数据的过期时间,或者直接设置为永久key
  • 在高峰期来临前刷新数据有效期,确保数据不丢失
  • 设置二级缓存,给key设置不同的失效时间,保证数据不会被同时淘汰
缓存穿透

访问了大量不存在的数据,导致产生了大量的数据库访问,从而给服务器造成巨大压力

解决
  • 缓存不存在的数据,对那些查询结果为空的进行缓存,并设置过期时间
  • 白名单策略,如果查询不存在就查看白名单,如果不是正常数据就拦截
  • 实时监控,并对一些恶意数据进行黑名单处理
  • 对查询内容进行key加密,在查询时会进行key校验,只有通过校验的才能够进行访问

性能指标监控

可以通过info命令查看

相关指标
  • 性能指标:performance
  • 内存指标: memory
  • 基本活动指标:basic activity
  • 持久性指标:persistence
  • 错误指标:error
performance
  • latency:redis响应一个请求的时间
  • instantaneous_ops_per_sec:平均每秒处理的请求数
  • hit rate:缓存命中率
memory
  • used_memory:已使用内存
  • mem_fragmentation_ratio:内存碎片率
  • evicted_keys:由于最大内存限制被移出的key数量
  • blocked_clients:由于等待获取操作(如blpop)而被阻塞的客户端数量
basic activity
  • connected_clients:客户端连接数
  • connected_slaves:slave数
  • master_last_io_seconds_ago:最近一次主从交互后的秒数
  • keyspace:key值的数量,举例:
# Keyspace
db0:keys=17,expires=0,avg_ttl=0
db1:keys=5,expires=0,avg_ttl=0
persistence
  • rdb_last_save_time:最后一次进行持久化操作的时间戳
  • rdb_changes_since_last_save:最后一次持久化之后数据库的更改数
error
  • rejected_connections:由于达到最大连接数后被拒绝的连接数
  • keyspace_misses:key查找失败的次数
  • master_link_down_since_seconds:主从断开的秒数
常用监控命令
压力测试
redis-benchmark [-h host] [-p port] [-c connect] [-n requestnum] ...
# 默认建立50个连接,每个连接发送10000次请求来进行性能测试
redis-benchmark -c 100 -n 10000
# 建立100个连接,发送10000次请求

结果查看(截取):

# mset操作结果
====== MSET (10 keys) ======
  100000 requests completed in 6.55 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

0.05% <= 1 milliseconds
21.63% <= 2 milliseconds
51.52% <= 3 milliseconds
78.09% <= 4 milliseconds
90.86% <= 5 milliseconds
95.71% <= 6 milliseconds
97.62% <= 7 milliseconds
98.45% <= 8 milliseconds
98.90% <= 9 milliseconds
99.19% <= 10 milliseconds
99.39% <= 11 milliseconds
99.57% <= 12 milliseconds
99.66% <= 13 milliseconds
99.72% <= 14 milliseconds
99.77% <= 15 milliseconds
99.82% <= 16 milliseconds
99.86% <= 17 milliseconds
99.89% <= 18 milliseconds
99.90% <= 19 milliseconds
99.91% <= 20 milliseconds
99.92% <= 21 milliseconds
99.93% <= 22 milliseconds
99.94% <= 23 milliseconds
99.95% <= 24 milliseconds
99.95% <= 25 milliseconds
99.95% <= 26 milliseconds
99.96% <= 27 milliseconds
99.97% <= 28 milliseconds
99.98% <= 29 milliseconds
99.99% <= 30 milliseconds
100.00% <= 31 milliseconds
15264.84 requests per second
# 可以看出5毫秒完成了90%以上的操作,31毫秒内完成了10000个请求操作
查看服务器调试信息

命令:

monitor
# 打印服务器的调试信息
慢查询日志

命令:

slowlog get
# 查看慢查询日志
slowlog len
# 查看慢查询日志条数
slowlog reset
# 重置慢查询日志

相关配置:

slowlog-log-slower-than 1000
# 设置慢查询日志的时间下限
slowlog-max-len 100
# 设置慢查询日志get操作时显示的日志数量

你可能感兴趣的:(Redis 总结)