Redis(三)

Redis(三)

Redis集群

单机出现的问题

前面讲了redis单机情况下的配置和持久化, 但是单机情况下有一些性能上的障碍是绕不过去的.

  1. 单机时, redis故障服务停止
  2. 单机容量有限
  3. 单机计算压力大

AKF理论

为了解决redis单机的问题, 有一个AKF的理论:

AKF扩展立方体(Scalability Cube),是《架构即未来》一书中提出的可扩展模型,这个立方体有三个轴线,每个轴线描述扩展性的一个维度,他们分别是产品、流程和团队:

  • X轴 —— 代表无差别的克隆服务和数据,工作可以很均匀的分散在不同的服务实例上;

  • Y轴 —— 关注应用中职责的划分,比如数据类型,交易执行类型的划分;

  • Z轴 —— 关注服务和数据的优先级划分,如分地域划分。

即, 我们可以在x轴,扩展多个redis实例. 当主redis挂掉后不至于停止服务, 从redis可以继续提供服务.

后来发现既然redis都已经启动了, 索性也不要浪费, 由主redis提供写操作, 从redis提供读操作.

但是因为在X轴是全量的克隆, 原本redis有10G大小, 复制也是有10G的大小, 存储的数据都是一样的, 并不能解决容量有限的问题.

因此可以在Y轴进行扩展, 对数据进行按照功能业务进行划分, 不同的数据存入不同的redis, 这样可以提升redis可存储数据大小. 同样在拆分出的redis也可以进行x轴的扩展.

如果业务继续进行扩大, 可以将每个主redis继续进行拆分, 比如根据用户的id进行拆分, Z轴上的redis依然是提供相同业务的服务, 但是根据用户的信息使他们访问不同的同一z轴上的redis, 这样对redis的容量进行了进一步的扩展.

这样的解决方案看起来很美好, 但是解决一个问题又引入了其他问题:

通过AKF进行X轴镜像复制, 如何保证主从redis的数据一致性

  1. 当客户端对主redis进行写操作后, 主机并不直接返回结果, 而是阻塞等待所有的从节点都确认一致后再返回.

    这个方案是强一致性的. 但是这个方案成本极高, 极有可能会破坏可用性. 如果主redis对从redis进行同步时, 因为网络或其他原因导致返回过慢, 比如需要10m才返回, 这时客户端要等待10m, 但是网络通信基本都会有超时的概念.

    我们进行X轴的扩展本来就是为了解决单点故障时的可用性, 但是因为上面的原因, 无法保证可用性, 所以上面的方案只是看起来美好,实际成本过高.强一致性, 会破坏可用性

  2. 那如果在主机写成功后立即给客户端返回结果, 然后再向从机中同步, 提高可用性. 但是可能会发生从机一直写失败的情况, 导致部分数据丢失.

    这个方案因为是异步同步, 所以客户端体验很好, 但是因为异步同步可能导致失败, 最终造成数据丢失.

  3. 如果在主机和从机中间添加一层, 这一层要足够的快, 足够可靠, 而且可以持久化. 这样主机和中间层中间同步数据时是同步式的, 因为足够快, 所以同步数据结束后就可以向客户端返回. 而从机最终可以和中间层达成同步, 达成数据的最终一致.

    因为客户端什么时候请求数据我们无法预测, 从机和中间层什么时候同步数据完成我们也无法预测, 所以可能会出现客户端请求时发生数据不一致的情况, 所以redis也可以强调成强一致性.

主从&主备

主备是客户端不会访问备用机, 只会访问主机.

主从是客户端不光会访问主机也会访问从机.

redis两种都可以选择, 更倾向于主从, 主从复制.

监控

这时当主机挂掉了以后, 客户端如果没有做改变, 依然是访问已经挂掉的主机, 服务依旧不可用.

所以需要对主机做HA 高可用.

当主机挂掉后, 使一个从机变成主机, 其他从机追随新的主机, 客户端也访问新的主机.

这个步骤可以手动完成, 但是人并不能时时刻刻盯着服务器是否宕机, 所以需要一些自动的方式完成这些工作.

那么, 启动一个监控程序来监视redis主机是否宕机? 既然是程序, 也有可能会发生宕机的情况, 就像单机redis一样, 我们并不希望监控程序挂掉, 那么我们可以开启多个监控程序同时监视redis主机.

什么时候算主机宕机呢? 每个监控程序都认为主机挂了, 那么就认为主机挂了, 所有监控程序都认为主机还活着, 那么就认为主机活着.

