一、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

作用:

  • 检测网络通信状态;
  • 辅助实现 min-slave 选项;
  • 检测命令丢失: 心跳信息会带有slave的offset,master收到心跳后,可以与自己保存的offset对比,大与收到的offset,说明有命令传播失败;

步骤:

  1. slave 收到 SLAVEOF 命令,设置 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  命令,可以让服务器把指定的节点加入到自己所在的集群中,假设向服务器 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 日
如果觉得我的文章对你有用,请随意赞赏