Redis --- 基础
0. 概述
1. Nosql 和传统数据库的区别
# SQL和Nosql的差异
SQL
S(Structured) # 结构化,即对存入数据的结构有一定的要求
如上图,必须有上图三个字段,如果可以给id,name,age 分别添加Primarykey,Unique,unsigned约束,则插入的数据必须符合约束,也可以对插入的数据类型进行约束,如bigint(20),varcahar(32),int(3),插入数据时,数据库会对插入的数据进行校验,如果不符合要求,数据库会报错,则插入失败
R(Relaional) # 关联的,可以通过建立外键,关联其他表中的字段,以表示两张表有联系
SQL查询 # 固定的查询语句,不管是mysql,Oracle都可以用统一的查询语句进行查询
事务 # 全部满足ACID
Nosql
S
对结构,及插入的数据没什么特定的约束
R
用Json文档嵌套形式表示关联,如:
{
id:1,
name:xiaoming,
age:18,
orders:[
{
id:1,
item:{
id:10,title:"荣耀6",price:4999,
}
},
{
id:3,
item:{
id:10,title:"荣耀7",price:5999,
}
}
],
}
SQL查询 # 无固定的查询语句
redis:get user:1
mongoDB db.users.find({_id:1})
elasticsearch GET http://localhos:9200/users/1
事务(BASE) # 无法全部满足ACID 如redis只能满足最终一致性,无事务
2. Redis 底层存储形式
Redis 底层数据存储形式为 key-value 形式
1. 客户端 命令
1. 本地客户端启动
# 输入命令来启动 Redis 客户端
redis-cli
127.0.0.1:6379> PING
PONG
在以上实例中我们连接到本地的 redis 服务并执行 PING 命令,该命令用于检测 redis 服务是否启动。
2. 远程客户端启动
如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令。
语法格式
redis-cli -h host -p port -a password
示例:
以下实例演示了如何连接到主机为 127.0.0.1,端口为 6379 ,密码为 mypass 的 redis 服务上。
redis-cli -h 127.0.0.1 -p 6379 -a "mypass"
# 启动成功后
redis 127.0.0.1:6379>
2. 键 命令(key)
1. 基本语法
Redis 键命令用于管理 Redis 的键。
语法格式
redis 127.0.0.1:6379> COMMAND KEY_NAME
示例:
redis 127.0.0.1:6379> SET runoobkey redis
redis 127.0.0.1:6379> DEL runoobkey
在以上实例中 DEL 是一个命令, runoobkey 是一个键。 如果键被删除成功,命令执行后输出 (integer) 1,否则将输出 (integer) 0
2. 常用命令
| 序号 | 命令及描述 |
|---|---|
| 1 | DEL key 该命令用于在 key 存在时删除 key。 |
| 2 | DUMP key 序列化给定 key ,并返回被序列化的值。 |
| 3 | EXISTS key 检查给定 key 是否存在。 |
| 4 | EXPIRE key seconds 为给定 key 设置过期时间,以秒计。 |
| 5 | EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
| 6 | PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。 |
| 7 | PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
| 8 | KEYS pattern 查找所有符合给定模式( pattern)的 key 。 |
| 9 | MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。 |
| 10 | PERSIST key 移除 key 的过期时间,key 将持久保持。 |
| 11 | PTTL key 以毫秒为单位返回 key 的剩余的过期时间。 |
| 12 | TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
| 13 | RANDOMKEY 从当前数据库中随机返回一个 key 。 |
| 14 | RENAME key newkey 修改 key 的名称 |
| 15 | RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
| 16 | [SCAN cursor MATCH pattern] [COUNT count] 迭代数据库中的数据库键。 |
| 17 | TYPE key 返回 key 所储存的值的类型。 |
3. 字符串(String)
0. 简述及命名规则
**基本概述: **
String 类型,也就是字符串类型,是 Redis 中最简单的存储类型, 其 value 是字符串,不过根据字符串的格式不同,可以分为三类:
- string:普通字符串
- int:整数类型,可以自增、自减操作
- float:浮点类型,可以自增、自减操作
无论是那种格式,底层都是字节形式存储的,不过是编码方式不同,字符串类型的最大空间不能超过 512MB
**Key的命名: **
Redis 没有 table 的概念,如果区分不同类型的 Key ?
**例如: ** 需要存储用户,商品信息到 Redis中,有一个用户id为1,商品id 也为1,如下可以设计为
# 以:拼接
项目名:业务名:类型:id
1. 常用命令
1. 设置 || 修改
1. 设置 或 修改 一个指定 key 的值
# 语法格式
SET key value
# 示例
set name xiaoming
2. 同时设置或修改 一个 或 多个指定 key 的值
# 语法格式
MSET key value key2 value2...
# 示例
mset name xiaoming age 18
3. 不存在则添加, 存在则不做任何操作
# 当给定 key 不存在则添加,存在即不做任何操作 类似 set key value nx命令。
SETNX key value
2. 取值
1. 获取一个指定 key 的值
# 语法格式
GET key
# 示例
get name
2. 同时获取一个 或 多个指定 key 的值
# 语法格式
MGET key1 key2..
# 示例
mget name age
3. 指定值运算
INCR key #将 key 中储存的数字值增一。
incr age
DECR key #将 key 中储存的数字值减一。
decr age
INCRBY key increment #将 key 所储存的值加上给定的增量值(increment)
incrby age 5
incrby age -1 # 即自增-1 即自减1
DECRBY key decrement #所储存的值减去给定的减量值(decrement) 。
decrby age 5
INCRBYFLOAT key increment #将 key 所储存的值加上给定的浮点增量值(increment) 。
INCRBYFLOAT age 0.5
DECRBYFLOAT key increment #将 key 所储存的值减去给定的浮点增量值(increment) 。
DECRBYFLOAT age 0.5
GETBIT key offset #对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
SETBIT key offset value #对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
4. 设置过期时间
SETEX key seconds value #将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位) 类似 set key value ex 10。
setex name 10 xiaoming
5. 其他命令
GETRANGE key start end #返回 key 中字符串值的子字符GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
SETRANGE key offset value #用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
STRLEN key #返回 key 所储存的字符串值的长度。
MSETNX key value [key value ...\]] #同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
PSETEX key milliseconds value #这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
APPEND key value 如果 key #已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
2. 命令列表
| 序号 | 命令及描述 |
|---|---|
| 1 | SET key value 设置指定 key 的值 |
| 2 | GET key 获取指定 key 的值。 |
| 3 | GETRANGE key start end 返回 key 中字符串值的子字符 |
| 4 | GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
| 5 | GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
| 6 | [MGET key1 key2..] 获取所有(一个或多个)给定 key 的值。 |
| 7 | SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
| 8 | SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
| 9 | SETNX key value 只有在 key 不存在时设置 key 的值。 |
| 10 | SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
| 11 | STRLEN key 返回 key 所储存的字符串值的长度。 |
| 12 | [MSET key value key value ...] 同时设置一个或多个 key-value 对。 |
| 13 | [MSETNX key value key value ...] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
| 14 | PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
| 15 | INCR key 将 key 中储存的数字值增一。 |
| 16 | INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 。 |
| 17 | INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
| 18 | DECR key 将 key 中储存的数字值减一。 |
| 19 | DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。 |
| 20 | APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 |
7. 哈希(Hash)
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
string类型有一个缺点就是无法单独对字符串中的某一个字段进行修改,只能重新覆盖,下图是hash类型的存储方式:
2. 常用命令
1. 设置与取值
HSET filed key value # 设置或修改hash类型key中指定field 的值
hset renyuan name xiaoming
HGET filed key # 获取hash类型key中指定field 的值。
hget renyuan name
HMSET key filed value key2 filed2 value2... # 同时设置一个或多个 key-filed-value 对。
hmset reyuan name xiaoming age 18
HMGET key1 key2.. # 获取给定hash类型key的filed的值。
hmget renyuan name age
HGETALL key # 获取hash类型key中的所有field-value值
hgetall renyuan
HKEYS # 获取hash类型的key中的所有filed
hkeys renyuan
HVALS # 获取hash类型的key中的所有value
hvals renyuan
HSETNX key value #设置一个 key-value 对,当给定 key的filed 不存在则添加,存在即不做任何操作 类似 set key filed value nx命令。
2. 指定值运算
HINCR filed key #将 key 中filed储存的数字值增一。
hincr renyuan age
HDECR filed key #将 key 中filed储存的数字值减一。
decr renyuan age
HINCRBY filed key increment #将 key filed所储存的值加上给定的增量值(increment)
incrby renyuan age 5
incrby renyuan age -1 # 即自增-1 即自减1
HDECRBY filed key decrement #filed所储存的值减去给定的减量值(decrement) 。
decrby renyuan age 5
HINCRBYFLOAT filed key increment #将 key filed所储存的值加上给定的浮点增量值(increment) 。
INCRBYFLOAT renyuan age 0.5
HDECRBYFLOAT key increment #将 key filed所储存的值减去给定的浮点增量值(increment) 。
DECRBYFLOAT age 0.5
HGETBIT key offset #对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
HSETBIT key offset value #对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
3. 设置过期时间
HSETEX key seconds value #将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位) 类似 set key value ex 10。
setex name 10 xiaoming
下表列出了 redis hash 基本的相关命令:
| 序号 | 命令及描述 |
|---|---|
| 1 | [HDEL key field1 field2] 删除一个或多个哈希表字段 |
| 2 | HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。 |
| 3 | HGET key field 获取存储在哈希表中指定字段的值。 |
| 4 | HGETALL key 获取在哈希表中指定 key 的所有字段和值 |
| 5 | HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
| 6 | HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
| 7 | HKEYS key 获取所有哈希表中的字段 |
| 8 | HLEN key 获取哈希表中字段的数量 |
| 9 | [HMGET key field1 field2] 获取所有给定字段的值 |
| 10 | [HMSET key field1 value1 field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
| 11 | HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。 |
| 12 | HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。 |
| 13 | HVALS key 获取哈希表中所有值。 |
| 14 | [HSCAN key cursor MATCH pattern] [COUNT count] 迭代哈希表中的键值对。 |
8. 列表(List)
类似双向链表
特性:
有序,元素可以重复,插入和删除快,查询速度一般
1. 常用命令
LPUSH key element... # 向列表左侧插入一个或多个元素
LPOP key count # 移除并返回列表左侧的第一个元素,可指定数量,没有则返回nil
RPUSH key element... # 向列表右侧插入一个活多个元素
RPOP key count # 移除并返回列表右侧的第一个元素,可指定数量,没有则返回nil
LRANGE key start end # 返回下标范围内的所有元素
BLPOP key timeout #(B-block) 与lpop类似,如果取的值存在list类型中则立刻返回,如果没有此元素时则等待指定时间,然后返回nil,单位为s
BRPOP
下表列出了列表相关的基本命令:
| 序号 | 命令及描述 |
|---|---|
| 1 | [BLPOP key1 key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
| 2 | [BRPOP key1 key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
| 3 | BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
| 4 | LINDEX key index 通过索引获取列表中的元素 |
| 5 | LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素 |
| 6 | LLEN key 获取列表长度 |
| 7 | LPOP key 移出并获取列表的第一个元素 |
| 8 | [LPUSH key value1 value2] 将一个或多个值插入到列表头部 |
| 9 | LPUSHX key value 将一个值插入到已存在的列表头部 |
| 10 | LRANGE key start stop 获取列表指定范围内的元素 |
| 11 | LREM key count value 移除列表元素 |
| 12 | LSET key index value 通过索引设置列表元素的值 |
| 13 | LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
| 14 | RPOP key 移除列表的最后一个元素,返回值为移除的元素。 |
| 15 | RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
| 16 | [RPUSH key value1 value2] 在列表中添加一个或多个值 |
| 17 | RPUSHX key value 为已存在的列表添加值 |
9. 集合(Set)
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
2. 常用命令
SADD key member... # 向set中添加一个或多个元素
sadd s1 a b c
SREM key member... # 移除set中的一个或多个元素
srem s1 a b
SCARD key # 返回set中元素的个数
scard s1
SISMEMBER key member # 判断一个元素是否是存在集合中
sismember s1 a
SMEMBERS # 获取set中的所有元素
smembers
SINTER key1 key2... # 求key1和key2的交集
sinter s1 s2
SDIFF key1 key2 # 求key1和key2的差集
sdiff s1 s2
SUNION key1 key2 # 求key1和key2的并集
sunion s1 s2
3. 命令列表
下表列出了 Redis 集合基本命令:
| 序号 | 命令及描述 |
|---|---|
| 1 | [SADD key member1 member2] 向集合添加一个或多个成员 |
| 2 | SCARD key 获取集合的成员数 |
| 3 | [SDIFF key1 key2] 返回第一个集合与其他集合之间的差异。 |
| 4 | [SDIFFSTORE destination key1 key2] 返回给定所有集合的差集并存储在 destination 中 |
| 5 | [SINTER key1 key2] 返回给定所有集合的交集 |
| 6 | [SINTERSTORE destination key1 key2] 返回给定所有集合的交集并存储在 destination 中 |
| 7 | SISMEMBER key member 判断 member 元素是否是集合 key 的成员 |
| 8 | SMEMBERS key 返回集合中的所有成员 |
| 9 | SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合 |
| 10 | SPOP key 移除并返回集合中的一个随机元素 |
| 11 | [SRANDMEMBER key count] 返回集合中一个或多个随机数 |
| 12 | [SREM key member1 member2] 移除集合中一个或多个成员 |
| 13 | [SUNION key1 key2] 返回所有给定集合的并集 |
| 14 | [SUNIONSTORE destination key1 key2] 所有给定集合的并集存储在 destination 集合中 |
| 15 | [SSCAN key cursor MATCH pattern] [COUNT count] 迭代集合中的元素 |
10. 有序集合(sorted set)
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
sortedSet 底层实现是跳表加hash表,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
ZADD key score member... # 向sortedset中添加一个或多个元素,如果已经存在则更新其score值
sadd s1 a b c
ZREM key member... # 移除sortedset中的一个或多个元素
srem s1 a b
ZSCORE key member # 获取sortedset中的指定 元素的score值
zscore s1 a
ZRANK key member # 获取sortedset 中的指定元素的排名
ZCARD key # 返回sortedset中元素的个数
scard s1
ZCOUNT key min max # 统计score值在给定范围内的所有元素的个数
ZINCRBY key increment member # 让sortedset 中的指定元素自增,步长为指定的increment值
ZRANGE key min max # 按照score排序后,获取指定下标范围内的元素
ZRANGEBYSCORE key min max # 按照score排序后,获取指定score范围内的元素
ZISMEMBER key member # 判断一个元素是否是存在有序集合中
sismember s1 a
ZMEMBERS # 获取sortedset中的所有元素
smembers
ZINTER key1 key2... # 求key1和key2的交集
sinter s1 s2
ZDIFF key1 key2 # 求key1和key2的差集
sdiff s1 s2
ZUNION key1 key2 # 求key1和key2的并集
sunion s1 s2
# 注意: 所有的排名默认都是升序,如果要降序则在命令的Z后面加REV即可
在以上实例中我们通过命令 ZADD 向 redis 的有序集合中添加了三个值并关联上分数。
2. 常用命令
下表列出了 redis 有序集合的基本命令:
| 序号 | 命令及描述 |
|---|---|
| 1 | [ZADD key score1 member1 score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
| 2 | ZCARD key 获取有序集合的成员数 |
| 3 | ZCOUNT key min max 计算在有序集合中指定区间分数的成员数 |
| 4 | ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment |
| 5 | [ZINTERSTORE destination numkeys key key ...] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中 |
| 6 | ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量 |
| 7 | [ZRANGE key start stop WITHSCORES] 通过索引区间返回有序集合指定区间内的成员 |
| 8 | [ZRANGEBYLEX key min max LIMIT offset count] 通过字典区间返回有序集合的成员 |
| 9 | [ZRANGEBYSCORE key min max WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员 |
| 10 | ZRANK key member 返回有序集合中指定成员的索引 |
| 11 | [ZREM key member member ...] 移除有序集合中的一个或多个成员 |
| 12 | ZREMRANGEBYLEX key min max 移除有序集合中给定的字典区间的所有成员 |
| 13 | ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员 |
| 14 | ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员 |
| 15 | [ZREVRANGE key start stop WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
| 16 | [ZREVRANGEBYSCORE key max min WITHSCORES] 返回有序集中指定分数区间内的成员,分数从高到低排序 |
| 17 | ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
| 18 | ZSCORE key member 返回有序集中,成员的分数值 |
| 19 | [ZUNIONSTORE destination numkeys key key ...] 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
| 20 | [ZSCAN key cursor MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值) |
11. 特殊数据类型
6. 集群(分布式缓存)解决的问题
1. 单点 Redis 的问题
1. 数据丢失问题
2. 并发问题
3. 故障恢复问题
4. 存储能力问题:基于内存存储,有上限
2. Redis 集群的优点
对应上面单节点问题:
1. redis数据持久化
2. 搭建主从集群,实现读写分离
3. 利用redis哨兵,实现健康检测和自动恢复
4. 搭建分片集群,利用插槽机制实现动态扩容
7. 数据持久化
RDB快照持久化
RDB:Redis DataBase Backup File,redis数据快照,简单说就是把内存中的所有数据都记录到磁盘中,当redis实例故障重启后,从磁盘读取快照文件,恢复数据
快照文件称为RDB文件,默认保存在当前运行目录
命令
# 由redis主进程执行RDB,会阻塞所有命令
save
# 后台异步执行,子进程执行RDB,避免主进程受到影响
bgsave
# redis主动停机时会主动执行一次RDB,宕机不会执行
RDB配置
#redis内部有触发RDB的机制,可以在redis。conf文件中找到,格式为:
save 900 1 # 900秒内,如果至少有一个key被修改,这执行bgsave,如果是 save "" 则表示禁用RDB
save 300 10
save 60 10000
# 这60s之间的数据如何保证持久化?后面会有方案解决
# RDB 的其他配置也可以在redis。conf中设置
# 是否压缩,建议不开启(no),压缩也会消耗cpu,磁盘不值钱
rdbcomperssion yes
# RBD 文件名称
dbfilename dump.rdb
# 文件保存路径目录
dir ../
原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取内存数据并写入RDB文件
# 在fork过程中还是会占用主进程,redis是如何加快这个过程的呢?
在linux系统中所有的进程都无法操作物理内存,而是由操作系统给每一个进程分配一个虚拟内存,进程只能操作虚拟内存,而操作系统会维护虚拟内存与物理内存的映射关系表,即页表,主进程操作虚拟内存,虚拟内存通过页表的映射关系,而操作物理内存的真正存储位置,这样就可以实现redis进程对物理内存的读写,而执行fork的时候,会创建一个子进程,fork的过程就是将主进程的页表拷贝给子进程,再由子进程操作页表来实现对物理内存的操作,读取内存数据写入新的RDB文件,再用新的RDB文件替换之前的RDB文件
# 因为两个进程操作的是同一块存储空间,肯定会发生读写冲突,导致脏数据的写入,redis又是如何避免的?
fork采用的是copy-on-write技术:
fork会先将物理内存标记为read-Only模式,只读不可以写入,如果想要写数据,必须将源数据拷贝一份再完成写的操作,页表映射关系重新建立,保证读的数据都是最新数据,极端情况下,写入RDB的过程较长,主进程不停的写,则会不停的拷贝,这样内存占用会翻倍,所以一般redis的内存占用不会占用机器的全部内存,会预留一些空间
AOF持久化
Append Only File(追加文件),redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件,同样会在正常结束时检查AOF文件,
格式为下图:
3表示3个字符
AOF配置
AOF默认是关闭的,需要修改redis。conf配置文件来开启AOF
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
AOF的命令记录的频率也可以配置
# 表示每执行一次写命令,立即记录到AOF文件
appendsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1s将缓冲区数据写到AOF文件,是默认方案
appendsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回次破案
appendfsync no
解决AOF文件过大问题
因为记录的是所有的写命令,不管数据最终值是多少,中间改变过得命令都会记录到文件,所以AOF文件的体积必然会比RDB大很多,并且无意义命令过多,对同一个key的写操作只有最后一个操作才是有意义的,那么可以通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少得命令达到相同结果,那么什么时候开始重写呢?
AOF文件重写配置
# AOF文件比三尺文件 增长超过百分之多少触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才出发重写
auto-aof-rewrite-min-size 64mb
数据持久化方案对比与选择
可以两者结合来使用,将RDB作为备份策略放在其他的机房,用来做容灾策略,
8. 主从
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,需要搭建主从集群,实现读写分离
搭建主从架构
master-slave/replica
共包含三个节点,一个主节点,两个从节点
ip port 角色
192.168.150.101 7001 master
192.168.150.101 7002 slave
192.168.150.101 7003 slave
准备实例和配置
要在同一个虚拟机开启三个实例,必须准备三份不同的文件和目录,配置文件所在目录也就是工作目录
创建目录
创建三个文件夹,分别为7001 7002 7003
cd /tmp
mkdir 7001 7002 7003
拷贝配置文件到各个目录
需提前开启RDB,关闭AOF
# 方式一:逐个拷贝
cp redis-6.2.4/redis.conf 7001
cp redis-6.2.4/redis.conf 7002
cp redis-6.2.4/redis.conf 7003
# 方式二:管道组合命令,一键拷贝
echo 7001 7002 7003 | xargs -t -n 1 cp redis-6.2.4/redis.conf
修改每个实例的端口和工作目录
sed -i -e 's/6379/7001/g' -e '.\//dir \/tmp\/7001\//g' 7001/redis.conf
sed -i -e 's/6379/7002/g' -e '.\//dir \/tmp\/7002\//g' 7002/redis.conf
sed -i -e 's/6379/7003/g' -e '.\//dir \/tmp\/7003\//g' 7003/redis.conf
修改每个实例的声明IP
虚拟机有多个IP,为了避免将来读到不一样的ip,需要再redis.conf文件中指定每一个实例的绑定ip信息,格式如下
# redis实例的声明ip
replica-announce-ip 192.168.150.101
在tmp目录下执行命令
# 逐一执行
sed -i 'la replica-announce-ip 192.168.150.101' 7001/redis.conf
sed -i 'la replica-announce-ip 192.168.150.101' 7002/redis.conf
sed -i 'la replica-announce-ip 192.168.150.101' 7003/redis.conf
# 一键修改
printf '%s \n' 7001 7002 7003 | xargs -I{} -t sed -i 'la replica-announce-ip 192.168.150.101' {}/redis.conf
启动
为了方便查看日志,打开3个ssh窗口,分别启动3个redis实例,命令;
redis-server 7001/redis.conf
redis-server 7002/redis.conf
redis-server 7003/redis.conf
开启主从关系
要配置主从模式可以使用replicaof或者slaveof(5.0以前)命令
有临时和永久两种模式:
修改配置文件(永久有效)
# 在redis-conf中添加一行配置
slaveof <masterip> <maseterport>
使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效)
slaveof <masterip> <masterport>
注意:在5.0之后新增命令replicaof,与salveof效果一致
查看集群状态信息
info replication
数据同步原理
主从第一次同步是全量同步
# master如何判断slave是不是第一次来同步数据?首先了解两个概念
Replication id: 简称replid,是数据集的标记,id一致则说明是同一个数据集,每一个master都有唯一的replid,slave则会集成master节点的replid
offset:偏移量,随着记录在repl_baklog 中的数据增多而逐渐增大,slave完成同步时也会记录当前同步的offset,如果slave的offset小于master的offset说明slave数据落后于master,需要更新
因此slave在做数据同步,必须向master声明自己的replication id和offset , master才可以判断到底需要同步那些数据
# 如何判断是不是第一次来做数据同步?
redis服务本身时就有自己的replication id,当做搭建主从关系申请数据同步时,slave节点会将自己的replication id和offset,发送给主节点,先尝试做增量同步,主节点通过判断replication id和自己的replication id是否一致就能知道slave是否是第一次来,第一次来就将自己的replication id和offset发送给slave并做全量同步,主节点做bgsave生成RDB文件,然后将自己的replication id和offset以及文件发送给从节点,salve将主节点的replication id和offset存储在本地替换,并且清空本地数据,加载RDB文件,将其中的数据加载到slave节点的内存中,master将RDB期间的命令记录在repl_baklog中,并持续的将log中的命令持续发送给slave,slave执行接收到的命令做数据同步
# 增量同步
slave重启后同步,则执行增量同步
当slave重启后,也是将自己的replication id和offset发送给master,master判断slave发送过来的replication id和自己的一致则表示不是第一次来,回复slave continue,并通过根据offset来从repl_baklog中同步数据到slave中
# repl_baklog
类似于一个环形数组,offset类似于下标索引,master数据写满之后会从0开始覆盖之前的数据,只要master和slave的数据差异小于整个环形数组的大小,则永远不会做不了增量同步,如果大于整个环形数据,则只能做全量同步
主从数据同步优化
# 尽量避免全量同步
# 避免不了就对全量同步做优化
1.在master中配置repl-diskless-sync yes 启用无磁盘复制,避免全量同步的磁盘IO(写RDB文件时不写在磁盘中,直接写在网络中发送出去,当磁盘慢,网络快的情况下使用,需要增加网络带宽)
2.redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
# 减少全量同步
1.适当提高repl_baklog的大小,发现slave宕机时,尽快实现故障恢复,尽可能避免全量同步
# 从节点比较多,都去找主节点做数据同步,造上主节点压力过大
1.限制一个master上的slave节点数量,如果是在时太多slave,则可以采用主-从-从链式结构,减少master压力
9. 哨兵机制(sentinel)
redis哨兵机制是来实现主从集群自动故障恢复的
监控:sentinel会不断地检查master和slave是否按预期工作
故障恢复:如果master故障,sentinel会将一个slave提升为master,当故障实例恢复后也以新的master为主
通知:负责通知客户端读写的节点ip地址,当集群发生故障时,会将最新消息推送给redis客户端
服务状态监控
sentinel 基于心跳机制检测服务状态,每隔1s向集群的每个实例发送ping命令
主观下线:如果sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线,quorum值最好超过sentinel实例数量的一半
一旦发现master故障,sentinel需要再slave中选择一个作为新的master:
1.首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点,以排除数据过旧的节点
2.判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
3.如果slave-priority值一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
4.最后判断slave节点的运行id大小,越小优先级越高
故障转移
当选中了其中一个slave为新的master后(如slave1),故障转移的步骤?
1.sentinel给备选的slave节点发送slaveof no one命令,让该节点成为master
2.sentinel会向其他slave发送slaveof 新master节点ip 端口命令,让这些slave成为新master的从节点,开始从新的master上同步数据
3.最后sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点(配置文件加一行 slaveof 新master节点ip 端口)
搭建哨兵集群
准备实例和配置
cd /tmp
mkdir s1 s2 s3
在s1目录创建一个sentinel.conf文件,添加以下内容:
port 27001
sentinel announce-ip 192.168.150.101
sentinel monitor mymaster 192.168.150.101 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s1"
# 配置参数解读
# port 27001:是当前sentinel实例的端口
# sentinel announce-ip 声明自己的ip地址
# sentinel monitor mymaster:指定主节点信息,mymaster主节点名字(可以任意写),2是选举master时的quorum值(配置sentinel数量的一半)
# sentinel down-after-milliseconds mymaster 5000 slave与master断开的最长超时时间
# sentinel failover-timeout mymaster 60000 slave故障恢复的超时时间
# dir "/tmp/s1" sentinel的工作目录
然后将s1/sentinel.conf文件拷贝到s2,s3两个目录中(在、tmp目录执行下面命令)
cp s1/sentinel.conf s2
cp s1/sentinel.conf s3
修改s2,s3配置文件
# s2
port 27002
sentinel announce-ip 192.168.150.101
sentinel monitor mymaster 192.168.150.101 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s2"
# s3
port 27003
sentinel announce-ip 192.168.150.101
sentinel monitor mymaster 192.168.150.101 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s3"
启动三个sentinel实例
redis-sentinel s1/sentinel.conf
redis-sentinel s2/sentinel.conf
redis-sentinel s3/sentinel.conf
redis客户端接收sentinel的主节点变化通知
10. 分片集群
分片集群结构
主从和哨兵可以解决高可用,高并发读的问题,但是还有两个问题没有解决:
1.海量数据存储问题
2.高并发写的问题
使用分片集群可以解决上述问题,分片集群特征:
1.集群中有多个master,每个master保存不同数据
2.每个master都可以有多个slave节点
3.master之间通过ping检测彼此的健康状态(哨兵机制)
4.客户端请求可以访问集群任意节点,最终都会被转发到正确节点
分片集群搭建
准备实例和配置
cd /tmp
mkdir 7001 7002 7003 8001 8002 8003
在/tmp下准备一个新的redis.conf文件,内容如下:
port 6379
# 开启分片集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要自己创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
# 节点心跳失效的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/6379
# 绑定地址
0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.168.150.101
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log
将这个文件拷贝到每个目录下
cd /tmp
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
修改每个目录下的redis.conf,将其中的6379修改为跟目录一致
cd /tmp
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
启动
cd /tmp
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
查看redis服务进程
ps -ef | grep redis
关闭进程
ps -eef | grep redis | awk '{print $2}' | xargs kill
或者(推荐这种方式)
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown
创建集群
虽然服务启动了,但是目前服务之间都是独立的,没有任何联系
需要执行命令来创建集群,在redis5.0之前创建集群比较麻烦,5.0之后集群管理命令都集成到了redis-cli中
redis5.0之前
redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb实现的,因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境
yum -y install zlib ruby rubygems
gem install redis
然后通过命令来管理集群
cd /tmp/redis-6.2.4/src
# 创建集群
./redis-trib.rb create --replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003
redis5.0之后
redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003
命令说明:
- redis-cli --cluster 或者 ./redis-trib.rb: 代表集群操作将命令
- create: 代表创建集群
- --replicas 1 或者 --cluster-replicas 1:指定集群中每个master的副本个数为1(也就是1个从节点),此时节点总数除以(replicas+1)得到的就是master的数量,因此节点列表中的前n个就是master,其他节点都是slave节点,随机分配到不同的master
查看集群状态
redis-cli -p 7001 cluster nodes
客户端连接集群
# 一定要加-c参数
redis-cli -c -p 7001
散列插槽
redis 会将每一个master节点映射到0-16383个插槽(hash slot)上,查看集群信息时就能看到
数据key不是与节点绑定,而是与插槽绑定,redis会根据key的有效部分计算插槽值,分为两种情况:
1.key中包含"{}",且"{}"中至少包含1个字符,"{}"中的部分是有效部分
2.key中不包含"{}",整个key都是有效部分
例如:key是num,那么根据num计算,如果{litcast}num,则根据itcast计算,计算方式是利用CRC16算法得到的一个hash值,然后对16384取余,得到的结果就是slot值
# 么redis的数据要跟插槽绑定而不是跟节点绑定
因为redis的主节点会宕机,扩容,删除等情况,如果节点丢失,那么表示数据也丢失了,而数据跟插槽绑定,当节点宕机时,可以将插槽转移到其他节点,扩容时也可以将插槽转移到新的节点
# 如何将同一类数据固定的保存在同一个Redis实例?
保证key的有效部分是一样的,可以采取这种方式:
{phone}:1
{phone}:2
则表示id为1的用户的电话,id为2的用户的电话,因为redis在带有{}的key中计算slot值,会根据{}中的内容计算
集群伸缩
添加节点
# 这样默认添加的是主节点
redis-cli --cluster add-node 新新节点ip:端口 集群中已经存在的ip:端口
# 参数说明
# 指定为从节点
--cluster-slave
# 指定为哪个主节点添加从节点
--cluster-master-id
插槽分配
# 新添加的节点是没有插槽的,需要手动分配插槽
1.redis-cli --cluster reshard host:port
2.需要分配插槽个数 1-16384:3000(看情况指定)
3.新节点的node ID: 可以从集群状态查找到新节点的node ID
4.数据源node ID:可以从集群状态查找到源节点的node ID
5.done
6.yes
# 参数说明
--cluster-from
--cluster-to
--cluster-slots
--cluster-yes
--cluster-timeout
--cluster-pipeline
--cluster-replace
案例
向集群中添加一个新的master节点,并向这个新节点存储num=10
# 难点分析:
因为num这个key计算出来的slot值是固定的,这个slot值可能已经被集群中的其他节点占用了,难点在于如何将这个数据存储在我想要存储的节点上
故障转移
自动故障转移
# 当redis分片集群中有一个master宕机会发生什么?
1.首先该实例与其他master实例失去连接
2.然后其他master节点会检测到该实例疑似宕机的状态
3.当确定该节点宕机后,自动提升一个slave为新的master
手动故障转移(数据迁移)
# 热转移,比如之前的master节点机器老旧了,需要替换
# 在新的slave节点执行cluster failover 命令可以手动让集群中的对应master节点宕机,新的slave节点会成为新的master节点,实现无感知的数据迁移,流程如下:
# 命令为:cluster failover
手动的Failover 支持三种不同的模式(参数):
1.缺省(没有任何参数):默认的流程,如下图(推荐)
2.force:省略了对offset的一致性校验
3.takeover:直接执行第5不,忽略数据一致性,忽略master状态和其他master意见
11. 消息队列
12. 发布订阅
13. Redis发布订阅与消息队列的区别
使用Redis发布订阅功能确实可以实现一个简易的消息队列,但和真正的消息队列还是有区别的:
消息持久化:
-
发布订阅:Redis的发布订阅不支持消息持久化,也就是如果订阅者没在线,它们就会错过发布的消息。
-
消息队列:消息是持久化的,即使没有在线的消费者,消息也不会丢。
通信模式:
-
发布订阅:发布者将消息发送到特定频道,然后所有订阅了该频道的订阅者都会接收到消息。
-
消息队列:消息队列通常是一种点对点的模式,每条消息只有一个消费者。
可靠性:
-
发布订阅:消息发布后即使没有订阅者也不会有影响,因为发布者和订阅者是解耦的,由于没有确认机制,消息丢失的可能性大。
-
消息队列:消息队列都有确认和重试机制,确保消息的可靠传递,即使消费者离线或故障,消息也不会丢。
使用场景:
-
发布订阅:适用于实时消息推送、事件通知等场景,因为消息的及时性比可靠性更为重要。
-
消息队列:适用于任务队列、异步处理、流量削峰等场景,消息的可靠性比较重要。
14. pipeline
Redis管道(Pipeline)是一种提高Redis客户端性能的方法,它允许你发送多个命令,然后用一次操作从服务器接收所有响应。这样可以避免网络RTT(round trip time)延迟,因为发送和接收命令可以同时进行。
Redis管道没有固定的命令数量限制,但是实际上管道的大小会受到客户端缓冲区大小和服务器配置的限制。管道的大小应该根据你的具体需求和服务器的处理能力来调整。
在使用Redis客户端库时,通常可以通过创建一个客户端连接,然后使用该连接的pipeline方法来创建管道。

浙公网安备 33010602011771号