如果监控程序中有的认为活着有的认为已经挂了呢? 这个判断条件太强, 可能仅仅是一个监控设备的网络有问题, 但是实际服务是正常, 大部分监控也是正常, 这时候我们应该认为主机的正常工作的, 是主机和那一个监控之间的连接出现了问题. 也有可能是大部分监控服务都已经断掉, 仅仅有少量的监控仍检测到主机存活, 这时应该认为主机已经挂掉, 而不应该判断为存活.

那么这一部分的取值范围是多少呢?

要求的超过半数的监控认为redis挂掉才会认为是挂掉了.

为什么呢?

如果有挂掉和存活的数量各占一半, 那么redis主机到底是判断存活还是挂掉. 如果有一主二从, 三台redis, 主挂掉了, 两个从各获得一半的投票, 这种情况就被称为脑裂.可能同时会出现两个主机.而主机负责写, 这种情况下, 可能会发生两台机器上都改动到了同一个key, 在merge的时候并不能知道以哪个为准.

监控数量

一般监控都会设置奇数个, 因为如果是3台, 过半是2, 允许最多一个节点挂掉. 如果是4台, 过半是3, 也是允许最多一个节点挂掉.

3和4允许挂掉的最大节点的相同的, 但是3台的成本低于4台. 而且4台的机器数量多了, 会更容易出现故障.

如何设置主从结构

命令的方式:

  • 启动多个redis

  • slaveof命令, 在5.0以后换成了replicaof.

  • 从机连接到主机后, 主机会将现有数据打成一个RDB文件, 发送给从机, 从机接收到这个RDB后, 将原本自己的数据删除, 再load从主机接收到的文件.

  • 从机连接主机后, 主机中写入的会自动同步到从机, 而且从机变成只读模式, 不可以写入数据.

  • 如果从机掉线, 再重新连回的时候, 如果已经启动再使用命令追随,会触发一次RDB. 如果启动时就设置追随, 且RDB文件没有发生改变, 就不会发生RDB.

  • 但是如果开启AOF, 无论是如何追随, 都是全量同步, 因为从机AOF生成的dump.rdb中没有rdb号.

  • 如果主机挂了, 想要把其中一台从机变成主机, 只需要在从机上执行replicaof no one.

  • 让其他从机追随新的主机即可.

以上是人工的设置redis主从的操作, 但是人为的每次都手动去设置太麻烦, 所以也可以将上述过程写到配置文件中.

  • 在配置文件中修改replicaof <masterip> <masterport>

  • replica-server-stale-data yes 是当主机和从机复制数据的时候, 从开始复制到复制完成有一段时间, 这段时间内是否还继续提供数据的读取.

  • replica-read-only yes从机是否是只读模式

  • repl-diskless-sync no 主机和从机中间复制,是先把RDB写到磁盘上再通过网络发送过去, 还是直接通过网络发送过去.

  • repl-backlog-size 1mb增量复制, 如果从机短暂下线, 回来后想要获取主机最新的数据, 可以根据这个配置, 根据RDB文件中的号和一个偏移量增量的去同步新数据. 如果增量超过这个值, 就不再增量的同步, 而是全量同步.

但是配置文件还是没法解决主机挂掉的问题, 当主机挂掉的时候还是需要人工的去解决这个问题.

Sentinel 哨兵

Redis 的 Sentinel 文档

Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:

  • 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

启动哨兵

redis自己就可以做为哨兵, redis-sentinel是一个指向redis-server的链接. 所以理论上一台redis可以既存储数据又作为哨兵.

但是我们一般不这么做, 哨兵就只做哨兵的工作.

哨兵需要一个配置文件, 配置文件中最少包含两条信息

port 26379 # 哨兵开在哪个端口
sentinel monitor mymaster 127.0.0.1 6379 2 # 哨兵监控哪个主机, ip和端口是哪里, 选举几票过半

配置好哨兵的配置信息后,redis-server ./26379.conf --sentinel就可以开启哨兵.

哨兵启动后会自动连接主机, 而且通过主机可以发现追随主机的从机.

可以启动多个哨兵, 多个哨兵之间也可以互相发现.

哨兵之间互相发现是使用了PUB/SUB的机制, 多个哨兵通过在master上进行沟通, 发现其他哨兵.

当主机挂掉之后, 哨兵不会立即开始选举新的节点, 因为网络通信会有延迟, 可能主机仅仅只是网络阻塞.

当主机失联时间超过配置文件中配置的阈值时, 哨兵开始选举新的主机, 并会将所有从机都设置跟随新的主机.

并且哨兵会修改自己的配置文件, 会将新的主机的地址写到配置文件中.

