Loading... ## 一、Redis特点 单线程(6.0版本后支持网络连接多线程) 操作基于内存,读写数据不需要磁盘I/O,速度快 Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。 Redis的所有操作都是原子性的 Redis还支持 publish/subscribe, 通知, key 过期等等特性。 ## 二、数据结构 ### 1.String 适用于简单的key-value场景,如储存一条配置信息 | 命令| 描述 | ----- | ------- |SET key value| 设置指定key的值| |GET key | 获取指定key的值| |GETRANGE key start end| 返回key中字符串的子字符串| |SETEX key seconds value| 设值并添加过期时间| |SETNX key value |只有key不存在时设定key的值| |STRLEN key | 返回字符串值长度| |INCR key| 将key储存的数值增1| |INCRBY key increment| 将key储存的数值增加指定值| |DECR key| 将key所储存的值减1| |DECRBY key decrement| 将key所储存的数值减少指定值| |APPEND key value | 如果将指定值追加到原本值的末尾| ### 2.Hash Redis hash 是一个string类型的 field(字段)和value(值)的映射表,hash特别适合用于储存对象数据 key本身相当于一个哈希表名,字段和值都放在这个哈希表中。 |命令 |描述| ------- | ------ | HMSET key field1 value1 [field2 value2 ] | 同时将多个字段写入到key中(此时key相当于对象名,字段相当于对象属性)| |HSET key field value |设置指定key的指定字段的值| |HMGET key field1 [field2]| 获取指定key所有给定字段的值| |HGET key field |获取指定key中指定字段的值。| |HGETALL key| 获取在哈希表中指定 key 的所有字段和值| |HEXISTS key field| 查看哈希表 key 中,指定的字段是否存在。| |HDEL key field1 [field2]| 删除一个或多个哈希表字段| |HLEN key| 获取指定key中字段的数量| ### 3.List Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边) key相当于一个List名 适合存储一些有序且数据相对固定的数据, |命令| 描述| ------- | -------- |RPUSH key value1 [value2]| 在列表尾部添加一个或多个值| |LPUSH key value1 [value2]| 在列表头部添加一个或者多个值| |LSET key index value| 通过索引设置列表指定位置的值| |LRANGE key start stop| 获取列表指定范围内的元素| |LLEN key| 获取列表长度| |LPOP key |移出并返回列表的第一个元素| |RPOP key| 移出并返回列表的最后一个元素| |LREM key count value| 根据count值,移除列表中与value相等的元素。 `count > 0 : 从表头开始向表尾搜索,删除数量为count; count < 0 : 从表尾开始向表头搜索,删除数量为count的绝对值; count = 0 :移除表中所有与value相等的值`| |LTRIM key start stop| 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。| ### 4.Set Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。 Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 set提供了诸如交集、并集、差集操作,可实现查找两个人共同的好友等功能 |命令 | 描述| ------ | -------- |SADD key member1 [ member2 ]| 向集合添加一个或多个成员| |SCARD key |获取集合的成员数| |SDIFF key1 [ key2 ] |返回第一个集合与第二个集合的差异| |SISMEMBER key member |判断 member 元素是否是集合 key 的成员 |SMEMBERS key |返回集合中的所有成员 |SINTER key1 [key2] |返回给定所有集合的交集(intersection) |SUNION key1 [key2] |返回所有给定集合的并集(union) |SPOP key |移除并返回集合中的一个随机元素 |SREM key member1 [member2]| 移除集合中一个或多个成员 |SMOVE source destination member| 将 member 元素从 source 集合移动到 destination 集合| |SDIFFSTORE destination key1 [key2]| 返回给定所有集合的差集并存储在 destination 中| |SINTERSTORE destination key1 [key2]| 返回给定所有集合的交集并存储在 destination 中| |SUNIONSTORE destination key1 [key2]| 所有给定集合的并集存储在 destination 集合中| ### 5.Sorted Set(ZSet) Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。 有序集合的成员是唯一的,但分数(score)却可以重复。 底层实现是skiplist (跳跃表) 每个member不仅对应一个指定的score,还会根据先后顺序自动对应一个从0开始的索引。-1 可以代表最后一个成员。该索引是实时更新的,于分数值有关, 它相当于一个分数排名!!!只不过这个排名是从0开始的。 |命令 |描述| ------ | --------- |ZADD key score1 member1 [score2 member2] |将一个或多个成员元素及其分数值加入到有序集当中。如果某个成员已经是有序集的成员,那么更新这个成员的分数值,并通过重新插入这个成员元素,来保证该成员在正确的位置上,分数值可以是整数值或双精度浮点数;如果有序集合 key 不存在,则创建一个空的有序集并执行 ZADD 操作;当 key 存在但不是有序集类型时,返回一个错误。| |ZCARD key |获取有序集合的成员数| |ZCOUNT key min max| 计算在有序集合中指定区间分数的成员数| |ZINCRBY key increment member| 有序集合中对指定成员的分数加上增量 increment| |ZRANGE key startindex stopindex [WITHSCORES] [REV]|返回指定索引区间有序集成员。默认返回成员按照分数值从小到大排列。添加withscores 后会在返回值中添加对应分数值。添加 rev会反向排列| |ZRANGEBYLEX key min max |返回在指定字典区间有序集成员。返回值排列顺序是按照分数值从小到大排列。min 字典中排序位置较小的成员,必须以`[`开头,或者以`(`开头,可使用`-`代替;max 字典中排序位置较大的成员,必须以`[`开头,或者以`(`开头,可使用`+`代替 | |ZRANGEBYSCORE key min max [WITHSCORES] | 返回指定分数区间的有序集集成员。排列顺序为分数值从小到大。min 较小的分数值,默认为大于等于该值,可以通过添加前缀`( `使之变成大于 。max 较大的分数值,默认为小于等于该值,可以通过添加前缀`(`使之变成小于。| |ZRANK key member| 返回有序集合中指定成员的索引| |ZREM key member [member ...] |移除有序集合中的一个或多个成员| |ZREMRANGEBYSCORE key min max |移除有序集合中给定的分数区间的所有成员| ## 三、 Redis高可用 Redis 通过复制、哨兵、集群,三种技术实现高可用: * 基于主从复制实现数据的备份,可用于实现读写分离; * 基于哨兵实现主从切换,主服务器不可用时执行故障转移,解决主服务器的单点问题; * 数据量大且单类数据可能超过单 redis 服务能力时,可用集群解决,最多可组建拥有2048*8个master节点的redis 集群; ### 主从 master-slave redis主从模式(master-slave,为保政治正确,已改名master-replica),提供了除持久化外另一种数据的热备功能,也为读写分离提供了途径; redis主从模式通过复制功能实现,redis提供了SLAVEOF(REPLICAOF),让一个服务器(slave)去复制另一个服务器(master); **复制功能的开启**: 通过客户端向服务器发送指令:slaveof(replicaof) host port ,或者在slave配置文件中配置 replicaof 选项; **注**: slaveof(replicaof) host port 是异步命令,当服务器收到该命令后,会先将host/port保存到服务器状态(redisServer)的masterhost及masterport属性里,然后回复 OK,再开始执行真正的复制操作; **redis的复制功能的实现** 包括 同步(SYNC/PSYNC) 和 命令传播(command propagate) 两个操作: * 在刚开启主从同步功能或者断线重连时,使用同步命令让slave的数据状态跟master保持一致; * 同步后,若master执行写命令,状态又将不一致,通过命令传播让slave执行同样的命令,使状态保持一致; **同步**: * SYNC命令:redis2.8之前使用,slave向master发送sync命令,master会执行bgsave命令生成rdb文件,然后把rdb文件发送给slave,slave通过rdb恢复数据,达到与master一致的状态; * PSYNC命令:因为SYNC命令只能执行全量同步,对于slave断线重连后只需要执行部分重同步就可以达到一致的情况,仍使用SYNC命令有很大的浪费,因为在redis2.8中,提供了 PSYNC 命令来取代 SYNC 命令 **心跳检测** 在命令传播阶段,slave 会定时向 master 发送心跳信息,默认每秒一次,命令格式: REPLCONF ACK <offset> 作用: * 检测网络通信状态; * 辅助实现 min-slave 选项; * 检测命令丢失: 心跳信息会带有slave的offset,master收到心跳后,可以与自己保存的offset对比,大与收到的offset,说明有命令传播失败; 步骤: 1. slave 收到 SLAVEOF <host> <port>命令,设置 master 的地址端口信息,然后回复OK; 2. 向 master 建立套接字连接; 3. 发送 PING 命令; 4. 身份验证,如果 slave 有 masterauth 配置; 5. slave 发送自身端口信息; 6. 进行同步; 7. 命令传播; 注:master 和 slave 需要互为对方的客户端,因为彼此都要向对方发送命令; ### 哨兵 Sentinel master-slave方案解决了数据的复制问题,但是 当 master 宕机时,slave 并不会自动切换为新的 master,以继续提供服务,于是,Sentinel System 有了用武之地; 由一个或多个 Sentinel 实例组成的 Sentinel System,可以监视任意多个 master 及其 slave,并行使以下职责: * 监视各服务器运行状态; * 发现异常进行通知; * master 下线后从其 slave 中选举新 master,进行故障转移; * 下线的 master 上线后,将其降级成新 master 的 slave; **哨兵功能的总结**: 1. 每个 Sentinel 可监视任意多个服务器,每个服务器也可被任意多个 Sentinel 监视; 2. 多个监视同一主服务器的 Sentinel 视为一个集群,在被监视主服务器下线后,该集群将选举出一个 Sentinel Leader,由该 leader 对其进行故障转移; **故障转移**: 1. 选举 Sentinel leader,用于执行故障转移; 2. 由 Sentinel leader 从故障主服务器的所有从服务器中选一个做新的主服务器; 3. 向选出的新 master 发送 slaveof no one命令,然后一次次的发送INFO命令查看服务器的role角色,当变成 master 说明升级成功; 4. 向其它slave服务器发送slaveof命令,让它们复制新的服务器; 5. 若下线的主服务器上线,则发送slaveof命令,让其降级为从服务器,复制新的主服务器; **Sentinel节点感知**: * Sentinel 会向被监视服务器发起两条连接,一条命令连接,一条订阅连接; * 建立订阅频道后,会在被监视服务器上订阅 —sentinel—:hello频道,并以 2S 一次的频率,向该频道发送消息,以向其它监视该服务器的Sentinel宣示自己的存在; * Sentinel 通过接收订阅消息并分析,获知与自己监视同一服务器的其它 Sentinel,并在被监视主服务器下线时,与其它 Sentinel 进行选举选出leader,进行故障转移; **判断下线(主观下线、客观下线)**: * Sentinel 以每秒一次的频率向其它实例(包括主、从、其它Sentinel)发送PING,并根* 据回复判断服务器是否在线,当一个实例在指定的次数中不能返回有效回复时,会将这个服务器判断为 主观下线 * 判断一个主服务器进入主观下线后,向同样监视这个主服务器的其它Sentinel询问,看是否同意这个主服务器进入主观下线状态; * 当足够多的 Sentinel 判断主服务器进入主观下线后,将这个主服务器判断为 客观下线 发现主服务器进入客观下线状态后,发起一次故障转移操作; **选举哨兵头领 Sentinel Leader**: * 所有节点都会广播一条设置信息,要求所有节点调自己为局部leader; * 收到广播的节点,若尚未设置自己的局部leader,则按广播设置其为自己的局部leader,并回复OK,否则回复失败; * 广播的节点按收到的回复,比对是否同一纪元,统计回复OK的数量,若超过半数,则成为全局Leader; * 若一纪元没有一个节点获得半数以上,则休眠一个随机时间,纪元加一,再次选举,直到选出全局leader; **选出新的主服务器**: * 排除下线或者断线的从服务器; * 排除最近5秒没有回复过 leader Sentinel 的 INFO 命令的从服务器; * 排除与已下线服务器连接断开超过 down-after-millinseconds * 10的从服务器; * 根据从服务器优先级,对剩余从服务器排序,选出优先级最高的; * 若优先级最高的有多个,选出其中复制偏移量最大的; * 若优先级最高的、偏移量最大的,仍有多个,则按 runid ,选最小的; ### 集群 cluster 通过主从与哨兵,redis即可实现高可用,然额,仍然存在一个问题,单台服务器的内存是有限的,不够用怎么办?redis有缓存淘汰机制,可以解决一部分问题,但业务需求是无限的,当不能过期与淘汰的数据大到一台主机不够用时,怎么办呢?SO,跟所有其它分布式系统一样,还需要横向扩展能力,幸好,自redis3.0开始,开始提供集群功能; **启动集群**: 当redis 服务器以集群模式启动时,即成为一个 节点 ,默认运行在一个只包含自己的集群中,使用cluster meet <host> <port> 命令,可以让服务器把指定的节点加入到自己所在的集群中,假设向服务器 A 发送命令:cluster meet 127.0.0.1 12345,假设监听端口12345 的服务器为B,那么节点A和节点B将首先进行**握手** : 1. A 为B 创建一个clusterNode结构,并添加到自己的clusterState.nodes字典中; 2. A 根据 cluster meet 命令指定的地址和端口,向节点B发送meet消息; 3. B 节点收到 A 的消息,为节点A创建一个clusterNode结构,并添加到自己的clusterState.nodes结构中,然后向 A 回复一条 PONG 消息; 4. A 收到 PONG消息可知 B 已收到自己的握手消息,再次发一条确认消息 PING; 5. B 收到 A 发送的 PING消息,握手完成; 6. 完成握手后,A 会通过Gossip协议向集群内的其它节点发送节点B的消息,其它服务器也会与B进行握手,最终,集群内所有服务器都将感知其它服务器,保存有其它服务的clusterNode结构,并与之建立通信; **槽指派**: redis 集群内部,通过分片的方式来保存键值数据,每个分片称之为一个槽(slot),共有0-16383共计16384个槽(2048 * 8); 集群建立后,处于未上线状态,需要进行槽指派后,才上线并开始提供服务; **集群命令执行**: 集群中因为数据分片存储,执行命令的过程稍微有一点差异:收到命令后,先对key进行hash映射取得该Key的槽号,然后判断该槽是否归自己处理,若是则执行命令,否则取得负责该槽的节点,返回一个MOVED错误,并把该节点信息返回,引导客户端向正确的节点请求服务; * 该槽归自己处理,进行处理; * 该槽不归自己处理,返回一个MOVED错误,并把负责处理该槽的节点信息返回,引导客户端向正确的节点请求服务; * 该槽数据目前正在迁移: * 首先在自己的库中查找该键,若存在,处理; * 若不存在,返回ASK错误,并给出新节点信息,引导客户端向新节点请求服务;客户端需要先向新节点发送asking命令,再发送正式的命令,否则将会收到一个moved错误 注:正在被导入的槽和数据,不算归自己管,只有导入完成后,才会集群内广播 **关于集群的内部结构**: * clusterNode: 表示一个节点 * clusterLink: 表示一个节点的连接信息 * clusterState: 每个节点都保存着一个clusterState,记录了以当前节点为视角,集群目前所处的状态,如是否在线、包含多少节点、当前配置纪元等等;其中两个属性记录了所有槽指派信息: * clusterState.slots: char数组,长度2048(16384/8),用每一个二进制位表示一个槽是否归当前节点处理,为0表示不归我管; * clusterState.numslots:当前节点负责处理的槽的数量,即slots数组中位的值为1的数量; * clusterState.nodes:clusterNode结构数组,长度16384 **为什么是16384个槽** :由记录槽指派信息的结构可知,其实是2048,redis节点维护一个长度2048的char数组,用数组元素中的每一位表示一个槽,而char的长度为8位,于是共可以存储2048*8=16384个槽; 最后修改:2021 年 12 月 27 日 09 : 52 AM © 允许规范转载