1 简介
本文介绍缓存服务redis的集中集群方式。哨兵Sentinel提供高可用性,监控主从服务器并在故障时切换。分布式集群支持1000节点,采用分片、复制和故障转移。
集群使用异步复制,牺牲强一致性以保证性能。消息转发通过PUBLISH/SUBSCRIBE实现,支持事务和Lua脚本,提供慢查询日志和Monitor监控。
集群通过节点握手、槽位分配、MOVED/ASK错误处理确保可靠性。
2 集群和插槽分片
主从复制
用户可以通过执行SLAVEOF指令或者设置slaveof,让一个服务器去复制replicate 另一个服务器master
对主服务器进行复制的服务器被称之为从服务器 slave进行复制的主从服务器双方的数据库将保持相同的数据,概念上这种现象称之为: 数据库状态一致。
因此,在主服务设置某个键,可以到从服务获取这个键的信息
比如主服务设置SET name jack
从服务获取
GET name jack
删除的时候,如果从主服务删除,从服务也将被删除,这也就是状态的一致的表现。
PSYNC 从服务器请求复制主服务器数据。
如果从服务器没有复制过主服务器数据,或者执行过 SLAVEOF no one指令,
那么从服务器在开始向主服务开始一次新的服务指令为 PSYNC ? -1
主动请求主服务器进行完整重同步。
如果从服务器已经复制了某个主服务器,那么从服务器在开始一次新的复制时,将向主服务器发
PSYNC runid offset 指令
runid 为上一次复制的主服务器运行id,而offset则是从服务器当前复制偏移量。
接受这个指令的主服务器通过这两个参数判断应该对从服务器执行哪个操作。
高可用:哨兵Sentinel
在集群功能出现之前使用哨兵模式,可以提供一定的可靠性。
一个或多个Sentinel实例组成一个系统,监视任意多个服务器,这些服务器之下可能有多个从服务器。
在主服务器下线后,从服务器将被哨岗服务升级为新的主服务。
在 5以下的旧版本,支持redis-sentinel
在 5之上的版本,redis-server
3 分布式集群
- 高可用:分布式集群
性能:支持1000 个节点的集群
安全:redis系统尝试 保留来自大多数主节点连接的客户端写入. 确认的写入可能会丢失.
可用性:大多数主节点可访问时 可存活下来. 而当主节点下线时,复制这个主节点的从节点将代替主节点处理数据。
特点:
可自定义的插槽分片指令
可用执行集群命令
可重新分片
故障转移功能支持
消息转移功能支持
集群通过分片 sharding 进行数据共享,并提供复制和故障转移功能。
1 配置和启动 至少3*1 节点
查看上一节。
2,Cluster数据分片
默认redis cluster不使用 一致散列算法,而是一种不同形式的分片。每个键在概念上都是散列槽的一部分。
redis 集群有16384个散列槽。 要计算给定键的哈希槽是哪一个,我们只需取密钥的 CRC16 模数值 16384
redis每个节点都负责哈希hash 槽的一个子集,您可能有一个包含3个节点的集群
节点A包含从0到5500的哈希槽
节点B包含从5501 到 11000的哈希槽
节点C包含从11001 到16383的哈希槽
这允许在集群中轻松添加和删除节点。如果我想添加一个新节点D,我需要将一些哈希槽从节点A,B,C移动到D。
如果我想从集群中删除节点A,可以移动A提供的哈希槽到B或C,当节点A为空时,我可以将其从集群中完全删除。
由于移动 hash槽不需要停止操作,所以添加删除,更改节点持有的哈希槽百分比不需要任何停机时间。
同时redis支持多键操作(事务,lua脚本)所有键都属于同一个哈希槽,用户可以使用 散列标签 的概念强制多个键成为同一个散列槽的一部分。
散列标签纪录在redis 集群规范中,但如果键中{} 中有字符串,则仅对字符串内容进行散列。
如 this{foo}key,another{foo}key保证在同一个散列槽中,并且可以在具有多个键为参数的命令中一起使用
3,主从模式
用于灾备恢复
集群主节点A,B,C与从节点A1,B1,C1组成,任何主节点故障,从节点成为主节点,系统将继续允许。
4, 集群一致性保证
使用异步复制,没有很强一致性保证,
客户端写入主A
主A想您的客户端回复 OK
主设备A将写入传播到它的从服务A1,A2...
A回复客户端时,不会等到全部从设备的确认回复。
如果强制 数据刷新到磁盘后才回复 客户端,那就相当于同步复制。需要在性能与一致性之间权衡。
redis的绝对同步写入数据到磁盘。 指令 wait 使得丢失写入的可能性大大降低。 但是在复杂的环境仍然 有可能将无法接收写入的从站选为主站。
官方解决办法为红锁。
写入的最大窗口:
如果分区的多数侧已经有足够的时间来选举一个从属 作为主,那么少数侧的每个主节点都将停止接受写入。
最大窗口的时间量 是一个非常重要的配置指令 称为节点超时。
节点超时后,主节点被视为发生故障,此时可以 用其副本之一替换。 同时将无法感知其他主节点,也不再接受写入。
集群的主要可靠性保证方式,特征如下:
1 集群节点通过握手将其他节点添加到自己所处的集群中。
2 集群中16385个槽可以分别指派给集群各个节点,每个节点都记录哪些槽位分配给自己,哪些给了其他节点。
3 节点在接到一个指令请求后,先检测这个指令请求要处理的键所在的槽位是否自己负责,不是的话将向客户端返回 MOVED错误和正确节点
4 集群重新分片工作是redis-trib负责的,重新分片的关键术语某个槽位的所有键值对从一个节点转移到另一个节点。
5 如果节点A正迁移槽位i到节点B,那么当节点A没有在自己的数据库找到命令指定的数据库键时,节点A向客户端返回ASK错误和节点B
6 MOVED错误表示槽位的负责权已经从一个节点转移到另一个,ASK错误指示两个节点迁移槽位的临时措施。
7 集群的从节点用于复制主节点,在主节点下线后,代替主节点继续处理数据。
8 集群节点通过发送和接受消息进行通信,常见的包括 MEET,PING,PONG,PUBLISH,FAIL 五类。
4 消息转发和事务处理
高效的发布和订阅
以PUBLISH,SUBSCRIBE,PSUBSCRIBE指令组成
执行SUBSCRIBE指令,客户端可以订阅一个或多个频道,从而成为这些频道的订阅者 subscriber,
每当有其他客户端向被订阅频道发消息,频道的全部订阅者都会收到该消息。
一个客户端发布,允许多个客户端 多次消费
即发即弃: 非持久化消息机制,发布者和订阅者必须同时在线。
不保证数据完整性。
不支持消息确认机制 Ack/Nack/Reject 需要自己在应用层面实现,但如果这样,可以直接使用MQ,ZeroMq,RobbitMQ,Kafka(需要另启用独立服务)。
- 数据库的变更通知:通过发布订阅获知数据库的变更通知。
例: 获取 0号默认数据库的全部删除通知
SUBSCRIBE "_ _keyevent@0_ _:del"
默认情况下,所有通知都是禁用的,因为大多数用户不需要 这个功能,而且这个功能有一些开销。
请注意,如果你没有指定K或E中的至少一个,就不会有事件被传递。
可以通知Pub/Sub客户在钥匙空间中发生的事件。 这个功能的文档在http://redis.io/topics/notifications
例如,如果钥匙空间事件通知被启用,并且一个客户端 对存储在数据库0中的密钥 "foo "进行DEL操作,两个 消息将通过Pub/Sub发布。
# PUBLISH __keyspace@0__:foo del
# PUBLISH __keyevent@0__:del foo
可以选择Redis将在一组的类。每个类都由一个字符来标识。
# K 关键空间事件,以 __keyspace@<db>__ 前缀发布。
# E 关键事件,用__keyevent@<db>__前缀发布。
# g 通用命令(非特定类型),如DEL, EXPIRE, RENAME, ...
# $ 字符串命令
# l 列表命令
# s 设置命令
# h 哈希命令
# z 排序的集合命令
# x 过期事件(每次密钥过期时产生的事件)。
# e 驱逐事件(当一把钥匙被驱逐到最大内存时产生的事件)。
# A g$lshzxe的别名,因此 "AKE "字符串表示所有事件。
"notify-keyspace-events "的参数是一个字符串,它由由零或多个字符组成。
空字符串意味着通知被禁用。
例子:要启用列表和通用事件,从事件名称的角度来看事件名称,使用。
notify-keyspace-events Elg
例子2:获取订阅频道的过期密钥流
name __keyevent@0__:expired use:
notify-keyspace-events Ex
数据库事件通知执行过程如下
1 server.notify_keyspace_events 属性就是服务器配置
notigy-keyspace-events 选项所设置的值
2 如果给定的通知时服务器允许的,那么下一步将检查服务器是否允许发生键空间通知。
如果可以则构建并发生
3 最后,函数检测服务器是否允许发生键事件通知,如果允许,程序构建并发出
PUBLISH的底层实现函数就是 pubsubPubishMessage函数。
- 支持事务
通过MULTI,EXEC,WATCH指令实现事务 transaction。
事务提供一种将多个指令请求打包,然后一次性,按顺序执行的机制,
并且在事务执行期间,服务器不会中断事务而改去执行其他客户端命令。
它需要执行完成事务的全部指令后,才处理其他客户端命令请求。
比如一个典型的事务执行过程:
MULTI,
SET name jack,
GET name,
SET age 55,
GET age,
EXEC.
- 扩展脚本Lua
通过在服务器嵌入Lua脚本的支持,客户端可以使用Lua脚本在服务器原子地执行Redis指令。
127.0.0.1:6379> EVAL "return 'hello,wrold'" 0 "hello world"
"hello,wrold"
EVALSHA 指令对执行过的脚本做校验和验证。
管理指令包括 SCRIPT FLUSH,
127.0.0.1:6379> SCRIPT FLUSH
OK
127.0.0.1:6379> SCRIPT EXISTS
(empty list or set)
127.0.0.1:6379> SCRIPT LOAD
127.0.0.1:6379> SCRIPT KILL
- 排序
排序指令可以对列表键,集合键,有序集合的值进行排序。
支持数字和字母 升降序排序。
默认情况下,排序是数字的,元素按解释为双精度浮点数的值进行比较。这是SORT最简单的形式:
SORT mylist
假设mylist是一个数字列表,此命令将返回相同的列表,其中的元素从小到大排序。为了将数字从大到小排序,使用DESC修饰符:
SORT mylist DESC
当mylist包含字符串值并且您想按字典顺序对它们进行排序时,请使用ALPHA修饰符:
SORT mylist ALPHA
Redis 支持 UTF-8,前提是您正确设置了LC_COLLATE环境变量。
- 二进制位数组
缓存提供了 SETBIT, GETBIT, BITCOUNT, BITOP 四个指令用于处理二进制位数组
SETBIT 用于为位数组制定偏移量的二进制位设置值,位数组的偏移量从0开始计数,二进制位值可以为0 或 1
SETBIT bit 3 1
(integer) 0
GETBIT 用于获取位数组制定偏移量的二进制值。
GETBIT bit 3
(integer) 1
BITCOUNT 用于统计位数组,值为1的二进制为数量。
BITCOUNT bit
(integer) 2
BITOP 对多个位数组按位与,或,亦或运算
SETBIT bit 3 1
(integer) 1
127.0.0.1:6379> SETBIT bit 1 1
(integer) 0
127.0.0.1:6379> SETBIT z 2 1
(integer) 0
127.0.0.1:6379> SETBIT z 0 1
(integer) 0
BITOP AND and-result bit z
(integer) 1
127.0.0.1:6379> BITOP OR or-result bit z
(integer) 1
127.0.0.1:6379> BITOP XOR xor-result bit z
(integer) 1
慢查询日志
慢查询日志用于记录执行事件超过给定时长的命令请求,用户也可通过此功能产生日志去监视和优化查询速度。127.0.0.1:6379> CONFIG SET slowlog-log-slower-than 0 OK
慢查询日志最多记录5个,CONFIG SET slowlog-max-len 5
127.0.0.1:6379> CONFIG SET slowlog-max-len 5
OK
127.0.0.1:6379> SET msg "hello"
OK
127.0.0.1:6379> SETBIT z 2 1
(integer) 1
127.0.0.1:6379> SET database "mysql"
OK
使用SLOWLOG GET指令查看服务器保存的慢查询日志:
127.0.0.1:6379> SLOWLOG GET
1) 1) (integer) 9
2) (integer) 1675563638
3) (integer) 28
4) 1) "SET"
2) "database"
3) "mysql"
5) "127.0.0.1:55216"
6) ""
2) 1) (integer) 8
2) (integer) 1675563624
3) (integer) 5
4) 1) "SETBIT"
2) "z"
3) "2"
4) "1"
5) "127.0.0.1:55216"
6) ""
3) 1) (integer) 7
2) (integer) 1675563621
3) (integer) 40
4) 1) "SET"
2) "msg"
3) "hello"
5) "127.0.0.1:55216"
6) ""
4) 1) (integer) 6
2) (integer) 1675563605
3) (integer) 5
4) 1) "CONFIG"
2) "SET"
3) "slowlog-max-len"
4) "5"
5) "127.0.0.1:55216"
6) ""
5) 1) (integer) 5
2) (integer) 1675563598
3) (integer) 15
4) 1) "CONFIG"
2) "SET"
3) "slowlog-max-than"
4) "5"
5) "127.0.0.1:55216"
6) ""
- 监视器 monitor
通过执行monitor命令,客户端可以将自己变成监视器。 实时接收当前服务或数据库的执行情况。
监视数据库0,默认数据库
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> monitor
OK
监视数据库10
127.0.0.1:6379> select 10
OK
127.0.0.1:6379[10]> monitor
OK
5 小结
企图在一个篇幅内说完缓存服务的结构注定是徒劳的。
但是读者可以大致一览,其实现的思想,对底层具体实现有一个了解,在执行和使用redis指令时将更清晰它是怎样运行的,
有助于定位问题和提高功能实现效率。
参考:
https://redis.io/commands/sort/
www.lua.org/manual/5.x/manual.html
skip lists A probabilistic Alternative to BST
<C语言实现> 1~4章节
<Redis设计与实现>