原文链接:https://segmentfault.com/a/1190000004846986
简介
在分布式系统中,最常见的场景就是主备架构。但是如果主机不幸宕机,如何正确的通知客户端当前后端服务器的状况成为一个值得研究的问题。本文描述了一种简单的模型用于解决此问题。背景
以一个分布式的Key-Value数据库为背景。数据库对外提供3个接口
Get(key)
Put(key, value)
Append(key, value)
客户端对数据库的操作请求必须发往主机, 只有当主机不可访问(主机宕机或网络问题)时,备机代替主机,并且再从集群中选一个新的机器作为备机。
问题来了 客户端如何知道当前谁是主机谁是备机?
Get(key)
Put(key, value)
Append(key, value)
客户端对数据库的操作请求必须发往主机, 只有当主机不可访问(主机宕机或网络问题)时,备机代替主机,并且再从集群中选一个新的机器作为备机。
问题来了 客户端如何知道当前谁是主机谁是备机?
目的
为了保证客户端与服务器就谁是主机谁是备机这个问题达成共识。
整体架构
为了解决这个问题,我们在Server和Clint中间加入一个ViewServer。viewserver的作用相当于一个中介。举个例子,当客户端想要执行Put操作时,需要先向viewserver询问当前的Primary是谁,随后客户端根据viewserver的回复将Put请求发到相应的server。
View
viewserver返回给客户端的信息需要包含至少3条信息:
1. 当前主机是谁
2. 当前备机是谁
3. 当前状态的版本号
我们将这3个信息称为viewserver的当前View。
1. 当前主机是谁
2. 当前备机是谁
3. 当前状态的版本号
我们将这3个信息称为viewserver的当前View。
typedef struct View {
Viewnum int
Primary string
Backup string
}
Server
在多个server中,只有一个主机,一个备机,其余的server都处于空闲状态。所有的server都需要每隔一段时间向viewserver发送Ping消息,以证明自己还活着。并且通过Ping的返回值,得到当前viewserver认为的主机和备机是谁。这样,如果主机和备机都正常,在一段时间之后,后端server都会有一致的主机和备机。Client
在操作后端server之前,先讯问viewserver当前主机是谁.
ViewServer
viewserver的功能比较复杂,主要负责:
- 回复客户端当前View
- 检测后端的server的存活情况
- 保证当前主机得到最新的View
在检测后端server存活情况时,有几种情况
- 主机Primary失联
- 备机Backup失联
- 空闲机器失联
当检测到主机或者备机失联时,viewserver应该对当前View做调整。如果主机失联,则把备机选做主机,然后从空间机器中挑选一个作为备机。如果备机失联,只从空闲机器中选择一个作为备机即可。但是,这两种情况都需要对View的版本号进行增加。这个View版本号只有后端server关心,客户端只关心当前主机。加入这个版本号的目的,主要是为了确保当前主机得到了最新的View。比如备机的更换会需要主机向备机做数据拷贝,如果主机得不到这个消息,整个主备系统就失效了。
上图描述了两台server与viewserver之间的通信.
每次server端Ping需要包含一个参数,用于表示当前这个server所了解到了View版本号.
1. 在初始状态,server1向viewserver发送Ping,并且用0做为参数.由于初始状态viewserver还没有选择主机和备机,所以先到先得,选择server1为主机.于是返回给server1的View为[server1, NULL, 1], 表示当前主机为server1, 备机为NULL, 望的到的版本号为1(希望一段时间后当前的主机Primary以这个新版本号发起Ping).
2. server2此时加入了集群,向viewserver发送了Ping. 由于server2与server1没有联系,所以Ping的参数还是用0,表示这是新加入的机器.虽然此时viewserver发现又有了一台机器加入,并且当前只有主机,没有备机,但是还不能选择server2做为备机.因为server1还没有用1做为参数发起Ping, 说明viewserver当前的这个View(主机为server1,备机为NULL)可能还没有被当前主机(server1)收到.因此返回给server2的View为[server1, NULL, 1].
3. server1顺利收到viewserver返回的View,得知viewserver想要版本号为1的Ping, 于是隔一个PingInerval再向viewserver发起Ping,同时以1为参数. viewserver顺利收到这个Ping, 发现正是自己希望得到的(当前主机以新版本号发起的Ping), 并且了解到一个事实:当前主机server1已经知道了当前最新的主备情况.于是viewserver将View的版本号更新.返回给server1[server1, NULL, 2].
4. server2在一个PingInterval后再次向viewser发起Ping, 因为之前一次Ping返回的ViewNum为1, 因此用1为参数.当viewserver顺利收到Ping时,由于viewserver知道当前主机server1以经获得了它目前保存的这个状态,因此将server2选为备机, 构成了一个新的状态(主机为server1, 备机为server2), 这个状态还没有任何server知道, 当前的主机server1显然也不知道.因此ViewNum不能更新.于是返回[server1, server2, 2]给server2.
5. server1向viewserver发起Ping(2), viewserver通过这个Ping得知当前主机server1以经获得了自己最新的状态.然后server1失联.
6. server2向viewserver发起Ping(2)
在长期没有的到server1的Ping后,viewserver会认为server1以经不能继续对外提供服务了,于是选择备机server2做为主机,如果有其他空闲机器,可以从中选一个做为新的备机.viewserver之所以选择server2做为备机是因为确定server2可以万全代替以经失联的server1, 因为server2做为备机这个信息server1以经收到了,确保server2的状态与server1相同是server1的工作,比如主机会向备机发送数据做数据同步.
每次server端Ping需要包含一个参数,用于表示当前这个server所了解到了View版本号.
1. 在初始状态,server1向viewserver发送Ping,并且用0做为参数.由于初始状态viewserver还没有选择主机和备机,所以先到先得,选择server1为主机.于是返回给server1的View为[server1, NULL, 1], 表示当前主机为server1, 备机为NULL, 望的到的版本号为1(希望一段时间后当前的主机Primary以这个新版本号发起Ping).
2. server2此时加入了集群,向viewserver发送了Ping. 由于server2与server1没有联系,所以Ping的参数还是用0,表示这是新加入的机器.虽然此时viewserver发现又有了一台机器加入,并且当前只有主机,没有备机,但是还不能选择server2做为备机.因为server1还没有用1做为参数发起Ping, 说明viewserver当前的这个View(主机为server1,备机为NULL)可能还没有被当前主机(server1)收到.因此返回给server2的View为[server1, NULL, 1].
3. server1顺利收到viewserver返回的View,得知viewserver想要版本号为1的Ping, 于是隔一个PingInerval再向viewserver发起Ping,同时以1为参数. viewserver顺利收到这个Ping, 发现正是自己希望得到的(当前主机以新版本号发起的Ping), 并且了解到一个事实:当前主机server1已经知道了当前最新的主备情况.于是viewserver将View的版本号更新.返回给server1[server1, NULL, 2].
4. server2在一个PingInterval后再次向viewser发起Ping, 因为之前一次Ping返回的ViewNum为1, 因此用1为参数.当viewserver顺利收到Ping时,由于viewserver知道当前主机server1以经获得了它目前保存的这个状态,因此将server2选为备机, 构成了一个新的状态(主机为server1, 备机为server2), 这个状态还没有任何server知道, 当前的主机server1显然也不知道.因此ViewNum不能更新.于是返回[server1, server2, 2]给server2.
5. server1向viewserver发起Ping(2), viewserver通过这个Ping得知当前主机server1以经获得了自己最新的状态.然后server1失联.
6. server2向viewserver发起Ping(2)
在长期没有的到server1的Ping后,viewserver会认为server1以经不能继续对外提供服务了,于是选择备机server2做为主机,如果有其他空闲机器,可以从中选一个做为新的备机.viewserver之所以选择server2做为备机是因为确定server2可以万全代替以经失联的server1, 因为server2做为备机这个信息server1以经收到了,确保server2的状态与server1相同是server1的工作,比如主机会向备机发送数据做数据同步.