一、redis入门笔记
Redis
简介(远程字典服务器)
历史与发展
- salvatore sanfilippo 对mysql的性能感到失望
- 自己开发了一款--redis
特性
-
存储结构
-
redis支持的键值数据类型
- 字符串类型
- 散列类型
- 列表类型
- 集合类型
- 有序集合类型
-
对不同数据类型提供了非常方便的操作
-
-
内存存储和持久化
- redis--所有数据都存储在内存中--读写速度快
- redis提供了对持久化的支持--可以将内存中的数据异步存储到硬盘中,同时不影响继续提供服务
-
功能丰富
-
缓存
-
队列系统
- 列表类型-可以用来实现队列--并且支持阻塞式读取
-
限定键生存时间
-
限制数据占用的最大内存空间
-
支持发布/订阅消息模式
-
-
简单稳定
- 命令操作简单-常用的十几个
- 提供多种语言的库操作redis
- 某些库--支持直接以编程语言的数据类型存储
- 代码量少(3w)-对于自定义的用户吸引大
-
非常轻量级(使用内存特别小)
准备
安装redis
-
Linux等
- wget https://download.redis.io/redis-stable.tar.gz 下载最新稳定版-或者下载指定版本
- 下载后,解压缩 tar 命令解压 (tar -zxvf redis-xxx.tar.gz)
- 解压后,进入redis的根目录,cd redis-xxx、然后执行make命令
- make install 安装,这里需要再src文件夹下执行(cd src && make install)--会把redis可执行程序复制到/usr/local/bin(减少进入redis目录执行)
- 初始化脚本方式启动redis---https://blog.csdn.net/u011940361/article/details/79596483
-
MacOS
- 通过Homebrew安装(安装方式可直接参考网上的安装)
-
Windows
- 不支持,不建议,但是Microsoft自己出了redis的windows版本并不停更新
启动
-
redis-server直接启动
-
redis-server /path/redis.conf指定配置文件方式启动
-
通过初始化脚本启动
- /etc/redis/6379.conf 配置文件目录
- /var/redis/6379/ 存放redis数据目录
- /etc/init.d/redis_6379 redis初始化脚本
- 通过service redis_6379 start 启动
停止
- 正确停止方式(redis-cli shutdown)
- 或者直接kill -9 redis端口(效果跟shutdown一样)
- redis可以妥善处理Sigterm信号
命令行客户端
-
redis-cli -h -p(可以指定host和port进入)
-
客户端内直接发送命令
-
命令返回值
- 状态返回值
- 错误返回值
- 整数回复
- 字符串返回值
- 多行字符串返回值 keys *
配置
- redis-server /path/reids.conf --loglevel warning 指定配置文件和添加参数-覆盖配置文件内的参数
多数据库
- redis默认16个数据库
- 默认使用编号0数据库
- select 1 更改数据库
- flushall 直接清空redis实例数据内容
- 更像是命名空间
- 0存储运行环境数据、1存储开发环境数据
- 数据库直接不该存不同应用数据,A应用使用一个redis,B应用使用一个redis实例
入门
命令
- 删除前缀开头的key ---- linux下执行命令,redis-cli keys "ba*"|xargs redis-cli del
- keys * 列出所有键
- type key 返回键值类型
- exists key 判断键是否存在
- lpush key value 向指定列表类型键中添加一个元素
数据类型
-
字符类型
-
简介
- 可存储任何类型的字符串,包括二进制数据
- 可以存储用户邮箱,json化的对象,甚至是一张图片
- 最大容量512M
-
命令
-
set key value / get key
-
incr 字符刚好是整数
- 键名命名方式:对象类型:对象ID:对象属性 如:user:1:frends
- 自增ID,redis中使用,对象类型(复数):count 作为键名
-
-
命令拾遗
-
INCRBY key increment 增加指定数量
-
DECR key / DECRBY key increment
-
增加指定浮点数: incrbyfloat key increment
-
append 向尾部追加value ---append key value
-
获取字符串长度----strlen key
-
同时获取或设置多个键值---MGET / MSET
-
位运算
- getbit key (获取字符串二进制位的值)
- setbit key value (设置字符串二进制位的值)
- bitcount key [start] [end] 获取字符串二进制位为1的个数 start end可以指定获取开始和结束字符为1的个数
- bitop operation destkey key [key...] 对多个字符串进行or and not xor 位操作 并把结果存储到destkey中
- bitpos key [start] [end] 获知指定键第一个0、或者1的位置/ 其中start和end是指定开始和结束字节
-
-
-
hash散列
-
介绍
- 键 字段 字段值
对象类型:id 字段名 字段值 - hash散列支持不同键,定义相同字段和不同字段
- 键 字段 字段值
-
命令
- hset key field fvalue
hget key field
hgetall key
hmget/hmset可以同时添加多个字段和获取多个字段 - hexists key field 判断字段是否存在
- hsetnx key field value nx代表如果不存在则设置,如果存在则不执行任何操作
- hincrby key field increment 指定字段增加指定数值
- hdel key field [field...]删除一个或者多个字段
- hset key field fvalue
-
实践
-
命令拾遗
- hkeys key 获取所有的字段
hvals key 获取所有字段值 - hlen key 获取字段数量
- hkeys key 获取所有的字段
-
-
LIst列表
-
介绍
- 存储一个有序的字符串列表,列表两端添加元素,或者获取列表的某一片段
- 双向列表
- 一个列表类型键最大可容纳2的32次方-1个元素2^32 -1
- 通过索引访问元素比较慢
-
命令
- LPUSH key value value1/rpush key value value1 lpush是往左边插入元素,rpush是往右边插入元素
- lpop key / rpop key 列表两端弹出元素
- 列表当作栈使用--lpush-lpop,rpush-rpop,列表当作队列使用,lpush-rpop,rpush-lpop
- llen key 获取列表中元素的个数,读取现成的值,复杂的O(1)
- lrange key start end 包左包右,支持负索引,lrange key 0 -1 可以获取所有元素
- lrem key count value 删除列表中前count个值为value的元素,count > 0 从左边开始数,count < 0 从右边开始数然后count取绝对值,count指定期望删除的个数
-
实践
-
命令拾遗
- 获得和设置指定索引的值 lindex key index,lset key index value
- 只保留列表指定片段 ltrim key start end,常和lpush结合限制列表长度
- 列表指定位置插入元素,linsert key before|after pivot value,向列表key的pivot元素的前面或者后面插入元素value
- rpoplpush source destination source中rpop出一个元素插入到destination中
-
-
set集合
-
介绍
- 集合采用值为空的散列表hash table
- 无序,唯一,复杂度O(1)
- 交集并集差集
-
命令
- sadd key menber menber1/ srem key menber menber1 集合中添加和删除一个或多个
- smembers key 获取集合所有元素
- sismember key menber 判断元素是否在集合内,复杂度O(1)
- 集合间运算,sdiff /sinter/sunion set [set...] 差集,交集,并集
-
实践
-
命令拾遗
- scard key 获取集合的个数
- sdiffstore/sinterstore/sunionstore destination key [key...]进行集合运算并存储到destination中
- srandmember key count 随机获取key的count个元素,count如果为负数,可能存在重复
- spop key 随机弹出一个元素
-
-
zset有序集合
-
介绍
-
命令
- zadd key score member [...] 增加元素
- zscore key member 获取元素分数
- zrange key start stop [withscores]/zrevrange key start stop [whithscores] zrange是顺序,zrevrange是逆序,获取key指定索引的元素,withscores 是否加分数
- zrangebyscore key min max [withscores] limit offsets count 获取指定分数范围内的元素,limit是从第几个开始(offset代表索引),获取count个
- zincrby key increment member 增加member元素的分数(增加increment)
-
实践
-
命令拾遗
-
zcard key 获取集合元素的数量
-
zcount key min max 获取指定分数范围内的元素个数
-
zrem key member [...]删除一个或多个元素
-
zremrangebyrank key start stop 安装小-> 大分数排名删除指定范围内的元素
-
zremrangebyscore key min max 安装分数范围删除元素
-
zrank key member 获得元素排名
-
集合运算
- zinterstore destination numkeys key [...] weights weight [...] aggregate sum|min|max 计算集合的交集,结果存在destination中,weights不为零,则会参与分数计算,默认aggregate的为sum,分数和
- zunionstore 用法与交集运算一样
-
-
命令进一步学习
事务
-
multi开头,exec结束,中间命令执行
-
执行情况
-
语法错误
- multi内都不执行
-
运行错误
- multi内部分正常执行
-
-
watch
- 监控一个或多个键
- 如果监控内的键被修改了,则multi不会执行
过期时间
-
expire 单位秒 s
-
expire key time
- 对键设置过期时间
-
ttl key
- 查询还有多久时间过期
-
-
persist 单位秒 s
- 把键改为持久化(set命令也会让键持久化)
-
时间单位不同
- expire 、expireat at以unix时间
- expireat、pexpireat p以毫秒做时间单位
-
作用
-
用于缓存
-
maxmemory 设置内存大小
-
maxmemory-policy 指定策略删除键
- 常用策略lru
-
-
排序
-
sort 命令
-
可排序的类型
-
列表
-
集合
-
有序集合
- 忽略分数,对元素排序
-
-
alpha参数
- 按照字典顺序排序非数字元素 sort key alpha
-
desc参数
- 按照从大到小排序 sort key desc
-
limit offset count 参数
-
-
by 参数
- 对象的某个属性进行排序
- 示例: sort tag:java:posts by post:-> time desc 其中tag:java:posts的值会带入到中,sort读取post:2的time属性然后进行排序
- by 参考键,参考键可以是字符串或hash散列类型
- 如果参考键值相同,sort会再比较元素本身
- 不带*不会排序,常量键
-
get 参数
- get参数使用方法和by一样
- sort中可以有多个get,但只能有一个by
- sort tag:java:posts by post:->time desc get post:->title 会获取post的title属性
- get #会返回元素本身
-
store 参数
- 用于存储sort命令结果
- sort tag:java:posts by post:->time desc get post:->title get # store sort.result 把结果存到sort.result 键中
- store参数和expire结合---缓存排序结果
-
性能优化
-
复杂度 O(n+mlog(m)) n是排序的集合大小,m是返回结果数量
-
优化
- 让n尽量小
- 使用limit 让m尽量少
- 用store参数缓存
-
消息通知
-
任务队列
-
生产者-消费者(push到任务队列,pop任务队列)
-
特点
- 松耦合
- 易于拓展
-
-
redis实现任务队列
- brpop key time time是超时时间 brpop会一直阻塞
- blpop key time 其中返回值,第一个是键,第二个是值
-
优先级队列
-
brpop queue:1 queue:2 queue:3
- 如果只有一个queue有值,则pop有值的元素
- 如果都有值,则先pop 出queue:1,即使queue:2中有很多值
-
-
发布订阅
-
publish channel value
- 发布内容到频道
-
subscribe channel
-
订阅频道
-
返回值类型(返回值都有三个)
- subscribe 类型
- message类型
- unsubscribe类型
-
-
unsubscribe channel
- 取消订阅频道,如果不指定频道,会取消所以频道订阅
-
-
按照规则订阅
-
psubscribe pattern 按照规则订阅
- psubscribe channel1.?*
-
psubscribe和subscribe互不影响
-
管道
-
往返时延
- 客户端和服务端的通讯是tcp,请求到应答的时间,称为往返时延
-
pipeline
- redis支持pipeline,如果命令不相互影响,则可以把命令一次放到管道中执行,节省redis的往返时延时间
节省空间
-
出现原因
- 内存仍旧是昂贵的
-
如何节省空间
-
精简键名和键值
- very:important:person:20 ---> vip:20
-
内部编码优化(两种编码类型)
-
字符串类型
-
查看
- object encoding key
-
redis内部自建了0-9999 个数字共享对象
-
如果存储ID是数字的字符串,存储空间非常小,直接引用共享对象
-
embstr新引入,与raw编码相比,sdsndr存储是连续的,两次操作变一次
-
-
hash散列类型
- hash-max-ziplist-entries 512
hash-max-ziplist-value 64 - hash类型会根据以上的2个配置参数透明切换使用的编码,如果小于上述配置,使用ziplis,否则使用hashtable
- hash类型的字段,字段值可以使用字符串类型的优化方式
- ziplist,紧凑的编码风格,牺牲了读取性能,以换取高的空间利用率,当hash类型的字段多时,不应设置entries和value的值过高
- hash-max-ziplist-entries 512
-
列表类型
- quicklist = linkedlist和ziplist的结合,原理:长链表分成若干个以链表形式组织的ziplist,以此减少占用的空间,同时提升ziplist编码的性能效果
-
集合类型
-
有序集合类型
-
-
数据类型对应的内部编码方式
-
字符串类型
- raw
- int
- embstr
-
hash类型
- hashtable
- ziplist
-
list类型
- linkedlist
- ziplist
-
集合
- hashtable
- intset
-
有序集合
- skiplist
- ziplist
-
-
注意
- 如果设置maxmemory,则需要redisobjec来记录每个lru,则不会使用共享对象
-
lua
语法
-
数据类型
- nil
- boolean true false
- 字符串
- table 唯一的数据结构
- 函数 function
-
变量
- local a = 1 redis脚本不能使用全局变量
-
注释
- -- 单行注释
- -- [[ ]] 多行注释
-
赋值
-
支持多重赋值
- local a, b = 1, 2
-
-
操作符
- 、-、*、/、%
- == 、~= 、< 、>、 <=、>=
- not 、and、or
- 操作数不是nil或false,逻辑操作符就认为操作数是真
- print(1 and 5) -- 5
print(1 or 5) -- 1
print(not 0) -- false
print('' or 1) -- '' - print('hello' .. ' ' .. 'world!') -- 'hello world!'
- print(#'hello') -- 5
-
语句
-
if
- if 条件表达式 then
语句块
elseif 条件表达式 then
语句块
else
语句块
end
- if 条件表达式 then
-
循环语句
-
while
- while 条件表达式 do
语句块
end
- while 条件表达式 do
-
repeat
- repeat
语句块
until 条件表达式
- repeat
-
for
-
for 变量=初值, 终值, 步长 do
语句块
end -
for语句的通用形式
- for 变量 1, 变量 2, ..., 变量 N in 迭代器 do
语句块
end - for index, value in ipairs(a) do
print(index) -- index迭代数组a的索引-- value迭代数组a的值print(value)
end
- for 变量 1, 变量 2, ..., 变量 N in 迭代器 do
-
-
-
-
表类型
- 表的定义方式为:
a = {} --将变量a赋值为一个空表 - a['field'] = 'value' --将field字段赋值value
- people = { --也可以这样定义
name = 'Bob',age = 29
}
- 表的定义方式为:
-
函数
- function (参数列表)
函数体
end - local square = function (num)
return num * num
end
- function (参数列表)
标准库
-
基础库
- ipairs 遍历int
- pairs 遍历非int
- unpack 把结果转换成数组
- tonumber
- tostring
-
table库
-
table是lua唯一的对象类型--类似数组
-
数组转换为字符串
- table.concat({1,2,3}, ',', 2) --- 2, 3 ----',' 分隔符,2--开始索引
- table.concat({1,2,3}, ',', 2,2) --- 2 ----最后的2是结束索引
-
向数组插入元素
- table.insert({1,2,4}, 3, 3) 结果1,2,3,4
- table.insert({1,2,3,4}, 5) 结果1,2,3,4,5默认最后insert
-
从数组中弹出一个元素 a =
- table.remove(a) 默认弹出最后的元素
- table.remove(a,1) 1代表索引
-
-
math库
- 提供了常用的数学运算函数
- math.abs(x)
- math.ceil(x) 进1取整 1.3 -> 2
- math.floor(x) 向下取整 1.3 -> 1
- math.sqrt(x) x的平方根
- math.random() 取值[0,1)
- math.random(x) 取值[1,x)
- math.randomseed(x) 设置种子的值生成随机数
-
其他库
-
cjson
- local json_people_str = cjson.encode(people) 使用cjson序列化成字符串
- local json_people_obj = cjson.decode(people) cjson将序列化后的字符串还原成表
-
cmsgpack
- local msg_people_str = cmsgpack.pack(people) cmsgpack序列化成字符串
- local msg_people_obj = cmsgpack.unpack(people) cmsgpack将序列化后的字符串还原成表
-
redis和lua相互调用
-
lua调用redis
- local value = redis.call('get', 'foo')
-
redis调用lua脚本
-
EVAL命令
- ===> EVAL 脚本内容 key参数的数量 [key...] [arg...]
- EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 foo bar
-
EVALSHA命令
- 执行顺序 : 计算脚本的SHA1摘要并记录在脚本缓存 -> evalsha命令根据提供的摘要从脚本缓存中查找对应脚本 -> 如果查找到就执行脚本 -> 找不到,就重新执行eval命令
- redis客户端已经内部封装好了,替我们实现了,对使用者是透明的
-
-
返回值
- 空结果对应lua的false
- lua的false比较特殊,会被转换成空结果
- local value = redis.call('get', 'foo')
- EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 foo bar
-
KEYS和ARGV
- EVAL命令没传递对应的keys和args,可能会导致一些功能不可以
- keys 不传,导致集群没法准确查找对应的节点
-
沙盒和随机数
-
沙盒,保证脚本的执行不受到外界干扰,保证多次执行结果的一致性。复制和AOF持久化的时候结果一致性
-
random,redis替换了math.random和math.randomseed函数,使得每次执行脚本生成的随机数列相同
- 如果有需求,可通过生成随机数并参数方式传递给脚本
-
redis对随机结果进行了排序,内部通过lua的table.sort函数实现
-
redis对产生结果是随机,并且无法排序的,使用lua_random_dirty标记,此后只允许调用只读命令,不允许修改数据库的值
对应的redis命令有:spop、srandmember、randomkey、time
-
-
redis执行脚本的其他命令
- SCRIPT LOAD命令,将脚本加入缓存而不执行
- SCRIPT EXISTS 可以同时查找一个或者多个脚本sha1摘要是否被缓存
- SCRIPT FLUSH 清空脚本缓存
- SCRIPT KILL 强制终止当前脚本的执行
-
原子性和执行时间
-
执行时间 : 脚本不适合执行耗时操作
-
原子性: 脚本执行原子性,执行期间不执行其他命令
- 防止脚本执行时间过长,redis提供了lua-time-limit限制脚本最长运行时间为5s,超过5s,redis开始接收其他命令但不会执行并返回错误
- 能执行的命令有script kill和shutdown nosave,如果脚本执行的是set,push等设置操作,则只能执行shutdown nosave命令
-
持久化
RDB
-
符合一定条件时,将内存中的数据生成一份副本保存到硬盘上 -- 称为快照
-
备份的情况:
-
根据配置规则进行自动快照备份
- 时间窗口m ,改动的键的个数n,时间m内被改键大于n,符合自动快照条件-- redis预置了3个条件
-
用户执行SAVA、BGSAVE命令进行
- 执行save命令时,redis同步进行快照操作,会阻塞所有来自客户端的请求。避免在生产环境使用save命令
- 执行bgsave命令,后台异步快照操作,redis可以继续响应客户端的请求。lastsave命令获取最近一次成功执行快照的时间(unix时间戳)
-
执行flushall清空数据库数据时会进行快照操作
- 如果有自定义快照条件则触发,没有自定义快照条件则不会触发
-
执行复制时
- 设置了主从模式时、 没有自定义快照条件和手动执行快照操作时,也会生成rdb快照文件
- 复制初始化,需要在硬盘生成rdb快照(即使关闭rdb快照,复制初始化也会生成rdb快照)
-
-
rdb快照原理:
-
快照文件存储在redis工作目录的dump.rdb文件中
-
redis使用fork函数复制当前进程的副本,当前进程指父进程,副本指子进程
-
父进程继续接收客户端的请求,子进程进行快照操作
-
子进程写入完所有数据,使用dump.rdb文件替换旧的rdb文件 --- 至此完成一次快照操作
-
特点
- 1.fork操作时,操作系统使用写时复制策略(copy-on-write),父子进程共享同一内存数据
- 2.共享同一内存,使得物理内存不会增加
- 3.如果父进程需要写入数据,redis会从内存中copy需要的片的数据,使得子进程的快照操作不受影响
- 4.需要确保linux系统允许应用程序申请超过可用内存的空间,通过/etc/sysctl.conf文件加入vm.overcommit_memory=1,然后重启系统
- 5.rdb文件任何时候都是完整的,rdb文件是经过压缩的
- 6.新的rdb文件是fork一刻的数据
- 7.当rdb实现持久化,一旦redis异常退出,会丢失最后一次快照以后更改的所有数据
- 8.redis启动,载入rdb文件,1000万个键,1g的数据,大概需要20-30s
-
AOF
-
简介
- redis存储非临时数据时,需要打开AOF持久化降低进程中止导致的数据丢失
- 目录和名称和rdb文件的目录和名称一样
-
AOF可以将redis的每一条写命令追加到硬盘文件中,会降低redis的性能,此影响可接受。可通过较快的硬盘提高AOF的性能
-
开启AOF
- 通过 appendonly参数启用:
appendonly yes
- 通过 appendonly参数启用:
-
AOF的实现
- aof文件记录了redis客户端发送给redis的每一条命令
- 重复的命令,可以通过重写压缩aof文件的大小
- auto-aof-rewrite-percentage -- 距离上次重写超过百分之多少,则重写
- auto-aof-rewrite-min-size -- 限制允许重写的最小aof文件的大小
-
同步硬盘数据
- appendfsync always ---性能最差
- appendfsync everysec -- 折中
- appendfsync no --性能最快,最不安全
集群
出现原因
- 单个redis实例,负载效率瓶颈
- 单个redis实例,内存存储瓶颈
复制
-
特点
- redis可以很容易实现复制
- 分为主从数据库
- 当主数据库数据更新时,会同步到从数据库
-
主数据库
- redis-server 默认启动
-
从数据库
- 添加 slaveof参数 redis-server --port 6380 --slaveof 127.0.0.1 6379
-
主从数据库关系
- 一个主数据库可以拥有多个从数据库
- 一个从数据库只能有一个主数据库
-
命令
- info replication 查看数据库主从信息
- 配置 slave-read-only 一般不使用
- slaveof 127.0.0.1 6379 可以更改主数据库
- slaveof no one命令,停止接受其他数据库的同步,并转换为主数据库
-
原理
-
从数据库启动,发送sync信号给主数据库
-
主数据库接收sync信号,保存快照rdb,把快照和缓存命令发送给从数据库
-
从数据库,接收快照和缓存命令,并载入
-
数据初始化同步完成后,主数据库数据变化都会同步给从数据库
-
主从数据库,断开,2.8版本后支持增量复制
-
特点
- 从数据库同步的时候并不会阻塞
- 配置slave-serve-stale-data no,同步完成前阻塞
-
乐观复制
- 容忍一定时间内主从数据库内容不同
- 主数据库会立刻响应客户端请求
- min-slaves-to-write 3
min-slaves-max-lag 10
write 3表示只有3个以上从数据库连接时,主数据库才可写,lag 10 表示失去最长时间超过10s认为从数据库失去连接 - write和lag特性默认关闭,分布式系统中,打开并合理配置以降低主从架构和网络分区导致的数据不一致的问题
-
-
图结构
-
主数据库A
-
从数据库C
-
从数据库B
- 从数据库D
- 从数据库E
-
-
说明:A会同步数据到B/C,而B会同步到D/E但不会同步到A/C
-
-
读写分离与一致性
- 通过复制实现读写分离,一主多从,可以提高服务器负载能力-类似电商网站,读的频率高
- 一主数据库不能满足需求时,需要使用集群功能
-
从数据库持久化
-
出现原因
- 持久化相对耗时,为了提高性能
-
步骤
-
一个或多个从数据库,并开启持久化,主数据库不开启持久化
-
从数据库故障,会从主数据库同步数据,不用担心数据不一致问题
-
主数据库故障
- 把一从数据库slaveof no one设置为新的主库
- 手动重启主库以从库方式启动,由新主同步数据过来
- 注意不要通过管理工具自动重启旧主库,和设置自动重启
-
哨兵模式---避免手动维护的麻烦和容易出错
-
-
-
无硬盘复制
-
复制的优点
- 简化逻辑,复用已有的代码
-
复制的缺点
- 主库禁用rdb方式时,复制初始化同样会生成rdb快照,主库启动以rdb恢复数据,复制发生时间不确定,恢复数据可能是任何时间点
- 每次从库复制同步,执行一次rdb快照,对硬盘进行读写,性能降低
-
无硬盘复制
- 通过网络方式发送快照内容给从库
- repl-diskless-syncyes开启无硬盘复制
-
-
增量复制
-
前提
- 每个redis启动有一个唯一ID,重启后ID会改变
- 主库存命令时,存放到积压队列,并记录挤压队列存放命令的偏移量范围
- 从库接收时,会记录下主库ID和命令偏移量范围
-
过程
- 从库发送psync信号执行增量复制,传递ID和偏移量
- 主库判断ID是否相同,以及偏移量是否在积压队列偏移量中
-
配置
- repl-backlog-size积压队列大小
- repl-backlog-ttl,多久时间释放积压队列,默认1小时
-
哨兵
-
概念
-
一主多从,读多写少情况下,主库故障导致的人工介入,难以自动化----redis提供哨兵工具实现自动化的系统监控和故障恢复功能
-
监控redis系统的运行情况---哨兵是一个独立的进程,一主多从中可以使用多个哨兵监控,保证系统的健壮性
-
功能
- 监控主库和从库是否运行正常
- 主库故障,从库自动转为主库
-
-
入门
-
哨兵简单模式示例,一主2从
-
启动一主,两从数据库,info replication查看
-
创建sentinel.conf配置文件:sentinel monitor mymaster 127.0.0.1 6379
-
$redis-sentinel /path/to/sentinel.conf启动哨兵
-
测试
- 关闭主库,查看信息,重启主库查看信息
- 可以看到从库变主库,主库变从库的输出
-
sentinel monitor master-name address redis-port quorum ---- quorum表示需要多少哨兵节点同意,判断客观的标准
-
-
原理
-
定时操作(贯穿哨兵的生命周期)
-
每10s发送info,获取主从数据库的信息
- 获取主库信息
- 把主库的从库加入监控
-
每2秒往主从库发送自己的__sentinel__:hello信息,获取其他监控同一主库的哨兵节点
- 发送自己的信息与其他哨兵共享信息
- 判断是不是新发现的哨兵并建立连接,只发送ping连接
-
每1秒往主从和其他哨兵节点发送ping
- 定时监控主从库和哨兵有没有停止服务,通过ping
- 时间间隔和down-after-milliseconds配置相关
- 小于1s,则按配置,大于1s,则1s发送一次ping
- down-after-milliseconds配置的时间,作为主观判断是否下线的标准
-
-
判断主库下线过程
- 通过down-after主观判断是否线下
- 哨兵发送sentinel is-master-down-by-addr,询问其他哨兵的意见
- 超过quorum的哨兵同意,则为客观下线
- 非主库则直接主观判断
-
领头哨兵选举
-
raft算法
-
过程
- 发现主库故障的哨兵发起选举请求(可能多个)
- 如果目标哨兵没选举过其他哨兵,直接同意
- 超过半数,并大于quorum的时候,领头哨兵
- 多个哨兵选举时,可能没有哨兵当选
- 隔一段时间再次发起选举请求,直到选举出来
-
-
故障恢复
-
挑选主数据库
- 挑选优先级最高的主库
- 优先级出现多个,挑选复制偏移量大的
- 都一样,则选运行id最小的
-
发送主库信息给其他从库
- 发送slaveof no one,转为主库
- 发送slaveof命令,从库更换主库
-
重启主库
- 更新内部记录,旧的主库转从库
- 恢复服务时,自动以从库提供服务
-
-
-
部署
-
监控效果的好坏取决于哨兵的视角
-
相对稳妥的方式--哨兵的视角跟节点的视角相近或者一致
-
过程
- 每个节点部署一个哨兵
- 每个哨兵与其对应的节点的网络环境相同或相近
- 例如网络分区,每个哨兵观察的分区都是主分区
-
缺点
- 每个哨兵都会与系统建立连接,产生较多连接
- redis不支持连接复用,产生多个冗余连接
- 配置哨兵需要根据实际生产情况进行选择
-
-
集群
-
概念
-
出现原因
- 即使使用哨兵,所有redis实例仍然存储了集群中所有数据,会出现木桶效应
-
旧版的水平扩容
-
通过客户端分片方式--指定对应的键存储到对应的节点,读取的时候直接到该节点读取
-
优点
- 每个节点只存放总量的1/N
-
缺点
- 分片后,想增加节点,需要对数据手工迁移
- 保证数据一致性,需要集群暂时下线,操作复杂
-
-
集群
-
广义集群
- 拥有和单机实例同样的功能
- 网络分区后,有容错性
-
redis的集群功能
- 特性上看哨兵是集群的子集
- 哨兵和集群是redis的两个独立功能
- 需要水平扩容时,集群是非常好的选择
-
-
-
配置集群
-
入门
-
每个集群中至少需要3个主库才能正常运行
-
配置中打开cluster-enableed yes
-
集群将当前节点记录的集群状态持久化存储,当前工作目录下的nodes.conf文件
- 每个节点使用不同工作目录
- cluster-config-file选项修改持久化文件名
-
节点ID,同一个ID,可能地址和端口是不同的
-
通过redis-trib.rb工具启动集群,需要先安装ruby(redis-trib.rb由ruby编写)
- 依赖gem包---gem install reids
- /path/to/reids-trib.rb create --replicas 1 127.0.0.1 6380 ...多个节点地址端口,启动 (replicas 代表每个主库的从库数量)
-
集群创建过程
- 首先发送ping命令连接每个节点,如果有连接不成功的,创建集群失败
- 发送info命令,获取对应节点的信息,确定是否开启了集群
- cluster meet ip port,告诉节点,其他节点可以并入集群
- 分配主从数据库,分配原则,每个节点的ip地址不同,从库和主库的ip地址不同
- 分配插槽给主库,指定哪些键归哪些节点管理
- 成为子库的节点发送cluster replicate 主库ID,将当前节点转换为从库,复制指定ID(主库)数据
-
Cluster nodes,获取集群所有节点信息
-
-
节点增加
-
cluster meet ip port (新节点中执行)- ip port为集群中任意节点ip端口
-
过程
- 新节点A和其中一个B握手,B把A当作新节点
- B使用Gossip协议,通知其他集群节点A的加入
-
-
插槽分配
-
概念
- 总数16384个插槽
- 新加入要么cluster replicate复制成为从库,要么 分配插槽成为主库
-
键与插槽对应关系
-
键的有效部分CRC16算法计算散列值,取对16384取余
-
键的有效部分
- {xxx} 大括号中间部分
- 没大括号,则全部键名为有效部分
-
-
插槽分配过程
-
插槽没分配过
- cluster addslots slots1.... 直接分配插槽
- cluster slots 查看插槽分配情况
-
插槽分配过,想移动到其他节点
-
借助redis-trib.rb工具
- /path/to/redis-trib.rb reshard 127.0.0.1 6380 -- 告诉rb要重新分配
- rb会开始询问如何从新分配
- 按rb的询问交互重新分配插槽
-
不借助工具
-
cluster setslot命令完成插槽迁移,不会连同键一起迁移
-
cluster setslot 插槽号 node 新节点id--
cluster setslot 0 node dfkjdfkjdkfjdkf -
迁移键
- cluster getkeysinslot 插槽号 要返回的键的数量
- migrate 目标节点ip 目标节点port 键名 数据库号码 超时时间 [copy] [replace]
- copy代表不将键从当前库删除,采用复制、replace 如果目标节点存在同名键则覆盖
- 集群模式只能使用0号数据库
- 例子: redis 6381> migrate 127.0.0.1 6380 abc 0 15999 repace
-
-
重新分配插槽,数据访问读取问题
-
出现问题
- A迁B过程中访问,访问B获取不到数据
- A迁B完成,访问旧A获取不到数据
-
解决方案(集群不下线)
-
Cluster setslot 插槽号 migrating 新节点运行ID
-
cluster setslot 插槽号 importing 原节点运行ID
-
redis-trib.rb
- cluster setslot 1 importing A
- cluster setslot 1 migrating B
- cluster getkeysinslot 1 (获取1号插槽键列表)
- 对每个键执行migrate命令迁移到B
- cluster setslot 1 node B完成迁移
-
-
-
-
-
-
获取插槽对应的节点
-
过程
- 往A请求,发现键不在A节点,A节点返回move重定向
- 客户端往move的节点发送再一次命令获取键
-
有实现的redis库
- 有些redis库实现了move重定向,此过程对用户透明
-
无实现的redis库
-
手动访问move的节点
- redis 6380> get foo bar
ERR: MOVE 127.0.0.1 6381 - reids 6381> get foo bar
- redis 6380> get foo bar
-
通过-c 参数
- redis-cli -c -p 6380
redis 6380> get foo bar
-> redirected 127.0.0.1 6381 get foo bar
- redis-cli -c -p 6380
-
优化
- 由于原先一次的请求现在需要两次
- 请求正确的节点时,缓存路由信息,判断插槽属于哪个节点负责
- 缓存所有插槽信息后,每次命令均只发送给正确的节点,以达到和单机实例同样的性能
-
-
-
故障恢复
- 每隔1s每个节点会随机选择5个节点发送ping命令,判断是否下线 -- 主观下线,类似 哨兵
- 一个节点主观判断后会通知其他节点,如果超过半数,则客户下线-- 类似哨兵
- 故障恢复过程和哨兵一样
- 如果主库没有从库,则有插槽没能写入数据,则集群不可用,下线状态,不能正常工作
-
管理
安全
-
可信的环境
-
数据库密码
- 配置文件-requirepass参数为redis设置密码
- 例子:equirepass TAFK(@~!ji^XALQ(sYh5xIwTn5D$s7JF
- 主从库复制时,从库配置masterauth参数设置主库密码,从库自动使用auth命令认证
-
命名命令
- redis支持重命名已有的命令
- 例子:rename-command FLUSHALL
oyfekmjvmwxq5a9c8usofuo369x0it2k - 禁用命令,直接重命名为空字符串
-
概念
-
redis运行在可信环境为前提
-
默认任何地址的请求都能访问
-
修改
- bing 参数,修改只允许本机连接redis
- bing参数只能绑定一个地址,通过防火墙可自由地设置访问规则
-
-
通信协议
-
简单协议
-
概念
- 命令和各个参数空格隔开
- 前面的返回是经过redis-cli封装的,telnet才是真正的返回内容
-
适合场景-telnet与redis通信
-
统一请求协议
- reids的AOF文件
- 主从复制,主库发送从库的内容
-
管理工具
-
redis-cli
-
可执行大部分redis命令
-
管理redis常用命令
- slowlog get 命令获取耗时命令日志
- monitor命令监控reids执行的所有命令(非常影响性能)
- 例子:redis-cli MONITOR | head -n <要分析的命令数> | ./redis-faina.py
-
-
工具
-
phpRedisAdmin
-
类似管理mysql的工具
-
网页端管理工具
-
支持功能
- 键值操作
- 导入导出数据库数据
- 查看数据库和键信息等
-
-
Rdbtools
- redis快照文件解析器
- 可根据快照文件导出json数据文件,分析redis中键占用空间情况等
-
XMind - Trial Version