容量问题

上面解决的是单机挂了的问题, 但是我们提出了三个问题.

这里讨论容量问题, 因为在x轴扩展都是镜像复制, 容量该多大还是多大, 10G的空间只能装固定条数的 , 30G空间开3台redis依然只能装这么多条数据.

要解决容量问题, 可以在客户端进行业务逻辑拆分. 将数据按照业务拆分到不同的redis中, 某一部分的业务就去特定的redis中存取.

但是有的时候业务就是拆不开, 数据总数就是很大.

这时候可以按照一些算法拆分.这时候就是对数据的分片.

  • modula(hash+取模)

    比如将数据取hash并取模,存入不同的redis中.

    但是hash这种方法有一个天生的弊端, 取模的数必须是固定的. %3和%4的值是不一样的.

    比如最开始起了3台redis, 我们希望将同种的数据尽量的向同一台redis中放, 比如 k1 第一次进入了1号机器, 下次来的时候我们希望他还是去1号机器中取. 随着公司业务的扩展, 可能3台redis不够用了, 又添加了两台. 现在是5台redis, 这时 k1 再来请求就不一定还是去1号机器了.

    hash+取模这种方式会影响分布式下的扩展性.

  • random

    这时候就是将数据随机的分发到不同的redis中, 这时候客户端也不知道去哪里取.

    一般这种场景是用在消息队列上.

  • 一致性hash算法

    一致性hash算法会将data和node都加入计算. 一般规划成一个环形, 哈希环.

    将node按照hash算法获得一个值, 指向环上一个点. 数据也通过hash算法获得一个值, 指向环上一个点.

    数据只要找到他前面一个最近的点, 就是他应该存放的node.

    这样, 当新加一个节点的时候, 只有新加入的节点hash后面的一部分数据会被影响, 而其他大部分数据都不会受到影响.

    优点是加节点不会造成数据的大规模重hash, 而且确实可以解决数据分布的问题.

    缺点是新增节点会造成一小部分数据不能命中.

    可能会造成缓存中没有, 请求会压到数据库,造成缓存的击穿.

    解决方案可能会增加复杂度. 如果正常取时没有取到想要的值, 就再往前取一个节点.

    所以这种方案更适合作缓存用, 而不是数据库.

    虚拟节点: 可以使一个节点拼上一个固定的值得出不同的在hash环上的位置, 这样可能会对数据倾斜的问题有所改善.

数据分治带来的问题

聚合操作很难实现, 事务很难实现.

redis作者将这个问题抛给用户, 计算向数据移动.

分区:怎样将数据分布到多个redis实例

Redis 集群教程

redis集群代理

实际使用中, 一个server不可能只有一个client连接. 有可能有十个,几十个的client连接. 这就会给服务器带来很大的压力.

网络连接的成本是很高的, 这时可以使用代理来减轻服务器压力. 并且上面的计算可以放在代理层做. 这样保证了客户端的简单透明.

Twemproxy

predis

redis集群

如果最开始只有两个redis, 但是做取模的时候预先留好扩展的位置, 比如取模10.

这样的话, 可以使用一个映射表, 将0,1,2,3,4映射到redis1, 将5,6,7,8,9映射到redis2.

如果将来要新扩展一台机器, 可以将3,4,8,9交给新增的机器. 这样只要将redis1中的3,4和redis2中的8,9放到redis3中即可.

Redis的实现是, 客户端可以随意连接一台redis, 被连接的redis收到后先做取模运算, 算出值, 并通过内部的映射表告诉客户端实际应该连哪台redis, 将客户端重定向到应该连接的那个redis中.

这要求每个服务器都要实现取模运算, 都要维护其他redis的取模值映射表.

要开启redis集群, 在redis源码中提供了脚本供我们学习.在redis/utils/create-cluster中.

To create a cluster, follow these steps:

  1. Edit create-cluster and change the start / end port, depending on the
    number of instances you want to create.
  2. Use “./create-cluster start” in order to run the instances.
  3. Use “./create-cluster create” in order to execute redis-cli –cluster create, so that
    an actual Redis cluster will be created. (If you’re accessing your setup via a local container, ensure that the CLUSTER_HOST value is changed to your local IP)
  4. Now you are ready to play with the cluster. AOF files and logs for each instances are created in the current directory.

