{"action":"closed","issue":{"url":"https://api.github.com/repos/zshuangyan/articles/issues/2","repository_url":"https://api.github.com/repos/zshuangyan/articles","labels_url":"https://api.github.com/repos/zshuangyan/articles/issues/2/labels{/name}","comments_url":"https://api.github.com/repos/zshuangyan/articles/issues/2/comments","events_url":"https://api.github.com/repos/zshuangyan/articles/issues/2/events","html_url":"https://github.com/zshuangyan/articles/issues/2","id":315758550,"number":2,"title":"Redis HA","user":{"login":"zshuangyan","id":3966367,"avatar_url":"https://avatars1.githubusercontent.com/u/3966367?v=4","gravatar_id":"","url":"https://api.github.com/users/zshuangyan","html_url":"https://github.com/zshuangyan","followers_url":"https://api.github.com/users/zshuangyan/followers","following_url":"https://api.github.com/users/zshuangyan/following{/other_user}","gists_url":"https://api.github.com/users/zshuangyan/gists{/gist_id}","starred_url":"https://api.github.com/users/zshuangyan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zshuangyan/subscriptions","organizations_url":"https://api.github.com/users/zshuangyan/orgs","repos_url":"https://api.github.com/users/zshuangyan/repos","events_url":"https://api.github.com/users/zshuangyan/events{/privacy}","received_events_url":"https://api.github.com/users/zshuangyan/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-04-19T07:30:52Z","updated_at":"2018-04-19T07:34:22Z","closed_at":"2018-04-19T07:34:22Z","author_association":"OWNER","body":"### 架构图  \r\n![image](https://user-images.githubusercontent.com/3966367/38977308-99a4ec70-43e6-11e8-8f1b-1c8c85cbf0c7.png)\r\n\r\n\r\n#### 部署细节：  \r\n在k8s上部署了三个service，分别是HAProxy，Redis master, Redis slave。  \r\nHAProxy： HAProxy用来对客户端暴露一个固定的nodeport端口号，通过配置HAProxy的配置文件，使HAProxy检测后端两个Redis节点中处于up状态并且是主节点的节点，并把客户端的流量转发给主节点。为HAProxy这个服务分配了三个pod，以提供高可靠性。  \r\nRedis-Master是默认的主节点，它的容器每次都是以主节点的方式启动\r\nRedis-Slave是默认的从节点，它的容器每次都以从节点的方式启动\r\n\r\n在三台虚拟机（10.202.43.79, 10.202.43.93， 10.202.41.81）上部署了Redis Sentinel。只部署一个哨兵节点，当这个哨兵节点down掉，就无法执行故障转移了；只部署两个哨兵节点也不行，因为Redis执行故障转移的节点要得到大多数节点的投票，当两个哨兵节点的其中一个节点挂掉时，就无法成功选出能够执行故障转移的哨兵节点；因此至少需要三个哨兵节点，在其中一个哨兵节点挂掉的情况下，仍能够正常执行故障转移。哨兵也是有状态的应用，当执行故障转移后，会把新的主节点的ip地址和端口号写入配置文件中，如果所有哨兵都down掉且配置文件丢失的话，那么哨兵就无法跟踪正确的主节点了，这也就是三个哨兵节点没有部署在k8s上面的原因。  \r\n\r\n在k8s上创建Redis应用后，启动两台虚拟机（10.202.43.79，10.202.43.93）上的Redis节点，设置其主节点为K8s上的主节点，配置slave-priority为0，使其不能在哨兵节点执行故障转移的时候被选为主节点；使能备份节点执行aof和rdb两种持久化方式，因为aof可以提供更高的数据可靠性，因此我们可以尽量降低rdb保存的频率以减少对虚拟机的cpu和内存的消耗。基于两台虚拟机上的Redis节点不会down的前提，在任意时刻，至少有一个备份节点同步主节点的数据。k8s上的任意一个Redis节点被重新调度后，容器首先会检查43.79上面的Redis服务是否可达，如果可达，则同步这个Redis实例的aof和rdb文件，否则检查43.93上面的Redis服务是否可达，如果可达，则同步这个Redis上的aof和rdb文件。同步完成后，检测数据库目录下aof文件是否存在，如果存在则执行redis-aof-check命令检查aof文件是否破损，如果破损则执行修复操作，如果修复不成功则删除aof文件。Redis启动时会检查数据库目录中是否有aof文件，如果存在aof文件，那么则使用aof文件恢复数据库，否则才使用rdb文件数据数据库，因此如果aof文件存在破损且无法修复的情况，再执行对rdb文件的检测操作。如果数据库中既不存在aof文件也不存在rdb文件，那么就正好对应了k8s最初创建应用时，备份节点还没有up的情况，redis数据库目录中没有文件可加载，直接启动。\r\n\r\n### 可靠性评估    \r\n#### K8s上的Redis主节点down  \r\n先大概描述一下哨兵节点的故障迁移机制：   \r\n假设我们在哨兵节点中配置的master-down-after-milliseconds 5000，那么在5s内哨兵节点向主节点发送的PING命令都没有得到有效回复，哨兵就会将这个主节点视为主观下线，如果达到指定数量（哨兵配置文件中，每个哨兵配置的数量可能不一样）的哨兵节点认为主节点down，那么该哨兵节点就会将主节点视为客观下线状态，进入主节点客观下线状态的哨兵会发起选举，获得大多数节点投票的哨兵将执行故障转移，故障转移操作的具体过程是：从当前主节点的所有从节点中选取一个符合条件的从节点，对改从节点执行slaveof no one的操作使其提升为主节点，然后向其他从节点和哨兵节点发送更新主节点的ip地址和端口号的操作。\r\n\r\n对应到我们这里就是当K8s上的主节点pod发生故障时，之前连接到该主节点的客户端的连接将会关闭，由于HAProxy发现后端没有主节点，将拒绝客户端的新的读写请求，直到哨兵节点把Redis Slave节点设置为主节点，Redis服务才对外可达。负责故障迁移的哨兵节点把从节点提升为主节点后，会在配置文件中更新主从节点的信息，并且向之前的主节点，两个备份节点和其他两个哨兵节点发送更新主节点的消息，因此两个备份节点将会以新的主节点为主节点。  \r\n\r\n再往回看一下，down掉的主节点可能是Redis-Master的pod，也可能是Redis-Slave的pod。如果是Redis-Master发生了重新调度，那么它获取备份节点的aof和rdb文件，将以主节点的方式启动redis服务，但是由于哨兵节点在检测到该节点up后，就会向该节点发送信息主节点配置的消息，该节点会切换成从节点运行（备注：这里获取备份节点的数据库文件其实是不必要的，但是为了过程简化，针对所有情况都执行了）。如果是Redis-Slave发生了重新调度，它获取备份节点的aof和rdb文件后将自动以从节点的方式启动。   \r\n\r\n#### K8s上的Redis从节点down  \r\n哨兵节点发现从节点down后，不会做任何操作，从节点重新调度，从备份节点中获取aof和rdb文件，如果当时挂掉的从节点是Redis-Master，那么它将以主节点的方式启动，当它up后会被哨兵节点置为从节点，如果当时挂掉的从节点是Redis-Slave，那么它将以自动以从节点的方式启动。  \r\n\r\n#### K8s上HAProxy的某个pod挂掉  \r\n挂掉后，可以执行转发的HAProxy的数量从3个降低到2个，Redis的读写操作不丢失。\r\n\r\n#### K8s上的主节点和从节点同时挂掉  \r\n哨兵节点发现主节点down，执行故障转移操作，但是发现没有可选的节点提升为主节点，故障转移失败。在哨兵检测到主节点不可达的情况下，每隔一段时间就会尝试执行故障转移操作。    \r\n如果当时挂掉的时候主节点是Redis-Master，从节点是Redis-Slave  \r\n情况一：Redis-Slave先起来，同步备份节点的数据后以从节点的方式启动，哨兵在执行故障转移过程中发现这个从节点于是把这个从节点提升为主节点；Redis-Master起来时，同样从备份节点同步数据，然后以主节点的方式启动，但是哨兵节点发现它后会把它置为从节点。  \r\n情况二：Redis-Master先起来，同步备份节点的数据后以主节点的方式启动，哨兵节点发现主节点可达后，不再执行故障转移操作；Redis-Slave起来时，同样从备份节点同步数据，然后以从节点的方式启动。  \r\n\r\n如果当时挂掉的时候主节点是Redis-Slave,从节点是Redis-Master  \r\n情况一：Redis-Slave先起来，同步备份节点的数据后以从节点的方式启动，哨兵节点检测到该节点up但是是从节点的状态后，还是认为主节点处于down的状态；Redis-Master后起来，同步备份节点的数据后以主节点的方式启动，哨兵节点会把它置为从节点，然后哨兵节点在故障转移操作中把该节点升为主节点。  \r\n\r\n情况二：Redis-Master先起来，同步备份节点的数据后以主节点的方式启动，哨兵在故障转移的过程中会选举该节点为主节点，故障转移成功；Redis-Slave起来时，同样从备份节点同步数据，然后以从节点的方式启动。  \r\n\r\n#### 虚拟机上的节点挂掉\r\n基于两台虚拟机上相同服务同时不会都挂掉的前提（在这里假设三台虚拟机上也最多挂一个），可能会出现如下场景：  \r\n1. 挂掉一台虚拟机上的Redis节点；  \r\n2. 挂掉一台虚拟机上的哨兵节点；  \r\n3. 挂掉一个Redis节点和一个哨兵节点；  \r\n\r\n无论哪种情况下，虚拟机上的哨兵功能和备份节点功能都得到保障  \r\n\r\n\r\n\r\n### 虚拟机上的节点恢复机制  \r\n#### Redis节点  \r\n在虚拟机上每隔10分钟执行一个shell脚本，在这个shell脚本中有如下逻辑：检查本地的这个Redis实例是否在线，如果不在线，首先检查它的aof文件是否破损，如果破损则执行恢复操作，若恢复不成功则删除，rdb文件同理，并将其以主节点的方式up（哨兵节点会自动配置改节点为从节点）。在虚拟机上部署监控，当slave节点连续超过30分钟处于down状态，那么将发送邮件通知（目前这一块还待完善）。  \r\n\r\n#### 哨兵节点\r\n在虚拟机上每隔10分钟执行一个shell脚本，在这个shell脚本中有如下逻辑：检测本地的这个Redis实例是否在线，如果不在线，则尝试把它启动起来，由于每个哨兵节点都保存有主节点对应的所有哨兵节点的信息，因此这个哨兵节点up后就又能和之前的哨兵节点进行通信了，并且更新自己的配置。并且同样部署监控，当哨兵节点连续超过30分钟处于down状态，那么将发送邮件通知（目前这一块还待完善）。"}}
{"actor":{"display_login":"zshuangyan"}}