Redis
文章目录
一、Redis简介
Redis是由C语言开发的一款开源的、高性能的键值对存储数据库
Redis数据库支持5种 数据类型,分别是String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Sorted Set(有序集合)
Redis内置复制、Lua脚本、LRU收回、事务及不同级别磁盘持久化功能,同时通过Redis Sentinel实现高可用,通过Redis Cluster提供自动分区等相关功能
Redis命令不区分大小写
Redis的所有操作都是原子性的
启动Redis客户端时添加**–raw**参数将底层编程的字符串转为中文
./redis-cli --raw
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lL736B0U-1628564900821)(images/Redis启动.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2u5B57nh-1628564900823)(images/Redis启动-raw.png)]
二、Redis数据类型
String(字符串)
字符串类型是Redis中最基本的数据类型,它是二进制安全的,任何形式的字符串都可以存储,包括二进制数据、序列化后的数据、JSON化的对象、甚至是一张Base64编码后的图片
String类型的键最大能存储512MB的数据
SET命令:设置键值对
命令格式:SET key value [ex seconds] [PX milliseconds] [NX|XX]
使用SET命令将字符串值value设置到key中,如果key中以及存在其他值,则会覆盖旧值,并且会忽略值的类型,针对带有生存时间的key来说,当SET命令成功执行时,该key上的生存时间将清除
- SET命令的可选参数:
- EX seconds:用于设置key的过期时间,单位为秒,其中
SET key value EX seconds
等价于SETEX key seconds value
- PX milliseconds:用于设置key的过期时间,单位为毫秒,其中
SET key value PX milliseconds
等价于PSETEX key milliseconds value
- NX:表示当key不存在是,才对key进行设置操作,其中
SET key value NX
等价于SETNX key value
- XX:表示当key存在是,才对key进行设置操作
- EX seconds:用于设置key的过期时间,单位为秒,其中
MSET命令:设置多个键值对
命令格式:MSET key value [key value ...]
使用MSET命令同时设置多个键值对,如果某个key已经存在,那么MSET命令会用新值覆盖旧值
MSET命令是一个原子性操作,所有给定key都会在同一时间内被设置更新,不存在某些key被更新而另一些key没有被更新的情况
MSET命令总是返回OK,不可能设置失败
SETNX命令:设置不存在的键值对
命令格式:SETNX key value
SETNX是set if not exists
的缩写,只有在key不存在时才进行设置,如果key存在,则什么都不做
SETNX命令设置成返回1,设置失败返回0
MSETNX命令:设置多个不存在的键值对
命令格式:MSETNX key value [key value ...]
使用MSETNX命令同时设置多个键值对,当且仅当所有给定key都不存在时才设置,如果有一个给定key已经存在,那么MSETNX命令会拒绝执行所有给定key的设置操作,MSETNX命令是原子性的,所有字段要么全部设置成功,要么全部设置失败
GET命令:获取键值对的值
命令格式:GET key
MGET命令:获取多个键值对的值
命令格式:MGET key [key...]
使用MGET命令同时返回多个给定key的值,key之间使用空格隔开,如果给定的key中有不存在的key,那么该key返回nil
GETRANGE命令:获取键的子字符串值
命令格式:GETRANGE key start end
使用GETRANGE命令来获取key中字符串从start开始到end结束的子字符串,start和end参数是整数,为负值时表示从字符串最后计数,如-1表示最后一个字符,并且读取字符串时只能从左向右读取
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0MIWtAF-1628564900824)(images/getrange.png)]
SETBIT命令:设置键的偏移量
命令格式:SETBIT key offset value
使用SETBIT命令对key所存储的字符串值设置或清除指定偏移量上的位(bit)(二进制位,值为0或1),value参数值(0或1)决定了位的设置或清除
GETBIT命令:获取键的偏移量值
命令格式:GETBIT key offset
使用GETBIT命令来获取指定key所存储的字符串值偏移量上的位
SETEX命令:为键设置生存时间(秒)
命令格式:SETEX key seconds value
使用SETEX命令将value值设置到key中,并设置key的生存时间
SETEX命令是一个原子性命令,设置value和生存时间是同一时间完成的
使用ttl key
可查看key剩余的生存时间
PSETEX命令:为键设置生存时间(毫秒)
命令格式:PSETEX key milliseconds value
使用pttl key
查看key剩余的生存时间
SETRANGE命令:替换键的值
命令格式:SETRANGE key offset value
使用SETRANGE命令从指定的位置(offset)开始将key的值替换为新的字符串,如果key不存在,就当做空白字符串处理
GETSET命令:为键设置新值
命令格式:GETSET key value
使用GETSET命令将给定key的值设置为value,并返回key的旧值,当key存在但不是字符串类型时会返回错误
APPEND命令:为键追加值
命令格式:APPEND key value
如果key存在且是字符串类型的,则将value值追加到key旧值的末尾,如果key不存在,则将key设置为value
设置成功将返回key中字符串的长度
BITOP命令:对键进行位元运算
命令格式:BITOP operation destkey key [key...]
使用BITOP命令对一个或多个保存二进制位的字符串key进行位元运算,并将运算结果保存到destkey中,operation表示位元操作符AND(逻辑与)、OR(逻辑或)、NOT(逻辑非)、XOR(逻辑异或)
STRLEN命令:统计键的值的字符串长度
命令格式:STRLEN key
使用命令STRLEN统计key值的字符串长度
DECR命令:键值减一
命令格式:DECR key
使用DECR命令将key中存储的数字值减一,如果key不存在,则key的值先被初始化为0,再执行DECR操作减一
DECRBY命令:键的值减去减量值
命令格式:DECRBU key decrement
使用DECRBY命令将key所存储的值减去减量值decrement,如果key不存在,则key的值先被初始化为0,再执行DECRBY操作
INCR命令:键值加一
命令格式:INCR key
使用INCR命令将key中存储的值加一,如果key不存在,则key的值先被初始化为0,在执行INCR操作
INCRBY命令:键值加增量值
命令格式:INCRBY key increment
使用INCRBY命令将key所存储的值加上增量值increment,如果key不存在,则key的值先被初始化为0,在执行INCRBY命令
INCRBYFLOAT命令:键值加上浮点数增量值
命令格式:INCRBYFLOAT key increment
使用INCRBYFLOAT命令将key所存储的值加上浮点数增量值increment,如果key不存在,则key的值先被初始化为0,再执行INCRBYFLOAT命令
Hash(哈希)
Redis的Hash类型是一个String类型的域(field)和值(value)的映射表,Hash数据类型常常用来存储对象信息
Redis中,每个哈希表可以存储2³² -1个键值对
HSET命令:为哈希表的域设置值
命令格式:HSET key field value
使用HSET命令将哈希表key中的field的值设置为value,当该key不存在时,将会创建一个新的哈希表并进行HSET操作,如果field已经存在哈希表中,那么新值将覆盖旧值
如果field是一个新建域,则HSET操作成将返回1,如果哈希表中已经存在field,那么新值覆盖旧值后将返回0
key 相当于哈希表的表名,field相当于是键名
HSETNX命令:为哈希表不存在的域设置值
命令格式:HSETNX key field value
使用HSETNX命令当且仅当域不存在时,将哈希表key中的field的值设置为value,如果field已经存在,那么HSETNX命令将会执行无效,如果key不存在,则会首先创建一个新key,然后执行HSETNX命令
HMSET命令:设置多个域和值到哈希表中
命令格式:HMSET key field value [field value...]
HMSET命令用于将一个或多个域-值(field-value)对设置到哈希表key中
HGET命令:获取哈希表中域的值
命令格式:HGET key field
使用HGET命令获取哈希表key中field的值
HGETALL命令:获取哈希表中所有的域和值
命令格式:HGETALL key
使用HGETALL命令获取哈希表key中所有的field和value
执行该命令将会以列表的形式返回哈希表中的域(field)和值(value)
HMGET命令:获取多个域的值
命令格式:HMGET key field [field...]
HMGET命令用于获取哈希表key中一个或多个field的值
HKEYS命令:获取哈希表中的所有域
命令格式:HKEYS key
使用HKEYS命令获取哈希表key中的所有域(field)
HVALS命令:获取哈希表中所有域的值
命令格式:HVALS key
使用HVALS命令用于获取哈希表key中所有域的值
HLEN命令:统计哈希表中域的数量
命令格式:HLEN key
HLEN命令用于统计哈希表key中域的数量
HSTRLEN命令:统计域的值的字符串长度
命令格式:HSTRLEN key field
HSTRLEN命令用于统计哈希表key中给定域field相关联的值的字符串长度
HINCRBY命令:为哈希表中的域加上整理值
命令格式:HINCRBY key field increment
HINCRBY命令用于为哈希表key中的field的值加上增量值,增量值可以是一个负数,表示减操作
HINCRBYFLOAT命令:为哈希表中的域加上浮点数增量值
命令格式:HINCRBYFLOAT key field increment
HDEL命令:删除哈希表中的多个域
命令格式:HDEL key field [field...]
HDEL命令用于删除哈希表key中的一个或多个指定field
HEXISTS命令:判断哈希表中的域是否存在
命令格式:HEXISTS key field
HEXISTS命令用于判断哈希表key中的field是否存在
列表(List)
Redis的列表(List)数据类型可以看作简单的字符串列表,安装插入顺序排序
在操作列表时,可以将一个元素插入这个列表的头部和尾部
LPUSH命令:将多个值插入到列表头部
命令格式:LPUSH key value [value...]
LPUSH命令用于将一个或多个value值按照从左到右插入到列表key中
RPUSH命令:将多个值插入列表尾部
命令格式:RPUSH key value [value...]
LINSERT命令:插入一个值到列表中
命令格式:LINSERT key BEFORE | AFTER pivot value
LINSERT命令用于向列表中插入一个值,这个值的位置在pivot之前或之后,如果pivot不存在,则执行该命令无效
LPUSHX命令:将值插入列表头部
命令格式:LPUSHX key value
LPUSHX命令将value值插入列表key的头部,如果key不存在,则什么也不做
RPUSHX命令:将值插入列表尾部
RPUSHX key value
LSET命令:修改列表元素值
命令格式:LSET key index value
LSET命令用于设置下标为index的列表key的值为value,当下标index参数超出范围或列表key为空时,将返回错误
LLEN命令:统计列表的长度
命令格式:LLEN key
LINDEX命令:获取列表元素的值
命令格式:LINDEX key index
LINDEX命令用于获取列表key中下标为index的元素
LRANGE命令:获取列表指定区间内的元素
命令格式:LRANGE key start end
LPOP命令:返回并删除列表的头元素
命令格式:LPOP key
RPOP命令:返回并删除列表的尾元素
命令格式:RPOP key
BLPOP命令:在指定时间内删除列表的头元素
命令格式:BLPOP key [key...] timeout
BLPOP命令是列表的阻塞式弹出原语,是LPOP 命令的阻塞版本,当列表key中没有任何元素时,连接将被命令BLPOP阻塞,直到等待超时或有可弹出元素为止
BRPOP命令:在指定时间内删除列表的尾元素
命令格式:BRPOP key [key...] timeout
LREM命令:删除指定个数的元素
命令格式:LREM key count value
LREM命令用于根据参数count的值,删除列表key中与指定参数value相等的元素
- 当count等于0时,表示删除列表key中所有与value相等的元素
- 当count大于0时,表示从列表key的表头开始向表尾搜索,删除count个于value相等的元素
- 当count小于0时,表示从列表key的表尾开始向表头搜索,删除count个与value相等的元素
LTRIM key start stop
命令格式:LTRIM key start stop
LTRIM命令用于对一个列表进行修剪,让列表key只保留指定区间内的元素(表头处)
RPOPLLUSH命令:将列表元素移动到另一个列表中
命令格式:RPOPLPUSH source destination
RPOPLPUSH命令是一个原子操作,会将source中的最后一个元素弹出,并将弹出的元素插入到列表destination中
BRPOPLPUSH命令:在指定时间内移动列表元素到另一个列表中
命令格式:BPOPLPUSH source destination timeout
集合(Set)
Redis的集合(Set)是String类型的无序集合,集合无序且不存在重复的元素
集合是通过哈希表来实现的,复杂度为O(1)
SADD命令:添加多个元素到集合中
命令格式:SADD key member [member...]
SADD命令用于将一个或多个member元素添加到集合key中
SMOVE命令:移动集合元素到另一个集合中
命令格式: SMOVE source destination member
SMOVE命令用于将集合source中的member元素移动到集合destination中,SMOVE命令属于原子性操作
SUNIONSTORE命令:保存多个集合元素到新集合中
命令格式:SUNIONSTORE destination key [key...]
SUNIONSTORE命令用于获取一个或多个集合key中的全部元素,并将这些元素保存到集合destination中
SISMEMBER命令:判断某个元素是否在集合中
命令格式:SISMEMBER key member
SISMEMBER命令用于判断元素member是否在集合key中
SCARD命令:获取集合中元素的数量
命令格式:SCARD key
SCARD命令用于获取集合key中元素的数量
SMEMBERS命令:获取集合中的所有元素
命令格式:SMEMBERS key
SMEMBERS命令用于获取集合key中的所有元素
SRANDMEMBER命令:随机获取集合中的一个元素
命令格式:SRANDMEMBER key [count]
SRANDMEMBER命令用于随机返回集合key中的count个元素
当count为正数且小于集合元素个数的最大值时,返回count个元素
当count大于等于集合基数时,返回整个集合
当count为负数是,返回一个元素可能重复多次的数组,这个数组的长度为count的绝对值
SUNION命令:获取多个集合中的所有元素
命令格式:SUNION key [key...]
SUNION命令用于获取一个或多个集合key中的全部元素,返回的集合为所给定集合的并集
SDIFF命令:获取多个集合元素的差集
命令格式:SDIFF key [key...]
SDIFFSTORE命令:获取多个集合差集的元素个数
命令格式:SDIFFSTORE destination key [key...]
SDIFFSTORE命令用于获取一个或多个集合key的全部元素,并将获取的元素保存到集合destination中
SINTER命令:获取多个集合元素的交集
命令格式:SINTER key [key...]
SINTERSTORE命令:获取多个集合交集的元素个数
命令格式:SINTERSTORE destination key [key...]
SINTERSTORE命令用于获取给定的一个或多个集合key中的全部元素,并将这些元素保存到集合destination中
SPOP命令:删除集合中的元素
SPOP key [count]
SPOP命令用于随机删除集合key中的count个元素
SREM命令:删除集合中的多个元素
命令格式:SREM key member [member...]
SREM命令用于删除集合key中的一个或多个member元素
有序集合(Sorted Set)
Redis的有序集合(Sorted Set)是String类型的集合,有序集合中不存在重复的元素,每个集合元素都有一个对应的double类型的分数,Redis通过元素对应的分数来为集合进行排序
ZADD命令:添加多个元素到有序集合中
命令格式:ZADD key score member [score member]...
ZADD命令用于将一个或多个member元素及其对应的分数score值加入到有序集合key中
score分数可以是一个double类型的浮点数,也可以是整数
ZINCRBY命令:为分数值加上增量
命令格式:ZINCRBY key incrememt member
ZINCRBY命令用于为有序集合key中的member元素的score值加上增量increment,当increment是负数时,表示减去
ZCARD命令:获取有序集合中的元素数量
命令格式:ZCARD key
ZCOUNT命令:获取在分数区间内的元素数量
命令格式:ZCOUNT key min max
ZCOUNT命令用于获取有序集合key中,score值在min和max之间的元素数量
ZLEXCOUNT命令:获取在指定区间内的元素数量
命令格式:ZLEXCOUNT key min max
ZLEXCOUNT命令用于获取有序集合key中介于min和max之间的元素数量,这个有序集合key中的所有元素score值都相等
ZRANGE命令:获取在指定区间内的元素(升序)
命令格式:ZRANGE key start stop [WITHSCORE]
ZREVRANGE命令:获取在指定区间内的元素(降序)
命令格式:ZREVRANGE key start stop [WITHSCORE]
ZSCORE命令:获取元素的分数值
命令格式:ZSCORE key member
ZRANGEBYLEX命令:获取集合在指定范围内的元素
命令格式:ZRANGEBYLEX key min max [LIMIT offset count]
ZRANGEBYSCORE命令:获取在指定分数区间内的元素
ZRANGEBYSCORE key min max [WITHSCORE] [LIMIT offset count]
ZREVRANGEBYSCORE命令:获取在指定区间内的所有元素
命令格式:ZREVRANGEBYSCORE key max min [WITHSCORE] [LIMIT offset count]
ZRANK命令:获取有序集合元素的排名
命令格式:ZRANK key member
ZREVRANK命令:获取有序集合元素的倒序排名
命令格式:ZREVRANK key member
ZINTERSTORE命令:保存多个有序集合的交集
命令格式:ZINTERSCORE destination numkeys key [key...] [WEIGHTS weight [weight...]] [AGGREGATE SUM | MIN | MAX]
ZUNIONSTORE命令:保存多个有序集合的并集
命令格式:ZUNIONSTORE destination numkeys key [key...] [WEIGHTS weight [weight...]] [AGGREGATE SUM | MIN | MAX]
ZREM命令:删除有序集合中的多个元素
命令格式:ZREM key member [member...]
ZREMRANGEBYLEX命令:删除有序集合在指定区间内的元素
命令格式:ZREMRANGEBYLEX key min max
ZREMRANGEBYRANK命令:删除有序集合在指定排名区间内的元素
命令格式:ZREMRANGEBYRANK key start stop
ZREMRANGEBYSCORE命令:删除有序集合在指定的分数区间内的元素
命令格式:ZREMRANGEBYSCORE key min max
三、Redis必备命令
键(Key)命令
Redis的键命令用于管理Redis的键,删除键,查询键,修改键
EXISTS命令:判断键是否存在
命令格式:EXISTS key
KEYS命令:查找键
命令格式:KEYS pattern
KEYS命令用于按照某种模式查找指定的key,模式类似正则表达式
OBJECT命令:查看键的对象
命令格式:OBJECT subcommand [argumemts [argumemts]]
- OBJECT REFCOUNT key:用于返回给定key引用所存储值的次数
- OBJECT ENCODING key:用于返回给定key所存储的值所使用的底层数据结构
- OBJECT IDLETIME key: 用于返回给定key自存储依赖空闲时间
RANDOMKEY命令:随机返回一个键
命令格式:RANDOMKEY
RENAME命令:修改键的名称
命令格式:RENAME key newkey
RENAME命令用于修改key的名称,如果key不存在,返回错误,如果newkey已经存在,则会覆盖
RENAMENX命令:修改键的名称
命令格式:RENAMENX key newkey
RENAMENX命令用于修改key的名称,当且仅当newkey不存在时才修改,如果newkey已存在,则返回错误
DUMP命令:序列化键
命令格式:DUMP key
RESTORE命令:对序列化值进行反序列化
命令格式:RESTORE key ttl serialized-value [REPLACE]
PTTL命令:获取键的生存时间(毫秒)
命令格式:PTTL key
TTL命令:获取键的生存时间(秒)
命令格式:TTL key
EXPIRE命令:设置键的生存时间(秒)
命令格式:EXPIRE key seconds
PEXPIRE命令:设置键的生存时间(毫秒)
命令格式:PEXPIRE key milliseconds
EXPIREAT命令:设置键的生存UNIX时间戳(秒)
命令格式:EXPIREAT key timestamp
PEXPIREAT命令:设置键的生存UNIX时间戳(毫秒)
命令格式:PEXPIREAT key timestamp
MIGRATE命令:转移键值对到远程目标数据库
命令格式:MIGRATE host port key destination-db timeout [COPY] [REPLACE]
MIGRATE命令用于将key从当前数据库复制到指定的目标数据库中,一旦复制成功,当前数据库中的key会被删除
MIGRATE命令是原子操作,执行时会阻塞两个数据库
MOVE命令:转移键值对到本地目的数据库
命令格式:MOVE key db
SORT命令:对键值对进行排序
命令格式:SORT key [BY pattren] [LIMIT offset count] [GET pattern] [ASC | DESC] [ALPHA] [STORE destination``]
TYPE命令:获取键对应值的类型
命令格式:TYPE key
DEL命令:删除键
命令格式:DEL key [key...]
PERSIST命令:删除键的生存时间
命令格式:PERSIST key
HyperLogLog命令
HyperLogLog是Redis用来做基数统计的算法,当Redis数据库中的数据量非常庞大时,使用HyperLogLog命令来计算相关基数时,它具有所需空间固定、所占空间小的优点
PFADD命令:向HyperLogLog中添加键值对
命令格式:PFADD key element [elememt...]
PFCOUNT命令:获取HyperLogLog的基数
命令格式:PFCOUNT key [key...]
PFMERGE命令:合并多个HyperLogLog为一个新的HyperLogLog
命令格式:PFMERGE destkey sourcekey [sourcekey...]
脚本命令
Redis脚本使用Lua解释器来执行,使用Redis脚本可以一次性将多个请求命令发送出去,减少网络开销
SCRIPT LOAD 命令:添加Lua脚本到缓存中
命令格式:SCRIPT LOAD script
SCRIPT EXISTS命令:判断脚本是否已在缓存中
命令格式:SCRIPT EXISTS sha1 [sha1...]
EVAL命令:对Lua脚本求值
命令格式:EVAL script numkeys key [key...] arg [arg...]
EVALSHA命令:对缓存中的脚本求值
命令格式:EVALSHA sha1 numkeys key [key...] arg [arg...]
SCRIPT KILL 命令:杀死正在运行的Lua脚本
命令格式:SCRIPT KILL
SCRIPT FLUSH命令:清除缓存中的Lua脚本
命令格式:SCRIPT FLUSH
连接命令
Redis的连接命令用于连接Redis数据库,查看服务状态,切换数据库等
AUTH命令:用于解锁密码
命令格式:AUTH password
可通过CONFIG SET requirepass password
命令来设置Redis的密码,以后要连接Redis则必须通过AUTH来验证
QUIT命令:断开客户端与服务器的连接
SELECT命令:切换数据库
命令格式:SELECT index
服务器命令
CLIENT LIST命令:获取客户端相关信息
CLIENT PAUSH命令:在指定时间范围内停止运行来自客户端的命令
命令格式:CLIENT PAUSH timeout
CLIENT KILL 命令:关闭客户端连接
命令格式:CLIENT KILL ip:port
配置相关命令
CONFIG SET
命令格式:CONFIG SET parameter value
CONFIG SET命令用于修改Redis服务器的配置,修改后不用重启Redis也能生效
CONFIG GET
CONFIG GET parameter
CONFIG GET命令用于获取Redis服务器的配置信息,parameter支持正则表达式
BGSAVE:将Redis中的数据异步保存的磁盘中
BGSAVE会新创建一个进程用于保存数据,是异步的
ROLE:查看主从服务器中的角色
角色有master、slave、sentinel
SLAVEOF
命令格式 SLAVE host port
SLAVEOF命令会将当前Redis服务器作为制定Redis服务器的从设备
SHOWLOG:查看Redis的慢日志
SHUTDOWN:关闭Redis服务器或客户端
四、Redis数据库
Redis数据库结构
Redis的数据库保存在server.h中的结构体redisServer的redisDb *db数组中,每一个redisDb数组中的元素都是一各redisDb结构,代表一个Redis数据库,db数组大小默认为16
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
使用TTL来返回一个键剩余的生存时间(秒),使用PTTL来返回一个键剩余的生存时间(毫秒)
删除键
- 定时删除:为一个键设置一个定时器(Timer),当一个键的生存时间到期时Timer会自动删除该键
- 惰性删除:在获取键时对该键进行过期检查,如果该键过期则进行删除
- 定期删除:每隔一段时间对数据库进行一次检查,筛选出过期的键并删除
Redis数据库通知
- 键空间通知:某个键执行过什么命令
- 键事件通知:某个命令被什么键执行了
五、Redis客户端与服务器
Redis服务器同时负责与多个客户端进行网络连接就,处理多个客户端请求。
redis客户端
Redis数据库采用I/O多路复用技术实现文件事件处理器,服务端采用单线程单进程的方式来处理多个客户端发送过来的请求,同时与多个客户端建立网络通信,服务器会为与它相连接的客户端创建相应的redisClient结构,里面保存了当前客户端的相关属性及执行的相关命令的数据结构
客户端的属性大致分为两类:一类是与Redis特定功能相关的属性,另一类则是一些通用的属性
名字属性
默认情况下,连接到Redis服务器的客户端是没有名字的,可以使用CLIENT setname
来为客户端设置其名称
套接字属性
客户端套接字由客户端状态的fd
属性记录,当fd=-1
时,表示该客户端时伪客户端(对Redis服务器的请求命令来自于Lua脚本或AOF文件),当fd > -1
时,表示该客户端时普通客户端(采用套接字与Redis服务器进行通信的)
标志属性
客户端的标志属性flags用来记录客户端的角色(Role)及客户端目前所处的状态
-
单个标志:flag = flag
-
组合标志:flag = flag | flag|…
客户端角色的标志
- 在Redis主从服务器中,主从服务器会相互成为对方的客户端,主服务器的标志为REDIS_MASTER,从服务器的标志为REDIS_SLAVE
- Redis使用标志REDIS_LUA_CLILENT来表示该客户端时一个专门用来执行Lua脚本的客户端
客户端状态的标志
- REDIS_ASKING:表示客户端向运行在集群模式下的服务器发送ASKING命令
- REDIS_CLOSE_ASAP:表示客户端的输出缓冲区过大,超出了服务端允许的范围,服务器会在下一次请求时关闭该客户端
- REDIS_CLOSE_AFTER_REPLY:表示客户端错误,服务器会将该客户端对应的输出缓冲区的数据返回并关闭该客户端
- REDIS_DIRTY_CAS:表示事务使用WATCH命令监视的数据库键被修改
- REDIS_DIRTY_EXEC:表示事务在命令入队是出现错误
REDIS_DIRTY_CAS和REDIS_DIRTY_EXEC标志都代表了Redis的事务安全性被破坏了
- REDIS_MULTI:表示客户端正在执行事务
- REDIS_MONITOR:表示客户端正在执行MONITOR命令
- REDIS_FORCE_AOF:表示让服务器将当前的请求命令强制写入AOF文件中
- REDIS_FORCE_REPL:表示强制让服务器将当前执行的命令复制到与它相连的从服务器
时间属性
- ctime:记录了客户端被创建的时间(age域)
- lastinteraction:记录了客户端与服务器最后一次交互的时间(idle域),可用于查看客户端的空转时间
- obuf_soft_limit_reached_time:记录了客户端输出缓冲区第一次到达软性限制的时间
客户端缓冲区
服务器采用软性限制(Soft Limit)和硬性限制(Hard Limit)两种模式来限制客户端缓冲区大小
- 软性限制:当输出缓冲区的大小小于软性限制大小时,客户端能够正常运行,当输出缓冲区大小大于软性限制大小时,服务器会监听超过的时间,当输出缓冲区大小大于软性限制大小超过一定时间后,客户端会被关闭,这个限制时间由时间属性obuf_soft_limit_reach_time属性记录,当值为0时表示正常
- 硬性限制:当输出缓冲区的大小大于硬性限制大小时,客户端会被立刻关闭
使用命令client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
来设置客户端的输出缓冲区的软性限制和硬性限制
客户端的缓冲区分为输出缓冲区和输入缓冲区
- 输出缓冲区:用于缓存服务器返回给客户端的数据
- 固定输出缓冲区:用于存储比较小的数据
- 可变输出缓冲区:用于存储大数据
- 输入缓冲区:用于缓存客户端发送的命令,它会根据输入的内容动态增大或缩小,最大可达到1GB
客户端的authenticated属性
authenticated属性用于记录客户端是否通过了身份验证,值为0表示没有通过验证,为1表示通过验证
客户端argv和argc属性
- argv属性是一个数组,用于保存客户端要执行的指令,数组的第一个元素是要执行的命令,后面的元素是命令的参数
- argc属性用于记录argv属性的数组长度
当客户端向服务器发送命令时,服务器会将接受到的命令保存到客户端状态(redisClient)的querybuf属性中,再从中分析这个命令,将分析后的命令参数和参数个数保存到argv和argc属性中
Redis服务器
服务器处理命令请求
- 客户端接受用户输入的命令
- 客户端将命令转化为服务器能够识别的协议格式,然后发送给服务器
- 服务器接受并分析命令,再执行命令对应的操作
- 服务器返回结果
服务器执行命令
- 服务器接受客户端发送过来的命令并将命令保存到客户端的输入缓冲区中
- 对输入缓冲区的命令进行解析,获取命令参数和参数个数,并保存到客户端状态的argv和argc属性中
- 调用命令执行器执行对应的命令
命令执行器
命令执行器会根据客户端状态的argv[0]参数,在命令表(Command Table)中查找参数对应的命令,并将查找到的命令保存到客户端状态的cmd属性中,再通过一些判断(客户端的身份验证),最后调用该命令对应的实现函数来执行命令
命令表(Command Table)是一个字典,用于保存Redis命令,字典的键为命令的名称,值为该命令对应的实现信息,为redisCommand结构(保存有一个函数指针,指向命令对应的实现函数)
服务器返回命令结果
在返回给客户端结果前,服务器还会执行的操作:
- 是否需要添加一条慢查询日志
- 是否要执行AOF
服务器函数
serverCron函数
serverCron函数用于管理服务器资源并维持服务器的正常运行,每100毫秒执行一次
clientCron函数
在调用serverCron函数时会被调用,进行客户端的检查:
- 检查这个客户端与服务器的连接是否超时,超时则释放该客户端
- 检查这个客户端的输入缓冲区的大小,以便对服务器的内存资源进行管理
databasesCron函数
对数据库进行检查,删除过期的键
六、Redis底层数据结构
简单动态字符串SDS
Redis数据库并没有直接使用C语言中的字符串,而是重新构建了一种名为简单动态字符串SDS的抽象类型,将其用为Redis的默认字符串表示
SDS是一个C语言结构体,位于sds.h中的sdshdr结构体系列,主要底层源码:
typefef char *sds; //一个指向SDS结构体的指针
struct __attribute__ ((__packed__)) sdshdr5{
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8{
uint8_t len; //字符串的长度
uint8_t alloc; //被分配到的空间
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16{
uint16_t len; //字符串的长度
uint16_t alloc; //被分配到的空间
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32{
uint32_t len; //字符串的长度
uint32_t alloc; //被分配到的空间
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64{
uint64_t len; //字符串的长度
uint64_t alloc; //被分配到的空间
unsigned char flags;
char buf[]; //最后一个字节用于保存空字符'\0',表示字符串结束
};
从SDS底层实现能够看出,SDS定义了多种不同类型的结构体来适应不同场景的需求
C语言字符串与SDS的区别:
- C语言字符串的API是二进制不安全的(出现空字符则会认为这个字符串结束),每修改一次字符串就需要重新分配一次内存,获取字符串长度的时间复杂度为O(n)
- SDS的API是二进制安全的,获取字符串长度的世界复杂度为O(1)
Redis链表
在Redis中,多处用的了链表结构,例如列表(List)的底层实现就是链表结构,底层实现代码:
typedef struct listNode{
struct listNode *prev;
struct listNode *next;
void *value;
}listNode
typedef struct list{
struct *head;
listNode *tail;
void *(*dup) (void *ptr); //节点值复制函数
void (*free) (void *ptr); //节点释放函数
int (*match) (void *ptr, void *key); //比较节点值是否相等
unsigned long len;
}list;
Redis压缩列表ziplist
Redis的压缩列表是列表和哈希表的底层实现之一
-
当一个列表包含的元素较小且这些列表元素是小整数值或短字符串,Redis就会采用压缩列表来实现这个列表
-
当一个哈希表包含的键值对较少且每个键值对的键和值是小整数值或短字符串是,Redis会采用压缩列表来实现这个哈希表
在Redis中,压缩列表是一个顺序型的数据结构,由一系列特殊编码的连续内存块组成,为节省内存,一个压缩列表可以包含任意多个节点, 每个节点都可以保存一个整数值或一个字节数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UOaJhG7O-1628564900827)(images/ziplist.png)]
zlbytes:保存这个压缩列表所占用的字节内存
zltail:保存了这个压缩列表的表尾到起始位置的距离
allen:保存着这个压缩列表中节点的长度
entry:压缩列表的节点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uRNb3yAh-1628564900827)(images/entry.png)]
-
previous_entry_length:上一个节点的长度
-
encoding:当前节点保存的数据的类型和长度
-
content:节点内容,可以是一个数值或一个字节数组
zlend:该属性保存着一个特殊值0xFF,表示这个压缩列表的末端
快速列表quicklist
快速列表是由压缩列表组成的双向列表,链表的每个节点都以压缩列表的结构来保存数据
Redis字典
Redis的数据库底层就是使用字典来实现的,Redis的哈希表也是使用字典实现的
Redis整数集合
Redis集合的底层实现有多种方式,其中一种为整数集合,当一个集合中的元素包含整数值并且元素数量较少时就会使用整数集合来存储
typedef struct intset{
//编码方式:inti16、inti32、init64
uint32_t encoding;
uint32_t length;
//保存集合元素的数组
int8_t contents[];
}
集合元素中的元素编码方式的转换:根据新编码方式的元素的类型,将这个数组空间扩大并分配空间,再将原来数组中的元素转换为新的编码方式,并将类型转换后的元素按照从小到大的顺序排序
Redis跳表
Redis采用跳表实现了有序集合
typedef struct zskiplist{
structz skiplistNode *header, *tail;
//跳表中节点数量
unsigned long length;
跳表中层数最大的节点层数
int level;
}zskiplist;
typedef struct zskiplistNode{
struct zskiplistLevel{
//指向当前节点后的其他节点
struct zskiplistNode *forward;
//与当前节点的距离
unsign int span;
}level[];
//指向当前节点的前一个节点
struct zskiplistNode *backward;
double score;
//指向一个SDS结构的对象,在跳表中必须唯一
robj *obj;
}zskiplistNode;
Redis对象
Redis中有5种数据对象:字符串对象、列表、哈希、集合、有序集合
Redis对象系统实现了基于引用计数的内存回收机制
typedef struct redisObject{
//对象类型:REDIS_STRING、REDIS_LIST...
unsigned type:4;
//编码方式:raw,embstr,int,ziplist...
unsigned encoding:4;
void *ptr;
}
七、Redis事务
数据库是一个面向多用户的共享管理系统,具备并发控制和封锁机制来保证数据库中数据的完整性
Redis事务简介
Redis事务的基本功能由MULTI、EXEC、DISCARD以及WATCH命令实现
-
MULTI命令用于开启Redis的事务,将客户端置为事务状态
-
EXEC命令用于提交事务,执行从MULTI到此命令自己的命令队列,并将客户端状态置位非事务状态
-
DISCARD命令用于取消事务,将事务队列中的命令清空和置客户端为非事务状态
-
WATCH命令用于监视键值对,使得EXEC命令需要有条件的执行,当被监视的键值对被其他客户端修改时,当前事务将执行失败
当开启Redis事务后向Redis事务队列中添加命令时命令格式错误(命令不存在,命令格式不对)时,当使用EXEC执行事务时将会直接失败,而当命令执行的对象不存在(键不存在)时,只有当前的命令会执行失败,其他命令依然能够成功执行
Redis事务不支持回滚机制
Redis事务的持久性:当使用AOF持久化方式并设置appendfsync=always时,可以保证Redis事务具有持久性
当AOF appendfsync=everysec时每隔一秒进行持久化命令不能保证Redis事务的持久性,RDB持久化方式也不能保证Redis事务的持久性
Redis 事务的实现过程
- 使用MULTI命令开启事务,将客户端状态置为事务状态(flags=REDIS_MULTI)
- 执行MULTI命令之后的所有的命令都不会立刻执行,而是加入到Redis的事务命令队列中,直到执行EXEC命令后才能从事务命令队列中依次执行命令(事务对列是一个先进先出的队列)
悲观锁和乐观锁
- 悲观锁:每次到数据库取数据时都认为这个数据会被其他客户端修改,因而每次都进行加锁而使其他客户端阻塞
- 乐观锁:每次取数据时都认为这个数据不会被其他客户端修改,因而不进行加锁,当采用了CAS机制,每次都进行比较,成功则操作数据,失败则直接放弃执行
事务的 WATCH命令
使用WATCH命令能够对特定的键值对进行监视,当被监视的键值对没有被其他客户端操作时才对该键值对进行操作,类似于CAS
每个Redis数据库都有一个watch_keys字典,该字典的键为被监视的数据对象,值为一个记录所有监视该数据对象的客户端链表,客户端有个REDIS_DIRTY_CAS标识,表示该客户端该某个键值对进行了监视
当执行SET等命令时,服务器都会调用touchWatchKey函数对watch_keys字典进行检查,以此来判断是否有其他客户端正在监视该数据对象
八、Redis消息订阅
消息订阅发布
发布者将消息发送到某一主题,订阅者从该主题中获取消息
PUBLISH
消息发布者将消息发送到指定的频道,返回接收到该消息的订阅者数量
命令格式:PUBLISH channel message
SUBSCRIBE
客户端订阅指定的频道,一旦客户端进入到订阅状态,就不能运行除SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE和PUNSUBSCRIBE之外的其他命令
命令格式:SUBSCRIBE channel [channel...]
PSUBSCRIBE
客户端以指定的模式来订阅相关频道,支持正则表达式
命令格式:PSUBSCRIBE pattern [pattern...]
PUNSUBSCRIBE
客户端以特定模式取消订阅相关频道
UNSUBSCRIBE
客户端退订指定频道,如果没有参数则退订所有已订阅的频道
命令格式:UNSUBSCRIBE [channel] [channel...]
PUBSUB
PUBSUB 是一个自检命令,用于检测发布订阅子系统的状态
底层
当一个客户端订阅一个或多个频道时,服务器会保存该客户端与频道之间的关系到pubsub_channels
字典里面,该字典的键是客户端订阅的频道名称,值为一个保存订阅该频道的客户端链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7bWHqAwV-1628564900829)(images/redis发布订阅.png)]
Redis消息队列
常见的消息队列有两种模式:消息发布订阅模式和生产者消费者模式
- 消息发布订阅模式:每个订阅了特定频道的客户端都能够接收到该消息
- 生产者消费者模式:只有一个消费者能够接收到该消息(客户端之间具有竞争关系)
九、Redis持久化
Redis支持两种持久化方式:AOF持久化和RDB持久化,AOF持久化方式会将每次执行的命令及时保存到磁盘中,而RDB持久化方式会根据指定的规则“定时”将内存中的数据保存到硬盘中,通常将两者结合起来使用
AOF持久化
AOF(Append Only File)持久化保存服务器执行的所有写操作命令到日志文件中,当服务器重启时,通过加装日志文件中的命令并执行这些命令来恢复数据
默认情况下AOF持久化没有被开启,可以通过修改配置文件redis.conf中的appendonly参数为yes来开启
appendonly yes
开启AOF持久化功能后,服务器每执行一条写命令,Redis就会将该写命令追加到AOF文件中,AOF文件的默认名称为”appendonly.aof“,可以通过命令appendonlyfilemane
来修改AOF文件的名称
appendonlyfilename "appendonlyfilename.aof"
-
AOF持久化的实现
-
命令追加:Redis服务器每执行一条写命令,就会将这条写命令追加到缓存区aof_buf中
在追加命令的时候,Redis并没有直接将命令写入到磁盘中,而是将命令追加到缓存区aof_buf中,以此来减小I/O操作,提供性能
-
AOF持久化文件写入(write)和文件同步(sync):根据appendfsync参数设置的不同同步策略,将缓存区aof_buf中的数据内容同步到硬盘中
Redis为AOF缓冲区的同步提供了多种策略,涉及操作系统的write和fsync函数,为提高文件的写入效率,当用户调用write函数将数据写入文件时,操作系统会先将这些数据暂存到一个内存缓存区中,当这个缓存区被填满或到时限后,才会将数据写入到磁盘文件中
-
-
AOF文件重写
-
目的:定期重写AOF文件,以达到压缩的目的
- AOF持久化的实现是通过保存被执行的写命令来保存数据库数据的,随着服务器的运行时间增长,AOF文件会越来越大,定期重写AOF文件可以减小AOF文件的大小
-
原理:AOF文件重写就是把Redis数据库里的数据转换为写命令,然后同步到AOF文件中,再重写的过程中,Redis服务器会重新创建一个AOF文件替换旧的AOF文件
-
AOF文件重写的功能
- AOF文件重写会丢弃过期的数据,丢弃无效的命令
- 将多条命令合并为一条命令
假如Redis数据库中的键包含的元素个数超过64个,那么需要使用多条命令来记录这个键的元素,即AOF文件一条命令最多保存64个元素
-
AOF文件重写的触发方式
- 手动触发:执行
BGREWRITEAOF
命令触发AOF文件重写 - 自动触发:通过设置Redis.conf中的参数
auto_aof_rewrite_percentage
和auto_aof_rewrite_min_size
的值,以及aof_current_size
和aof_base_size
的状态来确认何时自动触发
- 手动触发:执行
-
AOF文件处理
- 如果开启了AOF持久化功能,那么在Redis服务器启动的时候就会先加载AOF文件来恢复数据库数据
- Redis创建一个伪客户端,用于执行AOF文件中的所有写命令,因为只能在客户端的上下文中才能执行Redis的命令
- 伪客户端读取AOF文件,分析并提取AOF文件中的一条写命令
- 伪客户端执行读取出来的写命令
- 重复2、3步,直到AOF文件的所有的命令被读取完毕
- 如果开启了AOF持久化功能,那么在Redis服务器启动的时候就会先加载AOF文件来恢复数据库数据
-
RDB持久化
RDB持久化生成的RDB文件时一个经过压缩的二进制文件,称为快照文件,通过该文件可以还原Redis的数据库数据
- 原理:在指定的时间间隔内,RDB持久化可以生成数据集的时间点快照,即在指定时间间隔内,Redis会自动将内存中的所有数据生成一份副本并存储到磁盘中
- 过程:根据Redis配置文件redis.conf的配置自动进行快照
save 900 1
save 300 10
save 60 10000
save m n
:在时间m内被修改的键的个数大于n时自动执行快照生成RDB文件
save m n
的实现原理:Redis服务器每100毫秒自动执行serverCron函数,serverCron函数会自动遍历save m n
配置的保存条件,判断是否有一个条件满足,如果满足条件者执行BGSAVE命令生成RDB文件
- 用户在客户端执行SAVE或BGSAVE命令能够手动触发快照生成RDB文件,如果用户定义了自动快照条件,则执行FLUSHALL命令也会触发快照
FLUSHALL命令会情况数据库中的所有数据
- 快照实现过程
- Redis调用fork函数复制一份当前进程的副本(RDB进程)用于将内存中的数据写到硬盘上的一个临时的RDB文件中
- 当该RDB进程把所有数据写完后生成的RDB文件会替换掉旧的RDB文件
当Redis调用fork函数时,操作系统会使用写时复制策略,即两进程共享一个内存空间
在进行快照生成的过程中,Redis不会修改RDB文件,只有当快照生成后,旧的RDB文件才会被临时的RDB文件替换,同时旧的RDB文件被删除,在整个过程中,RDB文件是完整的
- RDB文件
- Redis将数据库快照保存到dump.rdb文件中
- 配置文件配置:在Redis配置文件中,dir用于指定RDB文件、AOF文件的所在的目录,dbfilename用于指定文件名称
- 命令修改:
CONFIG SET dir 文件路径
CONFIG SET dbfilename 文件名
如果Redis启动了AOF持久化功能,那么在启动服务器时会优先加载AOF文件来还原数据库状态,如果关闭了AOF持久化功能,则会加载RDB文件来恢复数据库数据
AOF与RDB
建议同时使用AOF和RDB持久化,以便最大限度保证数据的持久性和安全性
AOF:AOF文件体积较大,恢复数据慢,严重影响服务器的性能,但实时保存数据
RDB:恢复数据较AOF快
十、Redis集群
Redis集群的主从复制模式
在Redis中,通过执行SLAVEOF命令或通过配置文件设置slaveof选项,就可以让一台服务器去复制另一台服务器,被复制的服务器称为主服务器,对主服务器进行复制的服务器称为从服务器,从而实现当主服务器的数据更新时,从服务器能够根据策略和配置自动同步到从服务器中,其中master以写为中,slave以读为主
一个master服务器可以有多个slave服务器,slave服务器也可以是其他从服务器的master,但一个slave只能有一个master,实现主从复制的服务器的数据时一样的
自Redis2.8后,Redis采用异步复制,即复制功能不会阻塞主服务器,服务器仍然可以同时来处理来自客户端的命令请求,从服务器会以每秒一次的频率向主服务器报告复制流的处理进度
-
主从复制配置:必须通过命令或修改配置文件来实现
-
SLAVEOF命令实现主从复制
SLAVEOF ip port
:配置当前Redis服务器为ip:port上的Redis服务器的从节点- 使用
SLAVEOF no one
来解除主从服务器
-
修改配置文件实现主从复制
-
slaveof <masterip> <masterport> masterauth <master-password> slave-serve-stale-data yes //yes表示异步执行,可以同时处理客户端的命令请求 slave-read-only yes //slave是否支持只读 repl-diskless-sync no //是否使用socket方式来复制数据
-
Redis提供两种复制模式:diks和socket
- disk方式:master会创建一个新进程,先把RDB文件保存到硬盘中,再把硬盘中的RDB文件发送给slave,多个slave共享这个RDB文件
- socket方式:master创建一个新进程,直接把RDB文件以socket方式发送给slave,需要一个个slave发送RDB文件
命令方式实现主从复制功能优点在于不需要重启服务器,配置方式优点在于统一管理
-
-
主从复制的原理
自Redis2.8后,开始使用PSYNC命令来代替SYNC命令来执行复制时的同步操作
PSYNC命令具有全量同步和部分同步两种模式
-
全量同步:用于处理第一次复制的情况,通过主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里的写命令来进行同步
-
部分同步;用于处理从服务器离线后重新连接的复制情况,即同步从服务器中没有的数据
PSYNC命令所需要的资源远远少于使用SYNC命令所需要的资源,并且完成同步的速度也较快,SYNC命令需要生成、发送和加载整个RDB文件(即进行的是全量同步操作),而PSYNC进行部分同步时只需要将从服务器缺少的写命令发送给从服务器即可
-
-
部分同步模式的组成
- 主服务器复制偏移量和从服务器复制偏移量:通过比较主从服务器的偏移量来确认主从服务器的数据库状态是否一致,如果从服务器的复制偏移量少于主服务器的复制偏移量,则在进行部分同步时只需要将缺少的偏移量发送给从服务器即可,当主从服务器的复制偏移量相等时则证明两服务器数据一致
- 主从服务器的运行ID:每台Redis服务器都会有自己的运行ID,运行ID在服务器启动时自动生成的,由40位16进制的字符组成
- 在从服务器第一次对主服务器进行复制同步时,主服务器会将自己的运行ID发送给从服务器,从服务器会将主服务器的运行ID保存起来,当从服务器再次连接主服务器时可以进行身份验证
- 如果从服务器保存的运行ID与当前所连接的主服务器的运行ID一致,则证明该从服务器之前连接过自己,进行部分同步复制操作
- 如果从服务器保存的运行ID与当前所连接的主服务器的运行ID不一致,则证明该从服务器是第一次复制主服务器,则进行全量同步复制操作
- 在从服务器第一次对主服务器进行复制同步时,主服务器会将自己的运行ID发送给从服务器,从服务器会将主服务器的运行ID保存起来,当从服务器再次连接主服务器时可以进行身份验证
- 复制缓冲区:复制缓冲区是一个由主服务器维护的一个固定长度、先进先出的队列,默认复制缓冲区的大小为1MB
- 主服务器在执行写命令后,会将写命令发送给所有与它相连的从服务器,同时将这些写命令入队到复制缓冲区中
复制缓冲区保存最近发送的写命令以及每个写命令对应的复制偏移量,当从服务器执行PSYNC命令将从服务器的复制偏移量发送给主服务器时,如果从服务器的复制偏移量在复制缓冲区的范围内则进行部分同步,如果从服务器的复制偏移量在复制缓冲区范围外则进行全量同步操作
-
PSYNC命令的实现过程
- 如果从服务器初次复制主服务器,从服务器会向主服务器发送
PSYNC ? -1
命令,主动请求主服务器执行全量同步操作 - 如果从服务器已经复制过主服务器,则会向主服务器发送
PSYNC runid offset
命令 ,runid是上一次复制的主服务器的运行ID,offset是从服务器的复制偏移量,主服务器会根据这两个参数来判断是要执行全量同步还是部分同步操作
- 如果从服务器初次复制主服务器,从服务器会向主服务器发送
-
复制功能的实现步骤
-
通过SLAVEOF命令设置主服务器的IP地址和端口号
SLAVEOF 127.0.0.1 6380
Redis服务器会将客户端发送过来的IP地址和端口号保存到服务器状态的masterhost和masterport属性中
-
主从服务器建议套接字连接
主从服务器连接成功后从服务器会为这个套接字连接创建一个专门用于处理复制工作的文件事件处理器来完成复制功能,比如接收RDB文件
-
从服务器向主服务器发送PING命令
- 如果主服务器向从服务器返回“PONG”,就表示主从服务器之间的网络连接正常,可以完成复制工作
- 如果主服务器在规定时间范围内没有回复或PING出现错误,则表示网络连接异常或主服务器异常,不能完成复制操作,从服务器会断开这次连接
-
根据从服务器的配置判断是否需要进行验证
-
从服务器向主服务器发送端口信息(告知主服务器slave的端口)
-
从服务器向主服务器发送PSYNC命令完成同步功能
-
主从服务器之间进行命令传播,进而维持数据库状态一致
-
-
Redis读写分离
- 问题
- 复制数据延迟
- 读到过期的数据
- 在读写分离过程中从节点故障
- 问题
-
Redis心跳机制
在主从服务器之间进行命令传播的时候,从服务器默认会每一秒的频率向主服务器发送
REPLCONF ACK <replicaton_offset>
命令,用于检测主从服务器之间的网络连接情况,replication_offset是从服务器的复制偏移量
Redis集群的高可用哨兵模式
哨兵模式(Sentinel)是由一个或多个哨兵组成的哨兵系统,主要用于监控任意多台主服务器是否发生故障,以及监控这些主服务器的从服务器,当主服务器发生故障时,哨兵模式会通过选举的发生从主服务器下的从服务器中选举一台新的主服务器来代替之前的主服务器继续处理命令请求及完成相关工作,实现故障的自动转移实现高可用热部署的目的
-
哨兵模式(Sentinel)的配置
- 配置主从复制节点
port 6379 //主节点
port 6380 //从节点1 slaveof 127.0.0.1 6379
port 6381 //从节点2 slaveof 127.0.0.1 6379
- 配置开启Sentinel监控主节点
port 26379 //主节点的Sentinel //监控ip地址为127.0.0.1,端口号为6379,名称为mymaster的服务器,2表示需要其他2个Sentinel同意才能进行故障转移 sentinel monitor mymaster 127.0.0.1 6379 2 //判断该服务器恢复PING命令的时限为30秒来确认是否下线 sentinel down-after-milliseconds mymaster 30000 //该服务器故障后进行转移时最多可以有多少台服务器同时对新主服务器进行复制 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 #Generated by CONFIG REWRITE sentinel known-slave mymaster 127.0.0.1 6380 sentinel known-slave mymaster 127.0.0.1 6381
port 26380 //主节点的Sentinel sentinel monitor mymaster 127.0.0.1 6379 2
port 26381 //主节点的Sentinel sentinel monitor mymaster 127.0.0.1 6379 2
这三个端口的Sentinel都会监控主节点和两个从节点
启动命令
redis-sentinel sentinel-26379.conf
连接命令
redis-cli -p 26379
查看Sentinel信息
INFO sentinel
127.0.0.1:26379> INFO sentinel #sentinel sentinel_masters:1 sentinel_tilt:0 master0:name=mymaster,status=ok,address=127.0.0.1:6379,slave=2,sentinels=1
-
Sentinel命令
- Sentinel不能执行操作数据库的相关命令,常用命令有:
- SENTINEL master:用于查看哨兵监控的主服务器的相关信息和状态
- SENTINEL slaves : 用于列出指定服务器的所有从服务器的信息和状态
- SENTINEL get-master-addr-by-name :用于查看指定名称的服务器的ip地址和端口信息
- INFO:查看Redis的相关配置信息
- INFO sentinel:用于查看Sentinel的基本状态信息
- Sentinel不能执行操作数据库的相关命令,常用命令有:
-
哨兵模式的实现原理
-
启动Sentinel并初始化:在完成哨兵模式的相关配置后,需要启动Sentinel来监控相关的主从服务器
-
启动命令:
redis-sentinel sentinel.conf
或redis-server sentinel
-
Redis服务器保存了一个sentinelState的数据结构,保存了服务器中所有和Sentinel功能有关的状态
-
struct sentinelState{ char myid[CONFIG_RUN_ID_SIZE+1]; //Sentinel的ID uint64_t current_epoch; //当前纪元,用于实现故障转移 dict *masters; //保存了被Sentinel监控的主服务器的字典,键为主服务器的名字,值为指向sentinelRedisInstance结构的指针 }sentinel;
-
每个sentinelRedisInstance结构表示一个被Sentinel监控的Redis服务器实例,该实例可以是主从服务器,也可以是一个Sentinel
-
sentinel在监控主服务器后,它们之间会建立两个异步网络连接
- 命令连接:这个连接专用于向服务器发送命令,以此来保持与主服务器的通信
- 消息订阅连接:这个连接用于订阅主服务器的_sentinel_:hello频道消息,在这个频道中的消息并不会被保存到Redis服务器中,也就是说如果订阅该频道的服务器下线或其他原因接收不到消息,那么这条消息将会丢失
-
-
获取主从服务器的信息
- 在Sentinel启动并初始化后,默认情况下会每10s一次的频率通过命令连接向被监控的主服务器发送INFO命令并分析INFO命令返回的结果来获取主服务器的信息
-
Sentinel向主从服务器发送信息
PUBLISH _sentinel_:hello "<s_ip>,<s_sport>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
- s_ip:Sentinel的IP地址
- s_port:Sentinel的端口号
- s_runid:Sentinel的运行ID
- s_epoch:Sentinel当前的配置纪元,一个配置纪元就是一台新主服务器的配置版本号
- m_name:主服务器的名称
- m_ip:主服务器的IP地址
- m_port:主服务器的端口号
- m-epoch:主服务器当前的配置纪元
-
接收来自主从服务器的频道信息
- 如果信息记录的Sentinel的运行ID和自己的运行ID相同,说明该信息是自己发送的,不必理会
- 如果信息记录的Sentinel的运行ID与自己的运行ID不同,就说明该信息是由其他Sentinel发送的,则会根据信息中来对对应的主服务器的实例结构字典进行更新
-
监控主服务器的下线状态
- 默认情况下Sentinel会每秒一次的频率向它所监控的主服务器及其他Sentinel发送一条PING命令,来检查主服务器和其他Sentinel的运行状态
-
完成故障转移
- 当一台主服务器故障时,监控这台主服务器的多个Sentinel会进行协商,采用一定的规则和方法选举一个Sentinel领导来进行故障转移,选举出新的主服务器
- 一个主从复制结构可以由多个Sentinel监控,每个Sentinel监控到主服务器故障后,都会向其他监控该主服务器的Sentinel发送让自己成为Sentinel领导来完成故障转移,但只有一个Sentinel可以成为Sentinel领导,如果某个Sentinel被半数以上的其他Sentinel设置为领导后,那么这个Sentinel将会成为这些Sentinel的领导
- 在Sentinel领导选举完成后,就会进行主服务器的故障转移,Sentinel领导会选一个合适的从服务器作为新的主服务器,让这个slave发送
SLAVEOF no one
命令,并让其他的slave发送SLAVEOF
命令成为新主服务器的从节点 - 之后Sentinel会通知客户端新的Master是谁,避免客户端发送命令到旧的主服务器上,Sentinel依然会监控旧的主服务器,当旧的主服务器恢复正常后,就会让它成为新的Master的slave
- 合适的Slave
- 选择slave-priority参数值最大的slave节点
- 选择复制偏移量最大的slave节点,因为复制偏移量越大说明数据越完整
- 选择slave的运行ID最小的,运行ID越小说明它是最早的节点
- 当一台主服务器故障时,监控这台主服务器的多个Sentinel会进行协商,采用一定的规则和方法选举一个Sentinel领导来进行故障转移,选举出新的主服务器
-
-
Sentinel的下线状态
- 主观下线(SDOWN):指的是只有单个Sentinel认为该主服务器下线了
- 客观下线(ODOWN):多个Sentinel认为主服务器下线了,需要Sentinel之间发生消息来确认
-
Sentinel内部的定时任务
- 每个Sentinel节点会每10s对master和slave执行INFO命令,用于确认master和slave的关系
- 每个Sentinel每2s会通过master节点的_sentinel _:hello频道交换信息
- 每个Sentinel会每1s向其他Sentinel和Redis节点发送PING命令,用于检测节点的运行状态
Redis集群
Redis集群(Redis Cluster)是一个分布式、容错的Redis实现,由多个Redis节点组成,在多个Redis节点之间进行数据共享,Redis集群不支持多数据库功能,默认使用了0号数据库
-
优点:可以实现将数据自动切分到多个节点上,当集群中有部分节点故障而无法提供服务时,集群仍然能够完成相关的命令请求,Redis集群可以解决高并发,大数据量的问题
-
集群中的节点和槽
- 节点:一个集群通常由多个节点(Node)组成,通过使用命令
CLUSTER MEET <ip> <port>
来实现节点的连接,让节点加入集群,每个节点都是一台Redis服务器,在Redis服务器启动时会根据Redis配置文件的clutster-enabled
参数是否为yes
来决定是否开启服务器的集群模式,在开启集群模式的情况下,服务器会创建一个clusterState
类型的结构来保存当前节点视角下的集群状态(当前集群的节点数,各节点的槽),每个节点都有一个clusterNode
结构来保存自己的当前状态(创建时间,IP地址) - 槽:Redis集群为了能够存储大量的数据信息,采用分片的方式将大量数据保存各个节点中,而每个节点使用槽的概念来存储数据,每个节点负责一定范围的槽,每个槽映射一个数据集
- 节点:一个集群通常由多个节点(Node)组成,通过使用命令
-
搭建集群
-
配置
port 7000 cluster-enabled yes //开启集群模式 cluster-config-file node-7000.conf cluster-require-full-coverage no //是否需要所有节点都正常才提供服务
-
meet操作:
redis-cli -p 7000 cluster meet 127.0.0.1 7001
-
指派槽:
redis-cli -p 7000 cluster addslots 0
:为7000端口的节点指派一个槽 -
主从分配:
redis-cli -p <从节点端口> cluster replicate <主节点node-id>
:主节点的node-id可以通过redis-cli -p <主节点端口> cluster nodes
来获取
-
-
集群的消息
- PING消息:PING消息用于检测某个节点是否允许正常,默认情况下,集群中的每个节点每隔1s会从已知节点列表中随机选出5个节点发送PING消息来确认节点的状态
- PONG消息:当节点接收到PING或MEET消息后返回的消息来告诉其他节点自己运行正常,另外,一个节点也可以向其他节点广播发送PONG节点来刷新其他节点自己的最新状态
- MEET消息:请求节点加入到集群中
- FALL消息:节点下线消息
- PUBLISH消息:当一个节点接收到PUBLISH消息后会执行该消息中的命令
十一、Redis高级功能
慢查询
Redis慢查询日志功能用于记录服务器执行命令超过规定时间的命令的详细信息,分析慢查询日志来优化Redis的查询速度
- 慢查询的预设阈值slowlog-log-slower-than:规定执行时间超出多少微妙(us)的查询会被记录到日志中
- 慢查询日志的长度slowlog-max-len:规定服务器最多能保存多少条慢查询日志,如果日志数量超过该值,则在下一条日志被记录到慢查询日志里前会把第一条日志删除掉再进行记录
获取慢查询日志:SLOWLOG GET num
,num表示要获取日志的数量
慢查询日志由4个属性组成:慢查询ID、UNIX时间戳、命令执行的时长(微秒)、命令及命令参数
typedef struct slowlogEntry{
robj **argv;
int argc;
long long id;
long long duration;
time_t time;
sds cname; //客户端名称
sds peerid;
}slowlogEntry;
- 慢查询命令
- SLOWLOG GET n:获取指定数量的慢查询日志
- SLOWLOG LEN:获取慢查询日志的长度
- SLOWLOG RESET:清空慢查询日志
慢查询日志是保存在内存中的,服务器有专门的结构体类保存慢查询日志,需要定期对慢查询日志进行持久化
流水线(Pipeline)
Redis采用TCP协议来对外提供服务,请求一次响应一次,多次请求会十分损耗性能,可以使用管道技术来讲多个请求封装到管道中一次性请求,返回时也会将返回结果放到管道中一次性返回
地理位置的应用
在Redis3.2版本后引入GEO(地理位置功能),主要用于存储地理位置的经纬度,以及计算两个地理位置之间的距离,GEO使用Redis的ZSET来实现的
GEOADD location-set longitude latitude name [logtitude logtitude name...]