In order to stop a cluster:

  1. Use “./create-cluster stop” to stop all the instances. After you stopped the instances you can use “./create-cluster start” to restart them if you change your mind.
  2. Use “./create-cluster clean” to remove all the AOF / log files to restart with a clean environment.
  1. 修改create-cluster脚本.

    在脚本文件中, NODES=6的意思是起多少台redis实例,REPLICAS=1的意思是每个主机有多少从机.

    上面这个配置的意思是总共6台redis, 每个主机有1台从机, 即有三个主机组成的集群.

  2. ./create-cluster start启动脚本中配置的NODES台redis.

  3. ./create-cluster create脚本会提示每个master所占的槽位, 从机和主机的对应,

  4. 输入yes确定.

这样就启动了redis集群.

试试连接 redis-cli -p 30001

成功连上, 试着set k1 hello.

提示 (error) MOVED 12706 127.0.0.1:30003

和我们前面说的一样, redis集群计算出这个key应该在哪台机器上, 使客户端重定向到相应的机器.

但是普通客户端只能收到这个error提示, 不能自动的重定向.

使用redis-cli -c -p 30001, 这时再尝试set key.

现在会返回key应该去哪个槽, 已经重定向到了对应redis, 并且返回操作成功的OK.

这是通过脚本启动的, 脚本只能在本机上启动这么多redis. 我们想要实际上在多台机器上启动redis.

redis-cli --cluster help中可以查看如何创建分布式集群.

标签

但是我们前面说过, 集群的问题是不能使用聚合和事务.

因此我们可以使用标签.

比如, k1会发送到redis1, k2会发送到redis3.

在key前面加上 {xx} 用花括号给key打一个标签, 这时同样标签的数据都会发送到同一个节点上.

对于同一个标签, 可以进行聚合和事务操作.

如果不使用标签, 直接在redis1上开启事务, 当set k1时, k1进入QUEUE. set k2时会跳到redis3. 这时EXEC会提示没有事务, 因为事务开在redis1上, 而现在已经跳到redis3, 所以会出现这种情况.

缓存常见问题

击穿

如果给key设置了过期时间, 当过期时间到了以后可能这个key就会失效.

或者设置了LRU, LFU, 将较冷的数据置换出去.

如果这个key刚好失效, 此时刚好来了一大批流量请求这个可以. 这些流量就会打到数据库上.

这时想要规避这个问题的重点就是如何防止大量请求直接连数据库.

可以使用锁机制, 只让获取到锁的请求去数据库拿数据.

涉及到锁机制, 就要考虑死锁的问题. 如果使用setnx方式去获取锁, 当一个请求获取到锁后, 如果这个请求突然挂了, 没有正确的释放锁. 这时其他所有的请求都无法获取到锁, 这就造成了死锁.

可以通过给锁的key加上过期时间来使死锁变活, 如果获取锁的线程挂了没有正确释放锁, 在key过期后就会自动释放.

那么这个过期时间设置多久呢? 如果设置10s过期, 在1s的时候请求就挂了, 其他所有的请求都要等待9s才可以获取锁.

如果设置1s过期, 实际请求还没挂的时候1s就已经到了, 可能数据库产生拥塞, 还没做完就释放锁.

可以开多个线程完成取数据的操作, 一个线程去数据库取数据, 一个线程每隔一段时间去维护锁时间.

穿透

冲业务接收查询的是系统根本不存在的数据, 这样的话无论请求多少次, 有多少个请求, 都无法在缓存中查到.也无法从数据库中查到.

白白消耗服务器的资源做查询.

这种问题可以使用布隆过滤器来解决.

雪崩

雪崩是大量的key同时失效. 此时大量的请求请求失效的不同key.

造成请求数据库数量过大.

解决方案是随机过期时间.

如果必须是到某一点时刻同时过期, 可以像击穿一样引入锁机制. 或者确定这个时间点, 在这个时间点附近的请求都可以睡眠随机时间. 以减缓流量压到redis或数据库的时间.

redis做分布式锁的问题

redis做分布式锁, 使用setnx来实现cas算法, 加上过期时间来保证不会发生死锁, 使用守护线程延长过期时间防止过期时间不足.


   转载规则


《Redis(三)》 echi1995 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Zookeeper Zookeeper
ZookeeperZookeeper做分布式协调服务. 可以看到, Zookeeper和redis的主从复制很像, 也是有一主多从的. 那么Zookeeper一定也有redis一样的问题, 如果主机挂了怎么办. 是不是也要引入哨兵模式来实
下一篇 
Redis(二) Redis(二)
Redis(二)管道可以使用nc连接redis, 只要连接到redis对应的端口, 比如6379, 就可以使用redis中的命令. 通过echo将多个命令组合起来, echo可以识别\n, 使用\n将多条命令拼起来, 在一次网络请求发送过去
  目录